From 88e91e5e5d0b03a9365219b93598db864e800cc8 Mon Sep 17 00:00:00 2001 From: Ian Fijolek Date: Tue, 15 Nov 2022 11:43:05 -0800 Subject: [PATCH] Deploy authelia Backed by lldap and mysql and deployed on whoami for now as a forward proxy example Would be good to add oidc for Nomad as well as make policies configurable via Nomad variables. --- .secrets-baseline | 44 ++- .terraform.lock.hcl | 19 -- core/authelia.yml | 354 ++++++++++++++++++++++++ core/lldap.nomad | 2 + core/main.tf | 80 ++++++ databases/redis.nomad | 2 +- services/service/main.tf | 2 + services/service/service_template.nomad | 16 +- services/service/vars.tf | 17 ++ services/whoami.nomad | 5 +- 10 files changed, 516 insertions(+), 25 deletions(-) create mode 100644 core/authelia.yml diff --git a/.secrets-baseline b/.secrets-baseline index 1db1eab..b6abfbf 100644 --- a/.secrets-baseline +++ b/.secrets-baseline @@ -144,6 +144,48 @@ "is_secret": false } ], + "core/authelia.yml": [ + { + "type": "Secret Keyword", + "filename": "core/authelia.yml", + "hashed_secret": "7cb6efb98ba5972a9b5090dc2e517fe14d12cb04", + "is_verified": false, + "line_number": 55, + "is_secret": false + }, + { + "type": "Secret Keyword", + "filename": "core/authelia.yml", + "hashed_secret": "a32b08d97b1615dc27f58b6b17f67624c04e2c4f", + "is_verified": false, + "line_number": 182, + "is_secret": false + }, + { + "type": "Secret Keyword", + "filename": "core/authelia.yml", + "hashed_secret": "d16a67474cca598880e37d64557f1264586386bd", + "is_verified": false, + "line_number": 248, + "is_secret": false + }, + { + "type": "Secret Keyword", + "filename": "core/authelia.yml", + "hashed_secret": "7e1f5e63ab2c1f926e5fb81cc004dc24af411376", + "is_verified": false, + "line_number": 249, + "is_secret": false + }, + { + "type": "Secret Keyword", + "filename": "core/authelia.yml", + "hashed_secret": "0bb90d739912b79b54b811fec298da9f59008a26", + "is_verified": false, + "line_number": 304, + "is_secret": false + } + ], "core/metrics/grafana/grafana.ini": [ { "type": "Basic Auth Credentials", @@ -195,5 +237,5 @@ } ] }, - "generated_at": "2023-03-24T18:23:24Z" + "generated_at": "2023-07-07T00:58:58Z" } diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl index 7d4f8ef..04ee26b 100644 --- a/.terraform.lock.hcl +++ b/.terraform.lock.hcl @@ -1,25 +1,6 @@ # This file is maintained automatically by "terraform init". # Manual edits may be lost in future updates. -provider "registry.terraform.io/hashicorp/external" { - version = "2.3.1" - hashes = [ - "h1:bROCw6g5D/3fFnWeJ01L4IrdnJl1ILU8DGDgXCtYzaY=", - "zh:001e2886dc81fc98cf17cf34c0d53cb2dae1e869464792576e11b0f34ee92f54", - "zh:2eeac58dd75b1abdf91945ac4284c9ccb2bfb17fa9bdb5f5d408148ff553b3ee", - "zh:2fc39079ba61411a737df2908942e6970cb67ed2f4fb19090cd44ce2082903dd", - "zh:472a71c624952cff7aa98a7b967f6c7bb53153dbd2b8f356ceb286e6743bb4e2", - "zh:4cff06d31272aac8bc35e9b7faec42cf4554cbcbae1092eaab6ab7f643c215d9", - "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:7ed16ccd2049fa089616b98c0bd57219f407958f318f3c697843e2397ddf70df", - "zh:842696362c92bf2645eb85c739410fd51376be6c488733efae44f4ce688da50e", - "zh:8985129f2eccfd7f1841ce06f3bf2bbede6352ec9e9f926fbaa6b1a05313b326", - "zh:a5f0602d8ec991a5411ef42f872aa90f6347e93886ce67905c53cfea37278e05", - "zh:bf4ab82cbe5256dcef16949973bf6aa1a98c2c73a98d6a44ee7bc40809d002b8", - "zh:e70770be62aa70198fa899526d671643ff99eecf265bf1a50e798fc3480bd417", - ] -} - provider "registry.terraform.io/hashicorp/nomad" { version = "1.4.19" hashes = [ diff --git a/core/authelia.yml b/core/authelia.yml new file mode 100644 index 0000000..e99c0f2 --- /dev/null +++ b/core/authelia.yml @@ -0,0 +1,354 @@ +theme: auto + +# jwt_secret: < in file > + +{{ with nomadVar "nomad/jobs" }} +default_redirection_url: https://authelia.{{ .base_hostname }}/ +{{ end }} + +## Set the default 2FA method for new users and for when a user has a preferred method configured that has been +## disabled. This setting must be a method that is enabled. +## Options are totp, webauthn, mobile_push. +default_2fa_method: "" + +server: + host: 0.0.0.0 + port: 9091 + disable_healthcheck: false + +log: + ## Level of verbosity for logs: info, debug, trace. + level: debug + + ## Format the logs are written as: json, text. + format: json + +telemetry: + metrics: + enabled: false + # address: '0.0.0.0:{{ env "NOMAD_PORT_metrics" }}' + +totp: + disable: false + issuer: {{ with nomadVar "nomad/jobs" }}{{ .base_hostname }}{{ end }} + digits: 6 + + ## The TOTP algorithm to use. + ## It is CRITICAL you read the documentation before changing this option: + ## https://www.authelia.com/c/totp#algorithm + algorithm: sha1 + +webauthn: + disable: false + timeout: 60s + display_name: {{ with nomadVar "nomad/jobs" }}{{ .base_hostname }}{{ end }} + user_verification: preferred + +duo_api: + disable: true + # hostname: + # integration_key: + # secret_key: + # enable_self_enrollment: false + +authentication_backend: + disable_reset_password: false + + ## Password Reset Options. + password_reset: + + ## External reset password url that redirects the user to an external reset portal. This disables the internal reset + ## functionality. + # TODO: not sure if this is needed, probably not? + custom_url: "" + + refresh_interval: 5m + + ldap: + implementation: custom + + # stunnel url + url: ldap://127.0.0.1:389 + timeout: 5s + + # TODO: Maybe use stunnel for this + start_tls: false + + base_dn: {{ with nomadVar "nomad/jobs" }}{{ .ldap_base_dn }}{{ end }} + additional_users_dn: ou=people + additional_groups_dn: ou=groups + + username_attribute: uid + group_name_attribute: cn + mail_attribute: mail + display_name_attribute: displayName + + # To allow sign in both with username and email, one can use a filter like + # (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)) + users_filter: "(&({username_attribute}={input})(objectClass=person))" + # Only supported filter by lldap right now + groups_filter: (member={dn}) + + ## The username and password of the admin user. + {{ with nomadVar "nomad/jobs/authelia" }} + user: uid={{ .lldap_admin_user }},ou=people,{{ with nomadVar "nomad/jobs" }}{{ .ldap_base_dn }}{{ end }} + {{ end }} + # password set using secrets file + # password: + +password_policy: + standard: + enabled: false + min_length: 8 + max_length: 0 + require_uppercase: true + require_lowercase: true + require_number: true + require_special: true + + zxcvbn: + enabled: false + min_score: 3 + +## +## Access Control Configuration +## +## Access control is a list of rules defining the authorizations applied for one resource to users or group of users. +## +## If 'access_control' is not defined, ACL rules are disabled and the 'bypass' rule is applied, i.e., access is allowed +## to anyone. Otherwise restrictions follow the rules defined. +## +## Note: One can use the wildcard * to match any subdomain. +## It must stand at the beginning of the pattern. (example: *.mydomain.com) +## +## Note: You must put patterns containing wildcards between simple quotes for the YAML to be syntactically correct. +## +## Definition: A 'rule' is an object with the following keys: 'domain', 'subject', 'policy' and 'resources'. +## +## - 'domain' defines which domain or set of domains the rule applies to. +## +## - 'subject' defines the subject to apply authorizations to. This parameter is optional and matching any user if not +## provided. If provided, the parameter represents either a user or a group. It should be of the form +## 'user:' or 'group:'. +## +## - 'policy' is the policy to apply to resources. It must be either 'bypass', 'one_factor', 'two_factor' or 'deny'. +## +## - 'resources' is a list of regular expressions that matches a set of resources to apply the policy to. This parameter +## is optional and matches any resource if not provided. +## +## Note: the order of the rules is important. The first policy matching (domain, resource, subject) applies. +access_control: + ## Default policy can either be 'bypass', 'one_factor', 'two_factor' or 'deny'. It is the policy applied to any + ## resource if there is no policy to be applied to the user. + default_policy: deny + + networks: + - name: internal + networks: + - 192.168.1.0/24 + - 192.168.2.0/24 + - 192.168.10.0/24 + - name: VPN + networks: 192.168.5.0/24 + + rules: + ## Rules applied to everyone + - domain: '*.{{ with nomadVar "nomad/jobs" }}{{ .base_hostname }}{{ end }}' + policy: one_factor + + - domain: + # TODO: Drive these from Nomad variables + - 'secure.{{ with nomadVar "nomad/jobs" }}{{ .base_hostname }}{{ end }}' + policy: two_factor + +session: + ## The name of the session cookie. + name: authelia_session + domain: {{ with nomadVar "nomad/jobs" }}{{ .base_hostname }}{{ end }} + + # Stored in a secrets file + # secret: + + expiration: 1h + inactivity: 5m + remember_me_duration: 1M + + # TODO: use redis when I figure out authentication and database indexes + # redis: + # host: + # port: + # + # # username: authelia + # # password: authelia + # database_index: 0 + # maximum_active_connections: 8 + # minimum_idle_connections: 0 + +regulation: + max_retries: 3 + find_time: 2m + ban_time: 5m + +## +## Storage Provider Configuration +## +## The available providers are: `local`, `mysql`, `postgres`. You must use one and only one of these providers. +storage: + # encryption_key: + + ## + ## MySQL / MariaDB (Storage Provider) + ## + mysql: + host: 127.0.0.1 + port: 3306 + {{ with nomadVar "nomad/jobs/authelia" }} + database: {{ .db_name }} + username: {{ .db_user }} + # password: + {{- end }} + timeout: 5s + +## +## Notification Provider +## +## Notifications are sent to users when they require a password reset, a Webauthn registration or a TOTP registration. +## The available providers are: filesystem, smtp. You must use only one of these providers. +notifier: + ## You can disable the notifier startup check by setting this to true. + disable_startup_check: false + +{{ with nomadVar "nomad/jobs" }} + smtp: + host: {{ .smtp_server }} + port: {{ .smtp_port }} + username: {{ .smtp_user }} + # password: + +{{- end }} +{{ with nomadVar "nomad/jobs/authelia" }} + sender: "{{ .email_sender }}" + + ## Subject configuration of the emails sent. {title} is replaced by the text from the notifier. + subject: "[Authelia] {title}" + + ## This address is used during the startup check to verify the email configuration is correct. + ## It's not important what it is except if your email server only allows local delivery. + startup_check_address: test@iamthefij.com +{{- end }} + +# identity_providers: + ## + ## OpenID Connect (Identity Provider) + ## + ## It's recommended you read the documentation before configuration of this section: + ## https://www.authelia.com/c/oidc + # oidc: + ## The hmac_secret is used to sign OAuth2 tokens (authorization code, access tokens and refresh tokens). + ## HMAC Secret can also be set using a secret: https://www.authelia.com/c/secrets + # hmac_secret: this_is_a_secret_abc123abc123abc + + ## The issuer_private_key is used to sign the JWT forged by OpenID Connect. + ## Issuer Private Key can also be set using a secret: https://www.authelia.com/c/secrets + # issuer_private_key: | + # --- KEY START + # --- KEY END + + ## The lifespans configure the expiration for these token types. + # access_token_lifespan: 1h + # authorize_code_lifespan: 1m + # id_token_lifespan: 1h + # refresh_token_lifespan: 90m + + ## Enables additional debug messages. + # enable_client_debug_messages: false + + ## SECURITY NOTICE: It's not recommended changing this option, and highly discouraged to have it below 8 for + ## security reasons. + # minimum_parameter_entropy: 8 + + ## SECURITY NOTICE: It's not recommended changing this option, and highly discouraged to have it set to 'never' + ## for security reasons. + # enforce_pkce: public_clients_only + + ## Cross-Origin Resource Sharing (CORS) settings. + # cors: + ## List of endpoints in addition to the metadata endpoints to permit cross-origin requests on. + # endpoints: + # - authorization + # - token + # - revocation + # - introspection + # - userinfo + + ## List of allowed origins. + ## Any origin with https is permitted unless this option is configured or the + ## allowed_origins_from_client_redirect_uris option is enabled. + # allowed_origins: + # - https://example.com + + ## Automatically adds the origin portion of all redirect URI's on all clients to the list of allowed_origins, + ## provided they have the scheme http or https and do not have the hostname of localhost. + # allowed_origins_from_client_redirect_uris: false + + ## Clients is a list of known clients and their configuration. + # clients: + # - + ## The ID is the OpenID Connect ClientID which is used to link an application to a configuration. + # id: myapp + + ## The description to show to users when they end up on the consent screen. Defaults to the ID above. + # description: My Application + + ## The client secret is a shared secret between Authelia and the consumer of this client. + # secret: this_is_a_secret + + ## Sector Identifiers are occasionally used to generate pairwise subject identifiers. In most cases this is not + ## necessary. Read the documentation for more information. + ## The subject identifier must be the host component of a URL, which is a domain name with an optional port. + # sector_identifier: example.com + + ## Sets the client to public. This should typically not be set, please see the documentation for usage. + # public: false + + ## The policy to require for this client; one_factor or two_factor. + # authorization_policy: two_factor + + ## By default users cannot remember pre-configured consents. Setting this value to a period of time using a + ## duration notation will enable users to remember consent for this client. The time configured is the amount + ## of time the pre-configured consent is valid for granting new authorizations to the user. + # pre_configured_consent_duration: + + ## Audience this client is allowed to request. + # audience: [] + + ## Scopes this client is allowed to request. + # scopes: + # - openid + # - groups + # - email + # - profile + + ## Redirect URI's specifies a list of valid case-sensitive callbacks for this client. + # redirect_uris: + # - https://oidc.example.com:8080/oauth2/callback + + ## Grant Types configures which grants this client can obtain. + ## It's not recommended to define this unless you know what you're doing. + # grant_types: + # - refresh_token + # - authorization_code + + ## Response Types configures which responses this client can be sent. + ## It's not recommended to define this unless you know what you're doing. + # response_types: + # - code + + ## Response Modes configures which response modes this client supports. + # response_modes: + # - form_post + # - query + # - fragment + + ## The algorithm used to sign userinfo endpoint responses for this client, either none or RS256. + # userinfo_signing_algorithm: none diff --git a/core/lldap.nomad b/core/lldap.nomad index 3cee62c..f991cd9 100644 --- a/core/lldap.nomad +++ b/core/lldap.nomad @@ -1,6 +1,7 @@ job "lldap" { datacenters = ["dc1"] type = "service" + priority = 80 group "lldap" { @@ -51,6 +52,7 @@ job "lldap" { } env = { + "LLDAP_VERBOSE" = "true" "LLDAP_LDAP_PORT" = "${NOMAD_PORT_ldap}" "LLDAP_HTTP_PORT" = "${NOMAD_PORT_web}" } diff --git a/core/main.tf b/core/main.tf index 1a53aab..e44681d 100644 --- a/core/main.tf +++ b/core/main.tf @@ -53,3 +53,83 @@ resource "nomad_job" "ddclient" { resource "nomad_job" "lldap" { jobspec = file("${path.module}/lldap.nomad") } + +module "authelia" { + source = "../services/service" + + name = "authelia" + priority = 70 + image = "authelia/authelia:latest" + args = ["--config", "$${NOMAD_TASK_DIR}/authelia.yml"] + ingress = true + service_port = 9091 + # metrics_port = 9959 + env = { + AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE = "$${NOMAD_SECRETS_DIR}/ldap_password.txt" + AUTHELIA_JWT_SECRET_FILE = "$${NOMAD_SECRETS_DIR}/jwt_secret.txt" + AUTHELIA_SESSION_SECRET_FILE = "$${NOMAD_SECRETS_DIR}/session_secret.txt" + AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE = "$${NOMAD_SECRETS_DIR}/storage_encryption_key.txt" + AUTHELIA_STORAGE_MYSQL_PASSWORD_FILE = "$${NOMAD_SECRETS_DIR}/mysql_password.txt" + AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE = "$${NOMAD_SECRETS_DIR}/smtp_password.txt" + } + + use_mysql = true + use_ldap = true + mysql_bootstrap = { + enabled = true + } + + service_tags = [ + # Configure traefik to add this middleware + "traefik.http.middlewares.authelia.forwardAuth.address=http://$${NOMAD_IP_main}:$${NOMAD_HOST_PORT_main}/api/verify?rd=https%3A%2F%2Fauthelia.thefij.rocks%2F", + "traefik.http.middlewares.authelia.forwardAuth.trustForwardHeader=true", + "traefik.http.middlewares.authelia.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email", + "traefik.http.middlewares.authelia-basic.forwardAuth.address=http://$${NOMAD_IP_main}:$${NOMAD_HOST_PORT_main}/api/verify?auth=basic", + "traefik.http.middlewares.authelia-basic.forwardAuth.trustForwardHeader=true", + "traefik.http.middlewares.authelia-basic.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email", + ] + + templates = [ + { + data = file("${path.module}/authelia.yml") + dest = "authelia.yml" + mount = false + }, + { + data = "{{ with nomadVar \"nomad/jobs/authelia\" }}{{ .lldap_admin_password }}{{ end }}" + dest_prefix = "$${NOMAD_SECRETS_DIR}" + dest = "ldap_password.txt" + mount = false + }, + { + data = "{{ with nomadVar \"nomad/jobs/authelia\" }}{{ .jwt_secret }}{{ end }}" + dest_prefix = "$${NOMAD_SECRETS_DIR}" + dest = "jwt_secret.txt" + mount = false + }, + { + data = "{{ with nomadVar \"nomad/jobs/authelia\" }}{{ .session_secret }}{{ end }}" + dest_prefix = "$${NOMAD_SECRETS_DIR}" + dest = "session_secret.txt" + mount = false + }, + { + data = "{{ with nomadVar \"nomad/jobs/authelia\" }}{{ .storage_encryption_key }}{{ end }}" + dest_prefix = "$${NOMAD_SECRETS_DIR}" + dest = "storage_encryption_key.txt" + mount = false + }, + { + data = "{{ with nomadVar \"nomad/jobs/authelia\" }}{{ .db_pass }}{{ end }}" + dest_prefix = "$${NOMAD_SECRETS_DIR}" + dest = "mysql_password.txt" + mount = false + }, + { + data = "{{ with nomadVar \"nomad/jobs\" }}{{ .smtp_password }}{{ end }}" + dest_prefix = "$${NOMAD_SECRETS_DIR}" + dest = "smtp_password.txt" + mount = false + }, + ] +} diff --git a/databases/redis.nomad b/databases/redis.nomad index 3b6fc33..4d55e3e 100644 --- a/databases/redis.nomad +++ b/databases/redis.nomad @@ -1,7 +1,7 @@ job "redis" { datacenters = ["dc1"] type = "service" - priority = 60 + priority = 80 group "cache" { count = 1 diff --git a/services/service/main.tf b/services/service/main.tf index 0545b24..f4198ba 100644 --- a/services/service/main.tf +++ b/services/service/main.tf @@ -12,8 +12,10 @@ resource "nomad_job" "service" { docker_devices = var.docker_devices service_port = var.service_port + ports = var.ports sticky_disk = var.sticky_disk resources = var.resources + service_tags = var.service_tags ingress = var.ingress ingress_rule = var.ingress_rule diff --git a/services/service/service_template.nomad b/services/service/service_template.nomad index fd20266..a3fa4a5 100644 --- a/services/service/service_template.nomad +++ b/services/service/service_template.nomad @@ -13,7 +13,14 @@ job "${name}" { host_network = "wesher" to = ${service_port} } - %{ endif } + %{ endif ~} + %{ for port in ports ~} + port "${port.name}" { + %{ if port.host_network != null }host_network = "${port.host_network}"%{ endif ~} + %{ if port.to != null }to = ${port.to}%{ endif ~} + %{ if port.static != null }static = ${port.static}%{ endif ~} + } + %{ endfor } } %{ for constraint in constraints ~} @@ -62,8 +69,11 @@ job "${name}" { %{ endif ~} %{ for middleware in ingress_middlewares ~} "traefik.http.routers.${name}.middlewares=${middleware}", - %{~ endfor } - %{~ endif } + %{ endfor ~} + %{ endif ~} + %{ for tag in service_tags ~} + "${tag}", + %{ endfor ~} ] } diff --git a/services/service/vars.tf b/services/service/vars.tf index e27597c..daf9a1e 100644 --- a/services/service/vars.tf +++ b/services/service/vars.tf @@ -95,6 +95,23 @@ variable "ingress_middlewares" { description = "Traefik middlewares that should be used" } +variable "service_tags" { + type = list(string) + default = [] + description = "Additional tags to be added to the service." +} + +variable "ports" { + type = list(object({ + name = string + host_network = optional(string) + to = optional(number) + static = optional(number) + })) + default = [] + description = "Additional ports (not service_port) to be bound." +} + variable "templates" { type = list(object({ data = string diff --git a/services/whoami.nomad b/services/whoami.nomad index 0ff680e..bc9402c 100644 --- a/services/whoami.nomad +++ b/services/whoami.nomad @@ -28,7 +28,10 @@ job "whoami" { tags = [ "traefik.enable=true", "traefik.http.routers.whoami.entryPoints=websecure", - "traefik.http.routers.whoami.middlewares=basic-auth@file", + # "traefik.http.routers.whoami.middlewares=basic-auth@file", + "traefik.http.routers.whoami.middlewares=authelia@nomad", + # "traefik.http.routers.whoami.middlewares=authelia-basic@consulcatalog", + # "traefik.http.routers.whoami.middlewares=authelia@file", ] }