# 0055 — Member SSH access to Icarus-hpc
Status: Proposed. Date: 21 October 2026. The implementation is in
[DD-0055](../dd/0055-hpc-ssh-key-provisioning.md).
Deciders: ikons; review by Metallo owners (dkatsimpris) and the Icarus-hpc
administrators (jimsiak).
This decision builds on the September prototype
([#137](https://github.com/pharos-ai-factory/pharos-platform/issues/137), Approach C).
It keeps the prototype's fingerprint identity, lock ordering, withdrawal handling
and compensation for an add that lands after its withdrawal. It moves key ownership
from the Waldur offering user to the member, in a Metallo context, and delivers
changes to Icarus-hpc through the Site API instead of a CSV queue.
## Terminology
| Term | Meaning |
| --- | --- |
| **Pharos platform** | Our platform: this repository's services and contracts. |
| **Icarus-cloud** | The Pharos platform installation that serves Icarus. |
| **Icarus-hpc** | The isolated HPC infrastructure, administered by the NTUA HPC administrators. |
| **Site agent** | Upstream `waldur-site-agent` with the `ldap` and `slurm` plugins, run as `pharos-agent` on Icarus-hpc. |
| **Access agent** | `pharos-ssh-access-agent`, run as `pharos-ssh-access` on Icarus-hpc. It changes member SSH public keys in LDAP. |
| **Site Runtime** | The existing Metallo Runtime, run as `pharos-runtime` on Icarus-hpc. |
```mermaid
flowchart LR
subgraph HPC["Icarus-hpc (NTUA administered)"]
direction TB
SLURM["Slurm"]
AGENT["pharos-agent
Site agent"]
LDAP[("OpenLDAP
ou=pharos")]
ACCESS["pharos-ssh-access
access agent"]
RT["pharos-runtime
Site Runtime"]
SSHD["SSSD → sshd
login nodes"]
AGENT -->|"associations"| SLURM
AGENT -->|"entries, POSIX, groups"| LDAP
ACCESS -->|"keys + ldapPublicKey class"| LDAP
LDAP --> SSHD
end
subgraph CLOUD["Icarus-cloud (Pharos platform)"]
direction TB
WALDUR["Waldur"]
SITEAPI["Metallo Site API"]
SA["Metallo ssh_access"]
API["API"]
WEB["Web"]
BETO["Beto
Party, Project"]
SITEAPI --> SA
WEB --> API --> SA
BETO -.->|"events, after
Beto merges"| SA
end
AGENT ==>|"HTTPS"| WALDUR
ACCESS ==>|"HTTPS"| SITEAPI
RT ==>|"HTTPS"| SITEAPI
```
Thick arrows are outbound HTTPS connections from Icarus-hpc; each agent uses its
own credential.
## Context
[GitHub #678](https://github.com/pharos-ai-factory/pharos-platform/issues/678)
defines member-facing SSH key routes: gates, a stable key ID across resubmission,
per-member fingerprint uniqueness and the states `pending_deployment`, `deployed`,
`rejected`, `pending_revocation` and `revoked`. Keys belong to the member, not to a
Project or Waldur offering. Each member has one native account on Icarus-hpc.
Upstream Waldur 8.1.3 offers per-user key storage (`/api/keys/`) and a provider key
listing, but no per-key state, comment or result reporting. Writing a key for a
member needs that member's own Waldur credential. Key events reach only staff,
support or the member.
The September prototype is a Waldur extension. A member files a request to add or
remove a key, the HPC side approves or rejects it, and the extension keeps the list
of approved keys for each offering user.
#678 was written afterwards and describes the same lifecycle from the key's point
of view. A pending add request corresponds to `pending_deployment`, an approved add
to `deployed` and a rejected add to `rejected`. A pending remove request corresponds
to `pending_revocation`, and an approved remove to `revoked`.
#678 also adds a few things. A key keeps one ID for its whole life, so a revoked key
can be resubmitted without pasting it again; in the prototype each request has its
own ID. #678 checks the AUP and the HPC username before accepting a key, and lets the
page validate a key without submitting it.
The main difference is scope. The prototype attaches keys to an offering user,
that is, to one member in one offering. SSH keys are personal, so this decision
attaches them to the member, across all their Projects and offerings.
On Icarus-hpc, the Site agent's account-name plugin creates each member's POSIX entry
under `ou=pharos` under the username Metallo wrote from the Party's `site` binding, and
pins the UID it got on the offering user. The offering's policy is `service_provider`;
Waldur itself does not derive the name from a claim. The LDAP schema is `openssh-lpk`.
The Icarus account model is proposed in
[PR #643](https://github.com/pharos-ai-factory/pharos-platform/pull/643).
The Icarus-hpc administrators confirmed on 21 October 2026:
- `authorized_keys` is used only for specific local users, so LDAP is the only key
source for Pharos members;
- their own key-sync script writes elsewhere, not under `ou=pharos`, and Pharos UIDs
come from a separate range;
- when a Waldur project ends they remove the Slurm association; the user can still
log in with SSH but cannot submit jobs;
- the access agent can run on the same host as the Site agent; outbound HTTPS from
that host to Icarus-cloud is permitted;
- there is no site-specific key policy beyond LDAP and Waldur defaults;
- SSSD propagation time is not yet known.
On 23 October 2026 they also confirmed:
- Pharos UIDs come from 5000000–5499999 and allocation groups from 5500000–5599999;
- the directory is OpenLDAP without LDAPS;
- no GRNET users are synchronised into the Icarus LDAP, so the Site agent creates
every Pharos entry under `ou=pharos`;
- the 120-second propagation window is acceptable;
- they will apply the directory, service-account and Slurm setup that Pharos specifies.
## Decision
1. **Metallo owns member SSH keys.** A new Metallo context, `ssh_access`, owns each
member's keys for a Site and their #678 lifecycle in its own PostgreSQL database
with dedicated credentials. Waldur does not store these keys. No Waldur
extension is installed.
2. **The Site agent creates accounts; the access agent writes keys.** The upstream
Site agent remains the only writer of LDAP entries, POSIX attributes and groups.
The access agent never creates, renames, disables or deletes entries. It adds or
removes individual `sshPublicKey` values. It adds the `ldapPublicKey` auxiliary
class atomically with the first key and removes it atomically with the last
key, supporting directories where the class requires `sshPublicKey` (`MUST`).
3. **The LDAP server enforces one writer per attribute.** The access agent binds as
`cn=pharos-ssh-access` with write access to `sshPublicKey` and only the
`ldapPublicKey` value of `objectClass` under `ou=pharos`, plus read access to
identity and concurrency attributes. All other class values remain read-only
to that bind. The Site agent's bind DN loses write access to `sshPublicKey`;
account creation must omit `ldapPublicKey` when the schema requires a key.
4. **The two agents run as separate service identities on the same host.** The
access agent has its own Unix user and systemd unit. It has no Slurm, Waldur,
root or sudo authority, and no platform database credential.
5. **The access agent reaches Icarus-cloud only by outbound HTTPS.** It pulls work
from, and acknowledges results to, the existing Metallo Site API on dedicated
`/v1/site/ssh-access/*` routes on the separate Site API host. The edge routes
that hostname and prefix to the Site API while browser API and web traffic keep
separate hosts, backends and credentials. It authenticates with its own native
service credential: a high-entropy bearer whose digest and grant the Site API
retains. Its grant covers only the SSH access routes for its Site. The Site
Runtime's grant does not cover them, and the same verifier serves the native Site
Runtime.
6. **The access agent converges per member.** Work is level-triggered: for each
member it applies the desired key set to the exact entry identified by `uid` and
`uidNumber`. Each modification carries a critical RFC 4528 assertion for both
`uidNumber` and the entry's immutable `entryUUID`, so an account replacement at
the same DN cannot receive the previous account's key. Read-back verifies the
same identity. A fresh read and an `entryCSN` assertion also fence concurrent
key/class changes; contention retries rather than rejecting a valid key. A missing entry leaves keys pending and retried; it is not a
rejection. The agent reconciles after uncertain outcomes.
7. **Departure follows Icarus-hpc practice.** Leaving every HPC Project removes
the Slurm association through the Site agent; the LDAP entry and its keys remain,
so the member can still retrieve data. New submissions are refused with
`not_eligible`. Keys are removed only by member revocation or by an AUP change.
8. **Beto inputs arrive through Metallo-owned ports.** `ssh_access` reads member
Site identity and HPC eligibility through its own ports. Until Beto's Party and
Project integration events
([`feat/beto-party-project-governance`](https://github.com/pharos-ai-factory/pharos-platform/tree/feat/beto-party-project-governance))
are on `main`, configured fixture adaptors supply them. Subsequent adaptors
consume `PartyProviderBindingEstablished`, `PartyProviderBindingRetired`,
`ProjectChanged` and `ProjectAccessChanged`.
9. **The browser contract follows #678 within current platform rules.** Routes are
declared in the API's OpenAPI contract and reach `ssh_access` through typed
command and query bindings. Browser updates use a typed Protobuf projection and
Centrifugo once the page is mounted. There is no browser polling, no NextAuth
route placement and no email matching.
## Consequences
**Positive:** one authority for keys and their states, held per member; Waldur and
the Site agent need no Pharos-specific code; native authority on Icarus-hpc is limited to the key attribute and one auxiliary class value
and enforced by the LDAP server; ordering between account creation and key
submission does not matter; the native credential verifier and Site API route also
serve the native Site Runtime.
**Negative:** keys added in Waldur Homeport are ignored on Icarus-hpc; a new
PostgreSQL context and a Site-side process must be operated; `deployed` and
`revoked` depend on the SSSD cache: the administrators accepted a 120-second window,
which the laboratory measured but Icarus-hpc has not yet qualified.
**Neutral:** the access agent and Site API routes can ship in separate PRs behind the
same lifecycle; Beto adaptors replace fixtures without changing the context.
## Open questions
- Context name: `ssh_access` is proposed; `credentials` was also considered but
overlaps with service credentials and delegated member JWTs.
- Source of the AUP gate: the retained Themelio AUP or the Waldur offering's Terms
of Service.
- The access agent uses `ldapi://` with SASL EXTERNAL on the directory host,
mapped from its Unix identity to its restricted LDAP identity without a password.
The upstream Site agent supports simple bind only and uses loopback `ldap://`
on Icarus-hpc. Confirm that listener and the service-user mapping with jimsiak;
the access agent still refuses non-loopback cleartext LDAP.
- The Site agent's bind DN ACL (pending jimsiak). No existing entry needs
`ldapPublicKey`, since no GRNET users are synchronised.
- Per-member key limit (10 proposed) and FIDO (`sk-ssh-ed25519`) keys.
## Related documents
- [DD-0055](../dd/0055-hpc-ssh-key-provisioning.md).
- [GitHub #678](https://github.com/pharos-ai-factory/pharos-platform/issues/678),
[#137](https://github.com/pharos-ai-factory/pharos-platform/issues/137),
[#141](https://github.com/pharos-ai-factory/pharos-platform/issues/141).
- [PR #643: Icarus HPC accounts, member execution and run path](https://github.com/pharos-ai-factory/pharos-platform/pull/643).
- [Secure Site communication](../../services/metallo/docs/epics/instantiate-and-run/secure-site-communication.md).
- [Waldur Site agent LDAP plugin](https://github.com/waldur/waldur-site-agent/blob/main/plugins/ldap/README.md).