Can I secure http IN location and lock down 1880?

I have a flow that exposes a http IN location that I use for a webhook. I would like to allow posts to that location from the public network and at the same time lock down 1880 on the public network so that no one can edit the flows.

I am a little stumped on how to do this.

Look at the Securing Node-RED section of the docs. Securing Node-RED : Node-RED

The adminAuth section is about securing the Flow Editor

The httpNodeAuth section is about securing the http-in nodes

Sorry, I should have been a little more clear. My question is not about authentication. I plan to do that as well.

The cloud service that I use has the ability to set up global firewalls. I would like to have the firewall completely block 1880 on the public network yet have the ability to post to the http IN location from the public network.

The easiest solution is to set up NGINX or similar to act as a reverse proxy. You can then redirect/block any URL.

Yep, a basic port based firewall just won't work. You probably need to add a prefix to the admin API with the httpAdminRoot option then using a reverse proxy to filter all access to that root and allow access to anything else.

(You could use httpNodeRoot pre-pend to the http-in paths to get a similar root to filter on. Both give the same effect)

so far in my researching of Nginx reverse proxies I see many examples of doing it at a port level. So I can make port 1881 public and have it proxy to 1880 for example.

What I would really like to do is have an incoming port (1881) proxy to the webhook (http in) location that I have created in node-red. I cannot figure out how to make this work.

You don't have to do it on a separate port if you don't want to. That's one of the benefits of using a proxy. You could, for example, simply make use of port 443 (since you will need to present your API as https anyway for public use).

Then you are simply mapping from an external URL path to an internal URL path.

If you are running NR on port 1880, no https and your API presents on http://localhost:1880/my-internal-path, your external URL might be https://my.example.com/my-external-path.

So here is a config fragment that illustrates this:

# Proxy just the Node-RED Dashboard https://my.public.domain/red/dash/ to http://localhost:1880/ui/
location  /red/dash/ {

  # ==> Feel free to have different auth here <==

  # A full set of headers have to be redone for every context that defines another header
  include /etc/nginx/conf.d/includes/common_security_headers.conf;

  # Reverse Proxy
  proxy_pass https://localhost:1880/ui/; # <== CHANGE TO MATCH THE EDITOR's URL
  
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

  # Reverse Proxy for websockets
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "upgrade";
}

You also need to define the web server itself:

# Default settings for NGINX
#
# See http://tautt.com/best-nginx-configuration-for-security/

# don't send the nginx version number in error pages and Server header
server_tokens off;

# Create a new var. if https is being used
map $https  $isHttps {
  "on"     true;
  default  '';
}
# Set Strict Transport Security (HSTS) if https in use - see security_headers.hdr_conf
map $https  $sts {
  "on"     "max-age=15768000; includeSubDomains preload";
  default  '';
}

# A full set of headers have to be redone for every context - only if another header is set
include /etc/nginx/conf.d/includes/common_security_headers.conf;

# Default HTTP Server - Redirect all http traffic to https
server {
  listen *:80 default_server;
  #listen [::]:80 default_server;

  # If you want to limit this config to specific (sub)domain names:
  server_name .knightnet.co.uk;

  # Shouldn't be needed but just in case
  location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
  }
  error_page  404  /index.html;
  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
      root   /usr/share/nginx/html;
  }

  # Permanent redirect
  return 301 https://$host$request_uri;
}

# Default HTTPS server - only allow TLS - include Node-RED and other locations
server {
	# Ports to listen on. Remove insecure ports if not needed.
	listen *:443 ssl default_server;
    #listen 1880 ssl http2;
	#listen [::]:443 ssl http2 default_server;

  # If you want to limit this config to specific (sub)domain names:
  #server_name .my.example.com;

  # What to serve if no html file name provided
  index index.html index.htm;

  # A full set of headers have to be redone for every context that defines another header
  #include /etc/nginx/conf.d/includes/common_security_headers.conf;

  # TLS:
    # Specify the public cert and private key - need fullchain for max security
    ssl_certificate     /servers/path/to/fullchain.cer;
    ssl_certificate_key /servers/path/to/my-private.key;
    # Require safe TLS protocols only
    ssl_protocols TLSv1.2 TLSv1.3;
    # Only use secure encryption ciphers
    #ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
    ssl_ciphers EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH;
    ssl_prefer_server_ciphers On;
    # Configure for Strict Transport Security (HSTS) - set conditionally in security_headers.hdr_conf
    #add_header Strict-Transport-Security "max-age=15768000; includeSubDomains preload" always;

    # enable session resumption to improve https performance http://vincent.bernat.im/en/blog/2011-ssl-session-reuse-rfc5077.html
    ssl_session_cache shared:SSL:128m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits. To generate your dhparam.pem file, run in the terminal
    #     openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048
    #ssl_dhparam /etc/nginx/ssl/dhparam.pem;

    # enable ocsp stapling http://blog.mozilla.org/security/2013/07/29/ocsp-stapling-in-firefox/
      # Local router DNS resolver first followed by
      # Cloudflare resolver 1dot1dot1dot1.cloudflare-dns.com as this is fast and secure
      resolver 192.168.1.1 1.1.1.1 1.0.0.1  ipv6=off; # [2606:4700:4700::1111] [2606:4700:4700::1001];
      ssl_stapling on;
      ssl_stapling_verify on;
      # trusted cert not required if using fullchain above
      #ssl_trusted_certificate /etc/nginx/ssl/star_forgott_com.crt;
  # End of TLS

  # Authorisation example: EITHER local network (not router) OR basic auth
    satisfy any;

    deny  192.168.1.1;
    allow 192.168.1.0/24;
    deny  all;

    # To set passwords: `sudo apt install apache2-utils` then `sudo htpasswd -c /etc/nginx/.htpasswd somename`
    auth_basic           "Home";
    auth_basic_user_file /etc/nginx/.htpasswd;
  # End of authorisation
  
  # If you want to log access for this server separate to the main log at /var/log/nginx/access.log
  # access_log  /var/log/nginx/host.access.log  main;

  ## Help prevent buffer overflow attacks
    client_body_buffer_size 1K;
    client_header_buffer_size 1k;
    client_max_body_size 1k;
    large_client_header_buffers 2 1k;

  # Default root folder - if all else fails, look here for common content
  root   /usr/share/nginx/html;
  # Redirect missing pages to default static home page
  error_page  404  /index.html;
  # redirect server error pages to the static page /50x.html
  error_page   500 502 503 504  /50x.html;

  # Configuration for Node-RED
  include /etc/nginx/conf.d/includes/red.conf;

  # Test URL's for generating errors
  location = /err500 {
    return 500;
  }
  location = /err404 {
    return 404;
  }

} # --- End of default HTTPS server --- #

@TotallyInformation My humble apologies. This thread went toward a Nginx path but I actually use Caddy. I switched quite a while back. I was searching Nginx because I could not find a straight forward set of info for Caddy and node-red. IN additional searching I noticed you replied in another Caddy based thread

Nice. Let us know how you get on. I've actually just flipped to Caddy which seems nicer to configure and has built-in Let's Encrypt if you want it. It also configures http/2 right out of the box without any additional config.

I've also worked out how to block external access to the Editor but leave the Dashboard open and even to be able to have the Dashboard and the Editor on different ports.

It sounds like exactly what I am looking for. Would yo mind sharing that code snippet?

Actually, I did switch for a while but then I started to realise that, for what I was doing, NGINX had better support and was more widely used which is why I've written a more comprehensive security guide into uibuilder that references NGINX.

Let me have a look to see where I got to with Caddy though. It won't be as complete as the NGINX config though.


And here is what I have:

# JK's Caddyfile
#
# https://caddyserver.com/docs/caddyfile/concepts

# Global Settings
{
	# Caddy is great for HTTPS but we don't always want it. 'off' or 'disable_redirects'
	auto_https off

	# Only use local certs not Let's Encyrpt (We already have LE configured via acme.sh)
	local_certs
}

# Snippets - use as `import redirect`
(redirect) {
	# Redirect http to https - not needed if using auto_https
	@http {
		protocol http
	}
	redir @http https://{host}{uri}
}
(tlsconfig) {
	# Manual TLS configuration - not needed for aut_https
	tls /home/auser/.acme.sh/example.com/fullchain.cer /home/auser/.acme.sh/example.com/example.com.key
	import redirect
}
(internalnet) {
	# Define the internal network (LAN/WiFi) as a named matcher
	@internal {
		# Non-routable IP addresses - @see https://www.arin.net/reference/research/statistics/address_filters
		remote_ip 192.168.0.0/16
		#remote_ip 10.0.0.0/8
		#remote_ip 172.16.0.0/12
	}
}

# Port http/80 - this won't be overridden by auto_https
home.example.com:80 {
	#import redirect
	# Set this path to your site's directory.
	#root * /usr/share/caddy
	# Enable the static file server.
	#file_server
	# Compression (zstd or gzip)
	#encode zstd
}

# uibuilder
home.example.com:443 {
	import internalnet
	import tlsconfig
	handle @internal {
		# Proxy Cockpit to a path but only for internal users
		#reverse_proxy /config/* http://localhost:9090
	}
	# handle config.example.com:4000 {
	#  	redir https://home.example.com{uri}
	# }
	#path /ui
	redir / /home
	# Proxy everything on :1880 to port :2021
	reverse_proxy localhost:4000
	# Compression (zstd or gzip)
	#encode zstd
}

# Metrics
home.example.com:2015 {
	metrics /metrics
	import tlsconfig
}

# Test using manual TLS certs
home.example.com:2016 {
	respond "Goodbye, world!"
	import tlsconfig
}

# Grafana charts/dashboards
charts.example.com, dash.example.com {
	import tlsconfig
	# Only allow internal access
	import internalnet
	handle @internal {
		# Proxy everything on :3000 (Graphana) to port :2018
		reverse_proxy https://home.example.com:3000
		# Compression (zstd or gzip)
		#encode zstd
	}
	# Othwise get rid of any remaining requests (not from local network)
	respond "Access to this resource is forbidden from external networks." 403
	#redir * http://example.com
}

# NOTE: port :2019 is used for the internal config endpoint.

# Proxy Just Node-RED Editor (/red) on :1880 to port :2020 (or leave it on 1880 if you prefer)
# but block access if not on the local network
# also redirect NR's Dashboard
# Don't forget to block external access to port :1880 as well.
config.example.com:80 {
	import redirect
}
config.example.com:443, home.example.com:2020 {
	# Block any requests to :2020 unless from home network
	# @internal {
	# 	remote_ip 192.168.0.0/16
	# 	#remote_ip 10.0.0.0/8
	# }
	#import tlsconfig
	import internalnet
	handle @internal {
		# Block access to Node-RED's Dashboard
		#redir /ui /
		# Or send it to the unproxied (or other correct) location
		#redir /ui http://{host}:1880/ui
		redir /ui http://home.example.com:1880/ui
		#rewrite https://config.example.com:4000* https://home.example.com:4000{uri} 

		# Add the /red prefix to everything (assumes that the Editor has been moved to :1880/red in NR's settings.js)
		rewrite * /red{uri}
		# Now we can proxy everything else
		reverse_proxy http://localhost:1880
	}
	# Othwise get rid of any remaining requests (not from local network)
	respond "Access to this resource is forbidden from external networks." 403
	#redir * http://example.com
}

# Proxy Node-RED from port 1880 to port 2021
home.example.com:2021 {
	# Proxy everything on :1880 to port :2021
	reverse_proxy localhost:1880
	# Compression (zstd or gzip)
	#encode zstd
}

# Proxy cockpit to a sub-domain (requires changes to cockpit.conf)
#cockpit.example.com {
#	import tlsconfig
#	reverse_proxy localhost:9090
#}


# Refer to the Caddy docs for more information:
# https://caddyserver.com/docs/caddyfile

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.