From e5bbdadd6af019319bbca0f9dc923e57653d9339 Mon Sep 17 00:00:00 2001 From: geek Date: Tue, 1 Aug 2017 17:38:20 -0500 Subject: [PATCH] feat: support TLS auth --- docker/api/package.json | 2 +- docker/frontend/Dockerfile | 2 +- docker/frontend/bin/reload-nginx.sh | 23 ++++++- docker/frontend/etc/containerpilot.json5 | 4 +- docker/frontend/etc/nginx.conf.tmpl | 28 +++++++-- local-compose.yml | 10 ++- packages/cp-frontend/src/state/store.js | 8 +-- packages/cp-gql-schema/package.json | 2 +- packages/portal-api/package.json | 4 +- setup.sh | 77 +++++++++++++++++++++++- 10 files changed, 139 insertions(+), 21 deletions(-) diff --git a/docker/api/package.json b/docker/api/package.json index f6bdbc2c..e05991e1 100644 --- a/docker/api/package.json +++ b/docker/api/package.json @@ -21,7 +21,7 @@ "joi": "^10.6.0", "joyent-cp-gql-schema": "^1.2.0", "piloted": "^3.1.1", - "portal-api": "^1.3.2", + "portal-api": "^1.3.3", "toppsy": "^1.1.0", "triton": "^5.2.0" } diff --git a/docker/frontend/Dockerfile b/docker/frontend/Dockerfile index eaa68a3f..0d086bd6 100644 --- a/docker/frontend/Dockerfile +++ b/docker/frontend/Dockerfile @@ -5,7 +5,7 @@ FROM node:8-alpine # Install dependencies RUN set -x \ && apk update \ - && apk add --update curl bash build-base git nginx python \ + && apk add --update curl bash build-base git nginx python openssl \ && apk upgrade \ && rm -rf /var/cache/apk/* diff --git a/docker/frontend/bin/reload-nginx.sh b/docker/frontend/bin/reload-nginx.sh index e5c78814..4b092818 100755 --- a/docker/frontend/bin/reload-nginx.sh +++ b/docker/frontend/bin/reload-nginx.sh @@ -1,8 +1,27 @@ -#!/bin/sh +#!/bin/bash # Render Nginx configuration template using values from Consul, -# but do not reload because Nginx has't started yet +# but do not reload because Nginx has't started yet. +# Install key files for TLS auth in nginx preStart() { + # Copy creds from env vars to files on disk + if [ -n ${!NGINX_CA_CRT} ] \ + && [ -n ${!NGINX_SERVER_KEY} ] \ + && [ -n ${!NGINX_SERVER_CRT} ] + then + local nginx_path=/etc/nginx/certs + mkdir -p $nginx_path + mkdir -p $nginx_path/ca + mkdir -p $nginx_path/server + echo -e "${NGINX_CA_CRT}" | tr '#' '\n' > $nginx_path/ca/ca.crt + echo -e "${NGINX_SERVER_KEY}" | tr '#' '\n' > $nginx_path/server/server.key + echo -e "${NGINX_SERVER_CRT}" | tr '#' '\n' > $nginx_path/server/server.crt + + chmod 444 $nginx_path/ca/ca.crt + chmod 444 $nginx_path/server/server.key + chmod 444 $nginx_path/server/server.crt + fi + consul-template \ -once \ -consul localhost:8500 \ diff --git a/docker/frontend/etc/containerpilot.json5 b/docker/frontend/etc/containerpilot.json5 index e348cae3..46c532b8 100644 --- a/docker/frontend/etc/containerpilot.json5 +++ b/docker/frontend/etc/containerpilot.json5 @@ -37,8 +37,8 @@ once: 'exitSuccess' }, health: { - exec: '/usr/bin/curl -o /dev/null --fail -s http://localhost:{{.PORT}}', - interval: 5, + exec: 'pstree nginx', + interval: 10, ttl: 25 } }, diff --git a/docker/frontend/etc/nginx.conf.tmpl b/docker/frontend/etc/nginx.conf.tmpl index 7b11a355..732e03d8 100644 --- a/docker/frontend/etc/nginx.conf.tmpl +++ b/docker/frontend/etc/nginx.conf.tmpl @@ -25,6 +25,15 @@ events { http { index index.html index.htm; + server { + server_name _; + listen 80; + listen [::]:80; + location / { + rewrite ^ https://$host$request_uri? permanent; + } + } + {{ if service "api" }} upstream api_hosts { {{range service "api"}} @@ -33,10 +42,19 @@ http { }{{ end }} server { - server_name _; - listen {{ env "PORT" }} default_server; - listen [::]:{{ env "PORT" }} default_server; + listen 443 ssl; + listen [::]:443 ssl; root /opt/app/packages/cp-frontend/build; + + ssl_certificate /etc/nginx/certs/server/server.crt; + ssl_certificate_key /etc/nginx/certs/server/server.key; + ssl_client_certificate /etc/nginx/certs/ca/ca.crt; + ssl_verify_client on; + ssl_session_timeout 1d; + + ssl_protocols TLSv1.1 TLSv1.2; + ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK'; + location / { try_files $uri /index.html; } @@ -44,7 +62,9 @@ http { rewrite /api/(.*) /$1 break; proxy_pass http://api_hosts; proxy_redirect off; - proxy_set_header Host $host; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Client-Dn $ssl_client_s_dn; } } diff --git a/local-compose.yml b/local-compose.yml index abf5f271..b2fce420 100644 --- a/local-compose.yml +++ b/local-compose.yml @@ -51,13 +51,17 @@ frontend: mem_limit: 512m links: - consul:consul + env_file: + - _env environment: - CONSUL=consul - - PORT=80 - - REACT_APP_GQL_HOSTNAME=localhost - - REACT_APP_GQL_PORT=80 + - PORT=443 + - REACT_APP_GQL_HOSTNAME=workshop.host + - REACT_APP_GQL_PORT=443 + - REACT_APP_GQL_PROTOCOL=https ports: - "80:80" + - "443:443" dns: - 127.0.0.1 diff --git a/packages/cp-frontend/src/state/store.js b/packages/cp-frontend/src/state/store.js index ab127af2..13d67459 100644 --- a/packages/cp-frontend/src/state/store.js +++ b/packages/cp-frontend/src/state/store.js @@ -13,9 +13,9 @@ const GLOBAL = } }; -const GQL_PORT = process.env.REACT_APP_GQL_PORT || 80; -const GQL_HOSTNAME = - process.env.REACT_APP_GQL_HOSTNAME || GLOBAL.location.hostname; +const GQL_PORT = process.env.REACT_APP_GQL_PORT || 443; +const GQL_HOSTNAME = process.env.REACT_APP_GQL_HOSTNAME || GLOBAL.location.hostname; +const GQL_PROTOCOL = process.env.REACT_APP_GQL_PROTOCOL || 'https'; export const client = new ApolloClient({ dataIdFromObject: o => { @@ -31,7 +31,7 @@ export const client = new ApolloClient({ return `${o.__typename}:${id}`; }, networkInterface: createNetworkInterface({ - uri: `http://${GQL_HOSTNAME}:${GQL_PORT}/api/graphql` + uri: `${GQL_PROTOCOL}://${GQL_HOSTNAME}:${GQL_PORT}/api/graphql` }) }); diff --git a/packages/cp-gql-schema/package.json b/packages/cp-gql-schema/package.json index 4b99730e..eda6b0f7 100644 --- a/packages/cp-gql-schema/package.json +++ b/packages/cp-gql-schema/package.json @@ -1,6 +1,6 @@ { "name": "joyent-cp-gql-schema", - "version": "1.2.0", + "version": "1.3.0", "license": "MPL-2.0", "repository": "github:yldio/joyent-portal", "main": "index.js", diff --git a/packages/portal-api/package.json b/packages/portal-api/package.json index 63ef002f..09bde301 100644 --- a/packages/portal-api/package.json +++ b/packages/portal-api/package.json @@ -1,6 +1,6 @@ { "name": "portal-api", - "version": "1.3.2", + "version": "1.3.3", "description": "", "main": "./lib/index.js", "scripts": { @@ -40,7 +40,7 @@ "force-array": "^3.1.0", "graphi": "^2.2.1", "hoek": "^4.1.1", - "joyent-cp-gql-schema": "^1.0.4", + "joyent-cp-gql-schema": "^1.3.0", "lodash.find": "^4.6.0", "lodash.flatten": "^4.4.0", "lodash.get": "^4.4.2", diff --git a/setup.sh b/setup.sh index 0f8aefe2..0a3fdc17 100755 --- a/setup.sh +++ b/setup.sh @@ -3,7 +3,7 @@ set -e -o pipefail help() { echo - echo 'Usage ./setup.sh ~/path/to/TRITON_PRIVATE_KEY' + echo 'Usage ./setup.sh ~/path/to/TRITON_PRIVATE_KEY ~/path/to/CA_CRT ~/path/to/SERVER_KEY ~/path/to/SERVER_CRT' echo echo 'Checks that your Triton and Docker environment is sane and configures' echo 'an environment file to use.' @@ -39,6 +39,75 @@ check() { # Assign args to named vars TRITON_PRIVATE_KEY_PATH=$1 + + if [ -z "$2" ]; then + tput rev # reverse + tput bold # bold + echo 'Please provide a path to the NGINX CA crt file.' + tput sgr0 # clear + + help + exit 1 + fi + + if [ ! -f "$2" ]; then + tput rev # reverse + tput bold # bold + echo 'CA certificate for NGINX is unreadable.' + tput sgr0 # clear + + help + exit 1 + fi + + NGINX_CA_CRT_PATH=$2 + + + if [ -z "$3" ]; then + tput rev # reverse + tput bold # bold + echo 'Please provide a path to the server key file.' + tput sgr0 # clear + + help + exit 1 + fi + + if [ ! -f "$3" ]; then + tput rev # reverse + tput bold # bold + echo 'Server key file for NGINX is unreadable.' + tput sgr0 # clear + + help + exit 1 + fi + + NGINX_SERVER_KEY_PATH=$3 + + + if [ -z "$4" ]; then + tput rev # reverse + tput bold # bold + echo 'Please provide a path to the server crt file.' + tput sgr0 # clear + + help + exit 1 + fi + + if [ ! -f "$4" ]; then + tput rev # reverse + tput bold # bold + echo 'Server crt file for NGINX is unreadable.' + tput sgr0 # clear + + help + exit 1 + fi + + NGINX_SERVER_CRT_PATH=$4 + command -v docker >/dev/null 2>&1 || { echo tput rev # reverse @@ -83,8 +152,14 @@ check() { echo TRITON_KEY_PATH=${TRITON_CREDS_PATH}/key.pem >> _env echo TRITON_CERT=$(cat "${DOCKER_CERT_PATH}"/cert.pem | tr '\n' '#') >> _env echo TRITON_CERT_PATH=${TRITON_CREDS_PATH}/cert.pem >> _env + echo SDC_KEY=$(cat "${TRITON_PRIVATE_KEY_PATH}" | tr '\n' '#') >> _env echo SDC_KEY_PUB=$(cat "${TRITON_PRIVATE_KEY_PATH}.pub" | tr '\n' '#') >> _env + + echo NGINX_CA_CRT=$(cat "${NGINX_CA_CRT_PATH}" | tr '\n' '#') >> _env + echo NGINX_SERVER_KEY=$(cat "${NGINX_SERVER_KEY_PATH}" | tr '\n' '#') >> _env + echo NGINX_SERVER_CRT=$(cat "${NGINX_SERVER_CRT_PATH}" | tr '\n' '#') >> _env + echo >> _env }