From 24268938bd2373fba38c06408ca48d1d5706dfa0 Mon Sep 17 00:00:00 2001 From: Fabio Scotto di Santolo Date: Tue, 8 Sep 2026 18:48:13 +0200 Subject: [PATCH] Hotfix/duckdns token (#8) * fix: source DuckDNS token from Vault * chore: rotate DuckDNS Vault token --- .gitignore | 2 + AGENTS.md | 5 ++ README.it.md | 19 +++++ README.md | 18 ++++ ansible/inventory/group_vars/server.yml | 3 - ansible/inventory/host_vars/prometheus.yml | 1 + .../roles/profile_server/tasks/duckdns.yml | 33 ++++++++ ansible/roles/profile_server/tasks/main.yml | 4 + .../roles/profile_server/templates/duck.sh.j2 | 24 ++++++ ansible/site.yml | 2 + dotfiles/server/duckdns/duck.log | 1 - dotfiles/server/duckdns/duck.sh | 1 - secrets/vault.yml | 83 ++++++++++--------- secrets/vault.yml.example | 1 + 14 files changed, 153 insertions(+), 44 deletions(-) create mode 100644 .gitignore create mode 100644 ansible/roles/profile_server/tasks/duckdns.yml create mode 100644 ansible/roles/profile_server/templates/duck.sh.j2 delete mode 100644 dotfiles/server/duckdns/duck.log delete mode 100644 dotfiles/server/duckdns/duck.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..268f95b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# DuckDNS runtime files contain a rendered Vault token and must stay on the host. +/dotfiles/server/duckdns/ diff --git a/AGENTS.md b/AGENTS.md index e96f268..7f1652c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -53,6 +53,7 @@ Ansible-driven personal infrastructure repo for Fedora and Void desktops, Fedora - AI coding agents: `ansible-playbook ansible/site.yml --limit --tags ai_agents --check --diff` - Mail bootstrap: `sh -n scripts/bootstrap_mail.sh` and `shellcheck scripts/bootstrap_mail.sh` - Server compose render: `docker compose -f /opt/docker/server/docker-compose.yml config` + - DuckDNS config only: `ansible-playbook ansible/site.yml --limit prometheus --tags duckdns --check --diff` ## Conventions - Use FQCN Ansible modules. @@ -96,6 +97,10 @@ The dotfile vars follow the same split: `desktop_common_dotfiles` carries mode-i - Windows applications are installed manually and are not managed from the WSL profile. ## Rocky Server Notes +- DuckDNS is rendered by `profile_server` from host-local `server_duckdns_domain` and + `vault_duckdns_token`. Keep the rotated token in encrypted Vault or untracked local vars, never in + dotfiles. The private `~/duckdns/duck.sh` keeps the existing entrypoint; rendering uses `no_log` + and disables diffs. Provisioning does not execute the updater or change its external schedule. - `rocky_server` is a child of both `platform_rocky` and `server`; `prometheus` is its active target. - The target must already provide `server_username` with local sudo access before the profile runs. - The Rocky profile installs Docker CE, uses firewalld, preserves SELinux enforcement, and renders the diff --git a/README.it.md b/README.it.md index 3c4619e..1dcba60 100644 --- a/README.it.md +++ b/README.it.md @@ -184,6 +184,25 @@ Lo stato attuale del profilo server include: - attivazione di firewalld con servizio SSH esplicitamente abilitato - Syncthing escluso dal profilo server Rocky +### DuckDNS + +`profile_server` genera `~/duckdns/duck.sh` con permessi `0700`, mantenendo il percorso dello +script e `duck.log`. Definire `server_duckdns_domain` negli host vars del server e salvare il +**nuovo token rigenerato** in `vault_duckdns_token`, nel Vault cifrato `secrets/vault.yml` +(`ansible-vault edit secrets/vault.yml`) oppure negli override non versionati `secrets/vault.local.yml`. +Non committare lo script generato e non passare il token sulla riga di comando. Il rendering +nasconde output e diff sensibili; lo script verifica TLS e passa il token a curl tramite stdin. +Il playbook non esegue lo script e non modifica la sua schedulazione esterna. + +```bash +ansible-playbook ansible/site.yml --limit prometheus --tags duckdns --check --diff +ansible-playbook ansible/site.yml --limit prometheus --tags duckdns +``` + +La cancellazione dalla cronologia non revoca il token: rigenerarlo sul pannello DuckDNS. +Dopo la bonifica, riclonare gli altri checkout senza unire nuovamente la vecchia storia; +salvare separatamente eventuali modifiche non committate senza copiare segreti. + ### Migrazione dati Dopo il provisioning Rocky, eseguire `scripts/migrate_prometheus_data.sh` **sul server Ubuntu diff --git a/README.md b/README.md index 0257eaa..7991aff 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,24 @@ ansible-playbook ansible/site.yml --limit prometheus \ The target must already provide `server_username` with local sudo access. +### DuckDNS + +`profile_server` renders `~/duckdns/duck.sh` with mode `0700`, keeping the existing updater path +and `duck.log`. Set `server_duckdns_domain` in the server's host vars and store the **rotated** +`vault_duckdns_token` in encrypted `secrets/vault.yml` (using `ansible-vault edit secrets/vault.yml`) +or untracked `secrets/vault.local.yml`. Never commit the rendered script or put the token on a +command line. Rendering hides secret output/diffs; the updater verifies TLS and passes the token +to curl through stdin. The playbook neither runs the updater nor changes its external schedule. + +```bash +ansible-playbook ansible/site.yml --limit prometheus --tags duckdns --check --diff +ansible-playbook ansible/site.yml --limit prometheus --tags duckdns +``` + +An exposed token must be revoked/regenerated on DuckDNS: deleting it from Git history does not +revoke it. After a history cleanup, re-clone other checkouts rather than merging the old history +back in; preserve any uncommitted work separately without copying secrets. + ### Data migration Provision Rocky first, then run the migration script **on the retired Ubuntu source host**. It is diff --git a/ansible/inventory/group_vars/server.yml b/ansible/inventory/group_vars/server.yml index c5311a4..e947249 100644 --- a/ansible/inventory/group_vars/server.yml +++ b/ansible/inventory/group_vars/server.yml @@ -37,9 +37,6 @@ server_dotfiles: - src: .themes.gitignore dest: .themes.gitignore mode: "0644" - - src: duckdns/ - dest: duckdns/ - mode: preserve server_templates: - src: server/.gitconfig.j2 diff --git a/ansible/inventory/host_vars/prometheus.yml b/ansible/inventory/host_vars/prometheus.yml index f9496dd..edd48ca 100644 --- a/ansible/inventory/host_vars/prometheus.yml +++ b/ansible/inventory/host_vars/prometheus.yml @@ -6,3 +6,4 @@ ansible_port: 22 ansible_ssh_private_key_file: /home/fscotto/.ssh/id_ed25519 server_username: rocky +server_duckdns_domain: fscotto diff --git a/ansible/roles/profile_server/tasks/duckdns.yml b/ansible/roles/profile_server/tasks/duckdns.yml new file mode 100644 index 0000000..3ae073d --- /dev/null +++ b/ansible/roles/profile_server/tasks/duckdns.yml @@ -0,0 +1,33 @@ +--- +- name: Require DuckDNS domain and Vault token before deployment + ansible.builtin.assert: + that: + - >- + server_duckdns_domain | default('') is + regex('[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?', match_type='fullmatch') + - >- + vault_duckdns_token | default('') is + regex('[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}', match_type='fullmatch') + fail_msg: >- + Define server_duckdns_domain in host_vars and the rotated vault_duckdns_token + in encrypted Vault or an untracked local vars file before deploying DuckDNS. + no_log: true + +- name: Ensure private DuckDNS directory exists + ansible.builtin.file: + path: "{{ server_user_home }}/duckdns" + state: directory + owner: "{{ server_username }}" + group: "{{ server_user_group }}" + mode: "0700" + +- name: Render DuckDNS updater with the Vault token + ansible.builtin.template: + src: duck.sh.j2 + dest: "{{ server_user_home }}/duckdns/duck.sh" + owner: "{{ server_username }}" + group: "{{ server_user_group }}" + mode: "0700" + validate: /bin/sh -n %s + no_log: true + diff: false diff --git a/ansible/roles/profile_server/tasks/main.yml b/ansible/roles/profile_server/tasks/main.yml index b302e6d..e5aa68a 100644 --- a/ansible/roles/profile_server/tasks/main.yml +++ b/ansible/roles/profile_server/tasks/main.yml @@ -18,6 +18,10 @@ Server container secrets are missing. Define vault_navidrome_db_password and vault_postgres_root_password in secrets/vault.yml or another vars source. +- name: Configure DuckDNS updater + tags: [dotfiles, dotfiles:server, duckdns] + ansible.builtin.import_tasks: duckdns.yml + - name: Ensure server directories exist tags: [dotfiles, services] ansible.builtin.file: diff --git a/ansible/roles/profile_server/templates/duck.sh.j2 b/ansible/roles/profile_server/templates/duck.sh.j2 new file mode 100644 index 0000000..061abf4 --- /dev/null +++ b/ansible/roles/profile_server/templates/duck.sh.j2 @@ -0,0 +1,24 @@ +#!/bin/sh +# Managed by Ansible. Contains a Vault token; never copy this file into Git. +set -eu +umask 077 + +log_file={{ (server_user_home ~ '/duckdns/duck.log') | quote }} + +# Keep the token out of process arguments and verify the HTTPS certificate. +if ! response=$(curl --fail --silent --show-error --connect-timeout 10 --max-time 30 --config - <<'DUCKDNS_CONFIG' +url = "https://www.duckdns.org/update?domains={{ server_duckdns_domain }}&token={{ vault_duckdns_token }}&ip=" +DUCKDNS_CONFIG +); then + printf 'ERROR\n' > "$log_file" + exit 1 +fi + +case "$response" in + OK) printf 'OK\n' > "$log_file" ;; + *) + printf 'KO\n' > "$log_file" + printf 'DuckDNS update failed.\n' >&2 + exit 1 + ;; +esac diff --git a/ansible/site.yml b/ansible/site.yml index 3a25282..6c57bd2 100644 --- a/ansible/site.yml +++ b/ansible/site.yml @@ -7,6 +7,7 @@ tags: [always] ansible.builtin.include_vars: file: "{{ playbook_dir }}/../secrets/vault.yml" + no_log: true when: >- lookup('ansible.builtin.fileglob', playbook_dir + '/../secrets/vault.yml', errors='ignore', wantlist=True) | length > 0 @@ -15,6 +16,7 @@ tags: [always] ansible.builtin.include_vars: file: "{{ playbook_dir }}/../secrets/vault.local.yml" + no_log: true when: >- lookup('ansible.builtin.fileglob', playbook_dir + '/../secrets/vault.local.yml', errors='ignore', wantlist=True) | length > 0 diff --git a/dotfiles/server/duckdns/duck.log b/dotfiles/server/duckdns/duck.log deleted file mode 100644 index a0aba93..0000000 --- a/dotfiles/server/duckdns/duck.log +++ /dev/null @@ -1 +0,0 @@ -OK \ No newline at end of file diff --git a/dotfiles/server/duckdns/duck.sh b/dotfiles/server/duckdns/duck.sh deleted file mode 100644 index 5810a0f..0000000 --- a/dotfiles/server/duckdns/duck.sh +++ /dev/null @@ -1 +0,0 @@ -echo url="https://www.duckdns.org/update?domains=fscotto&token=d173a037-8a10-4e40-8d9d-bebedbdde086&ip=" | curl -k -o ~/duckdns/duck.log -K - diff --git a/secrets/vault.yml b/secrets/vault.yml index 410ac30..4db5cca 100644 --- a/secrets/vault.yml +++ b/secrets/vault.yml @@ -1,40 +1,45 @@ $ANSIBLE_VAULT;1.1;AES256 -65623833316230316230376465656261383230643661393032396462643232383334383236653134 -3964353234623165633736303035623132643565393461660a323930623462323635613361373836 -37613164656538353734333932326136303532346161663238613466616236636432346534626331 -3434663266366637650a336235363033333562626135333962626265666332386261643666306638 -64343262353661346435326662633833656533643464653665356233316466333432303536356539 -32623430303663616261386636613534326231613366636536303761646335396132623231336361 -61656337313766353533643266396230626439353033373630666336616230316362616135323462 -30306633373234386639633932393437323931613739383135353835393630623937636639343338 -35636530643330376538393262383738333735656230363633383463386366343065633961663562 -37653264326565376166666262643136616333656233386564333030663532353836306131316530 -62633738306434303236623239646638613230323434393761656265393064373235333435393264 -36363936336330636433643133346164643733356239616530653831626165316238643834356236 -36333262313764653137633930663131306132306434376433306230306564366633363738313634 -35316230623134623932396132316530366662643366643439643334366233646334633764346266 -35373764633138343235323931346134613265356238616138323134326338633335336339313862 -61653939366263386431636334333661306538643166353661336561396365366434353830643465 -31626136626336633363653531363064643534396336623331363130313966653966373636663232 -65653132363139653239383636333861323435613362666637366162323062376139633033373563 -66656134313931313965303134613836383534636438363936353836326639346565653031363839 -63623738366261363734376536646532396231616233303061363364383238616434333366613933 -63616539613336393863633361303466353738313666393164343638373530376666663666666235 -33666234353736633335343639623664326566333162333539636235346663663737366432366665 -66373831303666666533343234373466333132343135326261663532663062313939623963383437 -34653732653830313638343631313437623931623531323566306232373165636662316633333438 -66666537623966623932323463323330316337613637383339613637653637333832623965636434 -34653330336663323263353636353139323337646166623862343565646637616136646232373633 -33356134373464646664303865353763323531383661323930353038646333386366353665396431 -64303739643735376235626134663566343165646433393966373961393738323036353437346436 -66656166306561353637623462303039393465363261343164393062643130383736623261383232 -38623036626464613138363635646233323737653137633830316138663835656633336663626338 -33386232633862616432396662343738343462623263303337393533313264396637356134373739 -61323266623665353631316461633462646536386266343934356235363065653162363566623661 -35643332303333306362303538333034316461326363653632313765396664643533373862333533 -37306536356465393933366238663031653630386239383839316365323761316236326330306637 -36373434643864343064646437323736393330326361663231373933613864303339653661616139 -36356134636136333830663031656132656237383331663362313238653133366538323462376632 -63373766383939656265313934636462626633376162623931333363393837303336363036303439 -32643730383735383535633439326564376261343937343934366366373039653761383934316162 -383035326562353431616536646238323164 +62633966363435396136666635303362663932366464323231363563373462383134326361346533 +3465353461326565356435633866393230383838356235340a333036383038653134323066663031 +37373135643331356366373831343163363835656462343962636538623937356231376337616330 +3831626261343761310a306133633264613430613165343265303664383439333230663665333231 +30393934323438333066383737663665316634613932353232343334626261636661393434646163 +63316537383866353338353261373161623562343037383032633932383630666339633234326639 +64303735663532376433393464616230616236353134383734623132333061356664613039323265 +35373434336261313438633866643765323330623336343732346465613264613666616631303633 +31333437623437626135623032306434306538323461623161336431353630336539646531313365 +30356330356438353037663262643463363636386162393764323133373261376263663338636333 +33336537383061643234356365653137333639623863303530666665373663376166316533623737 +31616262363132326133666230396636663035343432633830323430336332326261613932633837 +62303232306331663936366435666164383335313335326662663363643563643938623739386663 +65356431353362623634333437346566363166366161313132653131613861386437306562633934 +38306135636161366530303461636665386639396639356265323361623639616265323462333534 +61626439616631356532353437653539333631636265346238653439313130326239633035613130 +65366161356231356638313633393735356465393339343034356138316366656431336435386437 +36366339366463396638303062366263356436666232393437613766623631313264646565316534 +32373534366363373737616563616161346265323936336665653934656365323731633637376331 +31336262356632313533393366346565373434373134653630303431643037363561613830663339 +39313837356362643164386432653030306132643234303162363463376130666239653461623736 +65333734303532356462636538326430353831336562666563336333323764346234643037373064 +63343533386564356232633661346232666135393365323133323365663637373261396264333432 +64616463643931653162373666323731653635616633386231653863623830666637623638353062 +61363339613364343239363336626266386561343032333262396436663736353835303863643865 +35616136363830326661383066316537633537616565343361633036373163316566653638343638 +33323162376537353961383965666165663631396663356533643432356630396435356636366636 +32363264303633656335333335356166323164386338363437663435336635643334646532616539 +66646336393834613337336661336638613463626230336132386265396165656461623533333738 +33353063306336623463396130386335383338383861376330616566643638333230303862356462 +63356464313263666538306230636363643665643933323236363864303634343539326239363134 +65376231623836363730376631303032393330326362383438373434383633656134373539653461 +38306632636463346365396131316234623736626335633361663861316437326635633237346262 +38643730666130656335663232366362333164353433613731363131366138633766623433363031 +37613337383961386661393733623238393062663065663931313231366366626261666336656134 +63646237643864643462653337306462646631663433663461343764306163646466326334323335 +61396162353032633937333066313438333461376161666166336337356430373737313266363238 +63633365613331636234333765633536653639343234396262653438353132623239303431316134 +35663930323535373830313766353630636237343330346232316637336531336466326564306630 +62356639356330396465353432333135313130653366303931616165663638633461613961643766 +62633564666566616665366336396436386330323536356633323965343566396232303434666534 +33333636313136326535303136643265393834613733343362346333336339636437353236326161 +65633230656531633238623830343432663365313532343435623133636239333763633930316666 +34363535313731666432 diff --git a/secrets/vault.yml.example b/secrets/vault.yml.example index e48db25..0152974 100644 --- a/secrets/vault.yml.example +++ b/secrets/vault.yml.example @@ -1,4 +1,5 @@ --- +vault_duckdns_token: "CHANGEME" vault_personal_full_name: "REPLACE_ME" vault_git_email: "REPLACE_ME" vault_git_signing_key: "REPLACE_ME"