diff options
| -rw-r--r-- | cmd/authelia-gen/cmd_code.go | 2 | ||||
| -rw-r--r-- | cmd/authelia-gen/cmd_docs_data.go | 2 | ||||
| -rw-r--r-- | cmd/authelia-gen/helpers.go | 51 | ||||
| -rw-r--r-- | cmd/authelia-gen/helpers_test.go | 4 | ||||
| -rw-r--r-- | config.template.yml | 24 | ||||
| -rw-r--r-- | docs/content/configuration/storage/postgres.md | 30 | ||||
| -rw-r--r-- | docs/data/configkeys.json | 1076 | ||||
| -rw-r--r-- | docs/static/schemas/v4.39/json-schema/configuration.json | 30 | ||||
| -rw-r--r-- | internal/configuration/config.template.yml | 24 | ||||
| -rw-r--r-- | internal/configuration/schema/keys.go | 731 | ||||
| -rw-r--r-- | internal/configuration/schema/storage.go | 35 | ||||
| -rw-r--r-- | internal/configuration/validator/const.go | 1 | ||||
| -rw-r--r-- | internal/configuration/validator/storage.go | 42 | ||||
| -rw-r--r-- | internal/configuration/validator/storage_test.go | 280 | ||||
| -rw-r--r-- | internal/storage/sql_provider_backend_mysql_test.go | 60 | ||||
| -rw-r--r-- | internal/storage/sql_provider_backend_postgres.go | 42 | ||||
| -rw-r--r-- | internal/storage/sql_provider_backend_postgres_test.go | 8 | ||||
| -rw-r--r-- | internal/storage/sql_provider_backend_sqlite_test.go | 44 | 
18 files changed, 1475 insertions, 1011 deletions
diff --git a/cmd/authelia-gen/cmd_code.go b/cmd/authelia-gen/cmd_code.go index a704c2949..0d4624237 100644 --- a/cmd/authelia-gen/cmd_code.go +++ b/cmd/authelia-gen/cmd_code.go @@ -173,7 +173,7 @@ func codeKeysRunE(cmd *cobra.Command, args []string) (err error) {  	data := tmplConfigurationKeysData{  		Timestamp: time.Now(), -		Keys:      readTags("", reflect.TypeOf(schema.Configuration{}), false, false), +		Keys:      readTags("", reflect.TypeOf(schema.Configuration{}), false, false, true),  	}  	if root, err = cmd.Flags().GetString(cmdFlagRoot); err != nil { diff --git a/cmd/authelia-gen/cmd_docs_data.go b/cmd/authelia-gen/cmd_docs_data.go index 36dd4423b..91f157a54 100644 --- a/cmd/authelia-gen/cmd_docs_data.go +++ b/cmd/authelia-gen/cmd_docs_data.go @@ -123,7 +123,7 @@ func docsKeysRunE(cmd *cobra.Command, args []string) (err error) {  		data []ConfigurationKey  	) -	keys := readTags("", reflect.TypeOf(schema.Configuration{}), true, true) +	keys := readTags("", reflect.TypeOf(schema.Configuration{}), true, true, true)  	for _, key := range keys {  		if strings.HasSuffix(key, ".*") { diff --git a/cmd/authelia-gen/helpers.go b/cmd/authelia-gen/helpers.go index 0a9894da4..814a35c7e 100644 --- a/cmd/authelia-gen/helpers.go +++ b/cmd/authelia-gen/helpers.go @@ -12,6 +12,7 @@ import (  	"path/filepath"  	"reflect"  	"regexp" +	"sort"  	"strings"  	"time" @@ -116,8 +117,20 @@ func readVersion(cmd *cobra.Command) (version *model.SemanticVersion, err error)  	return model.NewSemanticVersion(packageJSON.Version)  } +func readTags(prefix string, t reflect.Type, envSkip, deprecatedSkip, doSort bool) (tags []string) { +	tags = iReadTags(prefix, t, envSkip, deprecatedSkip, false) + +	tags = removeDuplicate(tags) + +	if doSort { +		sort.Strings(tags) +	} + +	return tags +} +  //nolint:gocyclo -func readTags(prefix string, t reflect.Type, envSkip, deprecatedSkip bool) (tags []string) { +func iReadTags(prefix string, t reflect.Type, envSkip, deprecatedSkip, parentSlice bool) (tags []string) {  	tags = make([]string, 0)  	if envSkip && (t.Kind() == reflect.Slice || t.Kind() == reflect.Map) { @@ -126,7 +139,7 @@ func readTags(prefix string, t reflect.Type, envSkip, deprecatedSkip bool) (tags  	if t.Kind() != reflect.Struct {  		if t.Kind() == reflect.Slice { -			tags = append(tags, readTags(getKeyNameFromTagAndPrefix(prefix, "", true, false), t.Elem(), envSkip, deprecatedSkip)...) +			tags = append(tags, iReadTags(getKeyNameFromTagAndPrefix(prefix, "", true, false), t.Elem(), envSkip, deprecatedSkip, true)...)  		}  		return @@ -150,7 +163,11 @@ func readTags(prefix string, t reflect.Type, envSkip, deprecatedSkip bool) (tags  		switch kind := field.Type.Kind(); kind {  		case reflect.Struct:  			if !containsType(field.Type, decodedTypes) { -				tags = append(tags, readTags(getKeyNameFromTagAndPrefix(prefix, tag, false, false), field.Type, envSkip, deprecatedSkip)...) +				if parentSlice { +					tags = append(tags, getKeyNameFromTagAndPrefix(prefix, tag, false, false)) +				} + +				tags = append(tags, iReadTags(getKeyNameFromTagAndPrefix(prefix, tag, false, false), field.Type, envSkip, deprecatedSkip, false)...)  				continue  			} @@ -169,7 +186,7 @@ func readTags(prefix string, t reflect.Type, envSkip, deprecatedSkip bool) (tags  			case reflect.Struct:  				if !containsType(field.Type.Elem(), decodedTypes) {  					tags = append(tags, getKeyNameFromTagAndPrefix(prefix, tag, false, false)) -					tags = append(tags, readTags(getKeyNameFromTagAndPrefix(prefix, tag, kind == reflect.Slice, kind == reflect.Map), field.Type.Elem(), envSkip, deprecatedSkip)...) +					tags = append(tags, iReadTags(getKeyNameFromTagAndPrefix(prefix, tag, kind == reflect.Slice, kind == reflect.Map), field.Type.Elem(), envSkip, deprecatedSkip, kind == reflect.Slice)...)  					continue  				} @@ -178,13 +195,17 @@ func readTags(prefix string, t reflect.Type, envSkip, deprecatedSkip bool) (tags  					tags = append(tags, getKeyNameFromTagAndPrefix(prefix, tag, true, true))  				} -				tags = append(tags, readTags(getKeyNameFromTagAndPrefix(prefix, tag, kind == reflect.Slice, kind == reflect.Map), field.Type.Elem(), envSkip, deprecatedSkip)...) +				tags = append(tags, iReadTags(getKeyNameFromTagAndPrefix(prefix, tag, kind == reflect.Slice, kind == reflect.Map), field.Type.Elem(), envSkip, deprecatedSkip, true)...)  			}  		case reflect.Ptr:  			switch field.Type.Elem().Kind() {  			case reflect.Struct:  				if !containsType(field.Type.Elem(), decodedTypes) { -					tags = append(tags, readTags(getKeyNameFromTagAndPrefix(prefix, tag, false, false), field.Type.Elem(), envSkip, deprecatedSkip)...) +					if parentSlice { +						tags = append(tags, getKeyNameFromTagAndPrefix(prefix, tag, false, false)) +					} + +					tags = append(tags, iReadTags(getKeyNameFromTagAndPrefix(prefix, tag, false, false), field.Type.Elem(), envSkip, deprecatedSkip, false)...)  					continue  				} @@ -197,7 +218,7 @@ func readTags(prefix string, t reflect.Type, envSkip, deprecatedSkip bool) (tags  				if k == reflect.Struct {  					if !containsType(field.Type.Elem(), decodedTypes) { -						tags = append(tags, readTags(getKeyNameFromTagAndPrefix(prefix, tag, true, false), field.Type.Elem(), envSkip, deprecatedSkip)...) +						tags = append(tags, iReadTags(getKeyNameFromTagAndPrefix(prefix, tag, true, false), field.Type.Elem(), envSkip, deprecatedSkip, field.Type.Elem().Kind() == reflect.Slice)...)  						continue  					} @@ -309,3 +330,19 @@ func readCompose(path string) (compose *Compose, err error) {  	return compose, nil  } + +func removeDuplicate[T comparable](sliceList []T) []T { +	var list []T + +	allKeys := make(map[T]bool) + +	for _, item := range sliceList { +		if _, value := allKeys[item]; !value { +			allKeys[item] = true + +			list = append(list, item) +		} +	} + +	return list +} diff --git a/cmd/authelia-gen/helpers_test.go b/cmd/authelia-gen/helpers_test.go index a161e5d8e..a6aac0ccc 100644 --- a/cmd/authelia-gen/helpers_test.go +++ b/cmd/authelia-gen/helpers_test.go @@ -136,10 +136,10 @@ func TestContainsType(t *testing.T) {  func TestReadTags(t *testing.T) {  	assert.NotPanics(t, func() { -		readTags("", reflect.TypeOf(schema.Configuration{}), false, false) +		iReadTags("", reflect.TypeOf(schema.Configuration{}), false, false, false)  	})  	assert.NotPanics(t, func() { -		readTags("", reflect.TypeOf(schema.Configuration{}), true, true) +		iReadTags("", reflect.TypeOf(schema.Configuration{}), true, true, false)  	})  } diff --git a/config.template.yml b/config.template.yml index 2e01b7a15..0fd1756e9 100644 --- a/config.template.yml +++ b/config.template.yml @@ -1076,6 +1076,30 @@ session:      ## The default scheme is 'unix' if the address is an absolute path otherwise it's 'tcp'. The default port is '5432'.      # address: 'tcp://127.0.0.1:5432' +    ## List of additional server instance configurations to fallback to when the primary instance is not available. +    # servers: +      # - +        ## The Address of this individual instance. +        # address: 'tcp://127.0.0.1:5432' + +        ## The TLS configuration for this individual instance. +        # tls: +          # server_name: 'postgres.example.com' +          # skip_verify: false +          # minimum_version: 'TLS1.2' +          # maximum_version: 'TLS1.3' +          # certificate_chain: | +            # -----BEGIN CERTIFICATE----- +            # ... +            # -----END CERTIFICATE----- +            # -----BEGIN CERTIFICATE----- +            # ... +            # -----END CERTIFICATE----- +          # private_key: | +            # -----BEGIN RSA PRIVATE KEY----- +            # ... +            # -----END RSA PRIVATE KEY----- +      ## The database name to use.      # database: 'authelia' diff --git a/docs/content/configuration/storage/postgres.md b/docs/content/configuration/storage/postgres.md index 2e04f3aeb..78bdeda34 100644 --- a/docs/content/configuration/storage/postgres.md +++ b/docs/content/configuration/storage/postgres.md @@ -36,6 +36,7 @@ storage:    encryption_key: 'a_very_important_secret'    postgres:      address: 'tcp://127.0.0.1:5432' +    servers: []      database: 'authelia'      schema: 'public'      username: 'authelia' @@ -94,6 +95,35 @@ storage:      address: 'unix:///var/run/postgres.sock'  ``` +### servers + +{{< confkey type="list(object)" required="no" >}} + +This specifies a list of additional fallback [PostgreSQL] instances to use should issues occur with the primary instance +which is configured with the [address](#address) and [tls](#tls) options. + +Each server instance has the [address](#address) and [tls](#tls) option which both have the same requirements and +effect, and have the same configuration syntax. This means all other settings including but not limited to +[database](#database), [schema](#schema), [username](#username), and [password](#password); must be the same as the +primary instance, and they must be fully replicated. + +Example configuration: + +```yaml +storage: +  postgres: +    address: 'tcp://postgres1:5432' +    tls: +      server_name: 'postgres1.local' +    servers: +      - address: 'tcp://postgres2:5432' +        tls: +          server_name: 'postgres2.local' +      - address: 'tcp://postgres3:5432' +        tls: +          server_name: 'postgres3.local' +``` +  ### database  {{< confkey type="string" required="yes" >}} diff --git a/docs/data/configkeys.json b/docs/data/configkeys.json index a93d83920..19ff5d878 100644 --- a/docs/data/configkeys.json +++ b/docs/data/configkeys.json @@ -1,308 +1,308 @@  [      { -        "path": "theme", +        "path": "access_control.default_policy",          "secret": false, -        "env": "AUTHELIA_THEME" +        "env": "AUTHELIA_ACCESS_CONTROL_DEFAULT_POLICY"      },      { -        "path": "certificates_directory", +        "path": "authentication_backend.file.password.algorithm",          "secret": false, -        "env": "AUTHELIA_CERTIFICATES_DIRECTORY" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ALGORITHM"      },      { -        "path": "default_2fa_method", +        "path": "authentication_backend.file.password.argon2.iterations",          "secret": false, -        "env": "AUTHELIA_DEFAULT_2FA_METHOD" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_ITERATIONS"      },      { -        "path": "log.level", +        "path": "authentication_backend.file.password.argon2.key_length",          "secret": false, -        "env": "AUTHELIA_LOG_LEVEL" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_KEY_LENGTH"      },      { -        "path": "log.format", +        "path": "authentication_backend.file.password.argon2.memory",          "secret": false, -        "env": "AUTHELIA_LOG_FORMAT" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_MEMORY"      },      { -        "path": "log.file_path", +        "path": "authentication_backend.file.password.argon2.parallelism",          "secret": false, -        "env": "AUTHELIA_LOG_FILE_PATH" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_PARALLELISM"      },      { -        "path": "log.keep_stdout", +        "path": "authentication_backend.file.password.argon2.salt_length",          "secret": false, -        "env": "AUTHELIA_LOG_KEEP_STDOUT" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_SALT_LENGTH"      },      { -        "path": "identity_providers.oidc.hmac_secret", -        "secret": true, -        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE" +        "path": "authentication_backend.file.password.argon2.variant", +        "secret": false, +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_VARIANT"      },      { -        "path": "identity_providers.oidc.enable_client_debug_messages", +        "path": "authentication_backend.file.password.bcrypt.cost",          "secret": false, -        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_ENABLE_CLIENT_DEBUG_MESSAGES" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_BCRYPT_COST"      },      { -        "path": "identity_providers.oidc.minimum_parameter_entropy", +        "path": "authentication_backend.file.password.bcrypt.variant",          "secret": false, -        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_MINIMUM_PARAMETER_ENTROPY" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_BCRYPT_VARIANT"      },      { -        "path": "identity_providers.oidc.enforce_pkce", +        "path": "authentication_backend.file.password.pbkdf2.iterations",          "secret": false, -        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_ENFORCE_PKCE" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_PBKDF2_ITERATIONS"      },      { -        "path": "identity_providers.oidc.enable_pkce_plain_challenge", +        "path": "authentication_backend.file.password.pbkdf2.salt_length",          "secret": false, -        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_ENABLE_PKCE_PLAIN_CHALLENGE" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_PBKDF2_SALT_LENGTH"      },      { -        "path": "identity_providers.oidc.enable_jwt_access_token_stateless_introspection", +        "path": "authentication_backend.file.password.pbkdf2.variant",          "secret": false, -        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_ENABLE_JWT_ACCESS_TOKEN_STATELESS_INTROSPECTION" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_PBKDF2_VARIANT"      },      { -        "path": "identity_providers.oidc.discovery_signed_response_alg", +        "path": "authentication_backend.file.password.scrypt.block_size",          "secret": false, -        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_DISCOVERY_SIGNED_RESPONSE_ALG" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_BLOCK_SIZE"      },      { -        "path": "identity_providers.oidc.discovery_signed_response_key_id", +        "path": "authentication_backend.file.password.scrypt.iterations",          "secret": false, -        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_DISCOVERY_SIGNED_RESPONSE_KEY_ID" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_ITERATIONS"      },      { -        "path": "identity_providers.oidc.require_pushed_authorization_requests", +        "path": "authentication_backend.file.password.scrypt.key_length",          "secret": false, -        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_REQUIRE_PUSHED_AUTHORIZATION_REQUESTS" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_KEY_LENGTH"      },      { -        "path": "identity_providers.oidc.cors.endpoints", +        "path": "authentication_backend.file.password.scrypt.parallelism",          "secret": false, -        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_CORS_ENDPOINTS" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_PARALLELISM"      },      { -        "path": "identity_providers.oidc.cors.allowed_origins_from_client_redirect_uris", +        "path": "authentication_backend.file.password.scrypt.salt_length",          "secret": false, -        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_CORS_ALLOWED_ORIGINS_FROM_CLIENT_REDIRECT_URIS" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_SALT_LENGTH"      },      { -        "path": "identity_providers.oidc.lifespans.access_token", +        "path": "authentication_backend.file.password.sha2crypt.iterations",          "secret": false, -        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_LIFESPANS_ACCESS_TOKEN" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SHA2CRYPT_ITERATIONS"      },      { -        "path": "identity_providers.oidc.lifespans.authorize_code", +        "path": "authentication_backend.file.password.sha2crypt.salt_length",          "secret": false, -        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_LIFESPANS_AUTHORIZE_CODE" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SHA2CRYPT_SALT_LENGTH"      },      { -        "path": "identity_providers.oidc.lifespans.id_token", +        "path": "authentication_backend.file.password.sha2crypt.variant",          "secret": false, -        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_LIFESPANS_ID_TOKEN" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SHA2CRYPT_VARIANT"      },      { -        "path": "identity_providers.oidc.lifespans.refresh_token", +        "path": "authentication_backend.file.path",          "secret": false, -        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_LIFESPANS_REFRESH_TOKEN" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PATH"      },      { -        "path": "identity_providers.oidc.lifespans.jwt_secured_authorization", +        "path": "authentication_backend.file.search.case_insensitive",          "secret": false, -        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_LIFESPANS_JWT_SECURED_AUTHORIZATION" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_SEARCH_CASE_INSENSITIVE"      },      { -        "path": "identity_providers.oidc", +        "path": "authentication_backend.file.search.email",          "secret": false, -        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_SEARCH_EMAIL"      },      { -        "path": "authentication_backend.password_reset.disable", +        "path": "authentication_backend.file.watch",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_PASSWORD_RESET_DISABLE" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_WATCH"      },      { -        "path": "authentication_backend.password_reset.custom_url", +        "path": "authentication_backend.ldap.additional_groups_dn",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_PASSWORD_RESET_CUSTOM_URL" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ADDITIONAL_GROUPS_DN"      },      { -        "path": "authentication_backend.refresh_interval", +        "path": "authentication_backend.ldap.additional_users_dn",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_REFRESH_INTERVAL" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ADDITIONAL_USERS_DN"      },      { -        "path": "authentication_backend.file.path", +        "path": "authentication_backend.ldap.address",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PATH" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ADDRESS"      },      { -        "path": "authentication_backend.file.watch", +        "path": "authentication_backend.ldap.attributes.birthdate",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_WATCH" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_BIRTHDATE"      },      { -        "path": "authentication_backend.file.password.algorithm", +        "path": "authentication_backend.ldap.attributes.country",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ALGORITHM" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_COUNTRY"      },      { -        "path": "authentication_backend.file.password.argon2.variant", +        "path": "authentication_backend.ldap.attributes.display_name",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_VARIANT" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_DISPLAY_NAME"      },      { -        "path": "authentication_backend.file.password.argon2.iterations", +        "path": "authentication_backend.ldap.attributes.distinguished_name",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_ITERATIONS" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_DISTINGUISHED_NAME"      },      { -        "path": "authentication_backend.file.password.argon2.memory", +        "path": "authentication_backend.ldap.attributes.family_name",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_MEMORY" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_FAMILY_NAME"      },      { -        "path": "authentication_backend.file.password.argon2.parallelism", +        "path": "authentication_backend.ldap.attributes.gender",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_PARALLELISM" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_GENDER"      },      { -        "path": "authentication_backend.file.password.argon2.key_length", +        "path": "authentication_backend.ldap.attributes.given_name",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_KEY_LENGTH" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_GIVEN_NAME"      },      { -        "path": "authentication_backend.file.password.argon2.salt_length", +        "path": "authentication_backend.ldap.attributes.group_name",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_SALT_LENGTH" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_GROUP_NAME"      },      { -        "path": "authentication_backend.file.password.sha2crypt.variant", +        "path": "authentication_backend.ldap.attributes.locale",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SHA2CRYPT_VARIANT" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_LOCALE"      },      { -        "path": "authentication_backend.file.password.sha2crypt.iterations", +        "path": "authentication_backend.ldap.attributes.locality",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SHA2CRYPT_ITERATIONS" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_LOCALITY"      },      { -        "path": "authentication_backend.file.password.sha2crypt.salt_length", +        "path": "authentication_backend.ldap.attributes.mail",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SHA2CRYPT_SALT_LENGTH" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_MAIL"      },      { -        "path": "authentication_backend.file.password.pbkdf2.variant", +        "path": "authentication_backend.ldap.attributes.member_of",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_PBKDF2_VARIANT" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_MEMBER_OF"      },      { -        "path": "authentication_backend.file.password.pbkdf2.iterations", +        "path": "authentication_backend.ldap.attributes.middle_name",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_PBKDF2_ITERATIONS" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_MIDDLE_NAME"      },      { -        "path": "authentication_backend.file.password.pbkdf2.salt_length", +        "path": "authentication_backend.ldap.attributes.nickname",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_PBKDF2_SALT_LENGTH" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_NICKNAME"      },      { -        "path": "authentication_backend.file.password.bcrypt.variant", +        "path": "authentication_backend.ldap.attributes.phone_extension",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_BCRYPT_VARIANT" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_PHONE_EXTENSION"      },      { -        "path": "authentication_backend.file.password.bcrypt.cost", +        "path": "authentication_backend.ldap.attributes.phone_number",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_BCRYPT_COST" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_PHONE_NUMBER"      },      { -        "path": "authentication_backend.file.password.scrypt.iterations", +        "path": "authentication_backend.ldap.attributes.picture",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_ITERATIONS" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_PICTURE"      },      { -        "path": "authentication_backend.file.password.scrypt.block_size", +        "path": "authentication_backend.ldap.attributes.postal_code",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_BLOCK_SIZE" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_POSTAL_CODE"      },      { -        "path": "authentication_backend.file.password.scrypt.parallelism", +        "path": "authentication_backend.ldap.attributes.profile",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_PARALLELISM" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_PROFILE"      },      { -        "path": "authentication_backend.file.password.scrypt.key_length", +        "path": "authentication_backend.ldap.attributes.region",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_KEY_LENGTH" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_REGION"      },      { -        "path": "authentication_backend.file.password.scrypt.salt_length", +        "path": "authentication_backend.ldap.attributes.street_address",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_SALT_LENGTH" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_STREET_ADDRESS"      },      { -        "path": "authentication_backend.file.search.email", +        "path": "authentication_backend.ldap.attributes.username",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_SEARCH_EMAIL" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_USERNAME"      },      { -        "path": "authentication_backend.file.search.case_insensitive", +        "path": "authentication_backend.ldap.attributes.website",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_FILE_SEARCH_CASE_INSENSITIVE" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_WEBSITE"      },      { -        "path": "authentication_backend.ldap.address", +        "path": "authentication_backend.ldap.attributes.zoneinfo",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ADDRESS" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_ZONEINFO"      },      { -        "path": "authentication_backend.ldap.implementation", +        "path": "authentication_backend.ldap.base_dn",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_IMPLEMENTATION" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_BASE_DN"      },      { -        "path": "authentication_backend.ldap.timeout", +        "path": "authentication_backend.ldap.group_search_mode",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TIMEOUT" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_GROUP_SEARCH_MODE"      },      { -        "path": "authentication_backend.ldap.start_tls", +        "path": "authentication_backend.ldap.groups_filter",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_START_TLS" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_GROUPS_FILTER"      },      { -        "path": "authentication_backend.ldap.tls.minimum_version", +        "path": "authentication_backend.ldap.implementation",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_MINIMUM_VERSION" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_IMPLEMENTATION"      },      { -        "path": "authentication_backend.ldap.tls.maximum_version", -        "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_MAXIMUM_VERSION" +        "path": "authentication_backend.ldap.password", +        "secret": true, +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE"      },      { -        "path": "authentication_backend.ldap.tls.skip_verify", +        "path": "authentication_backend.ldap.permit_feature_detection_failure",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_SKIP_VERIFY" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PERMIT_FEATURE_DETECTION_FAILURE"      },      { -        "path": "authentication_backend.ldap.tls.server_name", +        "path": "authentication_backend.ldap.permit_referrals",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_SERVER_NAME" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PERMIT_REFERRALS"      },      { -        "path": "authentication_backend.ldap.tls.private_key", -        "secret": true, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_PRIVATE_KEY_FILE" +        "path": "authentication_backend.ldap.permit_unauthenticated_bind", +        "secret": false, +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PERMIT_UNAUTHENTICATED_BIND"      },      { -        "path": "authentication_backend.ldap.tls.certificate_chain", -        "secret": true, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_CERTIFICATE_CHAIN_FILE" +        "path": "authentication_backend.ldap.pooling.count", +        "secret": false, +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_POOLING_COUNT"      },      {          "path": "authentication_backend.ldap.pooling.enable", @@ -310,11 +310,6 @@          "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_POOLING_ENABLE"      },      { -        "path": "authentication_backend.ldap.pooling.count", -        "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_POOLING_COUNT" -    }, -    {          "path": "authentication_backend.ldap.pooling.retries",          "secret": false,          "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_POOLING_RETRIES" @@ -325,434 +320,434 @@          "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_POOLING_TIMEOUT"      },      { -        "path": "authentication_backend.ldap.base_dn", +        "path": "authentication_backend.ldap.start_tls",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_BASE_DN" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_START_TLS"      },      { -        "path": "authentication_backend.ldap.additional_users_dn", +        "path": "authentication_backend.ldap.timeout",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ADDITIONAL_USERS_DN" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TIMEOUT"      },      { -        "path": "authentication_backend.ldap.users_filter", -        "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_USERS_FILTER" +        "path": "authentication_backend.ldap.tls.certificate_chain", +        "secret": true, +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_CERTIFICATE_CHAIN_FILE"      },      { -        "path": "authentication_backend.ldap.additional_groups_dn", +        "path": "authentication_backend.ldap.tls.maximum_version",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ADDITIONAL_GROUPS_DN" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_MAXIMUM_VERSION"      },      { -        "path": "authentication_backend.ldap.groups_filter", +        "path": "authentication_backend.ldap.tls.minimum_version",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_GROUPS_FILTER" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_MINIMUM_VERSION"      },      { -        "path": "authentication_backend.ldap.group_search_mode", -        "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_GROUP_SEARCH_MODE" +        "path": "authentication_backend.ldap.tls.private_key", +        "secret": true, +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_PRIVATE_KEY_FILE"      },      { -        "path": "authentication_backend.ldap.attributes.distinguished_name", +        "path": "authentication_backend.ldap.tls.server_name",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_DISTINGUISHED_NAME" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_SERVER_NAME"      },      { -        "path": "authentication_backend.ldap.attributes.username", +        "path": "authentication_backend.ldap.tls.skip_verify",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_USERNAME" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_SKIP_VERIFY"      },      { -        "path": "authentication_backend.ldap.attributes.display_name", +        "path": "authentication_backend.ldap.user",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_DISPLAY_NAME" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_USER"      },      { -        "path": "authentication_backend.ldap.attributes.family_name", +        "path": "authentication_backend.ldap.users_filter",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_FAMILY_NAME" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_USERS_FILTER"      },      { -        "path": "authentication_backend.ldap.attributes.given_name", +        "path": "authentication_backend.password_reset.custom_url",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_GIVEN_NAME" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_PASSWORD_RESET_CUSTOM_URL"      },      { -        "path": "authentication_backend.ldap.attributes.middle_name", +        "path": "authentication_backend.password_reset.disable",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_MIDDLE_NAME" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_PASSWORD_RESET_DISABLE"      },      { -        "path": "authentication_backend.ldap.attributes.nickname", +        "path": "authentication_backend.refresh_interval",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_NICKNAME" +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_REFRESH_INTERVAL"      },      { -        "path": "authentication_backend.ldap.attributes.gender", +        "path": "certificates_directory",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_GENDER" +        "env": "AUTHELIA_CERTIFICATES_DIRECTORY"      },      { -        "path": "authentication_backend.ldap.attributes.birthdate", +        "path": "default_2fa_method",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_BIRTHDATE" +        "env": "AUTHELIA_DEFAULT_2FA_METHOD"      },      { -        "path": "authentication_backend.ldap.attributes.website", +        "path": "duo_api.disable",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_WEBSITE" +        "env": "AUTHELIA_DUO_API_DISABLE"      },      { -        "path": "authentication_backend.ldap.attributes.profile", +        "path": "duo_api.enable_self_enrollment",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_PROFILE" +        "env": "AUTHELIA_DUO_API_ENABLE_SELF_ENROLLMENT"      },      { -        "path": "authentication_backend.ldap.attributes.picture", +        "path": "duo_api.hostname",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_PICTURE" +        "env": "AUTHELIA_DUO_API_HOSTNAME"      },      { -        "path": "authentication_backend.ldap.attributes.zoneinfo", -        "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_ZONEINFO" +        "path": "duo_api.integration_key", +        "secret": true, +        "env": "AUTHELIA_DUO_API_INTEGRATION_KEY_FILE"      },      { -        "path": "authentication_backend.ldap.attributes.locale", -        "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_LOCALE" +        "path": "duo_api.secret_key", +        "secret": true, +        "env": "AUTHELIA_DUO_API_SECRET_KEY_FILE"      },      { -        "path": "authentication_backend.ldap.attributes.phone_number", +        "path": "identity_providers.oidc",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_PHONE_NUMBER" +        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC"      },      { -        "path": "authentication_backend.ldap.attributes.phone_extension", +        "path": "identity_providers.oidc.cors.allowed_origins_from_client_redirect_uris",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_PHONE_EXTENSION" +        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_CORS_ALLOWED_ORIGINS_FROM_CLIENT_REDIRECT_URIS"      },      { -        "path": "authentication_backend.ldap.attributes.street_address", +        "path": "identity_providers.oidc.cors.endpoints",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_STREET_ADDRESS" +        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_CORS_ENDPOINTS"      },      { -        "path": "authentication_backend.ldap.attributes.locality", +        "path": "identity_providers.oidc.discovery_signed_response_alg",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_LOCALITY" +        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_DISCOVERY_SIGNED_RESPONSE_ALG"      },      { -        "path": "authentication_backend.ldap.attributes.region", +        "path": "identity_providers.oidc.discovery_signed_response_key_id",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_REGION" +        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_DISCOVERY_SIGNED_RESPONSE_KEY_ID"      },      { -        "path": "authentication_backend.ldap.attributes.postal_code", +        "path": "identity_providers.oidc.enable_client_debug_messages",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_POSTAL_CODE" +        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_ENABLE_CLIENT_DEBUG_MESSAGES"      },      { -        "path": "authentication_backend.ldap.attributes.country", +        "path": "identity_providers.oidc.enable_jwt_access_token_stateless_introspection",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_COUNTRY" +        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_ENABLE_JWT_ACCESS_TOKEN_STATELESS_INTROSPECTION"      },      { -        "path": "authentication_backend.ldap.attributes.mail", +        "path": "identity_providers.oidc.enable_pkce_plain_challenge",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_MAIL" +        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_ENABLE_PKCE_PLAIN_CHALLENGE"      },      { -        "path": "authentication_backend.ldap.attributes.member_of", +        "path": "identity_providers.oidc.enforce_pkce",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_MEMBER_OF" +        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_ENFORCE_PKCE"      },      { -        "path": "authentication_backend.ldap.attributes.group_name", -        "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ATTRIBUTES_GROUP_NAME" +        "path": "identity_providers.oidc.hmac_secret", +        "secret": true, +        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE"      },      { -        "path": "authentication_backend.ldap.permit_referrals", +        "path": "identity_providers.oidc.lifespans.access_token",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PERMIT_REFERRALS" +        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_LIFESPANS_ACCESS_TOKEN"      },      { -        "path": "authentication_backend.ldap.permit_unauthenticated_bind", +        "path": "identity_providers.oidc.lifespans.authorize_code",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PERMIT_UNAUTHENTICATED_BIND" +        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_LIFESPANS_AUTHORIZE_CODE"      },      { -        "path": "authentication_backend.ldap.permit_feature_detection_failure", +        "path": "identity_providers.oidc.lifespans.id_token",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PERMIT_FEATURE_DETECTION_FAILURE" +        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_LIFESPANS_ID_TOKEN"      },      { -        "path": "authentication_backend.ldap.user", +        "path": "identity_providers.oidc.lifespans.jwt_secured_authorization",          "secret": false, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_USER" +        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_LIFESPANS_JWT_SECURED_AUTHORIZATION"      },      { -        "path": "authentication_backend.ldap.password", -        "secret": true, -        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE" +        "path": "identity_providers.oidc.lifespans.refresh_token", +        "secret": false, +        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_LIFESPANS_REFRESH_TOKEN"      },      { -        "path": "session.name", +        "path": "identity_providers.oidc.minimum_parameter_entropy",          "secret": false, -        "env": "AUTHELIA_SESSION_NAME" +        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_MINIMUM_PARAMETER_ENTROPY"      },      { -        "path": "session.same_site", +        "path": "identity_providers.oidc.require_pushed_authorization_requests",          "secret": false, -        "env": "AUTHELIA_SESSION_SAME_SITE" +        "env": "AUTHELIA_IDENTITY_PROVIDERS_OIDC_REQUIRE_PUSHED_AUTHORIZATION_REQUESTS"      },      { -        "path": "session.expiration", +        "path": "identity_validation.elevated_session.characters",          "secret": false, -        "env": "AUTHELIA_SESSION_EXPIRATION" +        "env": "AUTHELIA_IDENTITY_VALIDATION_ELEVATED_SESSION_CHARACTERS"      },      { -        "path": "session.inactivity", +        "path": "identity_validation.elevated_session.code_lifespan",          "secret": false, -        "env": "AUTHELIA_SESSION_INACTIVITY" +        "env": "AUTHELIA_IDENTITY_VALIDATION_ELEVATED_SESSION_CODE_LIFESPAN"      },      { -        "path": "session.remember_me", +        "path": "identity_validation.elevated_session.elevation_lifespan",          "secret": false, -        "env": "AUTHELIA_SESSION_REMEMBER_ME" +        "env": "AUTHELIA_IDENTITY_VALIDATION_ELEVATED_SESSION_ELEVATION_LIFESPAN"      },      { -        "path": "session", +        "path": "identity_validation.elevated_session.require_second_factor",          "secret": false, -        "env": "AUTHELIA_SESSION" +        "env": "AUTHELIA_IDENTITY_VALIDATION_ELEVATED_SESSION_REQUIRE_SECOND_FACTOR"      },      { -        "path": "session.secret", -        "secret": true, -        "env": "AUTHELIA_SESSION_SECRET_FILE" +        "path": "identity_validation.elevated_session.skip_second_factor", +        "secret": false, +        "env": "AUTHELIA_IDENTITY_VALIDATION_ELEVATED_SESSION_SKIP_SECOND_FACTOR"      },      { -        "path": "session.redis.host", +        "path": "identity_validation.reset_password.jwt_algorithm",          "secret": false, -        "env": "AUTHELIA_SESSION_REDIS_HOST" +        "env": "AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_ALGORITHM"      },      { -        "path": "session.redis.port", +        "path": "identity_validation.reset_password.jwt_lifespan",          "secret": false, -        "env": "AUTHELIA_SESSION_REDIS_PORT" +        "env": "AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_LIFESPAN"      },      { -        "path": "session.redis.timeout", -        "secret": false, -        "env": "AUTHELIA_SESSION_REDIS_TIMEOUT" +        "path": "identity_validation.reset_password.jwt_secret", +        "secret": true, +        "env": "AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE"      },      { -        "path": "session.redis.max_retries", +        "path": "log.file_path",          "secret": false, -        "env": "AUTHELIA_SESSION_REDIS_MAX_RETRIES" +        "env": "AUTHELIA_LOG_FILE_PATH"      },      { -        "path": "session.redis.username", +        "path": "log.format",          "secret": false, -        "env": "AUTHELIA_SESSION_REDIS_USERNAME" +        "env": "AUTHELIA_LOG_FORMAT"      },      { -        "path": "session.redis.password", -        "secret": true, -        "env": "AUTHELIA_SESSION_REDIS_PASSWORD_FILE" +        "path": "log.keep_stdout", +        "secret": false, +        "env": "AUTHELIA_LOG_KEEP_STDOUT"      },      { -        "path": "session.redis.database_index", +        "path": "log.level",          "secret": false, -        "env": "AUTHELIA_SESSION_REDIS_DATABASE_INDEX" +        "env": "AUTHELIA_LOG_LEVEL"      },      { -        "path": "session.redis.maximum_active_connections", +        "path": "notifier.disable_startup_check",          "secret": false, -        "env": "AUTHELIA_SESSION_REDIS_MAXIMUM_ACTIVE_CONNECTIONS" +        "env": "AUTHELIA_NOTIFIER_DISABLE_STARTUP_CHECK"      },      { -        "path": "session.redis.minimum_idle_connections", +        "path": "notifier.filesystem.filename",          "secret": false, -        "env": "AUTHELIA_SESSION_REDIS_MINIMUM_IDLE_CONNECTIONS" +        "env": "AUTHELIA_NOTIFIER_FILESYSTEM_FILENAME"      },      { -        "path": "session.redis.tls.minimum_version", +        "path": "notifier.smtp.address",          "secret": false, -        "env": "AUTHELIA_SESSION_REDIS_TLS_MINIMUM_VERSION" +        "env": "AUTHELIA_NOTIFIER_SMTP_ADDRESS"      },      { -        "path": "session.redis.tls.maximum_version", +        "path": "notifier.smtp.disable_html_emails",          "secret": false, -        "env": "AUTHELIA_SESSION_REDIS_TLS_MAXIMUM_VERSION" +        "env": "AUTHELIA_NOTIFIER_SMTP_DISABLE_HTML_EMAILS"      },      { -        "path": "session.redis.tls.skip_verify", +        "path": "notifier.smtp.disable_require_tls",          "secret": false, -        "env": "AUTHELIA_SESSION_REDIS_TLS_SKIP_VERIFY" +        "env": "AUTHELIA_NOTIFIER_SMTP_DISABLE_REQUIRE_TLS"      },      { -        "path": "session.redis.tls.server_name", +        "path": "notifier.smtp.disable_starttls",          "secret": false, -        "env": "AUTHELIA_SESSION_REDIS_TLS_SERVER_NAME" +        "env": "AUTHELIA_NOTIFIER_SMTP_DISABLE_STARTTLS"      },      { -        "path": "session.redis.tls.private_key", -        "secret": true, -        "env": "AUTHELIA_SESSION_REDIS_TLS_PRIVATE_KEY_FILE" +        "path": "notifier.smtp.identifier", +        "secret": false, +        "env": "AUTHELIA_NOTIFIER_SMTP_IDENTIFIER"      },      { -        "path": "session.redis.tls.certificate_chain", +        "path": "notifier.smtp.password",          "secret": true, -        "env": "AUTHELIA_SESSION_REDIS_TLS_CERTIFICATE_CHAIN_FILE" +        "env": "AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE"      },      { -        "path": "session.redis.high_availability.sentinel_name", +        "path": "notifier.smtp.sender",          "secret": false, -        "env": "AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_NAME" +        "env": "AUTHELIA_NOTIFIER_SMTP_SENDER"      },      { -        "path": "session.redis.high_availability.sentinel_username", +        "path": "notifier.smtp.startup_check_address",          "secret": false, -        "env": "AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_USERNAME" +        "env": "AUTHELIA_NOTIFIER_SMTP_STARTUP_CHECK_ADDRESS"      },      { -        "path": "session.redis.high_availability.sentinel_password", -        "secret": true, -        "env": "AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_PASSWORD_FILE" +        "path": "notifier.smtp.subject", +        "secret": false, +        "env": "AUTHELIA_NOTIFIER_SMTP_SUBJECT"      },      { -        "path": "session.redis.high_availability.route_by_latency", +        "path": "notifier.smtp.timeout",          "secret": false, -        "env": "AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_ROUTE_BY_LATENCY" +        "env": "AUTHELIA_NOTIFIER_SMTP_TIMEOUT"      },      { -        "path": "session.redis.high_availability.route_randomly", -        "secret": false, -        "env": "AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_ROUTE_RANDOMLY" +        "path": "notifier.smtp.tls.certificate_chain", +        "secret": true, +        "env": "AUTHELIA_NOTIFIER_SMTP_TLS_CERTIFICATE_CHAIN_FILE"      },      { -        "path": "totp.disable", +        "path": "notifier.smtp.tls.maximum_version",          "secret": false, -        "env": "AUTHELIA_TOTP_DISABLE" +        "env": "AUTHELIA_NOTIFIER_SMTP_TLS_MAXIMUM_VERSION"      },      { -        "path": "totp.issuer", +        "path": "notifier.smtp.tls.minimum_version",          "secret": false, -        "env": "AUTHELIA_TOTP_ISSUER" +        "env": "AUTHELIA_NOTIFIER_SMTP_TLS_MINIMUM_VERSION"      },      { -        "path": "totp.algorithm", -        "secret": false, -        "env": "AUTHELIA_TOTP_ALGORITHM" +        "path": "notifier.smtp.tls.private_key", +        "secret": true, +        "env": "AUTHELIA_NOTIFIER_SMTP_TLS_PRIVATE_KEY_FILE"      },      { -        "path": "totp.digits", +        "path": "notifier.smtp.tls.server_name",          "secret": false, -        "env": "AUTHELIA_TOTP_DIGITS" +        "env": "AUTHELIA_NOTIFIER_SMTP_TLS_SERVER_NAME"      },      { -        "path": "totp.period", +        "path": "notifier.smtp.tls.skip_verify",          "secret": false, -        "env": "AUTHELIA_TOTP_PERIOD" +        "env": "AUTHELIA_NOTIFIER_SMTP_TLS_SKIP_VERIFY"      },      { -        "path": "totp.skew", +        "path": "notifier.smtp.username",          "secret": false, -        "env": "AUTHELIA_TOTP_SKEW" +        "env": "AUTHELIA_NOTIFIER_SMTP_USERNAME"      },      { -        "path": "totp.secret_size", +        "path": "notifier.template_path",          "secret": false, -        "env": "AUTHELIA_TOTP_SECRET_SIZE" +        "env": "AUTHELIA_NOTIFIER_TEMPLATE_PATH"      },      { -        "path": "totp.allowed_algorithms", +        "path": "ntp.address",          "secret": false, -        "env": "AUTHELIA_TOTP_ALLOWED_ALGORITHMS" +        "env": "AUTHELIA_NTP_ADDRESS"      },      { -        "path": "totp.allowed_digits", +        "path": "ntp.disable_failure",          "secret": false, -        "env": "AUTHELIA_TOTP_ALLOWED_DIGITS" +        "env": "AUTHELIA_NTP_DISABLE_FAILURE"      },      { -        "path": "totp.allowed_periods", +        "path": "ntp.disable_startup_check",          "secret": false, -        "env": "AUTHELIA_TOTP_ALLOWED_PERIODS" +        "env": "AUTHELIA_NTP_DISABLE_STARTUP_CHECK"      },      { -        "path": "totp.disable_reuse_security_policy", +        "path": "ntp.max_desync",          "secret": false, -        "env": "AUTHELIA_TOTP_DISABLE_REUSE_SECURITY_POLICY" +        "env": "AUTHELIA_NTP_MAX_DESYNC"      },      { -        "path": "duo_api.disable", +        "path": "ntp.version",          "secret": false, -        "env": "AUTHELIA_DUO_API_DISABLE" +        "env": "AUTHELIA_NTP_VERSION"      },      { -        "path": "duo_api.hostname", +        "path": "password_policy.standard.enabled",          "secret": false, -        "env": "AUTHELIA_DUO_API_HOSTNAME" +        "env": "AUTHELIA_PASSWORD_POLICY_STANDARD_ENABLED"      },      { -        "path": "duo_api.integration_key", -        "secret": true, -        "env": "AUTHELIA_DUO_API_INTEGRATION_KEY_FILE" +        "path": "password_policy.standard.max_length", +        "secret": false, +        "env": "AUTHELIA_PASSWORD_POLICY_STANDARD_MAX_LENGTH"      },      { -        "path": "duo_api.secret_key", -        "secret": true, -        "env": "AUTHELIA_DUO_API_SECRET_KEY_FILE" +        "path": "password_policy.standard.min_length", +        "secret": false, +        "env": "AUTHELIA_PASSWORD_POLICY_STANDARD_MIN_LENGTH"      },      { -        "path": "duo_api.enable_self_enrollment", +        "path": "password_policy.standard.require_lowercase",          "secret": false, -        "env": "AUTHELIA_DUO_API_ENABLE_SELF_ENROLLMENT" +        "env": "AUTHELIA_PASSWORD_POLICY_STANDARD_REQUIRE_LOWERCASE"      },      { -        "path": "access_control.default_policy", +        "path": "password_policy.standard.require_number",          "secret": false, -        "env": "AUTHELIA_ACCESS_CONTROL_DEFAULT_POLICY" +        "env": "AUTHELIA_PASSWORD_POLICY_STANDARD_REQUIRE_NUMBER"      },      { -        "path": "ntp.address", +        "path": "password_policy.standard.require_special",          "secret": false, -        "env": "AUTHELIA_NTP_ADDRESS" +        "env": "AUTHELIA_PASSWORD_POLICY_STANDARD_REQUIRE_SPECIAL"      },      { -        "path": "ntp.version", +        "path": "password_policy.standard.require_uppercase",          "secret": false, -        "env": "AUTHELIA_NTP_VERSION" +        "env": "AUTHELIA_PASSWORD_POLICY_STANDARD_REQUIRE_UPPERCASE"      },      { -        "path": "ntp.max_desync", +        "path": "password_policy.zxcvbn.enabled",          "secret": false, -        "env": "AUTHELIA_NTP_MAX_DESYNC" +        "env": "AUTHELIA_PASSWORD_POLICY_ZXCVBN_ENABLED"      },      { -        "path": "ntp.disable_startup_check", +        "path": "password_policy.zxcvbn.min_score",          "secret": false, -        "env": "AUTHELIA_NTP_DISABLE_STARTUP_CHECK" +        "env": "AUTHELIA_PASSWORD_POLICY_ZXCVBN_MIN_SCORE"      },      { -        "path": "ntp.disable_failure", +        "path": "privacy_policy.enabled",          "secret": false, -        "env": "AUTHELIA_NTP_DISABLE_FAILURE" +        "env": "AUTHELIA_PRIVACY_POLICY_ENABLED"      },      { -        "path": "regulation.max_retries", +        "path": "privacy_policy.policy_url",          "secret": false, -        "env": "AUTHELIA_REGULATION_MAX_RETRIES" +        "env": "AUTHELIA_PRIVACY_POLICY_POLICY_URL"      },      { -        "path": "regulation.find_time", +        "path": "privacy_policy.require_user_acceptance",          "secret": false, -        "env": "AUTHELIA_REGULATION_FIND_TIME" +        "env": "AUTHELIA_PRIVACY_POLICY_REQUIRE_USER_ACCEPTANCE"      },      {          "path": "regulation.ban_time", @@ -760,518 +755,523 @@          "env": "AUTHELIA_REGULATION_BAN_TIME"      },      { -        "path": "storage.local.path", +        "path": "regulation.find_time",          "secret": false, -        "env": "AUTHELIA_STORAGE_LOCAL_PATH" +        "env": "AUTHELIA_REGULATION_FIND_TIME"      },      { -        "path": "storage.mysql.address", +        "path": "regulation.max_retries",          "secret": false, -        "env": "AUTHELIA_STORAGE_MYSQL_ADDRESS" +        "env": "AUTHELIA_REGULATION_MAX_RETRIES"      },      { -        "path": "storage.mysql.database", +        "path": "server.address",          "secret": false, -        "env": "AUTHELIA_STORAGE_MYSQL_DATABASE" +        "env": "AUTHELIA_SERVER_ADDRESS"      },      { -        "path": "storage.mysql.username", +        "path": "server.asset_path",          "secret": false, -        "env": "AUTHELIA_STORAGE_MYSQL_USERNAME" +        "env": "AUTHELIA_SERVER_ASSET_PATH"      },      { -        "path": "storage.mysql.password", -        "secret": true, -        "env": "AUTHELIA_STORAGE_MYSQL_PASSWORD_FILE" +        "path": "server.buffers.read", +        "secret": false, +        "env": "AUTHELIA_SERVER_BUFFERS_READ"      },      { -        "path": "storage.mysql.timeout", +        "path": "server.buffers.write",          "secret": false, -        "env": "AUTHELIA_STORAGE_MYSQL_TIMEOUT" +        "env": "AUTHELIA_SERVER_BUFFERS_WRITE"      },      { -        "path": "storage.mysql.tls.minimum_version", +        "path": "server.disable_healthcheck",          "secret": false, -        "env": "AUTHELIA_STORAGE_MYSQL_TLS_MINIMUM_VERSION" +        "env": "AUTHELIA_SERVER_DISABLE_HEALTHCHECK"      },      { -        "path": "storage.mysql.tls.maximum_version", +        "path": "server.endpoints.enable_expvars",          "secret": false, -        "env": "AUTHELIA_STORAGE_MYSQL_TLS_MAXIMUM_VERSION" +        "env": "AUTHELIA_SERVER_ENDPOINTS_ENABLE_EXPVARS"      },      { -        "path": "storage.mysql.tls.skip_verify", +        "path": "server.endpoints.enable_pprof",          "secret": false, -        "env": "AUTHELIA_STORAGE_MYSQL_TLS_SKIP_VERIFY" +        "env": "AUTHELIA_SERVER_ENDPOINTS_ENABLE_PPROF"      },      { -        "path": "storage.mysql.tls.server_name", +        "path": "server.headers.csp_template",          "secret": false, -        "env": "AUTHELIA_STORAGE_MYSQL_TLS_SERVER_NAME" +        "env": "AUTHELIA_SERVER_HEADERS_CSP_TEMPLATE"      },      { -        "path": "storage.mysql.tls.private_key", -        "secret": true, -        "env": "AUTHELIA_STORAGE_MYSQL_TLS_PRIVATE_KEY_FILE" +        "path": "server.timeouts.idle", +        "secret": false, +        "env": "AUTHELIA_SERVER_TIMEOUTS_IDLE"      },      { -        "path": "storage.mysql.tls.certificate_chain", -        "secret": true, -        "env": "AUTHELIA_STORAGE_MYSQL_TLS_CERTIFICATE_CHAIN_FILE" +        "path": "server.timeouts.read", +        "secret": false, +        "env": "AUTHELIA_SERVER_TIMEOUTS_READ"      },      { -        "path": "storage.postgres.address", +        "path": "server.timeouts.write",          "secret": false, -        "env": "AUTHELIA_STORAGE_POSTGRES_ADDRESS" +        "env": "AUTHELIA_SERVER_TIMEOUTS_WRITE"      },      { -        "path": "storage.postgres.database", +        "path": "server.tls.certificate",          "secret": false, -        "env": "AUTHELIA_STORAGE_POSTGRES_DATABASE" +        "env": "AUTHELIA_SERVER_TLS_CERTIFICATE"      },      { -        "path": "storage.postgres.username", +        "path": "server.tls.client_certificates",          "secret": false, -        "env": "AUTHELIA_STORAGE_POSTGRES_USERNAME" +        "env": "AUTHELIA_SERVER_TLS_CLIENT_CERTIFICATES"      },      { -        "path": "storage.postgres.password", -        "secret": true, -        "env": "AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE" +        "path": "server.tls.key", +        "secret": false, +        "env": "AUTHELIA_SERVER_TLS_KEY"      },      { -        "path": "storage.postgres.timeout", +        "path": "session",          "secret": false, -        "env": "AUTHELIA_STORAGE_POSTGRES_TIMEOUT" +        "env": "AUTHELIA_SESSION"      },      { -        "path": "storage.postgres.schema", +        "path": "session.expiration",          "secret": false, -        "env": "AUTHELIA_STORAGE_POSTGRES_SCHEMA" +        "env": "AUTHELIA_SESSION_EXPIRATION"      },      { -        "path": "storage.postgres.tls.minimum_version", +        "path": "session.inactivity",          "secret": false, -        "env": "AUTHELIA_STORAGE_POSTGRES_TLS_MINIMUM_VERSION" +        "env": "AUTHELIA_SESSION_INACTIVITY"      },      { -        "path": "storage.postgres.tls.maximum_version", +        "path": "session.name",          "secret": false, -        "env": "AUTHELIA_STORAGE_POSTGRES_TLS_MAXIMUM_VERSION" +        "env": "AUTHELIA_SESSION_NAME"      },      { -        "path": "storage.postgres.tls.skip_verify", +        "path": "session.redis.database_index",          "secret": false, -        "env": "AUTHELIA_STORAGE_POSTGRES_TLS_SKIP_VERIFY" +        "env": "AUTHELIA_SESSION_REDIS_DATABASE_INDEX"      },      { -        "path": "storage.postgres.tls.server_name", +        "path": "session.redis.high_availability.route_by_latency",          "secret": false, -        "env": "AUTHELIA_STORAGE_POSTGRES_TLS_SERVER_NAME" +        "env": "AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_ROUTE_BY_LATENCY"      },      { -        "path": "storage.postgres.tls.private_key", -        "secret": true, -        "env": "AUTHELIA_STORAGE_POSTGRES_TLS_PRIVATE_KEY_FILE" +        "path": "session.redis.high_availability.route_randomly", +        "secret": false, +        "env": "AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_ROUTE_RANDOMLY"      },      { -        "path": "storage.postgres.tls.certificate_chain", -        "secret": true, -        "env": "AUTHELIA_STORAGE_POSTGRES_TLS_CERTIFICATE_CHAIN_FILE" +        "path": "session.redis.high_availability.sentinel_name", +        "secret": false, +        "env": "AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_NAME"      },      { -        "path": "storage.encryption_key", +        "path": "session.redis.high_availability.sentinel_password",          "secret": true, -        "env": "AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE" +        "env": "AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_PASSWORD_FILE"      },      { -        "path": "notifier.disable_startup_check", +        "path": "session.redis.high_availability.sentinel_username",          "secret": false, -        "env": "AUTHELIA_NOTIFIER_DISABLE_STARTUP_CHECK" +        "env": "AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_USERNAME"      },      { -        "path": "notifier.filesystem.filename", +        "path": "session.redis.host",          "secret": false, -        "env": "AUTHELIA_NOTIFIER_FILESYSTEM_FILENAME" +        "env": "AUTHELIA_SESSION_REDIS_HOST"      },      { -        "path": "notifier.smtp.address", +        "path": "session.redis.max_retries",          "secret": false, -        "env": "AUTHELIA_NOTIFIER_SMTP_ADDRESS" +        "env": "AUTHELIA_SESSION_REDIS_MAX_RETRIES"      },      { -        "path": "notifier.smtp.timeout", +        "path": "session.redis.maximum_active_connections",          "secret": false, -        "env": "AUTHELIA_NOTIFIER_SMTP_TIMEOUT" +        "env": "AUTHELIA_SESSION_REDIS_MAXIMUM_ACTIVE_CONNECTIONS"      },      { -        "path": "notifier.smtp.username", +        "path": "session.redis.minimum_idle_connections",          "secret": false, -        "env": "AUTHELIA_NOTIFIER_SMTP_USERNAME" +        "env": "AUTHELIA_SESSION_REDIS_MINIMUM_IDLE_CONNECTIONS"      },      { -        "path": "notifier.smtp.password", +        "path": "session.redis.password",          "secret": true, -        "env": "AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE" +        "env": "AUTHELIA_SESSION_REDIS_PASSWORD_FILE"      },      { -        "path": "notifier.smtp.identifier", +        "path": "session.redis.port",          "secret": false, -        "env": "AUTHELIA_NOTIFIER_SMTP_IDENTIFIER" +        "env": "AUTHELIA_SESSION_REDIS_PORT"      },      { -        "path": "notifier.smtp.sender", +        "path": "session.redis.timeout",          "secret": false, -        "env": "AUTHELIA_NOTIFIER_SMTP_SENDER" +        "env": "AUTHELIA_SESSION_REDIS_TIMEOUT"      },      { -        "path": "notifier.smtp.subject", -        "secret": false, -        "env": "AUTHELIA_NOTIFIER_SMTP_SUBJECT" +        "path": "session.redis.tls.certificate_chain", +        "secret": true, +        "env": "AUTHELIA_SESSION_REDIS_TLS_CERTIFICATE_CHAIN_FILE"      },      { -        "path": "notifier.smtp.startup_check_address", +        "path": "session.redis.tls.maximum_version",          "secret": false, -        "env": "AUTHELIA_NOTIFIER_SMTP_STARTUP_CHECK_ADDRESS" +        "env": "AUTHELIA_SESSION_REDIS_TLS_MAXIMUM_VERSION"      },      { -        "path": "notifier.smtp.disable_require_tls", +        "path": "session.redis.tls.minimum_version",          "secret": false, -        "env": "AUTHELIA_NOTIFIER_SMTP_DISABLE_REQUIRE_TLS" +        "env": "AUTHELIA_SESSION_REDIS_TLS_MINIMUM_VERSION"      },      { -        "path": "notifier.smtp.disable_html_emails", -        "secret": false, -        "env": "AUTHELIA_NOTIFIER_SMTP_DISABLE_HTML_EMAILS" +        "path": "session.redis.tls.private_key", +        "secret": true, +        "env": "AUTHELIA_SESSION_REDIS_TLS_PRIVATE_KEY_FILE"      },      { -        "path": "notifier.smtp.disable_starttls", +        "path": "session.redis.tls.server_name",          "secret": false, -        "env": "AUTHELIA_NOTIFIER_SMTP_DISABLE_STARTTLS" +        "env": "AUTHELIA_SESSION_REDIS_TLS_SERVER_NAME"      },      { -        "path": "notifier.smtp.tls.minimum_version", +        "path": "session.redis.tls.skip_verify",          "secret": false, -        "env": "AUTHELIA_NOTIFIER_SMTP_TLS_MINIMUM_VERSION" +        "env": "AUTHELIA_SESSION_REDIS_TLS_SKIP_VERIFY"      },      { -        "path": "notifier.smtp.tls.maximum_version", +        "path": "session.redis.username",          "secret": false, -        "env": "AUTHELIA_NOTIFIER_SMTP_TLS_MAXIMUM_VERSION" +        "env": "AUTHELIA_SESSION_REDIS_USERNAME"      },      { -        "path": "notifier.smtp.tls.skip_verify", +        "path": "session.remember_me",          "secret": false, -        "env": "AUTHELIA_NOTIFIER_SMTP_TLS_SKIP_VERIFY" +        "env": "AUTHELIA_SESSION_REMEMBER_ME"      },      { -        "path": "notifier.smtp.tls.server_name", +        "path": "session.same_site",          "secret": false, -        "env": "AUTHELIA_NOTIFIER_SMTP_TLS_SERVER_NAME" +        "env": "AUTHELIA_SESSION_SAME_SITE"      },      { -        "path": "notifier.smtp.tls.private_key", +        "path": "session.secret",          "secret": true, -        "env": "AUTHELIA_NOTIFIER_SMTP_TLS_PRIVATE_KEY_FILE" +        "env": "AUTHELIA_SESSION_SECRET_FILE"      },      { -        "path": "notifier.smtp.tls.certificate_chain", +        "path": "storage.encryption_key",          "secret": true, -        "env": "AUTHELIA_NOTIFIER_SMTP_TLS_CERTIFICATE_CHAIN_FILE" +        "env": "AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE"      },      { -        "path": "notifier.template_path", +        "path": "storage.local.path",          "secret": false, -        "env": "AUTHELIA_NOTIFIER_TEMPLATE_PATH" +        "env": "AUTHELIA_STORAGE_LOCAL_PATH"      },      { -        "path": "server.address", +        "path": "storage.mysql.address",          "secret": false, -        "env": "AUTHELIA_SERVER_ADDRESS" +        "env": "AUTHELIA_STORAGE_MYSQL_ADDRESS"      },      { -        "path": "server.asset_path", +        "path": "storage.mysql.database",          "secret": false, -        "env": "AUTHELIA_SERVER_ASSET_PATH" +        "env": "AUTHELIA_STORAGE_MYSQL_DATABASE"      },      { -        "path": "server.disable_healthcheck", -        "secret": false, -        "env": "AUTHELIA_SERVER_DISABLE_HEALTHCHECK" +        "path": "storage.mysql.password", +        "secret": true, +        "env": "AUTHELIA_STORAGE_MYSQL_PASSWORD_FILE"      },      { -        "path": "server.tls.certificate", +        "path": "storage.mysql.timeout",          "secret": false, -        "env": "AUTHELIA_SERVER_TLS_CERTIFICATE" +        "env": "AUTHELIA_STORAGE_MYSQL_TIMEOUT"      },      { -        "path": "server.tls.key", -        "secret": false, -        "env": "AUTHELIA_SERVER_TLS_KEY" +        "path": "storage.mysql.tls.certificate_chain", +        "secret": true, +        "env": "AUTHELIA_STORAGE_MYSQL_TLS_CERTIFICATE_CHAIN_FILE"      },      { -        "path": "server.tls.client_certificates", +        "path": "storage.mysql.tls.maximum_version",          "secret": false, -        "env": "AUTHELIA_SERVER_TLS_CLIENT_CERTIFICATES" +        "env": "AUTHELIA_STORAGE_MYSQL_TLS_MAXIMUM_VERSION"      },      { -        "path": "server.headers.csp_template", +        "path": "storage.mysql.tls.minimum_version",          "secret": false, -        "env": "AUTHELIA_SERVER_HEADERS_CSP_TEMPLATE" +        "env": "AUTHELIA_STORAGE_MYSQL_TLS_MINIMUM_VERSION"      },      { -        "path": "server.endpoints.enable_pprof", -        "secret": false, -        "env": "AUTHELIA_SERVER_ENDPOINTS_ENABLE_PPROF" +        "path": "storage.mysql.tls.private_key", +        "secret": true, +        "env": "AUTHELIA_STORAGE_MYSQL_TLS_PRIVATE_KEY_FILE"      },      { -        "path": "server.endpoints.enable_expvars", +        "path": "storage.mysql.tls.server_name",          "secret": false, -        "env": "AUTHELIA_SERVER_ENDPOINTS_ENABLE_EXPVARS" +        "env": "AUTHELIA_STORAGE_MYSQL_TLS_SERVER_NAME"      },      { -        "path": "server.buffers.read", +        "path": "storage.mysql.tls.skip_verify",          "secret": false, -        "env": "AUTHELIA_SERVER_BUFFERS_READ" +        "env": "AUTHELIA_STORAGE_MYSQL_TLS_SKIP_VERIFY"      },      { -        "path": "server.buffers.write", +        "path": "storage.mysql.username",          "secret": false, -        "env": "AUTHELIA_SERVER_BUFFERS_WRITE" +        "env": "AUTHELIA_STORAGE_MYSQL_USERNAME"      },      { -        "path": "server.timeouts.read", +        "path": "storage.postgres.address",          "secret": false, -        "env": "AUTHELIA_SERVER_TIMEOUTS_READ" +        "env": "AUTHELIA_STORAGE_POSTGRES_ADDRESS"      },      { -        "path": "server.timeouts.write", +        "path": "storage.postgres.database",          "secret": false, -        "env": "AUTHELIA_SERVER_TIMEOUTS_WRITE" +        "env": "AUTHELIA_STORAGE_POSTGRES_DATABASE"      },      { -        "path": "server.timeouts.idle", -        "secret": false, -        "env": "AUTHELIA_SERVER_TIMEOUTS_IDLE" +        "path": "storage.postgres.password", +        "secret": true, +        "env": "AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE"      },      { -        "path": "telemetry.metrics.enabled", +        "path": "storage.postgres.schema",          "secret": false, -        "env": "AUTHELIA_TELEMETRY_METRICS_ENABLED" +        "env": "AUTHELIA_STORAGE_POSTGRES_SCHEMA"      },      { -        "path": "telemetry.metrics.address", +        "path": "storage.postgres.timeout",          "secret": false, -        "env": "AUTHELIA_TELEMETRY_METRICS_ADDRESS" +        "env": "AUTHELIA_STORAGE_POSTGRES_TIMEOUT"      },      { -        "path": "telemetry.metrics.buffers.read", -        "secret": false, -        "env": "AUTHELIA_TELEMETRY_METRICS_BUFFERS_READ" +        "path": "storage.postgres.tls.certificate_chain", +        "secret": true, +        "env": "AUTHELIA_STORAGE_POSTGRES_TLS_CERTIFICATE_CHAIN_FILE"      },      { -        "path": "telemetry.metrics.buffers.write", +        "path": "storage.postgres.tls.maximum_version",          "secret": false, -        "env": "AUTHELIA_TELEMETRY_METRICS_BUFFERS_WRITE" +        "env": "AUTHELIA_STORAGE_POSTGRES_TLS_MAXIMUM_VERSION"      },      { -        "path": "telemetry.metrics.timeouts.read", +        "path": "storage.postgres.tls.minimum_version",          "secret": false, -        "env": "AUTHELIA_TELEMETRY_METRICS_TIMEOUTS_READ" +        "env": "AUTHELIA_STORAGE_POSTGRES_TLS_MINIMUM_VERSION"      },      { -        "path": "telemetry.metrics.timeouts.write", +        "path": "storage.postgres.tls.private_key", +        "secret": true, +        "env": "AUTHELIA_STORAGE_POSTGRES_TLS_PRIVATE_KEY_FILE" +    }, +    { +        "path": "storage.postgres.tls.server_name",          "secret": false, -        "env": "AUTHELIA_TELEMETRY_METRICS_TIMEOUTS_WRITE" +        "env": "AUTHELIA_STORAGE_POSTGRES_TLS_SERVER_NAME"      },      { -        "path": "telemetry.metrics.timeouts.idle", +        "path": "storage.postgres.tls.skip_verify",          "secret": false, -        "env": "AUTHELIA_TELEMETRY_METRICS_TIMEOUTS_IDLE" +        "env": "AUTHELIA_STORAGE_POSTGRES_TLS_SKIP_VERIFY"      },      { -        "path": "webauthn.disable", +        "path": "storage.postgres.username",          "secret": false, -        "env": "AUTHELIA_WEBAUTHN_DISABLE" +        "env": "AUTHELIA_STORAGE_POSTGRES_USERNAME"      },      { -        "path": "webauthn.enable_passkey_login", +        "path": "telemetry.metrics.address",          "secret": false, -        "env": "AUTHELIA_WEBAUTHN_ENABLE_PASSKEY_LOGIN" +        "env": "AUTHELIA_TELEMETRY_METRICS_ADDRESS"      },      { -        "path": "webauthn.experimental_enable_passkey_uv_two_factors", +        "path": "telemetry.metrics.buffers.read",          "secret": false, -        "env": "AUTHELIA_WEBAUTHN_EXPERIMENTAL_ENABLE_PASSKEY_UV_TWO_FACTORS" +        "env": "AUTHELIA_TELEMETRY_METRICS_BUFFERS_READ"      },      { -        "path": "webauthn.display_name", +        "path": "telemetry.metrics.buffers.write",          "secret": false, -        "env": "AUTHELIA_WEBAUTHN_DISPLAY_NAME" +        "env": "AUTHELIA_TELEMETRY_METRICS_BUFFERS_WRITE"      },      { -        "path": "webauthn.attestation_conveyance_preference", +        "path": "telemetry.metrics.enabled",          "secret": false, -        "env": "AUTHELIA_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE" +        "env": "AUTHELIA_TELEMETRY_METRICS_ENABLED"      },      { -        "path": "webauthn.timeout", +        "path": "telemetry.metrics.timeouts.idle",          "secret": false, -        "env": "AUTHELIA_WEBAUTHN_TIMEOUT" +        "env": "AUTHELIA_TELEMETRY_METRICS_TIMEOUTS_IDLE"      },      { -        "path": "webauthn.filtering.prohibit_backup_eligibility", +        "path": "telemetry.metrics.timeouts.read",          "secret": false, -        "env": "AUTHELIA_WEBAUTHN_FILTERING_PROHIBIT_BACKUP_ELIGIBILITY" +        "env": "AUTHELIA_TELEMETRY_METRICS_TIMEOUTS_READ"      },      { -        "path": "webauthn.selection_criteria.attachment", +        "path": "telemetry.metrics.timeouts.write",          "secret": false, -        "env": "AUTHELIA_WEBAUTHN_SELECTION_CRITERIA_ATTACHMENT" +        "env": "AUTHELIA_TELEMETRY_METRICS_TIMEOUTS_WRITE"      },      { -        "path": "webauthn.selection_criteria.discoverability", +        "path": "theme",          "secret": false, -        "env": "AUTHELIA_WEBAUTHN_SELECTION_CRITERIA_DISCOVERABILITY" +        "env": "AUTHELIA_THEME"      },      { -        "path": "webauthn.selection_criteria.user_verification", +        "path": "totp.algorithm",          "secret": false, -        "env": "AUTHELIA_WEBAUTHN_SELECTION_CRITERIA_USER_VERIFICATION" +        "env": "AUTHELIA_TOTP_ALGORITHM"      },      { -        "path": "webauthn.metadata.enabled", +        "path": "totp.allowed_algorithms",          "secret": false, -        "env": "AUTHELIA_WEBAUTHN_METADATA_ENABLED" +        "env": "AUTHELIA_TOTP_ALLOWED_ALGORITHMS"      },      { -        "path": "webauthn.metadata.validate_trust_anchor", +        "path": "totp.allowed_digits",          "secret": false, -        "env": "AUTHELIA_WEBAUTHN_METADATA_VALIDATE_TRUST_ANCHOR" +        "env": "AUTHELIA_TOTP_ALLOWED_DIGITS"      },      { -        "path": "webauthn.metadata.validate_entry", +        "path": "totp.allowed_periods",          "secret": false, -        "env": "AUTHELIA_WEBAUTHN_METADATA_VALIDATE_ENTRY" +        "env": "AUTHELIA_TOTP_ALLOWED_PERIODS"      },      { -        "path": "webauthn.metadata.validate_entry_permit_zero_aaguid", +        "path": "totp.digits",          "secret": false, -        "env": "AUTHELIA_WEBAUTHN_METADATA_VALIDATE_ENTRY_PERMIT_ZERO_AAGUID" +        "env": "AUTHELIA_TOTP_DIGITS"      },      { -        "path": "webauthn.metadata.validate_status", +        "path": "totp.disable",          "secret": false, -        "env": "AUTHELIA_WEBAUTHN_METADATA_VALIDATE_STATUS" +        "env": "AUTHELIA_TOTP_DISABLE"      },      { -        "path": "webauthn.metadata.validate_status_permitted", +        "path": "totp.disable_reuse_security_policy",          "secret": false, -        "env": "AUTHELIA_WEBAUTHN_METADATA_VALIDATE_STATUS_PERMITTED" +        "env": "AUTHELIA_TOTP_DISABLE_REUSE_SECURITY_POLICY"      },      { -        "path": "webauthn.metadata.validate_status_prohibited", +        "path": "totp.issuer",          "secret": false, -        "env": "AUTHELIA_WEBAUTHN_METADATA_VALIDATE_STATUS_PROHIBITED" +        "env": "AUTHELIA_TOTP_ISSUER"      },      { -        "path": "password_policy.standard.enabled", +        "path": "totp.period",          "secret": false, -        "env": "AUTHELIA_PASSWORD_POLICY_STANDARD_ENABLED" +        "env": "AUTHELIA_TOTP_PERIOD"      },      { -        "path": "password_policy.standard.min_length", +        "path": "totp.secret_size",          "secret": false, -        "env": "AUTHELIA_PASSWORD_POLICY_STANDARD_MIN_LENGTH" +        "env": "AUTHELIA_TOTP_SECRET_SIZE"      },      { -        "path": "password_policy.standard.max_length", +        "path": "totp.skew",          "secret": false, -        "env": "AUTHELIA_PASSWORD_POLICY_STANDARD_MAX_LENGTH" +        "env": "AUTHELIA_TOTP_SKEW"      },      { -        "path": "password_policy.standard.require_uppercase", +        "path": "webauthn.attestation_conveyance_preference",          "secret": false, -        "env": "AUTHELIA_PASSWORD_POLICY_STANDARD_REQUIRE_UPPERCASE" +        "env": "AUTHELIA_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE"      },      { -        "path": "password_policy.standard.require_lowercase", +        "path": "webauthn.disable",          "secret": false, -        "env": "AUTHELIA_PASSWORD_POLICY_STANDARD_REQUIRE_LOWERCASE" +        "env": "AUTHELIA_WEBAUTHN_DISABLE"      },      { -        "path": "password_policy.standard.require_number", +        "path": "webauthn.display_name",          "secret": false, -        "env": "AUTHELIA_PASSWORD_POLICY_STANDARD_REQUIRE_NUMBER" +        "env": "AUTHELIA_WEBAUTHN_DISPLAY_NAME"      },      { -        "path": "password_policy.standard.require_special", +        "path": "webauthn.enable_passkey_login",          "secret": false, -        "env": "AUTHELIA_PASSWORD_POLICY_STANDARD_REQUIRE_SPECIAL" +        "env": "AUTHELIA_WEBAUTHN_ENABLE_PASSKEY_LOGIN"      },      { -        "path": "password_policy.zxcvbn.enabled", +        "path": "webauthn.experimental_enable_passkey_uv_two_factors",          "secret": false, -        "env": "AUTHELIA_PASSWORD_POLICY_ZXCVBN_ENABLED" +        "env": "AUTHELIA_WEBAUTHN_EXPERIMENTAL_ENABLE_PASSKEY_UV_TWO_FACTORS"      },      { -        "path": "password_policy.zxcvbn.min_score", +        "path": "webauthn.filtering.prohibit_backup_eligibility",          "secret": false, -        "env": "AUTHELIA_PASSWORD_POLICY_ZXCVBN_MIN_SCORE" +        "env": "AUTHELIA_WEBAUTHN_FILTERING_PROHIBIT_BACKUP_ELIGIBILITY"      },      { -        "path": "privacy_policy.enabled", +        "path": "webauthn.metadata.enabled",          "secret": false, -        "env": "AUTHELIA_PRIVACY_POLICY_ENABLED" +        "env": "AUTHELIA_WEBAUTHN_METADATA_ENABLED"      },      { -        "path": "privacy_policy.require_user_acceptance", +        "path": "webauthn.metadata.validate_entry",          "secret": false, -        "env": "AUTHELIA_PRIVACY_POLICY_REQUIRE_USER_ACCEPTANCE" +        "env": "AUTHELIA_WEBAUTHN_METADATA_VALIDATE_ENTRY"      },      { -        "path": "privacy_policy.policy_url", +        "path": "webauthn.metadata.validate_entry_permit_zero_aaguid",          "secret": false, -        "env": "AUTHELIA_PRIVACY_POLICY_POLICY_URL" +        "env": "AUTHELIA_WEBAUTHN_METADATA_VALIDATE_ENTRY_PERMIT_ZERO_AAGUID"      },      { -        "path": "identity_validation.reset_password.jwt_lifespan", +        "path": "webauthn.metadata.validate_status",          "secret": false, -        "env": "AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_LIFESPAN" +        "env": "AUTHELIA_WEBAUTHN_METADATA_VALIDATE_STATUS"      },      { -        "path": "identity_validation.reset_password.jwt_algorithm", +        "path": "webauthn.metadata.validate_status_permitted",          "secret": false, -        "env": "AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_ALGORITHM" +        "env": "AUTHELIA_WEBAUTHN_METADATA_VALIDATE_STATUS_PERMITTED"      },      { -        "path": "identity_validation.reset_password.jwt_secret", -        "secret": true, -        "env": "AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE" +        "path": "webauthn.metadata.validate_status_prohibited", +        "secret": false, +        "env": "AUTHELIA_WEBAUTHN_METADATA_VALIDATE_STATUS_PROHIBITED"      },      { -        "path": "identity_validation.elevated_session.code_lifespan", +        "path": "webauthn.metadata.validate_trust_anchor",          "secret": false, -        "env": "AUTHELIA_IDENTITY_VALIDATION_ELEVATED_SESSION_CODE_LIFESPAN" +        "env": "AUTHELIA_WEBAUTHN_METADATA_VALIDATE_TRUST_ANCHOR"      },      { -        "path": "identity_validation.elevated_session.elevation_lifespan", +        "path": "webauthn.selection_criteria.attachment",          "secret": false, -        "env": "AUTHELIA_IDENTITY_VALIDATION_ELEVATED_SESSION_ELEVATION_LIFESPAN" +        "env": "AUTHELIA_WEBAUTHN_SELECTION_CRITERIA_ATTACHMENT"      },      { -        "path": "identity_validation.elevated_session.characters", +        "path": "webauthn.selection_criteria.discoverability",          "secret": false, -        "env": "AUTHELIA_IDENTITY_VALIDATION_ELEVATED_SESSION_CHARACTERS" +        "env": "AUTHELIA_WEBAUTHN_SELECTION_CRITERIA_DISCOVERABILITY"      },      { -        "path": "identity_validation.elevated_session.require_second_factor", +        "path": "webauthn.selection_criteria.user_verification",          "secret": false, -        "env": "AUTHELIA_IDENTITY_VALIDATION_ELEVATED_SESSION_REQUIRE_SECOND_FACTOR" +        "env": "AUTHELIA_WEBAUTHN_SELECTION_CRITERIA_USER_VERIFICATION"      },      { -        "path": "identity_validation.elevated_session.skip_second_factor", +        "path": "webauthn.timeout",          "secret": false, -        "env": "AUTHELIA_IDENTITY_VALIDATION_ELEVATED_SESSION_SKIP_SECOND_FACTOR" +        "env": "AUTHELIA_WEBAUTHN_TIMEOUT"      }  ] diff --git a/docs/static/schemas/v4.39/json-schema/configuration.json b/docs/static/schemas/v4.39/json-schema/configuration.json index 39d9ad7d4..2e72ef96c 100644 --- a/docs/static/schemas/v4.39/json-schema/configuration.json +++ b/docs/static/schemas/v4.39/json-schema/configuration.json @@ -3679,7 +3679,7 @@          "address": {            "$ref": "#/$defs/AddressTCP",            "title": "Address", -          "description": "The address of the database." +          "description": "The address of the SQL Server."          },          "database": {            "type": "string", @@ -3723,7 +3723,7 @@          "address": {            "$ref": "#/$defs/AddressTCP",            "title": "Address", -          "description": "The address of the database." +          "description": "The address of the SQL Server."          },          "database": {            "type": "string", @@ -3754,14 +3754,22 @@            "title": "Timeout",            "description": "The timeout for the database connection."          }, +        "tls": { +          "$ref": "#/$defs/TLS" +        },          "schema": {            "type": "string",            "title": "Schema",            "description": "The default schema name to use.",            "default": "public"          }, -        "tls": { -          "$ref": "#/$defs/TLS" +        "servers": { +          "items": { +            "$ref": "#/$defs/StoragePostgreSQLServer" +          }, +          "type": "array", +          "title": "Servers", +          "description": "The fallback PostgreSQL severs to connect to in addition to the one defined by the address."          },          "ssl": {            "$ref": "#/$defs/StoragePostgreSQLSSL", @@ -3811,6 +3819,20 @@        "type": "object",        "description": "StoragePostgreSQLSSL represents the SSL configuration of a PostgreSQL database."      }, +    "StoragePostgreSQLServer": { +      "properties": { +        "address": { +          "$ref": "#/$defs/AddressTCP", +          "title": "Address", +          "description": "The address of the PostgreSQL Server." +        }, +        "tls": { +          "$ref": "#/$defs/TLS" +        } +      }, +      "additionalProperties": false, +      "type": "object" +    },      "TLS": {        "properties": {          "minimum_version": { diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml index 2e01b7a15..0fd1756e9 100644 --- a/internal/configuration/config.template.yml +++ b/internal/configuration/config.template.yml @@ -1076,6 +1076,30 @@ session:      ## The default scheme is 'unix' if the address is an absolute path otherwise it's 'tcp'. The default port is '5432'.      # address: 'tcp://127.0.0.1:5432' +    ## List of additional server instance configurations to fallback to when the primary instance is not available. +    # servers: +      # - +        ## The Address of this individual instance. +        # address: 'tcp://127.0.0.1:5432' + +        ## The TLS configuration for this individual instance. +        # tls: +          # server_name: 'postgres.example.com' +          # skip_verify: false +          # minimum_version: 'TLS1.2' +          # maximum_version: 'TLS1.3' +          # certificate_chain: | +            # -----BEGIN CERTIFICATE----- +            # ... +            # -----END CERTIFICATE----- +            # -----BEGIN CERTIFICATE----- +            # ... +            # -----END CERTIFICATE----- +          # private_key: | +            # -----BEGIN RSA PRIVATE KEY----- +            # ... +            # -----END RSA PRIVATE KEY----- +      ## The database name to use.      # database: 'authelia' diff --git a/internal/configuration/schema/keys.go b/internal/configuration/schema/keys.go index f2efaca93..fbab487ef 100644 --- a/internal/configuration/schema/keys.go +++ b/internal/configuration/schema/keys.go @@ -8,443 +8,450 @@ package schema  // Keys is a list of valid schema keys detected by reflecting over a schema.Configuration struct.  var Keys = []string{ -	"theme", +	"access_control.default_policy", +	"access_control.networks", +	"access_control.networks[].name", +	"access_control.networks[].networks", +	"access_control.rules", +	"access_control.rules[].domain", +	"access_control.rules[].domain_regex", +	"access_control.rules[].methods", +	"access_control.rules[].networks", +	"access_control.rules[].policy", +	"access_control.rules[].query", +	"access_control.rules[].query[][].key", +	"access_control.rules[].query[][].operator", +	"access_control.rules[].query[][].value", +	"access_control.rules[].resources", +	"access_control.rules[].subject", +	"authentication_backend.file.extra_attributes", +	"authentication_backend.file.extra_attributes.*", +	"authentication_backend.file.extra_attributes.*.multi_valued", +	"authentication_backend.file.extra_attributes.*.value_type", +	"authentication_backend.file.password.algorithm", +	"authentication_backend.file.password.argon2.iterations", +	"authentication_backend.file.password.argon2.key_length", +	"authentication_backend.file.password.argon2.memory", +	"authentication_backend.file.password.argon2.parallelism", +	"authentication_backend.file.password.argon2.salt_length", +	"authentication_backend.file.password.argon2.variant", +	"authentication_backend.file.password.bcrypt.cost", +	"authentication_backend.file.password.bcrypt.variant", +	"authentication_backend.file.password.iterations", +	"authentication_backend.file.password.key_length", +	"authentication_backend.file.password.memory", +	"authentication_backend.file.password.parallelism", +	"authentication_backend.file.password.pbkdf2.iterations", +	"authentication_backend.file.password.pbkdf2.salt_length", +	"authentication_backend.file.password.pbkdf2.variant", +	"authentication_backend.file.password.salt_length", +	"authentication_backend.file.password.scrypt.block_size", +	"authentication_backend.file.password.scrypt.iterations", +	"authentication_backend.file.password.scrypt.key_length", +	"authentication_backend.file.password.scrypt.parallelism", +	"authentication_backend.file.password.scrypt.salt_length", +	"authentication_backend.file.password.sha2crypt.iterations", +	"authentication_backend.file.password.sha2crypt.salt_length", +	"authentication_backend.file.password.sha2crypt.variant", +	"authentication_backend.file.path", +	"authentication_backend.file.search.case_insensitive", +	"authentication_backend.file.search.email", +	"authentication_backend.file.watch", +	"authentication_backend.ldap.additional_groups_dn", +	"authentication_backend.ldap.additional_users_dn", +	"authentication_backend.ldap.address", +	"authentication_backend.ldap.attributes.birthdate", +	"authentication_backend.ldap.attributes.country", +	"authentication_backend.ldap.attributes.display_name", +	"authentication_backend.ldap.attributes.distinguished_name", +	"authentication_backend.ldap.attributes.extra", +	"authentication_backend.ldap.attributes.extra.*", +	"authentication_backend.ldap.attributes.extra.*.multi_valued", +	"authentication_backend.ldap.attributes.extra.*.name", +	"authentication_backend.ldap.attributes.extra.*.value_type", +	"authentication_backend.ldap.attributes.family_name", +	"authentication_backend.ldap.attributes.gender", +	"authentication_backend.ldap.attributes.given_name", +	"authentication_backend.ldap.attributes.group_name", +	"authentication_backend.ldap.attributes.locale", +	"authentication_backend.ldap.attributes.locality", +	"authentication_backend.ldap.attributes.mail", +	"authentication_backend.ldap.attributes.member_of", +	"authentication_backend.ldap.attributes.middle_name", +	"authentication_backend.ldap.attributes.nickname", +	"authentication_backend.ldap.attributes.phone_extension", +	"authentication_backend.ldap.attributes.phone_number", +	"authentication_backend.ldap.attributes.picture", +	"authentication_backend.ldap.attributes.postal_code", +	"authentication_backend.ldap.attributes.profile", +	"authentication_backend.ldap.attributes.region", +	"authentication_backend.ldap.attributes.street_address", +	"authentication_backend.ldap.attributes.username", +	"authentication_backend.ldap.attributes.website", +	"authentication_backend.ldap.attributes.zoneinfo", +	"authentication_backend.ldap.base_dn", +	"authentication_backend.ldap.group_search_mode", +	"authentication_backend.ldap.groups_filter", +	"authentication_backend.ldap.implementation", +	"authentication_backend.ldap.password", +	"authentication_backend.ldap.permit_feature_detection_failure", +	"authentication_backend.ldap.permit_referrals", +	"authentication_backend.ldap.permit_unauthenticated_bind", +	"authentication_backend.ldap.pooling.count", +	"authentication_backend.ldap.pooling.enable", +	"authentication_backend.ldap.pooling.retries", +	"authentication_backend.ldap.pooling.timeout", +	"authentication_backend.ldap.start_tls", +	"authentication_backend.ldap.timeout", +	"authentication_backend.ldap.tls.certificate_chain", +	"authentication_backend.ldap.tls.maximum_version", +	"authentication_backend.ldap.tls.minimum_version", +	"authentication_backend.ldap.tls.private_key", +	"authentication_backend.ldap.tls.server_name", +	"authentication_backend.ldap.tls.skip_verify", +	"authentication_backend.ldap.user", +	"authentication_backend.ldap.users_filter", +	"authentication_backend.password_reset.custom_url", +	"authentication_backend.password_reset.disable", +	"authentication_backend.refresh_interval",  	"certificates_directory",  	"default_2fa_method", -	"log.level", -	"log.format", -	"log.file_path", -	"log.keep_stdout", -	"identity_providers.oidc.hmac_secret", -	"identity_providers.oidc.jwks", -	"identity_providers.oidc.jwks[].key_id", -	"identity_providers.oidc.jwks[].use", -	"identity_providers.oidc.jwks[].algorithm", -	"identity_providers.oidc.jwks[].key", -	"identity_providers.oidc.jwks[].certificate_chain", -	"identity_providers.oidc.enable_client_debug_messages", -	"identity_providers.oidc.minimum_parameter_entropy", -	"identity_providers.oidc.enforce_pkce", -	"identity_providers.oidc.enable_pkce_plain_challenge", -	"identity_providers.oidc.enable_jwt_access_token_stateless_introspection", -	"identity_providers.oidc.discovery_signed_response_alg", -	"identity_providers.oidc.discovery_signed_response_key_id", -	"identity_providers.oidc.require_pushed_authorization_requests", -	"identity_providers.oidc.cors.endpoints", -	"identity_providers.oidc.cors.allowed_origins", -	"identity_providers.oidc.cors.allowed_origins_from_client_redirect_uris", +	"default_redirection_url", +	"definitions.network", +	"definitions.network.*", +	"definitions.user_attributes", +	"definitions.user_attributes.*", +	"definitions.user_attributes.*.expression", +	"duo_api.disable", +	"duo_api.enable_self_enrollment", +	"duo_api.hostname", +	"duo_api.integration_key", +	"duo_api.secret_key", +	"identity_providers.oidc", +	"identity_providers.oidc.authorization_policies", +	"identity_providers.oidc.authorization_policies.*", +	"identity_providers.oidc.authorization_policies.*.default_policy", +	"identity_providers.oidc.authorization_policies.*.rules", +	"identity_providers.oidc.authorization_policies.*.rules[].networks", +	"identity_providers.oidc.authorization_policies.*.rules[].policy", +	"identity_providers.oidc.authorization_policies.*.rules[].subject", +	"identity_providers.oidc.claims_policies", +	"identity_providers.oidc.claims_policies.*", +	"identity_providers.oidc.claims_policies.*.access_token", +	"identity_providers.oidc.claims_policies.*.custom_claims", +	"identity_providers.oidc.claims_policies.*.custom_claims.*", +	"identity_providers.oidc.claims_policies.*.custom_claims.*.attribute", +	"identity_providers.oidc.claims_policies.*.id_token",  	"identity_providers.oidc.clients", -	"identity_providers.oidc.clients[].client_id", -	"identity_providers.oidc.clients[].client_name", -	"identity_providers.oidc.clients[].client_secret", -	"identity_providers.oidc.clients[].sector_identifier_uri", -	"identity_providers.oidc.clients[].public", -	"identity_providers.oidc.clients[].redirect_uris", -	"identity_providers.oidc.clients[].request_uris", +	"identity_providers.oidc.clients[]", +	"identity_providers.oidc.clients[].access_token_encrypted_response_alg", +	"identity_providers.oidc.clients[].access_token_encrypted_response_enc", +	"identity_providers.oidc.clients[].access_token_encrypted_response_key_id", +	"identity_providers.oidc.clients[].access_token_signed_response_alg", +	"identity_providers.oidc.clients[].access_token_signed_response_key_id", +	"identity_providers.oidc.clients[].allow_multiple_auth_methods",  	"identity_providers.oidc.clients[].audience", -	"identity_providers.oidc.clients[].scopes", -	"identity_providers.oidc.clients[].grant_types", -	"identity_providers.oidc.clients[].response_types", -	"identity_providers.oidc.clients[].response_modes", -	"identity_providers.oidc.clients[].authorization_policy", -	"identity_providers.oidc.clients[].lifespan", -	"identity_providers.oidc.clients[].claims_policy", -	"identity_providers.oidc.clients[].requested_audience_mode", -	"identity_providers.oidc.clients[].consent_mode", -	"identity_providers.oidc.clients[].pre_configured_consent_duration", -	"identity_providers.oidc.clients[].require_pushed_authorization_requests", -	"identity_providers.oidc.clients[].require_pkce", -	"identity_providers.oidc.clients[].pkce_challenge_method", -	"identity_providers.oidc.clients[].authorization_signed_response_alg", -	"identity_providers.oidc.clients[].authorization_signed_response_key_id",  	"identity_providers.oidc.clients[].authorization_encrypted_response_alg",  	"identity_providers.oidc.clients[].authorization_encrypted_response_enc",  	"identity_providers.oidc.clients[].authorization_encrypted_response_key_id", -	"identity_providers.oidc.clients[].id_token_signed_response_alg", -	"identity_providers.oidc.clients[].id_token_signed_response_key_id", +	"identity_providers.oidc.clients[].authorization_policy", +	"identity_providers.oidc.clients[].authorization_signed_response_alg", +	"identity_providers.oidc.clients[].authorization_signed_response_key_id", +	"identity_providers.oidc.clients[].claims_policy", +	"identity_providers.oidc.clients[].client_id", +	"identity_providers.oidc.clients[].client_name", +	"identity_providers.oidc.clients[].client_secret", +	"identity_providers.oidc.clients[].consent_mode", +	"identity_providers.oidc.clients[].grant_types",  	"identity_providers.oidc.clients[].id_token_encrypted_response_alg",  	"identity_providers.oidc.clients[].id_token_encrypted_response_enc",  	"identity_providers.oidc.clients[].id_token_encrypted_response_key_id", -	"identity_providers.oidc.clients[].access_token_signed_response_alg", -	"identity_providers.oidc.clients[].access_token_signed_response_key_id", -	"identity_providers.oidc.clients[].access_token_encrypted_response_alg", -	"identity_providers.oidc.clients[].access_token_encrypted_response_enc", -	"identity_providers.oidc.clients[].access_token_encrypted_response_key_id", -	"identity_providers.oidc.clients[].userinfo_signed_response_alg", -	"identity_providers.oidc.clients[].userinfo_signed_response_key_id", -	"identity_providers.oidc.clients[].userinfo_encrypted_response_alg", -	"identity_providers.oidc.clients[].userinfo_encrypted_response_enc", -	"identity_providers.oidc.clients[].userinfo_encrypted_response_key_id", -	"identity_providers.oidc.clients[].introspection_signed_response_alg", -	"identity_providers.oidc.clients[].introspection_signed_response_key_id", +	"identity_providers.oidc.clients[].id_token_signed_response_alg", +	"identity_providers.oidc.clients[].id_token_signed_response_key_id",  	"identity_providers.oidc.clients[].introspection_encrypted_response_alg",  	"identity_providers.oidc.clients[].introspection_encrypted_response_enc",  	"identity_providers.oidc.clients[].introspection_encrypted_response_key_id", -	"identity_providers.oidc.clients[].request_object_signing_alg", -	"identity_providers.oidc.clients[].request_object_encryption_alg", -	"identity_providers.oidc.clients[].request_object_encryption_enc", -	"identity_providers.oidc.clients[].token_endpoint_auth_method", -	"identity_providers.oidc.clients[].token_endpoint_auth_signing_alg", -	"identity_providers.oidc.clients[].revocation_endpoint_auth_method", -	"identity_providers.oidc.clients[].revocation_endpoint_auth_signing_alg",  	"identity_providers.oidc.clients[].introspection_endpoint_auth_method",  	"identity_providers.oidc.clients[].introspection_endpoint_auth_signing_alg", -	"identity_providers.oidc.clients[].pushed_authorization_request_endpoint_auth_method", -	"identity_providers.oidc.clients[].pushed_authorization_request_endpoint_auth_signing_alg", -	"identity_providers.oidc.clients[].allow_multiple_auth_methods", -	"identity_providers.oidc.clients[].jwks_uri", +	"identity_providers.oidc.clients[].introspection_signed_response_alg", +	"identity_providers.oidc.clients[].introspection_signed_response_key_id",  	"identity_providers.oidc.clients[].jwks", -	"identity_providers.oidc.clients[].jwks[].key_id", -	"identity_providers.oidc.clients[].jwks[].use",  	"identity_providers.oidc.clients[].jwks[].algorithm", -	"identity_providers.oidc.clients[].jwks[].key",  	"identity_providers.oidc.clients[].jwks[].certificate_chain", -	"identity_providers.oidc.clients[]", -	"identity_providers.oidc.authorization_policies.*", -	"identity_providers.oidc.authorization_policies", -	"identity_providers.oidc.authorization_policies.*.default_policy", -	"identity_providers.oidc.authorization_policies.*.rules", -	"identity_providers.oidc.authorization_policies.*.rules[].policy", -	"identity_providers.oidc.authorization_policies.*.rules[].subject", -	"identity_providers.oidc.authorization_policies.*.rules[].networks", +	"identity_providers.oidc.clients[].jwks[].key", +	"identity_providers.oidc.clients[].jwks[].key_id", +	"identity_providers.oidc.clients[].jwks[].use", +	"identity_providers.oidc.clients[].jwks_uri", +	"identity_providers.oidc.clients[].lifespan", +	"identity_providers.oidc.clients[].pkce_challenge_method", +	"identity_providers.oidc.clients[].pre_configured_consent_duration", +	"identity_providers.oidc.clients[].public", +	"identity_providers.oidc.clients[].pushed_authorization_request_endpoint_auth_method", +	"identity_providers.oidc.clients[].pushed_authorization_request_endpoint_auth_signing_alg", +	"identity_providers.oidc.clients[].redirect_uris", +	"identity_providers.oidc.clients[].request_object_encryption_alg", +	"identity_providers.oidc.clients[].request_object_encryption_enc", +	"identity_providers.oidc.clients[].request_object_signing_alg", +	"identity_providers.oidc.clients[].request_uris", +	"identity_providers.oidc.clients[].requested_audience_mode", +	"identity_providers.oidc.clients[].require_pkce", +	"identity_providers.oidc.clients[].require_pushed_authorization_requests", +	"identity_providers.oidc.clients[].response_modes", +	"identity_providers.oidc.clients[].response_types", +	"identity_providers.oidc.clients[].revocation_endpoint_auth_method", +	"identity_providers.oidc.clients[].revocation_endpoint_auth_signing_alg", +	"identity_providers.oidc.clients[].scopes", +	"identity_providers.oidc.clients[].sector_identifier_uri", +	"identity_providers.oidc.clients[].token_endpoint_auth_method", +	"identity_providers.oidc.clients[].token_endpoint_auth_signing_alg", +	"identity_providers.oidc.clients[].userinfo_encrypted_response_alg", +	"identity_providers.oidc.clients[].userinfo_encrypted_response_enc", +	"identity_providers.oidc.clients[].userinfo_encrypted_response_key_id", +	"identity_providers.oidc.clients[].userinfo_signed_response_alg", +	"identity_providers.oidc.clients[].userinfo_signed_response_key_id", +	"identity_providers.oidc.cors.allowed_origins", +	"identity_providers.oidc.cors.allowed_origins_from_client_redirect_uris", +	"identity_providers.oidc.cors.endpoints", +	"identity_providers.oidc.discovery_signed_response_alg", +	"identity_providers.oidc.discovery_signed_response_key_id", +	"identity_providers.oidc.enable_client_debug_messages", +	"identity_providers.oidc.enable_jwt_access_token_stateless_introspection", +	"identity_providers.oidc.enable_pkce_plain_challenge", +	"identity_providers.oidc.enforce_pkce", +	"identity_providers.oidc.hmac_secret", +	"identity_providers.oidc.issuer_certificate_chain", +	"identity_providers.oidc.issuer_private_key", +	"identity_providers.oidc.jwks", +	"identity_providers.oidc.jwks[].algorithm", +	"identity_providers.oidc.jwks[].certificate_chain", +	"identity_providers.oidc.jwks[].key", +	"identity_providers.oidc.jwks[].key_id", +	"identity_providers.oidc.jwks[].use",  	"identity_providers.oidc.lifespans.access_token",  	"identity_providers.oidc.lifespans.authorize_code", -	"identity_providers.oidc.lifespans.id_token", -	"identity_providers.oidc.lifespans.refresh_token", -	"identity_providers.oidc.lifespans.jwt_secured_authorization", -	"identity_providers.oidc.lifespans.custom.*",  	"identity_providers.oidc.lifespans.custom", +	"identity_providers.oidc.lifespans.custom.*",  	"identity_providers.oidc.lifespans.custom.*.access_token",  	"identity_providers.oidc.lifespans.custom.*.authorize_code", -	"identity_providers.oidc.lifespans.custom.*.id_token", -	"identity_providers.oidc.lifespans.custom.*.refresh_token",  	"identity_providers.oidc.lifespans.custom.*.grants.authorize_code.access_token",  	"identity_providers.oidc.lifespans.custom.*.grants.authorize_code.authorize_code",  	"identity_providers.oidc.lifespans.custom.*.grants.authorize_code.id_token",  	"identity_providers.oidc.lifespans.custom.*.grants.authorize_code.refresh_token", -	"identity_providers.oidc.lifespans.custom.*.grants.implicit.access_token", -	"identity_providers.oidc.lifespans.custom.*.grants.implicit.authorize_code", -	"identity_providers.oidc.lifespans.custom.*.grants.implicit.id_token", -	"identity_providers.oidc.lifespans.custom.*.grants.implicit.refresh_token",  	"identity_providers.oidc.lifespans.custom.*.grants.client_credentials.access_token",  	"identity_providers.oidc.lifespans.custom.*.grants.client_credentials.authorize_code",  	"identity_providers.oidc.lifespans.custom.*.grants.client_credentials.id_token",  	"identity_providers.oidc.lifespans.custom.*.grants.client_credentials.refresh_token", -	"identity_providers.oidc.lifespans.custom.*.grants.refresh_token.access_token", -	"identity_providers.oidc.lifespans.custom.*.grants.refresh_token.authorize_code", -	"identity_providers.oidc.lifespans.custom.*.grants.refresh_token.id_token", -	"identity_providers.oidc.lifespans.custom.*.grants.refresh_token.refresh_token", +	"identity_providers.oidc.lifespans.custom.*.grants.implicit.access_token", +	"identity_providers.oidc.lifespans.custom.*.grants.implicit.authorize_code", +	"identity_providers.oidc.lifespans.custom.*.grants.implicit.id_token", +	"identity_providers.oidc.lifespans.custom.*.grants.implicit.refresh_token",  	"identity_providers.oidc.lifespans.custom.*.grants.jwt_bearer.access_token",  	"identity_providers.oidc.lifespans.custom.*.grants.jwt_bearer.authorize_code",  	"identity_providers.oidc.lifespans.custom.*.grants.jwt_bearer.id_token",  	"identity_providers.oidc.lifespans.custom.*.grants.jwt_bearer.refresh_token", -	"identity_providers.oidc.claims_policies.*", -	"identity_providers.oidc.claims_policies", -	"identity_providers.oidc.claims_policies.*.id_token", -	"identity_providers.oidc.claims_policies.*.access_token", -	"identity_providers.oidc.claims_policies.*.custom_claims.*", -	"identity_providers.oidc.claims_policies.*.custom_claims", -	"identity_providers.oidc.claims_policies.*.custom_claims.*.attribute", -	"identity_providers.oidc.scopes.*", +	"identity_providers.oidc.lifespans.custom.*.grants.refresh_token.access_token", +	"identity_providers.oidc.lifespans.custom.*.grants.refresh_token.authorize_code", +	"identity_providers.oidc.lifespans.custom.*.grants.refresh_token.id_token", +	"identity_providers.oidc.lifespans.custom.*.grants.refresh_token.refresh_token", +	"identity_providers.oidc.lifespans.custom.*.id_token", +	"identity_providers.oidc.lifespans.custom.*.refresh_token", +	"identity_providers.oidc.lifespans.id_token", +	"identity_providers.oidc.lifespans.jwt_secured_authorization", +	"identity_providers.oidc.lifespans.refresh_token", +	"identity_providers.oidc.minimum_parameter_entropy", +	"identity_providers.oidc.require_pushed_authorization_requests",  	"identity_providers.oidc.scopes", +	"identity_providers.oidc.scopes.*",  	"identity_providers.oidc.scopes.*.claims", -	"identity_providers.oidc", -	"identity_providers.oidc.issuer_certificate_chain", -	"identity_providers.oidc.issuer_private_key", -	"authentication_backend.password_reset.disable", -	"authentication_backend.password_reset.custom_url", -	"authentication_backend.refresh_interval", -	"authentication_backend.file.path", -	"authentication_backend.file.watch", -	"authentication_backend.file.password.algorithm", -	"authentication_backend.file.password.argon2.variant", -	"authentication_backend.file.password.argon2.iterations", -	"authentication_backend.file.password.argon2.memory", -	"authentication_backend.file.password.argon2.parallelism", -	"authentication_backend.file.password.argon2.key_length", -	"authentication_backend.file.password.argon2.salt_length", -	"authentication_backend.file.password.sha2crypt.variant", -	"authentication_backend.file.password.sha2crypt.iterations", -	"authentication_backend.file.password.sha2crypt.salt_length", -	"authentication_backend.file.password.pbkdf2.variant", -	"authentication_backend.file.password.pbkdf2.iterations", -	"authentication_backend.file.password.pbkdf2.salt_length", -	"authentication_backend.file.password.bcrypt.variant", -	"authentication_backend.file.password.bcrypt.cost", -	"authentication_backend.file.password.scrypt.iterations", -	"authentication_backend.file.password.scrypt.block_size", -	"authentication_backend.file.password.scrypt.parallelism", -	"authentication_backend.file.password.scrypt.key_length", -	"authentication_backend.file.password.scrypt.salt_length", -	"authentication_backend.file.password.iterations", -	"authentication_backend.file.password.memory", -	"authentication_backend.file.password.parallelism", -	"authentication_backend.file.password.key_length", -	"authentication_backend.file.password.salt_length", -	"authentication_backend.file.search.email", -	"authentication_backend.file.search.case_insensitive", -	"authentication_backend.file.extra_attributes.*", -	"authentication_backend.file.extra_attributes", -	"authentication_backend.file.extra_attributes.*.multi_valued", -	"authentication_backend.file.extra_attributes.*.value_type", -	"authentication_backend.ldap.address", -	"authentication_backend.ldap.implementation", -	"authentication_backend.ldap.timeout", -	"authentication_backend.ldap.start_tls", -	"authentication_backend.ldap.tls.minimum_version", -	"authentication_backend.ldap.tls.maximum_version", -	"authentication_backend.ldap.tls.skip_verify", -	"authentication_backend.ldap.tls.server_name", -	"authentication_backend.ldap.tls.private_key", -	"authentication_backend.ldap.tls.certificate_chain", -	"authentication_backend.ldap.pooling.enable", -	"authentication_backend.ldap.pooling.count", -	"authentication_backend.ldap.pooling.retries", -	"authentication_backend.ldap.pooling.timeout", -	"authentication_backend.ldap.base_dn", -	"authentication_backend.ldap.additional_users_dn", -	"authentication_backend.ldap.users_filter", -	"authentication_backend.ldap.additional_groups_dn", -	"authentication_backend.ldap.groups_filter", -	"authentication_backend.ldap.group_search_mode", -	"authentication_backend.ldap.attributes.distinguished_name", -	"authentication_backend.ldap.attributes.username", -	"authentication_backend.ldap.attributes.display_name", -	"authentication_backend.ldap.attributes.family_name", -	"authentication_backend.ldap.attributes.given_name", -	"authentication_backend.ldap.attributes.middle_name", -	"authentication_backend.ldap.attributes.nickname", -	"authentication_backend.ldap.attributes.gender", -	"authentication_backend.ldap.attributes.birthdate", -	"authentication_backend.ldap.attributes.website", -	"authentication_backend.ldap.attributes.profile", -	"authentication_backend.ldap.attributes.picture", -	"authentication_backend.ldap.attributes.zoneinfo", -	"authentication_backend.ldap.attributes.locale", -	"authentication_backend.ldap.attributes.phone_number", -	"authentication_backend.ldap.attributes.phone_extension", -	"authentication_backend.ldap.attributes.street_address", -	"authentication_backend.ldap.attributes.locality", -	"authentication_backend.ldap.attributes.region", -	"authentication_backend.ldap.attributes.postal_code", -	"authentication_backend.ldap.attributes.country", -	"authentication_backend.ldap.attributes.mail", -	"authentication_backend.ldap.attributes.member_of", -	"authentication_backend.ldap.attributes.group_name", -	"authentication_backend.ldap.attributes.extra.*", -	"authentication_backend.ldap.attributes.extra", -	"authentication_backend.ldap.attributes.extra.*.name", -	"authentication_backend.ldap.attributes.extra.*.multi_valued", -	"authentication_backend.ldap.attributes.extra.*.value_type", -	"authentication_backend.ldap.permit_referrals", -	"authentication_backend.ldap.permit_unauthenticated_bind", -	"authentication_backend.ldap.permit_feature_detection_failure", -	"authentication_backend.ldap.user", -	"authentication_backend.ldap.password", -	"session.name", -	"session.same_site", -	"session.expiration", -	"session.inactivity", -	"session.remember_me", +	"identity_validation.elevated_session.characters", +	"identity_validation.elevated_session.code_lifespan", +	"identity_validation.elevated_session.elevation_lifespan", +	"identity_validation.elevated_session.require_second_factor", +	"identity_validation.elevated_session.skip_second_factor", +	"identity_validation.reset_password.jwt_algorithm", +	"identity_validation.reset_password.jwt_lifespan", +	"identity_validation.reset_password.jwt_secret", +	"log.file_path", +	"log.format", +	"log.keep_stdout", +	"log.level", +	"notifier.disable_startup_check", +	"notifier.filesystem.filename", +	"notifier.smtp.address", +	"notifier.smtp.disable_html_emails", +	"notifier.smtp.disable_require_tls", +	"notifier.smtp.disable_starttls", +	"notifier.smtp.host", +	"notifier.smtp.identifier", +	"notifier.smtp.password", +	"notifier.smtp.port", +	"notifier.smtp.sender", +	"notifier.smtp.startup_check_address", +	"notifier.smtp.subject", +	"notifier.smtp.timeout", +	"notifier.smtp.tls.certificate_chain", +	"notifier.smtp.tls.maximum_version", +	"notifier.smtp.tls.minimum_version", +	"notifier.smtp.tls.private_key", +	"notifier.smtp.tls.server_name", +	"notifier.smtp.tls.skip_verify", +	"notifier.smtp.username", +	"notifier.template_path", +	"ntp.address", +	"ntp.disable_failure", +	"ntp.disable_startup_check", +	"ntp.max_desync", +	"ntp.version", +	"password_policy.standard.enabled", +	"password_policy.standard.max_length", +	"password_policy.standard.min_length", +	"password_policy.standard.require_lowercase", +	"password_policy.standard.require_number", +	"password_policy.standard.require_special", +	"password_policy.standard.require_uppercase", +	"password_policy.zxcvbn.enabled", +	"password_policy.zxcvbn.min_score", +	"privacy_policy.enabled", +	"privacy_policy.policy_url", +	"privacy_policy.require_user_acceptance", +	"regulation.ban_time", +	"regulation.find_time", +	"regulation.max_retries", +	"server.address", +	"server.asset_path", +	"server.buffers.read", +	"server.buffers.write", +	"server.disable_healthcheck", +	"server.endpoints.authz", +	"server.endpoints.authz.*", +	"server.endpoints.authz.*.authn_strategies", +	"server.endpoints.authz.*.authn_strategies[].name", +	"server.endpoints.authz.*.authn_strategies[].schemes", +	"server.endpoints.authz.*.implementation", +	"server.endpoints.enable_expvars", +	"server.endpoints.enable_pprof", +	"server.headers.csp_template", +	"server.timeouts.idle", +	"server.timeouts.read", +	"server.timeouts.write", +	"server.tls.certificate", +	"server.tls.client_certificates", +	"server.tls.key",  	"session", -	"session.secret",  	"session.cookies", -	"session.cookies[].name", -	"session.cookies[].same_site", -	"session.cookies[].expiration", -	"session.cookies[].inactivity", -	"session.cookies[].remember_me",  	"session.cookies[]", -	"session.cookies[].domain",  	"session.cookies[].authelia_url",  	"session.cookies[].default_redirection_url", -	"session.cookies[]", +	"session.cookies[].domain", +	"session.cookies[].expiration", +	"session.cookies[].inactivity", +	"session.cookies[].name", +	"session.cookies[].remember_me", +	"session.cookies[].same_site", +	"session.domain", +	"session.expiration", +	"session.inactivity", +	"session.name", +	"session.redis.database_index", +	"session.redis.high_availability.nodes", +	"session.redis.high_availability.nodes[].host", +	"session.redis.high_availability.nodes[].port", +	"session.redis.high_availability.route_by_latency", +	"session.redis.high_availability.route_randomly", +	"session.redis.high_availability.sentinel_name", +	"session.redis.high_availability.sentinel_password", +	"session.redis.high_availability.sentinel_username",  	"session.redis.host", -	"session.redis.port", -	"session.redis.timeout",  	"session.redis.max_retries", -	"session.redis.username", -	"session.redis.password", -	"session.redis.database_index",  	"session.redis.maximum_active_connections",  	"session.redis.minimum_idle_connections", -	"session.redis.tls.minimum_version", +	"session.redis.password", +	"session.redis.port", +	"session.redis.timeout", +	"session.redis.tls.certificate_chain",  	"session.redis.tls.maximum_version", -	"session.redis.tls.skip_verify", -	"session.redis.tls.server_name", +	"session.redis.tls.minimum_version",  	"session.redis.tls.private_key", -	"session.redis.tls.certificate_chain", -	"session.redis.high_availability.sentinel_name", -	"session.redis.high_availability.sentinel_username", -	"session.redis.high_availability.sentinel_password", -	"session.redis.high_availability.route_by_latency", -	"session.redis.high_availability.route_randomly", -	"session.redis.high_availability.nodes", -	"session.redis.high_availability.nodes[].host", -	"session.redis.high_availability.nodes[].port", -	"session.domain", -	"totp.disable", -	"totp.issuer", -	"totp.algorithm", -	"totp.digits", -	"totp.period", -	"totp.skew", -	"totp.secret_size", -	"totp.allowed_algorithms", -	"totp.allowed_digits", -	"totp.allowed_periods", -	"totp.disable_reuse_security_policy", -	"duo_api.disable", -	"duo_api.hostname", -	"duo_api.integration_key", -	"duo_api.secret_key", -	"duo_api.enable_self_enrollment", -	"access_control.default_policy", -	"access_control.networks", -	"access_control.networks[].name", -	"access_control.networks[].networks", -	"access_control.rules", -	"access_control.rules[].domain", -	"access_control.rules[].domain_regex", -	"access_control.rules[].policy", -	"access_control.rules[].subject", -	"access_control.rules[].networks", -	"access_control.rules[].resources", -	"access_control.rules[].methods", -	"access_control.rules[].query[][].operator", -	"access_control.rules[].query[][].key", -	"access_control.rules[].query[][].value", -	"access_control.rules[].query", -	"ntp.address", -	"ntp.version", -	"ntp.max_desync", -	"ntp.disable_startup_check", -	"ntp.disable_failure", -	"regulation.max_retries", -	"regulation.find_time", -	"regulation.ban_time", +	"session.redis.tls.server_name", +	"session.redis.tls.skip_verify", +	"session.redis.username", +	"session.remember_me", +	"session.same_site", +	"session.secret", +	"storage.encryption_key",  	"storage.local.path",  	"storage.mysql.address",  	"storage.mysql.database", -	"storage.mysql.username",  	"storage.mysql.password",  	"storage.mysql.timeout", -	"storage.mysql.tls.minimum_version", +	"storage.mysql.tls.certificate_chain",  	"storage.mysql.tls.maximum_version", -	"storage.mysql.tls.skip_verify", -	"storage.mysql.tls.server_name", +	"storage.mysql.tls.minimum_version",  	"storage.mysql.tls.private_key", -	"storage.mysql.tls.certificate_chain", +	"storage.mysql.tls.server_name", +	"storage.mysql.tls.skip_verify", +	"storage.mysql.username",  	"storage.postgres.address",  	"storage.postgres.database", -	"storage.postgres.username",  	"storage.postgres.password", -	"storage.postgres.timeout",  	"storage.postgres.schema", -	"storage.postgres.tls.minimum_version", -	"storage.postgres.tls.maximum_version", -	"storage.postgres.tls.skip_verify", -	"storage.postgres.tls.server_name", -	"storage.postgres.tls.private_key", -	"storage.postgres.tls.certificate_chain", -	"storage.postgres.ssl.mode", -	"storage.postgres.ssl.root_certificate", +	"storage.postgres.servers", +	"storage.postgres.servers[].address", +	"storage.postgres.servers[].tls", +	"storage.postgres.servers[].tls.certificate_chain", +	"storage.postgres.servers[].tls.maximum_version", +	"storage.postgres.servers[].tls.minimum_version", +	"storage.postgres.servers[].tls.private_key", +	"storage.postgres.servers[].tls.server_name", +	"storage.postgres.servers[].tls.skip_verify",  	"storage.postgres.ssl.certificate",  	"storage.postgres.ssl.key", -	"storage.encryption_key", -	"notifier.disable_startup_check", -	"notifier.filesystem.filename", -	"notifier.smtp.address", -	"notifier.smtp.timeout", -	"notifier.smtp.username", -	"notifier.smtp.password", -	"notifier.smtp.identifier", -	"notifier.smtp.sender", -	"notifier.smtp.subject", -	"notifier.smtp.startup_check_address", -	"notifier.smtp.disable_require_tls", -	"notifier.smtp.disable_html_emails", -	"notifier.smtp.disable_starttls", -	"notifier.smtp.tls.minimum_version", -	"notifier.smtp.tls.maximum_version", -	"notifier.smtp.tls.skip_verify", -	"notifier.smtp.tls.server_name", -	"notifier.smtp.tls.private_key", -	"notifier.smtp.tls.certificate_chain", -	"notifier.smtp.host", -	"notifier.smtp.port", -	"notifier.template_path", -	"server.address", -	"server.asset_path", -	"server.disable_healthcheck", -	"server.tls.certificate", -	"server.tls.key", -	"server.tls.client_certificates", -	"server.headers.csp_template", -	"server.endpoints.enable_pprof", -	"server.endpoints.enable_expvars", -	"server.endpoints.authz.*", -	"server.endpoints.authz", -	"server.endpoints.authz.*.implementation", -	"server.endpoints.authz.*.authn_strategies", -	"server.endpoints.authz.*.authn_strategies[].name", -	"server.endpoints.authz.*.authn_strategies[].schemes", -	"server.buffers.read", -	"server.buffers.write", -	"server.timeouts.read", -	"server.timeouts.write", -	"server.timeouts.idle", -	"telemetry.metrics.enabled", +	"storage.postgres.ssl.mode", +	"storage.postgres.ssl.root_certificate", +	"storage.postgres.timeout", +	"storage.postgres.tls.certificate_chain", +	"storage.postgres.tls.maximum_version", +	"storage.postgres.tls.minimum_version", +	"storage.postgres.tls.private_key", +	"storage.postgres.tls.server_name", +	"storage.postgres.tls.skip_verify", +	"storage.postgres.username",  	"telemetry.metrics.address",  	"telemetry.metrics.buffers.read",  	"telemetry.metrics.buffers.write", +	"telemetry.metrics.enabled", +	"telemetry.metrics.timeouts.idle",  	"telemetry.metrics.timeouts.read",  	"telemetry.metrics.timeouts.write", -	"telemetry.metrics.timeouts.idle", +	"theme", +	"totp.algorithm", +	"totp.allowed_algorithms", +	"totp.allowed_digits", +	"totp.allowed_periods", +	"totp.digits", +	"totp.disable", +	"totp.disable_reuse_security_policy", +	"totp.issuer", +	"totp.period", +	"totp.secret_size", +	"totp.skew", +	"webauthn.attestation_conveyance_preference",  	"webauthn.disable", +	"webauthn.display_name",  	"webauthn.enable_passkey_login",  	"webauthn.experimental_enable_passkey_uv_two_factors", -	"webauthn.display_name", -	"webauthn.attestation_conveyance_preference", -	"webauthn.timeout", -	"webauthn.filtering.prohibit_backup_eligibility",  	"webauthn.filtering.permitted_aaguids", +	"webauthn.filtering.prohibit_backup_eligibility",  	"webauthn.filtering.prohibited_aaguids", -	"webauthn.selection_criteria.attachment", -	"webauthn.selection_criteria.discoverability", -	"webauthn.selection_criteria.user_verification",  	"webauthn.metadata.enabled", -	"webauthn.metadata.validate_trust_anchor",  	"webauthn.metadata.validate_entry",  	"webauthn.metadata.validate_entry_permit_zero_aaguid",  	"webauthn.metadata.validate_status",  	"webauthn.metadata.validate_status_permitted",  	"webauthn.metadata.validate_status_prohibited", -	"password_policy.standard.enabled", -	"password_policy.standard.min_length", -	"password_policy.standard.max_length", -	"password_policy.standard.require_uppercase", -	"password_policy.standard.require_lowercase", -	"password_policy.standard.require_number", -	"password_policy.standard.require_special", -	"password_policy.zxcvbn.enabled", -	"password_policy.zxcvbn.min_score", -	"privacy_policy.enabled", -	"privacy_policy.require_user_acceptance", -	"privacy_policy.policy_url", -	"identity_validation.reset_password.jwt_lifespan", -	"identity_validation.reset_password.jwt_algorithm", -	"identity_validation.reset_password.jwt_secret", -	"identity_validation.elevated_session.code_lifespan", -	"identity_validation.elevated_session.elevation_lifespan", -	"identity_validation.elevated_session.characters", -	"identity_validation.elevated_session.require_second_factor", -	"identity_validation.elevated_session.skip_second_factor", -	"definitions.network.*", -	"definitions.network.*", -	"definitions.network", -	"definitions.user_attributes.*", -	"definitions.user_attributes", -	"definitions.user_attributes.*.expression", -	"default_redirection_url", +	"webauthn.metadata.validate_trust_anchor", +	"webauthn.selection_criteria.attachment", +	"webauthn.selection_criteria.discoverability", +	"webauthn.selection_criteria.user_verification", +	"webauthn.timeout",  } diff --git a/internal/configuration/schema/storage.go b/internal/configuration/schema/storage.go index b51982a59..4bdf397f5 100644 --- a/internal/configuration/schema/storage.go +++ b/internal/configuration/schema/storage.go @@ -22,31 +22,36 @@ type StorageLocal struct {  // StorageSQL represents the configuration of the SQL database.  type StorageSQL struct { -	Address  *AddressTCP   `koanf:"address" json:"address" jsonschema:"title=Address" jsonschema_description:"The address of the database."` +	Address  *AddressTCP   `koanf:"address" json:"address" jsonschema:"title=Address" jsonschema_description:"The address of the SQL Server."`  	Database string        `koanf:"database" json:"database" jsonschema:"title=Database" jsonschema_description:"The database name to use upon a successful connection."`  	Username string        `koanf:"username" json:"username" jsonschema:"title=Username" jsonschema_description:"The username to use to authenticate."`  	Password string        `koanf:"password" json:"password" jsonschema:"title=Password" jsonschema_description:"The password to use to authenticate."`  	Timeout  time.Duration `koanf:"timeout" json:"timeout" jsonschema:"default=5 seconds,title=Timeout" jsonschema_description:"The timeout for the database connection."` +	TLS      *TLS          `koanf:"tls" json:"tls"`  }  // StorageMySQL represents the configuration of a MySQL database.  type StorageMySQL struct {  	StorageSQL `koanf:",squash"` - -	TLS *TLS `koanf:"tls" json:"tls"`  }  // StoragePostgreSQL represents the configuration of a PostgreSQL database.  type StoragePostgreSQL struct {  	StorageSQL `koanf:",squash"` -	Schema     string `koanf:"schema" json:"schema" jsonschema:"default=public,title=Schema" jsonschema_description:"The default schema name to use."` -	TLS *TLS `koanf:"tls" json:"tls"` +	Schema string `koanf:"schema" json:"schema" jsonschema:"default=public,title=Schema" jsonschema_description:"The default schema name to use."` + +	Servers []StoragePostgreSQLServer `koanf:"servers" json:"servers" jsonschema:"title=Servers" jsonschema_description:"The fallback PostgreSQL severs to connect to in addition to the one defined by the address."`  	// Deprecated: Use the TLS configuration instead.  	SSL *StoragePostgreSQLSSL `koanf:"ssl" json:"ssl" jsonschema:"deprecated,title=SSL"`  } +type StoragePostgreSQLServer struct { +	Address *AddressTCP `koanf:"address" json:"address" jsonschema:"title=Address" jsonschema_description:"The address of the PostgreSQL Server."` +	TLS     *TLS        `koanf:"tls" json:"tls"` +} +  // StoragePostgreSQLSSL represents the SSL configuration of a PostgreSQL database.  type StoragePostgreSQLSSL struct {  	Mode            string `koanf:"mode" json:"mode" jsonschema:"deprecated,enum=disable,enum=verify-ca,enum=require,enum=verify-full,title=Mode" jsonschema_description:"The SSL mode to use, deprecated and replaced with the TLS options."` @@ -64,9 +69,9 @@ var DefaultSQLStorageConfiguration = StorageSQL{  var DefaultMySQLStorageConfiguration = StorageMySQL{  	StorageSQL: StorageSQL{  		Address: &AddressTCP{Address{true, false, -1, 3306, &url.URL{Scheme: AddressSchemeTCP, Host: "localhost:3306"}}}, -	}, -	TLS: &TLS{ -		MinimumVersion: TLSVersion{tls.VersionTLS12}, +		TLS: &TLS{ +			MinimumVersion: TLSVersion{tls.VersionTLS12}, +		},  	},  } @@ -74,11 +79,19 @@ var DefaultMySQLStorageConfiguration = StorageMySQL{  var DefaultPostgreSQLStorageConfiguration = StoragePostgreSQL{  	StorageSQL: StorageSQL{  		Address: &AddressTCP{Address{true, false, -1, 5432, &url.URL{Scheme: AddressSchemeTCP, Host: "localhost:5432"}}}, +		TLS: &TLS{ +			MinimumVersion: TLSVersion{tls.VersionTLS12}, +		},  	}, -	Schema: "public", -	TLS: &TLS{ -		MinimumVersion: TLSVersion{tls.VersionTLS12}, +	Servers: []StoragePostgreSQLServer{ +		{ +			Address: &AddressTCP{Address{true, false, -1, 5432, &url.URL{Scheme: AddressSchemeTCP, Host: "localhost:5432"}}}, +			TLS: &TLS{ +				MinimumVersion: TLSVersion{tls.VersionTLS12}, +			}, +		},  	}, +	Schema: "public",  	SSL: &StoragePostgreSQLSSL{  		Mode: "disable",  	}, diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go index 7298f293d..396423982 100644 --- a/internal/configuration/validator/const.go +++ b/internal/configuration/validator/const.go @@ -155,6 +155,7 @@ const (  	errStrStorageMultiple                          = "storage: option 'local', 'mysql' and 'postgres' are mutually exclusive but %s have been configured"  	errStrStorageEncryptionKeyMustBeProvided       = "storage: option 'encryption_key' is required"  	errStrStorageEncryptionKeyTooShort             = "storage: option 'encryption_key' must be 20 characters or longer" +	errFmtStorageAddressValidate                   = "storage: %s: option 'address' with value '%s' is invalid: %w"  	errFmtStorageUserPassMustBeProvided            = "storage: %s: option 'username' and 'password' are required" //nolint:gosec  	errFmtStorageOptionMustBeProvided              = "storage: %s: option '%s' is required"  	errFmtStorageOptionAddressConflictWithHostPort = "storage: %s: option 'host' and 'port' can't be configured at the same time as 'address'" diff --git a/internal/configuration/validator/storage.go b/internal/configuration/validator/storage.go index d0ad1bb7d..647847dc9 100644 --- a/internal/configuration/validator/storage.go +++ b/internal/configuration/validator/storage.go @@ -62,7 +62,7 @@ func validateSQLConfiguration(config, defaults *schema.StorageSQL, validator *sc  		var err error  		if err = config.Address.ValidateSQL(); err != nil { -			validator.Push(fmt.Errorf(errFmtServerAddress, config.Address.String(), err)) +			validator.Push(fmt.Errorf(errFmtStorageAddressValidate, provider, config.Address.String(), err))  		}  	} @@ -132,6 +132,46 @@ func validatePostgreSQLConfiguration(config *schema.StoragePostgreSQL, validator  			validator.Push(fmt.Errorf(errFmtStoragePostgreSQLInvalidSSLMode, utils.StringJoinOr(validStoragePostgreSQLSSLModes), config.SSL.Mode)) //nolint:staticcheck  		}  	} + +	validatePostgreSQLConfigurationServers(config, validator) +} + +func validatePostgreSQLConfigurationServers(config *schema.StoragePostgreSQL, validator *schema.StructValidator) { +	for i, server := range config.Servers { +		description := fmt.Sprintf("postgres: servers: #%d", i+1) + +		if server.Address == nil { +			validator.Push(fmt.Errorf(errFmtStorageOptionMustBeProvided, description, "address")) +		} else { +			var err error + +			if err = server.Address.ValidateSQL(); err != nil { +				validator.Push(fmt.Errorf(errFmtStorageAddressValidate, description, server.Address.String(), err)) +			} +		} + +		if server.Address != nil && server.Address.IsTCP() && server.Address.Port() == 0 { +			server.Address.SetPort(schema.DefaultPostgreSQLStorageConfiguration.Address.Port()) +		} + +		if server.TLS != nil { +			configDefaultTLS := &schema.TLS{ +				ServerName: server.Address.Hostname(), +			} + +			if config.TLS != nil { +				configDefaultTLS.MinimumVersion = config.TLS.MinimumVersion +				configDefaultTLS.MaximumVersion = config.TLS.MaximumVersion +			} else { +				configDefaultTLS.MinimumVersion = schema.DefaultPostgreSQLStorageConfiguration.TLS.MinimumVersion +				configDefaultTLS.MaximumVersion = schema.DefaultPostgreSQLStorageConfiguration.TLS.MaximumVersion +			} + +			if err := ValidateTLSConfig(server.TLS, configDefaultTLS); err != nil { +				validator.Push(fmt.Errorf(errFmtStorageTLSConfigInvalid, description, err)) +			} +		} +	}  }  func validateLocalStorageConfiguration(config *schema.StorageLocal, validator *schema.StructValidator) { diff --git a/internal/configuration/validator/storage_test.go b/internal/configuration/validator/storage_test.go index fae3ab681..8cf5c83f8 100644 --- a/internal/configuration/validator/storage_test.go +++ b/internal/configuration/validator/storage_test.go @@ -32,7 +32,7 @@ func (suite *StorageSuite) TestShouldValidateOneStorageIsConfigured() {  	suite.Require().Len(suite.val.Warnings(), 0)  	suite.Require().Len(suite.val.Errors(), 1) -	suite.Assert().EqualError(suite.val.Errors()[0], "storage: configuration for a 'local', 'mysql' or 'postgres' database must be provided") +	suite.EqualError(suite.val.Errors()[0], "storage: configuration for a 'local', 'mysql' or 'postgres' database must be provided")  }  func (suite *StorageSuite) TestShouldValidateMultipleStorageIsConfigured() { @@ -44,7 +44,7 @@ func (suite *StorageSuite) TestShouldValidateMultipleStorageIsConfigured() {  	suite.Require().Len(suite.val.Warnings(), 0)  	suite.Require().Len(suite.val.Errors(), 1) -	suite.Assert().EqualError(suite.val.Errors()[0], "storage: option 'local', 'mysql' and 'postgres' are mutually exclusive but 'local', 'mysql', and 'postgres' have been configured") +	suite.EqualError(suite.val.Errors()[0], "storage: option 'local', 'mysql' and 'postgres' are mutually exclusive but 'local', 'mysql', and 'postgres' have been configured")  }  func (suite *StorageSuite) TestShouldValidateLocalPathIsProvided() { @@ -57,7 +57,7 @@ func (suite *StorageSuite) TestShouldValidateLocalPathIsProvided() {  	suite.Require().Len(suite.val.Warnings(), 0)  	suite.Require().Len(suite.val.Errors(), 1) -	suite.Assert().EqualError(suite.val.Errors()[0], "storage: local: option 'path' is required") +	suite.EqualError(suite.val.Errors()[0], "storage: local: option 'path' is required")  	suite.val.Clear()  	suite.config.Local.Path = "/myapth" @@ -73,9 +73,9 @@ func (suite *StorageSuite) TestShouldValidateMySQLHostUsernamePasswordAndDatabas  	ValidateStorage(suite.config, suite.val)  	suite.Require().Len(suite.val.Errors(), 3) -	suite.Assert().EqualError(suite.val.Errors()[0], "storage: mysql: option 'address' is required") -	suite.Assert().EqualError(suite.val.Errors()[1], "storage: mysql: option 'username' and 'password' are required") -	suite.Assert().EqualError(suite.val.Errors()[2], "storage: mysql: option 'database' is required") +	suite.EqualError(suite.val.Errors()[0], "storage: mysql: option 'address' is required") +	suite.EqualError(suite.val.Errors()[1], "storage: mysql: option 'username' and 'password' are required") +	suite.EqualError(suite.val.Errors()[2], "storage: mysql: option 'database' is required")  	suite.val.Clear()  	suite.config.MySQL = &schema.StorageMySQL{ @@ -101,19 +101,19 @@ func (suite *StorageSuite) TestShouldSetDefaultMySQLTLSServerName() {  			Username: "myuser",  			Password: "pass",  			Database: "database", -		}, -		TLS: &schema.TLS{ -			MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS12}, +			TLS: &schema.TLS{ +				MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS12}, +			},  		},  	}  	ValidateStorage(suite.config, suite.val) -	suite.Assert().Len(suite.val.Warnings(), 0) -	suite.Assert().Len(suite.val.Errors(), 0) +	suite.Len(suite.val.Warnings(), 0) +	suite.Len(suite.val.Errors(), 0) -	suite.Assert().Equal(suite.config.MySQL.Address.Hostname(), suite.config.MySQL.TLS.ServerName) -	suite.Assert().Equal("mysql", suite.config.MySQL.TLS.ServerName) +	suite.Equal(suite.config.MySQL.Address.Hostname(), suite.config.MySQL.TLS.ServerName) +	suite.Equal("mysql", suite.config.MySQL.TLS.ServerName)  }  func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidMySQLAddressScheme() { @@ -131,7 +131,7 @@ func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidMySQLAddressScheme() {  	suite.Len(suite.val.Warnings(), 0)  	suite.Require().Len(suite.val.Errors(), 1) -	suite.EqualError(suite.val.Errors()[0], "server: option 'address' with value 'udp://mysql:1234' is invalid: scheme must be one of 'tcp', 'tcp4', 'tcp6', or 'unix' but is configured as 'udp'") +	suite.EqualError(suite.val.Errors()[0], "storage: mysql: option 'address' with value 'udp://mysql:1234' is invalid: scheme must be one of 'tcp', 'tcp4', 'tcp6', or 'unix' but is configured as 'udp'")  }  func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidMySQLTLSVersion() { @@ -143,18 +143,18 @@ func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidMySQLTLSVersion() {  			Username: "myuser",  			Password: "pass",  			Database: "database", -		}, -		TLS: &schema.TLS{ -			MinimumVersion: schema.TLSVersion{Value: tls.VersionSSL30}, //nolint:staticcheck +			TLS: &schema.TLS{ +				MinimumVersion: schema.TLSVersion{Value: tls.VersionSSL30}, //nolint:staticcheck +			},  		},  	}  	ValidateStorage(suite.config, suite.val) -	suite.Assert().Len(suite.val.Warnings(), 0) +	suite.Len(suite.val.Warnings(), 0)  	suite.Require().Len(suite.val.Errors(), 1) -	suite.Assert().EqualError(suite.val.Errors()[0], "storage: mysql: tls: option 'minimum_version' is invalid: minimum version is TLS1.0 but SSL3.0 was configured") +	suite.EqualError(suite.val.Errors()[0], "storage: mysql: tls: option 'minimum_version' is invalid: minimum version is TLS1.0 but SSL3.0 was configured")  }  func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidMySQLTLSMinVersionGreaterThanMaximum() { @@ -166,19 +166,19 @@ func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidMySQLTLSMinVersionGreate  			Username: "myuser",  			Password: "pass",  			Database: "database", -		}, -		TLS: &schema.TLS{ -			MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13}, -			MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS11}, +			TLS: &schema.TLS{ +				MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13}, +				MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS11}, +			},  		},  	}  	ValidateStorage(suite.config, suite.val) -	suite.Assert().Len(suite.val.Warnings(), 0) +	suite.Len(suite.val.Warnings(), 0)  	suite.Require().Len(suite.val.Errors(), 1) -	suite.Assert().EqualError(suite.val.Errors()[0], "storage: mysql: tls: option combination of 'minimum_version' and 'maximum_version' is invalid: minimum version TLS1.3 is greater than the maximum version TLS1.1") +	suite.EqualError(suite.val.Errors()[0], "storage: mysql: tls: option combination of 'minimum_version' and 'maximum_version' is invalid: minimum version TLS1.3 is greater than the maximum version TLS1.1")  }  func (suite *StorageSuite) TestShouldValidatePostgreSQLHostUsernamePasswordAndDatabaseAreProvided() { @@ -187,9 +187,9 @@ func (suite *StorageSuite) TestShouldValidatePostgreSQLHostUsernamePasswordAndDa  	ValidateStorage(suite.config, suite.val)  	suite.Require().Len(suite.val.Errors(), 3) -	suite.Assert().EqualError(suite.val.Errors()[0], "storage: postgres: option 'address' is required") -	suite.Assert().EqualError(suite.val.Errors()[1], "storage: postgres: option 'username' and 'password' are required") -	suite.Assert().EqualError(suite.val.Errors()[2], "storage: postgres: option 'database' is required") +	suite.EqualError(suite.val.Errors()[0], "storage: postgres: option 'address' is required") +	suite.EqualError(suite.val.Errors()[1], "storage: postgres: option 'username' and 'password' are required") +	suite.EqualError(suite.val.Errors()[2], "storage: postgres: option 'database' is required")  	suite.val.Clear()  	suite.config.PostgreSQL = &schema.StoragePostgreSQL{ @@ -204,8 +204,8 @@ func (suite *StorageSuite) TestShouldValidatePostgreSQLHostUsernamePasswordAndDa  	}  	ValidateStorage(suite.config, suite.val) -	suite.Assert().Len(suite.val.Warnings(), 0) -	suite.Assert().Len(suite.val.Errors(), 0) +	suite.Len(suite.val.Warnings(), 0) +	suite.Len(suite.val.Errors(), 0)  }  func (suite *StorageSuite) TestShouldValidatePostgresSchemaDefault() { @@ -222,13 +222,13 @@ func (suite *StorageSuite) TestShouldValidatePostgresSchemaDefault() {  	ValidateStorage(suite.config, suite.val) -	suite.Assert().Len(suite.val.Warnings(), 0) -	suite.Assert().Len(suite.val.Errors(), 0) +	suite.Len(suite.val.Warnings(), 0) +	suite.Len(suite.val.Errors(), 0) -	suite.Assert().Nil(suite.config.PostgreSQL.SSL) //nolint:staticcheck -	suite.Assert().Nil(suite.config.PostgreSQL.TLS) +	suite.Nil(suite.config.PostgreSQL.SSL) //nolint:staticcheck +	suite.Nil(suite.config.PostgreSQL.TLS) -	suite.Assert().Equal("public", suite.config.PostgreSQL.Schema) +	suite.Equal("public", suite.config.PostgreSQL.Schema)  }  func (suite *StorageSuite) TestShouldValidatePostgresPortDefault() { @@ -261,22 +261,22 @@ func (suite *StorageSuite) TestShouldValidatePostgresTLSDefaults() {  			Username: "myuser",  			Password: "pass",  			Database: "database", +			TLS:      &schema.TLS{},  		}, -		TLS: &schema.TLS{},  	}  	ValidateStorage(suite.config, suite.val) -	suite.Assert().Len(suite.val.Warnings(), 0) -	suite.Assert().Len(suite.val.Errors(), 0) +	suite.Len(suite.val.Warnings(), 0) +	suite.Len(suite.val.Errors(), 0) -	suite.Assert().Nil(suite.config.PostgreSQL.SSL) //nolint:staticcheck +	suite.Nil(suite.config.PostgreSQL.SSL) //nolint:staticcheck  	suite.Require().NotNil(suite.config.PostgreSQL.TLS) -	suite.Assert().Equal(uint16(tls.VersionTLS12), suite.config.PostgreSQL.TLS.MinimumVersion.Value) +	suite.Equal(uint16(tls.VersionTLS12), suite.config.PostgreSQL.TLS.MinimumVersion.Value)  } -func (suite *StorageSuite) TestShouldSetDefaultPostgreSQLTLSServerName() { +func (suite *StorageSuite) TestShouldValidatePostgresServers() {  	suite.config.PostgreSQL = &schema.StoragePostgreSQL{  		StorageSQL: schema.StorageSQL{  			Address: &schema.AddressTCP{ @@ -286,20 +286,54 @@ func (suite *StorageSuite) TestShouldSetDefaultPostgreSQLTLSServerName() {  			Password: "pass",  			Database: "database",  		}, -		TLS: &schema.TLS{ -			MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS12}, +		Servers: []schema.StoragePostgreSQLServer{ +			{ +				Address: &schema.AddressTCP{ +					Address: MustParseAddress("udp://server1:4321"), +				}, +			}, +			{}, +			{ +				Address: &schema.AddressTCP{ +					Address: MustParseAddress("tcp://server1"), +				}, +			}, +			{ +				Address: &schema.AddressTCP{ +					Address: MustParseAddress("tcp://server1:5432"), +				}, +				TLS: &schema.TLS{}, +			}, +			{ +				Address: &schema.AddressTCP{ +					Address: MustParseAddress("tcp://server1:5432"), +				}, +				TLS: &schema.TLS{ +					MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13}, +					MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS10}, +				}, +			},  		},  	}  	ValidateStorage(suite.config, suite.val) -	suite.Assert().Len(suite.val.Warnings(), 0) -	suite.Assert().Len(suite.val.Errors(), 0) +	suite.Len(suite.val.Warnings(), 0) +	suite.Require().Len(suite.config.PostgreSQL.Servers, 5) -	suite.Assert().Equal(suite.config.PostgreSQL.Address.Hostname(), suite.config.PostgreSQL.TLS.ServerName) +	suite.Equal(uint16(5432), suite.config.PostgreSQL.Servers[2].Address.Port()) +	suite.Equal("server1", suite.config.PostgreSQL.Servers[3].TLS.ServerName) +	suite.Equal(schema.TLSVersion{Value: tls.VersionTLS12}, suite.config.PostgreSQL.Servers[3].TLS.MinimumVersion) + +	errors := suite.val.Errors() + +	suite.Require().Len(errors, 3) +	suite.EqualError(errors[0], "storage: postgres: servers: #1: option 'address' with value 'udp://server1:4321' is invalid: scheme must be one of 'tcp', 'tcp4', 'tcp6', or 'unix' but is configured as 'udp'") +	suite.EqualError(errors[1], "storage: postgres: servers: #2: option 'address' is required") +	suite.EqualError(errors[2], "storage: postgres: servers: #5: tls: option combination of 'minimum_version' and 'maximum_version' is invalid: minimum version TLS1.3 is greater than the maximum version TLS1.0")  } -func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidPostgreSQLTLSVersion() { +func (suite *StorageSuite) TestShouldValidatePostgresServersTLSMinMaxFromPrimary() {  	suite.config.PostgreSQL = &schema.StoragePostgreSQL{  		StorageSQL: schema.StorageSQL{  			Address: &schema.AddressTCP{ @@ -308,21 +342,77 @@ func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidPostgreSQLTLSVersion() {  			Username: "myuser",  			Password: "pass",  			Database: "database", +			TLS: &schema.TLS{ +				MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS10}, +				MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS13}, +			},  		}, -		TLS: &schema.TLS{ -			MinimumVersion: schema.TLSVersion{Value: tls.VersionSSL30}, //nolint:staticcheck +		Servers: []schema.StoragePostgreSQLServer{ +			{ +				Address: &schema.AddressTCP{ +					Address: MustParseAddress("tcp://server1:5432"), +				}, +				TLS: &schema.TLS{}, +			},  		},  	}  	ValidateStorage(suite.config, suite.val) -	suite.Assert().Len(suite.val.Warnings(), 0) +	suite.Len(suite.val.Warnings(), 0) +	suite.Len(suite.val.Errors(), 0) +	suite.Require().Len(suite.config.PostgreSQL.Servers, 1) + +	suite.Equal("server1", suite.config.PostgreSQL.Servers[0].TLS.ServerName) +	suite.Equal(schema.TLSVersion{Value: tls.VersionTLS10}, suite.config.PostgreSQL.Servers[0].TLS.MinimumVersion) +	suite.Equal(schema.TLSVersion{Value: tls.VersionTLS13}, suite.config.PostgreSQL.Servers[0].TLS.MaximumVersion) +} + +func (suite *StorageSuite) TestShouldValidatePostgresUDP() { +	suite.config.PostgreSQL = &schema.StoragePostgreSQL{ +		StorageSQL: schema.StorageSQL{ +			Address: &schema.AddressTCP{ +				Address: MustParseAddress("udp://postgre:4321"), +			}, +			Username: "myuser", +			Password: "pass", +			Database: "database", +			TLS: &schema.TLS{ +				MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS10}, +				MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS13}, +			}, +		}, +	} + +	ValidateStorage(suite.config, suite.val) + +	suite.Len(suite.val.Warnings(), 0)  	suite.Require().Len(suite.val.Errors(), 1) -	suite.Assert().EqualError(suite.val.Errors()[0], "storage: postgres: tls: option 'minimum_version' is invalid: minimum version is TLS1.0 but SSL3.0 was configured") +	suite.EqualError(suite.val.Errors()[0], "storage: postgres: option 'address' with value 'udp://postgre:4321' is invalid: scheme must be one of 'tcp', 'tcp4', 'tcp6', or 'unix' but is configured as 'udp'")  } -func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidPostgreSQLMinVersionGreaterThanMaximum() { +func (suite *StorageSuite) TestShouldValidatePostgresSetPort() { +	suite.config.PostgreSQL = &schema.StoragePostgreSQL{ +		StorageSQL: schema.StorageSQL{ +			Address: &schema.AddressTCP{ +				Address: MustParseAddress("tcp://postgre"), +			}, +			Username: "myuser", +			Password: "pass", +			Database: "database", +		}, +	} + +	ValidateStorage(suite.config, suite.val) + +	suite.Len(suite.val.Warnings(), 0) +	suite.Len(suite.val.Errors(), 0) + +	suite.Equal(uint16(5432), suite.config.PostgreSQL.Address.Port()) +} + +func (suite *StorageSuite) TestShouldSetDefaultPostgreSQLTLSServerName() {  	suite.config.PostgreSQL = &schema.StoragePostgreSQL{  		StorageSQL: schema.StorageSQL{  			Address: &schema.AddressTCP{ @@ -331,19 +421,65 @@ func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidPostgreSQLMinVersionGrea  			Username: "myuser",  			Password: "pass",  			Database: "database", +			TLS: &schema.TLS{ +				MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS12}, +			},  		}, -		TLS: &schema.TLS{ -			MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13}, -			MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS11}, +	} + +	ValidateStorage(suite.config, suite.val) + +	suite.Len(suite.val.Warnings(), 0) +	suite.Len(suite.val.Errors(), 0) + +	suite.Equal(suite.config.PostgreSQL.Address.Hostname(), suite.config.PostgreSQL.TLS.ServerName) +} + +func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidPostgreSQLTLSVersion() { +	suite.config.PostgreSQL = &schema.StoragePostgreSQL{ +		StorageSQL: schema.StorageSQL{ +			Address: &schema.AddressTCP{ +				Address: MustParseAddress("tcp://postgre:4321"), +			}, +			Username: "myuser", +			Password: "pass", +			Database: "database", +			TLS: &schema.TLS{ +				MinimumVersion: schema.TLSVersion{Value: tls.VersionSSL30}, //nolint:staticcheck +			},  		},  	}  	ValidateStorage(suite.config, suite.val) -	suite.Assert().Len(suite.val.Warnings(), 0) +	suite.Len(suite.val.Warnings(), 0)  	suite.Require().Len(suite.val.Errors(), 1) -	suite.Assert().EqualError(suite.val.Errors()[0], "storage: postgres: tls: option combination of 'minimum_version' and 'maximum_version' is invalid: minimum version TLS1.3 is greater than the maximum version TLS1.1") +	suite.EqualError(suite.val.Errors()[0], "storage: postgres: tls: option 'minimum_version' is invalid: minimum version is TLS1.0 but SSL3.0 was configured") +} + +func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidPostgreSQLMinVersionGreaterThanMaximum() { +	suite.config.PostgreSQL = &schema.StoragePostgreSQL{ +		StorageSQL: schema.StorageSQL{ +			Address: &schema.AddressTCP{ +				Address: MustParseAddress("tcp://postgre:4321"), +			}, +			Username: "myuser", +			Password: "pass", +			Database: "database", +			TLS: &schema.TLS{ +				MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13}, +				MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS11}, +			}, +		}, +	} + +	ValidateStorage(suite.config, suite.val) + +	suite.Len(suite.val.Warnings(), 0) +	suite.Require().Len(suite.val.Errors(), 1) + +	suite.EqualError(suite.val.Errors()[0], "storage: postgres: tls: option combination of 'minimum_version' and 'maximum_version' is invalid: minimum version TLS1.3 is greater than the maximum version TLS1.1")  }  func (suite *StorageSuite) TestShouldValidatePostgresSSLDefaults() { @@ -361,13 +497,13 @@ func (suite *StorageSuite) TestShouldValidatePostgresSSLDefaults() {  	ValidateStorage(suite.config, suite.val) -	suite.Assert().Len(suite.val.Warnings(), 1) -	suite.Assert().Len(suite.val.Errors(), 0) +	suite.Len(suite.val.Warnings(), 1) +	suite.Len(suite.val.Errors(), 0) -	suite.Assert().NotNil(suite.config.PostgreSQL.SSL) //nolint:staticcheck +	suite.NotNil(suite.config.PostgreSQL.SSL) //nolint:staticcheck  	suite.Require().Nil(suite.config.PostgreSQL.TLS) -	suite.Assert().Equal(schema.DefaultPostgreSQLStorageConfiguration.SSL.Mode, suite.config.PostgreSQL.SSL.Mode) //nolint:staticcheck +	suite.Equal(schema.DefaultPostgreSQLStorageConfiguration.SSL.Mode, suite.config.PostgreSQL.SSL.Mode) //nolint:staticcheck  }  func (suite *StorageSuite) TestShouldRaiseErrorOnTLSAndLegacySSL() { @@ -379,17 +515,17 @@ func (suite *StorageSuite) TestShouldRaiseErrorOnTLSAndLegacySSL() {  			Username: "myuser",  			Password: "pass",  			Database: "database", +			TLS:      &schema.TLS{},  		},  		SSL: &schema.StoragePostgreSQLSSL{}, -		TLS: &schema.TLS{},  	}  	ValidateStorage(suite.config, suite.val) -	suite.Assert().Len(suite.val.Warnings(), 0) +	suite.Len(suite.val.Warnings(), 0)  	suite.Require().Len(suite.val.Errors(), 1) -	suite.Assert().EqualError(suite.val.Errors()[0], "storage: postgres: can't define both 'tls' and 'ssl' configuration options") +	suite.EqualError(suite.val.Errors()[0], "storage: postgres: can't define both 'tls' and 'ssl' configuration options")  }  func (suite *StorageSuite) TestShouldValidatePostgresDefaultsDontOverrideConfiguration() { @@ -411,12 +547,12 @@ func (suite *StorageSuite) TestShouldValidatePostgresDefaultsDontOverrideConfigu  	ValidateStorage(suite.config, suite.val)  	suite.Require().Len(suite.val.Warnings(), 1) -	suite.Assert().Len(suite.val.Errors(), 0) +	suite.Len(suite.val.Errors(), 0) -	suite.Assert().Equal("require", suite.config.PostgreSQL.SSL.Mode) //nolint:staticcheck -	suite.Assert().Equal("authelia", suite.config.PostgreSQL.Schema) +	suite.Equal("require", suite.config.PostgreSQL.SSL.Mode) //nolint:staticcheck +	suite.Equal("authelia", suite.config.PostgreSQL.Schema) -	suite.Assert().EqualError(suite.val.Warnings()[0], "storage: postgres: ssl: the ssl configuration options are deprecated and we recommend the tls options instead") +	suite.EqualError(suite.val.Warnings()[0], "storage: postgres: ssl: the ssl configuration options are deprecated and we recommend the tls options instead")  }  func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeValid() { @@ -436,9 +572,9 @@ func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeValid() {  	ValidateStorage(suite.config, suite.val) -	suite.Assert().Len(suite.val.Warnings(), 1) +	suite.Len(suite.val.Warnings(), 1)  	suite.Require().Len(suite.val.Errors(), 1) -	suite.Assert().EqualError(suite.val.Errors()[0], "storage: postgres: ssl: option 'mode' must be one of 'disable', 'require', 'verify-ca', or 'verify-full' but it's configured as 'unknown'") +	suite.EqualError(suite.val.Errors()[0], "storage: postgres: ssl: option 'mode' must be one of 'disable', 'require', 'verify-ca', or 'verify-full' but it's configured as 'unknown'")  }  func (suite *StorageSuite) TestShouldRaiseErrorOnNoEncryptionKey() { @@ -451,7 +587,7 @@ func (suite *StorageSuite) TestShouldRaiseErrorOnNoEncryptionKey() {  	suite.Require().Len(suite.val.Warnings(), 0)  	suite.Require().Len(suite.val.Errors(), 1) -	suite.Assert().EqualError(suite.val.Errors()[0], "storage: option 'encryption_key' is required") +	suite.EqualError(suite.val.Errors()[0], "storage: option 'encryption_key' is required")  }  func (suite *StorageSuite) TestShouldRaiseErrorOnShortEncryptionKey() { @@ -464,7 +600,7 @@ func (suite *StorageSuite) TestShouldRaiseErrorOnShortEncryptionKey() {  	suite.Require().Len(suite.val.Warnings(), 0)  	suite.Require().Len(suite.val.Errors(), 1) -	suite.Assert().EqualError(suite.val.Errors()[0], "storage: option 'encryption_key' must be 20 characters or longer") +	suite.EqualError(suite.val.Errors()[0], "storage: option 'encryption_key' must be 20 characters or longer")  }  func TestShouldRunStorageSuite(t *testing.T) { diff --git a/internal/storage/sql_provider_backend_mysql_test.go b/internal/storage/sql_provider_backend_mysql_test.go new file mode 100644 index 000000000..f5bd2bee7 --- /dev/null +++ b/internal/storage/sql_provider_backend_mysql_test.go @@ -0,0 +1,60 @@ +package storage + +import ( +	"crypto/tls" +	"testing" + +	"github.com/stretchr/testify/assert" +	"github.com/stretchr/testify/require" + +	"github.com/authelia/authelia/v4/internal/configuration/schema" +) + +func TestNewMySQLProvider(t *testing.T) { +	standardAddress, err := schema.NewAddress("tcp://mysql") +	require.NoError(t, err) + +	testCases := []struct { +		name string +		have *schema.Configuration +	}{ +		{ +			"ShouldHandleBasic", +			&schema.Configuration{ +				Storage: schema.Storage{ +					MySQL: &schema.StorageMySQL{ +						StorageSQL: schema.StorageSQL{ +							Address:  &schema.AddressTCP{Address: *standardAddress}, +							Database: "authelia", +						}, +					}, +				}, +			}, +		}, +		{ +			"ShouldHandleTLS", +			&schema.Configuration{ +				Storage: schema.Storage{ +					MySQL: &schema.StorageMySQL{ +						StorageSQL: schema.StorageSQL{ +							Address:  &schema.AddressTCP{Address: *standardAddress}, +							Database: "authelia", +							TLS: &schema.TLS{ +								MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS12}, +								MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS13}, +								SkipVerify:     false, +								ServerName:     "mysql", +							}, +						}, +					}, +				}, +			}, +		}, +	} + +	for _, tc := range testCases { +		t.Run(tc.name, func(t *testing.T) { +			assert.NotNil(t, NewMySQLProvider(tc.have, nil)) +		}) +	} +} diff --git a/internal/storage/sql_provider_backend_postgres.go b/internal/storage/sql_provider_backend_postgres.go index a410e01fd..daa7eef02 100644 --- a/internal/storage/sql_provider_backend_postgres.go +++ b/internal/storage/sql_provider_backend_postgres.go @@ -11,6 +11,7 @@ import (  	"strconv"  	"github.com/jackc/pgx/v5" +	"github.com/jackc/pgx/v5/pgconn"  	"github.com/jackc/pgx/v5/stdlib"  	"github.com/authelia/authelia/v4/internal/configuration/schema" @@ -174,7 +175,12 @@ func dsnPostgreSQL(config *schema.StoragePostgreSQL, globalCACertPool *x509.Cert  	dsnConfig.TLSConfig = loadPostgreSQLTLSConfig(config, globalCACertPool)  	dsnConfig.ConnectTimeout = config.Timeout  	dsnConfig.RuntimeParams = map[string]string{ -		"search_path": config.Schema, +		"application_name": fmt.Sprintf("Authelia %s", utils.Version()), +		"search_path":      config.Schema, +	} + +	if len(config.Servers) != 0 { +		dsnPostgreSQLFallbacks(config, globalCACertPool, dsnConfig)  	}  	return stdlib.RegisterConnConfig(dsnConfig) @@ -207,20 +213,40 @@ func dsnPostgreSQLHostPort(address *schema.AddressTCP) (host string, port uint16  	return host, port  } +func dsnPostgreSQLFallbacks(config *schema.StoragePostgreSQL, globalCACertPool *x509.CertPool, dsnConfig *pgx.ConnConfig) { +	dsnConfig.Fallbacks = make([]*pgconn.FallbackConfig, len(config.Servers)) + +	for i, server := range config.Servers { +		fallback := &pgconn.FallbackConfig{ +			TLSConfig: loadPostgreSQLModernTLSConfig(server.TLS, globalCACertPool), +		} + +		fallback.Host, fallback.Port = dsnPostgreSQLHostPort(server.Address) + +		if fallback.Port == 0 && !server.Address.IsUnixDomainSocket() { +			fallback.Port = 5432 +		} + +		dsnConfig.Fallbacks[i] = fallback +	} +} +  func loadPostgreSQLTLSConfig(config *schema.StoragePostgreSQL, globalCACertPool *x509.CertPool) (tlsConfig *tls.Config) {  	if config.TLS != nil { -		return utils.NewTLSConfig(config.TLS, globalCACertPool) +		return loadPostgreSQLModernTLSConfig(config.TLS, globalCACertPool) +	} else if config.SSL != nil { //nolint:staticcheck +		return loadPostgreSQLLegacyTLSConfig(config, globalCACertPool)  	} -	return loadPostgreSQLLegacyTLSConfig(config, globalCACertPool) +	return nil +} + +func loadPostgreSQLModernTLSConfig(config *schema.TLS, globalCACertPool *x509.CertPool) (tlsConfig *tls.Config) { +	return utils.NewTLSConfig(config, globalCACertPool)  }  //nolint:staticcheck // Used for legacy purposes.  func loadPostgreSQLLegacyTLSConfig(config *schema.StoragePostgreSQL, globalCACertPool *x509.CertPool) (tlsConfig *tls.Config) { -	if config.SSL == nil { -		return nil -	} -  	var (  		ca    *x509.Certificate  		certs []tls.Certificate @@ -238,7 +264,7 @@ func loadPostgreSQLLegacyTLSConfig(config *schema.StoragePostgreSQL, globalCACer  		case nil:  			caCertPool = globalCACertPool  		default: -			caCertPool = globalCACertPool.Clone() +			caCertPool = globalCACertPool  			caCertPool.AddCert(ca)  		} diff --git a/internal/storage/sql_provider_backend_postgres_test.go b/internal/storage/sql_provider_backend_postgres_test.go index 4a56c25a1..a8ed6d584 100644 --- a/internal/storage/sql_provider_backend_postgres_test.go +++ b/internal/storage/sql_provider_backend_postgres_test.go @@ -38,10 +38,10 @@ func TestNewPostgreSQLProvider(t *testing.T) {  					PostgreSQL: &schema.StoragePostgreSQL{  						StorageSQL: schema.StorageSQL{  							Address: &schema.AddressTCP{Address: *address}, -						}, -						TLS: &schema.TLS{ -							MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS12}, -							MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS13}, +							TLS: &schema.TLS{ +								MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS12}, +								MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS13}, +							},  						},  					},  				}, diff --git a/internal/storage/sql_provider_backend_sqlite_test.go b/internal/storage/sql_provider_backend_sqlite_test.go new file mode 100644 index 000000000..a591d0bed --- /dev/null +++ b/internal/storage/sql_provider_backend_sqlite_test.go @@ -0,0 +1,44 @@ +package storage + +import ( +	"path/filepath" +	"testing" + +	"github.com/stretchr/testify/assert" + +	"github.com/authelia/authelia/v4/internal/configuration/schema" +) + +func TestNewSQLiteProvider(t *testing.T) { +	dir := t.TempDir() +	testCases := []struct { +		name string +		have *schema.Configuration +	}{ +		{ +			"ShouldHandleBasic", +			&schema.Configuration{ +				Storage: schema.Storage{ +					Local: &schema.StorageLocal{ +						Path: filepath.Join(dir, "sqlite1.db"), +					}, +				}, +			}, +		}, +	} + +	for _, tc := range testCases { +		t.Run(tc.name, func(t *testing.T) { +			assert.NotNil(t, NewSQLiteProvider(tc.have)) +		}) +	} +} + +func TestSQLiteRegisteredFuncs(t *testing.T) { +	output := sqlite3BLOBToTEXTBase64([]byte("example")) +	assert.Equal(t, "ZXhhbXBsZQ==", output) + +	decoded, err := sqlite3TEXTBase64ToBLOB("ZXhhbXBsZQ==") +	assert.NoError(t, err) +	assert.Equal(t, []byte("example"), decoded) +}  | 
