summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/authelia-gen/cmd_docs_jsonschema.go14
-rw-r--r--cmd/authelia-gen/helpers.go4
-rw-r--r--config.template.yml24
-rw-r--r--docs/content/configuration/definitions/_index.md9
-rw-r--r--docs/content/configuration/definitions/introduction.md20
-rw-r--r--docs/content/configuration/definitions/network.md56
-rw-r--r--docs/content/configuration/prologue/common.md14
-rw-r--r--docs/content/configuration/security/access-control.md59
-rw-r--r--docs/layouts/shortcodes/confkey.html1
-rw-r--r--docs/static/schemas/v4.39/json-schema/configuration.json3421
-rw-r--r--docs/static/schemas/v4.39/json-schema/exports.identifiers.json58
-rw-r--r--docs/static/schemas/v4.39/json-schema/exports.totp.json69
-rw-r--r--docs/static/schemas/v4.39/json-schema/exports.webauthn.json130
-rw-r--r--docs/static/schemas/v4.39/json-schema/user-database.json71
-rw-r--r--internal/authorization/access_control_rule.go26
-rw-r--r--internal/authorization/authorizer_test.go25
-rw-r--r--internal/authorization/types.go16
-rw-r--r--internal/authorization/util.go69
-rw-r--r--internal/authorization/util_test.go179
-rw-r--r--internal/commands/context.go8
-rw-r--r--internal/configuration/config.template.yml24
-rw-r--r--internal/configuration/decode_hooks.go73
-rw-r--r--internal/configuration/decode_hooks_test.go89
-rw-r--r--internal/configuration/koanf_provider_filtered_file.go16
-rw-r--r--internal/configuration/provider.go96
-rw-r--r--internal/configuration/provider_test.go58
-rw-r--r--internal/configuration/schema/access_control.go20
-rw-r--r--internal/configuration/schema/configuration.go1
-rw-r--r--internal/configuration/schema/definitions.go7
-rw-r--r--internal/configuration/schema/keys.go2
-rw-r--r--internal/configuration/schema/types.go27
-rw-r--r--internal/configuration/schema/types_test.go2
-rw-r--r--internal/configuration/sources.go31
-rw-r--r--internal/configuration/test_resources/config_with_definitions.yml185
-rw-r--r--internal/configuration/test_resources/decode_networks.yml8
-rw-r--r--internal/configuration/test_resources/decode_networks_abc.yml8
-rw-r--r--internal/configuration/types.go8
-rw-r--r--internal/configuration/validator/access_control.go125
-rw-r--r--internal/configuration/validator/access_control_test.go146
-rw-r--r--internal/configuration/validator/const.go4
-rw-r--r--internal/configuration/validator/identity_providers.go2
-rw-r--r--internal/configuration/validator/keys.go8
-rw-r--r--internal/configuration/validator/storage_test.go39
-rw-r--r--internal/configuration/validator/util_test.go6
-rw-r--r--internal/utils/ip.go32
-rw-r--r--internal/utils/ip_test.go128
46 files changed, 4898 insertions, 520 deletions
diff --git a/cmd/authelia-gen/cmd_docs_jsonschema.go b/cmd/authelia-gen/cmd_docs_jsonschema.go
index f4f550ad5..37db76bb3 100644
--- a/cmd/authelia-gen/cmd_docs_jsonschema.go
+++ b/cmd/authelia-gen/cmd_docs_jsonschema.go
@@ -339,6 +339,20 @@ func getJSONSchemaOutputPath(cmd *cobra.Command, flag string) (dir, file string,
func jsonschemaKoanfMapper(t reflect.Type) *jsonschema.Schema {
switch t.String() {
+ case "[]*net.IPNet":
+ return &jsonschema.Schema{
+ OneOf: []*jsonschema.Schema{
+ {
+ Type: jsonschema.TypeString,
+ },
+ {
+ Type: jsonschema.TypeArray,
+ Items: &jsonschema.Schema{
+ Type: jsonschema.TypeString,
+ },
+ },
+ },
+ }
case "regexp.Regexp", "*regexp.Regexp":
return &jsonschema.Schema{
Type: jsonschema.TypeString,
diff --git a/cmd/authelia-gen/helpers.go b/cmd/authelia-gen/helpers.go
index 4b6e33aa6..94c9b2cf5 100644
--- a/cmd/authelia-gen/helpers.go
+++ b/cmd/authelia-gen/helpers.go
@@ -170,6 +170,10 @@ func readTags(prefix string, t reflect.Type, envSkip, deprecatedSkip bool) (tags
continue
}
case reflect.Slice:
+ if kind == reflect.Map {
+ 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)...)
}
case reflect.Ptr:
diff --git a/config.template.yml b/config.template.yml
index c6231783d..7ee0e16eb 100644
--- a/config.template.yml
+++ b/config.template.yml
@@ -323,6 +323,22 @@ identity_validation:
# disable_failure: false
##
+## Definitions
+##
+## The definitions are used in other areas as reference points to reduce duplication.
+##
+# definitions:
+ ## The network definitions.
+ # network:
+ ## The name of the definition followed by the list of CIDR network addresses in this definition.
+ # internal:
+ # - '10.10.0.0/16'
+ # - '172.16.0.0/12'
+ # - '192.168.2.0/24'
+ # VPN:
+ # - '10.9.0.0/16'
+
+##
## Authentication Backend Provider Configuration
##
## Used for verifying user passwords and retrieve information such as email address and groups users belong to.
@@ -622,14 +638,6 @@ identity_validation:
## resource if there is no policy to be applied to the user.
# default_policy: 'deny'
- # networks:
- # - name: 'internal'
- # networks:
- # - '10.10.0.0/16'
- # - '192.168.2.0/24'
- # - name: 'VPN'
- # networks: '10.9.0.0/16'
-
# rules:
## Rules applied to everyone
# - domain: 'public.example.com'
diff --git a/docs/content/configuration/definitions/_index.md b/docs/content/configuration/definitions/_index.md
new file mode 100644
index 000000000..80a4cfde9
--- /dev/null
+++ b/docs/content/configuration/definitions/_index.md
@@ -0,0 +1,9 @@
+---
+title: "Definitions"
+description: "Definitions Configuration"
+summary: ""
+date: 2024-10-19T23:15:30+11:00
+draft: false
+images: []
+weight: 198000
+---
diff --git a/docs/content/configuration/definitions/introduction.md b/docs/content/configuration/definitions/introduction.md
new file mode 100644
index 000000000..e2626567e
--- /dev/null
+++ b/docs/content/configuration/definitions/introduction.md
@@ -0,0 +1,20 @@
+---
+title: "Definitions"
+description: "Definitions Configuration"
+summary: "Authelia allows configuring reusable definitions."
+date: 2024-10-19T23:15:30+11:00
+draft: false
+images: []
+weight: 199100
+toc: true
+seo:
+ title: "" # custom title (optional)
+ description: "" # custom description (recommended)
+ canonical: "" # custom canonical URL (optional)
+ noindex: false # false (default) or true
+---
+
+## Definitions
+
+The definitions section controls several definitions which can be reused in other areas of the configuration rather than
+repeating values elsewhere.
diff --git a/docs/content/configuration/definitions/network.md b/docs/content/configuration/definitions/network.md
new file mode 100644
index 000000000..b02157a3f
--- /dev/null
+++ b/docs/content/configuration/definitions/network.md
@@ -0,0 +1,56 @@
+---
+title: "Network"
+description: "Network Definitions Configuration"
+summary: "Authelia allows configuring reusable network definitions."
+date: 2024-10-19T23:15:30+11:00
+draft: false
+images: []
+weight: 199100
+toc: true
+seo:
+ title: "" # custom title (optional)
+ description: "" # custom description (recommended)
+ canonical: "" # custom canonical URL (optional)
+ noindex: false # false (default) or true
+---
+
+The network section configures named network lists.
+
+## Configuration
+
+{{< config-alert-example >}}
+
+```yaml {title="configuration.yml"}
+definitions:
+ network:
+ network_name:
+ - '192.168.1.0/24'
+ - '192.168.2.20'
+ - '2001:db8::/32'
+ - '2001:db8:1234:5678::1'
+```
+
+## Options
+
+This section describes the individual configuration options. The configuration for this section is incredibly basic,
+effectively it's key value pairs, where the key is the name used elsewhere in the configuration, and the value is a list
+of network addresses.
+
+These definitions are used as [Access Control Networks](../security/access-control.md#networks) and
+[OpenID Connect 1.0 Authorization Policy Networks](../identity-providers/openid-connect/provider.md#networks).
+
+### key
+
+The key is the name of the policy. In the example above, the key is `network_name` and is the value which must be used
+in other areas of the configuration to reference it.
+
+### value
+
+{{< confkey type="string" syntax="network" required="yes" >}}
+
+The values which represent the CIDR notation of the IP's this definition applies to. In the example, the value is a list
+which contains `192.168.1.0/24`, `192.168.2.20`, `2001:db8::/32`, and `2001:db8:1234:5678::1`.
+
+CIDR notation (e.g., `192.168.1.0/24`) represents a range of IP addresses. The number after the slash indicates how many
+bits are used for the network portion. For example, `/24` means the first 24 bits are fixed, allowing the last 8 bits
+to vary (giving you 256 possible addresses). A single IP address like `192.168.2.20` can be written as is or with `/32`.
diff --git a/docs/content/configuration/prologue/common.md b/docs/content/configuration/prologue/common.md
index 7cbc65d26..ecf6bebcc 100644
--- a/docs/content/configuration/prologue/common.md
+++ b/docs/content/configuration/prologue/common.md
@@ -220,6 +220,20 @@ Bad Example:
domain_regex: "^(admin|secure)\.example\.com$"
```
+### Network
+
+We support a network syntax which unmarshalls strings into a network range. The string format uses the standard CIDR
+notation and assumes a single host (adapted as /32 for IPv4 and /128 for IPv6) if the CIDR suffix is absent.
+
+| Example | CIDR | Range |
+|:-----------------------------------------:|:------------------------------------------:|:---------------------------------------------------------------------------------:|
+| 192.168.0.1 | 192.168.0.1/32 | 192.168.0.1 |
+| 192.168.1.0/24 | 192.168.1.0/24 | 192.168.1.0 - 192.168.1.255 |
+| 192.168.2.1/24 | 192.168.2.0/24 | 192.168.2.0 - 192.168.2.255 |
+| 2001:db8:3333:4444:5555:6666:7777:8888 | 2001:db8:3333:4444:5555:6666:7777:8888/128 | 2001:db8:3333:4444:5555:6666:7777:8888 |
+| 2001:db8:3333:4400::/56 | 2001:db8:3333:4400::/56 | 2001:0db8:3333:4400:0000:0000:0000:0000 - 2001:0db8:3333:44ff:ffff:ffff:ffff:ffff |
+| 2001:db8:3333:4444:5555:6666:7777:8888/56 | 2001:db8:3333:4400::/56 | 2001:0db8:3333:4400:0000:0000:0000:0000 - 2001:0db8:3333:44ff:ffff:ffff:ffff:ffff |
+
## Structures
The following represent common data structures used within the configuration which have specific requirements that are
diff --git a/docs/content/configuration/security/access-control.md b/docs/content/configuration/security/access-control.md
index d67ebe271..e8e41f992 100644
--- a/docs/content/configuration/security/access-control.md
+++ b/docs/content/configuration/security/access-control.md
@@ -35,12 +35,6 @@ Some of the values within this page can automatically be replaced with documenta
```yaml {title="configuration.yml"}
access_control:
default_policy: 'deny'
- networks:
- - name: 'internal'
- networks:
- - '10.0.0.0/8'
- - '172.16.0.0/12'
- - '192.168.0.0/18'
rules:
- domain: 'private.{{< sitevar name="domain" nojs="example.com" >}}'
domain_regex: '^(\d+\-)?priv-img\.{{< sitevar name="domain" format="regex" nojs="example\.com" >}}$'
@@ -85,21 +79,6 @@ Authelia at all for performance reasons.
See the [policies] section for more information.
-### networks (global)
-
-{{< confkey type="list" required="no" >}}
-
-The main/global networks section contains a list of networks with a name label that can be reused in the
-[rules](#networks) section instead of redefining the same networks over and over again. This additionally makes
-complicated network related configuration a lot cleaner and easier to read.
-
-This section has two options, `name` and `networks`. Where the `networks` section is a list of IP addresses in CIDR
-notation and where `name` is a friendly name to label the collection of networks for reuse in the [networks] section of
-the [rules] section below.
-
-This configuration option *does nothing* by itself, it's only useful if you use these aliases in the [rules](#networks)
-section below.
-
### rules
{{< confkey type="list" required="no" >}}
@@ -366,13 +345,13 @@ access_control:
#### networks
-{{< confkey type="list(string)" required="no" >}}
+{{< confkey type="list(string)" syntax="network" required="no" >}}
-This criteria is a list of values which can be an IP Address, network address range in CIDR notation, or an alias from
-the [global](#networks-global) section. It matches against the first address in the `X-Forwarded-For` header, or if there
-are none it will fall back to the IP address of the packet TCP source IP address. For this reason it's important for you
-to configure the proxy server correctly in order to accurately match requests with this criteria. *__Note:__ you may
-combine CIDR networks with the alias rules as you please.*
+These criteria consist of a list of values which can be an IP Address, network address range in CIDR notation, or a named
+[Network Definition](../definitions/network.md). It matches against the first address in the `X-Forwarded-For` header,
+or if there are none it will fall back to the IP address of the packet TCP source IP address. For this reason, it's
+important for you to configure the proxy server correctly to accurately match requests with these criteria.
+*__Note:__ you may combine CIDR networks with the alias rules as you please.*
The main use case for this criteria is adjust the security requirements of a resource based on the location of a user.
You can theoretically consider a specific network to be one of the factors involved in authentication, you can deny
@@ -394,14 +373,14 @@ for administrators to tune the security to their specific needs if desired.
rules in this list are effectively the same rule just expressed in different ways.*
```yaml {title="configuration.yml"}
-access_control:
- default_policy: 'two_factor'
- networks:
- - name: 'internal'
- networks:
+definitions:
+ network:
+ internal:
- '10.0.0.0/8'
- '172.16.0.0/12'
- '192.168.0.0/18'
+access_control:
+ default_policy: 'two_factor'
rules:
- domain: 'secure.{{< sitevar name="domain" nojs="example.com" >}}'
policy: 'one_factor'
@@ -630,15 +609,15 @@ alphanumeric (including spaces).
Here is a detailed example of an example access control section:
```yaml {title="configuration.yml"}
+definitions:
+ network:
+ internal:
+ - '10.10.0.0/16'
+ - '192.168.2.0/24'
+ vpn: '10.9.0.0/16'
+
access_control:
default_policy: 'deny'
- networks:
- - name: 'internal'
- networks:
- - '10.10.0.0/16'
- - '192.168.2.0/24'
- - name: 'VPN'
- networks: '10.9.0.0/16'
rules:
- domain: 'public.{{< sitevar name="domain" nojs="example.com" >}}'
policy: 'bypass'
@@ -652,7 +631,7 @@ access_control:
policy: 'one_factor'
networks:
- 'internal'
- - 'VPN'
+ - 'vpn'
- '192.168.1.0/24'
- '10.0.0.1'
diff --git a/docs/layouts/shortcodes/confkey.html b/docs/layouts/shortcodes/confkey.html
index 933238a1b..458a23c87 100644
--- a/docs/layouts/shortcodes/confkey.html
+++ b/docs/layouts/shortcodes/confkey.html
@@ -12,6 +12,7 @@
{{- with $structure }}{{ $ref = . }}{{ end }}
{{- else if eq $syntax "duration" }}{{ $ref = "duration" }}
{{- else if eq $syntax "address" }}{{ $ref = "address" }}
+{{- else if eq $syntax "network" }}{{ $ref = "network" }}
{{- end }}
{{- end }}
<div class="mb-3">
diff --git a/docs/static/schemas/v4.39/json-schema/configuration.json b/docs/static/schemas/v4.39/json-schema/configuration.json
new file mode 100644
index 000000000..128236027
--- /dev/null
+++ b/docs/static/schemas/v4.39/json-schema/configuration.json
@@ -0,0 +1,3421 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://www.authelia.com/schemas/v4.39/json-schema/configuration.json",
+ "$ref": "#/$defs/Configuration",
+ "$defs": {
+ "AccessControl": {
+ "properties": {
+ "default_policy": {
+ "type": "string",
+ "enum": [
+ "deny",
+ "one_factor",
+ "two_factor"
+ ],
+ "title": "Default Authorization Policy",
+ "description": "The default policy applied to all authorization requests unrelated to OpenID Connect 1.0.",
+ "default": "deny"
+ },
+ "networks": {
+ "items": {
+ "$ref": "#/$defs/AccessControlNetwork"
+ },
+ "type": "array",
+ "title": "Named Networks",
+ "description": "The list of named networks which can be reused in any ACL rule."
+ },
+ "rules": {
+ "items": {
+ "$ref": "#/$defs/AccessControlRule"
+ },
+ "type": "array",
+ "title": "Rules List",
+ "description": "The list of ACL rules to enumerate for requests."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "AccessControl represents the configuration related to ACLs."
+ },
+ "AccessControlNetwork": {
+ "properties": {
+ "name": {
+ "type": "string",
+ "title": "Network Name",
+ "description": "The name of this network to be used in the networks section of the rules section."
+ },
+ "networks": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ }
+ ],
+ "title": "Networks",
+ "description": "The remote IP's or network ranges in CIDR notation that this rule applies to."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "required": [
+ "name",
+ "networks"
+ ],
+ "description": "AccessControlNetwork represents one ACL network group entry."
+ },
+ "AccessControlRule": {
+ "oneOf": [
+ {
+ "required": [
+ "domain"
+ ],
+ "title": "Domain"
+ },
+ {
+ "required": [
+ "domain_regex"
+ ],
+ "title": "Domain Regex"
+ }
+ ],
+ "properties": {
+ "domain": {
+ "$ref": "#/$defs/AccessControlRuleDomains",
+ "title": "Domain Literals",
+ "description": "The literal domains to match the domain against that this rule applies to."
+ },
+ "domain_regex": {
+ "$ref": "#/$defs/AccessControlRuleRegex",
+ "title": "Domain Regex Patterns",
+ "description": "The regex patterns to match the domain against that this rule applies to."
+ },
+ "policy": {
+ "type": "string",
+ "enum": [
+ "bypass",
+ "deny",
+ "one_factor",
+ "two_factor"
+ ],
+ "title": "Rule Policy",
+ "description": "The policy this rule applies when all criteria match."
+ },
+ "subject": {
+ "$ref": "#/$defs/AccessControlRuleSubjects",
+ "title": "AccessControlRuleSubjects",
+ "description": "The users or groups that this rule applies to."
+ },
+ "networks": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ }
+ ],
+ "title": "Networks",
+ "description": "The remote IP's, network ranges in CIDR notation, or network definition names that this rule applies to."
+ },
+ "resources": {
+ "$ref": "#/$defs/AccessControlRuleRegex",
+ "title": "Resources or Paths",
+ "description": "The regex patterns to match the resource paths that this rule applies to."
+ },
+ "methods": {
+ "$ref": "#/$defs/AccessControlRuleMethods",
+ "description": "The list of request methods this rule applies to."
+ },
+ "query": {
+ "items": {
+ "items": {
+ "$ref": "#/$defs/AccessControlRuleQuery"
+ },
+ "type": "array"
+ },
+ "type": "array",
+ "title": "Query Rules",
+ "description": "The list of query parameter rules this rule applies to."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "required": [
+ "policy"
+ ],
+ "description": "AccessControlRule represents one ACL rule entry."
+ },
+ "AccessControlRuleDomains": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "uniqueItems": true
+ }
+ ]
+ },
+ "AccessControlRuleMethods": {
+ "oneOf": [
+ {
+ "type": "string",
+ "enum": [
+ "GET",
+ "HEAD",
+ "POST",
+ "PUT",
+ "PATCH",
+ "DELETE",
+ "TRACE",
+ "CONNECT",
+ "OPTIONS",
+ "COPY",
+ "LOCK",
+ "MKCOL",
+ "MOVE",
+ "PROPFIND",
+ "PROPPATCH",
+ "UNLOCK"
+ ]
+ },
+ {
+ "items": {
+ "type": "string",
+ "enum": [
+ "GET",
+ "HEAD",
+ "POST",
+ "PUT",
+ "PATCH",
+ "DELETE",
+ "TRACE",
+ "CONNECT",
+ "OPTIONS",
+ "COPY",
+ "LOCK",
+ "MKCOL",
+ "MOVE",
+ "PROPFIND",
+ "PROPPATCH",
+ "UNLOCK"
+ ]
+ },
+ "type": "array",
+ "uniqueItems": true
+ }
+ ]
+ },
+ "AccessControlRuleQuery": {
+ "properties": {
+ "operator": {
+ "type": "string",
+ "enum": [
+ "equal",
+ "not equal",
+ "present",
+ "absent",
+ "pattern",
+ "not pattern"
+ ],
+ "title": "Operator",
+ "description": "The list of query parameter rules this rule applies to."
+ },
+ "key": {
+ "type": "string",
+ "title": "Key",
+ "description": "The Query Parameter key this rule applies to."
+ },
+ "value": {
+ "title": "Value",
+ "description": "The Query Parameter value for this rule."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "required": [
+ "key"
+ ],
+ "description": "AccessControlRuleQuery represents the ACL query criteria."
+ },
+ "AccessControlRuleRegex": {
+ "oneOf": [
+ {
+ "type": "string",
+ "format": "regex"
+ },
+ {
+ "items": {
+ "type": "string",
+ "format": "regex"
+ },
+ "type": "array",
+ "uniqueItems": true
+ }
+ ]
+ },
+ "AccessControlRuleSubjects": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^(user|group|oauth2:client):.+$"
+ },
+ {
+ "items": {
+ "type": "string",
+ "pattern": "^(user|group|oauth2:client):.+$"
+ },
+ "type": "array"
+ },
+ {
+ "items": {
+ "items": {
+ "type": "string",
+ "pattern": "^(user|group|oauth2:client):.+$"
+ },
+ "type": "array"
+ },
+ "type": "array",
+ "uniqueItems": true
+ }
+ ]
+ },
+ "AddressLDAP": {
+ "type": "string",
+ "pattern": "^((ldaps?:\\/\\/)?([^:\\/]*(:\\d+)|[^:\\/]+(:\\d+)?)?|ldapi:\\/\\/(\\/[^?\\n]+)?)$",
+ "format": "uri"
+ },
+ "AddressSMTP": {
+ "type": "string",
+ "pattern": "^((smtp|submissions?):\\/\\/)?([^:\\/]*(:\\d+)|[^:\\/]+(:\\d+)?)?$",
+ "format": "uri"
+ },
+ "AddressTCP": {
+ "type": "string",
+ "pattern": "^((tcp[46]?:\\/\\/)?([^:\\/]*(:\\d+)|[^:\\/]+(:\\d+)?)(\\/.*)?|unix:\\/\\/\\/[^?\\n]+(\\?(umask=[0-7]{3,4}|path=[a-z]+)(\u0026(umask=[0-7]{3,4}|path=[a-zA-Z0-9.~_-]+))?)?)$",
+ "format": "uri"
+ },
+ "AddressUDP": {
+ "type": "string",
+ "pattern": "^(udp[46]?:\\/\\/)?([^:\\/]*(:\\d+)|[^:\\/]+(:\\d+)?)(\\/.*)?$",
+ "format": "uri"
+ },
+ "AuthenticationBackend": {
+ "properties": {
+ "password_reset": {
+ "$ref": "#/$defs/AuthenticationBackendPasswordReset",
+ "title": "Password Reset",
+ "description": "Allows configuration of the password reset behaviour."
+ },
+ "refresh_interval": {
+ "$ref": "#/$defs/RefreshIntervalDuration",
+ "title": "Refresh Interval",
+ "description": "How frequently the user details are refreshed from the backend."
+ },
+ "file": {
+ "$ref": "#/$defs/AuthenticationBackendFile",
+ "title": "File Backend",
+ "description": "The file authentication backend configuration."
+ },
+ "ldap": {
+ "$ref": "#/$defs/AuthenticationBackendLDAP",
+ "title": "LDAP Backend",
+ "description": "The LDAP authentication backend configuration."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "AuthenticationBackend represents the configuration related to the authentication backend."
+ },
+ "AuthenticationBackendFile": {
+ "properties": {
+ "path": {
+ "type": "string",
+ "title": "Path",
+ "description": "The file path to the user database."
+ },
+ "watch": {
+ "type": "boolean",
+ "title": "Watch",
+ "description": "Enables watching the file for external changes and dynamically reloading the database.",
+ "default": false
+ },
+ "password": {
+ "$ref": "#/$defs/AuthenticationBackendFilePassword",
+ "title": "Password Options",
+ "description": "Allows configuration of the password hashing options when the user passwords are changed directly by Authelia."
+ },
+ "search": {
+ "$ref": "#/$defs/AuthenticationBackendFileSearch",
+ "title": "Search",
+ "description": "Configures the user searching behaviour."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "AuthenticationBackendFile represents the configuration related to file-based backend."
+ },
+ "AuthenticationBackendFilePassword": {
+ "properties": {
+ "algorithm": {
+ "type": "string",
+ "enum": [
+ "argon2",
+ "sha2crypt",
+ "pbkdf2",
+ "bcrypt",
+ "scrypt"
+ ],
+ "title": "Algorithm",
+ "description": "The password hashing algorithm to use.",
+ "default": "argon2"
+ },
+ "argon2": {
+ "$ref": "#/$defs/AuthenticationBackendFilePasswordArgon2",
+ "title": "Argon2",
+ "description": "Configure the Argon2 password hashing parameters."
+ },
+ "sha2crypt": {
+ "$ref": "#/$defs/AuthenticationBackendFilePasswordSHA2Crypt",
+ "title": "SHA2Crypt",
+ "description": "Configure the SHA2Crypt password hashing parameters."
+ },
+ "pbkdf2": {
+ "$ref": "#/$defs/AuthenticationBackendFilePasswordPBKDF2",
+ "title": "PBKDF2",
+ "description": "Configure the PBKDF2 password hashing parameters."
+ },
+ "bcrypt": {
+ "$ref": "#/$defs/AuthenticationBackendFilePasswordBCrypt",
+ "title": "BCrypt",
+ "description": "Configure the BCrypt password hashing parameters."
+ },
+ "scrypt": {
+ "$ref": "#/$defs/AuthenticationBackendFilePasswordSCrypt",
+ "title": "SCrypt",
+ "description": "Configure the SCrypt password hashing parameters."
+ },
+ "iterations": {
+ "type": "integer",
+ "title": "Iterations",
+ "description": "Deprecated: Use individual password options instead.",
+ "deprecated": true
+ },
+ "memory": {
+ "type": "integer",
+ "title": "Memory",
+ "description": "Deprecated: Use individual password options instead.",
+ "deprecated": true
+ },
+ "parallelism": {
+ "type": "integer",
+ "title": "Parallelism",
+ "description": "Deprecated: Use individual password options instead.",
+ "deprecated": true
+ },
+ "key_length": {
+ "type": "integer",
+ "title": "Key Length",
+ "description": "Deprecated: Use individual password options instead.",
+ "deprecated": true
+ },
+ "salt_length": {
+ "type": "integer",
+ "title": "Salt Length",
+ "description": "Deprecated: Use individual password options instead.",
+ "deprecated": true
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "AuthenticationBackendFilePassword represents the configuration related to password hashing."
+ },
+ "AuthenticationBackendFilePasswordArgon2": {
+ "properties": {
+ "variant": {
+ "type": "string",
+ "enum": [
+ "argon2id",
+ "argon2i",
+ "argon2d"
+ ],
+ "title": "Variant",
+ "description": "The Argon2 variant to be used.",
+ "default": "argon2id"
+ },
+ "iterations": {
+ "type": "integer",
+ "title": "Iterations",
+ "description": "The number of Argon2 iterations (parameter t) to be used.",
+ "default": 3
+ },
+ "memory": {
+ "type": "integer",
+ "maximum": 4294967295,
+ "minimum": 8,
+ "title": "Memory",
+ "description": "The Argon2 amount of memory in kibibytes (parameter m) to be used.",
+ "default": 65536
+ },
+ "parallelism": {
+ "type": "integer",
+ "maximum": 16777215,
+ "minimum": 1,
+ "title": "Parallelism",
+ "description": "The Argon2 degree of parallelism (parameter p) to be used.",
+ "default": 4
+ },
+ "key_length": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 4,
+ "title": "Key Length",
+ "description": "The Argon2 key output length.",
+ "default": 32
+ },
+ "salt_length": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 1,
+ "title": "Salt Length",
+ "description": "The Argon2 salt length.",
+ "default": 16
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "AuthenticationBackendFilePasswordArgon2 represents the argon2 hashing settings."
+ },
+ "AuthenticationBackendFilePasswordBCrypt": {
+ "properties": {
+ "variant": {
+ "type": "string",
+ "enum": [
+ "standard",
+ "sha256"
+ ],
+ "title": "Variant",
+ "description": "The BCrypt variant to be used.",
+ "default": "standard"
+ },
+ "cost": {
+ "type": "integer",
+ "maximum": 31,
+ "minimum": 10,
+ "title": "Cost",
+ "description": "The BCrypt cost to be used.",
+ "default": 12
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "AuthenticationBackendFilePasswordBCrypt represents the bcrypt hashing settings."
+ },
+ "AuthenticationBackendFilePasswordPBKDF2": {
+ "properties": {
+ "variant": {
+ "type": "string",
+ "enum": [
+ "sha1",
+ "sha224",
+ "sha256",
+ "sha384",
+ "sha512"
+ ],
+ "title": "Variant",
+ "description": "The PBKDF2 variant to be used.",
+ "default": "sha512"
+ },
+ "iterations": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 100000,
+ "title": "Iterations",
+ "description": "The PBKDF2 iterations to be used.",
+ "default": 310000
+ },
+ "salt_length": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 8,
+ "title": "Salt Length",
+ "description": "The PBKDF2 salt length to be used.",
+ "default": 16
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "AuthenticationBackendFilePasswordPBKDF2 represents the PBKDF2 hashing settings."
+ },
+ "AuthenticationBackendFilePasswordSCrypt": {
+ "properties": {
+ "iterations": {
+ "type": "integer",
+ "maximum": 58,
+ "minimum": 1,
+ "title": "Iterations",
+ "description": "The SCrypt iterations to be used.",
+ "default": 16
+ },
+ "block_size": {
+ "type": "integer",
+ "maximum": 36028797018963967,
+ "minimum": 1,
+ "title": "Key Length",
+ "description": "The SCrypt block size to be used.",
+ "default": 8
+ },
+ "parallelism": {
+ "type": "integer",
+ "maximum": 1073741823,
+ "minimum": 1,
+ "title": "Key Length",
+ "description": "The SCrypt parallelism factor to be used.",
+ "default": 1
+ },
+ "key_length": {
+ "type": "integer",
+ "maximum": 137438953440,
+ "minimum": 1,
+ "title": "Key Length",
+ "description": "The SCrypt key length to be used.",
+ "default": 32
+ },
+ "salt_length": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 8,
+ "title": "Salt Length",
+ "description": "The SCrypt salt length to be used.",
+ "default": 16
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "AuthenticationBackendFilePasswordSCrypt represents the scrypt hashing settings."
+ },
+ "AuthenticationBackendFilePasswordSHA2Crypt": {
+ "properties": {
+ "variant": {
+ "type": "string",
+ "enum": [
+ "sha256",
+ "sha512"
+ ],
+ "title": "Variant",
+ "description": "The SHA2Crypt variant to be used.",
+ "default": "sha512"
+ },
+ "iterations": {
+ "type": "integer",
+ "maximum": 999999999,
+ "minimum": 1000,
+ "title": "Iterations",
+ "description": "The SHA2Crypt iterations (parameter rounds) to be used.",
+ "default": 50000
+ },
+ "salt_length": {
+ "type": "integer",
+ "maximum": 16,
+ "minimum": 1,
+ "title": "Salt Length",
+ "description": "The SHA2Crypt salt length to be used.",
+ "default": 16
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "AuthenticationBackendFilePasswordSHA2Crypt represents the sha2crypt hashing settings."
+ },
+ "AuthenticationBackendFileSearch": {
+ "properties": {
+ "email": {
+ "type": "boolean",
+ "title": "Email Searching",
+ "description": "Allows users to either use their username or their configured email as a username.",
+ "default": false
+ },
+ "case_insensitive": {
+ "type": "boolean",
+ "title": "Case Insensitive Searching",
+ "description": "Allows usernames to be any case during the search.",
+ "default": false
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "AuthenticationBackendFileSearch represents the configuration related to file-based backend searching."
+ },
+ "AuthenticationBackendLDAP": {
+ "properties": {
+ "address": {
+ "$ref": "#/$defs/AddressLDAP",
+ "title": "Address",
+ "description": "The address of the LDAP directory server."
+ },
+ "implementation": {
+ "type": "string",
+ "enum": [
+ "custom",
+ "activedirectory",
+ "rfc2307bis",
+ "freeipa",
+ "lldap",
+ "glauth"
+ ],
+ "title": "Implementation",
+ "description": "The implementation which mostly decides the default values.",
+ "default": "custom"
+ },
+ "timeout": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Timeout",
+ "description": "The LDAP directory server connection timeout."
+ },
+ "start_tls": {
+ "type": "boolean",
+ "title": "StartTLS",
+ "description": "Enables the use of StartTLS.",
+ "default": false
+ },
+ "tls": {
+ "$ref": "#/$defs/TLS",
+ "title": "TLS",
+ "description": "The LDAP directory server TLS connection properties."
+ },
+ "base_dn": {
+ "type": "string",
+ "title": "Base DN",
+ "description": "The base for all directory server operations."
+ },
+ "additional_users_dn": {
+ "type": "string",
+ "title": "Additional User Base",
+ "description": "The base in addition to the Base DN for all directory server operations for users."
+ },
+ "users_filter": {
+ "type": "string",
+ "title": "Users Filter",
+ "description": "The LDAP filter used to search for user objects."
+ },
+ "additional_groups_dn": {
+ "type": "string",
+ "title": "Additional Group Base",
+ "description": "The base in addition to the Base DN for all directory server operations for groups."
+ },
+ "groups_filter": {
+ "type": "string",
+ "title": "Groups Filter",
+ "description": "The LDAP filter used to search for group objects."
+ },
+ "group_search_mode": {
+ "type": "string",
+ "enum": [
+ "filter",
+ "memberof"
+ ],
+ "title": "Groups Search Mode",
+ "description": "The LDAP group search mode used to search for group objects.",
+ "default": "filter"
+ },
+ "attributes": {
+ "$ref": "#/$defs/AuthenticationBackendLDAPAttributes"
+ },
+ "permit_referrals": {
+ "type": "boolean",
+ "title": "Permit Referrals",
+ "description": "Enables chasing LDAP referrals.",
+ "default": false
+ },
+ "permit_unauthenticated_bind": {
+ "type": "boolean",
+ "title": "Permit Unauthenticated Bind",
+ "description": "Enables omission of the password to perform an unauthenticated bind.",
+ "default": false
+ },
+ "permit_feature_detection_failure": {
+ "type": "boolean",
+ "title": "Permit Feature Detection Failure",
+ "description": "Enables failures when detecting directory server features using the Root DSE lookup.",
+ "default": false
+ },
+ "user": {
+ "type": "string",
+ "title": "User",
+ "description": "The user distinguished name for LDAP binding."
+ },
+ "password": {
+ "type": "string",
+ "title": "Password",
+ "description": "The password for LDAP authenticated binding."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "AuthenticationBackendLDAP represents the configuration related to LDAP server."
+ },
+ "AuthenticationBackendLDAPAttributes": {
+ "properties": {
+ "distinguished_name": {
+ "type": "string",
+ "title": "Attribute: Distinguished Name",
+ "description": "The directory server attribute which contains the distinguished name for all objects."
+ },
+ "username": {
+ "type": "string",
+ "title": "Attribute: User Username",
+ "description": "The directory server attribute which contains the username for all users."
+ },
+ "display_name": {
+ "type": "string",
+ "title": "Attribute: User Display Name",
+ "description": "The directory server attribute which contains the display name for all users."
+ },
+ "mail": {
+ "type": "string",
+ "title": "Attribute: User Mail",
+ "description": "The directory server attribute which contains the mail address for all users and groups."
+ },
+ "MemberOf": {
+ "type": "string",
+ "title": "Attribute: Member Of",
+ "description": "The directory server attribute which contains the objects that an object is a member of."
+ },
+ "group_name": {
+ "type": "string",
+ "title": "Attribute: Group Name",
+ "description": "The directory server attribute which contains the group name for all groups."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "AuthenticationBackendLDAPAttributes represents the configuration related to LDAP server attributes."
+ },
+ "AuthenticationBackendPasswordReset": {
+ "properties": {
+ "disable": {
+ "type": "boolean",
+ "title": "Disable",
+ "description": "Disables the Password Reset option.",
+ "default": false
+ },
+ "custom_url": {
+ "type": "string",
+ "format": "uri",
+ "title": "Custom URL",
+ "description": "Disables the internal Password Reset option and instead redirects users to this specified URL."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "AuthenticationBackendPasswordReset represents the configuration related to password reset functionality."
+ },
+ "Configuration": {
+ "properties": {
+ "theme": {
+ "type": "string",
+ "enum": [
+ "auto",
+ "light",
+ "dark",
+ "grey"
+ ],
+ "title": "Theme Name",
+ "description": "The name of the theme to apply to the web UI.",
+ "default": "light"
+ },
+ "certificates_directory": {
+ "type": "string",
+ "title": "Certificates Directory Path",
+ "description": "The path to a directory which is used to determine the certificates that are trusted."
+ },
+ "default_2fa_method": {
+ "type": "string",
+ "enum": [
+ "totp",
+ "webauthn",
+ "mobile_push"
+ ],
+ "title": "Default 2FA method",
+ "description": "When a user logs in for the first time this is the 2FA method configured for them."
+ },
+ "log": {
+ "$ref": "#/$defs/Log",
+ "title": "Log",
+ "description": "Logging Configuration."
+ },
+ "identity_providers": {
+ "$ref": "#/$defs/IdentityProviders",
+ "title": "Identity Providers",
+ "description": "Identity Providers Configuration."
+ },
+ "authentication_backend": {
+ "$ref": "#/$defs/AuthenticationBackend",
+ "title": "Authentication Backend",
+ "description": "Authentication Backend Configuration."
+ },
+ "session": {
+ "$ref": "#/$defs/Session",
+ "title": "Session",
+ "description": "Session Configuration."
+ },
+ "totp": {
+ "$ref": "#/$defs/TOTP",
+ "title": "TOTP",
+ "description": "Time-based One-Time Password Configuration."
+ },
+ "duo_api": {
+ "$ref": "#/$defs/DuoAPI",
+ "title": "Duo API",
+ "description": "Duo API Configuration."
+ },
+ "access_control": {
+ "$ref": "#/$defs/AccessControl",
+ "title": "Access Control",
+ "description": "Access Control Configuration."
+ },
+ "ntp": {
+ "$ref": "#/$defs/NTP",
+ "title": "NTP",
+ "description": "Network Time Protocol Configuration."
+ },
+ "regulation": {
+ "$ref": "#/$defs/Regulation",
+ "title": "Regulation",
+ "description": "Regulation Configuration."
+ },
+ "storage": {
+ "$ref": "#/$defs/Storage",
+ "title": "Storage",
+ "description": "Storage Configuration."
+ },
+ "notifier": {
+ "$ref": "#/$defs/Notifier",
+ "title": "Notifier",
+ "description": "Notifier Configuration."
+ },
+ "server": {
+ "$ref": "#/$defs/Server",
+ "title": "Server",
+ "description": "Server Configuration."
+ },
+ "telemetry": {
+ "$ref": "#/$defs/Telemetry",
+ "title": "Telemetry",
+ "description": "Telemetry Configuration."
+ },
+ "webauthn": {
+ "$ref": "#/$defs/WebAuthn",
+ "title": "WebAuthn",
+ "description": "WebAuthn Configuration."
+ },
+ "password_policy": {
+ "$ref": "#/$defs/PasswordPolicy",
+ "title": "Password Policy",
+ "description": "Password Policy Configuration."
+ },
+ "privacy_policy": {
+ "$ref": "#/$defs/PrivacyPolicy",
+ "title": "Privacy Policy",
+ "description": "Privacy Policy Configuration."
+ },
+ "identity_validation": {
+ "$ref": "#/$defs/IdentityValidation",
+ "title": "Identity Validation",
+ "description": "Identity Validation Configuration."
+ },
+ "definitions": {
+ "$ref": "#/$defs/Definitions",
+ "title": "Definitions",
+ "description": "Definitions for items reused elsewhere in the configuration."
+ },
+ "default_redirection_url": {
+ "type": "string",
+ "format": "uri",
+ "title": "The default redirection URL",
+ "description": "Deprecated: Use the session cookies option with the same name instead.",
+ "deprecated": true
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "Configuration object extracted from YAML configuration file."
+ },
+ "Definitions": {
+ "properties": {
+ "network": {
+ "patternProperties": {
+ ".*": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ }
+ ]
+ }
+ },
+ "type": "object",
+ "title": "Network Definitions",
+ "description": "Networks CIDR ranges that can be utilized elsewhere in the configuration."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object"
+ },
+ "DuoAPI": {
+ "properties": {
+ "disable": {
+ "type": "boolean",
+ "title": "Disable",
+ "description": "Disable the Duo API integration.",
+ "default": false
+ },
+ "hostname": {
+ "type": "string",
+ "format": "hostname",
+ "title": "Hostname",
+ "description": "The Hostname provided by your Duo API dashboard."
+ },
+ "integration_key": {
+ "type": "string",
+ "title": "Integration Key",
+ "description": "The Integration Key provided by your Duo API dashboard."
+ },
+ "secret_key": {
+ "type": "string",
+ "title": "Secret Key",
+ "description": "The Secret Key provided by your Duo API dashboard."
+ },
+ "enable_self_enrollment": {
+ "type": "boolean",
+ "title": "Enable Self Enrollment",
+ "description": "Enable the Self Enrollment flow.",
+ "default": false
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "DuoAPI represents the configuration related to Duo API."
+ },
+ "IdentityProviders": {
+ "properties": {
+ "oidc": {
+ "$ref": "#/$defs/IdentityProvidersOpenIDConnect"
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "IdentityProviders represents the Identity Providers configuration for Authelia."
+ },
+ "IdentityProvidersOpenIDConnect": {
+ "properties": {
+ "hmac_secret": {
+ "type": "string",
+ "title": "HMAC Secret",
+ "description": "The HMAC Secret used to sign Access Tokens."
+ },
+ "jwks": {
+ "items": {
+ "$ref": "#/$defs/JWK"
+ },
+ "type": "array",
+ "title": "Issuer JSON Web Keys",
+ "description": "The JWK's which are to be used to sign various objects like ID Tokens."
+ },
+ "enable_client_debug_messages": {
+ "type": "boolean",
+ "title": "Enable Client Debug Messages",
+ "description": "Enables additional debug messages for clients.",
+ "default": false
+ },
+ "minimum_parameter_entropy": {
+ "type": "integer",
+ "minimum": -1,
+ "title": "Minimum Parameter Entropy",
+ "description": "The minimum entropy of the nonce parameter.",
+ "default": 8
+ },
+ "enforce_pkce": {
+ "type": "string",
+ "enum": [
+ "public_clients_only",
+ "never",
+ "always"
+ ],
+ "title": "Enforce PKCE",
+ "description": "Controls enforcement of the use of Proof Key for Code Exchange on all clients.",
+ "default": "public_clients_only"
+ },
+ "enable_pkce_plain_challenge": {
+ "type": "boolean",
+ "title": "Enable PKCE Plain Challenge",
+ "description": "Enables use of the discouraged plain Proof Key for Code Exchange challenges.",
+ "default": false
+ },
+ "enable_jwt_access_token_stateless_introspection": {
+ "type": "boolean",
+ "title": "Enable JWT Access Token Stateless Introspection",
+ "description": "Allows the use of stateless introspection of JWT Access Tokens which is not recommended."
+ },
+ "discovery_signed_response_alg": {
+ "type": "string",
+ "enum": [
+ "none",
+ "RS256",
+ "RS384",
+ "RS512",
+ "ES256",
+ "ES384",
+ "ES512",
+ "PS256",
+ "PS384",
+ "PS512"
+ ],
+ "title": "Discovery Response Signing Algorithm",
+ "description": "The Algorithm this provider uses to sign the Discovery and Metadata Document responses.",
+ "default": "none"
+ },
+ "discovery_signed_response_key_id": {
+ "type": "string",
+ "title": "Discovery Response Signing Key ID",
+ "description": "The Key ID this provider uses to sign the Discovery and Metadata Document responses (overrides the 'discovery_signed_response_alg')."
+ },
+ "require_pushed_authorization_requests": {
+ "type": "boolean",
+ "title": "Require Pushed Authorization Requests",
+ "description": "Requires Pushed Authorization Requests for all clients for this Issuer."
+ },
+ "cors": {
+ "$ref": "#/$defs/IdentityProvidersOpenIDConnectCORS",
+ "title": "CORS",
+ "description": "Configuration options for Cross-Origin Request Sharing."
+ },
+ "clients": {
+ "items": {
+ "$ref": "#/$defs/IdentityProvidersOpenIDConnectClient"
+ },
+ "type": "array",
+ "title": "Clients",
+ "description": "OpenID Connect 1.0 clients registry."
+ },
+ "authorization_policies": {
+ "patternProperties": {
+ ".*": {
+ "$ref": "#/$defs/IdentityProvidersOpenIDConnectPolicy"
+ }
+ },
+ "type": "object",
+ "title": "Authorization Policies",
+ "description": "Custom client authorization policies."
+ },
+ "lifespans": {
+ "$ref": "#/$defs/IdentityProvidersOpenIDConnectLifespans",
+ "title": "Lifespans",
+ "description": "Token lifespans configuration."
+ },
+ "issuer_certificate_chain": {
+ "$ref": "#/$defs/X509CertificateChain",
+ "title": "Issuer Certificate Chain",
+ "description": "The Issuer Certificate Chain with an RSA Public Key used to sign ID Tokens.",
+ "deprecated": true
+ },
+ "issuer_private_key": {
+ "type": "string",
+ "pattern": "^-{5}(BEGIN (RSA )?PRIVATE KEY-{5}\\n([a-zA-Z0-9\\/+]{1,64}\\n)+([a-zA-Z0-9\\/+]{1,64}[=]{0,2})\\n-{5}END (RSA )?PRIVATE KEY-{5}\\n?)+$",
+ "title": "Issuer Private Key",
+ "description": "The Issuer Private Key with an RSA Private Key used to sign ID Tokens.",
+ "deprecated": true
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "IdentityProvidersOpenIDConnect represents the configuration for OpenID Connect 1.0."
+ },
+ "IdentityProvidersOpenIDConnectCORS": {
+ "properties": {
+ "endpoints": {
+ "items": {
+ "type": "string",
+ "enum": [
+ "authorization",
+ "pushed-authorization-request",
+ "token",
+ "introspection",
+ "revocation",
+ "userinfo"
+ ]
+ },
+ "type": "array",
+ "uniqueItems": true,
+ "title": "Endpoints",
+ "description": "List of endpoints to enable CORS handling for."
+ },
+ "allowed_origins": {
+ "items": {
+ "type": "string",
+ "format": "uri"
+ },
+ "type": "array",
+ "title": "Allowed Origins",
+ "description": "List of arbitrary allowed origins for CORS requests."
+ },
+ "allowed_origins_from_client_redirect_uris": {
+ "type": "boolean",
+ "title": "Allowed Origins From Client Redirect URIs",
+ "description": "Automatically include the redirect URIs from the registered clients.",
+ "default": false
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "IdentityProvidersOpenIDConnectCORS represents an OpenID Connect 1.0 CORS config."
+ },
+ "IdentityProvidersOpenIDConnectClient": {
+ "properties": {
+ "client_id": {
+ "type": "string",
+ "minLength": 1,
+ "title": "Client ID",
+ "description": "The Client ID."
+ },
+ "client_name": {
+ "type": "string",
+ "title": "Client Name",
+ "description": "The Client Name displayed to End-Users."
+ },
+ "client_secret": {
+ "$ref": "#/$defs/PasswordDigest",
+ "title": "Client Secret",
+ "description": "The Client Secret for Client Authentication."
+ },
+ "sector_identifier_uri": {
+ "type": "string",
+ "format": "uri",
+ "title": "Sector Identifier URI",
+ "description": "The Client Sector Identifier URI for Privacy Isolation via Pairwise subject types."
+ },
+ "public": {
+ "type": "boolean",
+ "title": "Public",
+ "description": "Enables the Public Client Type.",
+ "default": false
+ },
+ "redirect_uris": {
+ "$ref": "#/$defs/IdentityProvidersOpenIDConnectClientURIs",
+ "title": "Redirect URIs",
+ "description": "List of whitelisted redirect URIs."
+ },
+ "request_uris": {
+ "$ref": "#/$defs/IdentityProvidersOpenIDConnectClientURIs",
+ "title": "Request URIs",
+ "description": "List of whitelisted request URIs."
+ },
+ "audience": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "uniqueItems": true,
+ "title": "Audience",
+ "description": "List of authorized audiences."
+ },
+ "scopes": {
+ "items": {
+ "type": "string",
+ "enum": [
+ "openid",
+ "offline_access",
+ "groups",
+ "email",
+ "profile",
+ "authelia.bearer.authz"
+ ]
+ },
+ "type": "array",
+ "uniqueItems": true,
+ "title": "Scopes",
+ "description": "The Scopes this client is allowed request and be granted."
+ },
+ "grant_types": {
+ "items": {
+ "type": "string",
+ "enum": [
+ "authorization_code",
+ "implicit",
+ "refresh_token",
+ "client_credentials"
+ ]
+ },
+ "type": "array",
+ "uniqueItems": true,
+ "title": "Grant Types",
+ "description": "The Grant Types this client is allowed to use for the protected endpoints."
+ },
+ "response_types": {
+ "items": {
+ "type": "string",
+ "enum": [
+ "code",
+ "id_token token",
+ "id_token",
+ "token",
+ "code token",
+ "code id_token",
+ "code id_token token"
+ ]
+ },
+ "type": "array",
+ "uniqueItems": true,
+ "title": "Response Types",
+ "description": "The Response Types the client is authorized to request."
+ },
+ "response_modes": {
+ "items": {
+ "type": "string",
+ "enum": [
+ "form_post",
+ "form_post.jwt",
+ "query",
+ "query.jwt",
+ "fragment",
+ "fragment.jwt",
+ "jwt"
+ ]
+ },
+ "type": "array",
+ "uniqueItems": true,
+ "title": "Response Modes",
+ "description": "The Response Modes this client is authorized request."
+ },
+ "authorization_policy": {
+ "type": "string",
+ "title": "Authorization Policy",
+ "description": "The Authorization Policy to apply to this client."
+ },
+ "lifespan": {
+ "type": "string",
+ "title": "Lifespan Name",
+ "description": "The name of the custom lifespan to utilize for this client."
+ },
+ "requested_audience_mode": {
+ "type": "string",
+ "enum": [
+ "explicit",
+ "implicit"
+ ],
+ "title": "Requested Audience Mode",
+ "description": "The Requested Audience Mode used for this client."
+ },
+ "consent_mode": {
+ "type": "string",
+ "enum": [
+ "auto",
+ "explicit",
+ "implicit",
+ "pre-configured"
+ ],
+ "title": "Consent Mode",
+ "description": "The Consent Mode used for this client."
+ },
+ "pre_configured_consent_duration": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Pre-Configured Consent Duration",
+ "description": "The Pre-Configured Consent Duration when using Consent Mode pre-configured for this client."
+ },
+ "require_pushed_authorization_requests": {
+ "type": "boolean",
+ "title": "Require Pushed Authorization Requests",
+ "description": "Requires Pushed Authorization Requests for this client to perform an authorization.",
+ "default": false
+ },
+ "require_pkce": {
+ "type": "boolean",
+ "title": "Require PKCE",
+ "description": "Requires a Proof Key for this client to perform Code Exchange.",
+ "default": false
+ },
+ "pkce_challenge_method": {
+ "type": "string",
+ "enum": [
+ "plain",
+ "S256"
+ ],
+ "title": "PKCE Challenge Method",
+ "description": "The PKCE Challenge Method enforced on this client."
+ },
+ "authorization_signed_response_alg": {
+ "type": "string",
+ "enum": [
+ "none",
+ "RS256",
+ "RS384",
+ "RS512",
+ "ES256",
+ "ES384",
+ "ES512",
+ "PS256",
+ "PS384",
+ "PS512"
+ ],
+ "title": "Authorization Response Signing Algorithm",
+ "description": "The Authorization Endpoint Signing Algorithm this client uses.",
+ "default": "none"
+ },
+ "authorization_signed_response_key_id": {
+ "type": "string",
+ "title": "Authorization Response Signing Key ID",
+ "description": "The Key ID this client uses to sign the Authorization responses (overrides the 'authorization_signed_response_alg')."
+ },
+ "id_token_signed_response_alg": {
+ "type": "string",
+ "enum": [
+ "RS256",
+ "RS384",
+ "RS512",
+ "ES256",
+ "ES384",
+ "ES512",
+ "PS256",
+ "PS384",
+ "PS512"
+ ],
+ "title": "ID Token Signing Algorithm",
+ "description": "The algorithm (JWA) this client uses to sign ID Tokens.",
+ "default": "RS256"
+ },
+ "id_token_signed_response_key_id": {
+ "type": "string",
+ "title": "ID Token Signing Key ID",
+ "description": "The Key ID this client uses to sign ID Tokens (overrides the 'id_token_signing_alg')."
+ },
+ "access_token_signed_response_alg": {
+ "type": "string",
+ "enum": [
+ "none",
+ "RS256",
+ "RS384",
+ "RS512",
+ "ES256",
+ "ES384",
+ "ES512",
+ "PS256",
+ "PS384",
+ "PS512"
+ ],
+ "title": "Access Token Signing Algorithm",
+ "description": "The algorithm (JWA) this client uses to sign Access Tokens.",
+ "default": "none"
+ },
+ "access_token_signed_response_key_id": {
+ "type": "string",
+ "title": "Access Token Signing Key ID",
+ "description": "The Key ID this client uses to sign Access Tokens (overrides the 'access_token_signed_response_alg')."
+ },
+ "userinfo_signed_response_alg": {
+ "type": "string",
+ "enum": [
+ "none",
+ "RS256",
+ "RS384",
+ "RS512",
+ "ES256",
+ "ES384",
+ "ES512",
+ "PS256",
+ "PS384",
+ "PS512"
+ ],
+ "title": "UserInfo Response Signing Algorithm",
+ "description": "The UserInfo Endpoint Signing Algorithm this client uses.",
+ "default": "none"
+ },
+ "userinfo_signed_response_key_id": {
+ "type": "string",
+ "title": "UserInfo Response Signing Key ID",
+ "description": "The Key ID this client uses to sign the UserInfo responses (overrides the 'userinfo_signed_response_alg')."
+ },
+ "introspection_signed_response_alg": {
+ "type": "string",
+ "enum": [
+ "none",
+ "RS256",
+ "RS384",
+ "RS512",
+ "ES256",
+ "ES384",
+ "ES512",
+ "PS256",
+ "PS384",
+ "PS512"
+ ],
+ "title": "Introspection Response Signing Algorithm",
+ "description": "The Introspection Endpoint Signing Algorithm this client uses.",
+ "default": "none"
+ },
+ "introspection_signed_response_key_id": {
+ "type": "string",
+ "title": "Introspection Response Signing Key ID",
+ "description": "The Key ID this client uses to sign the Introspection responses (overrides the 'introspection_signed_response_alg')."
+ },
+ "request_object_signing_alg": {
+ "type": "string",
+ "enum": [
+ "RS256",
+ "RS384",
+ "RS512",
+ "ES256",
+ "ES384",
+ "ES512",
+ "PS256",
+ "PS384",
+ "PS512"
+ ],
+ "title": "Request Object Signing Algorithm",
+ "description": "The Request Object Signing Algorithm the provider accepts for this client."
+ },
+ "token_endpoint_auth_signing_alg": {
+ "type": "string",
+ "enum": [
+ "HS256",
+ "HS384",
+ "HS512",
+ "RS256",
+ "RS384",
+ "RS512",
+ "ES256",
+ "ES384",
+ "ES512",
+ "PS256",
+ "PS384",
+ "PS512"
+ ],
+ "title": "Token Endpoint Auth Signing Algorithm",
+ "description": "The Token Endpoint Auth Signing Algorithm the provider accepts for this client."
+ },
+ "token_endpoint_auth_method": {
+ "type": "string",
+ "enum": [
+ "none",
+ "client_secret_post",
+ "client_secret_basic",
+ "private_key_jwt",
+ "client_secret_jwt"
+ ],
+ "title": "Token Endpoint Auth Method",
+ "description": "The Token Endpoint Auth Method enforced by the provider for this client."
+ },
+ "allow_multiple_auth_methods": {
+ "type": "boolean",
+ "title": "Allow Multiple Authentication Methods",
+ "description": "Permits this registered client to accept misbehaving clients which use a broad authentication approach. This is not standards complaint, use at your own security risk."
+ },
+ "jwks_uri": {
+ "type": "string",
+ "format": "uri",
+ "title": "JSON Web Keys URI",
+ "description": "URI of the JWKS endpoint which contains the Public Keys used to validate request objects and the 'private_key_jwt' client authentication method for this client."
+ },
+ "jwks": {
+ "items": {
+ "$ref": "#/$defs/JWK"
+ },
+ "type": "array",
+ "title": "JSON Web Keys",
+ "description": "List of arbitrary Public Keys used to validate request objects and the 'private_key_jwt' client authentication method for this client."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "required": [
+ "client_id",
+ "scopes"
+ ],
+ "description": "IdentityProvidersOpenIDConnectClient represents a configuration for an OpenID Connect 1.0 client."
+ },
+ "IdentityProvidersOpenIDConnectClientURIs": {
+ "oneOf": [
+ {
+ "type": "string",
+ "format": "uri"
+ },
+ {
+ "items": {
+ "type": "string",
+ "format": "uri"
+ },
+ "type": "array",
+ "uniqueItems": true
+ }
+ ]
+ },
+ "IdentityProvidersOpenIDConnectLifespan": {
+ "properties": {
+ "access_token": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Access Token Lifespan",
+ "description": "The duration an Access Token is valid for."
+ },
+ "authorize_code": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Authorize Code Lifespan",
+ "description": "The duration an Authorization Code is valid for."
+ },
+ "id_token": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "ID Token Lifespan",
+ "description": "The duration an ID Token is valid for."
+ },
+ "refresh_token": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Refresh Token Lifespan",
+ "description": "The duration a Refresh Token is valid for."
+ },
+ "grants": {
+ "$ref": "#/$defs/IdentityProvidersOpenIDConnectLifespanGrants",
+ "title": "Grant Types",
+ "description": "Allows tuning the token lifespans for individual grant types."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "IdentityProvidersOpenIDConnectLifespan allows tuning the lifespans for OpenID Connect 1.0 issued tokens."
+ },
+ "IdentityProvidersOpenIDConnectLifespanGrants": {
+ "properties": {
+ "authorize_code": {
+ "$ref": "#/$defs/IdentityProvidersOpenIDConnectLifespanToken",
+ "title": "Authorize Code Grant",
+ "description": "Allows tuning the token lifespans for the authorize code grant."
+ },
+ "implicit": {
+ "$ref": "#/$defs/IdentityProvidersOpenIDConnectLifespanToken",
+ "title": "Implicit Grant",
+ "description": "Allows tuning the token lifespans for the implicit flow and grant."
+ },
+ "client_credentials": {
+ "$ref": "#/$defs/IdentityProvidersOpenIDConnectLifespanToken",
+ "title": "Client Credentials Grant",
+ "description": "Allows tuning the token lifespans for the client credentials grant."
+ },
+ "refresh_token": {
+ "$ref": "#/$defs/IdentityProvidersOpenIDConnectLifespanToken",
+ "title": "Refresh Token Grant",
+ "description": "Allows tuning the token lifespans for the refresh token grant."
+ },
+ "jwt_bearer": {
+ "$ref": "#/$defs/IdentityProvidersOpenIDConnectLifespanToken",
+ "title": "JWT Bearer Grant",
+ "description": "Allows tuning the token lifespans for the JWT bearer grant."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "IdentityProvidersOpenIDConnectLifespanGrants allows tuning the lifespans for each grant type."
+ },
+ "IdentityProvidersOpenIDConnectLifespanToken": {
+ "properties": {
+ "access_token": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Access Token Lifespan",
+ "description": "The duration an Access Token is valid for."
+ },
+ "authorize_code": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Authorize Code Lifespan",
+ "description": "The duration an Authorization Code is valid for."
+ },
+ "id_token": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "ID Token Lifespan",
+ "description": "The duration an ID Token is valid for."
+ },
+ "refresh_token": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Refresh Token Lifespan",
+ "description": "The duration a Refresh Token is valid for."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "IdentityProvidersOpenIDConnectLifespanToken allows tuning the lifespans for each token type."
+ },
+ "IdentityProvidersOpenIDConnectLifespans": {
+ "properties": {
+ "access_token": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Access Token Lifespan",
+ "description": "The duration an Access Token is valid for."
+ },
+ "authorize_code": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Authorize Code Lifespan",
+ "description": "The duration an Authorization Code is valid for."
+ },
+ "id_token": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "ID Token Lifespan",
+ "description": "The duration an ID Token is valid for."
+ },
+ "refresh_token": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Refresh Token Lifespan",
+ "description": "The duration a Refresh Token is valid for."
+ },
+ "jwt_secured_authorization": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "JARM",
+ "description": "Allows tuning the token lifespan for the JWT Secured Authorization Response Mode (JARM)."
+ },
+ "custom": {
+ "patternProperties": {
+ ".*": {
+ "$ref": "#/$defs/IdentityProvidersOpenIDConnectLifespan"
+ }
+ },
+ "type": "object",
+ "title": "Custom Lifespans",
+ "description": "Allows creating custom lifespans to be used by individual clients."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object"
+ },
+ "IdentityProvidersOpenIDConnectPolicy": {
+ "properties": {
+ "default_policy": {
+ "type": "string",
+ "enum": [
+ "one_factor",
+ "two_factor",
+ "deny"
+ ],
+ "title": "Default Policy",
+ "description": "The default policy action for this policy."
+ },
+ "rules": {
+ "items": {
+ "$ref": "#/$defs/IdentityProvidersOpenIDConnectPolicyRule"
+ },
+ "type": "array",
+ "title": "Rules",
+ "description": "The list of rules for this policy."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "IdentityProvidersOpenIDConnectPolicy configuration for OpenID Connect 1.0 authorization policies."
+ },
+ "IdentityProvidersOpenIDConnectPolicyRule": {
+ "properties": {
+ "policy": {
+ "type": "string",
+ "enum": [
+ "one_factor",
+ "two_factor",
+ "deny"
+ ],
+ "title": "Policy",
+ "description": "The policy to apply to this rule."
+ },
+ "subject": {
+ "$ref": "#/$defs/AccessControlRuleSubjects",
+ "title": "Subject",
+ "description": "Allows tuning the token lifespans for the authorize code grant."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "IdentityProvidersOpenIDConnectPolicyRule configuration for OpenID Connect 1.0 authorization policies rules."
+ },
+ "IdentityValidation": {
+ "properties": {
+ "reset_password": {
+ "$ref": "#/$defs/IdentityValidationResetPassword",
+ "title": "Reset Password",
+ "description": "Identity Validation options for the Reset Password flow."
+ },
+ "elevated_session": {
+ "$ref": "#/$defs/IdentityValidationElevatedSession",
+ "title": "Elevated Session",
+ "description": "Identity Validation options for obtaining an Elevated Session for flows such as the Credential Management flows."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "IdentityValidation represents the configuration for identity verification actions/flows."
+ },
+ "IdentityValidationElevatedSession": {
+ "properties": {
+ "code_lifespan": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Code Lifespan",
+ "description": "The lifespan of the randomly generated One Time Code after which it's considered invalid."
+ },
+ "elevation_lifespan": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Elevation Lifespan",
+ "description": "The lifespan of the elevation after initially validating the One-Time Code before it expires."
+ },
+ "otp_characters": {
+ "type": "integer",
+ "maximum": 12,
+ "minimum": 6,
+ "title": "OTP Characters",
+ "description": "Number of characters in the generated OTP codes.",
+ "default": 8
+ },
+ "require_second_factor": {
+ "type": "boolean",
+ "title": "Require Second Factor",
+ "description": "Requires the user use a second factor if they have any known second factor methods.",
+ "default": false
+ },
+ "skip_second_factor": {
+ "type": "boolean",
+ "title": "Skip Second Factor",
+ "description": "Skips the primary identity verification process if the user has authenticated with a second factor.",
+ "default": false
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "IdentityValidationElevatedSession represents the tunable aspects of the credential control identity verification action/flow."
+ },
+ "IdentityValidationResetPassword": {
+ "properties": {
+ "jwt_lifespan": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "JWT Lifespan",
+ "description": "The lifespan of the JSON Web Token after it's initially generated after which it's considered invalid."
+ },
+ "jwt_algorithm": {
+ "type": "string",
+ "enum": [
+ "HS256",
+ "HS384",
+ "HS512"
+ ],
+ "title": "JWT Algorithm",
+ "description": "The JSON Web Token Algorithm (JWA) used to sign the Reset Password flow JSON Web Token's.",
+ "default": "HS256"
+ },
+ "jwt_secret": {
+ "type": "string",
+ "title": "JWT Secret",
+ "description": "The secret key used to sign the Reset Password flow JSON Web Token's."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "IdentityValidationResetPassword represents the tunable aspects of the reset password identity verification action/flow."
+ },
+ "JWK": {
+ "properties": {
+ "key_id": {
+ "type": "string",
+ "maxLength": 100,
+ "title": "Key ID",
+ "description": "The ID of this JWK."
+ },
+ "use": {
+ "type": "string",
+ "enum": [
+ "sig"
+ ],
+ "title": "Use",
+ "description": "The Use of this JWK.",
+ "default": "sig"
+ },
+ "algorithm": {
+ "type": "string",
+ "enum": [
+ "HS256",
+ "HS384",
+ "HS512",
+ "RS256",
+ "RS384",
+ "RS512",
+ "ES256",
+ "ES384",
+ "ES512",
+ "PS256",
+ "PS384",
+ "PS512"
+ ],
+ "title": "Algorithm",
+ "description": "The Algorithm of this JWK."
+ },
+ "key": {
+ "type": "string",
+ "pattern": "^-{5}BEGIN (((RSA|EC) )?(PRIVATE|PUBLIC) KEY|CERTIFICATE)-{5}\\n([a-zA-Z0-9\\/+]{1,64}\\n)+([a-zA-Z0-9\\/+]{1,64}[=]{0,2})\\n-{5}END (((RSA|EC) )?(PRIVATE|PUBLIC) KEY|CERTIFICATE)-{5}\\n?$",
+ "description": "The Private/Public key material of this JWK in Base64 PEM format."
+ },
+ "certificate_chain": {
+ "$ref": "#/$defs/X509CertificateChain",
+ "title": "Certificate Chain",
+ "description": "The optional associated certificate which matches the Key public key portion for this JWK."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "JWK represents a JWK."
+ },
+ "Log": {
+ "properties": {
+ "level": {
+ "type": "string",
+ "enum": [
+ "error",
+ "warn",
+ "info",
+ "debug",
+ "trace"
+ ],
+ "title": "Level",
+ "description": "The minimum Level a Log message must be before it's added to the log."
+ },
+ "format": {
+ "type": "string",
+ "enum": [
+ "json",
+ "text"
+ ],
+ "title": "Format",
+ "description": "The Format of Log messages."
+ },
+ "file_path": {
+ "type": "string",
+ "title": "File Path",
+ "description": "The File Path to save the logs to instead of sending them to stdout, it's strongly recommended this option is only enabled with 'keep_stdout' also enabled."
+ },
+ "keep_stdout": {
+ "type": "boolean",
+ "title": "Keep Stdout",
+ "description": "Enables keeping stdout when using the File Path option.",
+ "default": false
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "Log represents the logging configuration."
+ },
+ "NTP": {
+ "properties": {
+ "address": {
+ "$ref": "#/$defs/AddressUDP",
+ "title": "NTP Address",
+ "description": "The remote address of the NTP server."
+ },
+ "version": {
+ "type": "integer",
+ "enum": [
+ 3,
+ 4
+ ],
+ "title": "NTP Version",
+ "description": "The NTP Version to use."
+ },
+ "max_desync": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Maximum Desync",
+ "description": "The maximum amount of time that the server can be out of sync."
+ },
+ "disable_startup_check": {
+ "type": "boolean",
+ "title": "Disable Startup Check",
+ "description": "Disables the NTP Startup Check entirely.",
+ "default": false
+ },
+ "disable_failure": {
+ "type": "boolean",
+ "title": "Disable Failure",
+ "description": "Disables complete failure whe the Startup Check fails and instead just logs the error.",
+ "default": false
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "NTP represents the configuration related to ntp server."
+ },
+ "Notifier": {
+ "properties": {
+ "disable_startup_check": {
+ "type": "boolean",
+ "title": "Disable Startup Check",
+ "description": "Disables the notifier startup checks.",
+ "default": false
+ },
+ "filesystem": {
+ "$ref": "#/$defs/NotifierFileSystem",
+ "title": "File System",
+ "description": "The File System notifier."
+ },
+ "smtp": {
+ "$ref": "#/$defs/NotifierSMTP",
+ "title": "SMTP",
+ "description": "The SMTP notifier."
+ },
+ "template_path": {
+ "type": "string",
+ "title": "Template Path",
+ "description": "The path for notifier template overrides."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "Notifier represents the configuration of the notifier to use when sending notifications to users."
+ },
+ "NotifierFileSystem": {
+ "properties": {
+ "filename": {
+ "type": "string",
+ "title": "Filename",
+ "description": "The file path of the notifications."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "NotifierFileSystem represents the configuration of the notifier writing emails in a file."
+ },
+ "NotifierSMTP": {
+ "properties": {
+ "address": {
+ "$ref": "#/$defs/AddressSMTP",
+ "title": "Address",
+ "description": "The SMTP server address."
+ },
+ "timeout": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Timeout",
+ "description": "The SMTP server connection timeout."
+ },
+ "username": {
+ "type": "string",
+ "title": "Username",
+ "description": "The username for SMTP authentication."
+ },
+ "password": {
+ "type": "string",
+ "title": "Password",
+ "description": "The password for SMTP authentication."
+ },
+ "identifier": {
+ "type": "string",
+ "title": "Identifier",
+ "description": "The identifier used during the HELO/EHLO command.",
+ "default": "localhost"
+ },
+ "sender": {
+ "oneOf": [
+ {
+ "type": "string",
+ "format": "email"
+ },
+ {
+ "type": "string",
+ "pattern": "^[^\u003c]+\\s\\\u003c[a-zA-Z0-9._~!#$%\u0026'*/=?^{|}+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z0-9-]+\\\u003e$"
+ }
+ ],
+ "title": "Sender",
+ "description": "The sender used for SMTP."
+ },
+ "subject": {
+ "type": "string",
+ "title": "Subject",
+ "description": "The subject format used.",
+ "default": "[Authelia] {title}"
+ },
+ "startup_check_address": {
+ "oneOf": [
+ {
+ "type": "string",
+ "format": "email"
+ },
+ {
+ "type": "string",
+ "pattern": "^[^\u003c]+\\s\\\u003c[a-zA-Z0-9._~!#$%\u0026'*/=?^{|}+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z0-9-]+\\\u003e$"
+ }
+ ],
+ "title": "Startup Check Address",
+ "description": "The address used for the recipient in the startup check."
+ },
+ "disable_require_tls": {
+ "type": "boolean",
+ "title": "Disable Require TLS",
+ "description": "Disables the requirement to use TLS.",
+ "default": false
+ },
+ "disable_html_emails": {
+ "type": "boolean",
+ "title": "Disable HTML Emails",
+ "description": "Disables the mixed content type of emails and only sends the plaintext version.",
+ "default": false
+ },
+ "disable_starttls": {
+ "type": "boolean",
+ "title": "Disable StartTLS",
+ "description": "Disables the opportunistic StartTLS functionality which is useful for bad SMTP servers which advertise support for it but don't actually support it.",
+ "default": false
+ },
+ "tls": {
+ "$ref": "#/$defs/TLS",
+ "title": "TLS",
+ "description": "The SMTP server TLS connection properties."
+ },
+ "host": {
+ "type": "string",
+ "description": "Deprecated: use address instead.",
+ "deprecated": true
+ },
+ "port": {
+ "type": "integer",
+ "description": "Deprecated: use address instead.",
+ "deprecated": true
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "NotifierSMTP represents the configuration of the SMTP server to send emails with."
+ },
+ "PasswordDigest": {
+ "type": "string",
+ "pattern": "^\\$((argon2(id|i|d)\\$v=19\\$m=\\d+,t=\\d+,p=\\d+|scrypt\\$ln=\\d+,r=\\d+,p=\\d+)\\$[a-zA-Z0-9\\/+]+\\$[a-zA-Z0-9\\/+]+|pbkdf2(-sha(224|256|384|512))?\\$\\d+\\$[a-zA-Z0-9\\/.]+\\$[a-zA-Z0-9\\/.]+|bcrypt-sha256\\$v=2,t=2b,r=\\d+\\$[a-zA-Z0-9\\/.]+\\$[a-zA-Z0-9\\/.]+|2(a|b|y)?\\$\\d+\\$[a-zA-Z0-9.\\/]+|(5|6)\\$rounds=\\d+\\$[a-zA-Z0-9.\\/]+\\$[a-zA-Z0-9.\\/]+|plaintext\\$.+|base64\\$[a-zA-Z0-9.=\\/]+)$"
+ },
+ "PasswordPolicy": {
+ "properties": {
+ "standard": {
+ "$ref": "#/$defs/PasswordPolicyStandard",
+ "title": "Standard",
+ "description": "The standard password policy engine."
+ },
+ "zxcvbn": {
+ "$ref": "#/$defs/PasswordPolicyZXCVBN",
+ "title": "ZXCVBN",
+ "description": "The ZXCVBN password policy engine."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "PasswordPolicy represents the configuration related to password policy."
+ },
+ "PasswordPolicyStandard": {
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "title": "Enabled",
+ "description": "Enables the standard password policy engine.",
+ "default": false
+ },
+ "min_length": {
+ "type": "integer",
+ "title": "Minimum Length",
+ "description": "Minimum password length."
+ },
+ "max_length": {
+ "type": "integer",
+ "title": "Maximum Length",
+ "description": "Maximum password length.",
+ "default": 8
+ },
+ "require_uppercase": {
+ "type": "boolean",
+ "title": "Require Uppercase",
+ "description": "Require uppercase characters.",
+ "default": false
+ },
+ "require_lowercase": {
+ "type": "boolean",
+ "title": "Require Lowercase",
+ "description": "Require lowercase characters.",
+ "default": false
+ },
+ "require_number": {
+ "type": "boolean",
+ "title": "Require Number",
+ "description": "Require numeric characters.",
+ "default": false
+ },
+ "require_special": {
+ "type": "boolean",
+ "title": "Require Special",
+ "description": "Require symbolic characters.",
+ "default": false
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "PasswordPolicyStandard represents the configuration related to standard parameters of password policy."
+ },
+ "PasswordPolicyZXCVBN": {
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "title": "Enabled",
+ "description": "Enables the ZXCVBN password policy engine.",
+ "default": false
+ },
+ "min_score": {
+ "type": "integer",
+ "title": "Minimum Score",
+ "description": "The minimum ZXCVBN score allowed.",
+ "default": 3
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "PasswordPolicyZXCVBN represents the configuration related to ZXCVBN parameters of password policy."
+ },
+ "PrivacyPolicy": {
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "title": "Enabled",
+ "description": "Enables the Privacy Policy functionality.",
+ "default": false
+ },
+ "require_user_acceptance": {
+ "type": "boolean",
+ "title": "Require User Acceptance",
+ "description": "Enables the requirement for users to accept the policy.",
+ "default": false
+ },
+ "policy_url": {
+ "type": "string",
+ "format": "uri",
+ "title": "Policy URL",
+ "description": "The URL of the privacy policy."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "PrivacyPolicy is the privacy policy configuration."
+ },
+ "RefreshIntervalDuration": {
+ "oneOf": [
+ {
+ "type": "string",
+ "enum": [
+ "always",
+ "never"
+ ]
+ },
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "default": "5 minutes"
+ },
+ "Regulation": {
+ "properties": {
+ "max_retries": {
+ "type": "integer",
+ "title": "Maximum Retries",
+ "description": "The maximum number of failed attempts permitted before banning a user.",
+ "default": 3
+ },
+ "find_time": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Find Time",
+ "description": "The amount of time to consider when determining the number of failed attempts."
+ },
+ "ban_time": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Ban Time",
+ "description": "The amount of time to ban the user for when it's determined the maximum retries has been exceeded."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "Regulation represents the configuration related to regulation."
+ },
+ "Server": {
+ "properties": {
+ "address": {
+ "$ref": "#/$defs/AddressTCP",
+ "title": "Address",
+ "description": "The address to listen on."
+ },
+ "asset_path": {
+ "type": "string",
+ "title": "Asset Path",
+ "description": "The directory where the server asset overrides reside."
+ },
+ "disable_healthcheck": {
+ "type": "boolean",
+ "title": "Disable Healthcheck",
+ "description": "Disables the healthcheck functionality.",
+ "default": false
+ },
+ "tls": {
+ "$ref": "#/$defs/ServerTLS",
+ "title": "TLS",
+ "description": "The server TLS configuration."
+ },
+ "headers": {
+ "$ref": "#/$defs/ServerHeaders",
+ "title": "Headers",
+ "description": "The server headers configuration."
+ },
+ "endpoints": {
+ "$ref": "#/$defs/ServerEndpoints",
+ "title": "Endpoints",
+ "description": "The server endpoints configuration."
+ },
+ "buffers": {
+ "$ref": "#/$defs/ServerBuffers",
+ "title": "Buffers",
+ "description": "The server buffers configuration."
+ },
+ "timeouts": {
+ "$ref": "#/$defs/ServerTimeouts",
+ "title": "Timeouts",
+ "description": "The server timeouts configuration."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "Server represents the configuration of the http server."
+ },
+ "ServerBuffers": {
+ "properties": {
+ "read": {
+ "type": "integer",
+ "title": "Read",
+ "description": "The read buffer size.",
+ "default": 4096
+ },
+ "write": {
+ "type": "integer",
+ "title": "Write",
+ "description": "The write buffer size.",
+ "default": 4096
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "ServerBuffers represents server buffer configurations."
+ },
+ "ServerEndpoints": {
+ "properties": {
+ "enable_pprof": {
+ "type": "boolean",
+ "title": "Enable PProf",
+ "description": "Enables the developer specific pprof endpoints which should not be used in production and only used for debugging purposes.",
+ "default": false
+ },
+ "enable_expvars": {
+ "type": "boolean",
+ "title": "Enable ExpVars",
+ "description": "Enables the developer specific ExpVars endpoints which should not be used in production and only used for debugging purposes.",
+ "default": false
+ },
+ "authz": {
+ "patternProperties": {
+ ".*": {
+ "$ref": "#/$defs/ServerEndpointsAuthz"
+ }
+ },
+ "type": "object",
+ "title": "Authz",
+ "description": "Configures the Authorization endpoints."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "ServerEndpoints is the endpoints configuration for the HTTP server."
+ },
+ "ServerEndpointsAuthz": {
+ "properties": {
+ "implementation": {
+ "type": "string",
+ "enum": [
+ "ForwardAuth",
+ "AuthRequest",
+ "ExtAuthz",
+ "Legacy"
+ ],
+ "title": "Implementation",
+ "description": "The specific Authorization implementation to use for this endpoint."
+ },
+ "authn_strategies": {
+ "items": {
+ "$ref": "#/$defs/ServerEndpointsAuthzAuthnStrategy"
+ },
+ "type": "array",
+ "title": "Authn Strategies",
+ "description": "The specific Authorization strategies to use for this endpoint."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "ServerEndpointsAuthz is the Authz endpoints configuration for the HTTP server."
+ },
+ "ServerEndpointsAuthzAuthnStrategy": {
+ "properties": {
+ "name": {
+ "type": "string",
+ "enum": [
+ "HeaderAuthorization",
+ "HeaderProxyAuthorization",
+ "HeaderAuthRequestProxyAuthorization",
+ "HeaderLegacy",
+ "CookieSession"
+ ],
+ "title": "Name",
+ "description": "The name of the Authorization strategy to use."
+ },
+ "schemes": {
+ "items": {
+ "type": "string",
+ "enum": [
+ "basic",
+ "bearer"
+ ]
+ },
+ "type": "array",
+ "title": "Authorization Schemes",
+ "description": "The name of the authorization schemes to allow with the header strategies.",
+ "default": [
+ "basic"
+ ]
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "ServerEndpointsAuthzAuthnStrategy is the Authz endpoints configuration for the HTTP server."
+ },
+ "ServerHeaders": {
+ "properties": {
+ "csp_template": {
+ "type": "string",
+ "title": "CSP Template",
+ "description": "The Content Security Policy template.",
+ "default": "default-src 'self'; frame-src 'none'; object-src 'none'; style-src 'self' 'nonce-%s'; frame-ancestors 'none'; base-uri 'self'"
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "ServerHeaders represents the customization of the http server headers."
+ },
+ "ServerTLS": {
+ "properties": {
+ "certificate": {
+ "type": "string",
+ "title": "Certificate",
+ "description": "Path to the Certificate."
+ },
+ "key": {
+ "type": "string",
+ "title": "Key",
+ "description": "Path to the Private Key."
+ },
+ "client_certificates": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "uniqueItems": true,
+ "title": "Client Certificates",
+ "description": "Path to the Client Certificates to trust for mTLS."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "ServerTLS represents the configuration of the http servers TLS options."
+ },
+ "ServerTimeouts": {
+ "properties": {
+ "read": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Read",
+ "description": "The read timeout."
+ },
+ "write": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Write",
+ "description": "The write timeout."
+ },
+ "idle": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Idle",
+ "description": "The idle timeout."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "ServerTimeouts represents server timeout configurations."
+ },
+ "Session": {
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "The session cookie name.",
+ "default": "authelia_session"
+ },
+ "same_site": {
+ "type": "string",
+ "enum": [
+ "lax",
+ "strict",
+ "none"
+ ],
+ "description": "The session cookie same site value.",
+ "default": "lax"
+ },
+ "expiration": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "description": "The session cookie expiration when remember me is not checked."
+ },
+ "inactivity": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "description": "The session inactivity timeout."
+ },
+ "remember_me": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "description": "The session cookie expiration when remember me is checked."
+ },
+ "secret": {
+ "type": "string",
+ "title": "Secret",
+ "description": "Secret used to encrypt the session data."
+ },
+ "cookies": {
+ "items": {
+ "$ref": "#/$defs/SessionCookie"
+ },
+ "type": "array",
+ "title": "Cookies",
+ "description": "List of cookie domain configurations."
+ },
+ "redis": {
+ "$ref": "#/$defs/SessionRedis",
+ "title": "Redis",
+ "description": "Redis Session Provider configuration."
+ },
+ "domain": {
+ "type": "string",
+ "title": "Domain",
+ "description": "Deprecated: Use the session cookies option with the same name instead.",
+ "deprecated": true
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "Session represents the configuration related to user sessions."
+ },
+ "SessionCookie": {
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "The session cookie name.",
+ "default": "authelia_session"
+ },
+ "same_site": {
+ "type": "string",
+ "enum": [
+ "lax",
+ "strict",
+ "none"
+ ],
+ "description": "The session cookie same site value.",
+ "default": "lax"
+ },
+ "expiration": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "description": "The session cookie expiration when remember me is not checked."
+ },
+ "inactivity": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "description": "The session inactivity timeout."
+ },
+ "remember_me": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "description": "The session cookie expiration when remember me is checked."
+ },
+ "domain": {
+ "type": "string",
+ "format": "hostname",
+ "title": "Domain",
+ "description": "The domain for this session cookie configuration."
+ },
+ "authelia_url": {
+ "type": "string",
+ "format": "uri",
+ "title": "Authelia URL",
+ "description": "The Root Authelia URL to redirect users to for this session cookie configuration."
+ },
+ "default_redirection_url": {
+ "type": "string",
+ "format": "uri",
+ "title": "Default Redirection URL",
+ "description": "The default redirection URL for this session cookie configuration."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "SessionCookie represents the configuration for a cookie domain."
+ },
+ "SessionRedis": {
+ "properties": {
+ "host": {
+ "type": "string",
+ "title": "Host",
+ "description": "The redis server host."
+ },
+ "port": {
+ "type": "integer",
+ "title": "Host",
+ "description": "The redis server port.",
+ "default": 6379
+ },
+ "timeout": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Timeout",
+ "description": "The Redis server connection timeout."
+ },
+ "max_retries": {
+ "type": "integer",
+ "title": "Maximum Retries",
+ "description": "The maximum number of retries on a failed command.",
+ "default": 3
+ },
+ "username": {
+ "type": "string",
+ "title": "Username",
+ "description": "The redis username."
+ },
+ "password": {
+ "type": "string",
+ "title": "Password",
+ "description": "The redis password."
+ },
+ "database_index": {
+ "type": "integer",
+ "title": "Database Index",
+ "description": "The redis database index.",
+ "default": 0
+ },
+ "maximum_active_connections": {
+ "type": "integer",
+ "title": "Maximum Active Connections",
+ "description": "The maximum connections that can be made to redis at one time.",
+ "default": 8
+ },
+ "minimum_idle_connections": {
+ "type": "integer",
+ "title": "Minimum Idle Connections",
+ "description": "The minimum idle connections that should be open to redis."
+ },
+ "tls": {
+ "$ref": "#/$defs/TLS"
+ },
+ "high_availability": {
+ "$ref": "#/$defs/SessionRedisHighAvailability"
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "SessionRedis represents the configuration related to redis session store."
+ },
+ "SessionRedisHighAvailability": {
+ "properties": {
+ "sentinel_name": {
+ "type": "string",
+ "title": "Sentinel Name",
+ "description": "The name of the sentinel instance."
+ },
+ "sentinel_username": {
+ "type": "string",
+ "title": "Sentinel Username",
+ "description": "The username for the sentinel instance."
+ },
+ "sentinel_password": {
+ "type": "string",
+ "title": "Sentinel Username",
+ "description": "The username for the sentinel instance."
+ },
+ "route_by_latency": {
+ "type": "boolean",
+ "title": "Route by Latency",
+ "description": "Uses the Route by Latency mode.",
+ "default": false
+ },
+ "route_randomly": {
+ "type": "boolean",
+ "title": "Route Randomly",
+ "description": "Uses the Route Randomly mode.",
+ "default": false
+ },
+ "nodes": {
+ "items": {
+ "$ref": "#/$defs/SessionRedisHighAvailabilityNode"
+ },
+ "type": "array",
+ "title": "Nodes",
+ "description": "The pre-populated list of nodes for the sentinel instance."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "SessionRedisHighAvailability holds configuration variables for Redis Cluster/Sentinel."
+ },
+ "SessionRedisHighAvailabilityNode": {
+ "properties": {
+ "host": {
+ "type": "string",
+ "title": "Host",
+ "description": "The redis sentinel node host."
+ },
+ "port": {
+ "type": "integer",
+ "title": "Port",
+ "description": "The redis sentinel node port.",
+ "default": 26379
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "SessionRedisHighAvailabilityNode Represents a Node."
+ },
+ "Storage": {
+ "properties": {
+ "local": {
+ "$ref": "#/$defs/StorageLocal",
+ "title": "Local",
+ "description": "The Local SQLite3 Storage configuration settings."
+ },
+ "mysql": {
+ "$ref": "#/$defs/StorageMySQL",
+ "title": "MySQL",
+ "description": "The MySQL/MariaDB Storage configuration settings."
+ },
+ "postgres": {
+ "$ref": "#/$defs/StoragePostgreSQL",
+ "title": "PostgreSQL",
+ "description": "The PostgreSQL Storage configuration settings."
+ },
+ "encryption_key": {
+ "type": "string",
+ "title": "Encryption Key",
+ "description": "The Storage Encryption Key used to secure security sensitive values in the storage engine."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "Storage represents the configuration of the storage backend."
+ },
+ "StorageLocal": {
+ "properties": {
+ "path": {
+ "type": "string",
+ "title": "Path",
+ "description": "The Path for the SQLite3 database file."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "StorageLocal represents the configuration when using local storage."
+ },
+ "StorageMySQL": {
+ "properties": {
+ "address": {
+ "$ref": "#/$defs/AddressTCP",
+ "title": "Address",
+ "description": "The address of the database."
+ },
+ "database": {
+ "type": "string",
+ "title": "Database",
+ "description": "The database name to use upon a successful connection."
+ },
+ "username": {
+ "type": "string",
+ "title": "Username",
+ "description": "The username to use to authenticate."
+ },
+ "password": {
+ "type": "string",
+ "title": "Password",
+ "description": "The password to use to authenticate."
+ },
+ "timeout": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Timeout",
+ "description": "The timeout for the database connection."
+ },
+ "tls": {
+ "$ref": "#/$defs/TLS"
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "StorageMySQL represents the configuration of a MySQL database."
+ },
+ "StoragePostgreSQL": {
+ "properties": {
+ "address": {
+ "$ref": "#/$defs/AddressTCP",
+ "title": "Address",
+ "description": "The address of the database."
+ },
+ "database": {
+ "type": "string",
+ "title": "Database",
+ "description": "The database name to use upon a successful connection."
+ },
+ "username": {
+ "type": "string",
+ "title": "Username",
+ "description": "The username to use to authenticate."
+ },
+ "password": {
+ "type": "string",
+ "title": "Password",
+ "description": "The password to use to authenticate."
+ },
+ "timeout": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Timeout",
+ "description": "The timeout for the database connection."
+ },
+ "schema": {
+ "type": "string",
+ "title": "Schema",
+ "description": "The default schema name to use.",
+ "default": "public"
+ },
+ "tls": {
+ "$ref": "#/$defs/TLS"
+ },
+ "ssl": {
+ "$ref": "#/$defs/StoragePostgreSQLSSL",
+ "title": "SSL",
+ "description": "Deprecated: Use the TLS configuration instead.",
+ "deprecated": true
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "StoragePostgreSQL represents the configuration of a PostgreSQL database."
+ },
+ "StoragePostgreSQLSSL": {
+ "properties": {
+ "mode": {
+ "type": "string",
+ "enum": [
+ "disable",
+ "verify-ca",
+ "require",
+ "verify-full"
+ ],
+ "title": "Mode",
+ "description": "The SSL mode to use, deprecated and replaced with the TLS options.",
+ "deprecated": true
+ },
+ "root_certificate": {
+ "type": "string",
+ "title": "Root Certificate",
+ "description": "Path to the Root Certificate to use, deprecated and replaced with the TLS options.",
+ "deprecated": true
+ },
+ "certificate": {
+ "type": "string",
+ "title": "Certificate",
+ "description": "Path to the Certificate to use, deprecated and replaced with the TLS options.",
+ "deprecated": true
+ },
+ "key": {
+ "type": "string",
+ "title": "Key",
+ "description": "Path to the Private Key to use, deprecated and replaced with the TLS options.",
+ "deprecated": true
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "StoragePostgreSQLSSL represents the SSL configuration of a PostgreSQL database."
+ },
+ "TLS": {
+ "properties": {
+ "minimum_version": {
+ "$ref": "#/$defs/TLSVersion",
+ "title": "Minimum Version",
+ "description": "The minimum TLS version accepted."
+ },
+ "maximum_version": {
+ "$ref": "#/$defs/TLSVersion",
+ "title": "Maximum Version",
+ "description": "The maximum TLS version accepted."
+ },
+ "skip_verify": {
+ "type": "boolean",
+ "title": "Skip Verify",
+ "description": "Disable all verification of the TLS properties.",
+ "default": false
+ },
+ "server_name": {
+ "type": "string",
+ "format": "hostname",
+ "title": "Server Name",
+ "description": "The expected server name to match the certificate against."
+ },
+ "private_key": {
+ "type": "string",
+ "pattern": "^-{5}BEGIN ((RSA|EC) )?PRIVATE KEY-{5}\\n([a-zA-Z0-9\\/+]{1,64}\\n)+([a-zA-Z0-9\\/+]{1,64}[=]{0,2})\\n-{5}END ((RSA|EC) )?PRIVATE KEY-{5}\\n?$",
+ "title": "Private Key",
+ "description": "The private key."
+ },
+ "certificate_chain": {
+ "$ref": "#/$defs/X509CertificateChain",
+ "title": "Certificate Chain",
+ "description": "The certificate chain."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "TLS is a representation of the TLS configuration."
+ },
+ "TLSVersion": {
+ "type": "string",
+ "enum": [
+ "TLS1.0",
+ "TLS1.1",
+ "TLS1.2",
+ "TLS1.3"
+ ]
+ },
+ "TOTP": {
+ "properties": {
+ "disable": {
+ "type": "boolean",
+ "title": "Disable",
+ "description": "Disables the TOTP 2FA functionality.",
+ "default": false
+ },
+ "issuer": {
+ "type": "string",
+ "title": "Issuer",
+ "description": "The issuer value for generated TOTP keys.",
+ "default": "Authelia"
+ },
+ "algorithm": {
+ "type": "string",
+ "enum": [
+ "SHA1",
+ "SHA256",
+ "SHA512"
+ ],
+ "title": "Algorithm",
+ "description": "The algorithm value for generated TOTP keys.",
+ "default": "SHA1"
+ },
+ "digits": {
+ "type": "integer",
+ "enum": [
+ 6,
+ 8
+ ],
+ "title": "Digits",
+ "description": "The digits value for generated TOTP keys.",
+ "default": 6
+ },
+ "period": {
+ "type": "integer",
+ "title": "Period",
+ "description": "The period value for generated TOTP keys.",
+ "default": 30
+ },
+ "skew": {
+ "type": "integer",
+ "title": "Skew",
+ "description": "The permitted skew for generated TOTP keys.",
+ "default": 1
+ },
+ "secret_size": {
+ "type": "integer",
+ "minimum": 20,
+ "title": "Secret Size",
+ "description": "The secret size for generated TOTP keys.",
+ "default": 32
+ },
+ "allowed_algorithms": {
+ "items": {
+ "type": "string",
+ "enum": [
+ "SHA1",
+ "SHA256",
+ "SHA512"
+ ]
+ },
+ "type": "array",
+ "title": "Allowed Algorithms",
+ "description": "List of algorithms the user is allowed to select in addition to the default.",
+ "default": [
+ "SHA1"
+ ]
+ },
+ "allowed_digits": {
+ "items": {
+ "type": "integer",
+ "enum": [
+ 6,
+ 8
+ ]
+ },
+ "type": "array",
+ "title": "Allowed Digits",
+ "description": "List of digits the user is allowed to select in addition to the default.",
+ "default": [
+ 6
+ ]
+ },
+ "allowed_periods": {
+ "items": {
+ "type": "integer"
+ },
+ "type": "array",
+ "title": "Allowed Periods",
+ "description": "List of periods the user is allowed to select in addition to the default.",
+ "default": [
+ 30
+ ]
+ },
+ "disable_reuse_security_policy": {
+ "type": "boolean",
+ "title": "Disable Reuse Security Policy",
+ "description": "Disables the security policy that prevents reuse of a TOTP code.",
+ "default": false
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "TOTP represents the configuration related to TOTP options."
+ },
+ "Telemetry": {
+ "properties": {
+ "metrics": {
+ "$ref": "#/$defs/TelemetryMetrics",
+ "title": "Metrics",
+ "description": "The telemetry metrics server configuration."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "Telemetry represents the telemetry config."
+ },
+ "TelemetryMetrics": {
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "title": "Enabled",
+ "description": "Enables the metrics server.",
+ "default": false
+ },
+ "address": {
+ "$ref": "#/$defs/AddressTCP",
+ "title": "Address",
+ "description": "The address for the metrics server to listen on."
+ },
+ "buffers": {
+ "$ref": "#/$defs/ServerBuffers",
+ "title": "Buffers",
+ "description": "The server buffers configuration for the metrics server."
+ },
+ "timeouts": {
+ "$ref": "#/$defs/ServerTimeouts",
+ "title": "Timeouts",
+ "description": "The server timeouts configuration for the metrics server."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "TelemetryMetrics represents the telemetry metrics config."
+ },
+ "WebAuthn": {
+ "properties": {
+ "disable": {
+ "type": "boolean",
+ "title": "Disable",
+ "description": "Disables the WebAuthn 2FA functionality.",
+ "default": false
+ },
+ "display_name": {
+ "type": "string",
+ "title": "Display Name",
+ "description": "The display name attribute for the WebAuthn relying party.",
+ "default": "Authelia"
+ },
+ "attestation_conveyance_preference": {
+ "type": "string",
+ "enum": [
+ "none",
+ "indirect",
+ "direct"
+ ],
+ "title": "Conveyance Preference",
+ "description": "The default conveyance preference for all WebAuthn credentials.",
+ "default": "indirect"
+ },
+ "user_verification": {
+ "type": "string",
+ "enum": [
+ "discouraged",
+ "preferred",
+ "required"
+ ],
+ "title": "User Verification",
+ "description": "The default user verification preference for all WebAuthn credentials.",
+ "default": "preferred"
+ },
+ "timeout": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$"
+ },
+ {
+ "type": "integer",
+ "description": "The duration in seconds"
+ }
+ ],
+ "title": "Timeout",
+ "description": "The default timeout for all WebAuthn ceremonies."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "description": "WebAuthn represents the webauthn config."
+ },
+ "X509CertificateChain": {
+ "type": "string",
+ "pattern": "^(-{5}BEGIN CERTIFICATE-{5}\\n([a-zA-Z0-9\\/+]{1,64}\\n)+([a-zA-Z0-9\\/+]{1,64}[=]{0,2})\\n-{5}END CERTIFICATE-{5}\\n?)+$"
+ }
+ }
+} \ No newline at end of file
diff --git a/docs/static/schemas/v4.39/json-schema/exports.identifiers.json b/docs/static/schemas/v4.39/json-schema/exports.identifiers.json
new file mode 100644
index 000000000..8c434245f
--- /dev/null
+++ b/docs/static/schemas/v4.39/json-schema/exports.identifiers.json
@@ -0,0 +1,58 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://www.authelia.com/schemas/v4.39/json-schema/exports.identifiers.json",
+ "$ref": "#/$defs/UserOpaqueIdentifiersExport",
+ "$defs": {
+ "UUID": {
+ "items": {
+ "type": "integer"
+ },
+ "type": "array",
+ "maxItems": 16,
+ "minItems": 16
+ },
+ "UserOpaqueIdentifier": {
+ "properties": {
+ "ID": {
+ "type": "integer"
+ },
+ "service": {
+ "type": "string",
+ "title": "Service",
+ "description": "The service name this UUID is used with."
+ },
+ "sector_id": {
+ "type": "string",
+ "title": "Sector Identifier",
+ "description": "Sector Identifier this UUID is used with."
+ },
+ "username": {
+ "type": "string",
+ "title": "Username",
+ "description": "The username of the user this UUID is for."
+ },
+ "identifier": {
+ "$ref": "#/$defs/UUID",
+ "title": "Identifier",
+ "description": "The random UUID for this opaque identifier."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object"
+ },
+ "UserOpaqueIdentifiersExport": {
+ "properties": {
+ "identifiers": {
+ "items": {
+ "$ref": "#/$defs/UserOpaqueIdentifier"
+ },
+ "type": "array",
+ "title": "Identifiers",
+ "description": "The list of opaque identifiers."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object"
+ }
+ }
+} \ No newline at end of file
diff --git a/docs/static/schemas/v4.39/json-schema/exports.totp.json b/docs/static/schemas/v4.39/json-schema/exports.totp.json
new file mode 100644
index 000000000..f7c111073
--- /dev/null
+++ b/docs/static/schemas/v4.39/json-schema/exports.totp.json
@@ -0,0 +1,69 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://www.authelia.com/schemas/v4.39/json-schema/exports.totp.json",
+ "$ref": "#/$defs/TOTPConfigurationDataExport",
+ "$defs": {
+ "TOTPConfigurationData": {
+ "properties": {
+ "created_at": {
+ "type": "string",
+ "format": "date-time",
+ "title": "Created At",
+ "description": "The time the configuration was created."
+ },
+ "last_used_at": {
+ "type": "string",
+ "format": "date-time",
+ "title": "Last Used At",
+ "description": "The time the configuration was last used at."
+ },
+ "username": {
+ "type": "string",
+ "title": "Username",
+ "description": "The username of the user this configuration belongs to."
+ },
+ "issuer": {
+ "type": "string",
+ "title": "Issuer",
+ "description": "The issuer name this was generated with."
+ },
+ "algorithm": {
+ "type": "string",
+ "title": "Algorithm",
+ "description": "The algorithm this configuration uses."
+ },
+ "digits": {
+ "type": "integer",
+ "title": "Digits",
+ "description": "The number of digits this configuration uses."
+ },
+ "period": {
+ "type": "integer",
+ "title": "Period",
+ "description": "The period of time this configuration uses."
+ },
+ "secret": {
+ "type": "string",
+ "title": "Secret",
+ "description": "The secret shared key for this configuration."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object"
+ },
+ "TOTPConfigurationDataExport": {
+ "properties": {
+ "totp_configurations": {
+ "items": {
+ "$ref": "#/$defs/TOTPConfigurationData"
+ },
+ "type": "array",
+ "title": "TOTP Configurations",
+ "description": "The list of TOTP configurations."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object"
+ }
+ }
+} \ No newline at end of file
diff --git a/docs/static/schemas/v4.39/json-schema/exports.webauthn.json b/docs/static/schemas/v4.39/json-schema/exports.webauthn.json
new file mode 100644
index 000000000..e2f233eab
--- /dev/null
+++ b/docs/static/schemas/v4.39/json-schema/exports.webauthn.json
@@ -0,0 +1,130 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://www.authelia.com/schemas/v4.39/json-schema/exports.webauthn.json",
+ "$ref": "#/$defs/WebAuthnCredentialDataExport",
+ "$defs": {
+ "WebAuthnCredentialData": {
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time",
+ "title": "Created At",
+ "description": "The time this credential was created."
+ },
+ "last_used_at": {
+ "type": "string",
+ "format": "date-time",
+ "title": "Last Used At",
+ "description": "The last time this credential was used."
+ },
+ "rpid": {
+ "type": "string",
+ "title": "Relying Party ID",
+ "description": "The Relying Party ID used to register this credential."
+ },
+ "username": {
+ "type": "string",
+ "title": "Username",
+ "description": "The username of the user this credential belongs to."
+ },
+ "description": {
+ "type": "string",
+ "title": "Description",
+ "description": "The user description of this credential."
+ },
+ "kid": {
+ "type": "string",
+ "title": "Public Key ID",
+ "description": "The Public Key ID of this credential."
+ },
+ "aaguid": {
+ "type": "string",
+ "title": "AAGUID",
+ "description": "The Authenticator Attestation Global Unique Identifier of this credential."
+ },
+ "attestation_type": {
+ "type": "string",
+ "title": "Attestation Type",
+ "description": "The attestation format type this credential uses."
+ },
+ "attachment": {
+ "type": "string",
+ "title": "Attachment",
+ "description": "The last recorded credential attachment type."
+ },
+ "transports": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "Transports",
+ "description": "The last recorded credential transports."
+ },
+ "sign_count": {
+ "type": "integer",
+ "title": "Sign Count",
+ "description": "The last recorded credential sign count."
+ },
+ "clone_warning": {
+ "type": "boolean",
+ "title": "Clone Warning",
+ "description": "The clone warning status of the credential."
+ },
+ "legacy": {
+ "type": "boolean",
+ "title": "Legacy",
+ "description": "The legacy value indicates this credential may need to be registered again."
+ },
+ "discoverable": {
+ "type": "boolean",
+ "title": "Discoverable",
+ "description": "The discoverable status of this credential."
+ },
+ "present": {
+ "type": "boolean",
+ "title": "Present",
+ "description": "The user presence status of this credential."
+ },
+ "verified": {
+ "type": "boolean",
+ "title": "Verified",
+ "description": "The verified status of this credential."
+ },
+ "backup_eligible": {
+ "type": "boolean",
+ "title": "Backup Eligible",
+ "description": "The backup eligible status of this credential."
+ },
+ "backup_state": {
+ "type": "boolean",
+ "title": "Backup Eligible",
+ "description": "The backup eligible status of this credential."
+ },
+ "public_key": {
+ "type": "string",
+ "title": "Public Key",
+ "description": "The credential public key."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object"
+ },
+ "WebAuthnCredentialDataExport": {
+ "properties": {
+ "webauthn_credentials": {
+ "items": {
+ "$ref": "#/$defs/WebAuthnCredentialData"
+ },
+ "type": "array",
+ "title": "WebAuthn Credentials",
+ "description": "The list of WebAuthn credentials."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object"
+ }
+ }
+} \ No newline at end of file
diff --git a/docs/static/schemas/v4.39/json-schema/user-database.json b/docs/static/schemas/v4.39/json-schema/user-database.json
new file mode 100644
index 000000000..2ee85aab0
--- /dev/null
+++ b/docs/static/schemas/v4.39/json-schema/user-database.json
@@ -0,0 +1,71 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://www.authelia.com/schemas/v4.39/json-schema/user-database.json",
+ "$ref": "#/$defs/FileUserDatabase",
+ "$defs": {
+ "FileUserDatabase": {
+ "properties": {
+ "users": {
+ "patternProperties": {
+ ".*": {
+ "$ref": "#/$defs/FileUserDatabaseUserDetails"
+ }
+ },
+ "type": "object",
+ "title": "Users",
+ "description": "The dictionary of users."
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "required": [
+ "users"
+ ],
+ "description": "FileUserDatabase is a user details database that is concurrency safe database and can be reloaded."
+ },
+ "FileUserDatabaseUserDetails": {
+ "properties": {
+ "password": {
+ "$ref": "#/$defs/PasswordDigest",
+ "title": "Password",
+ "description": "The hashed password for the user."
+ },
+ "displayname": {
+ "type": "string",
+ "title": "Display Name",
+ "description": "The display name for the user."
+ },
+ "email": {
+ "type": "string",
+ "title": "Email",
+ "description": "The email for the user."
+ },
+ "groups": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "Groups",
+ "description": "The groups list for the user."
+ },
+ "disabled": {
+ "type": "boolean",
+ "title": "Disabled",
+ "description": "The disabled status for the user.",
+ "default": false
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "required": [
+ "password",
+ "displayname"
+ ],
+ "description": "FileUserDatabaseUserDetails is the model of user details in the file database."
+ },
+ "PasswordDigest": {
+ "type": "string",
+ "pattern": "^\\$((argon2(id|i|d)\\$v=19\\$m=\\d+,t=\\d+,p=\\d+|scrypt\\$ln=\\d+,r=\\d+,p=\\d+)\\$[a-zA-Z0-9\\/+]+\\$[a-zA-Z0-9\\/+]+|pbkdf2(-sha(224|256|384|512))?\\$\\d+\\$[a-zA-Z0-9\\/.]+\\$[a-zA-Z0-9\\/.]+|bcrypt-sha256\\$v=2,t=2b,r=\\d+\\$[a-zA-Z0-9\\/.]+\\$[a-zA-Z0-9\\/.]+|2(a|b|y)?\\$\\d+\\$[a-zA-Z0-9.\\/]+|(5|6)\\$rounds=\\d+\\$[a-zA-Z0-9.\\/]+\\$[a-zA-Z0-9.\\/]+|plaintext\\$.+|base64\\$[a-zA-Z0-9.=\\/]+)$"
+ }
+ }
+} \ No newline at end of file
diff --git a/internal/authorization/access_control_rule.go b/internal/authorization/access_control_rule.go
index fa2953ca6..9c862b425 100644
--- a/internal/authorization/access_control_rule.go
+++ b/internal/authorization/access_control_rule.go
@@ -1,30 +1,26 @@
package authorization
import (
- "net"
-
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/utils"
)
// NewAccessControlRules converts a schema.AccessControl into an AccessControlRule slice.
func NewAccessControlRules(config schema.AccessControl) (rules []*AccessControlRule) {
- networksMap, networksCacheMap := parseSchemaNetworks(config.Networks)
-
for i, schemaRule := range config.Rules {
- rules = append(rules, NewAccessControlRule(i+1, schemaRule, networksMap, networksCacheMap))
+ rules = append(rules, NewAccessControlRule(i+1, schemaRule))
}
return rules
}
// NewAccessControlRule parses a schema ACL and generates an internal ACL.
-func NewAccessControlRule(pos int, rule schema.AccessControlRule, networksMap map[string][]*net.IPNet, networksCacheMap map[string]*net.IPNet) *AccessControlRule {
+func NewAccessControlRule(pos int, rule schema.AccessControlRule) *AccessControlRule {
r := &AccessControlRule{
Position: pos,
Query: NewAccessControlQuery(rule.Query),
Methods: schemaMethodsToACL(rule.Methods),
- Networks: schemaNetworksToACL(rule.Networks, networksMap, networksCacheMap),
+ Networks: AccessControlNetworks(rule.Networks),
Subjects: schemaSubjectsToACL(rule.Subjects),
Policy: NewLevel(rule.Policy),
}
@@ -49,7 +45,7 @@ type AccessControlRule struct {
Resources []AccessControlResource
Query []AccessControlQuery
Methods []string
- Networks []*net.IPNet
+ Networks AccessControlNetworks
Subjects []AccessControlSubjects
Policy Level
}
@@ -146,19 +142,7 @@ func (acr *AccessControlRule) MatchesMethods(object Object) (match bool) {
// MatchesNetworks returns true if the rule matches the networks.
func (acr *AccessControlRule) MatchesNetworks(subject Subject) (match bool) {
- // If there are no networks in this rule then the network condition is a match.
- if len(acr.Networks) == 0 {
- return true
- }
-
- // Iterate over the networks until we find a match (return true) or until we exit the loop (return false).
- for _, network := range acr.Networks {
- if network.Contains(subject.IP) {
- return true
- }
- }
-
- return false
+ return acr.Networks.IsMatch(subject)
}
// MatchesSubjects returns true if the rule matches the subjects.
diff --git a/internal/authorization/authorizer_test.go b/internal/authorization/authorizer_test.go
index 679b4e78f..d6bffe1bd 100644
--- a/internal/authorization/authorizer_test.go
+++ b/internal/authorization/authorizer_test.go
@@ -12,6 +12,7 @@ import (
"github.com/valyala/fasthttp"
"github.com/authelia/authelia/v4/internal/configuration/schema"
+ "github.com/authelia/authelia/v4/internal/utils"
)
type AuthorizerSuite struct {
@@ -680,32 +681,46 @@ func (s *AuthorizerSuite) TestShouldCheckMultipleSubjectsMatching() {
}
func (s *AuthorizerSuite) TestShouldCheckIPMatching() {
+ must := func(in []string) []*net.IPNet {
+ out := make([]*net.IPNet, len(in))
+
+ var err error
+
+ for i := range in {
+ if out[i], err = utils.ParseHostCIDR(in[i]); err != nil {
+ panic(err)
+ }
+ }
+
+ return out
+ }
+
tester := NewAuthorizerBuilder().
WithDefaultPolicy(deny).
WithRule(schema.AccessControlRule{
Domains: []string{"protected.example.com"},
Policy: bypass,
- Networks: []string{"192.168.1.8", "10.0.0.8"},
+ Networks: must([]string{"192.168.1.8", "10.0.0.8"}),
}).
WithRule(schema.AccessControlRule{
Domains: []string{"protected.example.com"},
Policy: oneFactor,
- Networks: []string{"10.0.0.7"},
+ Networks: must([]string{"10.0.0.7"}),
}).
WithRule(schema.AccessControlRule{
Domains: []string{"net.example.com"},
Policy: twoFactor,
- Networks: []string{"10.0.0.0/8"},
+ Networks: must([]string{"10.0.0.0/8"}),
}).
WithRule(schema.AccessControlRule{
Domains: []string{"ipv6.example.com"},
Policy: twoFactor,
- Networks: []string{"fec0::1/64"},
+ Networks: must([]string{"fec0::1/64"}),
}).
WithRule(schema.AccessControlRule{
Domains: []string{"ipv6-alt.example.com"},
Policy: twoFactor,
- Networks: []string{"fec0::1"},
+ Networks: must([]string{"fec0::1"}),
}).
Build()
diff --git a/internal/authorization/types.go b/internal/authorization/types.go
index ec517ccff..9f3f1d918 100644
--- a/internal/authorization/types.go
+++ b/internal/authorization/types.go
@@ -9,6 +9,22 @@ import (
"github.com/authelia/authelia/v4/internal/utils"
)
+type AccessControlNetworks []*net.IPNet
+
+func (a AccessControlNetworks) IsMatch(subject Subject) bool {
+ if len(a) == 0 {
+ return true
+ }
+
+ for _, network := range a {
+ if network.Contains(subject.IP) {
+ return true
+ }
+ }
+
+ return false
+}
+
// SubjectMatcher is a matcher that takes a subject.
type SubjectMatcher interface {
IsMatch(subject Subject) (match bool)
diff --git a/internal/authorization/util.go b/internal/authorization/util.go
index 7d3621b88..cccdd2454 100644
--- a/internal/authorization/util.go
+++ b/internal/authorization/util.go
@@ -1,7 +1,6 @@
package authorization
import (
- "net"
"regexp"
"strings"
@@ -121,74 +120,6 @@ func schemaMethodsToACL(methodRules []string) (methods []string) {
return methods
}
-func schemaNetworksToACL(networkRules []string, networksMap map[string][]*net.IPNet, networksCacheMap map[string]*net.IPNet) (networks []*net.IPNet) {
- for _, network := range networkRules {
- if _, ok := networksMap[network]; !ok {
- if _, ok := networksCacheMap[network]; ok {
- networks = append(networks, networksCacheMap[network])
- } else {
- cidr, err := parseNetwork(network)
- if err == nil {
- networks = append(networks, cidr)
- networksCacheMap[cidr.String()] = cidr
-
- if cidr.String() != network {
- networksCacheMap[network] = cidr
- }
- }
- }
- } else {
- networks = append(networks, networksMap[network]...)
- }
- }
-
- return networks
-}
-
-func parseSchemaNetworks(schemaNetworks []schema.AccessControlNetwork) (networksMap map[string][]*net.IPNet, networksCacheMap map[string]*net.IPNet) {
- // These maps store pointers to the net.IPNet values so we can reuse them efficiently.
- // The networksMap contains the named networks as keys, the networksCacheMap contains the CIDR notations as keys.
- networksMap = map[string][]*net.IPNet{}
- networksCacheMap = map[string]*net.IPNet{}
-
- for _, aclNetwork := range schemaNetworks {
- var networks []*net.IPNet
-
- for _, networkRule := range aclNetwork.Networks {
- cidr, err := parseNetwork(networkRule)
- if err == nil {
- networks = append(networks, cidr)
- networksCacheMap[cidr.String()] = cidr
-
- if cidr.String() != networkRule {
- networksCacheMap[networkRule] = cidr
- }
- }
- }
-
- if _, ok := networksMap[aclNetwork.Name]; len(networks) != 0 && !ok {
- networksMap[aclNetwork.Name] = networks
- }
- }
-
- return networksMap, networksCacheMap
-}
-
-func parseNetwork(networkRule string) (cidr *net.IPNet, err error) {
- if !strings.Contains(networkRule, "/") {
- ip := net.ParseIP(networkRule)
- if ip.To4() != nil {
- _, cidr, err = net.ParseCIDR(networkRule + "/32")
- } else {
- _, cidr, err = net.ParseCIDR(networkRule + "/128")
- }
- } else {
- _, cidr, err = net.ParseCIDR(networkRule)
- }
-
- return cidr, err
-}
-
func schemaSubjectsToACL(subjectRules [][]string) (subjects []AccessControlSubjects) {
for _, subjectRule := range subjectRules {
subject := AccessControlSubjects{}
diff --git a/internal/authorization/util_test.go b/internal/authorization/util_test.go
index 811509b30..8b241084f 100644
--- a/internal/authorization/util_test.go
+++ b/internal/authorization/util_test.go
@@ -59,151 +59,6 @@ func TestShouldSplitDomainCorrectly(t *testing.T) {
assert.Equal(t, "com", suffix)
}
-func TestShouldParseRuleNetworks(t *testing.T) {
- schemaNetworks := []schema.AccessControlNetwork{
- {
- Name: "desktop",
- Networks: []string{
- "10.0.0.1",
- },
- },
- {
- Name: "lan",
- Networks: []string{
- "10.0.0.0/8",
- "172.16.0.0/12",
- "192.168.0.0/16",
- },
- },
- }
-
- _, firstNetwork, err := net.ParseCIDR("192.168.1.20/32")
- require.NoError(t, err)
-
- networksMap, networksCacheMap := parseSchemaNetworks(schemaNetworks)
-
- assert.Len(t, networksCacheMap, 5)
-
- networks := []string{"192.168.1.20", "lan"}
-
- acl := schemaNetworksToACL(networks, networksMap, networksCacheMap)
-
- assert.Len(t, networksCacheMap, 7)
-
- require.Len(t, acl, 4)
- assert.Equal(t, firstNetwork, acl[0])
- assert.Equal(t, networksMap["lan"][0], acl[1])
- assert.Equal(t, networksMap["lan"][1], acl[2])
- assert.Equal(t, networksMap["lan"][2], acl[3])
-
- // Check they are the same memory address.
- assert.True(t, networksMap["lan"][0] == acl[1])
- assert.True(t, networksMap["lan"][1] == acl[2])
- assert.True(t, networksMap["lan"][2] == acl[3])
-
- assert.False(t, firstNetwork == acl[0])
-}
-
-func TestShouldParseACLNetworks(t *testing.T) {
- schemaNetworks := []schema.AccessControlNetwork{
- {
- Name: "test",
- Networks: []string{
- "10.0.0.1",
- },
- },
- {
- Name: "second",
- Networks: []string{
- "10.0.0.1",
- },
- },
- {
- Name: "duplicate",
- Networks: []string{
- "10.0.0.1",
- },
- },
- {
- Name: "duplicate",
- Networks: []string{
- "10.0.0.1",
- },
- },
- {
- Name: "ipv6",
- Networks: []string{
- "fec0::1",
- },
- },
- {
- Name: "ipv6net",
- Networks: []string{
- "fec0::1/64",
- },
- },
- {
- Name: "net",
- Networks: []string{
- "10.0.0.0/8",
- },
- },
- {
- Name: "badnet",
- Networks: []string{
- "bad/8",
- },
- },
- }
-
- _, firstNetwork, err := net.ParseCIDR("10.0.0.1/32")
- require.NoError(t, err)
-
- _, secondNetwork, err := net.ParseCIDR("10.0.0.0/8")
- require.NoError(t, err)
-
- _, thirdNetwork, err := net.ParseCIDR("fec0::1/64")
- require.NoError(t, err)
-
- _, fourthNetwork, err := net.ParseCIDR("fec0::1/128")
- require.NoError(t, err)
-
- networksMap, networksCacheMap := parseSchemaNetworks(schemaNetworks)
-
- require.Len(t, networksMap, 6)
- require.Contains(t, networksMap, "test")
- require.Contains(t, networksMap, "second")
- require.Contains(t, networksMap, "duplicate")
- require.Contains(t, networksMap, "ipv6")
- require.Contains(t, networksMap, "ipv6net")
- require.Contains(t, networksMap, "net")
- require.Len(t, networksMap["test"], 1)
-
- require.Len(t, networksCacheMap, 7)
- require.Contains(t, networksCacheMap, "10.0.0.1")
- require.Contains(t, networksCacheMap, "10.0.0.1/32")
- require.Contains(t, networksCacheMap, "10.0.0.1/32")
- require.Contains(t, networksCacheMap, "10.0.0.0/8")
- require.Contains(t, networksCacheMap, "fec0::1")
- require.Contains(t, networksCacheMap, "fec0::1/128")
- require.Contains(t, networksCacheMap, "fec0::1/64")
-
- assert.Equal(t, firstNetwork, networksMap["test"][0])
- assert.Equal(t, secondNetwork, networksMap["net"][0])
- assert.Equal(t, thirdNetwork, networksMap["ipv6net"][0])
- assert.Equal(t, fourthNetwork, networksMap["ipv6"][0])
-
- assert.Equal(t, firstNetwork, networksCacheMap["10.0.0.1"])
- assert.Equal(t, firstNetwork, networksCacheMap["10.0.0.1/32"])
-
- assert.Equal(t, secondNetwork, networksCacheMap["10.0.0.0/8"])
-
- assert.Equal(t, thirdNetwork, networksCacheMap["fec0::1/64"])
-
- assert.Equal(t, fourthNetwork, networksCacheMap["fec0::1"])
- assert.Equal(t, fourthNetwork, networksCacheMap["fec0::1/128"])
-}
-
func TestIsAuthLevelSufficient(t *testing.T) {
assert.False(t, IsAuthLevelSufficient(authentication.NotAuthenticated, Denied))
assert.False(t, IsAuthLevelSufficient(authentication.OneFactor, Denied))
@@ -249,40 +104,6 @@ func TestStringSliceToRegexpSlice(t *testing.T) {
}
}
-func TestSchemaNetworksToACL(t *testing.T) {
- testCases := []struct {
- name string
- have []string
- globals map[string][]*net.IPNet
- cache map[string]*net.IPNet
- expected []*net.IPNet
- }{
- {
- "ShouldLoadFromCache",
- []string{"192.168.0.0/24"},
- nil,
- map[string]*net.IPNet{"192.168.0.0/24": MustParseCIDR("192.168.0.0/24")},
- []*net.IPNet{MustParseCIDR("192.168.0.0/24")},
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- if tc.globals == nil {
- tc.globals = map[string][]*net.IPNet{}
- }
-
- if tc.cache == nil {
- tc.cache = map[string]*net.IPNet{}
- }
-
- actual := schemaNetworksToACL(tc.have, tc.globals, tc.cache)
-
- assert.Equal(t, tc.expected, actual)
- })
- }
-}
-
func TestIsOpenIDConnectMFA(t *testing.T) {
testCases := []struct {
name string
diff --git a/internal/commands/context.go b/internal/commands/context.go
index ec1c58d67..c36a17996 100644
--- a/internal/commands/context.go
+++ b/internal/commands/context.go
@@ -397,7 +397,8 @@ func (ctx *CmdCtx) ConfigEnsureExistsRunE(cmd *cobra.Command, _ []string) (err e
// HelperConfigLoadRunE loads the configuration into the CmdCtx.
func (ctx *CmdCtx) HelperConfigLoadRunE(cmd *cobra.Command, _ []string) (err error) {
var (
- filters []configuration.BytesFilter
+ definitions *schema.Definitions
+ filters []configuration.BytesFilter
)
if ctx.cconfig == nil {
@@ -426,10 +427,15 @@ func (ctx *CmdCtx) HelperConfigLoadRunE(cmd *cobra.Command, _ []string) (err err
ctx.cconfig.defaults,
ctx.cconfig.sources...)
+ if definitions, err = configuration.LoadDefinitions(ctx.cconfig.validator, ctx.cconfig.sources...); err != nil {
+ return err
+ }
+
if ctx.cconfig.keys, err = configuration.LoadAdvanced(
ctx.cconfig.validator,
"",
ctx.config,
+ definitions,
ctx.cconfig.sources...); err != nil {
return err
}
diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml
index c6231783d..7ee0e16eb 100644
--- a/internal/configuration/config.template.yml
+++ b/internal/configuration/config.template.yml
@@ -323,6 +323,22 @@ identity_validation:
# disable_failure: false
##
+## Definitions
+##
+## The definitions are used in other areas as reference points to reduce duplication.
+##
+# definitions:
+ ## The network definitions.
+ # network:
+ ## The name of the definition followed by the list of CIDR network addresses in this definition.
+ # internal:
+ # - '10.10.0.0/16'
+ # - '172.16.0.0/12'
+ # - '192.168.2.0/24'
+ # VPN:
+ # - '10.9.0.0/16'
+
+##
## Authentication Backend Provider Configuration
##
## Used for verifying user passwords and retrieve information such as email address and groups users belong to.
@@ -622,14 +638,6 @@ identity_validation:
## resource if there is no policy to be applied to the user.
# default_policy: 'deny'
- # networks:
- # - name: 'internal'
- # networks:
- # - '10.10.0.0/16'
- # - '192.168.2.0/24'
- # - name: 'VPN'
- # networks: '10.9.0.0/16'
-
# rules:
## Rules applied to everyone
# - domain: 'public.example.com'
diff --git a/internal/configuration/decode_hooks.go b/internal/configuration/decode_hooks.go
index 02610567e..c92834d2f 100644
--- a/internal/configuration/decode_hooks.go
+++ b/internal/configuration/decode_hooks.go
@@ -5,7 +5,9 @@ import (
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
+ "encoding/base64"
"fmt"
+ "net"
"net/mail"
"net/url"
"reflect"
@@ -602,6 +604,14 @@ func StringToCryptographicKeyHookFunc() mapstructure.DecodeHookFuncType {
dataStr := data.(string)
if value, err = utils.ParseX509FromPEM([]byte(dataStr)); err != nil {
+ if !strings.Contains(dataStr, "\n") && !strings.HasPrefix(dataStr, "-----") {
+ var key []byte
+
+ if key, err = base64.URLEncoding.DecodeString(dataStr); err == nil {
+ return key, nil
+ }
+ }
+
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "", expectedType, err)
}
@@ -750,3 +760,66 @@ func StringToPasswordDigestHookFunc() mapstructure.DecodeHookFuncType {
return *result, nil
}
}
+
+//nolint:gocyclo
+func StringToIPNetworksHookFunc(definitions map[string][]*net.IPNet) mapstructure.DecodeHookFuncType {
+ return func(f reflect.Type, t reflect.Type, data any) (value any, err error) {
+ if f.Kind() != reflect.String && (f.Kind() != reflect.Slice || (f.Elem().Kind() != reflect.Interface && f.Elem().Kind() != reflect.String)) {
+ return data, nil
+ }
+
+ expectedType := reflect.TypeOf(net.IPNet{})
+
+ isSlice := t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Ptr && t.Elem().Elem() == expectedType
+ isKind := t.Kind() == reflect.Ptr && t.Elem() == expectedType
+
+ if !isSlice && !isKind {
+ return data, nil
+ }
+
+ var values []string
+
+ switch d := data.(type) {
+ case string:
+ values = []string{d}
+ case []string:
+ values = d
+ case []any:
+ values = make([]string, 0, len(d))
+
+ for i := range d {
+ switch v := d[i].(type) {
+ case string:
+ values = append(values, v)
+ default:
+ values = append(values, fmt.Sprint(v))
+ }
+ }
+ }
+
+ var (
+ ok bool
+ definition []*net.IPNet
+ networks []*net.IPNet
+ network *net.IPNet
+ )
+
+ for _, str := range values {
+ if definitions != nil {
+ if definition, ok = definitions[str]; ok {
+ networks = append(networks, definition...)
+
+ continue
+ }
+ }
+
+ if network, err = utils.ParseHostCIDR(str); err != nil {
+ return nil, fmt.Errorf("failed to parse network %q: %w", str, err)
+ }
+
+ networks = append(networks, network)
+ }
+
+ return networks, nil
+ }
+}
diff --git a/internal/configuration/decode_hooks_test.go b/internal/configuration/decode_hooks_test.go
index 6612c7511..85c09dd84 100644
--- a/internal/configuration/decode_hooks_test.go
+++ b/internal/configuration/decode_hooks_test.go
@@ -10,9 +10,11 @@ import (
"encoding/pem"
"fmt"
"math"
+ "net"
"net/mail"
"net/url"
"os"
+ "path"
"reflect"
"regexp"
"strings"
@@ -2007,6 +2009,93 @@ func TestStringToX509CertificateChainHookFunc(t *testing.T) {
}
}
+func TestStringToIPNetworksHookFunc(t *testing.T) {
+ mustParseNet := func(in string) *net.IPNet {
+ _, n, err := net.ParseCIDR(in)
+ if err != nil {
+ panic(err)
+ }
+
+ return n
+ }
+
+ testCases := []struct {
+ name string
+ path string
+ have *schema.Definitions
+ expected TestConfigDefinitions
+ err string
+ }{
+ {
+ "ShouldDecode",
+ "decode_networks.yml",
+ &schema.Definitions{},
+ TestConfigDefinitions{
+ Definitions: schema.Definitions{
+ Network: map[string][]*net.IPNet{
+ "single": {
+ mustParseNet("192.168.0.1/32"),
+ },
+ "example": {
+ mustParseNet("192.168.1.20/32"),
+ mustParseNet("192.168.2.0/24"),
+ },
+ },
+ },
+ },
+ "",
+ },
+ {
+ "ShouldDecodeDefinitions",
+ "decode_networks_abc.yml",
+ &schema.Definitions{
+ Network: map[string][]*net.IPNet{
+ "abc": {
+ mustParseNet("1.1.1.1/32"),
+ mustParseNet("1.1.1.2/32"),
+ },
+ },
+ },
+ TestConfigDefinitions{
+ Definitions: schema.Definitions{
+ Network: map[string][]*net.IPNet{
+ "example": {
+ mustParseNet("192.168.1.20/32"),
+ mustParseNet("192.168.2.0/24"),
+ mustParseNet("1.1.1.1/32"),
+ mustParseNet("1.1.1.2/32"),
+ },
+ },
+ },
+ },
+ "",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ result := TestConfigDefinitions{}
+
+ val := schema.NewStructValidator()
+ _, err := configuration.LoadAdvanced(val, "", &result, tc.have, configuration.NewDefaultSourcesFiltered([]string{path.Join("./test_resources", tc.path)}, nil, configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter)...)
+
+ if tc.err == "" {
+ assert.NoError(t, err)
+
+ assert.Equal(t, tc.expected, result)
+
+ assert.Len(t, val.Errors(), 0)
+ } else {
+ assert.EqualError(t, err, tc.err)
+ }
+ })
+ }
+}
+
+type TestConfigDefinitions struct {
+ Definitions schema.Definitions `koanf:"definitions"`
+}
+
var (
//nolint:gosec
x509PrivateKeyRSABad = `
diff --git a/internal/configuration/koanf_provider_filtered_file.go b/internal/configuration/koanf_provider_filtered_file.go
index 0e54773b7..0b44e389c 100644
--- a/internal/configuration/koanf_provider_filtered_file.go
+++ b/internal/configuration/koanf_provider_filtered_file.go
@@ -18,6 +18,7 @@ import (
// FilteredFile implements a koanf.Provider.
type FilteredFile struct {
+ data []byte
path string
filters []BytesFilter
}
@@ -32,21 +33,25 @@ func FilteredFileProvider(path string, filters ...BytesFilter) *FilteredFile {
// ReadBytes reads the contents of a file on disk, passes it through any configured filters, and returns the bytes.
func (f *FilteredFile) ReadBytes() (data []byte, err error) {
- if data, err = os.ReadFile(f.path); err != nil {
+ if f.data != nil {
+ return f.data, nil
+ }
+
+ if f.data, err = os.ReadFile(f.path); err != nil {
return nil, err
}
- if len(data) == 0 || len(f.filters) == 0 {
- return data, nil
+ if len(f.data) == 0 || len(f.filters) == 0 {
+ return f.data, nil
}
for _, filter := range f.filters {
- if data, err = filter.Filter(data); err != nil {
+ if f.data, err = filter.Filter(f.data); err != nil {
return nil, err
}
}
- return data, nil
+ return f.data, nil
}
// Read is not supported by the filtered file koanf.Provider.
@@ -54,6 +59,7 @@ func (f *FilteredFile) Read() (map[string]any, error) {
return nil, errors.New("filtered file provider does not support this method")
}
+// BytesFilter is an interface describing utility structures that filter bytes into desired bytes.
type BytesFilter interface {
Name() (name string)
Filter(in []byte) (out []byte, err error)
diff --git a/internal/configuration/provider.go b/internal/configuration/provider.go
index c4f68260d..77c0b3577 100644
--- a/internal/configuration/provider.go
+++ b/internal/configuration/provider.go
@@ -2,6 +2,7 @@ package configuration
import (
"fmt"
+ "net"
"github.com/go-viper/mapstructure/v2"
"github.com/knadh/koanf/v2"
@@ -13,21 +14,18 @@ import (
func Load(val *schema.StructValidator, sources ...Source) (keys []string, configuration *schema.Configuration, err error) {
configuration = &schema.Configuration{}
- keys, err = LoadAdvanced(val, "", configuration, sources...)
+ keys, err = LoadAdvanced(val, "", configuration, nil, sources...)
return keys, configuration, err
}
// LoadAdvanced is intended to give more flexibility over loading a particular path to a specific interface.
-func LoadAdvanced(val *schema.StructValidator, path string, result any, sources ...Source) (keys []string, err error) {
+func LoadAdvanced(val *schema.StructValidator, path string, result any, definitions *schema.Definitions, sources ...Source) (keys []string, err error) {
if val == nil {
return keys, errNoValidator
}
- ko := koanf.NewWithConf(koanf.Conf{
- Delim: constDelimiter,
- StrictMerge: false,
- })
+ ko := koanf.NewWithConf(koanf.Conf{Delim: constDelimiter, StrictMerge: false})
if err = loadSources(ko, val, sources...); err != nil {
return ko.Keys(), err
@@ -39,11 +37,88 @@ func LoadAdvanced(val *schema.StructValidator, path string, result any, sources
return koanfGetKeys(ko), err
}
- unmarshal(final, val, path, result)
+ unmarshal(final, val, path, result, definitions)
+
+ mapDefinitionsResult(val, result)
return koanfGetKeys(final), nil
}
+func LoadDefinitions(val *schema.StructValidator, sources ...Source) (definitions *schema.Definitions, err error) {
+ ko := koanf.NewWithConf(koanf.Conf{Delim: constDelimiter, StrictMerge: false})
+
+ if err = loadSources(ko, val, sources...); err != nil {
+ return nil, err
+ }
+
+ var final *koanf.Koanf
+
+ if final, err = koanfRemapKeys(val, ko, deprecations, deprecationsMKM); err != nil {
+ return nil, err
+ }
+
+ legacy := &legacyDefinitions{}
+
+ c := koanf.UnmarshalConf{
+ DecoderConfig: &mapstructure.DecoderConfig{
+ DecodeHook: mapstructure.ComposeDecodeHookFunc(
+ mapstructure.StringToSliceHookFunc(","),
+ StringToIPNetworksHookFunc(nil),
+ ),
+ Metadata: nil,
+ Result: legacy,
+ WeaklyTypedInput: true,
+ },
+ }
+
+ if err = final.UnmarshalWithConf("", legacy, c); err != nil {
+ val.Push(fmt.Errorf("error occurred during unmarshalling definitions configuration: %w", err))
+ }
+
+ d := legacy.Definitions
+
+ mapDefinitions(val, legacy.AccessControl.Networks, &d)
+
+ return &d, nil
+}
+
+func mapDefinitionsResult(val *schema.StructValidator, result any) {
+ if config, ok := result.(*schema.Configuration); ok {
+ mapDefinitions(val, config.AccessControl.Networks, &config.Definitions)
+ }
+}
+
+func mapDefinitions(val *schema.StructValidator, networks []schema.AccessControlNetwork, definitions *schema.Definitions) {
+ if len(networks) == 0 {
+ return
+ }
+
+ var ok bool
+
+ if definitions.Network == nil {
+ definitions.Network = make(map[string][]*net.IPNet, len(networks))
+ }
+
+ for _, network := range networks {
+ if _, ok = definitions.Network[network.Name]; ok {
+ val.Push(fmt.Errorf("error occurred during unmarshalling definitions configuration: the definition for network with name '%s' exists in both the definitions section and access control section which is not permitted", network.Name))
+
+ continue
+ }
+
+ definitions.Network[network.Name] = network.Networks
+ }
+}
+
+type legacyDefinitions struct {
+ Definitions schema.Definitions `koanf:"definitions"`
+ AccessControl legacyAccessControl `koanf:"access_control"`
+}
+
+type legacyAccessControl struct {
+ Networks []schema.AccessControlNetwork `koanf:"networks"`
+}
+
func mapHasKey(k string, m map[string]any) bool {
if _, ok := m[k]; ok {
return true
@@ -52,7 +127,11 @@ func mapHasKey(k string, m map[string]any) bool {
return false
}
-func unmarshal(ko *koanf.Koanf, val *schema.StructValidator, path string, o any) {
+func unmarshal(ko *koanf.Koanf, val *schema.StructValidator, path string, o any, definitions *schema.Definitions) {
+ if definitions == nil {
+ definitions = &schema.Definitions{}
+ }
+
c := koanf.UnmarshalConf{
DecoderConfig: &mapstructure.DecoderConfig{
DecodeHook: mapstructure.ComposeDecodeHookFunc(
@@ -68,6 +147,7 @@ func unmarshal(ko *koanf.Koanf, val *schema.StructValidator, path string, o any)
StringToCryptographicKeyHookFunc(),
StringToTLSVersionHookFunc(),
StringToPasswordDigestHookFunc(),
+ StringToIPNetworksHookFunc(definitions.Network),
ToTimeDurationHookFunc(),
ToRefreshIntervalDurationHookFunc(),
),
diff --git a/internal/configuration/provider_test.go b/internal/configuration/provider_test.go
index 390ac4532..40e399769 100644
--- a/internal/configuration/provider_test.go
+++ b/internal/configuration/provider_test.go
@@ -1165,6 +1165,48 @@ func TestShouldFailIfYmlIsInvalid(t *testing.T) {
assert.ErrorContains(t, val.Errors()[0], "unmarshal errors")
}
+func TestConfigurationDefinitions(t *testing.T) {
+ var (
+ definitions *schema.Definitions
+ err error
+ )
+
+ val := schema.NewStructValidator()
+
+ config := &schema.Configuration{}
+
+ sources := NewDefaultSourcesWithDefaults([]string{"./test_resources/config_with_definitions.yml"}, nil, DefaultEnvPrefix, DefaultEnvDelimiter, nil)
+
+ definitions, err = LoadDefinitions(val, sources...)
+
+ require.NoError(t, err)
+
+ _, err = LoadAdvanced(val, "", config, definitions, sources...)
+
+ require.NoError(t, err)
+
+ require.Len(t, config.Definitions.Network, 2)
+
+ require.Contains(t, config.Definitions.Network, "lan")
+ require.Len(t, config.Definitions.Network["lan"], 2)
+ assert.Equal(t, "192.168.1.0/24", config.Definitions.Network["lan"][0].String())
+ assert.Equal(t, "192.168.2.0/24", config.Definitions.Network["lan"][1].String())
+
+ require.Contains(t, config.Definitions.Network, "abc")
+ require.Len(t, config.Definitions.Network["abc"], 2)
+ assert.Equal(t, "192.168.3.0/24", config.Definitions.Network["abc"][0].String())
+ assert.Equal(t, "192.168.4.0/24", config.Definitions.Network["abc"][1].String())
+
+ require.Len(t, config.AccessControl.Rules, 12)
+ require.Len(t, config.AccessControl.Rules[1].Networks, 5)
+
+ assert.Equal(t, "192.168.0.0/24", config.AccessControl.Rules[1].Networks[0].String())
+ assert.Equal(t, "192.168.1.0/24", config.AccessControl.Rules[1].Networks[1].String())
+ assert.Equal(t, "192.168.2.0/24", config.AccessControl.Rules[1].Networks[2].String())
+ assert.Equal(t, "192.168.3.0/24", config.AccessControl.Rules[1].Networks[3].String())
+ assert.Equal(t, "192.168.4.0/24", config.AccessControl.Rules[1].Networks[4].String())
+}
+
func TestConfigurationTemplate(t *testing.T) {
buf := &bytes.Buffer{}
@@ -1242,7 +1284,21 @@ func TestConfigurationTemplate(t *testing.T) {
val := schema.NewStructValidator()
- keys, _, err := Load(val, NewBytesSource(config))
+ var (
+ keys []string
+ definitions *schema.Definitions
+ )
+
+ c := &schema.Configuration{}
+
+ src := NewBytesSource(config)
+
+ definitions, err = LoadDefinitions(val, src)
+
+ require.NoError(t, err)
+
+ keys, err = LoadAdvanced(val, "", c, definitions, src)
+
require.NoError(t, err)
assert.Len(t, val.Errors(), 0)
diff --git a/internal/configuration/schema/access_control.go b/internal/configuration/schema/access_control.go
index 5671042a4..c5f969520 100644
--- a/internal/configuration/schema/access_control.go
+++ b/internal/configuration/schema/access_control.go
@@ -1,5 +1,7 @@
package schema
+import "net"
+
// AccessControl represents the configuration related to ACLs.
type AccessControl struct {
// The default policy if no other policy matches the request.
@@ -14,8 +16,8 @@ type AccessControl struct {
// AccessControlNetwork represents one ACL network group entry.
type AccessControlNetwork struct {
- Name string `koanf:"name" json:"name" jsonschema:"required,title=Network Name" jsonschema_description:"The name of this network to be used in the networks section of the rules section."`
- Networks AccessControlNetworkNetworks `koanf:"networks" json:"networks" jsonschema:"required,title=Networks" jsonschema_description:"The remote IP's or network ranges in CIDR notation that this rule applies to."`
+ Name string `koanf:"name" json:"name" jsonschema:"required,title=Network Name" jsonschema_description:"The name of this network to be used in the networks section of the rules section."`
+ Networks []*net.IPNet `koanf:"networks" json:"networks" jsonschema:"required,title=Networks" jsonschema_description:"The remote IP's or network ranges in CIDR notation that this rule applies to."`
}
// AccessControlRule represents one ACL rule entry.
@@ -24,7 +26,7 @@ type AccessControlRule struct {
DomainsRegex AccessControlRuleRegex `koanf:"domain_regex" json:"domain_regex" jsonschema:"oneof_required=Domain Regex,title=Domain Regex Patterns" jsonschema_description:"The regex patterns to match the domain against that this rule applies to."`
Policy string `koanf:"policy" json:"policy" jsonschema:"required,enum=bypass,enum=deny,enum=one_factor,enum=two_factor,title=Rule Policy" jsonschema_description:"The policy this rule applies when all criteria match."`
Subjects AccessControlRuleSubjects `koanf:"subject" json:"subject" jsonschema:"title=AccessControlRuleSubjects" jsonschema_description:"The users or groups that this rule applies to."`
- Networks AccessControlRuleNetworks `koanf:"networks" json:"networks" jsonschema:"title=Networks" jsonschema_description:"The remote IP's, network ranges in CIDR notation, or network names that this rule applies to."`
+ Networks []*net.IPNet `koanf:"networks" json:"networks" jsonschema:"title=Networks" jsonschema_description:"The remote IP's, network ranges in CIDR notation, or network definition names that this rule applies to."`
Resources AccessControlRuleRegex `koanf:"resources" json:"resources" jsonschema:"title=Resources or Paths" jsonschema_description:"The regex patterns to match the resource paths that this rule applies to."`
Methods AccessControlRuleMethods `koanf:"methods" json:"methods" jsonschema:"enum=GET,enum=HEAD,enum=POST,enum=PUT,enum=DELETE,enum=CONNECT,enum=OPTIONS,enum=TRACE,enum=PATCH,enum=PROPFIND,enum=PROPPATCH,enum=MKCOL,enum=COPY,enum=MOVE,enum=LOCK,enum=UNLOCK" jsonschema_description:"The list of request methods this rule applies to."`
Query [][]AccessControlRuleQuery `koanf:"query" json:"query" jsonschema:"title=Query Rules" jsonschema_description:"The list of query parameter rules this rule applies to."`
@@ -37,18 +39,6 @@ type AccessControlRuleQuery struct {
Value any `koanf:"value" json:"value" jsonschema:"title=Value" jsonschema_description:"The Query Parameter value for this rule."`
}
-// DefaultACLNetwork represents the default configuration related to access control network group configuration.
-var DefaultACLNetwork = []AccessControlNetwork{
- {
- Name: "localhost",
- Networks: []string{"127.0.0.1"},
- },
- {
- Name: "internal",
- Networks: []string{"10.0.0.0/8"},
- },
-}
-
// DefaultACLRule represents the default configuration related to access control rule configuration.
var DefaultACLRule = []AccessControlRule{
{
diff --git a/internal/configuration/schema/configuration.go b/internal/configuration/schema/configuration.go
index 56d0c16ae..607dbf64b 100644
--- a/internal/configuration/schema/configuration.go
+++ b/internal/configuration/schema/configuration.go
@@ -27,6 +27,7 @@ type Configuration struct {
PasswordPolicy PasswordPolicy `koanf:"password_policy" json:"password_policy" jsonschema:"title=Password Policy" jsonschema_description:"Password Policy Configuration."`
PrivacyPolicy PrivacyPolicy `koanf:"privacy_policy" json:"privacy_policy" jsonschema:"title=Privacy Policy" jsonschema_description:"Privacy Policy Configuration."`
IdentityValidation IdentityValidation `koanf:"identity_validation" json:"identity_validation" jsonschema:"title=Identity Validation" jsonschema_description:"Identity Validation Configuration."`
+ Definitions Definitions `koanf:"definitions" json:"definitions" jsonschema:"title=Definitions" jsonschema_description:"Definitions for items reused elsewhere in the configuration."`
// Deprecated: Use the session cookies option with the same name instead.
DefaultRedirectionURL *url.URL `koanf:"default_redirection_url" json:"default_redirection_url" jsonschema:"deprecated,format=uri,title=The default redirection URL"`
diff --git a/internal/configuration/schema/definitions.go b/internal/configuration/schema/definitions.go
new file mode 100644
index 000000000..67297564a
--- /dev/null
+++ b/internal/configuration/schema/definitions.go
@@ -0,0 +1,7 @@
+package schema
+
+import "net"
+
+type Definitions struct {
+ Network map[string][]*net.IPNet `koanf:"network" json:"network" jsonschema:"title=Network Definitions" jsonschema_description:"Networks CIDR ranges that can be utilized elsewhere in the configuration."`
+}
diff --git a/internal/configuration/schema/keys.go b/internal/configuration/schema/keys.go
index bc3b07460..eb3fad7ce 100644
--- a/internal/configuration/schema/keys.go
+++ b/internal/configuration/schema/keys.go
@@ -357,5 +357,7 @@ var Keys = []string{
"identity_validation.elevated_session.characters",
"identity_validation.elevated_session.require_second_factor",
"identity_validation.elevated_session.skip_second_factor",
+ "definitions.network.*",
+ "definitions.network",
"default_redirection_url",
}
diff --git a/internal/configuration/schema/types.go b/internal/configuration/schema/types.go
index 8cf34f740..1f8afc0c4 100644
--- a/internal/configuration/schema/types.go
+++ b/internal/configuration/schema/types.go
@@ -493,12 +493,6 @@ func (RefreshIntervalDuration) JSONSchema() *jsonschema.Schema {
}
}
-type AccessControlRuleNetworks []string
-
-func (AccessControlRuleNetworks) JSONSchema() *jsonschema.Schema {
- return &jsonschemaWeakStringUniqueSlice
-}
-
type IdentityProvidersOpenIDConnectClientURIs []string
func (IdentityProvidersOpenIDConnectClientURIs) JSONSchema() *jsonschema.Schema {
@@ -514,22 +508,6 @@ func (IdentityProvidersOpenIDConnectClientURIs) JSONSchema() *jsonschema.Schema
}
}
-// AccessControlNetworkNetworks represents the ACL AccessControlNetworkNetworks type.
-type AccessControlNetworkNetworks []string
-
-func (AccessControlNetworkNetworks) JSONSchema() *jsonschema.Schema {
- return &jsonschema.Schema{
- OneOf: []*jsonschema.Schema{
- &jsonschemaACLNetwork,
- {
- Type: jsonschema.TypeArray,
- Items: &jsonschemaACLNetwork,
- UniqueItems: true,
- },
- },
- }
-}
-
type AccessControlRuleDomains []string
func (AccessControlRuleDomains) JSONSchema() *jsonschema.Schema {
@@ -618,11 +596,6 @@ var jsonschemaWeakStringUniqueSlice = jsonschema.Schema{
},
}
-var jsonschemaACLNetwork = jsonschema.Schema{
- Type: jsonschema.TypeString,
- Pattern: `((^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))(\/([0-2]?[0-9]|3[0-2]))?$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))?(\/(12[0-8]|1[0-1][0-9]|[0-9]{1,2}))?$))`,
-}
-
var jsonschemaACLSubject = jsonschema.Schema{
Type: jsonschema.TypeString,
Pattern: "^(user|group|oauth2:client):.+$",
diff --git a/internal/configuration/schema/types_test.go b/internal/configuration/schema/types_test.go
index a8fdb4bc1..a36e2aa48 100644
--- a/internal/configuration/schema/types_test.go
+++ b/internal/configuration/schema/types_test.go
@@ -359,8 +359,6 @@ func TestJSONSchema(t *testing.T) {
&PasswordDigest{},
&TLSVersion{},
&X509CertificateChain{},
- &AccessControlRuleNetworks{},
- &AccessControlNetworkNetworks{},
&AccessControlRuleDomains{},
&AccessControlRuleMethods{},
&AccessControlRuleRegex{},
diff --git a/internal/configuration/sources.go b/internal/configuration/sources.go
index bd8b559b2..c5dd1fe0d 100644
--- a/internal/configuration/sources.go
+++ b/internal/configuration/sources.go
@@ -20,8 +20,10 @@ import (
// accessing this path it also returns an error.
func NewFileSource(path string) (source *FileSource) {
return &FileSource{
- koanf: koanf.New(constDelimiter),
- path: path,
+ koanf: koanf.New(constDelimiter),
+ provider: FilteredFileProvider(path),
+ providers: make(map[string]*FilteredFile),
+ path: path,
}
}
@@ -29,9 +31,11 @@ func NewFileSource(path string) (source *FileSource) {
// an issue accessing this path it also returns an error.
func NewFilteredFileSource(path string, filters ...BytesFilter) (source *FileSource) {
return &FileSource{
- koanf: koanf.New(constDelimiter),
- path: path,
- filters: filters,
+ koanf: koanf.New(constDelimiter),
+ provider: FilteredFileProvider(path, filters...),
+ providers: make(map[string]*FilteredFile),
+ path: path,
+ filters: filters,
}
}
@@ -83,7 +87,7 @@ func (s *FileSource) Load(val *schema.StructValidator) (err error) {
return s.loadDir(val)
}
- return s.koanf.Load(FilteredFileProvider(s.path, s.filters...), yaml.Parser())
+ return s.koanf.Load(s.provider, yaml.Parser())
}
func (s *FileSource) loadDir(_ *schema.StructValidator) (err error) {
@@ -93,6 +97,11 @@ func (s *FileSource) loadDir(_ *schema.StructValidator) (err error) {
return err
}
+ var (
+ provider *FilteredFile
+ ok bool
+ )
+
for _, entry := range entries {
if entry.IsDir() {
continue
@@ -100,9 +109,17 @@ func (s *FileSource) loadDir(_ *schema.StructValidator) (err error) {
name := entry.Name()
+ file := filepath.Join(s.path, name)
+
switch ext := filepath.Ext(name); ext {
case extYML, extYAML:
- if err = s.koanf.Load(FilteredFileProvider(filepath.Join(s.path, name), s.filters...), yaml.Parser()); err != nil {
+ if provider, ok = s.providers[file]; !ok {
+ provider = FilteredFileProvider(file, s.filters...)
+
+ s.providers[file] = provider
+ }
+
+ if err = s.koanf.Load(provider, yaml.Parser()); err != nil {
return err
}
}
diff --git a/internal/configuration/test_resources/config_with_definitions.yml b/internal/configuration/test_resources/config_with_definitions.yml
new file mode 100644
index 000000000..baf809893
--- /dev/null
+++ b/internal/configuration/test_resources/config_with_definitions.yml
@@ -0,0 +1,185 @@
+---
+default_redirection_url: 'https://home.example.com:8080/'
+
+server:
+ address: 'tcp://127.0.0.1:9091'
+ endpoints:
+ authz:
+ forward-auth:
+ implementation: 'ForwardAuth'
+ authn_strategies:
+ - name: 'HeaderProxyAuthorization'
+ - name: 'CookieSession'
+ ext-authz:
+ implementation: 'ExtAuthz'
+ authn_strategies:
+ - name: 'HeaderProxyAuthorization'
+ - name: 'CookieSession'
+ auth-request:
+ implementation: 'AuthRequest'
+ authn_strategies:
+ - name: 'HeaderAuthRequestProxyAuthorization'
+ - name: 'CookieSession'
+ legacy:
+ implementation: 'Legacy'
+
+log:
+ level: 'debug'
+
+totp:
+ issuer: 'authelia.com'
+
+duo_api:
+ hostname: 'api-123456789.example.com'
+ integration_key: 'ABCDEF'
+
+authentication_backend:
+ refresh_interval: 'disable'
+ ldap:
+ address: 'ldap://127.0.0.1'
+ tls:
+ private_key: |
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIEpAIBAAKCAQEA6z1LOg1ZCqb0lytXWZ+MRBpMHEXOoTOLYgfZXt1IYyE3Z758
+ cyalk0NYQhY5cZDsXPYWPvAHiPMUxutWkoxFwby56S+AbIMa3/Is+ILrHRJs8Exn
+ ZkpyrYFxPX12app2kErdmAkHSx0Z5/kuXiz96PHs8S8/ZbyZolLHzdfLtSzjvRm5
+ Zue5iFzsf19NJz5CIBfv8g5lRwtE8wNJoRSpn1xq7fqfuA0weDNFPzjlNWRLy6aa
+ rK7qJexRkmkCs4sLgyl+9NODYJpvmN8E1yhyC27E0joI6rBFVW7Ihv+cSPCdDzGp
+ EWe81x3AeqAa3mjVqkiq4u4Z2i8JDgBaPboqJwIDAQABAoIBAAFdLZ58jVOefDSU
+ L8F5R1rtvBs93GDa56f926jNJ6pLewLC+/2+757W+SAI+PRLntM7Kg3bXm/Q2QH+
+ Q1Y+MflZmspbWCdI61L5GIGoYKyeers59i+FpvySj5GHtLQRiTZ0+Kv1AXHSDWBm
+ 9XneUOqU3IbZe0ifu1RRno72/VtjkGXbW8Mkkw+ohyGbIeTx/0/JQ6sSNZTT3Vk7
+ 8i4IXptq3HSF0/vqZuah8rShoeNq72pD1YLM9YPdL5by1QkDLnqATDiCpLBTCaNV
+ I8sqYEun+HYbQzBj8ZACG2JVZpEEidONWQHw5BPWO95DSZYrVnEkuCqeH+u5vYt7
+ CHuJ3AECgYEA+W3v5z+j91w1VPHS0VB3SCDMouycAMIUnJPAbt+0LPP0scUFsBGE
+ hPAKddC54pmMZRQ2KIwBKiyWfCrJ8Xz8Yogn7fJgmwTHidJBr2WQpIEkNGlK3Dzi
+ jXL2sh0yC7sHvn0DqiQ79l/e7yRbSnv2wrTJEczOOH2haD7/tBRyCYECgYEA8W+q
+ E9YyGvEltnPFaOxofNZ8LHVcZSsQI5b6fc0iE7fjxFqeXPXEwGSOTwqQLQRiHn9b
+ CfPmIG4Vhyq0otVmlPvUnfBZ2OK+tl5X2/mQFO3ROMdvpi0KYa994uqfJdSTaqLn
+ jjoKFB906UFHnDQDLZUNiV1WwnkTglgLc+xrd6cCgYEAqqthyv6NyBTM3Tm2gcio
+ Ra9Dtntl51LlXZnvwy3IkDXBCd6BHM9vuLKyxZiziGx+Vy90O1xI872cnot8sINQ
+ Am+dur/tAEVN72zxyv0Y8qb2yfH96iKy9gxi5s75TnOEQgAygLnYWaWR2lorKRUX
+ bHTdXBOiS58S0UzCFEslGIECgYBqkO4SKWYeTDhoKvuEj2yjRYyzlu28XeCWxOo1
+ otiauX0YSyNBRt2cSgYiTzhKFng0m+QUJYp63/wymB/5C5Zmxi0XtWIDADpLhqLj
+ HmmBQ2Mo26alQ5YkffBju0mZyhVzaQop1eZi8WuKFV1FThPlB7hc3E0SM5zv2Grd
+ tQnOWwKBgQC40yZY0PcjuILhy+sIc0Wvh7LUA7taSdTye149kRvbvsCDN7Jh75lM
+ USjhLXY0Nld2zBm9r8wMb81mXH29uvD+tDqqsICvyuKlA/tyzXR+QTr7dCVKVwu0
+ 1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
+ -----END RSA PRIVATE KEY-----
+ base_dn: 'dc=example,dc=com'
+ additional_users_dn: 'ou=users'
+ users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
+ additional_groups_dn: 'ou=groups'
+ groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
+ user: 'cn=admin,dc=example,dc=com'
+ attributes:
+ username: 'uid'
+ group_name: 'cn'
+ mail: 'mail'
+
+definitions:
+ network:
+ lan:
+ - '192.168.1.0/24'
+ - '192.168.2.0/24'
+
+access_control:
+ default_policy: 'deny'
+ networks:
+ - name: 'abc'
+ networks:
+ - '192.168.3.0/24'
+ - '192.168.4.0/24'
+ rules:
+ # Rules applied to everyone
+ - domain: 'public.example.com'
+ policy: 'bypass'
+
+ - domain: 'secure.example.com'
+ policy: 'one_factor'
+ # Network based rule, if not provided any network matches.
+ networks:
+ - '192.168.0.0/24'
+ - 'lan'
+ - 'abc'
+ - domain: 'secure.example.com'
+ policy: 'two_factor'
+
+ - domain: ['singlefactor.example.com', 'onefactor.example.com']
+ policy: 'one_factor'
+
+ # Rules applied to 'admins' group
+ - domain: 'mx2.mail.example.com'
+ subject: 'group:admins'
+ policy: 'deny'
+ - domain: '*.example.com'
+ subject: 'group:admins'
+ policy: 'two_factor'
+
+ # Rules applied to 'dev' group
+ - domain: 'dev.example.com'
+ resources:
+ - '^/groups/dev/.*$'
+ subject: 'group:dev'
+ policy: 'two_factor'
+
+ # Rules applied to user 'john'
+ - domain: 'dev.example.com'
+ resources:
+ - '^/users/john/.*$'
+ subject: 'user:john'
+ policy: 'two_factor'
+
+ # Rules applied to 'dev' group and user 'john'
+ - domain: 'dev.example.com'
+ resources:
+ - '^/deny-all.*$'
+ subject: ['group:dev', 'user:john']
+ policy: 'deny'
+
+ # Rules applied to user 'harry'
+ - domain: 'dev.example.com'
+ resources:
+ - '^/users/harry/.*$'
+ subject: 'user:harry'
+ policy: 'two_factor'
+
+ # Rules applied to user 'bob'
+ - domain: '*.mail.example.com'
+ subject: 'user:bob'
+ policy: 'two_factor'
+ - domain: 'dev.example.com'
+ resources:
+ - '^/users/bob/.*$'
+ subject: 'user:bob'
+ policy: 'two_factor'
+
+session:
+ name: 'authelia_session'
+ expiration: '1h' # 1 hour
+ inactivity: '5m' # 5 minutes
+ domain: 'example.com'
+ redis:
+ host: '127.0.0.1'
+ port: 6379
+ high_availability:
+ sentinel_name: 'test'
+
+regulation:
+ max_retries: 3
+ find_time: '2m'
+ ban_time: '5m'
+
+storage:
+ mysql:
+ address: 'tcp://127.0.0.1:3306'
+ database: 'authelia'
+ username: 'authelia'
+
+notifier:
+ smtp:
+ address: 'smtp://127.0.0.1:1025'
+ username: 'test'
+ sender: 'admin@example.com'
+ disable_require_tls: true
+...
diff --git a/internal/configuration/test_resources/decode_networks.yml b/internal/configuration/test_resources/decode_networks.yml
new file mode 100644
index 000000000..2c1e1046c
--- /dev/null
+++ b/internal/configuration/test_resources/decode_networks.yml
@@ -0,0 +1,8 @@
+---
+definitions:
+ network:
+ single: '192.168.0.1'
+ example:
+ - '192.168.1.20'
+ - '192.168.2.0/24'
+...
diff --git a/internal/configuration/test_resources/decode_networks_abc.yml b/internal/configuration/test_resources/decode_networks_abc.yml
new file mode 100644
index 000000000..fcdf730a7
--- /dev/null
+++ b/internal/configuration/test_resources/decode_networks_abc.yml
@@ -0,0 +1,8 @@
+---
+definitions:
+ network:
+ example:
+ - '192.168.1.20'
+ - '192.168.2.0/24'
+ - 'abc'
+...
diff --git a/internal/configuration/types.go b/internal/configuration/types.go
index d1c487a2f..0a7c3d106 100644
--- a/internal/configuration/types.go
+++ b/internal/configuration/types.go
@@ -16,9 +16,11 @@ type Source interface {
// FileSource is a file configuration.Source.
type FileSource struct {
- koanf *koanf.Koanf
- path string
- filters []BytesFilter
+ koanf *koanf.Koanf
+ provider *FilteredFile
+ providers map[string]*FilteredFile
+ path string
+ filters []BytesFilter
}
// BytesSource is a raw bytes configuration.Source.
diff --git a/internal/configuration/validator/access_control.go b/internal/configuration/validator/access_control.go
index aa392351e..86aa37996 100644
--- a/internal/configuration/validator/access_control.go
+++ b/internal/configuration/validator/access_control.go
@@ -2,7 +2,6 @@ package validator
import (
"fmt"
- "net"
"regexp"
"strings"
@@ -11,51 +10,6 @@ import (
"github.com/authelia/authelia/v4/internal/utils"
)
-// IsPolicyValid check if policy is valid.
-func IsPolicyValid(policy string) (isValid bool) {
- return utils.IsStringInSlice(policy, validACLRulePolicies)
-}
-
-// IsSubjectValid check if a subject is valid.
-func IsSubjectValid(subject string) (isValid bool) {
- return subject == "" || IsSubjectValidStrict(subject) || strings.HasPrefix(subject, "oauth2:client:")
-}
-
-func IsSubjectValidStrict(subject string) (isValid bool) {
- return strings.HasPrefix(subject, "user:") || strings.HasPrefix(subject, "group:")
-}
-
-// IsNetworkGroupValid check if a network group is valid.
-func IsNetworkGroupValid(config schema.AccessControl, network string) bool {
- for _, networks := range config.Networks {
- if network != networks.Name {
- continue
- } else {
- return true
- }
- }
-
- return false
-}
-
-// IsNetworkValid checks if a network is valid.
-func IsNetworkValid(network string) (isValid bool) {
- if net.ParseIP(network) == nil {
- _, _, err := net.ParseCIDR(network)
- return err == nil
- }
-
- return true
-}
-
-func ruleDescriptor(position int, rule schema.AccessControlRule) string {
- if len(rule.Domains) == 0 {
- return fmt.Sprintf("#%d", position)
- }
-
- return fmt.Sprintf("#%d (domain '%s')", position, strings.Join(rule.Domains, ","))
-}
-
// ValidateAccessControl validates access control configuration.
func ValidateAccessControl(config *schema.Configuration, validator *schema.StructValidator) {
if config.AccessControl.DefaultPolicy == "" {
@@ -65,14 +19,6 @@ func ValidateAccessControl(config *schema.Configuration, validator *schema.Struc
if !IsPolicyValid(config.AccessControl.DefaultPolicy) {
validator.Push(fmt.Errorf(errFmtAccessControlDefaultPolicyValue, utils.StringJoinOr(validACLRulePolicies), config.AccessControl.DefaultPolicy))
}
-
- for _, n := range config.AccessControl.Networks {
- for _, networks := range n.Networks {
- if !IsNetworkValid(networks) {
- validator.Push(fmt.Errorf(errFmtAccessControlNetworkGroupIPCIDRInvalid, n.Name, networks))
- }
- }
- }
}
// ValidateRules validates an ACL Rule configuration.
@@ -103,9 +49,7 @@ func ValidateRules(config *schema.Configuration, validator *schema.StructValidat
}
}
- validateNetworks(rulePosition, rule, config.AccessControl, validator)
-
- validateSubjects(rulePosition, rule, validator)
+ validateSubjects(rulePosition, rule, config, validator)
validateMethods(rulePosition, rule, validator)
@@ -142,21 +86,22 @@ func validateDomains(rulePosition int, rule schema.AccessControlRule, validator
}
}
-func validateNetworks(rulePosition int, rule schema.AccessControlRule, config schema.AccessControl, validator *schema.StructValidator) {
- for _, network := range rule.Networks {
- if !IsNetworkValid(network) {
- if !IsNetworkGroupValid(config, network) {
- validator.Push(fmt.Errorf(errFmtAccessControlRuleNetworksInvalid, ruleDescriptor(rulePosition, rule), network))
- }
- }
- }
-}
+func validateSubjects(rulePosition int, rule schema.AccessControlRule, config *schema.Configuration, validator *schema.StructValidator) {
+ var (
+ id string
+ isValid bool
+ )
-func validateSubjects(rulePosition int, rule schema.AccessControlRule, validator *schema.StructValidator) {
for _, subjectRule := range rule.Subjects {
for _, subject := range subjectRule {
- if !IsSubjectValid(subject) {
+ if id, isValid = IsSubjectValid(subject); !isValid {
validator.Push(fmt.Errorf(errFmtAccessControlRuleSubjectInvalid, ruleDescriptor(rulePosition, rule), subject))
+
+ continue
+ }
+
+ if len(id) != 0 && !IsSubjectValidOAuth20(config, id) {
+ validator.Push(fmt.Errorf(errFmtAccessControlRuleOAuth2ClientSubjectInvalid, ruleDescriptor(rulePosition, rule), subject, id))
}
}
}
@@ -230,3 +175,47 @@ func validateQuery(i int, rule schema.AccessControlRule, config *schema.Configur
}
}
}
+
+// IsPolicyValid check if policy is valid.
+func IsPolicyValid(policy string) (isValid bool) {
+ return utils.IsStringInSlice(policy, validACLRulePolicies)
+}
+
+// IsSubjectValid validates if a subject has a valid prefix and returns the client id if applicable.
+func IsSubjectValid(subject string) (id string, isValid bool) {
+ if IsSubjectValidBasic(subject) {
+ return "", true
+ }
+
+ if strings.HasPrefix(subject, "oauth2:client:") {
+ return strings.TrimPrefix(subject, "oauth2:client:"), true
+ }
+
+ return "", false
+}
+
+func IsSubjectValidBasic(subject string) (isValid bool) {
+ return strings.HasPrefix(subject, "user:") || strings.HasPrefix(subject, "group:")
+}
+
+func IsSubjectValidOAuth20(config *schema.Configuration, id string) (isValid bool) {
+ if config.IdentityProviders.OIDC == nil || len(config.IdentityProviders.OIDC.Clients) == 0 {
+ return false
+ }
+
+ for _, client := range config.IdentityProviders.OIDC.Clients {
+ if client.ID == id {
+ return true
+ }
+ }
+
+ return false
+}
+
+func ruleDescriptor(position int, rule schema.AccessControlRule) string {
+ if len(rule.Domains) == 0 {
+ return fmt.Sprintf("#%d", position)
+ }
+
+ return fmt.Sprintf("#%d (domain '%s')", position, strings.Join(rule.Domains, ","))
+}
diff --git a/internal/configuration/validator/access_control_test.go b/internal/configuration/validator/access_control_test.go
index ad38ec9a0..b288ab9ad 100644
--- a/internal/configuration/validator/access_control_test.go
+++ b/internal/configuration/validator/access_control_test.go
@@ -23,9 +23,7 @@ func (suite *AccessControl) SetupTest() {
suite.config = &schema.Configuration{
AccessControl: schema.AccessControl{
DefaultPolicy: policyDeny,
-
- Networks: schema.DefaultACLNetwork,
- Rules: schema.DefaultACLRule,
+ Rules: schema.DefaultACLRule,
},
}
}
@@ -73,22 +71,6 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidDefaultPolicy() {
suite.Assert().EqualError(suite.validator.Errors()[0], "access_control: option 'default_policy' must be one of 'bypass', 'one_factor', 'two_factor', or 'deny' but it's configured as 'invalid'")
}
-func (suite *AccessControl) TestShouldRaiseErrorInvalidNetworkGroupNetwork() {
- suite.config.AccessControl.Networks = []schema.AccessControlNetwork{
- {
- Name: "internal",
- Networks: []string{"abc.def.ghi.jkl"},
- },
- }
-
- ValidateAccessControl(suite.config, suite.validator)
-
- suite.Assert().Len(suite.validator.Warnings(), 0)
- suite.Require().Len(suite.validator.Errors(), 1)
-
- suite.Assert().EqualError(suite.validator.Errors()[0], "access_control: networks: network group 'internal' is invalid: the network 'abc.def.ghi.jkl' is not a valid IP or CIDR notation")
-}
-
func (suite *AccessControl) TestShouldRaiseWarningOnBadDomain() {
suite.config.AccessControl.Rules = []schema.AccessControlRule{
{
@@ -164,23 +146,6 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidPolicy() {
suite.Assert().EqualError(suite.validator.Errors()[0], "access_control: rule #1 (domain 'public.example.com'): option 'policy' must be one of 'bypass', 'one_factor', 'two_factor', or 'deny' but it's configured as 'invalid'")
}
-func (suite *AccessControl) TestShouldRaiseErrorInvalidNetwork() {
- suite.config.AccessControl.Rules = []schema.AccessControlRule{
- {
- Domains: []string{"public.example.com"},
- Policy: "bypass",
- Networks: []string{"abc.def.ghi.jkl/32"},
- },
- }
-
- ValidateRules(suite.config, suite.validator)
-
- suite.Assert().Len(suite.validator.Warnings(), 0)
- suite.Require().Len(suite.validator.Errors(), 1)
-
- suite.Assert().EqualError(suite.validator.Errors()[0], "access_control: rule #1 (domain 'public.example.com'): the network 'abc.def.ghi.jkl/32' is not a valid Group Name, IP, or CIDR notation")
-}
-
func (suite *AccessControl) TestShouldRaiseErrorInvalidMethod() {
suite.config.AccessControl.Rules = []schema.AccessControlRule{
{
@@ -231,10 +196,105 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidSubject() {
suite.Require().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 2)
- suite.Assert().EqualError(suite.validator.Errors()[0], "access_control: rule #1 (domain 'public.example.com'): 'subject' option 'invalid' is invalid: must start with 'user:' or 'group:'")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "access_control: rule #1 (domain 'public.example.com'): 'subject' option 'invalid' is invalid: must start with 'user:', 'group:', or 'oauth2:client:'")
suite.Assert().EqualError(suite.validator.Errors()[1], fmt.Sprintf(errAccessControlRuleBypassPolicyInvalidWithSubjects, ruleDescriptor(1, suite.config.AccessControl.Rules[0])))
}
+func (suite *AccessControl) TestShouldValidateClientIDSubjectWithoutClient() {
+ domains := []string{"public.example.com"}
+ subjects := [][]string{{"oauth2:client:example"}}
+ suite.config.AccessControl.Rules = []schema.AccessControlRule{
+ {
+ Domains: domains,
+ Policy: "bypass",
+ Subjects: subjects,
+ },
+ }
+
+ ValidateRules(suite.config, suite.validator)
+
+ suite.Require().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 2)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "access_control: rule #1 (domain 'public.example.com'): option 'subject' with value 'oauth2:client:example' is invalid: the client id 'example' does not belong to a registered client")
+ suite.Assert().EqualError(suite.validator.Errors()[1], fmt.Sprintf(errAccessControlRuleBypassPolicyInvalidWithSubjects, ruleDescriptor(1, suite.config.AccessControl.Rules[0])))
+}
+
+func (suite *AccessControl) TestShouldValidateClientIDSubjectWithoutClientMatchingID() {
+ domains := []string{"public.example.com"}
+ subjects := [][]string{{"oauth2:client:example"}}
+ suite.config.IdentityProviders.OIDC = &schema.IdentityProvidersOpenIDConnect{
+ Clients: []schema.IdentityProvidersOpenIDConnectClient{
+ {
+ ID: "example2",
+ },
+ },
+ }
+ suite.config.AccessControl.Rules = []schema.AccessControlRule{
+ {
+ Domains: domains,
+ Policy: "bypass",
+ Subjects: subjects,
+ },
+ }
+
+ ValidateRules(suite.config, suite.validator)
+
+ suite.Require().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 2)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "access_control: rule #1 (domain 'public.example.com'): option 'subject' with value 'oauth2:client:example' is invalid: the client id 'example' does not belong to a registered client")
+ suite.Assert().EqualError(suite.validator.Errors()[1], fmt.Sprintf(errAccessControlRuleBypassPolicyInvalidWithSubjects, ruleDescriptor(1, suite.config.AccessControl.Rules[0])))
+}
+
+func (suite *AccessControl) TestShouldValidateClientIDSubject() {
+ domains := []string{"public.example.com"}
+ subjects := [][]string{{"oauth2:client:example"}}
+ suite.config.IdentityProviders.OIDC = &schema.IdentityProvidersOpenIDConnect{
+ Clients: []schema.IdentityProvidersOpenIDConnectClient{
+ {
+ ID: "example",
+ },
+ },
+ }
+ suite.config.AccessControl.Rules = []schema.AccessControlRule{
+ {
+ Domains: domains,
+ Policy: "one_factor",
+ Subjects: subjects,
+ },
+ }
+
+ ValidateRules(suite.config, suite.validator)
+
+ suite.Require().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 0)
+}
+
+func (suite *AccessControl) TestShouldValidateBasicSubject() {
+ domains := []string{"public.example.com"}
+ subjects := [][]string{{"user:example"}}
+ suite.config.IdentityProviders.OIDC = &schema.IdentityProvidersOpenIDConnect{
+ Clients: []schema.IdentityProvidersOpenIDConnectClient{
+ {
+ ID: "example",
+ },
+ },
+ }
+ suite.config.AccessControl.Rules = []schema.AccessControlRule{
+ {
+ Domains: domains,
+ Policy: "one_factor",
+ Subjects: subjects,
+ },
+ }
+
+ ValidateRules(suite.config, suite.validator)
+
+ suite.Require().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 0)
+}
+
func (suite *AccessControl) TestShouldRaiseErrorBypassWithSubjectDomainRegexGroup() {
suite.config.AccessControl.Rules = []schema.AccessControlRule{
{
@@ -398,18 +458,6 @@ func TestAccessControl(t *testing.T) {
suite.Run(t, new(AccessControl))
}
-func TestShouldReturnCorrectResultsForValidNetworkGroups(t *testing.T) {
- config := schema.AccessControl{
- Networks: schema.DefaultACLNetwork,
- }
-
- validNetwork := IsNetworkGroupValid(config, "internal")
- invalidNetwork := IsNetworkGroupValid(config, loopback)
-
- assert.True(t, validNetwork)
- assert.False(t, invalidNetwork)
-}
-
func MustCompileRegexps(exps []string) (regexps []regexp.Regexp) {
regexps = make([]regexp.Regexp, len(exps))
diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go
index 26c057495..ebc83c380 100644
--- a/internal/configuration/validator/const.go
+++ b/internal/configuration/validator/const.go
@@ -333,7 +333,9 @@ const (
errFmtAccessControlRuleNetworksInvalid = "access_control: rule %s: the network '%s' is not a " +
"valid Group Name, IP, or CIDR notation"
errFmtAccessControlRuleSubjectInvalid = "access_control: rule %s: 'subject' option '%s' is " +
- "invalid: must start with 'user:' or 'group:'"
+ "invalid: must start with 'user:', 'group:', or 'oauth2:client:'"
+ errFmtAccessControlRuleOAuth2ClientSubjectInvalid = "access_control: rule %s: option 'subject' with value '%s' is " +
+ "invalid: the client id '%s' does not belong to a registered client"
errFmtAccessControlRuleInvalidEntries = "access_control: rule %s: option '%s' must only have the values %s but the values %s are present"
errFmtAccessControlRuleInvalidDuplicates = "access_control: rule %s: option '%s' must have unique values but the values %s are duplicated"
errFmtAccessControlRuleQueryInvalid = "access_control: rule %s: query: option 'operator' must be one of %s but it's configured as '%s'"
diff --git a/internal/configuration/validator/identity_providers.go b/internal/configuration/validator/identity_providers.go
index b168e7d71..5a75f8460 100644
--- a/internal/configuration/validator/identity_providers.go
+++ b/internal/configuration/validator/identity_providers.go
@@ -115,7 +115,7 @@ func validateOIDCAuthorizationPoliciesPolicyRules(i int, policy string, config *
for _, subjects := range config.AuthorizationPolicies[policy].Rules[i].Subjects {
for _, subject := range subjects {
- if !IsSubjectValidStrict(subject) {
+ if !IsSubjectValidBasic(subject) {
validator.Push(fmt.Errorf(errFmtOIDCPolicyRuleSubjectInvalid, policy, i+1, subject))
n = -1
diff --git a/internal/configuration/validator/keys.go b/internal/configuration/validator/keys.go
index f53f8ee5a..ee2179135 100644
--- a/internal/configuration/validator/keys.go
+++ b/internal/configuration/validator/keys.go
@@ -78,7 +78,7 @@ func NewKeyPattern(key string) (pattern *regexp.Regexp, err error) {
}
}
-var reIsMapKey = regexp.MustCompile(`\.\*(\[]|\.)`)
+var reIsMapKey = regexp.MustCompile(`\.\*(\[]|\.|$)`)
// NewKeyMapPattern returns a pattern required to match map keys.
func NewKeyMapPattern(key string) (pattern *regexp.Regexp, err error) {
@@ -92,6 +92,10 @@ func NewKeyMapPattern(key string) (pattern *regexp.Regexp, err error) {
for i, part := range parts {
if i != 0 && !strings.HasPrefix(part, "[]") {
+ if len(parts) == i+1 && strings.HasSuffix(key, ".*") {
+ continue
+ }
+
buf.WriteString("\\.")
}
@@ -111,7 +115,7 @@ func NewKeyMapPattern(key string) (pattern *regexp.Regexp, err error) {
}
if i < n {
- buf.WriteString("\\.[a-z0-9](([a-z0-9-_/]+)?[a-z0-9])?")
+ buf.WriteString("\\.[a-zA-Z0-9](([a-zA-Z0-9/_-]+)?[a-zA-Z0-9])?")
}
}
diff --git a/internal/configuration/validator/storage_test.go b/internal/configuration/validator/storage_test.go
index 29f9d42c4..fae3ab681 100644
--- a/internal/configuration/validator/storage_test.go
+++ b/internal/configuration/validator/storage_test.go
@@ -116,6 +116,24 @@ func (suite *StorageSuite) TestShouldSetDefaultMySQLTLSServerName() {
suite.Assert().Equal("mysql", suite.config.MySQL.TLS.ServerName)
}
+func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidMySQLAddressScheme() {
+ suite.config.MySQL = &schema.StorageMySQL{
+ StorageSQL: schema.StorageSQL{
+ Address: &schema.AddressTCP{Address: MustParseAddress("udp://mysql:1234")},
+ Username: "myuser",
+ Password: "pass",
+ Database: "database",
+ },
+ }
+
+ ValidateStorage(suite.config, suite.val)
+
+ 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'")
+}
+
func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidMySQLTLSVersion() {
suite.config.MySQL = &schema.StorageMySQL{
StorageSQL: schema.StorageSQL{
@@ -213,6 +231,27 @@ func (suite *StorageSuite) TestShouldValidatePostgresSchemaDefault() {
suite.Assert().Equal("public", suite.config.PostgreSQL.Schema)
}
+func (suite *StorageSuite) TestShouldValidatePostgresPortDefault() {
+ suite.config.PostgreSQL = &schema.StoragePostgreSQL{
+ StorageSQL: schema.StorageSQL{
+ Address: &schema.AddressTCP{
+ Address: MustParseAddress("tcp://postgre"),
+ },
+ Username: "myuser",
+ Password: "pass",
+ Database: "database",
+ },
+ Schema: "public",
+ }
+
+ ValidateStorage(suite.config, suite.val)
+
+ suite.Assert().Len(suite.val.Warnings(), 0)
+ suite.Assert().Len(suite.val.Errors(), 0)
+
+ suite.Equal(uint16(5432), suite.config.PostgreSQL.Address.Port())
+}
+
func (suite *StorageSuite) TestShouldValidatePostgresTLSDefaults() {
suite.config.PostgreSQL = &schema.StoragePostgreSQL{
StorageSQL: schema.StorageSQL{
diff --git a/internal/configuration/validator/util_test.go b/internal/configuration/validator/util_test.go
index a41c1d138..49ff521ec 100644
--- a/internal/configuration/validator/util_test.go
+++ b/internal/configuration/validator/util_test.go
@@ -11,6 +11,12 @@ import (
"github.com/authelia/authelia/v4/internal/oidc"
)
+func TestMiscMissingCoverage(t *testing.T) {
+ kid, err := jwkCalculateKID(struct{}{}, nil, "")
+ assert.NoError(t, err)
+ assert.Equal(t, "", kid)
+}
+
func TestIsCookieDomainValid(t *testing.T) {
testCases := []struct {
domain string
diff --git a/internal/utils/ip.go b/internal/utils/ip.go
new file mode 100644
index 000000000..c5547fda5
--- /dev/null
+++ b/internal/utils/ip.go
@@ -0,0 +1,32 @@
+package utils
+
+import (
+ "net"
+ "strings"
+)
+
+// ParseHostCIDR parses a raw string as a *net.IPNet similar to net.ParseCIDR, in fact it leverages it. The only
+// differences between the functions is if the input does not contain a single '/' it first parses it with net.ParseIP
+// to determine if it's a IPv4 or IPv6 and then adds the relevant CIDR suffix for a single host, and it only returns the
+// *net.IPNet and error, discarding the net.IP.
+func ParseHostCIDR(s string) (cidr *net.IPNet, err error) {
+ switch strings.Count(s, "/") {
+ case 1:
+ _, cidr, err = net.ParseCIDR(s)
+
+ return
+ case 0:
+ switch n := net.ParseIP(s); {
+ case n == nil:
+ return nil, &net.ParseError{Type: "CIDR address", Text: s}
+ case n.To4() == nil:
+ _, cidr, err = net.ParseCIDR(s + "/128")
+ default:
+ _, cidr, err = net.ParseCIDR(s + "/32")
+ }
+
+ return
+ default:
+ return nil, &net.ParseError{Type: "CIDR address", Text: s}
+ }
+}
diff --git a/internal/utils/ip_test.go b/internal/utils/ip_test.go
new file mode 100644
index 000000000..8b63754fc
--- /dev/null
+++ b/internal/utils/ip_test.go
@@ -0,0 +1,128 @@
+package utils
+
+import (
+ "net"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestParseHostCIDR(t *testing.T) {
+ mustParse := func(in string) *net.IPNet {
+ _, out, err := net.ParseCIDR(in)
+ require.NoError(t, err)
+
+ return out
+ }
+
+ testCases := []struct {
+ name string
+ have string
+ expected *net.IPNet
+ err string
+ }{
+ {
+ "ShouldParseIPv4",
+ "192.168.1.1",
+ mustParse("192.168.1.1/32"),
+ "",
+ },
+ {
+ "ShouldParseIPv4Zero",
+ "0.0.0.0",
+ mustParse("0.0.0.0/32"),
+ "",
+ },
+ {
+ "ShouldParseIPv4ZeroWithZeroCIDR",
+ "0.0.0.0/0",
+ mustParse("0.0.0.0/0"),
+ "",
+ },
+ {
+ "ShouldParseIPv6",
+ "2001:db8:3333:4444:5555:6666:7777:8888",
+ mustParse("2001:db8:3333:4444:5555:6666:7777:8888/128"),
+ "",
+ },
+ {
+ "ShouldParseIPv4WithCIDR",
+ "192.168.1.1/24",
+ mustParse("192.168.1.0/24"),
+ "",
+ },
+ {
+ "ShouldParseIPv6WithCIDR",
+ "2001:db8:3333:4444:5555:6666:7777:8888/56",
+ mustParse("2001:db8:3333:4400::/56"),
+ "",
+ },
+ {
+ "ShouldParseIPv6Zero",
+ "::1",
+ mustParse("::1/128"),
+ "",
+ },
+ {
+ "ShouldParseIPv6ZeroWithCIDR",
+ "::1/0",
+ mustParse("::1/0"),
+ "",
+ },
+ {
+ "ShouldNotParseMultipleSlashes",
+ "192.168.1.1/24/0",
+ nil,
+ "invalid CIDR address: 192.168.1.1/24/0",
+ },
+ {
+ "ShouldNotParseString",
+ "abc",
+ nil,
+ "invalid CIDR address: abc",
+ },
+ {
+ "ShouldNotParseEmptyString",
+ "",
+ nil,
+ "invalid CIDR address: ",
+ },
+ {
+ "ShouldNotParseInvalidIPv4Format",
+ "256.256.256.256",
+ nil,
+ "invalid CIDR address: 256.256.256.256",
+ },
+ {
+ "ShouldNotParseIPv4IncompleteOctets",
+ "192.168.1",
+ nil,
+ "invalid CIDR address: 192.168.1",
+ },
+ {
+ "ShouldNotParseIPv6InvalidCIDR",
+ "2001:db8::1/129",
+ nil,
+ "invalid CIDR address: 2001:db8::1/129",
+ },
+ {
+ "ShouldNotParseIPv4InvalidCIDR",
+ "192.168.1.1/33",
+ nil,
+ "invalid CIDR address: 192.168.1.1/33",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ actual, err := ParseHostCIDR(tc.have)
+ if tc.err != "" {
+ assert.EqualError(t, err, tc.err)
+ } else {
+ require.NoError(t, err)
+ assert.Equal(t, tc.expected, actual)
+ }
+ })
+ }
+}