Add opt-in Railiance host time and private authority automation
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a09cbb-87c6-7900-a145-4ce53ba9f1a6
This commit is contained in:
parent
c402245207
commit
0d6cc3ec3b
13 changed files with 291 additions and 2 deletions
|
|
@ -5,6 +5,8 @@
|
||||||
roles:
|
roles:
|
||||||
- role: base
|
- role: base
|
||||||
tags: [base]
|
tags: [base]
|
||||||
|
- role: railiance_clock
|
||||||
|
tags: [clock]
|
||||||
- role: sops_agent
|
- role: sops_agent
|
||||||
tags: [sops]
|
tags: [sops]
|
||||||
- role: custodian_agent # injects ~/.ssh/id_custodian_agent.pub into authorized_keys
|
- role: custodian_agent # injects ~/.ssh/id_custodian_agent.pub into authorized_keys
|
||||||
|
|
|
||||||
6
ansible/playbooks/host-time.yaml
Normal file
6
ansible/playbooks/host-time.yaml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
- name: Converge only explicitly admitted host time infrastructure
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
roles:
|
||||||
|
- role: railiance_clock
|
||||||
18
ansible/roles/railiance_clock/defaults/main.yaml
Normal file
18
ansible/roles/railiance_clock/defaults/main.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
---
|
||||||
|
# Both baseline convergence and authority activation are explicit inventory opt-ins.
|
||||||
|
railiance_clock_enabled: false
|
||||||
|
railiance_clock_manage_timesyncd: false
|
||||||
|
railiance_clock_authority_enabled: false
|
||||||
|
railiance_clock_upstreams: [ntp.ubuntu.com]
|
||||||
|
railiance_clock_policy_id: railiance01-online-v1
|
||||||
|
railiance_clock_install_root: /opt/railiance-clock
|
||||||
|
railiance_clock_health_root: /run/railiance-clock
|
||||||
|
railiance_clock_state_root: /var/lib/railiance-clock
|
||||||
|
railiance_clock_wheelhouse: ""
|
||||||
|
railiance_clock_wheel_sha256: ""
|
||||||
|
railiance_clock_private_key_source: ""
|
||||||
|
railiance_clock_kid: ""
|
||||||
|
railiance_clock_admission_ref: ""
|
||||||
|
railiance_clock_max_error_ns: 500000000
|
||||||
|
railiance_clock_measurement_margin_ns: 10000000
|
||||||
|
railiance_clock_host_drift_ppm: 1000
|
||||||
13
ansible/roles/railiance_clock/handlers/main.yaml
Normal file
13
ansible/roles/railiance_clock/handlers/main.yaml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
- name: Restart reviewed timesyncd
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: systemd-timesyncd
|
||||||
|
state: restarted
|
||||||
|
- name: Reload clock service units
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
daemon_reload: true
|
||||||
|
- name: Restart admitted clock authority
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: railiance-clock.service
|
||||||
|
state: restarted
|
||||||
|
daemon_reload: true
|
||||||
128
ansible/roles/railiance_clock/tasks/authority.yaml
Normal file
128
ansible/roles/railiance_clock/tasks/authority.yaml
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
---
|
||||||
|
- name: Require versioned artifact and admitted signing-key delivery
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- railiance_clock_wheelhouse | length > 0
|
||||||
|
- railiance_clock_wheel_sha256 is match('^[0-9a-f]{64}$')
|
||||||
|
- railiance_clock_private_key_source | length > 0
|
||||||
|
- railiance_clock_kid | length > 0
|
||||||
|
- name: Create unprivileged authority account
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: railiance-clock
|
||||||
|
system: true
|
||||||
|
shell: /usr/sbin/nologin
|
||||||
|
create_home: false
|
||||||
|
- name: Create root-owned install tree
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ railiance_clock_install_root }}"
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0755'
|
||||||
|
- name: Create private signing-key directory
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ railiance_clock_state_root }}"
|
||||||
|
state: directory
|
||||||
|
owner: railiance-clock
|
||||||
|
group: railiance-clock
|
||||||
|
mode: '0700'
|
||||||
|
- name: Create public health runtime directory
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ railiance_clock_health_root }}"
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0755'
|
||||||
|
- name: Persist runtime directory creation across boot
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: /etc/tmpfiles.d/railiance-clock.conf
|
||||||
|
content: "d {{ railiance_clock_health_root }} 0755 root root -\n"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0644'
|
||||||
|
- name: Verify selected wheel checksum on controller
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ railiance_clock_wheelhouse }}/railiance_clock-0.1.0-py3-none-any.whl"
|
||||||
|
checksum_algorithm: sha256
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
register: clock_wheel
|
||||||
|
- name: Refuse artifact drift
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that: clock_wheel.stat.checksum == railiance_clock_wheel_sha256
|
||||||
|
- name: Copy offline wheelhouse with locked dependencies
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: "{{ railiance_clock_wheelhouse }}/"
|
||||||
|
dest: "{{ railiance_clock_install_root }}/wheels/"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0644'
|
||||||
|
register: clock_artifact_copy
|
||||||
|
- name: Install isolated authority package without network package lookup
|
||||||
|
ansible.builtin.pip:
|
||||||
|
name: railiance-clock==0.1.0
|
||||||
|
state: "{{ 'forcereinstall' if clock_artifact_copy.changed else 'present' }}"
|
||||||
|
virtualenv: "{{ railiance_clock_install_root }}/venv"
|
||||||
|
virtualenv_command: python3 -m venv
|
||||||
|
extra_args: "--no-index --find-links={{ railiance_clock_install_root }}/wheels"
|
||||||
|
notify: Restart admitted clock authority
|
||||||
|
- name: Deliver admitted signing key without logging values
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: "{{ railiance_clock_private_key_source }}"
|
||||||
|
dest: "{{ railiance_clock_state_root }}/signing.pem"
|
||||||
|
owner: railiance-clock
|
||||||
|
group: railiance-clock
|
||||||
|
mode: '0600'
|
||||||
|
no_log: true
|
||||||
|
diff: false
|
||||||
|
notify: Restart admitted clock authority
|
||||||
|
- name: Install prebuilt read-only kernel-health probe
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: "{{ railiance_clock_wheelhouse }}/railiance-clock-kernel-health"
|
||||||
|
dest: "{{ railiance_clock_install_root }}/kernel-health"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0755'
|
||||||
|
- name: Write non-secret authority and health policy configuration
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: "{{ item }}.json.j2"
|
||||||
|
dest: "{{ railiance_clock_install_root }}/{{ item }}.json"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0644'
|
||||||
|
loop: [authority, health-policy]
|
||||||
|
notify: Restart admitted clock authority
|
||||||
|
- name: Install host service units
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: "{{ item }}.j2"
|
||||||
|
dest: "/etc/systemd/system/{{ item }}"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0644'
|
||||||
|
loop: [railiance-clock.service, railiance-clock-health.service, railiance-clock-health.timer]
|
||||||
|
notify: Reload clock service units
|
||||||
|
- name: Load changed units before starting
|
||||||
|
ansible.builtin.meta: flush_handlers
|
||||||
|
- name: Export current host health
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: railiance-clock-health.service
|
||||||
|
state: started
|
||||||
|
- name: Enable bounded periodic host-health export
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: railiance-clock-health.timer
|
||||||
|
enabled: true
|
||||||
|
state: started
|
||||||
|
- name: Enable private authority
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: railiance-clock.service
|
||||||
|
enabled: true
|
||||||
|
state: started
|
||||||
|
- name: Verify usable authority independently of service liveness
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: http://127.0.0.1:8787/readyz
|
||||||
|
return_content: true
|
||||||
|
status_code: 200
|
||||||
|
register: clock_ready
|
||||||
|
retries: 3
|
||||||
|
delay: 2
|
||||||
|
until: clock_ready.status == 200
|
||||||
50
ansible/roles/railiance_clock/tasks/main.yaml
Normal file
50
ansible/roles/railiance_clock/tasks/main.yaml
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
---
|
||||||
|
- name: Converge explicitly admitted Railiance Clock host scope
|
||||||
|
when: railiance_clock_enabled | bool
|
||||||
|
block:
|
||||||
|
- name: Require supported host and concrete admission
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- ansible_system == 'Linux'
|
||||||
|
- ansible_distribution == 'Ubuntu'
|
||||||
|
- ansible_distribution_major_version == '24'
|
||||||
|
- railiance_clock_admission_ref | length > 0
|
||||||
|
- railiance_clock_upstreams | length > 0
|
||||||
|
- railiance_clock_host_drift_ppm | int >= 500
|
||||||
|
- railiance_clock_max_error_ns | int > railiance_clock_measurement_margin_ns | int
|
||||||
|
- name: Gather service facts without changing clocks
|
||||||
|
ansible.builtin.service_facts:
|
||||||
|
- name: Refuse competing discipline daemons
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- ansible_facts.services.get(item, {}).get('state', 'stopped') != 'running'
|
||||||
|
loop: [chrony.service, chronyd.service, ntp.service, ntpd.service]
|
||||||
|
- name: Ensure the existing time daemon is installed
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that: "'systemd-timesyncd.service' in ansible_facts.services"
|
||||||
|
- name: Configure reviewed timesyncd baseline only when explicitly selected
|
||||||
|
when: railiance_clock_manage_timesyncd | bool
|
||||||
|
block:
|
||||||
|
- name: Create timesyncd configuration directory
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /etc/systemd/timesyncd.conf.d
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0755'
|
||||||
|
- name: Install sole owner drop-in
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: timesyncd.conf.j2
|
||||||
|
dest: /etc/systemd/timesyncd.conf.d/60-railiance-clock.conf
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0644'
|
||||||
|
notify: Restart reviewed timesyncd
|
||||||
|
- name: Keep admitted daemon enabled
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: systemd-timesyncd
|
||||||
|
enabled: true
|
||||||
|
state: started
|
||||||
|
- name: Install authority and health export
|
||||||
|
when: railiance_clock_authority_enabled | bool
|
||||||
|
ansible.builtin.include_tasks: authority.yaml
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{{ {'private_key_file': railiance_clock_state_root + '/signing.pem', 'health_file': railiance_clock_health_root + '/health.json', 'health_owner_uid': 0, 'authority_id': 'railiance01', 'environment': 'prod', 'kid': railiance_clock_kid, 'policy_id': railiance_clock_policy_id, 'port': 8787, 'max_health_age_ns': 10000000000, 'host_drift_ppm': railiance_clock_host_drift_ppm | int, 'max_error_ns': railiance_clock_max_error_ns | int, 'max_continuity_error_ns': 50000000, 'max_sample_age_ns': 5000000000} | to_nice_json }}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{{ {'status': 'admitted', 'policy_id': railiance_clock_policy_id, 'upstreams': railiance_clock_upstreams, 'kernel_helper': railiance_clock_install_root + '/kernel-health', 'max_error_ns': railiance_clock_max_error_ns | int, 'measurement_margin_ns': railiance_clock_measurement_margin_ns | int, 'host_drift_ppm': railiance_clock_host_drift_ppm | int, 'admission_ref': railiance_clock_admission_ref} | to_nice_json }}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Read-only Railiance host clock quality export
|
||||||
|
After=systemd-timesyncd.service
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart={{ railiance_clock_install_root }}/venv/bin/railiance-clock health-export --policy {{ railiance_clock_install_root }}/health-policy.json --output {{ railiance_clock_health_root }}/health.json
|
||||||
|
User=root
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
CapabilityBoundingSet=
|
||||||
|
AmbientCapabilities=
|
||||||
|
ProtectSystem=strict
|
||||||
|
ReadWritePaths={{ railiance_clock_health_root }}
|
||||||
|
ProtectHome=yes
|
||||||
|
PrivateTmp=yes
|
||||||
|
ProtectClock=yes
|
||||||
|
RestrictAddressFamilies=AF_UNIX
|
||||||
|
TimeoutStartSec=12
|
||||||
|
UMask=0022
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Refresh Railiance host clock health
|
||||||
|
[Timer]
|
||||||
|
OnBootSec=2
|
||||||
|
OnUnitActiveSec=2
|
||||||
|
AccuracySec=100ms
|
||||||
|
Unit=railiance-clock-health.service
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Private Railiance application time authority
|
||||||
|
After=network.target railiance-clock-health.service
|
||||||
|
Requires=railiance-clock-health.service
|
||||||
|
[Service]
|
||||||
|
User=railiance-clock
|
||||||
|
Group=railiance-clock
|
||||||
|
ExecStart={{ railiance_clock_install_root }}/venv/bin/railiance-clock serve --config {{ railiance_clock_install_root }}/authority.json
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
CapabilityBoundingSet=
|
||||||
|
AmbientCapabilities=
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=yes
|
||||||
|
PrivateTmp=yes
|
||||||
|
PrivateDevices=yes
|
||||||
|
ProtectClock=yes
|
||||||
|
ProtectKernelTunables=yes
|
||||||
|
ProtectKernelModules=yes
|
||||||
|
ProtectControlGroups=yes
|
||||||
|
RestrictAddressFamilies=AF_INET AF_INET6
|
||||||
|
IPAddressDeny=any
|
||||||
|
IPAddressAllow=localhost
|
||||||
|
MemoryMax=128M
|
||||||
|
TasksMax=32
|
||||||
|
UMask=0077
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Managed by railiance-infra; {{ railiance_clock_admission_ref }}.
|
||||||
|
[Time]
|
||||||
|
NTP={{ railiance_clock_upstreams | join(' ') }}
|
||||||
|
FallbackNTP=
|
||||||
|
PollIntervalMinSec=32
|
||||||
|
PollIntervalMaxSec=64
|
||||||
|
RootDistanceMaxSec=500ms
|
||||||
|
|
@ -4,7 +4,7 @@ type: workplan
|
||||||
title: "Declare and verify the Railiance host UTC baseline"
|
title: "Declare and verify the Railiance host UTC baseline"
|
||||||
domain: financials
|
domain: financials
|
||||||
repo: railiance-infra
|
repo: railiance-infra
|
||||||
status: ready
|
status: active
|
||||||
flavor: planning
|
flavor: planning
|
||||||
owner: codex
|
owner: codex
|
||||||
topic_slug: railiance
|
topic_slug: railiance
|
||||||
|
|
@ -49,7 +49,7 @@ Define what source health can honestly claim before exposing it to the clock app
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: RAIL-HO-WP-0013-T02
|
id: RAIL-HO-WP-0013-T02
|
||||||
status: wait
|
status: progress
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "75f17ffa-b781-549d-82ce-d19d431c2618"
|
state_hub_task_id: "75f17ffa-b781-549d-82ce-d19d431c2618"
|
||||||
```
|
```
|
||||||
|
|
@ -92,3 +92,10 @@ IaC to apply/read back. Reverify usable host health and no extra listener/daemon
|
||||||
record rollback and monitoring ownership. Hand evidence to RCLK-WP-0005 and
|
record rollback and monitoring ownership. Hand evidence to RCLK-WP-0005 and
|
||||||
railiance-bootstrap for ordering/rehearsal. Keep all residuals live before closure.
|
railiance-bootstrap for ordering/rehearsal. Keep all residuals live before closure.
|
||||||
No corporate workstation time settings or app-clock trust adoption in this plan.
|
No corporate workstation time settings or app-clock trust adoption in this plan.
|
||||||
|
|
||||||
|
|
||||||
|
2026-09-15: operator requested implementation and consumer use. Added opt-in
|
||||||
|
railiance_clock owner role and host-time playbook; bootstrap invokes the same
|
||||||
|
role. Authority activation requires artifact hash, admission reference and
|
||||||
|
explicit signing-key delivery. Defaults change no clocks and deploy no service.
|
||||||
|
Native convergence, custody and measured UTC policy acceptance remain open.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue