From 71dbda62627822fb3bd2f6eb04dad08226a9c8ec Mon Sep 17 00:00:00 2001 From: oddlama Date: Thu, 22 Jun 2023 02:55:22 +0200 Subject: [PATCH] feat: promote oauth proxy config to a nginx virtualHosts option --- hosts/sentinel/oauth2.nix | 12 +- .../sentinel/secrets/oauth2-proxy-secret.age | Bin 476 -> 561 bytes hosts/ward/microvms/adguardhome/default.nix | 5 +- hosts/ward/microvms/grafana/default.nix | 2 +- hosts/ward/microvms/loki/default.nix | 4 +- .../loki/secrets/loki-basic-auth-hashes.age | Bin 1116 -> 1200 bytes modules/extra.nix | 33 +++- modules/oauth2-proxy.nix | 178 ++++++++++-------- 8 files changed, 136 insertions(+), 98 deletions(-) diff --git a/hosts/sentinel/oauth2.nix b/hosts/sentinel/oauth2.nix index 649c067..dc5e732 100644 --- a/hosts/sentinel/oauth2.nix +++ b/hosts/sentinel/oauth2.nix @@ -17,7 +17,6 @@ }; services.oauth2_proxy = { - # TODO cookie refresh provider = "oidc"; scope = "openid"; loginURL = "https://${config.proxiedDomains.kanidm}/ui/oauth2"; @@ -25,9 +24,16 @@ validateURL = "https://${config.proxiedDomains.kanidm}/oauth2/openid/web-sentinel/userinfo"; clientID = "web-sentinel"; keyFile = config.age.secrets.oauth2-proxy-secret.path; - email.domains = ["*"]; - extraConfig.skip-provider-button = true; + extraConfig = { + # TODO good idea? would fail when offline + # TODO autorestart after 30 minutes, infinite times. + oidc-issuer-url = "https://${config.proxiedDomains.kanidm}/oauth2/openid/web-sentinel"; + skip-provider-button = true; + + # TODO away + show-debug-on-error = true; + }; }; } diff --git a/hosts/sentinel/secrets/oauth2-proxy-secret.age b/hosts/sentinel/secrets/oauth2-proxy-secret.age index 4b09340d7bb252d2e6b1d5d3392c77e169d02f2f..8208406a9489a1fe5baa0f5b271f8ebb05760a83 100644 GIT binary patch delta 540 zcmV+%0^|MM1F-~LUl_+b7FXFLPc>#HdSy| zHh4-@MG9(CS!YC8dMiptD{yRbO-N91P*6=~OhtJ(RWxTcaBMG6Ms_h~MRi7PcM2^& zAaH4REpRe5HXwL$Q)M_&AVFzrNM&|DLq$kXF@H%+D{g96PgZSMPHc2E zVo_9iXG1GVX-EoWX;N!vH&S^uV{|xEW<^mjNl0x=ctSEbM|E>VPijVUbW=uHL}^t} zG*t>MJ|I>tXL4m>b7cy3QEGE;IXOd3IdnL4V`Fh^Xl-tHLsM@;a$#?Ab7oUeNm+Mc zL`6hLFHd=8IDb=WLQYsya8zYeF;`S^VP;KD3Q_)D%P2_|r}Cu|O8`5T!}Ka6w2Ois@O5Syg+Gaz6gF!p72 eQ=$a|Bo&eyuuy>Aa_+Q-ts_#4y&Uj6yLyzN@yeY5 delta 454 zcmWm7J&V&|007_9aIQk33=MOwDFMYrG0N<*5ldGBmof+*!n0PtVrfxdr z6uqvyY-h0Ht>XHbU(4m)}mWrV$1(ClNTd7n> zEX(Ut?C99;g})BZD}Q$$-@Lbr*I(a$5>uDoyw5LuzOwu0_oMsg&t9B-+Btjrg;<}? nPQEFXAJ0F&I~yEqe;$ARP_Z7KwvWoIH?GmCaQ)TWidFpwjQXFp diff --git a/hosts/ward/microvms/adguardhome/default.nix b/hosts/ward/microvms/adguardhome/default.nix index 6f6aeb4..82b9936 100644 --- a/hosts/ward/microvms/adguardhome/default.nix +++ b/hosts/ward/microvms/adguardhome/default.nix @@ -24,7 +24,6 @@ in { nodes.sentinel = { proxiedDomains.adguard = adguardhomeDomain; - extra.oauth2_proxy.nginx.virtualHosts."${adguardhomeDomain}".allowedGroups = ["adguardhome"]; services.nginx = { upstreams.adguardhome = { servers."${config.services.adguardhome.settings.bind_host}:${toString config.services.adguardhome.settings.bind_port}" = {}; @@ -36,8 +35,10 @@ in { virtualHosts.${adguardhomeDomain} = { forceSSL = true; useACMEHost = sentinelCfg.lib.extra.matchingWildcardCert adguardhomeDomain; + oauth2.enable = true; + oauth2.allowedGroups = ["adguardhome"]; locations."/" = { - proxyPass = "https://adguardhome"; + proxyPass = "http://adguardhome"; proxyWebsockets = true; }; }; diff --git a/hosts/ward/microvms/grafana/default.nix b/hosts/ward/microvms/grafana/default.nix index 7a1fa23..922528b 100644 --- a/hosts/ward/microvms/grafana/default.nix +++ b/hosts/ward/microvms/grafana/default.nix @@ -54,7 +54,7 @@ in { forceSSL = true; useACMEHost = sentinelCfg.lib.extra.matchingWildcardCert grafanaDomain; locations."/" = { - proxyPass = "https://grafana"; + proxyPass = "http://grafana"; proxyWebsockets = true; }; }; diff --git a/hosts/ward/microvms/loki/default.nix b/hosts/ward/microvms/loki/default.nix index ea64340..740af48 100644 --- a/hosts/ward/microvms/loki/default.nix +++ b/hosts/ward/microvms/loki/default.nix @@ -45,7 +45,7 @@ in { forceSSL = true; useACMEHost = sentinelCfg.lib.extra.matchingWildcardCert lokiDomain; locations."/" = { - proxyPass = "https://loki"; + proxyPass = "http://loki"; proxyWebsockets = true; extraConfig = '' auth_basic "Authentication required"; @@ -58,7 +58,7 @@ in { ''; }; locations."= /ready" = { - proxyPass = "https://loki"; + proxyPass = "http://loki"; extraConfig = '' auth_basic off; access_log off; diff --git a/hosts/ward/microvms/loki/secrets/loki-basic-auth-hashes.age b/hosts/ward/microvms/loki/secrets/loki-basic-auth-hashes.age index 56c4719b40825e89b1eb1e9f66d7b6320fdf8118..8c1a251586fbcff46fbc8b8f0cac8837e60ea2d5 100644 GIT binary patch delta 1184 zcmV;R1Yi5y2(Sr|Ab&=0Q9(3nV`y$^SWIIyOHN2KIYciva8hkBcQH#sYj=2AV@!5O zS4B@%O$tLnb$TyTba8WUYAZ@NdSW;-T5MTPdT((-ODkwkWkGRNd1+O8NltEKRSGRW zAaH4REpRe5HXwL$Q)M_&AVGC8Mpe>FECF}Y;QPcMPWi?PfTJ_K|@eZQc^Eb7de$d1p5-F$zpHPH0d;3N0-yAVp~~a5FYpc`rg(bV4*T zcxPv3Xh>N}Vt;Z=X=ZR(T4FJ4G)gj7M0aE~YYGFxp@GG^aVmMG&BHFq-uM|$R6jz8 zLa@$yM91sBtFAN-9rR({&H30b2B=!0lYgS&XyKrDd7m?cu3B5WOO)&k zd2IL?^1?bMVkK*!{(%nH2iR3DrtzTwZ{?gp&FJ8mo;O=QSr*LLqfbzN0HPIl@v$cZB4z>fT z$m4p*1X>JQ)uuGKBfaXnS#40}j24Uqf}b_+Un-}2U1=5RQvc;6DADoOb1KX^#T3dA zO+f91JcV0<>%JAD{BGN)`tMRpTU;Ef0~d{Z{(qK2pwuA8{hA8s%yN&8)1KF`xw5&F z1mjemGKN~=pi!)h2Sc{t?rR{b{FjGsN{o-A}wPZ;V2vZ3=)5TteX#pJR`OwSh#JOhw z?ti?tTrKg5cRk-LG3-uzDFFl`V#qsbw-Ie#HX8;gAqXSEpO^F1U)riYU`3_nV9?MW z?66-j*Wpscids;OD%M6!$%oV9m=?2%{_rL@CBXRZqM7_f$roy|PXfHzEMJC2Y!$rk z2$_ZW(_U?Ip$h-@hD_f=kMC;}a%L&hFn^2O#tP?E7G2YNu3E8#79C~A5kHhdSqX?Z zcCn1YZCw355Xl<=)qBgio?T9tDO3NN!AnU45sXxWVJKV1nV}K4B$_H08=l{)kWimeSddbL^aOfeSAo1Mue>$ec5Xa;(xnf z&U|9YFav8`l2d!7I>)=DQB(0Q$)ZI*p8np_IW}%iNzIa(T})W9J!$R2MFE4$w|ZIN zQ8A_WQH&}O`e#t2c{3J`ld}k;<)GQ<{k0=Bae44{mRczLsM_)cW*~Fb3$`XVnan}YA;k`Lqm5mV{BGqS88H$X9_Jo zAaH4REpRe5HXwL$Q)M_&AVGO|M{jXibWKHXN^MU=MOt-vRev=|S!F?NSz~Zgbz*LC zGBI&#IWkpdMlcFfYe+>eT1ZG%MRrFuQbSrbT4PH{MR90Sa&Kj4Y&BUlRd#M+bX7+; zK|u;FJ|Jc>Su;gTEoX9NVRL05F-0RqP+lNFOF1B9D{M_9TU8))bwqtZL~aUHQcQ4i zK?*G`Eg(}wT7N4@R!dEBL03d&R903uX>3Sqaa3?^PDw#XM_NdFRY_u4bWnOxW@`#S zChujRV=u?GvHW!W$*a4UlLW`DlsZty(AmF`xieM_WU)u_e4q!Rwv6!j@~gHdqo z5+m1M|7Ing__tM;0fCi4F&eH$=RHfm`nxCw$rE7Zaev6*q+gEp=RbINLX^*8rQ>WT zZcadW{W7OP7=BEqXk9}v`|t;Ql6}EXQ^T#Vkc1n}R-L@l9K-=xdUDPB%bd1FDf(tN zFe&yV@NFa;)(6o%`YWXcu$hgaPoDN&C%a2#gMqa#z@1b;;ErW-z_Y;pWqn>LfZsyc zQzU9yFn>PFU$m}Yn2B-rBD`U)^NMz|;AEns8A zk89!Adj!*J>^`JC?6{W#gjy-6*C5(EyF_j`1AmzL%Sjqg;kD9Bm%B@rHpmBitLYh# z{^QP0q9q2UW--F^Gooi&0t9E+WZa|3*dGB;p(j8_Bw|+MEr-`gyGys{4SevfTm8a?|G)0`pOTYECqEu!hG;_K$N#{nMWugS1T}WAdefXJO8zZJRmjVR45$N;c+`)&Z Rf8B7^HO}wliM7nz8d!cy{HOo` diff --git a/modules/extra.nix b/modules/extra.nix index 8041604..97aa425 100644 --- a/modules/extra.nix +++ b/modules/extra.nix @@ -37,6 +37,29 @@ in { }; }; + options.services.nginx.virtualHosts = mkOption { + type = types.attrsOf (types.submodule ({config, ...}: { + options.recommendedSecurityHeaders = mkOption { + type = types.bool; + default = true; + description = mdDoc ''Whether to add additional security headers to the "/" location.''; + }; + config = mkIf config.recommendedSecurityHeaders { + locations."/".extraConfig = '' + # Enable HTTP Strict Transport Security (HSTS) + add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; + + # Minimize information leaked to other domains + add_header Referrer-Policy "origin-when-cross-origin"; + + add_header X-XSS-Protection "1; mode=block"; + add_header X-Frame-Options "DENY"; + add_header X-Content-Type-Options "nosniff"; + ''; + }; + })); + }; + config = { lib.extra = { # For a given domain, this searches for a matching wildcard acme domain that @@ -80,16 +103,6 @@ in { error_log syslog:server=unix:/dev/log; access_log syslog:server=unix:/dev/log; ssl_ecdh_curve secp384r1; - - # Enable HTTP Strict Transport Security (HSTS) - add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; - - # Minimize information leaked to other domains - add_header Referrer-Policy "origin-when-cross-origin"; - - add_header X-XSS-Protection "1; mode=block"; - add_header X-Frame-Options "DENY"; - add_header X-Content-Type-Options "nosniff"; ''; }; diff --git a/modules/oauth2-proxy.nix b/modules/oauth2-proxy.nix index c4dbd1e..19439a4 100644 --- a/modules/oauth2-proxy.nix +++ b/modules/oauth2-proxy.nix @@ -37,13 +37,24 @@ in { nginx.virtualHosts = mkOption { default = {}; - description = mdDoc '' - Access to all virtualHostst that have an entry here will be put behind - the oauth2 proxy, requiring authentication before users can access the resource. - Also set `allowedGroups` for granuar access control. - ''; - type = types.attrsOf (types.submodule { - options.allowedGroups = mkOption { + description = + mdDoc '' + ''; + type = + types.attrsOf (types.submodule { + }); + }; + }; + + options.services.nginx.virtualHosts = mkOption { + type = types.attrsOf (types.submodule ({ + name, + config, + ... + }: { + options.oauth2 = { + enable = mkEnableOption (mdDoc "access protection of this resource using oauth2_proxy."); + allowedGroups = mkOption { type = types.listOf types.str; default = []; description = mdDoc '' @@ -51,21 +62,73 @@ in { empty list to allow any authenticated client. ''; }; - }); - }; + }; + config = mkIf config.oauth2.enable { + locations."/".extraConfig = '' + auth_request /oauth2/auth; + error_page 401 = /oauth2/sign_in; + + # pass information via X-User and X-Email headers to backend, + # requires running with --set-xauthrequest flag + auth_request_set $user $upstream_http_x_auth_request_user; + auth_request_set $email $upstream_http_x_auth_request_email; + proxy_set_header X-User $user; + proxy_set_header X-Email $email; + + # if you enabled --cookie-refresh, this is needed for it to work with auth_request + auth_request_set $auth_cookie $upstream_http_set_cookie; + add_header Set-Cookie $auth_cookie; + ''; + + locations."/oauth2/" = { + proxyPass = "https://${cfg.authProxyDomain}"; + extraConfig = '' + proxy_set_header X-Scheme $scheme; + proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri; + ''; + }; + + locations."= /oauth2/auth" = { + proxyPass = + "https://${cfg.authProxyDomain}" + + optionalString (config.oauth2.allowedGroups != []) + "?allowed_groups=${concatStringsSep "," config.oauth2.allowedGroups}"; + extraConfig = '' + internal; + + proxy_set_header X-Scheme $scheme; + # nginx auth_request includes headers but not body + proxy_set_header Content-Length ""; + proxy_pass_request_body off; + ''; + }; + }; + })); }; config = mkIf cfg.enable { services.oauth2_proxy = { enable = true; + + cookie.domain = ".${cfg.cookieDomain}"; cookie.secure = true; cookie.httpOnly = false; + cookie.refresh = "5m"; + cookie.expire = "30m"; + reverseProxy = true; httpAddress = "unix:///run/oauth2_proxy/oauth2_proxy.sock"; - - # Share the cookie with all subpages - extraConfig.whitelist-domain = ".${cfg.cookieDomain}"; setXauthrequest = true; + + extraConfig = { + # Share the cookie with all subpages + whitelist-domain = ".${cfg.cookieDomain}"; + set-authorization-header = true; + pass-access-token = true; + skip-jwt-bearer-tokens = true; + upstream = "static://202"; + # TODO allowed group? + }; }; systemd.services.oauth2_proxy.serviceConfig = { @@ -84,78 +147,33 @@ in { ''; }; - virtualHosts = - { - ${cfg.authProxyDomain} = { - forceSSL = true; - useACMEHost = config.lib.extra.matchingWildcardCert cfg.authProxyDomain; + virtualHosts.${cfg.authProxyDomain} = { + forceSSL = true; + useACMEHost = config.lib.extra.matchingWildcardCert cfg.authProxyDomain; - locations."/".extraConfig = '' - deny all; - return 404; - ''; + locations."/".extraConfig = '' + deny all; + return 404; + ''; - locations."/oauth2/" = { - proxyPass = "http://oauth2_proxy"; - extraConfig = '' - proxy_set_header X-Scheme $scheme; - proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri; - ''; - }; - - locations."= /oauth2/auth" = { - proxyPass = "http://oauth2_proxy"; - extraConfig = '' - proxy_set_header X-Scheme $scheme; - # nginx auth_request includes headers but not body - proxy_set_header Content-Length ""; - proxy_pass_request_body off; - ''; - }; - }; - } - // flip mapAttrs cfg.nginx.virtualHosts - (vhost: vhostCfg: let - authQuery = - optionalString (vhostCfg.allowedGroups != []) - "?allowed_groups=${concatStringsSep "," vhostCfg.allowedGroups}"; - in { - locations."/".extraConfig = '' - auth_request /oauth2/auth; - error_page 401 = /oauth2/sign_in; - - # pass information via X-User and X-Email headers to backend, - # requires running with --set-xauthrequest flag - auth_request_set $user $upstream_http_x_auth_request_user; - auth_request_set $email $upstream_http_x_auth_request_email; - proxy_set_header X-User $user; - proxy_set_header X-Email $email; - - # if you enabled --cookie-refresh, this is needed for it to work with auth_request - auth_request_set $auth_cookie $upstream_http_set_cookie; - add_header Set-Cookie $auth_cookie; + locations."/oauth2/" = { + proxyPass = "http://oauth2_proxy"; + extraConfig = '' + proxy_set_header X-Scheme $scheme; + proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri; ''; + }; - locations."/oauth2/" = { - proxyPass = "https://${cfg.authProxyDomain}"; - extraConfig = '' - proxy_set_header X-Scheme $scheme; - proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri; - ''; - }; - - locations."= /oauth2/auth" = { - proxyPass = "https://${cfg.authProxyDomain}${authQuery}"; - extraConfig = '' - internal; - - proxy_set_header X-Scheme $scheme; - # nginx auth_request includes headers but not body - proxy_set_header Content-Length ""; - proxy_pass_request_body off; - ''; - }; - }); + locations."= /oauth2/auth" = { + proxyPass = "http://oauth2_proxy"; + extraConfig = '' + proxy_set_header X-Scheme $scheme; + # nginx auth_request includes headers but not body + proxy_set_header Content-Length ""; + proxy_pass_request_body off; + ''; + }; + }; }; }; }