mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 02:54:26 +00:00
At -vvv Ansible prints each module argument and result, so a password in a command line, a request body, a header or a fact lands in the deploy log. Nothing enforced masking outside the MCP task files, and no check looked at parents at all. A new lint, test_credential_tasks_are_masked, finds tasks whose printed parts (module arguments, environment, loop) reference an identifier ending in password, secret, token, api_key or private_key, or secrets.credentials. A task passes when it or an enclosing block sets no_log, when every file that pulls it in does so through include_tasks with apply.no_log or import_tasks with no_log (transitively), when the secret only reaches a parameter the module masks itself, or when the module masks its values. no_log on an include_tasks task itself does not count, because it does not reach the included tasks. The rule can be suppressed per task with `# nocheck: credential-masking`, now listed in the suppression catalog, and the lint tests its parent and suppression logic on synthetic cases. It found 39 unmasked tasks in 19 roles, all real: join tokens, client secrets, API tokens in Cloudflare headers, database passwords in command lines, the ChatGPT bot login body and more. Each now sets no_log from MASK_CREDENTIALS_IN_LOGS. The sys-dns-hetzner-rdns alias hetzner_no_log, which only restated that switch, is gone. Not covered yet: template and copy tasks whose rendered file carries a secret print it as a diff under --diff. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
159 lines
6.6 KiB
YAML
159 lines
6.6 KiB
YAML
---
|
|
- name: "✅ Assert correct context"
|
|
ansible.builtin.assert:
|
|
that:
|
|
- application_id == 'svc-db-postgres'
|
|
- database_consumer_id != 'svc-db-postgres'
|
|
|
|
- name: 📐 Normalize postgres credential strings for module args
|
|
ansible.builtin.set_fact:
|
|
postgres_login_password_string: "{{ POSTGRES_PASSWORD | string }}"
|
|
postgres_database_password_string: "{{ database_password | string }}"
|
|
changed_when: false
|
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
|
|
|
- name: "🔍 Postgres | Resolve hosting node + container id"
|
|
ansible.builtin.include_tasks: "{{ lookup('path_absolute', 'roles/sys-svc-compose/tasks/utils/swarm/resolve_host_cid.yml') }}"
|
|
vars:
|
|
resolve_service_key: "{{ entity_name }}"
|
|
resolve_service_name: "{{ lookup('database', database_consumer_id, 'service_name') }}"
|
|
resolve_output_prefix: POSTGRES
|
|
resolve_compose_cid: "{{ lookup('database', database_consumer_id, 'container') }}"
|
|
|
|
- name: ⏳ Wait until postgres TCP auth works
|
|
block:
|
|
- name: ⏳ Wait for Postgres TCP auth
|
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
|
ansible.builtin.shell: > # nocheck: container-exec-resolver POSTGRES_LOCAL_CID resolved at delegate_to runtime
|
|
container exec -e PGPASSWORD={{ POSTGRES_PASSWORD | quote }} {{ POSTGRES_LOCAL_CID | quote }} sh -lc
|
|
'psql -h "{{ POSTGRES_LOCAL_HOST }}" -p "{{ POSTGRES_PORT }}"
|
|
-U postgres -d postgres -c "SELECT 1"'
|
|
delegate_to: "{{ POSTGRES_HOST_NODE }}"
|
|
register: pg_ready
|
|
until: pg_ready.rc == 0
|
|
retries: 30
|
|
delay: 5
|
|
changed_when: false
|
|
|
|
rescue:
|
|
- name: "💾 Postgres | Dump pg_hba.conf into the diagnostics folder" # nocheck: rescue-extra-diagnostics pg_hba.conf is postgres-specific auth config the generic rescue script cannot capture, and it is what a failed TCP-auth wait needs
|
|
ansible.builtin.script: "{{ lookup('path_absolute', 'roles/svc-db-postgres/files/shell/dump_pg_hba.sh') }}"
|
|
environment:
|
|
POSTGRES_CONTAINER_ADDRESS: "{{ POSTGRES_LOCAL_CID }}"
|
|
PGPASSWORD: "{{ POSTGRES_PASSWORD }}"
|
|
delegate_to: "{{ POSTGRES_HOST_NODE }}"
|
|
changed_when: false
|
|
failed_when: false
|
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
|
- name: "🛑 Postgres | fail after pg_hba dump"
|
|
ansible.builtin.fail:
|
|
msg: "Postgres TCP auth never became ready; pg_hba.conf was dumped into the diagnostics folder (the workflow-level rescue capture collects the full snapshot)"
|
|
|
|
- name: "📁 Create database: {{ database_name }}"
|
|
community.postgresql.postgresql_db:
|
|
name: "{{ database_name }}"
|
|
state: present
|
|
login_user: postgres
|
|
login_password: "{{ postgres_login_password_string }}"
|
|
login_host: "{{ POSTGRES_LOCAL_HOST }}"
|
|
login_port: "{{ POSTGRES_PORT }}"
|
|
register: postgresql_result
|
|
until: postgresql_result is succeeded
|
|
retries: "{{ POSTGRES_RETRIES }}"
|
|
delay: "{{ POSTGRES_DELAY }}"
|
|
|
|
- name: "📁 Create database user: {{ database_username }}"
|
|
community.postgresql.postgresql_user:
|
|
name: "{{ database_username }}"
|
|
password: "{{ postgres_database_password_string }}"
|
|
login_db: "{{ database_name }}"
|
|
state: present
|
|
login_user: postgres
|
|
login_password: "{{ postgres_login_password_string }}"
|
|
login_host: "{{ POSTGRES_LOCAL_HOST }}"
|
|
login_port: "{{ POSTGRES_PORT }}"
|
|
register: postgresql_result
|
|
until: postgresql_result is succeeded
|
|
retries: "{{ POSTGRES_RETRIES }}"
|
|
delay: "{{ POSTGRES_DELAY }}"
|
|
|
|
- name: "🟢 POSTGRES | Enable login for role {{ database_username }}"
|
|
# nocheck: database-query — svc-db-postgres bootstraps its own DB; the `database_query` module's `lookup('database', ...)` indirection isn't usable from inside the role that provides the lookup target.
|
|
community.postgresql.postgresql_query:
|
|
login_db: postgres
|
|
login_user: postgres
|
|
login_password: "{{ postgres_login_password_string }}"
|
|
login_host: "{{ POSTGRES_LOCAL_HOST }}"
|
|
login_port: "{{ POSTGRES_PORT }}"
|
|
query: |
|
|
ALTER ROLE "{{ database_username }}"
|
|
WITH LOGIN;
|
|
register: postgres_enable_login
|
|
until: postgres_enable_login is succeeded
|
|
retries: "{{ POSTGRES_RETRIES }}"
|
|
delay: "{{ POSTGRES_DELAY }}"
|
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
|
|
|
- name: "🔐 Grant ALL privileges on tables in public schema to {{ database_username }}"
|
|
community.postgresql.postgresql_privs:
|
|
login_db: "{{ database_name }}"
|
|
roles: "{{ database_username }}"
|
|
objs: ALL_IN_SCHEMA
|
|
privs: ALL
|
|
type: table
|
|
schema: public
|
|
state: present
|
|
login_user: postgres
|
|
login_password: "{{ postgres_login_password_string }}"
|
|
login_host: "{{ POSTGRES_LOCAL_HOST }}"
|
|
login_port: "{{ POSTGRES_PORT }}"
|
|
register: postgresql_result
|
|
until: postgresql_result is succeeded
|
|
retries: "{{ POSTGRES_RETRIES }}"
|
|
delay: "{{ POSTGRES_DELAY }}"
|
|
|
|
- name: "🔐 Grant all privileges on {{ database_name }} to {{ database_username }}"
|
|
community.postgresql.postgresql_privs:
|
|
login_db: "{{ database_name }}"
|
|
roles: "{{ database_username }}"
|
|
type: database
|
|
privs: ALL
|
|
state: present
|
|
login_user: postgres
|
|
login_password: "{{ postgres_login_password_string }}"
|
|
login_host: "{{ POSTGRES_LOCAL_HOST }}"
|
|
login_port: "{{ POSTGRES_PORT }}"
|
|
register: postgresql_result
|
|
until: postgresql_result is succeeded
|
|
retries: "{{ POSTGRES_RETRIES }}"
|
|
delay: "{{ POSTGRES_DELAY }}"
|
|
|
|
- name: "🔧 Set comprehensive schema privileges for {{ database_username }}"
|
|
# nocheck: database-query — svc-db-postgres bootstraps its own DB; the `database_query` module's `lookup('database', ...)` indirection isn't usable from inside the role that provides the lookup target.
|
|
community.postgresql.postgresql_query:
|
|
login_db: "{{ database_name }}"
|
|
login_user: postgres
|
|
login_password: "{{ postgres_login_password_string }}"
|
|
login_host: "{{ POSTGRES_LOCAL_HOST }}"
|
|
login_port: "{{ POSTGRES_PORT }}"
|
|
query: "{{ lookup('template', 'sql/grant_schema_privileges.sql.j2') }}"
|
|
register: postgresql_result
|
|
until: postgresql_result is succeeded
|
|
retries: "{{ POSTGRES_RETRIES }}"
|
|
delay: "{{ POSTGRES_DELAY }}"
|
|
|
|
- name: "✅ Ensure consumer-specific extensions ({{ database_consumer_id }})"
|
|
community.postgresql.postgresql_ext:
|
|
login_db: "{{ database_name }}"
|
|
ext: "{{ item }}"
|
|
state: present
|
|
login_user: postgres
|
|
login_password: "{{ postgres_login_password_string }}"
|
|
login_host: "{{ POSTGRES_LOCAL_HOST }}"
|
|
login_port: "{{ POSTGRES_PORT }}"
|
|
loop: "{{ lookup('service_extensions', 'postgres')[database_consumer_id] | default([]) }}"
|
|
register: postgresql_result
|
|
until: postgresql_result is succeeded
|
|
retries: "{{ POSTGRES_RETRIES }}"
|
|
delay: "{{ POSTGRES_DELAY }}"
|