diff --git a/Dockerfile b/Dockerfile index 2d8942a2..c1ab8925 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM ruby:2.2.3 -MAINTAINER Richie Young a +MAINTAINER Richie Young RUN apt-get update && apt-get install -qq -y build-essential nodejs wget --fix-missing --no-install-recommends diff --git a/bin/yacs-generate-cert b/bin/yacs-generate-cert index 368d5aaa..9435eb82 100755 --- a/bin/yacs-generate-cert +++ b/bin/yacs-generate-cert @@ -1,4 +1,7 @@ #!/bin/bash -cd $(dirname `readlink -f "$0"`)/../ +cd $(dirname `readlink -f "$0"`)/../nginx/ssl -openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout nginx/ssl/yacs.key -out nginx/ssl/yacs.cer -subj "/C=US/ST=New York/L=Troy/O=RPI/OU=RCOS/CN=yacs.cs.rpi.edu" +openssl req -x509 -nodes -days 730 -newkey rsa:2048 -keyout privkey.pem -out cert.pem -subj '/CN=localhost' + +cp cert.pem chain.pem +cp chain.pem fullchain.pem diff --git a/config/environments/production.rb b/config/environments/production.rb index 2590d026..1479065e 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -27,7 +27,11 @@ config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? # Compress JavaScripts and CSS. + config.assets.compress = true + + # Choose the compressors to use config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :yui # Do not fallback to assets pipeline if a precompiled asset is missed. config.assets.compile = false @@ -43,7 +47,7 @@ config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - # config.force_ssl = true + config.force_ssl = true # Use the lowest log level to ensure availability of diagnostic information # when problems arise. diff --git a/config/puma.rb b/config/puma.rb index 81719682..600de551 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -10,8 +10,8 @@ environment ENV['RAILS_ENV'] || 'development' ssl_bind '0.0.0.0', '3000', { - key: "/etc/puma/ssl/yacs.key", - cert: "/etc/puma/ssl/yacs.cer" + key: "/etc/puma/ssl/privkey.pem", + cert: "/etc/puma/ssl/cert.pem" } pidfile "/var/run/puma/puma.pid" diff --git a/config/secrets.yml b/config/secrets.yml index 75b94de7..a55534c6 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -11,10 +11,10 @@ # if you're sharing your code publicly. development: - secret_key_base: b0fc69091c512cae698ae9de98349b8f6fb4544c463afa7f17af359ddcdcef4dccb9a8d325ee41e4687b9df112ee6ba213fb631ab9cc800b69582c79aca4805f + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> test: - secret_key_base: 69005fa5977d1ac05ae1ffbbcc59e50094fdb52067f090fa0c6960fc1f905fd93f82abef05aa23dbf291c4405497f666a554cc15221535e784af95f02453f04f + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> # Do not keep production secrets in the repository, # instead read values from the environment. diff --git a/docker-compose.yml b/docker-compose.yml index e442ce41..4602efa3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,8 @@ version: '2' services: postgres: - image: postgres:9.5 + image: postgres:9.5.6 + restart: unless-stopped expose: - "5432" volumes: @@ -10,6 +11,7 @@ services: redis: image: redis + restart: unless-stopped expose: - "6379" volumes: @@ -17,7 +19,8 @@ services: nginx: build: ./nginx - image: nginx:mainline + image: nginx:1.11.9 + restart: unless-stopped ports: - "80:80" - "443:443" @@ -28,6 +31,7 @@ services: web: build: . + restart: unless-stopped environment: - RAILS_ENV=${RAILS_ENV} - SECRET_KEY_BASE=${SECRET_KEY_BASE} @@ -45,6 +49,7 @@ services: worker: build: . + restart: unless-stopped environment: - RAILS_ENV=${RAILS_ENV} volumes: diff --git a/nginx/Dockerfile b/nginx/Dockerfile index c6fe9974..d29cc95d 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -2,7 +2,8 @@ FROM nginx MAINTAINER Mark Robinson -RUN rm /etc/nginx/conf.d/default.conf +RUN rm /etc/nginx/nginx.conf && \ + rm /etc/nginx/conf.d/default.conf RUN mkdir /etc/nginx/cache diff --git a/nginx/nginx.conf b/nginx/nginx.conf index e4de52ba..e7e55436 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -1,154 +1,182 @@ worker_processes auto; +pcre_jit on; +user nginx; events { - worker_connections 1024; - multi_accept on; - use epoll; + worker_connections 2048; + multi_accept on; + use epoll; } http { - upstream app { - server web:3000; - keepalive 64; - } - - proxy_cache_path /etc/nginx/cache levels=1:2 keys_zone=STATIC:10m - inactive=24h max_size=1g; + upstream app { + server web:3000; + keepalive 64; + } - server { - listen 80 default_server; + proxy_cache_path /etc/nginx/cache levels=1:2 keys_zone=STATIC:10m inactive=24h max_size=1g; - server_name localhost; + limit_conn_zone $binary_remote_addr zone=limit_per_ip:10m; + limit_conn limit_per_ip 128; + limit_req_zone $binary_remote_addr zone=allips:10m rate=150r/s; + limit_req zone=allips burst=150 nodelay; - charset utf-8; + server { + listen 80 default_server; - # Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response. - return 301 https://$host$request_uri; - } + charset utf-8; - server { - listen 443 default_server ssl http2; + # Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response. + return 301 https://$host$request_uri; + } - server_name localhost; + server { + listen 443 ssl http2 deferred default_server; - root /usr/src/app/public; + root /usr/src/app/public; - charset utf-8; + charset utf-8; - ssl_certificate_key /etc/nginx/ssl/yacs.key; - ssl_certificate /etc/nginx/ssl/chain.cer; - ssl_trusted_certificate /etc/nginx/ssl/yacs.cer; + ssl_certificate_key /etc/nginx/ssl/privkey.pem; + ssl_certificate /etc/nginx/ssl/fullchain.pem; + ssl_trusted_certificate /etc/nginx/ssl/chain.pem; - ssl_session_tickets off; - ssl_session_cache shared:SSL:50m; - ssl_session_timeout 1d; + ssl_session_tickets off; + ssl_session_cache shared:SSL:32m; + ssl_buffer_size 8k; + ssl_session_timeout 60m; - # ssl_stapling on; - # ssl_stapling_verify on; + ssl_stapling on; + ssl_stapling_verify on; - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; - - ssl_prefer_server_ciphers on; - ssl_ecdh_curve secp384r1; - - # ssl_dhparam /etc/nginx/ssl/dhparam.pem; - - # add_header Public-Key-Pins 'pin-sha256="atnjelwPipMfcOPjHY5X45Nrfm1Q2f+EzC1Pr36bnXM="; pin-sha256="b1JA6+4svjmZnxGjAiQY3RS0A9FtjKLCWaRlVmCPM28="; pin-sha256="x4QzPSC810K5/cMjb05Qm4k3Bw5zBn4lTdO/nEW/Td4="; pin-sha256="lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU="; max-age=5184000;'; - - add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload;" always; - add_header X-Frame-Options DENY; - add_header X-Content-Type-Options nosniff; - add_header Content-Security-Policy "default-src 'none'; connect-src 'self'; script-src 'self' 'unsafe-inline' https://www.google-analytics.com https://js-agent.newrelic.com https://bam.nr-data.net; style-src 'self'; img-src 'self' https://www.google-analytics.com data:; font-src 'self' https://fonts.gstatic.com data:"; - - location @app { - proxy_pass https://app; - proxy_set_header Host $host; - - proxy_cache STATIC; - proxy_cache_valid 200 1d; - proxy_cache_use_stale error timeout invalid_header updating - http_500 http_502 http_503 http_504; - proxy_cache_lock on; - proxy_cache_revalidate on; - proxy_cache_min_uses 3; - - add_header X-Cache-Status $upstream_cache_status; - - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Client-Verify SUCCESS; - proxy_set_header X-Client-DN $ssl_client_s_dn; - proxy_set_header X-SSL-Subject $ssl_client_s_dn; - proxy_set_header X-SSL-Issuer $ssl_client_i_dn; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_read_timeout 1800; - proxy_connect_timeout 1800; - proxy_set_header Connection ""; - proxy_buffering off; - } - - location ~ /\. { - deny all; - } + ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS; + + ssl_prefer_server_ciphers on; + ssl_ecdh_curve secp384r1; + + # ssl_dhparam /etc/nginx/ssl/dhparam.pem; + + location @app { + proxy_pass https://app; + proxy_set_header Host $host; + + proxy_cache STATIC; + proxy_cache_valid 200 1d; + proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; + proxy_cache_lock on; + proxy_cache_revalidate on; + proxy_cache_min_uses 3; + + # add_header X-Cache-Status $upstream_cache_status; + + add_header X-Frame-Options DENY; + # add_header X-Content-Type-Options nosniff; + # add_header X-XSS-Protection "1; mode=block"; + add_header Content-Security-Policy "default-src 'none'; connect-src 'self'; script-src 'self' 'unsafe-inline' https://www.google-analytics.com https://js-agent.newrelic.com https://bam.nr-data.net; style-src 'self'; img-src 'self' https://www.google-analytics.com data:; font-src 'self' https://fonts.gstatic.com data:"; + + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Client-Verify SUCCESS; + proxy_set_header X-Client-DN $ssl_client_s_dn; + proxy_set_header X-SSL-Subject $ssl_client_s_dn; + proxy_set_header X-SSL-Issuer $ssl_client_i_dn; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 1800; + proxy_connect_timeout 1800; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_buffering off; + proxy_redirect off; + } + + location ~ /\. { + deny all; + } - location ~* ^.+\.(rb|log)$ { - deny all; - } + location ~* ^.+\.(rb|log)$ { + deny all; + } - location /assets/ { + location /assets/ { + expires max; + add_header Cache-Control "public"; + add_header X-Cache-Status $upstream_cache_status; - expires max; - add_header Cache-Control "public, max-age=315360000"; - add_header X-Cache-Status $upstream_cache_status; + gzip_static on; - try_files $uri @app; - } + try_files $uri @app; + } - location / { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host; - proxy_redirect off; + location / { + gzip_static on; - try_files $uri @app; - } - } + try_files $uri @app; + } + } - access_log off; + access_log off; - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; - keepalive_requests 200; - keepalive_timeout 65; - reset_timedout_connection on; + aio threads; + keepalive_requests 200; + keepalive_timeout 65; + keepalive_disable msie6; + reset_timedout_connection on; + + client_body_buffer_size 10K; + client_header_buffer_size 1k; + client_max_body_size 8m; + large_client_header_buffers 2 1k; - server_tokens off; + server_tokens off; - sendfile on; - tcp_nopush on; - tcp_nodelay on; + sendfile on; + tcp_nopush on; + tcp_nodelay on; - default_type application/octet-stream; - include /etc/nginx/mime.types; + default_type application/octet-stream; + include /etc/nginx/mime.types; - open_file_cache max=10000 inactive=30s; - open_file_cache_valid 60s; - open_file_cache_min_uses 2; - open_file_cache_errors on; - - gzip on; - gzip_static on; - gzip_comp_level 3; - gzip_min_length 256; - gzip_proxied no-cache no-store private expired auth; - gzip_types text/plain text/css image/png image/gif image/jpeg application/x-javascript text/xml application/xml application/x -ml+rss text/javascript; - gzip_vary on; - gzip_http_version 1.1; - gzip_disable "MSIE [1-6]\.(?!.*SV1)"; + open_file_cache max=10000 inactive=30s; + open_file_cache_valid 60s; + open_file_cache_min_uses 2; + open_file_cache_errors on; + + gzip on; + gzip_static on; + gzip_comp_level 5; + gzip_min_length 256; + gzip_buffers 4 8k; + gzip_proxied no-cache no-store private expired auth; + gzip_types text/plain text/css image/png image/gif image/jpeg application/x-javascript text/xml application/xml application/xml+rss text/javascript; + gzip_vary on; + gzip_http_version 1.1; + gzip_disable "MSIE [1-6]\.(?!.*SV1)"; + + # brotli on; + # brotli_static on; + # brotli_buffers 16 8k; + # brotli_comp_level 6; + # brotli_types + # text/css + # text/javascript + # text/xml + # text/plain + # text/x-component + # application/javascript + # application/x-javascript + # application/json + # application/xml + # application/rss+xml + # application/vnd.ms-fontobject + # font/truetype + # font/opentype + # image/svg+xml; }