From c5c6bda8b066f390b4c0863777dc3eba77fee792 Mon Sep 17 00:00:00 2001 From: James Elliott Date: Thu, 5 Aug 2021 14:02:07 +1000 Subject: [PATCH] refactor: configuration agnostic healthcheck (#2231) This makes the healthcheck simple and configured directly by Authelia's configuration on startup. --- .dockerignore | 3 ++ .healthcheck.env | 5 ++++ Dockerfile | 3 +- Dockerfile.arm32v7 | 3 +- Dockerfile.arm64v8 | 3 +- Dockerfile.coverage | 3 +- config.template.yml | 4 +++ docs/configuration/server.md | 18 ++++++++++++ healthcheck.sh | 28 +++++++++--------- internal/configuration/config.template.yml | 4 +++ internal/configuration/schema/server.go | 15 +++++----- internal/configuration/validator/const.go | 1 + internal/server/const.go | 8 ++++++ internal/server/server.go | 8 ++++++ internal/server/template.go | 33 ++++++++++++++++++++++ 15 files changed, 113 insertions(+), 26 deletions(-) create mode 100644 .healthcheck.env diff --git a/.dockerignore b/.dockerignore index 1a3b82626..c14a03acc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -21,3 +21,6 @@ examples internal/server/public_html authelia.service bootstrap.sh + +# Overrides +!.healthcheck.env diff --git a/.healthcheck.env b/.healthcheck.env new file mode 100644 index 000000000..20df13dd0 --- /dev/null +++ b/.healthcheck.env @@ -0,0 +1,5 @@ +# Default Template +X_AUTHELIA_HEALTHCHECK_SCHEME=http +X_AUTHELIA_HEALTHCHECK_HOST=localhost +X_AUTHELIA_HEALTHCHECK_PORT=9091 +X_AUTHELIA_HEALTHCHECK_PATH= diff --git a/Dockerfile b/Dockerfile index ced510a5e..3274125f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,6 +17,7 @@ ARG LDFLAGS_EXTRA RUN \ mv public_html internal/server/public_html && \ +chmod 0666 /go/src/app/.healthcheck.env && \ echo ">> Starting go build..." && \ GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -tags netgo \ -ldflags "-s -w ${LDFLAGS_EXTRA}" -trimpath -o authelia ./cmd/authelia @@ -30,7 +31,7 @@ WORKDIR /app RUN apk --no-cache add ca-certificates su-exec tzdata -COPY --from=builder-backend /go/src/app/authelia /go/src/app/LICENSE /go/src/app/entrypoint.sh /go/src/app/healthcheck.sh ./ +COPY --from=builder-backend /go/src/app/authelia /go/src/app/LICENSE /go/src/app/entrypoint.sh /go/src/app/healthcheck.sh /go/src/app/.healthcheck.env ./ EXPOSE 9091 diff --git a/Dockerfile.arm32v7 b/Dockerfile.arm32v7 index bfced427b..ec4724dc9 100644 --- a/Dockerfile.arm32v7 +++ b/Dockerfile.arm32v7 @@ -17,6 +17,7 @@ ARG LDFLAGS_EXTRA RUN \ mv public_html internal/server/public_html && \ +chmod 0666 /go/src/app/.healthcheck.env && \ echo ">> Starting go build..." && \ GOOS=linux GOARCH=arm CGO_ENABLED=0 go build -tags netgo \ -ldflags "-s -w ${LDFLAGS_EXTRA}" -trimpath -o authelia ./cmd/authelia @@ -31,7 +32,7 @@ WORKDIR /app RUN \ apk --no-cache add ca-certificates su-exec tzdata -COPY --from=builder-backend /go/src/app/authelia /go/src/app/LICENSE /go/src/app/entrypoint.sh /go/src/app/healthcheck.sh ./ +COPY --from=builder-backend /go/src/app/authelia /go/src/app/LICENSE /go/src/app/entrypoint.sh /go/src/app/healthcheck.sh /go/src/app/.healthcheck.env ./ EXPOSE 9091 diff --git a/Dockerfile.arm64v8 b/Dockerfile.arm64v8 index 9d0985747..a645e19d7 100644 --- a/Dockerfile.arm64v8 +++ b/Dockerfile.arm64v8 @@ -17,6 +17,7 @@ ARG LDFLAGS_EXTRA RUN \ mv public_html internal/server/public_html && \ +chmod 0666 /go/src/app/.healthcheck.env && \ echo ">> Starting go build..." && \ GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -tags netgo \ -ldflags "-s -w ${LDFLAGS_EXTRA}" -trimpath -o authelia ./cmd/authelia @@ -31,7 +32,7 @@ WORKDIR /app RUN \ apk --no-cache add ca-certificates su-exec tzdata -COPY --from=builder-backend /go/src/app/authelia /go/src/app/LICENSE /go/src/app/entrypoint.sh /go/src/app/healthcheck.sh ./ +COPY --from=builder-backend /go/src/app/authelia /go/src/app/LICENSE /go/src/app/entrypoint.sh /go/src/app/healthcheck.sh /go/src/app/.healthcheck.env ./ EXPOSE 9091 diff --git a/Dockerfile.coverage b/Dockerfile.coverage index 225ed9a01..6c34d60d2 100644 --- a/Dockerfile.coverage +++ b/Dockerfile.coverage @@ -32,6 +32,7 @@ ARG LDFLAGS_EXTRA RUN \ mv api internal/server/public_html/api && \ cd cmd/authelia && \ +chmod 0666 /go/src/app/.healthcheck.env && \ echo ">> Starting go build (coverage via go test)..." && \ CGO_ENABLED=0 go test -c --tags coverage -covermode=atomic \ -ldflags "${LDFLAGS_EXTRA}" -o authelia -coverpkg github.com/authelia/authelia/... @@ -45,7 +46,7 @@ RUN apk --no-cache add ca-certificates tzdata WORKDIR /app -COPY --from=builder-backend /go/src/app/cmd/authelia/authelia /go/src/app/LICENSE /go/src/app/healthcheck.sh ./ +COPY --from=builder-backend /go/src/app/cmd/authelia/authelia /go/src/app/LICENSE /go/src/app/healthcheck.sh /go/src/app/.healthcheck.env ./ EXPOSE 9091 diff --git a/config.template.yml b/config.template.yml index c552a45af..356e909dd 100644 --- a/config.template.yml +++ b/config.template.yml @@ -53,6 +53,10 @@ server: ## Enables the expvars endpoint. enable_expvars: false + ## Disables writing the health check vars to /app/.healthcheck.env which makes healthcheck.sh return exit code 0. + ## This is disabled by default if either /app/.healthcheck.env or /app/healthcheck.sh do not exist. + disable_healthcheck: false + ## Authelia by default doesn't accept TLS communication on the server port. This section overrides this behaviour. tls: ## The path to the DER base64/PEM format private key. diff --git a/docs/configuration/server.md b/docs/configuration/server.md index 40172e93a..3acbb5095 100644 --- a/docs/configuration/server.md +++ b/docs/configuration/server.md @@ -20,6 +20,7 @@ server: write_buffer_size: 4096 enable_pprof: false enable_expvars: false + disable_healthcheck: false tls: key: "" certificate: "" @@ -134,6 +135,23 @@ required: no Enables the go expvars endpoints. +### disable_healthcheck +
+type: boolean +{: .label .label-config .label-purple } +default: false +{: .label .label-config .label-blue } +required: no +{: .label .label-config .label-green } +
+ +On startup Authelia checks for the existence of /app/healthcheck.sh and /app/.healthcheck.env and if both of these exist +it writes the configuration vars for the healthcheck to the /app/.healthcheck.env file. In instances where this is not +desirable it's possible to disable these interactions entirely. + +An example situation where this is the case is in Kubernetes when set security policies that prevent writing to the +ephemeral storage of a container or just don't want to enable the internal health check. + ### tls Authelia typically listens for plain unencrypted connections. This is by design as most environments allow to diff --git a/healthcheck.sh b/healthcheck.sh index 147e18d74..afd152fae 100755 --- a/healthcheck.sh +++ b/healthcheck.sh @@ -1,23 +1,21 @@ #!/bin/sh -AUTHELIA_CONFIG=$(pgrep -af authelia | awk '{print $NF}') -AUTHELIA_SCHEME=$(grep ^tls "${AUTHELIA_CONFIG}") -AUTHELIA_HOST=$(grep ^host "${AUTHELIA_CONFIG}" | sed -e 's/host: //' -e 's/\r//') -AUTHELIA_PORT=$(grep ^port "${AUTHELIA_CONFIG}" | sed -e 's/port: //' -e 's/\r//') -AUTHELIA_PATH=$(grep ^\ \ path "${AUTHELIA_CONFIG}" | sed -e 's/ path: //' -e 's/\r//' -e 's/^/\//') - -if [ -z "${AUTHELIA_SCHEME}" ]; then - AUTHELIA_SCHEME=http -else - AUTHELIA_SCHEME=https +if [ -z "${X_AUTHELIA_HEALTHCHECK}" ]; then + exit 0 fi -if [ -z "${AUTHELIA_HOST}" ] || [ "${AUTHELIA_HOST}" = "0.0.0.0" ]; then - AUTHELIA_HOST=localhost +source /app/.healthcheck.env + +if [ -z "${X_AUTHELIA_HEALTHCHECK_SCHEME}" ]; then + X_AUTHELIA_HEALTHCHECK_SCHEME=http fi -if [ -z "${AUTHELIA_PORT}" ]; then - AUTHELIA_PORT=9091 +if [ -z "${X_AUTHELIA_HEALTHCHECK_HOST}" ]; then + X_AUTHELIA_HEALTHCHECK_HOST=localhost fi -wget --quiet --no-check-certificate --tries=1 --spider "${AUTHELIA_SCHEME}://${AUTHELIA_HOST}:${AUTHELIA_PORT}${AUTHELIA_PATH}/api/health" || exit 1 +if [ -z "${X_AUTHELIA_HEALTHCHECK_PORT}" ]; then + X_AUTHELIA_HEALTHCHECK_PORT=9091 +fi + +wget --quiet --no-check-certificate --tries=1 --spider "${X_AUTHELIA_HEALTHCHECK_SCHEME}://${X_AUTHELIA_HEALTHCHECK_HOST}:${X_AUTHELIA_HEALTHCHECK_PORT}${X_AUTHELIA_HEALTHCHECK_PATH}/api/health" || exit 1 diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml index c552a45af..356e909dd 100644 --- a/internal/configuration/config.template.yml +++ b/internal/configuration/config.template.yml @@ -53,6 +53,10 @@ server: ## Enables the expvars endpoint. enable_expvars: false + ## Disables writing the health check vars to /app/.healthcheck.env which makes healthcheck.sh return exit code 0. + ## This is disabled by default if either /app/.healthcheck.env or /app/healthcheck.sh do not exist. + disable_healthcheck: false + ## Authelia by default doesn't accept TLS communication on the server port. This section overrides this behaviour. tls: ## The path to the DER base64/PEM format private key. diff --git a/internal/configuration/schema/server.go b/internal/configuration/schema/server.go index 07169ad8a..d07165cb0 100644 --- a/internal/configuration/schema/server.go +++ b/internal/configuration/schema/server.go @@ -2,13 +2,14 @@ package schema // ServerConfiguration represents the configuration of the http server. type ServerConfiguration struct { - Host string `koanf:"host"` - Port int `koanf:"port"` - Path string `koanf:"path"` - ReadBufferSize int `koanf:"read_buffer_size"` - WriteBufferSize int `koanf:"write_buffer_size"` - EnablePprof bool `koanf:"enable_endpoint_pprof"` - EnableExpvars bool `koanf:"enable_endpoint_expvars"` + Host string `koanf:"host"` + Port int `koanf:"port"` + Path string `koanf:"path"` + ReadBufferSize int `koanf:"read_buffer_size"` + WriteBufferSize int `koanf:"write_buffer_size"` + EnablePprof bool `koanf:"enable_endpoint_pprof"` + EnableExpvars bool `koanf:"enable_endpoint_expvars"` + DisableHealthcheck bool `koanf:"disable_healthcheck"` TLS ServerTLSConfiguration `koanf:"tls"` } diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go index e15420db9..590d56c2c 100644 --- a/internal/configuration/validator/const.go +++ b/internal/configuration/validator/const.go @@ -139,6 +139,7 @@ var ValidKeys = []string{ "server.path", "server.enable_pprof", "server.enable_expvars", + "server.disable_healthcheck", "server.tls.key", "server.tls.certificate", diff --git a/internal/server/const.go b/internal/server/const.go index 6950d07f5..9251aa95e 100644 --- a/internal/server/const.go +++ b/internal/server/const.go @@ -6,3 +6,11 @@ const apiFile = "openapi.yml" const indexFile = "index.html" const dev = "dev" + +const healthCheckEnv = `# Written by Authelia Process +X_AUTHELIA_HEALTHCHECK=1 +X_AUTHELIA_HEALTHCHECK_SCHEME=%s +X_AUTHELIA_HEALTHCHECK_HOST=%s +X_AUTHELIA_HEALTHCHECK_PORT=%d +X_AUTHELIA_HEALTHCHECK_PATH=%s +` diff --git a/internal/server/server.go b/internal/server/server.go index ef5c2f6ce..a12eb850e 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -192,9 +192,17 @@ func Start(configuration schema.Configuration, providers middlewares.Providers) } if configuration.Server.TLS.Certificate != "" && configuration.Server.TLS.Key != "" { + if err = writeHealthCheckEnv(configuration.Server.DisableHealthcheck, "https", configuration.Server.Host, configuration.Server.Path, configuration.Server.Port); err != nil { + logger.Fatalf("Could not configure healthcheck: %v", err) + } + logger.Infof("Listening for TLS connections on %s%s", addrPattern, configuration.Server.Path) logger.Fatal(server.ServeTLS(listener, configuration.Server.TLS.Certificate, configuration.Server.TLS.Key)) } else { + if err = writeHealthCheckEnv(configuration.Server.DisableHealthcheck, "http", configuration.Server.Host, configuration.Server.Path, configuration.Server.Port); err != nil { + logger.Fatalf("Could not configure healthcheck: %v", err) + } + logger.Infof("Listening for non-TLS connections on %s%s", addrPattern, configuration.Server.Path) logger.Fatal(server.Serve(listener)) } diff --git a/internal/server/template.go b/internal/server/template.go index 1c65fb55f..70c327fd0 100644 --- a/internal/server/template.go +++ b/internal/server/template.go @@ -64,3 +64,36 @@ func ServeTemplatedFile(publicDir, file, base, rememberMe, resetPassword, sessio } } } + +func writeHealthCheckEnv(disabled bool, scheme, host, path string, port int) (err error) { + if disabled { + return nil + } + + _, err = os.Stat("/app/healthcheck.sh") + if err != nil { + return nil + } + + _, err = os.Stat("/app/.healthcheck.env") + if err != nil { + return nil + } + + file, err := os.OpenFile("/app/.healthcheck.env", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) + if err != nil { + return err + } + + defer func() { + _ = file.Close() + }() + + if host == "0.0.0.0" { + host = "localhost" + } + + _, err = file.WriteString(fmt.Sprintf(healthCheckEnv, scheme, host, port, path)) + + return err +}