From 1fa0e7d2c4a4388fd118d5860e42bb071a015179 Mon Sep 17 00:00:00 2001 From: Chris Xiao <30990835+chrisx8@users.noreply.github.com> Date: Thu, 22 Dec 2022 13:21:08 -0500 Subject: [PATCH] containers_web: switch to apache proxy * replaces nginx container * gitea uses http/1.1 only, due to https://github.com/go-gitea/gitea/issues/19265 * vaultwarden admin uses oidc sso --- roles/containers_web/README.md | 4 +- .../files/{nginx/www => }/favicon.ico | Bin .../files/httpd/conf.d/1_proxy.conf | 2 + .../files/httpd/conf.d/gitea.conf | 25 +++++++++ .../files/httpd/conf.d/sso.conf | 22 ++++++++ .../files/nginx/conf.d/1-default.conf | 14 ----- .../files/nginx/conf.d/gitea.conf | 25 --------- .../files/nginx/conf.d/sso.conf | 32 ------------ .../files/nginx/conf.d/vaultwarden.conf | 35 ------------- roles/containers_web/files/nginx/nginx.conf | 47 ----------------- .../containers_web/files/nginx/www/robots.txt | 2 - roles/containers_web/tasks/apache.yml | 35 +++++++++++++ roles/containers_web/tasks/main.yml | 22 ++------ roles/containers_web/tasks/nginx.yml | 17 ------ roles/containers_web/tasks/scripts.yml | 14 ----- .../templates/logrotate.conf.j2 | 13 ----- .../templates/vaultwarden.conf.j2 | 49 ++++++++++++++++++ roles/containers_web/vars/main.yml | 24 +++------ roles/httpd/files/1_default.conf | 4 +- setup.yml | 1 + 20 files changed, 151 insertions(+), 236 deletions(-) rename roles/containers_web/files/{nginx/www => }/favicon.ico (100%) create mode 100644 roles/containers_web/files/httpd/conf.d/1_proxy.conf create mode 100644 roles/containers_web/files/httpd/conf.d/gitea.conf create mode 100644 roles/containers_web/files/httpd/conf.d/sso.conf delete mode 100644 roles/containers_web/files/nginx/conf.d/1-default.conf delete mode 100644 roles/containers_web/files/nginx/conf.d/gitea.conf delete mode 100644 roles/containers_web/files/nginx/conf.d/sso.conf delete mode 100644 roles/containers_web/files/nginx/conf.d/vaultwarden.conf delete mode 100644 roles/containers_web/files/nginx/nginx.conf delete mode 100644 roles/containers_web/files/nginx/www/robots.txt create mode 100644 roles/containers_web/tasks/apache.yml delete mode 100644 roles/containers_web/tasks/nginx.yml delete mode 100644 roles/containers_web/templates/logrotate.conf.j2 create mode 100644 roles/containers_web/templates/vaultwarden.conf.j2 diff --git a/roles/containers_web/README.md b/roles/containers_web/README.md index 82590b12..b8762158 100644 --- a/roles/containers_web/README.md +++ b/roles/containers_web/README.md @@ -1,11 +1,10 @@ # Ansible Role: `containers_web` -This role provides role variables for `containers` role and installs containerized web apps, including: +This role provides role variables for `containers` role, installs an Apache web server, and installs containerized web apps, including: - [cloudflared](https://github.com/cloudflare/cloudflared) - [Gitea](https://gitea.io/) - [Keycloak](https://www.keycloak.org/) -- [Nginx](https://nginx.org/) - [Vaultwarden](https://github.com/dani-garcia/vaultwarden) Required facts: `distribution`, `virtualization_type` @@ -14,5 +13,6 @@ Required role vars: - `cloudflared_token`: Cloudflare Tunnel token. - `containers_web_cron_ping_url`: Webhook URL for cron job. +- `vaultwarden_oidc`: OIDC client credentials, requires `client_id` and `client_secret`. Supported OS: RHEL-like systems, version 8 or newer diff --git a/roles/containers_web/files/nginx/www/favicon.ico b/roles/containers_web/files/favicon.ico similarity index 100% rename from roles/containers_web/files/nginx/www/favicon.ico rename to roles/containers_web/files/favicon.ico diff --git a/roles/containers_web/files/httpd/conf.d/1_proxy.conf b/roles/containers_web/files/httpd/conf.d/1_proxy.conf new file mode 100644 index 00000000..afb82d73 --- /dev/null +++ b/roles/containers_web/files/httpd/conf.d/1_proxy.conf @@ -0,0 +1,2 @@ +RemoteIPHeader X-Forwarded-For +RemoteIPTrustedProxy 10.88.0.0/24 diff --git a/roles/containers_web/files/httpd/conf.d/gitea.conf b/roles/containers_web/files/httpd/conf.d/gitea.conf new file mode 100644 index 00000000..3af6c3e4 --- /dev/null +++ b/roles/containers_web/files/httpd/conf.d/gitea.conf @@ -0,0 +1,25 @@ + + ServerName gitea.chrisx.xyz + SSLEngine on + SSLCertificateFile /etc/ssl/chrisx.xyz/fullchain.pem + SSLCertificateKeyFile /etc/ssl/chrisx.xyz/privkey.pem + Protocols http/1.1 + + Header always set Referrer-Policy "no-referrer" + Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + Header always set X-Content-Type-Options "nosniff" + Header always set X-Robots-Tag "none" + Header always set X-XSS-Protection "1; mode=block" + + AllowEncodedSlashes NoDecode + + ProxyPass /robots.txt ! + ProxyPass / http://127.0.0.1:53000/ + ProxyPassReverse / http://127.0.0.1:53000/ + ProxyPreserveHost On + ProxyRequests off + RequestHeader set X-Forwarded-Proto expr=%{REQUEST_SCHEME} + + ErrorLog /var/log/httpd/gitea_error_log + CustomLog /var/log/httpd/gitea_access_log combined + diff --git a/roles/containers_web/files/httpd/conf.d/sso.conf b/roles/containers_web/files/httpd/conf.d/sso.conf new file mode 100644 index 00000000..f8606261 --- /dev/null +++ b/roles/containers_web/files/httpd/conf.d/sso.conf @@ -0,0 +1,22 @@ + + ServerName sso.chrisx.xyz + SSLEngine on + SSLCertificateFile /etc/ssl/chrisx.xyz/fullchain.pem + SSLCertificateKeyFile /etc/ssl/chrisx.xyz/privkey.pem + Protocols h2 http/1.1 + + Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + + AliasMatch "^/resources/.+/login/keycloak/img/favicon.ico$" /var/www/html/favicon.ico + RedirectMatch 404 "^/$" + + ProxyPassMatch "^/$" ! + ProxyPassMatch "^/resources/.+/login/keycloak/img/favicon.ico$" ! + ProxyPass / http://127.0.0.1:58080/ + ProxyPassReverse / http://127.0.0.1:58080/ + ProxyPreserveHost On + RequestHeader set X-Forwarded-Proto expr=%{REQUEST_SCHEME} + + ErrorLog /var/log/httpd/sso_error_log + CustomLog /var/log/httpd/sso_access_log combined + diff --git a/roles/containers_web/files/nginx/conf.d/1-default.conf b/roles/containers_web/files/nginx/conf.d/1-default.conf deleted file mode 100644 index 2ad77cf0..00000000 --- a/roles/containers_web/files/nginx/conf.d/1-default.conf +++ /dev/null @@ -1,14 +0,0 @@ -server { - listen 80 default_server; - listen [::]:80 default_server; - - return 301 https://$host$request_uri; -} - -server { - listen 443 ssl http2 default_server; - listen [::]:443 ssl http2 default_server; - - ssl_reject_handshake on; - return 444; -} diff --git a/roles/containers_web/files/nginx/conf.d/gitea.conf b/roles/containers_web/files/nginx/conf.d/gitea.conf deleted file mode 100644 index a0782ddc..00000000 --- a/roles/containers_web/files/nginx/conf.d/gitea.conf +++ /dev/null @@ -1,25 +0,0 @@ -server { - listen 443 ssl http2; - listen [::]:443 ssl http2; - server_name gitea.chrisx.xyz; - - ssl_certificate /etc/ssl/chrisx.xyz/fullchain.pem; - ssl_certificate_key /etc/ssl/chrisx.xyz/privkey.pem; - - add_header Referrer-Policy "no-referrer"; - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; - add_header X-Content-Type-Options "nosniff"; - add_header X-Robots-Tag "none"; - add_header X-XSS-Protection "1; mode=block"; - - location / { - proxy_pass http://gitea:3000; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location = /robots.txt { - alias /var/www/robots.txt; - } -} diff --git a/roles/containers_web/files/nginx/conf.d/sso.conf b/roles/containers_web/files/nginx/conf.d/sso.conf deleted file mode 100644 index c3124b96..00000000 --- a/roles/containers_web/files/nginx/conf.d/sso.conf +++ /dev/null @@ -1,32 +0,0 @@ -server { - listen 443 ssl http2; - listen [::]:443 ssl http2; - server_name sso.chrisx.xyz; - - ssl_certificate /etc/ssl/chrisx.xyz/fullchain.pem; - ssl_certificate_key /etc/ssl/chrisx.xyz/privkey.pem; - - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; - - location / { - proxy_pass http://keycloak:8080; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_buffer_size 128k; - proxy_buffers 4 256k; - proxy_busy_buffers_size 256k; - } - - location ~ ^/resources/.+/login/keycloak/img/favicon.ico$ { - alias /var/www/favicon.ico; - } - - location = /robots.txt { - alias /var/www/robots.txt; - } - - location = / { - internal; - } -} diff --git a/roles/containers_web/files/nginx/conf.d/vaultwarden.conf b/roles/containers_web/files/nginx/conf.d/vaultwarden.conf deleted file mode 100644 index fb9c5fb9..00000000 --- a/roles/containers_web/files/nginx/conf.d/vaultwarden.conf +++ /dev/null @@ -1,35 +0,0 @@ -server { - listen 443 ssl http2; - listen [::]:443 ssl http2; - server_name vaultwarden.chrisx.xyz; - - ssl_certificate /etc/ssl/chrisx.xyz/fullchain.pem; - ssl_certificate_key /etc/ssl/chrisx.xyz/privkey.pem; - - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; - add_header X-Robots-Tag "none" always; - - location / { - proxy_pass http://vaultwarden:8288; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /notifications/hub { - proxy_pass http://vaultwarden:3012; - proxy_set_header Connection "upgrade"; - proxy_set_header Upgrade $http_upgrade; - } - - location /notifications/hub/negotiate { - proxy_pass http://vaultwarden:8288; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location = /robots.txt { - alias /var/www/robots.txt; - } -} diff --git a/roles/containers_web/files/nginx/nginx.conf b/roles/containers_web/files/nginx/nginx.conf deleted file mode 100644 index c840e85d..00000000 --- a/roles/containers_web/files/nginx/nginx.conf +++ /dev/null @@ -1,47 +0,0 @@ -pid /var/run/nginx.pid; -user nobody; -worker_processes auto; -worker_rlimit_nofile 65535; - -error_log /var/log/nginx/error.log error; - -events { - multi_accept on; - worker_connections 2048; -} - -http { - charset utf-8; - sendfile on; - tcp_nopush on; - tcp_nodelay on; - server_tokens off; - log_not_found off; - types_hash_max_size 2048; - client_max_body_size 50M; - - include mime.types; - default_type application/octet-stream; - - ssl_session_timeout 1d; - ssl_session_cache shared:SSL:10m; - ssl_session_tickets off; - ssl_protocols TLSv1.3; - ssl_prefer_server_ciphers off; - ssl_stapling on; - ssl_stapling_verify on; - - gzip on; - gzip_vary on; - gzip_min_length 1024; - gzip_proxied expired no-cache no-store private auth; - gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml; - gzip_disable "MSIE [1-6]\."; - - set_real_ip_from 10.89.0.0/24; - real_ip_header X-Forwarded-For; - - access_log /var/log/nginx/access.log; - - include /etc/nginx/conf.d/*.conf; -} diff --git a/roles/containers_web/files/nginx/www/robots.txt b/roles/containers_web/files/nginx/www/robots.txt deleted file mode 100644 index 1f53798b..00000000 --- a/roles/containers_web/files/nginx/www/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -User-agent: * -Disallow: / diff --git a/roles/containers_web/tasks/apache.yml b/roles/containers_web/tasks/apache.yml new file mode 100644 index 00000000..f9a853cb --- /dev/null +++ b/roles/containers_web/tasks/apache.yml @@ -0,0 +1,35 @@ +- name: Copy favicon + ansible.builtin.copy: + src: favicon.ico + dest: /var/www/html + mode: 0644 + +- name: Generate new OIDC crypto password + ansible.builtin.set_fact: + oidc_crypto: "{{ lookup('password', '/dev/null length=64') }}" +- name: Copy vaultwarden site config + ansible.builtin.template: + src: vaultwarden.conf.j2 + dest: /etc/httpd/conf.d/vaultwarden.conf + mode: 0640 +- name: Copy Apache site config + ansible.builtin.copy: + src: httpd/conf.d + dest: /etc/httpd + mode: 0640 + +- name: Copy chrisx ssl certs + ansible.builtin.import_role: + name: sslcert + tasks_from: chrisx.yml + +- name: Configure SELinux httpd_can_network_connect + ansible.posix.seboolean: + name: httpd_can_network_connect + persistent: true + state: true + +- name: Restart httpd + ansible.builtin.systemd: + name: httpd + state: restarted diff --git a/roles/containers_web/tasks/main.yml b/roles/containers_web/tasks/main.yml index b3d0f08a..de204e8b 100644 --- a/roles/containers_web/tasks/main.yml +++ b/roles/containers_web/tasks/main.yml @@ -1,28 +1,16 @@ - name: Load secret vars ansible.builtin.include_vars: secrets.yml -- name: Create containers directory - ansible.builtin.file: - path: ~/containers - state: directory - mode: 0700 - register: _containers_dir - -- name: Configure Nginx - ansible.builtin.import_tasks: nginx.yml -- name: Copy helper scripts - ansible.builtin.import_tasks: scripts.yml - - name: Set up Postgres DBs ansible.builtin.import_tasks: postgres_db.yml become_user: postgres delegate_to: postgres -- name: Create container network - containers.podman.podman_network: - name: ct - state: present - - name: Launch containers ansible.builtin.import_role: name: containers + +- name: Configure Apache sites + ansible.builtin.import_tasks: apache.yml +- name: Copy helper scripts + ansible.builtin.import_tasks: scripts.yml diff --git a/roles/containers_web/tasks/nginx.yml b/roles/containers_web/tasks/nginx.yml deleted file mode 100644 index 62a3dca5..00000000 --- a/roles/containers_web/tasks/nginx.yml +++ /dev/null @@ -1,17 +0,0 @@ -- name: Create Nginx logs directory - ansible.builtin.file: - path: "{{ _containers_dir.path }}/log/nginx" - state: directory - mode: 0700 - register: _nginx_log_dir - -- name: Copy Nginx files - ansible.builtin.copy: - src: nginx - dest: "{{ _containers_dir.path }}" - mode: 0644 - -- name: Copy chrisx ssl certs - ansible.builtin.include_role: - name: sslcert - tasks_from: chrisx.yml diff --git a/roles/containers_web/tasks/scripts.yml b/roles/containers_web/tasks/scripts.yml index f7ddc016..c6717f63 100644 --- a/roles/containers_web/tasks/scripts.yml +++ b/roles/containers_web/tasks/scripts.yml @@ -1,17 +1,3 @@ -- name: Copy logrotate config - ansible.builtin.template: - src: logrotate.conf.j2 - dest: "{{ _containers_dir.path }}/log/logrotate.conf" - mode: 0644 -- name: Set up logrotate cron job - ansible.builtin.cron: - name: container logrotate - # yamllint disable-line rule:line-length - job: "/usr/sbin/logrotate -s {{ _containers_dir.path }}/log/logrotate.state {{ _containers_dir.path }}/log/logrotate.conf" - hour: 2 - minute: 0 - state: present - - name: Copy backup script ansible.builtin.template: src: backup.sh diff --git a/roles/containers_web/templates/logrotate.conf.j2 b/roles/containers_web/templates/logrotate.conf.j2 deleted file mode 100644 index 8289e638..00000000 --- a/roles/containers_web/templates/logrotate.conf.j2 +++ /dev/null @@ -1,13 +0,0 @@ -{{ _nginx_log_dir.path }}/*.log { - daily - missingok - rotate 30 - compress - delaycompress - notifempty - create 644 root root - sharedscripts - postrotate - podman exec nginx nginx -s reload - endscript -} diff --git a/roles/containers_web/templates/vaultwarden.conf.j2 b/roles/containers_web/templates/vaultwarden.conf.j2 new file mode 100644 index 00000000..8b2213ee --- /dev/null +++ b/roles/containers_web/templates/vaultwarden.conf.j2 @@ -0,0 +1,49 @@ + + ServerName vaultwarden.chrisx.xyz + SSLEngine on + SSLCertificateFile /etc/ssl/chrisx.xyz/fullchain.pem + SSLCertificateKeyFile /etc/ssl/chrisx.xyz/privkey.pem + Protocols h2 http/1.1 + + Header unset Referrer-Policy + Header unset X-XSS-Protection + Header always set Referrer-Policy "no-referrer" + Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + Header always set X-Robots-Tag "none" + Header always set X-XSS-Protection "1 mode=block" + + ProxyPass /admin/logout ! + ProxyPass /robots.txt ! + ProxyPass / http://127.0.0.1:58288/ + ProxyPassReverse / http://127.0.0.1:58288/ + ProxyPreserveHost On + RequestHeader set X-Forwarded-Proto expr=%{REQUEST_SCHEME} + + RewriteEngine on + RewriteCond %{HTTP:Upgrade} =websocket [NC] + RewriteRule /notifications/hub(.*) ws://127.0.0.1:53012/$1 [P,L] + Redirect 302 /admin/logout /admin/openid-connect?logout= + + ErrorLog /var/log/httpd/vaultwarden_error_log + CustomLog /var/log/httpd/vaultwarden_access_log combined + + OIDCClientID {{ vaultwarden_oidc.client_id }} + OIDCClientSecret {{ vaultwarden_oidc.client_secret }} + OIDCPassClaimsAs none + OIDCProviderMetadataURL https://sso.chrisx.xyz/realms/chrisx/.well-known/openid-configuration + OIDCRedirectURI https://vaultwarden.chrisx.xyz/admin/openid-connect + OIDCScope openid + OIDCSessionInactivityTimeout 3600 + OIDCStateMaxNumberOfCookies 2 true + OIDCCryptoPassphrase {{ oidc_crypto }} + + + AuthType openid-connect + Require valid-user + + + + AuthType openid-connect + Require claim roles:admin + + diff --git a/roles/containers_web/vars/main.yml b/roles/containers_web/vars/main.yml index d9e4aa17..86192d57 100644 --- a/roles/containers_web/vars/main.yml +++ b/roles/containers_web/vars/main.yml @@ -12,7 +12,6 @@ container_volumes: container_specs: - name: gitea image: docker.io/gitea/gitea:1.17.4-rootless - network: ct env: GITEA____APP_NAME: "Gitea" GITEA__cache__ENABLED: "true" @@ -32,7 +31,7 @@ container_specs: GITEA__picture__DISABLE_GRAVATAR: "true" GITEA__picture__ENABLE_FEDERATED_AVATAR: "false" GITEA__security__INSTALL_LOCK: "true" - GITEA__security__REVERSE_PROXY_TRUSTED_PROXIES: "10.88.0.0/24" + GITEA__security__REVERSE_PROXY_TRUSTED_PROXIES: "10.88.0.1" GITEA__server__DOMAIN: "gitea.chrisx.xyz" GITEA__server__LANDING_PAGE: "login" GITEA__server__OFFLINE_MODE: "true" @@ -54,13 +53,13 @@ container_specs: GITEA__ui__SHOW_USER_EMAIL: "false" ports: - "2222:2222" + - "127.0.0.1:53000:3000" volumes: - gitea_conf:/etc/gitea:Z - gitea_data:/var/lib/gitea:Z - name: keycloak image: quay.io/keycloak/keycloak:20.0.2 command: start - network: ct env: KC_DB: "postgres" # yamllint disable-line rule:line-length @@ -69,15 +68,17 @@ container_specs: KC_DB_PASSWORD: "{{ postgres_keycloak_password }}" KC_HOSTNAME: "sso.chrisx.xyz" KC_PROXY: "edge" + ports: + - "127.0.0.1:58080:8080" cap_drop: - all - name: vaultwarden image: docker.io/vaultwarden/server:latest - network: ct user: nobody env: # yamllint disable-line rule:line-length DATABASE_URL: "postgres://vaultwarden:{{ postgres_vaultwarden_password }}@postgres.arb.chrisx.xyz/vaultwarden?sslmode=verify-full&sslrootcert=/etc/ssl/certs/ca-certificates.crt" + DISABLE_ADMIN_TOKEN: "true" DOMAIN: "https://vaultwarden.chrisx.xyz" EVENTS_DAYS_RETAIN: "30" IP_HEADER: "X-Forwarded-For" @@ -90,22 +91,13 @@ container_specs: SMTP_PORT: "465" SMTP_SECURITY: "force_tls" WEBSOCKET_ENABLED: "true" + ports: + - "127.0.0.1:53012:3012" + - "127.0.0.1:58288:8288" volumes: - vaultwarden_data:/data:Z cap_drop: - all - - name: nginx - image: docker.io/library/nginx:alpine - network: ct - ports: - - "80:80" - - "443:443" - volumes: - - "{{ _containers_dir.path }}/nginx/conf.d:/etc/nginx/conf.d:Z" - - "{{ _containers_dir.path }}/nginx/www:/var/www:Z" - - "{{ _containers_dir.path }}/nginx/nginx.conf:/etc/nginx/nginx.conf:Z" - - "{{ _nginx_log_dir.path }}:/var/log/nginx:z" - - "/etc/ssl:/etc/ssl:z" - name: cloudflared image: docker.io/cloudflare/cloudflared:latest command: tunnel run --token {{ cloudflared_token }} diff --git a/roles/httpd/files/1_default.conf b/roles/httpd/files/1_default.conf index 3ac59acc..5752b87b 100644 --- a/roles/httpd/files/1_default.conf +++ b/roles/httpd/files/1_default.conf @@ -3,8 +3,8 @@ ServerSignature Off ServerTokens Prod TraceEnable Off -RemoteIPHeader X-Forwarded-For -RemoteIPTrustedProxy 10.69.0.11 +KeepAlive On +KeepAliveTimeout 5 SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 -TLSv1.2 SSLHonorCipherOrder Off diff --git a/setup.yml b/setup.yml index f1824ac0..f13315c5 100644 --- a/setup.yml +++ b/setup.yml @@ -67,5 +67,6 @@ - "distribution" - "virtualization_type" roles: + - httpd - containers_web - tailscale