Appearance
Assign a Cloud Credential
A Cloud names a provider account and a CloudCredential is the vaulted secret that pays for provisioning against it — but before the broker may spend a Credential on a Project, that Project must be assigned the credential. This lesson — the fourth in the provisioning track — exercises that assignment, and the assignment is governed by two parties:
- the project owner requests the binding for their Project, but cannot grant it;
- the platform operator — the credential's owner — approves it, and a principal may never approve its own request.
That split is the point: it keeps "who may provision" and "whose credential pays" separate, so standing spend on a Project always carries a second party's sign-off. You exercise both halves here, acting as each identity in turn.
You built the catalog across the three catalog lessons — Register a Blueprint, Create a Cloud, and Issue a Cloud Credential. Here you bind the Credential to a Project, so Provision a cloud Resource can spend it.
You will:
- as the platform operator, recover the Cloud Credential id you issued in the catalog lessons,
- as the project owner, create a Project and request a credential assignment for it, naming that Cloud Credential,
- as the platform operator, approve that assignment — the two-party governance that keeps the two concerns apart,
- as the project owner, confirm the approval landed on your Project and capture the assigned CloudCredential.
By the end you will understand the credential-assignment governance — the request-and-approve handshake, binding a specific Cloud Credential, and the self-approval guard — and you will hold an approved assignment ready for the provisioning lesson to consume.
This lesson takes about eight minutes.
Before you start
This lesson builds on Create a Cloud and Issue a Cloud Credential: the demo AWS Cloud and its Credential must already exist, because the assignment binds that Credential to a Project. Complete those first. Beyond them you need a running, logged-in stack from Set up your local plexsphere, plus jq on your $PATH to read fields out of the JSON responses.
This lesson uses two of the seeded human identities from the tutorials overview; you act as each in turn:
| Identity | Profile | Role in this lesson | |
|---|---|---|---|
| Project owner | admin@example.com | default | creates the Project and requests the assignment |
| Platform operator | operator@example.com | operator | owns the credential and approves the assignment |
Both fixture accounts sign in with the dev password password. The split is real: operator owns the Cloud's Credential and so may approve, while admin owns Projects but not the platform catalog — so each command below is run by the identity that is actually allowed to run it, and a principal may never approve its own request.
You switch between these two identities several times. The project owner is the identity you already use everywhere else, so it stays on your default profile — the one Set up your local plexsphere logged you into. The platform operator is a second identity: sign it into its own named operator profile once, and select it with --profile operator on just the operator's commands. The owner's commands carry no flag and resolve to the default profile as usual. That is two device-code sign-ins for the whole lesson, with no re-login when you switch back and forth.
Recreate the shell environment and read the Acme Corp Domain id with make dev-ids — you will switch identities several times, so it helps to have it in hand (plexctl login resolves the active IdP binding from the Domain):
bash
export PATH="$PWD/bin:$PATH"
export PLEXSPHERE_URL=http://localhost:8080
eval "$(make -s dev-ids)"
echo "domain=$DOMAIN_ID"Step 1 — As the platform operator, recover the Cloud Credential id
The project owner names the Cloud Credential in the request, so the operator's job here is to recover the credential id from the catalog and hand it over.
Sign in as the platform operator and save the session under the operator profile. The --platform flag runs the Domain-independent platform sign-in — the operator's authority (including ownership of the demo credential you approve in Step 5) rides their platform identity, not any tenant Domain. Complete sign-in in the browser as operator@example.com with the password password:
bash
plexctl login --profile-name operator --platformRecover the demo Cloud Credential from the catalog. Resolve the Cloud by its slug first, then read back the active demo-credential bound to it:
bash
CLOUD_ID=$(plexctl cloud list --all --profile operator --output json \
| jq -r '.items[] | select(.slug == "demo-cloud") | .id')
CREDENTIAL_ID=$(plexctl cloud credential list --cloud-id "$CLOUD_ID" --profile operator --output json \
| jq -r 'first(.items[] | select(.display_name == "demo-credential" and .status == "active") | .id)')
echo "$CREDENTIAL_ID"The Cloud slug is unique, and the credential you issued is the active demo-credential bound to it. $CREDENTIAL_ID is a shell variable, so it survives the identity switches ahead. $CLOUD_ID matters too — Step 3 grants that Cloud to the Project, which every credential assignment requires. You could instead name the Cloud in the request and let the server auto-select its newest eligible credential; that form takes the same prerequisite and saves you the credential lookup.
Step 2 — As the project owner, create a Project
Switch to the project owner — your default profile, the same identity every other lesson uses. Sign in a second time to refresh it, completing sign-in in the browser as admin@example.com with the password password:
bash
plexctl login --domain-id "$DOMAIN_ID"Create a Project under Acme Corp to hold your provisioned fleet. The owner's commands carry no --profile flag — they resolve to the default profile as usual:
bash
PROJECT_ID=$(plexctl project create \
--domain "$DOMAIN_ID" \
--slug provision-demo \
--display-name "Provision demo" \
--output json | jq -r '.id')
echo "$PROJECT_ID"text
019fb9c9-d96e-7361-bdfe-8776e9981f70Give the grant a moment to land. Your
adminaccess to a new Project reaches the authorization mirror a moment after the Project is created. If the next command returnsPermission Denied, wait a second and re-run it.
Step 3 — As the platform operator, put the Project on the Cloud
A credential is only usable where the Cloud underneath it was assigned too. Two different people decide those two things: the Cloud's operator says which Projects may consume the Cloud, and the credential's owner says which Projects may spend the credential. Holding the credential does not let you put a Project onto a Cloud that is not yours to give — so the Cloud Assignment comes first, and it is the operator's call.
Grant the demo Cloud to your Project. This is a single authoritative action rather than a two-party request: the operator created the Cloud, so they decide its consumers outright.
bash
plexctl cloud assignment grant \
--cloud-id "$CLOUD_ID" \
--project-id "$PROJECT_ID" \
--profile operator \
--output json | jq -r '.state, .materialised'text
approved
trueapproved with materialised: true means the Project may now use the Cloud. Skip this step and the next one fails with cloud_not_usable_in_project — the platform refuses to bind a credential the Project could never deploy with.
Step 4 — As the project owner, request a credential assignment
Before the broker may spend a CloudCredential on your Project, the Project must be assigned that credential — and that assignment is governed by two parties. As the Project owner, you request it, naming the Cloud Credential the operator handed you:
bash
ASSIGNMENT_ID=$(plexctl credential assignment request \
--project-id "$PROJECT_ID" \
--cloud-credential-id "$CREDENTIAL_ID" \
--output json | jq -r '.id')
echo "$ASSIGNMENT_ID"text
019fb9c9-ec31-70c8-b3c4-bfbe0fe4cc25The assignment lands in the requested state, bound to the credential you named. You cannot approve your own request — the platform deliberately splits the two halves.
Step 5 — As the platform operator, approve the assignment
Approval is the credential owner's call, not the requester's. The operator@example.com who issued the credential owns it, so switch back to that identity with --profile operator — no second sign-in, the profile you saved in Step 1 still holds the operator's token.
The operator does not go hunting through your Project for the request. Everything awaiting a decision — credential assignments, cloud assignments, and generic dual-control proposals alike — collects in one inbox, the approval queue. Read it as the operator:
bash
plexctl approval list --profile operator --status pending-approval --output json \
| jq '.items[] | {id, kind, state, target_resource}'json
{
"id": "019fb9c9-ec31-70c8-b3c4-bfbe0fe4cc25",
"kind": "credential_assignment",
"state": "pending-approval",
"target_resource": "cloudcredential:019fb9c7-fe03-7dcf-b671-382f2b307d6f"
}That is the assignment from Step 4. kind names the family the row came from, and target_resource names the object the decision spends — here the Cloud Credential the operator issued. The row reads pending-approval because that is the queue's word for "awaiting a decision"; the assignment itself is stored as requested.
target_resource is also why the operator sees this row at all. The queue applies no blanket gate — any signed-in principal may call it. Instead it checks each row on its own terms and keeps only the ones the caller may decide, which for an assignment row means holding assign on the object named in target_resource. The operator issued this credential, so it holds assign on it and the row is visible. A principal without that grant gets the same call answered with an empty list rather than an error.
Note what the operator still cannot do. Listing a Project's assignments with plexctl credential assignment list is gated on read on the owning Project, and the operator profile is the platform sign-in you saved in Step 1: a principal that holds no relation inside Acme Corp at all. Run that command as the operator and it is refused, which is the design rather than a misconfiguration:
text
plexctl: Permission Denied: credentialassignments ListCredentialAssignments: caller lacks project read on the owning ProjectSo the operator decides an assignment without being able to browse the Project it belongs to. This is also why the id travels through the queue rather than through your shell: on a real stack the operator is a different person and has no $ASSIGNMENT_ID to reach for.
Capture it from the queue, then, rather than from $ASSIGNMENT_ID. The operator narrows the inbox to the credential it owns — target_resource carries that credential's id — and reads the row's id off it:
bash
APPROVAL_ID=$(plexctl approval list --profile operator --status pending-approval --output json \
| jq -r --arg cred "$CREDENTIAL_ID" \
'first(.items[] | select(.kind == "credential_assignment" and .target_resource == "cloudcredential:" + $cred) | .id)')
echo "$APPROVAL_ID"text
019fb9c9-ec31-70c8-b3c4-bfbe0fe4cc25The same id Step 4 printed, arrived at the other way round: the requester read it off their own request, the operator found it by the credential the decision spends. Approve it:
bash
plexctl approval approve "$APPROVAL_ID" \
--profile operator --output json | jq '{state, materialised}'json
{
"state": "approved",
"materialised": true
}materialised: true means the broker now has a usable credential handle for this Project. The separation of duties you just exercised — owner requests, credential holder approves, and never the same principal — is enforced server-side, even on this single-tenant dev stack.
Step 6 — As the project owner, confirm the assignment is approved
Switch back to the project owner — drop the --profile flag so your commands resolve to the default profile again. The approval was the operator's action, so read the assignment back as its owner and see what changed:
bash
plexctl credential assignment list --project-id "$PROJECT_ID" --output json \
| jq '.items[] | select(.state == "approved") | {id, state, materialised, cloud_credential_id}'json
{
"id": "019fb9c9-ec31-70c8-b3c4-bfbe0fe4cc25",
"state": "approved",
"materialised": true,
"cloud_credential_id": "019fb9c7-fe03-7dcf-b671-382f2b307d6f"
}The same assignment id you saw in Step 5, now carrying two changes. state moved from requested to approved, and materialised flipped from false to true. Those are not the same statement: approved is the decision, materialised is its consequence, and only the second one means the broker actually holds a usable credential handle bound to this Project.
Now capture the credential itself. resource create in the provisioning lesson names the credential id directly rather than deriving it from the assignment, so pull it out of the approved row into a shell variable:
bash
CREDENTIAL_ID=$(plexctl credential assignment list --project-id "$PROJECT_ID" --output json \
| jq -r 'first(.items[] | select(.state == "approved") | .cloud_credential_id)')
echo "credential=$CREDENTIAL_ID"text
credential=019fb9c7-fe03-7dcf-b671-382f2b307d6fThat is the cloud_credential_id from the row above, and the same credential the operator recovered in Step 1 — the request named it, the approval bound it, and the value has not changed hands since. Your Project is cleared to provision.
Do this in the Console
The governance steps above have a browser equivalent in the Console. Open the Projects page, find your Project, and click Open to reach the Project detail page — the host for the Project-scoped governance surfaces:
- Cloud assignments governs which Clouds the Project may use, and every credential assignment below needs an approved row here first — the browser face of Step 3. As the project owner, Request Cloud names the Cloud, and each
approved-state row carries Revoke. An operator can also grant the Cloud outright from the Cloud detail page's Grant to Project, which is what Step 3 does on the command line. See Assign a Cloud to a Project for the full workflow. - Credential assignments is the project owner's half. Request assignment opens a dialog with two modes. By Cloud Credential names a specific credential — exactly what Step 4 does with
--cloud-credential-id. By Cloud (the default) instead names the Cloud and lets the server auto-select its newest eligible credential, reading it back off the resulting assignment so you never type a credential id. Both modes need the approved Cloud assignment above. Either way the request appears in therequestedstate, and eachapproved-state row carries Revoke.
The operator's half lives elsewhere, mirroring Step 5. Open Governance → Approvals for the queue that spans every Domain you may read: it lists all three row kinds, and clicking a row opens its detail view with Approve and Reject. Approving your own request is refused with an inline "you cannot approve your own request", the browser face of the server's self-approval guard. A Cloud owner can work the same rows narrowed to one Cloud from that Cloud's Assignments tab.
What you learned
- Two assignments, two decisions. A Cloud Assignment says which Projects may consume a Cloud; a Credential Assignment says which Projects may spend a credential. Different people hold those two calls, and the Cloud one comes first — a credential is refused where the Cloud under it was never assigned, so holding a credential cannot put a Project onto somebody else's Cloud.
- Credential assignment is two-party. The Project owner requests and the credential owner approves, and a principal may never approve its own request — separation of duties holds even on a single dev stack.
- You request by naming a specific Cloud Credential. The operator recovers the credential id and hands it over; you name it directly in the request and read it back off the approved assignment to confirm the binding. Naming the Cloud instead, to auto-select its newest eligible credential, is the alternative, and takes the same prerequisite.
- Approval materialises a usable handle.
materialised: truemeans the broker now holds a credential handle bound to the Project — the green light a Resource needs before it can be provisioned. - Governance guards standing spend up, not down. The two-party handshake protects granting a Project the right to spend a credential; tearing a Resource down later needs no such approval.
Where to go next
- Keep learning by doing — Provision a cloud Resource consumes the approved assignment you just made:
resource createan S3 bucket and watch the broker carry it throughPending → Provisioning → Readyagainst the emulated AWS.
Or pick the quadrant that matches what you need now:
- You have a job to do — the Assign a Cloud Credential and Assign a Cloud to a Project how-to guides cover the operator runbooks for requesting, approving, rejecting, and revoking an assignment.
- You want to understand why the assignment is shaped this way — the provisioning ReBAC context explains how the
assignanddeploychecks gate each step of the lifecycle. - You want the exact contract — the
plexctl credentialreference documents every flag and output shape for the request and revoke steps, andplexctl approvaldoes the same for the queue you decided in.