diff --git a/cmd/security-proxy-setup/entrypoint.sh b/cmd/security-proxy-setup/entrypoint.sh index 76f81fcf59..ce69fde2ed 100644 --- a/cmd/security-proxy-setup/entrypoint.sh +++ b/cmd/security-proxy-setup/entrypoint.sh @@ -57,7 +57,7 @@ fi : ${EDGEX_SERVICE_CORSCONFIGURATION_CORSMAXAGE:=`yq -r .all-services.Service.CORSConfiguration.CORSMaxAge /edgex/res/common_configuration.yaml`} echo "$(date) CORS settings dump ..." -( env | grep EDGEX_SERVICE_CORSCONFIGURATION ) || true +( set | grep EDGEX_SERVICE_CORSCONFIGURATION ) || true corssnippet=/etc/nginx/templates/cors.block.$$ touch "${corssnippet}" diff --git a/snap/local/helper-go/configure.go b/snap/local/helper-go/configure.go index e3a87b94cd..1ff8b3bd10 100644 --- a/snap/local/helper-go/configure.go +++ b/snap/local/helper-go/configure.go @@ -146,6 +146,7 @@ func configure() { supportScheduler, securitySecretStoreSetup, securityBootstrapper, // local executable + securityBootstrapperNginx, securityProxyAuth, ) if err != nil { diff --git a/snap/local/runtime-helpers/bin/security-bootstrapper-nginx b/snap/local/runtime-helpers/bin/security-bootstrapper-nginx new file mode 100755 index 0000000000..0d2005d087 --- /dev/null +++ b/snap/local/runtime-helpers/bin/security-bootstrapper-nginx @@ -0,0 +1,254 @@ +#!/bin/bash + +keyfile=nginx.key +certfile=nginx.crt + +# Check for default TLS certificate for reverse proxy, create if missing +# Normally we would run the below command in the nginx container itself, +# but nginx:alpine-slim does not container openssl, thus run it here instead. +mkdir -p "${SNAP_DATA}/nginx" +cd "${SNAP_DATA}/nginx" +if test ! -f "${keyfile}" ; then + # (NGINX will restart in a failure loop until a TLS key exists) + # Create default TLS certificate with 1 day expiry -- user must replace in production (do this as nginx user) + openssl req -x509 -nodes -days 1 -newkey ec -pkeyopt ec_paramgen_curve:secp384r1 -subj '/CN=localhost/O=EdgeX Foundry' -keyout "${keyfile}" -out "${certfile}" -addext "keyUsage = digitalSignature, keyCertSign" -addext "extendedKeyUsage = serverAuth" + echo "Default TLS certificate created. Recommend replace with your own." +fi + + +# +# Import CORS configuration from common config +# + +: ${EDGEX_SERVICE_CORSCONFIGURATION_ENABLECORS:=`yq -r .all-services.Service.CORSConfiguration.EnableCORS $SNAP_DATA/config/core-common-config-bootstrapper/res/configuration.yaml`} +: ${EDGEX_SERVICE_CORSCONFIGURATION_CORSALLOWCREDENTIALS:=`yq -r .all-services.Service.CORSConfiguration.CORSAllowCredentials $SNAP_DATA/config/core-common-config-bootstrapper/res/configuration.yaml`} +: ${EDGEX_SERVICE_CORSCONFIGURATION_CORSALLOWEDORIGIN:=`yq -r .all-services.Service.CORSConfiguration.CORSAllowedOrigin $SNAP_DATA/config/core-common-config-bootstrapper/res/configuration.yaml`} +: ${EDGEX_SERVICE_CORSCONFIGURATION_CORSALLOWEDMETHODS:=`yq -r .all-services.Service.CORSConfiguration.CORSAllowedMethods $SNAP_DATA/config/core-common-config-bootstrapper/res/configuration.yaml`} +: ${EDGEX_SERVICE_CORSCONFIGURATION_CORSALLOWEDHEADERS:=`yq -r .all-services.Service.CORSConfiguration.CORSAllowedHeaders $SNAP_DATA/config/core-common-config-bootstrapper/res/configuration.yaml`} +: ${EDGEX_SERVICE_CORSCONFIGURATION_CORSEXPOSEHEADERS:=`yq -r .all-services.Service.CORSConfiguration.CORSExposeHeaders $SNAP_DATA/config/core-common-config-bootstrapper/res/configuration.yaml`} +: ${EDGEX_SERVICE_CORSCONFIGURATION_CORSMAXAGE:=`yq -r .all-services.Service.CORSConfiguration.CORSMaxAge $SNAP_DATA/config/core-common-config-bootstrapper/res/configuration.yaml`} + +echo "$(date) CORS settings dump ..." +( set | grep EDGEX_SERVICE_CORSCONFIGURATION ) || true + +corssnippet=/tmp/cors.block.$$ +touch "${corssnippet}" +if test "${EDGEX_SERVICE_CORSCONFIGURATION_ENABLECORS}" = "true"; then + echo " if (\$request_method = 'OPTIONS') {" >> "${corssnippet}" + echo " add_header 'Access-Control-Allow-Origin' '${EDGEX_SERVICE_CORSCONFIGURATION_CORSALLOWEDORIGIN}';" >> "${corssnippet}" + echo " add_header 'Access-Control-Allow-Methods' '${EDGEX_SERVICE_CORSCONFIGURATION_CORSALLOWEDMETHODS}';" >> "${corssnippet}" + echo " add_header 'Access-Control-Allow-Headers' '${EDGEX_SERVICE_CORSCONFIGURATION_CORSALLOWEDHEADERS}';" >> "${corssnippet}" + # Access-Control-Expose-Headers should not be set on OPTIONS request + if test "${EDGEX_SERVICE_CORSCONFIGURATION_CORSALLOWCREDENTIALS}" = "true"; then + # CORS specificaiton says that if not true, omit the header entirely + echo " add_header 'Access-Control-Allow-Credentials' '${EDGEX_SERVICE_CORSCONFIGURATION_CORSALLOWCREDENTIALS}';" >> "${corssnippet}" + fi + echo " add_header 'Access-Control-Max-Age' ${EDGEX_SERVICE_CORSCONFIGURATION_CORSMAXAGE};" >> "${corssnippet}" + echo " add_header 'Vary' 'origin';" >> "${corssnippet}" + echo " add_header 'Content-Type' 'text/plain; charset=utf-8';" >> "${corssnippet}" + echo " add_header 'Content-Length' 0;" >> "${corssnippet}" + echo " return 204;" >> "${corssnippet}" + echo " }" >> "${corssnippet}" + echo " if (\$request_method != 'OPTIONS') {" >> "${corssnippet}" + # Always add headers regardless of response code. Omit preflight-related headers (allow-methods, allow-headers, allow-credentials, max-age) + echo " add_header 'Access-Control-Allow-Origin' '${EDGEX_SERVICE_CORSCONFIGURATION_CORSALLOWEDORIGIN}' always;" >> "${corssnippet}" + echo " add_header 'Access-Control-Expose-Headers' '${EDGEX_SERVICE_CORSCONFIGURATION_CORSEXPOSEHEADERS}' always;" >> "${corssnippet}" + echo " add_header 'Vary' 'origin' always;" >> "${corssnippet}" + echo " }" >> "${corssnippet}" + echo "" >> "${corssnippet}" +fi + +# +# Generate NGINX configuration based on EDGEX_ADD_PROXY_ROUTE and standard settings +# + +echo "$(date) Generating default NGINX config ..." + +IFS=', ' +for service in ${EDGEX_ADD_PROXY_ROUTE}; do + prefix=$(echo -n "${service}" | sed -n -e 's/\([-0-9a-zA-Z]*\)\..*/\1/p') + host=$(echo -n "${service}" | sed -n -e 's/.*\/\/\([-0-9a-zA-Z]*\):.*/\1/p') + port=$(echo -n "${service}" | sed -n -e 's/.*:\(\d*\)/\1/p') + varname=$(echo -n "${prefix}" | tr '-' '_') + echo $service $prefix $host $port + cat <> "${SNAP_DATA}/nginx/conf.d/generated-routes.inc" + +set \$upstream_$varname $host; +location /$prefix { +`cat "${corssnippet}"` + rewrite /$prefix/(.*) /\$1 break; + resolver 127.0.0.11 valid=30s; + proxy_pass http://\$upstream_$varname:$port; + proxy_redirect off; + proxy_set_header Host \$host; + auth_request /auth; + auth_request_set \$auth_status \$upstream_status; +} +EOH + +done +unset IFS + + +# This file can be modified by the user; deleted when docker volumes are pruned; +# but preserved across start/up and stop/down actions +if test -f "${SNAP_DATA}/nginx/conf.d/edgex-custom-rewrites.inc"; then + echo "Using existing custom-rewrites." +else + cat <<'EOH' > "${SNAP_DATA}/nginx/conf.d/edgex-custom-rewrites.inc" +# Add custom location directives to this file, for example: + +# set $upstream_device_virtual edgex-device-virtual; +# location /device-virtual { +# rewrite /device-virtual/(.*) /$1 break; +# resolver 127.0.0.11 valid=30s; +# proxy_pass http://$upstream_device_virtual:59900; +# proxy_redirect off; +# proxy_set_header Host $host; +# auth_request /auth; +# auth_request_set $auth_status $upstream_status; +# } +EOH +fi + +cat < "${SNAP_DATA}/nginx/conf.d/edgex-default.conf" +# +# Copyright (C) Intel Corporation 2023 +# SPDX-License-Identifier: Apache-2.0 +# + +# generated 2023-01-19, Mozilla Guideline v5.6, nginx 1.17.7, OpenSSL 1.1.1k, modern configuration, no HSTS, no OCSP +# https://ssl-config.mozilla.org/#server=nginx&version=1.17.7&config=modern&openssl=1.1.1k&hsts=false&ocsp=false&guideline=5.6 +server { + listen 127.0.0.1:8000; # Snap listen insecure on localhost only + listen 8443 ssl; + + ssl_certificate "/var/snap/edgexfoundry/current/nginx/nginx.crt"; + ssl_certificate_key "/var/snap/edgexfoundry/current/nginx/nginx.key"; + ssl_session_tickets off; + + access_log syslog:server=unix:/dev/log,tag=edgexfoundry; + + # Subrequest authentication + + location /auth { + internal; + proxy_pass http://127.0.0.1:59842; + proxy_redirect off; + proxy_set_header Host \$host; + proxy_set_header Content-Length ""; + proxy_set_header X-Forwarded-URI \$request_uri; + proxy_pass_request_body off; + } + + # Rewriting rules (customized for snaps) + + location /core-data { + `cat "${corssnippet}"` + rewrite /core-data/(.*) /\$1 break; + proxy_pass http://127.0.0.1:59880; + proxy_redirect off; + proxy_set_header Host \$host; + auth_request /auth; + auth_request_set \$auth_status \$upstream_status; + } + + + location /core-metadata { + `cat "${corssnippet}"` + rewrite /core-metadata/(.*) /\$1 break; + proxy_pass http://127.0.0.1:59881; + proxy_redirect off; + proxy_set_header Host \$host; + auth_request /auth; + auth_request_set \$auth_status \$upstream_status; + } + + + location /core-command { + `cat "${corssnippet}"` + rewrite /core-command/(.*) /\$1 break; + proxy_pass http://127.0.0.1:59882; + proxy_redirect off; + proxy_set_header Host \$host; + auth_request /auth; + auth_request_set \$auth_status \$upstream_status; + } + + + location /support-notifications { + `cat "${corssnippet}"` + rewrite /support-notifications/(.*) /\$1 break; + proxy_pass http://127.0.0.1:59860; + proxy_redirect off; + proxy_set_header Host \$host; + auth_request /auth; + auth_request_set \$auth_status \$upstream_status; + } + + + location /support-scheduler { + `cat "${corssnippet}"` + rewrite /support-scheduler/(.*) /\$1 break; + proxy_pass http://127.0.0.1:59861; + proxy_redirect off; + proxy_set_header Host \$host; + auth_request /auth; + auth_request_set \$auth_status \$upstream_status; + } + + location /app-rules-engine { + `cat "${corssnippet}"` + rewrite /app-rules-engine/(.*) /\$1 break; + proxy_pass http://127.0.0.1:59701; + proxy_redirect off; + proxy_set_header Host \$host; + auth_request /auth; + auth_request_set \$auth_status \$upstream_status; + } + + location /rules-engine { + `cat "${corssnippet}"` + rewrite /rules-engine/(.*) /\$1 break; + proxy_pass http://127.0.0.1:59720; + proxy_redirect off; + proxy_set_header Host \$host; + auth_request /auth; + auth_request_set \$auth_status \$upstream_status; + } + + # Note: Consul implements its own authentication mechanism (only allow API, /v1, through) + location /consul/v1 { + `cat "${corssnippet}"` + rewrite /consul/(.*) /\$1 break; + proxy_pass http://127.0.0.1:8500; + proxy_redirect off; + proxy_set_header Host \$host; + } + + # Note: Vault login API does not require authentication at the gateway for obvious reasons + # Expose URLs to log in to vault and to get a JWT + location /vault/v1/auth/userpass/login { + `cat "${corssnippet}"` + rewrite /vault/(.*) /\$1 break; + proxy_pass http://127.0.0.1:8200; + proxy_redirect off; + proxy_set_header Host \$host; + } + location /vault/v1/identity/oidc/token { + `cat "${corssnippet}"` + rewrite /vault/(.*) /\$1 break; + proxy_pass http://127.0.0.1:8200; + proxy_redirect off; + proxy_set_header Host \$host; + } + + include /var/snap/edgexfoundry/current/nginx/conf.d/edgex-custom-rewrites.inc; + +} + +# Don't output NGINX version in Server: header +server_tokens off; +EOH + +rm -f "${corssnippet}" diff --git a/snap/local/runtime-helpers/bin/setup-nginx.sh b/snap/local/runtime-helpers/bin/setup-nginx.sh deleted file mode 100755 index 86e33225f2..0000000000 --- a/snap/local/runtime-helpers/bin/setup-nginx.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -keyfile=nginx.key -certfile=nginx.crt - -# Check for default TLS certificate for reverse proxy, create if missing -# Normally we would run the below command in the nginx container itself, -# but nginx:alpine-slim does not container openssl, thus run it here instead. -mkdir -p "${SNAP_DATA}/nginx" -cd "${SNAP_DATA}/nginx" -if test ! -f "${keyfile}" ; then - # (NGINX will restart in a failure loop until a TLS key exists) - # Create default TLS certificate with 1 day expiry -- user must replace in production (do this as nginx user) - openssl req -x509 -nodes -days 1 -newkey ec -pkeyopt ec_paramgen_curve:secp384r1 -subj '/CN=localhost/O=EdgeX Foundry' -keyout "${keyfile}" -out "${certfile}" -addext "keyUsage = digitalSignature, keyCertSign" -addext "extendedKeyUsage = serverAuth" - echo "Default TLS certificate created. Recommend replace with your own." -fi diff --git a/snap/local/runtime-helpers/config/nginx/conf.d/edgex-default.conf b/snap/local/runtime-helpers/config/nginx/conf.d/edgex-default.conf deleted file mode 100644 index a4a462ea68..0000000000 --- a/snap/local/runtime-helpers/config/nginx/conf.d/edgex-default.conf +++ /dev/null @@ -1,127 +0,0 @@ -# -# Copyright (C) Intel Corporation 2023 -# SPDX-License-Identifier: Apache-2.0 -# - -# generated 2023-01-19, Mozilla Guideline v5.6, nginx 1.17.7, OpenSSL 1.1.1k, modern configuration, no HSTS, no OCSP -# https://ssl-config.mozilla.org/#server=nginx&version=1.17.7&config=modern&openssl=1.1.1k&hsts=false&ocsp=false&guideline=5.6 -server { - listen 127.0.0.1:8000; # Snap listen insecure on localhost only - listen 8443 ssl; - - ssl_certificate "/var/snap/edgexfoundry/current/nginx/nginx.crt"; - ssl_certificate_key "/var/snap/edgexfoundry/current/nginx/nginx.key"; - ssl_session_tickets off; - - access_log syslog:server=unix:/dev/log,tag=edgexfoundry; - - # Subrequest authentication - - location /auth { - internal; - proxy_pass http://127.0.0.1:59842; - proxy_redirect off; - proxy_set_header Host $host; - proxy_set_header Content-Length ""; - proxy_set_header X-Forwarded-URI $request_uri; - proxy_pass_request_body off; - } - - # Rewriting rules (customized for snaps) - - location /core-data { - rewrite /core-data/(.*) /$1 break; - proxy_pass http://127.0.0.1:59880; - proxy_redirect off; - proxy_set_header Host $host; - auth_request /auth; - auth_request_set $auth_status $upstream_status; - } - - - location /core-metadata { - rewrite /core-metadata/(.*) /$1 break; - proxy_pass http://127.0.0.1:59881; - proxy_redirect off; - proxy_set_header Host $host; - auth_request /auth; - auth_request_set $auth_status $upstream_status; - } - - - location /core-command { - rewrite /core-command/(.*) /$1 break; - proxy_pass http://127.0.0.1:59882; - proxy_redirect off; - proxy_set_header Host $host; - auth_request /auth; - auth_request_set $auth_status $upstream_status; - } - - - location /support-notifications { - rewrite /support-notifications/(.*) /$1 break; - proxy_pass http://127.0.0.1:59860; - proxy_redirect off; - proxy_set_header Host $host; - auth_request /auth; - auth_request_set $auth_status $upstream_status; - } - - - location /support-scheduler { - rewrite /support-scheduler/(.*) /$1 break; - proxy_pass http://127.0.0.1:59861; - proxy_redirect off; - proxy_set_header Host $host; - auth_request /auth; - auth_request_set $auth_status $upstream_status; - } - - location /app-rules-engine { - rewrite /app-rules-engine/(.*) /$1 break; - proxy_pass http://127.0.0.1:59701; - proxy_redirect off; - proxy_set_header Host $host; - auth_request /auth; - auth_request_set $auth_status $upstream_status; - } - - location /rules-engine { - rewrite /rules-engine/(.*) /$1 break; - proxy_pass http://127.0.0.1:59720; - proxy_redirect off; - proxy_set_header Host $host; - auth_request /auth; - auth_request_set $auth_status $upstream_status; - } - - # Note: Consul implements its own authentication mechanism (only allow API, /v1, through) - location /consul/v1 { - rewrite /consul/(.*) /$1 break; - proxy_pass http://127.0.0.1:8500; - proxy_redirect off; - proxy_set_header Host $host; - } - - # Note: Vault login API does not require authentication at the gateway for obvious reasons - # Expose URLs to log in to vault and to get a JWT - location /vault/v1/auth/userpass/login { - rewrite /vault/(.*) /$1 break; - proxy_pass http://127.0.0.1:8200; - proxy_redirect off; - proxy_set_header Host $host; - } - location /vault/v1/identity/oidc/token { - rewrite /vault/(.*) /$1 break; - proxy_pass http://127.0.0.1:8200; - proxy_redirect off; - proxy_set_header Host $host; - } - - include /var/snap/edgexfoundry/current/nginx/conf.d/edgex-custom-rewrites.inc; - -} - -# Don't output NGINX version in Server: header -server_tokens off; diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 61e7b418e9..48c855e446 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -119,7 +119,7 @@ apps: security-bootstrapper-nginx: after: - security-secretstore-setup - command: bin/setup-nginx.sh + command: bin/security-bootstrapper-nginx command-chain: - bin/source-env-file.sh daemon: oneshot @@ -252,7 +252,7 @@ apps: install-mode: disable plugs: [network, network-bind] stop-timeout: 10s - # this service pushes common configuration source into Configuration Provider +# this service pushes common configuration source into Configuration Provider core-common-config-bootstrapper: after: - security-bootstrapper-consul @@ -346,6 +346,8 @@ parts: stage-packages: - libssl-dev - zlib1g + stage-snaps: + - yq stage: - -usr/nginx/conf/nginx.conf