Files
computer-playbook/roles/web-app-yourls/vars/main.yml
kevinveenbirkenbachandClaude Opus 5 1bd8cd5619 fix(yourls): let every replica salt from the same cookie key
YOURLS_COOKIEKEY salts both yourls_cookie_value() and yourls_create_nonce()
through yourls_salt(). The role never exported it, so config-container.php
took its `getenv('YOURLS_COOKIEKEY') ?: 'modify this text with something
random'` fallback and the image entrypoint replaced that placeholder with
`head -c1m /dev/urandom | sha1sum` - once per container.

In compose that is invisible: one container, one key. In swarm the replica
count follows the inventory group, and the group holds three hosts, so three
tasks each salted differently. A login form rendered by one replica carried a
nonce the next could not verify, and YOURLS answered the POST with
yourls_die( 'Unauthorized action or expired link', 'Error', 403 ) - the exact
403 body captured in run 34687966607, job 103540992501. The login cookie fails
the same way, which is why the page snapshot shows YOURLS's own login screen
again and the administrator persona reported no reachable logout control.

Only the SSO-less variant can show it. With sso enabled oauth2-proxy carries
the identity and the native login, and with it the nonce path, is never
exercised.

The key becomes a stored credential beside administrator_password, same
algorithm and validation, so it is generated once per deployment and read back
identical. With the variable present the getenv branch wins and the entrypoint
leaves the placeholder alone.

Unchanged in origin/main, where env.j2 carries no COOKIEKEY and the variants
and replica lookup are identical, so any swarm deployment running YOURLS
without SSO on more than one replica has the same defect.

The first deploy after this invalidates existing sessions and open nonces,
because the salt changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-12 16:30:20 +02:00

20 lines
1.2 KiB
YAML

---
# General
application_id: "web-app-yourls"
entity_name: "{{ application_id | get_entity_name }}"
YOURLS_USER: "{{ lookup('config', application_id, 'users.administrator.username') }}"
YOURLS_PASSWORD: "{{ lookup('config', application_id, 'secrets.credentials.administrator_password') }}"
YOURLS_COOKIE_KEY: "{{ lookup('config', application_id, 'secrets.credentials.cookie_key') }}"
YOURLS_VERSION: "{{ lookup('config', application_id, 'services.yourls.version') }}"
YOURLS_IMAGE: "{{ lookup('config', application_id, 'services.yourls.image') }}"
YOURLS_CONTAINER: "{{ lookup('config', application_id, 'services.yourls.name') }}"
YOURLS_CONTAINER_ADDRESS: "{{ lookup('container_address', application_id, entity_name) }}"
YOURLS_ADMIN_LOCATION: "{{ lookup('config', application_id, 'server.locations.admin') }}"
YOURLS_LANDINGPAGE_STATUS_CODE: "{{ lookup('config', application_id, 'server.status_codes.default') }}"
YOURLS_SSO_PROXY_GATED: "{{ lookup('sso', application_id, 'is_proxy_gated') }}"
YOURLS_SSO_ADMIN_GROUP: "/{{ lookup('rbac_group_path', application_id=application_id, role='administrator') }}"
container_port: "{{ lookup('config', application_id, 'services.' ~ entity_name ~ '.ports.internal.http') }}"