From faf43de14f7e8f9d68455d024e34b2ec2886ea41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Michaud?= Date: Tue, 3 Mar 2020 08:18:25 +0100 Subject: [PATCH] [FEATURE] Add TLS support. (#677) * [FEATURE] Add TLS support. Fixes #368. * [FEATURE] Introduce OnError hook in suites. This hook allows to perform actions following an erroneous suite like displaying the logs of Authelia. * Display Authelia logs of Standalone suite when tests fail. * Fix Standalone suite. * Apply suggestions from code review * Rename ssl_key and ssl_cert into tls_key and tls_cert. --- cmd/authelia-scripts/cmd_suites.go | 24 +- cmd/authelia-suites/main.go | 23 +- config.template.yml | 2 + docs/configuration/miscellaneous.md | 14 +- .../configuration/schema/configuration.go | 7 +- .../configuration/validator/configuration.go | 6 + .../validator/configuration_test.go | 30 +++ internal/server/server.go | 17 +- internal/suites/Standalone/configuration.yml | 2 + internal/suites/Standalone/docker-compose.yml | 1 + internal/suites/Standalone/ssl/cert.pem | 19 ++ internal/suites/Standalone/ssl/key.pem | 28 +++ internal/suites/constants.go | 2 +- .../compose/authelia/resources/nginx.conf | 10 + .../nginx/portal/docker-compose.https.yml | 16 ++ .../compose/nginx/portal/nginx.https.conf | 227 ++++++++++++++++++ internal/suites/registry.go | 9 +- .../scenario_backend_protection_test.go | 2 + internal/suites/suite_standalone.go | 7 +- 19 files changed, 428 insertions(+), 18 deletions(-) create mode 100644 internal/suites/Standalone/ssl/cert.pem create mode 100644 internal/suites/Standalone/ssl/key.pem create mode 100644 internal/suites/example/compose/nginx/portal/docker-compose.https.yml create mode 100644 internal/suites/example/compose/nginx/portal/nginx.https.conf diff --git a/cmd/authelia-scripts/cmd_suites.go b/cmd/authelia-scripts/cmd_suites.go index 9f3b93baa..eb097427d 100644 --- a/cmd/authelia-scripts/cmd_suites.go +++ b/cmd/authelia-scripts/cmd_suites.go @@ -151,6 +151,14 @@ func runOnSetupTimeout(suite string) error { return utils.RunCommandWithTimeout(cmd, 15*time.Second) } +func runOnError(suite string) error { + cmd := utils.CommandWithStdout("go", "run", "cmd/authelia-suites/main.go", "error", suite) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Env = os.Environ() + return utils.RunCommandWithTimeout(cmd, 15*time.Second) +} + func setupSuite(suiteName string) error { log.Infof("Setup environment for suite %s...", suiteName) signalChannel := make(chan os.Signal) @@ -261,11 +269,19 @@ func runSuiteTests(suiteName string, withEnv bool) error { testErr := cmd.Run() - if withEnv { - err := teardownSuite(suiteName) + // If the tests failed, run the error hook. + if testErr != nil { + if err := runOnError(suiteName); err != nil { + // Do not return this error to return the test error instead + // and not skip the teardown phase. + log.Errorf("Error executing OnError callback: %v", err) + } + } - if err != nil { - log.Error(err) + if withEnv { + if err := teardownSuite(suiteName); err != nil { + // Do not return this error to return the test error instead + log.Errorf("Error running teardown: %v", err) } } diff --git a/cmd/authelia-suites/main.go b/cmd/authelia-suites/main.go index 2ac03c3a2..4a75aff42 100644 --- a/cmd/authelia-suites/main.go +++ b/cmd/authelia-suites/main.go @@ -38,6 +38,12 @@ func main() { Run: setupTimeoutSuite, } + errorCmd := &cobra.Command{ + Use: "error [suite]", + Short: "Run the OnError callback when some tests fail", + Run: runErrorCallback, + } + stopCmd := &cobra.Command{ Use: "teardown [suite]", Short: "Teardown the suite environment", @@ -46,8 +52,11 @@ func main() { rootCmd.AddCommand(startCmd) rootCmd.AddCommand(setupTimeoutCmd) + rootCmd.AddCommand(errorCmd) rootCmd.AddCommand(stopCmd) - rootCmd.Execute() + if err := rootCmd.Execute(); err != nil { + log.Fatal(err) + } } func createRunningSuiteFile(suite string) error { @@ -120,6 +129,18 @@ func setupTimeoutSuite(cmd *cobra.Command, args []string) { } } +func runErrorCallback(cmd *cobra.Command, args []string) { + suiteName := args[0] + s := suites.GlobalRegistry.Get(suiteName) + + if s.OnError == nil { + return + } + if err := s.OnError(); err != nil { + log.Fatal(err) + } +} + func teardownSuite(cmd *cobra.Command, args []string) { if os.Getenv("SKIP_TEARDOWN") != "" { return diff --git a/config.template.yml b/config.template.yml index 30c2422e3..b5a8b2384 100644 --- a/config.template.yml +++ b/config.template.yml @@ -5,6 +5,8 @@ # The host and port to listen on host: 0.0.0.0 port: 9091 +# tls_key: /var/lib/authelia/ssl/key.pem +# tls_cert: /var/lib/authelia/ssl/cert.pem # Level of verbosity for logs: info, debug, trace logs_level: debug diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md index 178909bf2..246a55c42 100644 --- a/docs/configuration/miscellaneous.md +++ b/docs/configuration/miscellaneous.md @@ -10,6 +10,7 @@ nav_order: 5 Here are the main customizable options in Authelia. ## Host & Port + `optional: true` Defines the address to listen on. @@ -17,6 +18,17 @@ Defines the address to listen on. host: 0.0.0.0 port: 9091 +## TLS + +`optional: true` + +Authelia can use TLS. Provide the certificate and the key with the +following configuration options: + + tls_key: /var/lib/authelia/ssl/key.pem + tls_cert: /var/lib/authelia/ssl/cert.pem + + ## Logs level `optional: true` @@ -50,4 +62,4 @@ can redirect her after the authentication process. However, when a user visits the sign in portal directly, the portal considers the targeted website is the portal. In that case and if the default redirection URL is configured, the user is redirected to that URL. If not defined, the user is not -redirected after authentication. \ No newline at end of file +redirected after authentication. diff --git a/internal/configuration/schema/configuration.go b/internal/configuration/schema/configuration.go index 23b4ebcc9..d7c518cac 100644 --- a/internal/configuration/schema/configuration.go +++ b/internal/configuration/schema/configuration.go @@ -2,8 +2,11 @@ package schema // Configuration object extracted from YAML configuration file. type Configuration struct { - Host string `mapstructure:"host"` - Port int `mapstructure:"port"` + Host string `mapstructure:"host"` + Port int `mapstructure:"port"` + TLSCert string `mapstructure:"tls_cert"` + TLSKey string `mapstructure:"tls_key"` + LogsLevel string `mapstructure:"logs_level"` // This secret is used by the identity validation process to forge JWT tokens diff --git a/internal/configuration/validator/configuration.go b/internal/configuration/validator/configuration.go index 6adbd722a..2e9934bcf 100644 --- a/internal/configuration/validator/configuration.go +++ b/internal/configuration/validator/configuration.go @@ -24,6 +24,12 @@ func Validate(configuration *schema.Configuration, validator *schema.StructValid configuration.LogsLevel = defaultLogsLevel } + if configuration.TLSKey != "" && configuration.TLSCert == "" { + validator.Push(fmt.Errorf("No TLS certificate provided, please check the \"tls_cert\" which has been configured")) + } else if configuration.TLSKey == "" && configuration.TLSCert != "" { + validator.Push(fmt.Errorf("No TLS key provided, please check the \"tls_key\" which has been configured")) + } + if configuration.DefaultRedirectionURL != "" { _, err := url.ParseRequestURI(configuration.DefaultRedirectionURL) if err != nil { diff --git a/internal/configuration/validator/configuration_test.go b/internal/configuration/validator/configuration_test.go index 654f28ba7..24faa68b4 100644 --- a/internal/configuration/validator/configuration_test.go +++ b/internal/configuration/validator/configuration_test.go @@ -99,3 +99,33 @@ func TestShouldAddDefaultAccessControl(t *testing.T) { assert.NotNil(t, config.AccessControl) assert.Equal(t, "deny", config.AccessControl.DefaultPolicy) } + +func TestShouldRaiseErrorWhenTLSCertWithoutKeyIsProvided(t *testing.T) { + validator := schema.NewStructValidator() + config := newDefaultConfig() + config.TLSCert = "/tmp/cert.pem" + + Validate(&config, validator) + require.Len(t, validator.Errors(), 1) + assert.EqualError(t, validator.Errors()[0], "No TLS key provided, please check the \"tls_key\" which has been configured") +} + +func TestShouldRaiseErrorWhenTLSKeyWithoutCertIsProvided(t *testing.T) { + validator := schema.NewStructValidator() + config := newDefaultConfig() + config.TLSKey = "/tmp/key.pem" + + Validate(&config, validator) + require.Len(t, validator.Errors(), 1) + assert.EqualError(t, validator.Errors()[0], "No TLS certificate provided, please check the \"tls_cert\" which has been configured") +} + +func TestShouldNotRaiseErrorWhenBothTLSCertificateAndKeyAreProvided(t *testing.T) { + validator := schema.NewStructValidator() + config := newDefaultConfig() + config.TLSCert = "/tmp/cert.pem" + config.TLSKey = "/tmp/key.pem" + + Validate(&config, validator) + require.Len(t, validator.Errors(), 0) +} diff --git a/internal/server/server.go b/internal/server/server.go index 057c02e16..f09ab2e0e 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -101,9 +101,18 @@ func StartServer(configuration schema.Configuration, providers middlewares.Provi ctx.SendFile(path.Join(publicDir, "index.html")) } - portPattern := fmt.Sprintf("%s:%d", configuration.Host, configuration.Port) - logging.Logger().Infof("Authelia is listening on %s", portPattern) + addrPattern := fmt.Sprintf("%s:%d", configuration.Host, configuration.Port) - logging.Logger().Fatal(fasthttp.ListenAndServe(portPattern, - middlewares.LogRequestMiddleware(router.Handler))) + if configuration.TLSCert != "" && configuration.TLSKey != "" { + logging.Logger().Infof("Authelia is listening on %s and uses TLS", addrPattern) + + logging.Logger().Fatal(fasthttp.ListenAndServeTLS(addrPattern, + configuration.TLSCert, configuration.TLSKey, + middlewares.LogRequestMiddleware(router.Handler))) + } else { + logging.Logger().Infof("Authelia is listening on %s and do not use TLS", addrPattern) + + logging.Logger().Fatal(fasthttp.ListenAndServe(addrPattern, + middlewares.LogRequestMiddleware(router.Handler))) + } } diff --git a/internal/suites/Standalone/configuration.yml b/internal/suites/Standalone/configuration.yml index 6fb5679bb..4fe68db98 100644 --- a/internal/suites/Standalone/configuration.yml +++ b/internal/suites/Standalone/configuration.yml @@ -3,6 +3,8 @@ ############################################################### port: 9091 +tls_cert: /var/lib/authelia/ssl/cert.pem +tls_key: /var/lib/authelia/ssl/key.pem logs_level: debug diff --git a/internal/suites/Standalone/docker-compose.yml b/internal/suites/Standalone/docker-compose.yml index 1371738db..aa7cb4c5f 100644 --- a/internal/suites/Standalone/docker-compose.yml +++ b/internal/suites/Standalone/docker-compose.yml @@ -7,5 +7,6 @@ services: volumes: - './Standalone/configuration.yml:/etc/authelia/configuration.yml:ro' - './Standalone/users.yml:/var/lib/authelia/users.yml' + - './Standalone/ssl:/var/lib/authelia/ssl' - '/tmp:/tmp' user: ${USER_ID}:${GROUP_ID} \ No newline at end of file diff --git a/internal/suites/Standalone/ssl/cert.pem b/internal/suites/Standalone/ssl/cert.pem new file mode 100644 index 000000000..9fabcb421 --- /dev/null +++ b/internal/suites/Standalone/ssl/cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIC/jCCAeagAwIBAgIRAKF0IRxC55eee6icERVf6fgwDQYJKoZIhvcNAQELBQAw +EjEQMA4GA1UEChMHQWNtZSBDbzAgFw0yMDAzMDExMjMzMzlaGA8yMTIwMDIwNjEy +MzMzOVowEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMi7/oSazFIxP3rHsSLjw5XPnpMKEaVwU1zLRzW6W80BDa/ER5to +I3POGLv8lAhtUwB6WvyilrCZfs/D5lkcCxswafU/2LNppFuODnW+PG9eobgOy6Nv +f+KbnZFPRV7PB2yK6DqMyb+tbTQ7F6rEf4i6n28DI0dNyNvUCk0ld3o93LZBvC/D +/+Ulf3Vtdfsd2TckXvdA8lH4VGQJ+FIxhboTlbW8VJlk1V7FZef7+m867kOnPSaj +zv5yygrIA0XPaMAZC/SZrXHMdhvcs43fgmmTel7JD4Sy/Z/pmFlrZr5Xa8jcWycJ +ILLuPnXhgKstgq5wtDkTMZ6rpgMrKcjMKcMCAwEAAaNNMEswDgYDVR0PAQH/BAQD +AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8w +DYILZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBABdWkbipzPivAvvamMmQ +5iPPeStfdr5MBxJGT9nPbeXdtS/13FJnspLBMMYOw/2AZk7VFrNjxkXc4NHZSlGz +FcGMlSO40fyirdYaQTDtS230ucLB+LzfZx37y9dKpEKVmQ151kKJjJ4hAZ47LmAQ +aFoDLRo7PA2HmnJ60GrI9wVp96uy1sQ6PcToIyMcVEQ/tLEEow+ykSeiZb9+qBKV +K9GUcu2LorhBtUMmEWs0TJElaf6eKUoG6JXM2byulDg24w5b9gC26kAlHWc5WDU5 +pAXOjlN/OYHB0sDbYViWIL390376fYIfu2N5EDKY4QjEYsWEs4Wm9HVS9IgHP/Gi +Xbo= +-----END CERTIFICATE----- diff --git a/internal/suites/Standalone/ssl/key.pem b/internal/suites/Standalone/ssl/key.pem new file mode 100644 index 000000000..a190f8b44 --- /dev/null +++ b/internal/suites/Standalone/ssl/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDIu/6EmsxSMT96 +x7Ei48OVz56TChGlcFNcy0c1ulvNAQ2vxEebaCNzzhi7/JQIbVMAelr8opawmX7P +w+ZZHAsbMGn1P9izaaRbjg51vjxvXqG4Dsujb3/im52RT0Vezwdsiug6jMm/rW00 +OxeqxH+Iup9vAyNHTcjb1ApNJXd6Pdy2Qbwvw//lJX91bXX7Hdk3JF73QPJR+FRk +CfhSMYW6E5W1vFSZZNVexWXn+/pvOu5Dpz0mo87+csoKyANFz2jAGQv0ma1xzHYb +3LON34Jpk3peyQ+Esv2f6ZhZa2a+V2vI3FsnCSCy7j514YCrLYKucLQ5EzGeq6YD +KynIzCnDAgMBAAECggEAC13R0LJvRWwyewJZvm8FQTNreEoGq8aLgeKk2p792cLo +gn5ry5n+/+y4q9RmkX+XRpynEE0omUFn09306jDTVCvOpCuEWsxtmR2XJgWqqGfE +Yoa78zo6FJvZNUQ22mKAuh23frFAL1FjsKRz96B+1EA1DPUxhzUZXZFJMAsiE9LZ +PxqPmnqXbPZsOb1XG33TAdCp6CC3H8KHICC+i4IC8prjKHGH/Q1saoNw8jmgwv0S +DelQUbEtqfmE6BmyTGxdeu4uW2Nv/wcENwySAOPKi5gstlbSKTa4IpKGp7CdquWi +stUW6pnSiEeDrDAzwC8uWdncOvnkAy2lRJkz/F9YoQKBgQDrCCqYdvGshecBBnfQ +fowxak2YBfG2jhAKPMHzrvQn5FIb+11x/jeXPEfOB6FShIzZ97JpFIpH3tcONlj3 +OVzGCTD6WdRTcltzXVneJtNog7DliNFY4YmIPmQJ+y+EvJW1rSZTZAZI1Nbijg3n +fSd0PTzvgOGHSl1//RI1mFx7MwKBgQDapIPPSF0yf1UJ6Hhzam5NHGZ9fSqV5Qs0 +Gi7uM08iDV5K7xiPglBkbN2EuMlgVnHaa5g8X897uwRSYR6nL4PRvcJiNSvnhWhe ++K3x7iHewIPYVfcghoqzuPKsXH2Zm26usdXHxBBa3IBbKtGaHnAd9h65AOUYAmAx +C2BzN90XMQKBgE2MjEFyPZunMulrsOziVG+Zm7ClhXOuvCwkj/pPp8/hzhXdgp+y +ObV09lxMuDX59l+VExEI7fd414yg8gngq3PMZJS2PxCpkvMlwhlCxk6d5ShXVHv3 +LuH9dBS3BJ7PerZPQ24QeuJdF+n45S2UZgg8jHaaF9AEAYXRgsicVSdxAoGAJI0U +K/bg/awjv0BJwqGsRt/Ukm32TJC5ysAF0HRrajnp5YULChKy9dbtQV7S63QIHIeY +L5+kw/6DvnHV+gULeGjMsjZJXK8Ev7u6+JLivqZYZDYa1iknztvAVegwZxmA61t3 +bantQgNSwerql2U3QQsAH9Vydw0On6RTP2+7WkECgYBWD3u64hBKmAxPkqPotkgI +w/jdOlv8FLHO79+oH1PtKvkzspcYaecKGDm/RNLIXLYnt0AmZEK4qQ4/zDFaR/rc +AhoxK2cKTRltMrhp1ivtFfLggVGogtYNxEnjnsD4KMvH3SjSNdt06YgtZ92++fOp +UsE8Mpf4/G5X7DmcHJHk+w== +-----END PRIVATE KEY----- diff --git a/internal/suites/constants.go b/internal/suites/constants.go index 968a581d1..14a0980b2 100644 --- a/internal/suites/constants.go +++ b/internal/suites/constants.go @@ -39,4 +39,4 @@ var MX2MailBaseURL = fmt.Sprintf("https://mx2.mail.%s", BaseDomain) var DuoBaseURL = "https://duo.example.com" // AutheliaBaseURL the base URL of Authelia service -var AutheliaBaseURL = "http://authelia.example.com:9091" +var AutheliaBaseURL = "https://authelia.example.com:9091" diff --git a/internal/suites/example/compose/authelia/resources/nginx.conf b/internal/suites/example/compose/authelia/resources/nginx.conf index 163f3ef51..e26661eaf 100644 --- a/internal/suites/example/compose/authelia/resources/nginx.conf +++ b/internal/suites/example/compose/authelia/resources/nginx.conf @@ -14,4 +14,14 @@ http { proxy_pass http://authelia-backend:9091; } } + + server { + # Reach authelia-backend using TLS. + listen 3001; + + location / { + proxy_set_header Host $http_host; + proxy_pass https://authelia-backend:9091; + } + } } \ No newline at end of file diff --git a/internal/suites/example/compose/nginx/portal/docker-compose.https.yml b/internal/suites/example/compose/nginx/portal/docker-compose.https.yml new file mode 100644 index 000000000..cf418a771 --- /dev/null +++ b/internal/suites/example/compose/nginx/portal/docker-compose.https.yml @@ -0,0 +1,16 @@ +version: '3' +services: + nginx-portal: + image: nginx:alpine + volumes: + - ./example/compose/nginx/portal/nginx.https.conf:/etc/nginx/nginx.conf + - ./example/compose/nginx/portal/ssl:/etc/ssl + networks: + authelianet: + aliases: + - public.example.com + - secure.example.com + - login.example.com + - duo.example.com + # Set the IP to be able to query on port 443 + ipv4_address: 192.168.240.100 diff --git a/internal/suites/example/compose/nginx/portal/nginx.https.conf b/internal/suites/example/compose/nginx/portal/nginx.https.conf new file mode 100644 index 000000000..75112c0f7 --- /dev/null +++ b/internal/suites/example/compose/nginx/portal/nginx.https.conf @@ -0,0 +1,227 @@ +# +# You can find a documented example of configuration in ./docs/proxies/nginx.md. +# +worker_processes 1; + +events { + worker_connections 1024; +} + +http { + server { + listen 8080 ssl; + server_name login.example.com; + + resolver 127.0.0.11 ipv6=off; + set $frontend_endpoint http://authelia-frontend:3001; + set $backend_endpoint https://authelia-backend:9091; + + ssl_certificate /etc/ssl/server.cert; + ssl_certificate_key /etc/ssl/server.key; + + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Frame-Options "SAMEORIGIN"; + + # Serve the backend API for the portal. + location /api { + proxy_set_header X-Real-IP $remote_addr; + + # Required by Authelia because "trust proxy" option is used. + # See https://expressjs.com/en/guide/behind-proxies.html + proxy_set_header X-Forwarded-Proto $scheme; + + # Required by Authelia to build correct links for identity validation. + proxy_set_header X-Forwarded-Host $http_host; + + # Needed for network ACLs to work. It appends the IP of the client to the list of IPs + # and allows Authelia to use it to match the network-based ACLs. + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_intercept_errors on; + + proxy_pass $backend_endpoint; + } + + # Serves the portal application. + location / { + # Allow websockets for webpack to auto-reload. + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host "127.0.0.1"; + + proxy_pass $frontend_endpoint; + } + } + + # Serves the home page. + server { + listen 8080 ssl; + server_name home.example.com; + + resolver 127.0.0.11 ipv6=off; + set $upstream_endpoint http://nginx-backend; + + ssl_certificate /etc/ssl/server.cert; + ssl_certificate_key /etc/ssl/server.key; + + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Frame-Options "SAMEORIGIN"; + + location / { + proxy_set_header Host $http_host; + proxy_pass $upstream_endpoint; + } + } + + # Example configuration of domains protected by Authelia. + server { + listen 8080 ssl; + server_name public.example.com + admin.example.com + secure.example.com + dev.example.com + singlefactor.example.com + mx1.mail.example.com mx2.mail.example.com; + + resolver 127.0.0.11 ipv6=off; + set $upstream_verify https://authelia-backend:9091/api/verify; + set $upstream_endpoint http://nginx-backend; + set $upstream_headers http://httpbin:8000/headers; + + ssl_certificate /etc/ssl/server.cert; + ssl_certificate_key /etc/ssl/server.key; + + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Frame-Options "SAMEORIGIN"; + + # Reverse proxy to the backend. It is protected by Authelia by forwarding authorization checks + # to the virtual endpoint introduced by nginx and declared in the next block. + location / { + auth_request /auth_verify; + + auth_request_set $user $upstream_http_remote_user; + proxy_set_header X-Forwarded-User $user; + + auth_request_set $groups $upstream_http_remote_groups; + proxy_set_header Remote-Groups $groups; + + # Route the request to the correct virtual host in the backend. + proxy_set_header Host $http_host; + + # Authelia relies on Proxy-Authorization header to authenticate in basic auth. + # but for the sake of simplicity (because Authorization in supported in most + # clients) we take Authorization from the frontend and rewrite it to + # Proxy-Authorization before sending it to Authelia. + proxy_set_header Proxy-Authorization $http_authorization; + + # mitigate HTTPoxy Vulnerability + # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/ + proxy_set_header Proxy ""; + + # Set the `target_url` variable based on the request. It will be used to build the portal + # URL with the correct redirection parameter. + set $target_url $scheme://$http_host$request_uri; + error_page 401 =302 https://login.example.com:8080/?rd=$target_url; + + proxy_pass $upstream_endpoint; + } + + # Virtual endpoint forwarding requests to Authelia server. + location /auth_verify { + internal; + proxy_set_header X-Real-IP $remote_addr; + + # Provide either X-Original-URL and X-Forwarded-Proto or + # X-Forwarded-Proto, X-Forwarded-Host and X-Forwarded-URI or both. + # Those headers will be used by Authelia to deduce the target url of the user. + # + # X-Forwarded-Proto is mandatory since Authelia uses the "trust proxy" option. + # See https://expressjs.com/en/guide/behind-proxies.html + proxy_set_header X-Original-URL $scheme://$http_host$request_uri; + + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $http_host; + proxy_set_header X-Forwarded-URI $request_uri; + + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Authelia can receive Proxy-Authorization to authenticate however most of the clients + # support Authorization instead. Therefore we rewrite Authorization into Proxy-Authorization. + proxy_set_header Proxy-Authorization $http_authorization; + + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + + proxy_pass $upstream_verify; + } + + # Used by suites to test the forwarded users and groups headers produced by Authelia. + location /headers { + auth_request /auth_verify; + + auth_request_set $user $upstream_http_remote_user; + proxy_set_header Custom-Forwarded-User $user; + + auth_request_set $groups $upstream_http_remote_groups; + proxy_set_header Custom-Forwarded-Groups $groups; + + set $target_url $scheme://$http_host$request_uri; + error_page 401 =302 https://login.example.com:8080/?rd=$target_url; + + proxy_pass $upstream_headers; + } + } + + # Fake Web Mail used to receive emails sent by Authelia. + server { + listen 8080 ssl; + server_name mail.example.com; + + resolver 127.0.0.11 ipv6=off; + set $upstream_endpoint http://smtp:1080; + + ssl_certificate /etc/ssl/server.cert; + ssl_certificate_key /etc/ssl/server.key; + + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Frame-Options "SAMEORIGIN"; + + location / { + proxy_set_header Host $http_host; + proxy_pass $upstream_endpoint; + } + } + + # Fake API emulating Duo behavior + server { + listen 443 ssl; + server_name duo.example.com; + + resolver 127.0.0.11 ipv6=off; + set $upstream_endpoint http://duo-api:3000; + + ssl_certificate /etc/ssl/server.cert; + ssl_certificate_key /etc/ssl/server.key; + + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Frame-Options "SAMEORIGIN"; + + location / { + proxy_set_header Host $http_host; + proxy_pass $upstream_endpoint; + } + } + + # Matches all domains. It redirects to the home page. + server { + listen 8080 ssl; + server_name _; + + ssl_certificate /etc/ssl/server.cert; + ssl_certificate_key /etc/ssl/server.key; + + return 301 https://home.example.com:8080/; + } +} + diff --git a/internal/suites/registry.go b/internal/suites/registry.go index 78389b8cc..79e86e7d6 100644 --- a/internal/suites/registry.go +++ b/internal/suites/registry.go @@ -9,10 +9,15 @@ import ( // Suite the definition of a suite type Suite struct { - SetUp func(tmpPath string) error - SetUpTimeout time.Duration + SetUp func(tmpPath string) error + SetUpTimeout time.Duration + + // Callback called when an error occur during setup phase. OnSetupTimeout func() error + // Callback called when at least one test fail + OnError func() error + TestTimeout time.Duration TearDown func(tmpPath string) error diff --git a/internal/suites/scenario_backend_protection_test.go b/internal/suites/scenario_backend_protection_test.go index 52d969e79..d3ddd1ac0 100644 --- a/internal/suites/scenario_backend_protection_test.go +++ b/internal/suites/scenario_backend_protection_test.go @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/suite" ) +// WARNING: This scenario is intended to be used with TLS enabled in the authelia backend. + type BackendProtectionScenario struct { suite.Suite } diff --git a/internal/suites/suite_standalone.go b/internal/suites/suite_standalone.go index 877b2fef7..c0b97ddfa 100644 --- a/internal/suites/suite_standalone.go +++ b/internal/suites/suite_standalone.go @@ -14,7 +14,7 @@ func init() { "internal/suites/example/compose/authelia/docker-compose.backend.{}.yml", "internal/suites/example/compose/authelia/docker-compose.frontend.{}.yml", "internal/suites/example/compose/nginx/backend/docker-compose.yml", - "internal/suites/example/compose/nginx/portal/docker-compose.yml", + "internal/suites/example/compose/nginx/portal/docker-compose.https.yml", "internal/suites/example/compose/smtp/docker-compose.yml", }) @@ -28,7 +28,7 @@ func init() { return waitUntilAutheliaBackendIsReady(dockerEnvironment) } - onSetupTimeout := func() error { + displayAutheliaLogs := func() error { backendLogs, err := dockerEnvironment.Logs("authelia-backend", nil) if err != nil { return err @@ -51,7 +51,8 @@ func init() { GlobalRegistry.Register(standaloneSuiteName, Suite{ SetUp: setup, SetUpTimeout: 5 * time.Minute, - OnSetupTimeout: onSetupTimeout, + OnError: displayAutheliaLogs, + OnSetupTimeout: displayAutheliaLogs, TearDown: teardown, TestTimeout: 3 * time.Minute, TearDownTimeout: 2 * time.Minute,