Replaced OIDC login for gitea with oauth2 proxy and LDAP to guaranty correct username etc.

This commit is contained in:
Kevin Veen-Birkenbach 2025-06-27 02:19:12 +02:00
parent 6d4723b321
commit bb73e948d3
No known key found for this signature in database
GPG Key ID: 44D8F11FD62F878E
27 changed files with 241 additions and 78 deletions

View File

@ -105,6 +105,7 @@ class InventoryManager:
"""Generate a value based on the provided algorithm.""" """Generate a value based on the provided algorithm."""
if algorithm == "random_hex": if algorithm == "random_hex":
return secrets.token_hex(64) return secrets.token_hex(64)
if algorithm == "sha256": if algorithm == "sha256":
return hashlib.sha256(secrets.token_bytes(32)).hexdigest() return hashlib.sha256(secrets.token_bytes(32)).hexdigest()
if algorithm == "sha1": if algorithm == "sha1":
@ -116,4 +117,7 @@ class InventoryManager:
return self.generate_secure_alphanumeric(64) return self.generate_secure_alphanumeric(64)
if algorithm == "base64_prefixed_32": if algorithm == "base64_prefixed_32":
return "base64:" + base64.b64encode(secrets.token_bytes(32)).decode() return "base64:" + base64.b64encode(secrets.token_bytes(32)).decode()
if algorithm == "random_hex_16":
# 16 Bytes → 32 Hex-Characters
return secrets.token_hex(16)
return "undefined" return "undefined"

View File

@ -38,9 +38,10 @@ defaults_oidc:
# Helper Variables: # Helper Variables:
# Keep in mind to mapp this variables if there is ever the possibility for the user to define them in the inventory # Keep in mind to mapp this variables if there is ever the possibility for the user to define them in the inventory
_ldap_dn_base: "dc={{primary_domain_sld}},dc={{primary_domain_tld}}" _ldap_dn_base: "dc={{primary_domain_sld}},dc={{primary_domain_tld}}"
_ldap_server_port: "{% if applications.ldap.network.docker | bool %}{{ ports.localhost.ldap.ldap }}{% else %}{{ ports.localhost.ldaps.ldap }}{% endif %}" _ldap_server_port: "{% if applications.ldap.network.docker | bool %}{{ ports.localhost.ldap.ldap }}{% else %}{{ ports.localhost.ldaps.ldap }}{% endif %}"
_ldap_user_id: "uid" _ldap_user_id: "uid"
_ldap_filters_users_all: "(|(objectclass=inetOrgPerson))"
ldap: ldap:
# Distinguished Names (DN) # Distinguished Names (DN)
@ -59,7 +60,9 @@ ldap:
# Attribut to identify the user # Attribut to identify the user
user_id: "{{ _ldap_user_id }}" user_id: "{{ _ldap_user_id }}"
mail: "mail" mail: "mail"
name: "cn" fullname: "cn"
firstname: "givenname"
surname: "sn"
# Password to access dn.bind # Password to access dn.bind
bind_credential: "{{applications.ldap.credentials.administrator_database_password}}" bind_credential: "{{applications.ldap.credentials.administrator_database_password}}"
server: server:
@ -74,5 +77,9 @@ ldap:
- inetOrgPerson # Extended Internet / intranet person RFC 2798 - inetOrgPerson # Extended Internet / intranet person RFC 2798
- posixAccount # POSIX/UNIX login attributes (uidNumber, gidNumber …) RFC 2307 - posixAccount # POSIX/UNIX login attributes (uidNumber, gidNumber …) RFC 2307
- nextcloudUser # Nextcloud-specific auxiliary attributes (nextcloudQuota, nextcloudEnabled) Nextcloud schema - nextcloudUser # Nextcloud-specific auxiliary attributes (nextcloudQuota, nextcloudEnabled) Nextcloud schema
- ldapPublicKey # Necessary for setting SSH keys for gitea
filters: filters:
user_filter: "(&(|(objectclass=inetOrgPerson))({{_ldap_user_id}}=%{{_ldap_user_id}}))" users:
login: "(&{{ _ldap_filters_users_all }}({{_ldap_user_id}}=%{{_ldap_user_id}}))"
all: "{{ _ldap_filters_users_all }}"

View File

@ -165,7 +165,7 @@ run:
- exec: rails r "SiteSetting.ldap_bind_password = '{{ ldap.bind_credential }}'" - exec: rails r "SiteSetting.ldap_bind_password = '{{ ldap.bind_credential }}'"
# LDAP additional configuration # LDAP additional configuration
- exec: rails r "SiteSetting.ldap_user_filter = '{{ ldap.filters.user_filter }}'" - exec: rails r "SiteSetting.ldap_user_filter = '{{ ldap.filters.users.login }}'"
- exec: rails r "SiteSetting.ldap_group_base_dn = '{{ ldap.dn.groups }}'" - exec: rails r "SiteSetting.ldap_group_base_dn = '{{ ldap.dn.groups }}'"
- exec: rails r "SiteSetting.ldap_group_member_check = 'memberUid'" - exec: rails r "SiteSetting.ldap_group_member_check = 'memberUid'"

View File

@ -27,3 +27,12 @@ To access the database execute
``` ```
## bash in application ## bash in application
docker-compose exec -it application /bin/sh docker-compose exec -it application /bin/sh
## user management
### Change password
```bash
docker-compose exec --user git application gitea admin user change-password \
--username administrator \
--password "MyNewSecureP@ssw0rd"
```

View File

@ -1,5 +1,5 @@
credentials: credentials:
oauth2_proxy_cookie_secret: oauth2_proxy_cookie_secret:
description: "Secret used to encrypt cookies for the OAuth2 proxy (hex-encoded, 16 bytes)" description: "Secret used to encrypt cookies for the OAuth2 proxy (hex-encoded, 16 bytes)"
algorithm: "sha256" algorithm: "random_hex_16"
validation: "^[a-f0-9]{32}$" validation: "^[a-f0-9]{32}$"

View File

@ -0,0 +1,7 @@
- name: Execute OIDC Cleanup Routine
include_tasks: cleanup/oidc.yml
when: not (applications | is_feature_enabled('oidc', application_id))
- name: Execute LDAP Cleanup Routine
include_tasks: cleanup/ldap.yml
when: not (applications | is_feature_enabled('ldap', application_id))

View File

@ -0,0 +1,22 @@
- name: "Lookup existing LDAP auth source ID"
shell: |
docker-compose -f "{{ docker_compose.directories.instance }}/docker-compose.yml" \
exec -T --user git application \
gitea admin auth list \
| awk -v name="LDAP ({{ primary_domain }})" '$0 ~ name {print $1; exit}'
args:
chdir: "{{ docker_compose.directories.instance }}"
register: ldap_source_id_raw
failed_when: false
changed_when: false
- name: "Delete existing LDAP auth source if present"
shell: |
docker-compose -f "{{ docker_compose.directories.instance }}/docker-compose.yml" \
exec -T --user git application \
gitea admin auth delete --id {{ ldap_source_id_raw.stdout }}
args:
chdir: "{{ docker_compose.directories.instance }}"
when: ldap_source_id_raw.stdout != ""
register: ldap_delete
failed_when: ldap_delete.rc != 0

View File

@ -0,0 +1,23 @@
- name: "Lookup existing OIDC auth source ID"
shell: |
docker-compose -f "{{ docker_compose.directories.instance }}/docker-compose.yml" \
exec -T --user git application \
gitea admin auth list \
| awk -v name="{{ oidc.button_text }}" '$0 ~ name {print $1; exit}'
args:
chdir: "{{ docker_compose.directories.instance }}"
register: oidc_source_id_raw
failed_when: false
changed_when: false
- name: "Delete existing OIDC auth source if present"
shell: |
docker-compose -f "{{ docker_compose.directories.instance }}/docker-compose.yml" \
exec -T --user git application \
gitea admin auth delete --id {{ oidc_source_id_raw.stdout }}
args:
chdir: "{{ docker_compose.directories.instance }}"
when: oidc_source_id_raw.stdout != ""
register: oidc_delete
failed_when: oidc_delete.rc != 0

View File

@ -45,10 +45,21 @@
changed_when: "'has been successfully created' in create_admin.stdout" changed_when: "'has been successfully created' in create_admin.stdout"
failed_when: create_admin.rc != 0 and 'user already exists' not in create_admin.stderr failed_when: create_admin.rc != 0 and 'user already exists' not in create_admin.stderr
- name: Execute OIDC Routine - name: "Wait until Gitea setup and migrations are ready"
include_tasks: oidc.yml uri:
vars: url: "http://127.0.0.1:{{ ports.localhost.http[application_id] }}/api/v1/version"
action: add method: GET
register: oidc_add status_code: 200
ignore_errors: true return_content: no
when: applications | is_feature_enabled('oidc', application_id) register: gitea_ready
until: gitea_ready.status == 200
retries: 20
delay: 5
when: applications | is_feature_enabled('oidc', application_id) or applications | is_feature_enabled('ldap', application_id)
- name: Execute Setup Routines
include_tasks: setup.yml
- name: Execute Cleanup Routines
include_tasks: cleanup.yml
when: mode_cleanup

View File

@ -0,0 +1,7 @@
- name: Execute OIDC Setup Routine
include_tasks: setup/oidc.yml
when: applications | is_feature_enabled('oidc', application_id)
- name: Execute LDAP Setup Routine
include_tasks: setup/ldap.yml
when: applications | is_feature_enabled('ldap', application_id)

View File

@ -0,0 +1,66 @@
- name: "Add LDAP Authentication Source"
shell: |
docker-compose -f "{{ docker_compose.directories.instance }}/docker-compose.yml" \
exec -T --user git application \
gitea admin auth add-ldap \
--name "LDAP ({{ primary_domain }})" \
--host "{{ ldap.server.domain }}" \
--port {{ ldap.server.port }} \
--security-protocol "{{ ldap.server.security | trim or 'unencrypted' }}" \
--bind-dn "{{ ldap.dn.administrator }}" \
--bind-password "{{ ldap.bind_credential }}" \
--user-search-base "{{ ldap.dn.users }}" \
--user-filter "{{ ldap.filters.users.login }}" \
--username-attribute "{{ ldap.attributes.user_id }}" \
--firstname-attribute "{{ ldap.attributes.firstname }}" \
--surname-attribute "{{ ldap.attributes.surname }}" \
--email-attribute "{{ ldap.attributes.mail }}" \
--synchronize-users # turns on per-login sync
args:
chdir: "{{ docker_compose.directories.instance }}"
register: ldap_manage
failed_when: ldap_manage.rc != 0 and "login source already exists" not in ldap_manage.stderr
- name: "Lookup existing LDAP auth source ID"
shell: |
docker-compose -f "{{ docker_compose.directories.instance }}/docker-compose.yml" \
exec -T --user git application \
gitea admin auth list \
| tail -n +2 \
| grep -F "LDAP ({{ primary_domain }})" \
| awk '{print $1; exit}'
args:
chdir: "{{ docker_compose.directories.instance }}"
register: ldap_source_id_raw
failed_when:
- ldap_source_id_raw.rc != 0
- ldap_source_id_raw.stdout == ""
changed_when: false
- name: "Set LDAP source ID fact"
set_fact:
ldap_source_id: "{{ ldap_source_id_raw.stdout }}"
- name: "Update LDAP Authentication Source"
shell: |
docker-compose -f "{{ docker_compose.directories.instance }}/docker-compose.yml" \
exec -T --user git application \
gitea admin auth update-ldap \
--id {{ ldap_source_id }} \
--name "LDAP ({{ primary_domain }})" \
--host "{{ ldap.server.domain }}" \
--port {{ ldap.server.port }} \
--security-protocol "{{ ldap.server.security | trim or 'unencrypted' }}" \
--bind-dn "{{ ldap.dn.administrator }}" \
--bind-password "{{ ldap.bind_credential }}" \
--user-search-base "{{ ldap.dn.users }}" \
--user-filter "(&(objectClass=inetOrgPerson)(uid=%s))" \
--username-attribute "{{ ldap.attributes.user_id }}" \
--firstname-attribute "{{ ldap.attributes.firstname }}" \
--surname-attribute "{{ ldap.attributes.surname }}" \
--email-attribute "{{ ldap.attributes.mail }}" \
--synchronize-users
args:
chdir: "{{ docker_compose.directories.instance }}"
register: ldap_manage
failed_when: ldap_manage.rc != 0

View File

@ -1,14 +1,3 @@
- name: "Wait until Gitea setup and migrations are ready"
uri:
url: "http://127.0.0.1:{{ ports.localhost.http[application_id] }}/api/v1/version"
method: GET
status_code: 200
return_content: no
register: gitea_ready
until: gitea_ready.status == 200
retries: 20
delay: 5
- name: "Add Keycloak OIDC Provider" - name: "Add Keycloak OIDC Provider"
shell: | shell: |
docker-compose -f "{{ docker_compose.directories.instance }}/docker-compose.yml" \ docker-compose -f "{{ docker_compose.directories.instance }}/docker-compose.yml" \

View File

@ -2,6 +2,8 @@ services:
{% include 'roles/docker-central-database/templates/services/' + database_type + '.yml.j2' %} {% include 'roles/docker-central-database/templates/services/' + database_type + '.yml.j2' %}
{% include 'roles/docker-oauth2-proxy/templates/container.yml.j2' %}
application: application:
{% include 'roles/docker-compose/templates/services/base.yml.j2' %} {% include 'roles/docker-compose/templates/services/base.yml.j2' %}
image: "{{ applications[application_id].images.gitea }}" image: "{{ applications[application_id].images.gitea }}"

View File

@ -1,20 +1,29 @@
# Configuration # Configuration
# @see https://docs.gitea.com/next/administration/config-cheat-sheet#repository-repository # @see https://docs.gitea.com/next/administration/config-cheat-sheet#repository-repository
# General
DOMAIN={{domains | get_domain(application_id)}}
RUN_MODE="{{ 'dev' if (CYMAIS_ENVIRONMENT | lower) == 'development' else 'prod' }}"
ROOT_URL="{{ web_protocol }}://{{domains | get_domain(application_id)}}/"
APP_NAME="{{ applications[application_id].title }}"
USER_UID=1000 USER_UID=1000
USER_GID=1000 USER_GID=1000
# Logging configuration
GITEA__log__MODE=console
GITEA__log__LEVEL={% if enable_debug | bool %}Debug{% else %}Info{% endif %}
# Database
DB_TYPE=mysql DB_TYPE=mysql
DB_HOST={{database_host}}:{{database_port}} DB_HOST={{database_host}}:{{database_port}}
DB_NAME={{database_name}} DB_NAME={{database_name}}
DB_USER={{database_username}} DB_USER={{database_username}}
DB_PASSWD={{database_password}} DB_PASSWD={{database_password}}
# SSH
SSH_PORT={{ports.public.ssh[application_id]}} SSH_PORT={{ports.public.ssh[application_id]}}
SSH_LISTEN_PORT=22 SSH_LISTEN_PORT=22
DOMAIN={{domains | get_domain(application_id)}}
SSH_DOMAIN={{domains | get_domain(application_id)}} SSH_DOMAIN={{domains | get_domain(application_id)}}
RUN_MODE="{{ 'dev' if (CYMAIS_ENVIRONMENT | lower) == 'development' else 'prod' }}"
ROOT_URL="{{ web_protocol }}://{{domains | get_domain(application_id)}}/"
APP_NAME="{{ applications[application_id].title }}"
# Mail Configuration # Mail Configuration
# @see https://docs.gitea.com/next/installation/install-with-docker#managing-deployments-with-environment-variables # @see https://docs.gitea.com/next/installation/install-with-docker#managing-deployments-with-environment-variables
@ -35,38 +44,18 @@ GITEA__REPOSITORY__DEFAULT_PUSH_CREATE_PRIVATE={{ applications[application_id].c
GITEA__security__INSTALL_LOCK=true # Locks the installation page GITEA__security__INSTALL_LOCK=true # Locks the installation page
{% if applications | is_feature_enabled('oidc',application_id) %} # (De)activate OIDC
GITEA__openid__ENABLE_OPENID_SIGNUP={{ applications | is_feature_enabled('oidc',application_id) | lower }}
GITEA__openid__ENABLE_OPENID_SIGNUP={{ applications | is_feature_enabled('oidc',application_id) | lower }}
GITEA__openid__ENABLE_OPENID_SIGNUP=true {% if applications | is_feature_enabled('oidc',application_id) or applications | is_feature_enabled('ldap',application_id) %}
GITEA__openid__ENABLE_OPENID_SIGNUP=true
{% endif %} EXTERNAL_USER_DISABLE_FEATURES=deletion,manage_credentials,change_username,change_full_name
{% if applications | is_feature_enabled('ldap',application_id) %} {% if applications | is_feature_enabled('ldap',application_id) %}
GITEA__ldap__SYNC_USER_ON_LOGIN=true
{% endif %}
# ------------------------------------------------
# LDAP Authentication (via BindDN)
# ------------------------------------------------
GITEA__auth__LDAP__ENABLED={{ applications | is_feature_enabled('ldap',application_id) | string | lower }}
GITEA__auth__LDAP__HOST={{ ldap.server.domain }}
GITEA__auth__LDAP__PORT={{ ldap.server.port }}
# security protocol: "", "SSL" or "TLS"
GITEA__auth__LDAP__SECURITY={{ ldap.server.security | trim or "unencrypted" }}
GITEA__auth__LDAP__BIND_DN={{ ldap.dn.administrator }}
GITEA__auth__LDAP__BIND_PASSWORD={{ ldap.bind_credential }}
GITEA__auth__LDAP__USER_SEARCH_BASE={{ ldap.dn.users }}
GITEA__auth__LDAP__USER_FILTER={{ ldap.filters.user_filter }}
# map LDAP attributes to Gitea fields
GITEA__auth__LDAP__ATTRIBUTE_USERNAME={{ ldap.attributes.user_id }}
GITEA__auth__LDAP__ATTRIBUTE_FULL_NAME={{ ldap.attributes.name }}
GITEA__auth__LDAP__ATTRIBUTE_MAIL={{ ldap.attributes.mail }}
# ------------------------------------------------
# Periodic sync for external LDAP users
# ------------------------------------------------
GITEA__cron__SYNC_EXTERNAL_USERS_ENABLED=true
# default: sync daily at midnight
GITEA__cron__SYNC_EXTERNAL_USERS_CRON=0 0 * * *
{% endif %} {% endif %}
# ------------------------------------------------ # ------------------------------------------------

View File

@ -11,12 +11,12 @@ features:
css: false css: false
portfolio_iframe: true portfolio_iframe: true
central_database: true central_database: true
ldap: false # Deactivated because OIDC is implemented ldap: true
oauth2: false # Deactivated. Use OIDC instead. oauth2: true
oidc: true oidc: false # Deactivated because users aren't auto-created.
oauth2_proxy: oauth2_proxy:
application: "application" application: "application"
port: "80" port: "3000"
acl: acl:
blacklist: blacklist:
- "/user/login" - "/user/login"

View File

@ -1944,7 +1944,7 @@
"true" "true"
], ],
"ldap.full.name.attribute": [ "ldap.full.name.attribute": [
"{{ ldap.attributes.name }}" "{{ ldap.attributes.fullname }}"
] ]
} }
}, },

View File

@ -1,7 +1,7 @@
credentials: credentials:
oauth2_proxy_cookie_secret: oauth2_proxy_cookie_secret:
description: "Secret used to encrypt OAuth2 proxy cookies (hex-encoded, 16 bytes)" description: "Secret used to encrypt cookies for the OAuth2 proxy (hex-encoded, 16 bytes)"
algorithm: "sha256" algorithm: "random_hex_16"
validation: "^[a-f0-9]{32}$" validation: "^[a-f0-9]{32}$"
administrator_password: administrator_password:

View File

@ -18,6 +18,8 @@ csp:
script-src-elem: script-src-elem:
unsafe-inline: true unsafe-inline: true
unsafe-eval: true unsafe-eval: true
script-src:
unsafe-inline: true
domains: domains:
aliases: aliases:
- "ldap.{{primary_domain}}" - "ldap.{{primary_domain}}"

View File

@ -0,0 +1,22 @@
- name: "1) Gather all existing user DNs"
community.general.ldap_search:
server_uri: "{{ ldap.server.uri }}"
bind_dn: "{{ ldap.dn.administrator }}"
bind_pw: "{{ ldap.bind_credential }}"
base: "{{ ldap.dn.users }}"
filter: "{{ ldap.filters.users.all }}"
attributes: ["dn"]
register: ldap_existing_users
- name: "2) Update each existing user with all user_objects"
community.general.ldap_attrs:
server_uri: "{{ ldap.server.uri }}"
bind_dn: "{{ ldap.dn.administrator }}"
bind_pw: "{{ ldap.bind_credential }}"
dn: "{{ item.dn }}"
attributes:
objectClass: "{{ ldap.user_objects }}"
state: exact
loop: "{{ ldap_existing_users.entries }}"
loop_control:
label: "{{ item.dn }}"

View File

@ -114,3 +114,6 @@
- data - data
loop_control: loop_control:
loop_var: folder loop_var: folder
- name: "Add Objects to all users"
include_tasks: add_user_objects.yml

View File

@ -6,6 +6,6 @@ credentials:
validation: "^[a-f0-9]{64}$" validation: "^[a-f0-9]{64}$"
oauth2_proxy_cookie_secret: oauth2_proxy_cookie_secret:
description: "Secret used to encrypt cookies in the OAuth2 Proxy (hex-encoded, 16 bytes)" description: "Secret used to encrypt cookies for the OAuth2 proxy (hex-encoded, 16 bytes)"
algorithm: "sha256" algorithm: "random_hex_16"
validation: "^[a-f0-9]{32}$" validation: "^[a-f0-9]{32}$"

View File

@ -107,7 +107,7 @@ plugin_configuration:
- -
appid: "user_ldap" appid: "user_ldap"
configkey: "s01ldap_login_filter" configkey: "s01ldap_login_filter"
configvalue: "{{ ldap.filters.user_filter }}" configvalue: "{{ ldap.filters.users.login }}"
- -
appid: "user_ldap" appid: "user_ldap"
configkey: "s01ldap_login_filter_mode" configkey: "s01ldap_login_filter_mode"
@ -163,7 +163,7 @@ plugin_configuration:
- -
appid: "user_ldap" appid: "user_ldap"
configkey: "s01ldap_userlist_filter" configkey: "s01ldap_userlist_filter"
configvalue: "(|(objectclass=inetOrgPerson))" configvalue: "{{ ldap.filters.users.login }}"
- -
appid: "user_ldap" appid: "user_ldap"
configkey: "s01use_memberof_to_detect_membership" configkey: "s01use_memberof_to_detect_membership"

View File

@ -2,5 +2,5 @@ credentials:
oauth2_proxy_cookie_secret: oauth2_proxy_cookie_secret:
description: "Secret used to encrypt cookies for the OAuth2 proxy (hex-encoded, 16 bytes)" description: "Secret used to encrypt cookies for the OAuth2 proxy (hex-encoded, 16 bytes)"
algorithm: "sha256" algorithm: "random_hex_16"
validation: "^[a-f0-9]{32}$" validation: "^[a-f0-9]{32}$"

View File

@ -1,7 +1,7 @@
credentials: credentials:
oauth2_proxy_cookie_secret: oauth2_proxy_cookie_secret:
description: "Secret used by OAuth2 Proxy to encrypt browser cookies (16 bytes hex-encoded)" description: "Secret used to encrypt cookies for the OAuth2 proxy (hex-encoded, 16 bytes)"
algorithm: "sha256" algorithm: "random_hex_16"
validation: "^[a-f0-9]{32}$" validation: "^[a-f0-9]{32}$"
administrator_password: administrator_password:

View File

@ -1,5 +1,5 @@
credentials: credentials:
oauth2_proxy_cookie_secret: oauth2_proxy_cookie_secret:
description: "Secret used by OAuth2 Proxy to encrypt session cookies (16 bytes hex-encoded)" description: "Secret used to encrypt cookies for the OAuth2 proxy (hex-encoded, 16 bytes)"
algorithm: "sha256" algorithm: "random_hex_16"
validation: "^[a-f0-9]{32}$" validation: "^[a-f0-9]{32}$"

View File

@ -1,5 +1,5 @@
credentials: credentials:
oauth2_proxy_cookie_secret: oauth2_proxy_cookie_secret:
description: "Secret used by OAuth2 Proxy to encrypt session cookies (16 bytes hex-encoded)" description: "Secret used to encrypt cookies for the OAuth2 proxy (hex-encoded, 16 bytes)"
algorithm: "sha256" algorithm: "random_hex_16"
validation: "^[a-f0-9]{32}$" validation: "^[a-f0-9]{32}$"

View File

@ -10,6 +10,6 @@ credentials:
validation: "^\\$2[aby]\\$.{56}$" validation: "^\\$2[aby]\\$.{56}$"
oauth2_proxy_cookie_secret: oauth2_proxy_cookie_secret:
description: "Secret used by OAuth2 Proxy to encrypt browser cookies (16 bytes hex-encoded)" description: "Secret used to encrypt cookies for the OAuth2 proxy (hex-encoded, 16 bytes)"
algorithm: "sha256" algorithm: "random_hex_16"
validation: "^[a-f0-9]{32}$" validation: "^[a-f0-9]{32}$"