diff --git a/salt/nginx/etc/nginx.conf b/salt/nginx/etc/nginx.conf index 0c98b7b28..8150265f5 100644 --- a/salt/nginx/etc/nginx.conf +++ b/salt/nginx/etc/nginx.conf @@ -225,6 +225,7 @@ http { limit_req zone=auth_throttle burst={{ NGINXMERGED.config.throttle_login_burst }} nodelay; limit_req_status 429; proxy_pass http://{{ GLOBALS.manager }}:4433; + proxy_set_header Connection "Close"; proxy_read_timeout 90; proxy_connect_timeout 90; proxy_set_header Host $host; @@ -237,6 +238,7 @@ http { location ~ ^/auth/.*?(whoami|logout|settings|errors|webauthn.js) { rewrite /auth/(.*) /$1 break; proxy_pass http://{{ GLOBALS.manager }}:4433; + proxy_set_header Connection "Close"; proxy_read_timeout 90; proxy_connect_timeout 90; proxy_set_header Host $host; diff --git a/salt/postgres/telegraf_users.sls b/salt/postgres/telegraf_users.sls index 1ac7c80ed..28d9d6247 100644 --- a/salt/postgres/telegraf_users.sls +++ b/salt/postgres/telegraf_users.sls @@ -44,12 +44,10 @@ postgres_wait_ready: # would otherwise never get so_telegraf. postgres_create_telegraf_db: cmd.run: - - name: | - if ! docker exec so-postgres psql -U postgres -tAc "SELECT 1 FROM pg_database WHERE datname='so_telegraf'" | grep -q 1; then - docker exec so-postgres psql -v ON_ERROR_STOP=1 -U postgres -c "CREATE DATABASE so_telegraf" - fi + - name: /usr/sbin/so-telegraf-postgres create_db - require: - cmd: postgres_wait_ready + - file: postgres_sbin # Provision the shared group role and schema once. Every per-minion role is a # member of so_telegraf, and each Telegraf connection does SET ROLE so_telegraf @@ -57,68 +55,26 @@ postgres_create_telegraf_db: # on first write are owned by the group role and every member can INSERT/SELECT. postgres_telegraf_group_role: cmd.run: - - name: | - docker exec -i so-postgres psql -v ON_ERROR_STOP=1 -U postgres -d so_telegraf <<'EOSQL' - DO $$ - BEGIN - IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'so_telegraf') THEN - CREATE ROLE so_telegraf NOLOGIN; - END IF; - END - $$; - GRANT CONNECT ON DATABASE so_telegraf TO so_telegraf; - CREATE SCHEMA IF NOT EXISTS telegraf AUTHORIZATION so_telegraf; - GRANT USAGE, CREATE ON SCHEMA telegraf TO so_telegraf; - CREATE SCHEMA IF NOT EXISTS partman; - CREATE EXTENSION IF NOT EXISTS pg_partman SCHEMA partman; - CREATE EXTENSION IF NOT EXISTS pg_cron; - -- Telegraf (running as so_telegraf) calls partman.create_parent() - -- on first write of each metric, which needs USAGE on the partman - -- schema, EXECUTE on its functions/procedures, and write access to - -- partman.part_config so it can register new partitioned parents. - GRANT USAGE, CREATE ON SCHEMA partman TO so_telegraf; - GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA partman TO so_telegraf; - GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA partman TO so_telegraf; - GRANT EXECUTE ON ALL PROCEDURES IN SCHEMA partman TO so_telegraf; - -- partman creates per-parent template tables (partman.template_*) at - -- runtime; default privileges extend DML/sequence access to them. - ALTER DEFAULT PRIVILEGES IN SCHEMA partman - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO so_telegraf; - ALTER DEFAULT PRIVILEGES IN SCHEMA partman - GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO so_telegraf; - -- Hourly partman maintenance. cron.schedule is idempotent by jobname. - SELECT cron.schedule( - 'telegraf-partman-maintenance', - '17 * * * *', - 'CALL partman.run_maintenance_proc()' - ); - EOSQL + - name: /usr/sbin/so-telegraf-postgres group_role - require: - cmd: postgres_create_telegraf_db + - file: postgres_sbin {% set creds = salt['pillar.get']('telegraf:postgres_creds', {}) %} {% for mid, entry in creds.items() %} {% if entry.get('user') and entry.get('pass') %} {% set u = entry.user %} -{% set p = entry.pass | replace("'", "''") %} +{% set p = entry.pass %} postgres_telegraf_role_{{ u }}: cmd.run: - - name: | - docker exec -i so-postgres psql -v ON_ERROR_STOP=1 -U postgres -d so_telegraf <<'EOSQL' - DO $$ - BEGIN - IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '{{ u }}') THEN - EXECUTE format('CREATE ROLE %I WITH LOGIN PASSWORD %L', '{{ u }}', '{{ p }}'); - ELSE - EXECUTE format('ALTER ROLE %I WITH PASSWORD %L', '{{ u }}', '{{ p }}'); - END IF; - END - $$; - GRANT CONNECT ON DATABASE so_telegraf TO "{{ u }}"; - GRANT so_telegraf TO "{{ u }}"; - EOSQL + - name: /usr/sbin/so-telegraf-postgres user + - env: + - ROLE_USER: {{ u | tojson }} + - ROLE_PASS: {{ p | tojson }} + - hide_output: True - require: + - file: postgres_sbin - cmd: postgres_telegraf_group_role {% endif %} @@ -130,21 +86,12 @@ postgres_telegraf_role_{{ u }}: {% set retention = salt['pillar.get']('postgres:telegraf:retention_days', 14) | int %} postgres_telegraf_retention_reconcile: cmd.run: - - name: | - docker exec -i so-postgres psql -v ON_ERROR_STOP=1 -U postgres -d so_telegraf <<'EOSQL' - DO $$ - BEGIN - IF EXISTS (SELECT 1 FROM pg_catalog.pg_extension WHERE extname = 'pg_partman') THEN - UPDATE partman.part_config - SET retention = '{{ retention }} days', - retention_keep_table = false - WHERE parent_table LIKE 'telegraf.%'; - END IF; - END - $$; - EOSQL + - name: /usr/sbin/so-telegraf-postgres retention + - env: + - RETENTION_DAYS: {{ retention }} - require: - cmd: postgres_telegraf_group_role + - file: postgres_sbin {% endif %} diff --git a/salt/postgres/tools/sbin/so-telegraf-postgres b/salt/postgres/tools/sbin/so-telegraf-postgres new file mode 100644 index 000000000..ef7c3f9e6 --- /dev/null +++ b/salt/postgres/tools/sbin/so-telegraf-postgres @@ -0,0 +1,110 @@ +#!/bin/bash +set -e + +# Provision Telegraf state inside the so-postgres container. +# Usage: so-telegraf-postgres +# create_db Ensure the so_telegraf database exists. +# group_role Provision the so_telegraf group role, telegraf/partman schemas, +# pg_partman, pg_cron, and the hourly partman maintenance job. +# user Create or update a per-minion login role granted to so_telegraf. +# Env: ROLE_USER, ROLE_PASS. +# retention Reconcile partman retention on telegraf parents. +# Env: RETENTION_DAYS. + +cmd="${1:?subcommand required}" + +case "$cmd" in + create_db) + if ! docker exec so-postgres psql -U postgres -tAc \ + "SELECT 1 FROM pg_database WHERE datname='so_telegraf'" | grep -q 1; then + docker exec so-postgres psql -v ON_ERROR_STOP=1 -U postgres \ + -c "CREATE DATABASE so_telegraf" + fi + ;; + + group_role) + docker exec -i so-postgres psql -v ON_ERROR_STOP=1 -U postgres -d so_telegraf <<'EOSQL' +DO $$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'so_telegraf') THEN + CREATE ROLE so_telegraf NOLOGIN; + END IF; +END +$$; +GRANT CONNECT ON DATABASE so_telegraf TO so_telegraf; +CREATE SCHEMA IF NOT EXISTS telegraf AUTHORIZATION so_telegraf; +GRANT USAGE, CREATE ON SCHEMA telegraf TO so_telegraf; +CREATE SCHEMA IF NOT EXISTS partman; +CREATE EXTENSION IF NOT EXISTS pg_partman SCHEMA partman; +CREATE EXTENSION IF NOT EXISTS pg_cron; +-- Telegraf (running as so_telegraf) calls partman.create_parent() +-- on first write of each metric, which needs USAGE on the partman +-- schema, EXECUTE on its functions/procedures, and write access to +-- partman.part_config so it can register new partitioned parents. +GRANT USAGE, CREATE ON SCHEMA partman TO so_telegraf; +GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA partman TO so_telegraf; +GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA partman TO so_telegraf; +GRANT EXECUTE ON ALL PROCEDURES IN SCHEMA partman TO so_telegraf; +-- partman creates per-parent template tables (partman.template_*) at +-- runtime; default privileges extend DML/sequence access to them. +ALTER DEFAULT PRIVILEGES IN SCHEMA partman + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO so_telegraf; +ALTER DEFAULT PRIVILEGES IN SCHEMA partman + GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO so_telegraf; +-- Hourly partman maintenance. cron.schedule is idempotent by jobname. +SELECT cron.schedule( + 'telegraf-partman-maintenance', + '17 * * * *', + 'CALL partman.run_maintenance_proc()' +); +EOSQL + ;; + + user) + : "${ROLE_USER:?ROLE_USER is required}" + : "${ROLE_PASS:?ROLE_PASS is required}" + # psql does not substitute :vars inside dollar-quoted strings, so the + # conditional CREATE/ALTER is built outside any DO block and dispatched + # with \gexec. format() handles identifier/literal quoting. + docker exec -i so-postgres psql \ + -v ON_ERROR_STOP=1 \ + -v role_user="$ROLE_USER" \ + -v role_pass="$ROLE_PASS" \ + -U postgres -d so_telegraf <<'EOSQL' +SELECT format( + CASE WHEN EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = :'role_user') + THEN 'ALTER ROLE %I WITH LOGIN PASSWORD %L' + ELSE 'CREATE ROLE %I WITH LOGIN PASSWORD %L' + END, + :'role_user', + :'role_pass' +) \gexec +GRANT CONNECT ON DATABASE so_telegraf TO :"role_user"; +GRANT so_telegraf TO :"role_user"; +EOSQL + ;; + + retention) + : "${RETENTION_DAYS:?RETENTION_DAYS is required}" + # \gset + \if guards against a missing pg_partman without using a DO + # block (psql :var substitution doesn't reach into dollar-quoted code). + docker exec -i so-postgres psql \ + -v ON_ERROR_STOP=1 \ + -v retention_days="$RETENTION_DAYS" \ + -U postgres -d so_telegraf <<'EOSQL' +SELECT CASE WHEN EXISTS (SELECT 1 FROM pg_catalog.pg_extension WHERE extname = 'pg_partman') + THEN 'true' ELSE 'false' END AS has_partman \gset +\if :has_partman +UPDATE partman.part_config +SET retention = :'retention_days' || ' days', + retention_keep_table = false +WHERE parent_table LIKE 'telegraf.%'; +\endif +EOSQL + ;; + + *) + echo "Unknown subcommand: $cmd" >&2 + exit 1 + ;; +esac