Problem: No authorization mode without threescale (#2088)

Problem
The current production deployment template uses 3scale to ensure that POST requests to the network (from anyone) only get through if they come from a client with an account (app_id and app_key).

A private network wants to launch so that all HTTP requests (POST and GET) sent to the nodes in the network get be dropped unless they come from a small set of known (and unchanging) clients/sources. They don't need 3scale. They will want a modified version of the production deployment template.

Solution
Generate a special HTTP header and share it with all the known clients/sources.
Have a single NGINX in each node which checks for that HTTP header value. If it's present, let the request pass through to the network. (HTTP headers are encrypted if HTTPS is used.)
Are there other simpler or better options?
This commit is contained in:
Shahbaz Nazir 2018-02-23 16:00:36 +01:00 committed by GitHub
parent cdec60a7c0
commit 0ddfc62e3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 251 additions and 21 deletions

View File

@ -92,6 +92,9 @@ data:
# it will use the default cache size; i.e. max((50% RAM - 1GB), 256MB) # it will use the default cache size; i.e. max((50% RAM - 1GB), 256MB)
storage-engine-cache-size: "" storage-engine-cache-size: ""
# POST API authorization mode [threescale | secrete-token]
authorization-mode: "threescale"
--- ---
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap

View File

@ -54,6 +54,16 @@ data:
--- ---
apiVersion: v1 apiVersion: v1
kind: Secret kind: Secret
metadata:
name: nginx-secret-header
namespace: default
type: Opaque
data:
# Base64-encoded secret token to authorize POST requests
secret-token: "<b64 encoded secret token for authorization>"
---
apiVersion: v1
kind: Secret
metadata: metadata:
name: https-certs name: https-certs
namespace: default namespace: default
@ -65,7 +75,7 @@ data:
# starting with your primary SSL cert (e.g. your_domain.crt) # starting with your primary SSL cert (e.g. your_domain.crt)
# followed by all intermediate certs. # followed by all intermediate certs.
# If cert if from DigiCert, download "Best format for nginx". # If cert if from DigiCert, download "Best format for nginx".
cert.pem: "<b64 encoded HTTPS certificate chain" cert.pem: "<b64 encoded HTTPS certificate chain>"
--- ---
apiVersion: v1 apiVersion: v1
kind: Secret kind: Secret

View File

@ -5,6 +5,7 @@ RUN apt-get update \
&& apt-get -y upgrade \ && apt-get -y upgrade \
&& apt-get autoremove \ && apt-get autoremove \
&& apt-get clean && apt-get clean
COPY nginx.conf.threescale.template /etc/nginx/nginx-threescale.conf
COPY nginx.conf.template /etc/nginx/nginx.conf COPY nginx.conf.template /etc/nginx/nginx.conf
COPY nginx_entrypoint.bash / COPY nginx_entrypoint.bash /
EXPOSE 80 443 27017 9986 46656 EXPOSE 80 443 27017 9986 46656

View File

@ -1,6 +1,7 @@
# Frontend API server that: # Frontend API server that:
# 1. Acts as the HTTPS termination point. # 1. Acts as the HTTPS termination point.
# 2. Forwards BDB HTTP requests to OpenResty backend. # 2. Authorizes HTTP requests with secret token header
# and forwards to BDB backend.
# 3. Forwards BDB WS requests to BDB backend. # 3. Forwards BDB WS requests to BDB backend.
# 4. Does health check with LB. # 4. Does health check with LB.
@ -57,9 +58,6 @@ http {
map $remote_addr $bdb_backend { map $remote_addr $bdb_backend {
default BIGCHAINDB_BACKEND_HOST; default BIGCHAINDB_BACKEND_HOST;
} }
map $remote_addr $openresty_backend {
default OPENRESTY_BACKEND_HOST;
}
# Frontend server for the external clients; acts as HTTPS termination point. # Frontend server for the external clients; acts as HTTPS termination point.
server { server {
@ -100,27 +98,27 @@ http {
proxy_pass http://$bdb_backend:BIGCHAINDB_API_PORT; proxy_pass http://$bdb_backend:BIGCHAINDB_API_PORT;
} }
# POST requests get forwarded to OpenResty instance. Enable CORS too.
if ($request_method = POST ) {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
add_header 'Access-Control-Expose-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
proxy_pass http://$openresty_backend:OPENRESTY_BACKEND_PORT;
}
# OPTIONS requests handling for CORS. # OPTIONS requests handling for CORS.
if ($request_method = 'OPTIONS') { if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range,app_key,app_id'; add_header 'Access-Control-Allow-Headers' 'DNT,X-Secret-Access-Token,User-Agent';
add_header 'Access-Control-Max-Age' 43200; add_header 'Access-Control-Max-Age' 43200;
add_header 'Content-Type' 'text/plain charset=UTF-8'; add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0; add_header 'Content-Length' 0;
return 204; return 204;
} }
# Check for security header to authorize POST requests
if ( $http_x_secret_access_token != "SECRET_ACCESS_TOKEN" ) {
return 403;
}
# POST requests get forwarded to BDB.
if ($request_method = POST ) {}
proxy_pass http://$bdb_backend:BIGCHAINDB_API_PORT;
}
# Only return this reponse if request_method is neither POST|GET|OPTIONS # Only return this reponse if request_method is neither POST|GET|OPTIONS
if ($request_method !~ ^(GET|OPTIONS|POST)$) { if ($request_method !~ ^(GET|OPTIONS|POST)$) {
return 444; return 444;

View File

@ -0,0 +1,198 @@
# Frontend API server that:
# 1. Acts as the HTTPS termination point.
# 2. Forwards BDB HTTP requests to OpenResty backend.
# 3. Forwards BDB WS requests to BDB backend.
# 4. Does health check with LB.
worker_processes 2;
daemon off;
user nobody nogroup;
pid /tmp/nginx.pid;
error_log /dev/stderr;
events {
# Each worker handles up to 512 connections. Increase this for heavy
# workloads.
worker_connections 512;
accept_mutex on;
use epoll;
}
http {
access_log /dev/stdout combined buffer=16k flush=5s;
# Allow 10 req/sec from the same IP address, and store the counters in a
# `zone` or shared memory location tagged as 'one'.
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
# Enable logging when requests are being throttled.
limit_req_log_level notice;
# HTTP status code that is returned to the client; 429 is for TooManyRequests,
# ref. RFC 6585
limit_req_status 429;
# Limit requests from the same client, allow `burst` to 20 r/s,
# `nodelay` or drop connection immediately in case it exceeds this
# threshold.
limit_req zone=one burst=20 nodelay;
# `slowloris` attack mitigation settings.
client_body_timeout 10s;
client_header_timeout 10s;
# Do not expose nginx data/version number in error response and header
server_tokens off;
# To prevent cross-site scripting
add_header X-XSS-Protection "1; mode=block";
# DNS resolver to use for all the backend names specified in this configuration.
resolver DNS_SERVER valid=30s ipv6=off;
keepalive_timeout 60s;
# The following map blocks enable lazy-binding to the backend at runtime,
# rather than binding as soon as NGINX starts.
map $remote_addr $bdb_backend {
default BIGCHAINDB_BACKEND_HOST;
}
map $remote_addr $openresty_backend {
default OPENRESTY_BACKEND_HOST;
}
# Frontend server for the external clients; acts as HTTPS termination point.
server {
listen CLUSTER_FRONTEND_PORT ssl;
server_name "CLUSTER_FQDN";
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/cert.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
underscores_in_headers on;
# Forward websockets directly to backend BDB.
location /api/v1/streams/valid_transactions {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://$bdb_backend:BIGCHAINDB_WS_PORT;
proxy_read_timeout 600s;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Forward other URL paths as per business logic/use case to BDB or
# OpenResty instance.
location / {
proxy_ignore_client_abort on;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# max client request body size: avg transaction size.
client_max_body_size 15k;
# No auth for GETs, forward directly to BDB.
if ($request_method = GET) {
proxy_pass http://$bdb_backend:BIGCHAINDB_API_PORT;
}
# POST requests get forwarded to OpenResty instance. Enable CORS too.
if ($request_method = POST ) {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
add_header 'Access-Control-Expose-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
proxy_pass http://$openresty_backend:OPENRESTY_BACKEND_PORT;
}
# OPTIONS requests handling for CORS.
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range,app_key,app_id';
add_header 'Access-Control-Max-Age' 43200;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
# Only return this reponse if request_method is neither POST|GET|OPTIONS
if ($request_method !~ ^(GET|OPTIONS|POST)$) {
return 444;
}
}
}
# Frontend server for the load balancer to respond to health checks.
server {
listen HEALTH_CHECK_PORT;
location = /health {
return 200;
}
}
# Frontend server for the external clients; returns a pretty error message
# when an HTTP request is sent instead of HTTPS.
server {
listen 80;
server_name "CLUSTER_FQDN";
location / {
add_header Upgrade "TLS/1.2, HTTP/1.1" always;
default_type text/plain;
return 426 'Consider using the HTTPS protocol next time!';
}
}
}
# NGINX stream block for TCP and UDP proxies.
stream {
log_format bdb_log '[$time_iso8601] $realip_remote_addr $remote_addr '
'$proxy_protocol_addr $proxy_protocol_port '
'$protocol $status $session_time $bytes_sent '
'$bytes_received "$upstream_addr" "$upstream_bytes_sent" '
'"$upstream_bytes_received" "$upstream_connect_time" ';
access_log /dev/stdout bdb_log buffer=16k flush=5s;
# Define a zone 'two' of size 10 megabytes to store the counters
# that hold number of TCP connections from a specific IP address.
limit_conn_zone $binary_remote_addr zone=two:10m;
# Enable logging when connections are being throttled.
limit_conn_log_level notice;
# Allow 256 connections from the same IP address.
limit_conn two 256;
# DNS resolver to use for all the backend names specified in this configuration.
resolver DNS_SERVER valid=30s ipv6=off;
# The following map block enables lazy-binding to the backend at runtime,
# rather than binding as soon as NGINX starts.
map $remote_addr $tm_backend {
default TM_BACKEND_HOST;
}
# Server to forward connection to nginx instance hosting
# tendermint node public key.
server {
listen TM_PUB_KEY_ACCESS_PORT;
proxy_pass $tm_backend:TM_PUB_KEY_ACCESS_PORT;
}
# Server to forward p2p connections to Tendermint instance.
server {
listen TM_P2P_PORT so_keepalive=3m:1m:5;
preread_timeout 60s;
tcp_nodelay on;
proxy_pass $tm_backend:TM_P2P_PORT;
}
}

View File

@ -1,6 +1,10 @@
#!/bin/bash #!/bin/bash
set -euo pipefail set -euo pipefail
# Authorization Modes
threescale_auth_mode="threescale"
secret_token_auth_mode="secret-token"
# Cluster vars # Cluster vars
cluster_fqdn=`printenv CLUSTER_FQDN` cluster_fqdn=`printenv CLUSTER_FQDN`
cluster_frontend_port=`printenv CLUSTER_FRONTEND_PORT` cluster_frontend_port=`printenv CLUSTER_FRONTEND_PORT`
@ -9,6 +13,7 @@ cluster_frontend_port=`printenv CLUSTER_FRONTEND_PORT`
# NGINX vars # NGINX vars
dns_server=`printenv DNS_SERVER` dns_server=`printenv DNS_SERVER`
health_check_port=`printenv HEALTH_CHECK_PORT` health_check_port=`printenv HEALTH_CHECK_PORT`
authorization_mode=`printenv AUTHORIZATION_MODE`
# MongoDB vars # MongoDB vars
@ -47,7 +52,8 @@ if [[ -z "${cluster_frontend_port:?CLUSTER_FRONTEND_PORT not specified. Exiting!
-z "${cluster_fqdn:?CLUSTER_FQDN not specified. Exiting!}" || \ -z "${cluster_fqdn:?CLUSTER_FQDN not specified. Exiting!}" || \
-z "${tm_pub_key_access_port:?TM_PUB_KEY_ACCESS_PORT not specified. Exiting!}" || \ -z "${tm_pub_key_access_port:?TM_PUB_KEY_ACCESS_PORT not specified. Exiting!}" || \
-z "${tm_backend_host:?TM_BACKEND_HOST not specified. Exiting!}" || \ -z "${tm_backend_host:?TM_BACKEND_HOST not specified. Exiting!}" || \
-z "${tm_p2p_port:?TM_P2P_PORT not specified. Exiting!}" ]]; then -z "${tm_p2p_port:?TM_P2P_PORT not specified. Exiting!}" || \
-z "${authorization_mode:-threescale_auth_mode}" ]]; then # Set the default authorization mode to threescale
echo "Missing required environment variables. Exiting!" echo "Missing required environment variables. Exiting!"
exit 1 exit 1
else else
@ -68,7 +74,18 @@ else
echo TM_P2P_PORT="$tm_p2p_port" echo TM_P2P_PORT="$tm_p2p_port"
fi fi
NGINX_CONF_FILE=/etc/nginx/nginx.conf if [[ ${authorization_mode} == ${secret_token_auth_mode} ]]; then
NGINX_CONF_FILE=/etc/nginx/nginx.conf
secret_access_token=`printenv SECRET_ACCESS_TOKEN`
sed -i "s|SECRET_ACCESS_TOKEN|${secret_token_header}|g"
elif [[ ${authorization_mode} == ${threescale_auth_mode} ]]; then
NGINX_CONF_FILE=/etc/nginx/nginx-threescale.conf
sed -i "s|OPENRESTY_BACKEND_PORT|${openresty_backend_port}|g" ${NGINX_CONF_FILE}
sed -i "s|OPENRESTY_BACKEND_HOST|${openresty_backend_host}|g" ${NGINX_CONF_FILE}
else
echo "Unrecognised authorization mode: ${authorization_mode}. Exiting!"
exit 1
fi
# configure the nginx.conf file with env variables # configure the nginx.conf file with env variables
sed -i "s|CLUSTER_FQDN|${cluster_fqdn}|g" ${NGINX_CONF_FILE} sed -i "s|CLUSTER_FQDN|${cluster_fqdn}|g" ${NGINX_CONF_FILE}
@ -76,8 +93,6 @@ sed -i "s|CLUSTER_FRONTEND_PORT|${cluster_frontend_port}|g" ${NGINX_CONF_FILE}
sed -i "s|MONGODB_FRONTEND_PORT|${mongo_frontend_port}|g" ${NGINX_CONF_FILE} sed -i "s|MONGODB_FRONTEND_PORT|${mongo_frontend_port}|g" ${NGINX_CONF_FILE}
sed -i "s|MONGODB_BACKEND_HOST|${mongo_backend_host}|g" ${NGINX_CONF_FILE} sed -i "s|MONGODB_BACKEND_HOST|${mongo_backend_host}|g" ${NGINX_CONF_FILE}
sed -i "s|MONGODB_BACKEND_PORT|${mongo_backend_port}|g" ${NGINX_CONF_FILE} sed -i "s|MONGODB_BACKEND_PORT|${mongo_backend_port}|g" ${NGINX_CONF_FILE}
sed -i "s|OPENRESTY_BACKEND_PORT|${openresty_backend_port}|g" ${NGINX_CONF_FILE}
sed -i "s|OPENRESTY_BACKEND_HOST|${openresty_backend_host}|g" ${NGINX_CONF_FILE}
sed -i "s|BIGCHAINDB_BACKEND_HOST|${bdb_backend_host}|g" ${NGINX_CONF_FILE} sed -i "s|BIGCHAINDB_BACKEND_HOST|${bdb_backend_host}|g" ${NGINX_CONF_FILE}
sed -i "s|BIGCHAINDB_API_PORT|${bdb_api_port}|g" ${NGINX_CONF_FILE} sed -i "s|BIGCHAINDB_API_PORT|${bdb_api_port}|g" ${NGINX_CONF_FILE}
sed -i "s|BIGCHAINDB_WS_PORT|${bdb_ws_port}|g" ${NGINX_CONF_FILE} sed -i "s|BIGCHAINDB_WS_PORT|${bdb_ws_port}|g" ${NGINX_CONF_FILE}
@ -89,4 +104,4 @@ sed -i "s|TM_P2P_PORT|${tm_p2p_port}|g" ${NGINX_CONF_FILE}
# start nginx # start nginx
echo "INFO: starting nginx..." echo "INFO: starting nginx..."
exec nginx -c /etc/nginx/nginx.conf exec nginx -c ${NGINX_CONF_FILE}

View File

@ -85,6 +85,11 @@ spec:
configMapKeyRef: configMapKeyRef:
name: tendermint-config name: tendermint-config
key: tm-p2p-port key: tm-p2p-port
- name: AUTHORIZATION_MODE
valueFrom:
configMapKeyRef:
name: vars
key: authorization-mode
ports: ports:
# return a pretty error message on port 80, since we are expecting # return a pretty error message on port 80, since we are expecting
# HTTPS traffic. # HTTPS traffic.