Skip to content

Commit 72d8bb2

Browse files
committed
fix(ansible): close env-file and Alloy privilege gaps
1 parent 7a6d744 commit 72d8bb2

19 files changed

Lines changed: 524 additions & 69 deletions

File tree

‎ansible/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ control machine):
201201
202202
```bash
203203
umask 077
204-
sudo install -m 600 -o root -g root grafana-alloy.env /etc/decdn/grafana-alloy.env
204+
sudo install -m 600 -o root -g root grafana-alloy.env /etc/grafana-alloy.env
205205
```
206206

207207
with `roles/grafana_alloy/files/grafana-alloy.env.example` as the template (remote-write

‎ansible/galaxy/CHANGELOG.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,20 @@ collection adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
2929
a missing port, a path/query/fragment and userinfo are rejected at deploy time.
3030
OTLP export is always compiled in; no `--features otlp` build is needed.
3131

32+
### Fixed
33+
34+
- Grafana Alloy credentials now default to root-controlled
35+
`/etc/grafana-alloy.env`; preflight also rejects a non-root-owned or writable
36+
parent directory and a root service identity. Teardown requires the managed
37+
stamp on the unit's first line, and destructive paths reject both `.` and `..`
38+
segments.
39+
- `decdn config validate` no longer bash-sources `/etc/decdn/decdn.env`. It
40+
loads the role-supported one-line EnvironmentFile assignment forms with a
41+
non-expanding host-side parser, so `$VAR` / `$(...)` in an inventory-rendered
42+
(`to_json`) RPC URL stay literal for both the gate and the daemon.
43+
- Manual dir-mode's read-only ELF probe runs under `--check` instead of being
44+
skipped and feeding empty output into the architecture assertion.
45+
3246
## [0.1.0] — unreleased
3347

3448
Initial packaging of the public deCDN node roles as a distributable collection.

‎ansible/molecule/default/converge.yml‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616
# (install.yml) instead of letting it degrade to a bare liveness check. Must
1717
# stay in sync with the version string printed by files/decdn-node-stub.
1818
decdn_node_version: "0.0.0-molecule-stub"
19-
decdn_rpc_url: "https://rpc.example.invalid/"
19+
# Shell metacharacters + a single quote are deliberate: the role's host-side
20+
# EnvironmentFile parser must preserve the value literally without executing
21+
# the command substitution (the stub + verify.yml check both properties).
22+
decdn_rpc_url: "https://rpc.example.invalid/?token=$HOME$(touch /tmp/decdn-rpc-expanded)&quote=o'brien"
2023
# The ACCEPTED side of the decdn_extra_env gate (#39): with an inventory
2124
# decdn_rpc_url the role authors the whole env file, so extra_env is rendered
2225
# rather than rejected. Nothing else in any scenario sets it alongside a
@@ -26,6 +29,9 @@
2629
# escape means the variable is silently absent at runtime.
2730
decdn_extra_env:
2831
DECDN_MOLECULE_EXTRA: 'a "quoted" \ value'
32+
# Makes the stub compare the environment received by `config validate`
33+
# against the exact literal above (not merely assert that no command ran).
34+
DECDN_MOLECULE_ASSERT_RPC_LITERAL: "1"
2935
decdn_chain_id: 421614
3036
decdn_region: "US"
3137
decdn_payment_pool_address: "0x1111111111111111111111111111111111111111"

‎ansible/molecule/default/files/decdn-node-stub‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ can be exercised end-to-end without a published release or a live chain:
1717
scenario uses this to prove the role's advisory probe warns
1818
(not fails) on timeout while the unit stays `running`.
1919
* `config validate ...`
20-
-> parse the rendered node.toml as TOML and exit 0. A stub has no
20+
-> parse the rendered node.toml as TOML and exit 0. In the default
21+
scenario it also checks the exact literal DECDN_RPC_URL passed
22+
by the role's non-expanding EnvironmentFile loader. A stub has no
2123
schema, so this checks syntax only; `molecule/schema` is what
2224
covers key-level drift against the real upstream field list.
2325
* `key-gen ...` -> stand in for the CLI's wallet generator (exercised by the
@@ -128,6 +130,22 @@ def config_validate(args):
128130
if not os.path.isfile(config):
129131
print(f"stub config validate: no such config file: {config}", file=sys.stderr)
130132
return 1
133+
if os.environ.get("DECDN_MOLECULE_ASSERT_RPC_LITERAL") == "1":
134+
expected_rpc = (
135+
"https://rpc.example.invalid/?token=$HOME"
136+
"$(touch /tmp/decdn-rpc-expanded)&quote=o'brien"
137+
)
138+
expected_extra = 'a "quoted" \\ value'
139+
if (
140+
os.environ.get("DECDN_RPC_URL") != expected_rpc
141+
or os.environ.get("DECDN_MOLECULE_EXTRA") != expected_extra
142+
):
143+
print(
144+
"stub config validate: an EnvironmentFile value was expanded or "
145+
"decoded incorrectly",
146+
file=sys.stderr,
147+
)
148+
return 1
131149
try:
132150
with open(config, "rb") as fh:
133151
tomllib.load(fh)

‎ansible/molecule/default/verify.yml‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,23 @@
7979
decdn_extra_env value correctly. Got: {{ env_body }}
8080
vars:
8181
env_body: "{{ decdn_env_body.content | b64decode }}"
82-
env_rpc_expected: 'DECDN_RPC_URL="https://rpc.example.invalid/"'
82+
env_rpc_expected: >-
83+
DECDN_RPC_URL="https://rpc.example.invalid/?token=$HOME$(touch /tmp/decdn-rpc-expanded)&quote=o'brien"
8384
env_expected: 'DECDN_MOLECULE_EXTRA="a \"quoted\" \\ value"'
8485

86+
- name: Check whether sourcing the RPC URL executed its command substitution
87+
ansible.builtin.stat:
88+
path: /tmp/decdn-rpc-expanded
89+
register: decdn_rpc_expansion_marker
90+
91+
- name: Assert config validate did not expand the RPC URL as a shell
92+
ansible.builtin.assert:
93+
that:
94+
- not decdn_rpc_expansion_marker.stat.exists
95+
fail_msg: >-
96+
config validate expanded a literal command substitution embedded in
97+
decdn_rpc_url; the env file must be loaded without bash source.
98+
8599
- name: Compute the env file's current sha256
86100
ansible.builtin.stat:
87101
path: "{{ decdn_etc }}/decdn.env"
@@ -352,7 +366,7 @@
352366
- /etc/alloy
353367
- /var/lib/alloy
354368
- /etc/systemd/system/alloy.service
355-
- /etc/decdn/grafana-alloy.env
369+
- /etc/grafana-alloy.env
356370

357371
- name: Assert no Alloy artifacts exist while observability is disabled
358372
ansible.builtin.assert:

‎ansible/molecule/grafana-cloud/prepare.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
# non-empty). Written directly at 0600 because preflight refuses anything else.
4343
- name: Stage the Grafana Cloud credential fixture on the host
4444
ansible.builtin.copy:
45-
dest: /etc/decdn/grafana-alloy.env
45+
dest: /etc/grafana-alloy.env
4646
owner: root
4747
group: root
4848
mode: "0600"

‎ansible/molecule/grafana-cloud/verify.yml‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
register: ga_files
1313
loop:
1414
- {path: /etc/alloy/config.alloy, mode: "0644"}
15-
- {path: /etc/decdn/grafana-alloy.env, mode: "0600"}
15+
- {path: /etc/grafana-alloy.env, mode: "0600"}
1616
- {path: /etc/systemd/system/alloy.service, mode: "0644"}
1717

1818
- name: Assert ownership/modes of the Alloy artifacts
@@ -101,7 +101,7 @@
101101
# --- Secret hygiene -------------------------------------------------------------
102102
- name: Read the credential fixture back
103103
ansible.builtin.slurp:
104-
src: /etc/decdn/grafana-alloy.env
104+
src: /etc/grafana-alloy.env
105105
register: ga_env_b64
106106

107107
- name: Assert the env file carries every required key verbatim
@@ -174,7 +174,7 @@
174174
ansible.builtin.assert:
175175
that:
176176
- >-
177-
'EnvironmentFile=/etc/decdn/grafana-alloy.env' in ga_unit
177+
'EnvironmentFile=/etc/grafana-alloy.env' in ga_unit
178178
# Alloy defines no --config.expand-env and no gRPC admin listener;
179179
# either flag makes `alloy run` exit non-zero, i.e. a crash-loop.
180180
# Checked against the ExecStart flag lines ONLY — the unit's comments
@@ -266,7 +266,7 @@
266266
- /etc/alloy
267267
- /var/lib/alloy
268268

269-
- name: Stage a foreign Alloy installation (no managed-by marker)
269+
- name: Stage a foreign Alloy installation (marker mentioned after line one)
270270
ansible.builtin.copy:
271271
dest: "{{ item.path }}"
272272
content: "{{ item.content }}"
@@ -276,6 +276,8 @@
276276
content: |
277277
[Unit]
278278
Description=Somebody else's Alloy
279+
# This role used to write:
280+
# MANAGED BY the grafana_alloy role — do not edit by hand.
279281
[Service]
280282
ExecStart=/bin/true
281283
- path: /etc/alloy/config.alloy

‎ansible/molecule/slow-readiness/verify.yml‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@
2323
- name: Gather service facts
2424
ansible.builtin.service_facts:
2525

26+
- name: Read node.toml for the disabled-observability branch
27+
ansible.builtin.slurp:
28+
src: /etc/decdn/node.toml
29+
register: node_toml_b64
30+
31+
- name: Assert disabled Grafana with no explicit endpoint omits OTLP entirely
32+
ansible.builtin.assert:
33+
that:
34+
- "'otlp_endpoint' not in (node_toml_b64.content | b64decode)"
35+
fail_msg: >-
36+
node.toml rendered observability.otlp_endpoint even though both
37+
decdn_grafana_cloud_enabled is false and decdn_otlp_endpoint is empty.
38+
2639
- name: Assert the deploy survived a metrics-probe timeout with the unit running
2740
ansible.builtin.assert:
2841
that:

0 commit comments

Comments
 (0)