Files
computer-playbook/roles/web-app-snipe-it/tasks/utils/mcp_mint.yml
kevinveenbirkenbachandClaude Opus 5 ee3c485776 fix(snipe-it): mint as apache and hand Passport's keys to it so the API can verify the MCP token
Run 34421944328 Snipe-IT#1 (swarm) failed its CLI contract with
"UNREACHABLE read call snipeit_get_license_summary: the provider
answered 500". The mint ran provision_mcp_identity.php through
container exec as root, so the first deploy's passport:keys wrote the
key files root-owned (0660/0600), and the NFS export's No_Root_Squash
kept them that way. Apache serves as apache and could not read the
public key, so every token-checked request failed with 500 on every
replica. The image's startup script chowns the keys to apache:root,
but only when a container starts, and the mint ran after that.

mcp_mint.yml now runs the same chown in the resolved container before
minting, which also heals keys an earlier deploy left root-owned, and
runs the mint itself with -u apache so keys it creates start out
readable.

The PHP script checks the token it just minted against its own API and
exits 1 on anything but 200, so a broken mint fails the task that made
it, within its retries, instead of surfacing as an unreachable provider
in the CLI contract. Each retry revokes the same-name token before
minting, so retries do not pile up live tokens.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 14:04:13 +02:00

65 lines
2.2 KiB
YAML

---
- name: "🔍 Resolve {{ application_id }} 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: "{{ SNIPE_IT_SERVICE }}"
resolve_output_prefix: SNIPEIT_IDENTITY
resolve_compose_cid: "{{ SNIPE_IT_CONTAINER }}"
- name: "🔐 Hand Passport's keys to apache, as the image's startup script does"
ansible.builtin.command:
argv:
- container
- exec
- "{{ SNIPEIT_IDENTITY_LOCAL_CID }}" # nocheck: container-exec-resolver
- chown
- -R
- apache:root
- /var/lib/snipeit/keys
changed_when: false
delegate_to: "{{ SNIPEIT_IDENTITY_HOST_NODE }}"
run_once: true
- name: "🔑 Mint the read-only Snipe-IT token the MCP adapter presents"
ansible.builtin.command:
argv:
- container
- exec
- -i
- -u
- apache
- -e
- "APP_KEY={{ SNIPE_IT_APP_KEY }}"
- -e
- "MCP_USER={{ SNIPEIT_MCP_UPSTREAM_USER }}"
- -e
- "MCP_PASSWORD={{ SNIPEIT_MCP_UPSTREAM_PASSWORD }}"
- -e
- "MCP_TOKEN_NAME={{ SNIPEIT_MCP_UPSTREAM_TOKEN_NAME }}"
- -e
- "MCP_CURRENT_TOKEN={{ MCP_UPSTREAM_CURRENT_KEY }}"
- "{{ SNIPEIT_IDENTITY_LOCAL_CID }}" # nocheck: container-exec-resolver
- php
stdin: "{{ lookup('file', 'php/provision_mcp_identity.php') }}"
register: snipeit_mcp_identity
retries: 30
delay: 10
until: snipeit_mcp_identity.rc == 0
changed_when: true
delegate_to: "{{ SNIPEIT_IDENTITY_HOST_NODE }}"
run_once: true
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "🛑 Refuse an adapter whose upstream identity did not converge"
ansible.builtin.assert:
that:
- snipeit_mcp_identity.stdout_lines | length == 2
fail_msg: >-
{{ application_id }} could not mint a read-only API token for its MCP adapter.
- name: "🔑 Hand the minted token to the adapter"
ansible.builtin.include_tasks: "{{ lookup('path_absolute', 'roles/svc-ai-mcp-adapter/tasks/upstream_key.yml') }}"
vars:
mcp_upstream_key_path: "{{ [SNIPEIT_MCP_INSTANCE_DIR, 'upstream.env'] | path_join }}"
mcp_upstream_key: "{{ snipeit_mcp_identity.stdout_lines[1] }}"