# qURL Tunnel Advanced Attested Key-Provider Onboarding

> Optional advanced customer enablement guide for qURL tunnel sidecars using AWS Nitro or GCP Confidential Space attested key providers.

Source: https://layerv.ai/docs/tunnels/attested-key-providers/

Use this optional advanced guide with the standard tunnel install guide at https://layerv.ai/docs/tunnels/ only when a customer wants cloud attestation to gate release of the qURL sidecar private key. Standard qURL tunnel onboarding remains the simple default.

---

## Outcomes

For this optional upper tier, onboarding is complete when:

- The state directory contains `private_key.sealed.json`, not durable plaintext `private_key`.
- The approved workload can unseal and reconnect without `QURL_API_KEY` or `QURL_API_KEY_FILE`.
- Replacement instances unseal only when AWS Nitro or GCP Confidential Space policy matches.
- Cloud audit logs show expected KMS operations and no unexpected decrypts.
- The customer has captured negative-policy-test evidence before production handoff.

Non-goals:

- This does not replace IAM, KMS, WIF, or key-policy review.
- This does not treat the NHP public key as secret.
- This does not make the GCP local token parse a JWT verifier.
- This does not make AWS first seal host-compromise-resistant on the parent host.

## Provider Model

Provider tiers:

- `file`: stores durable plaintext key material in the sidecar state directory. Use for development or low-sensitivity local deployments.
- `aws-kms` and `gcp-kms`: store sealed state, but do not add an attested release condition.
- `aws-nitro` and `gcp-confidential-space`: optional advanced upper tier that ties unseal to cloud attestation and approved workload identity.

For this advanced tier, cloud policy is the release boundary. The qURL client fails closed and checks local configuration, but confidentiality comes from AWS KMS Recipient attestation policy or GCP Workload Identity Federation plus Cloud KMS IAM.

## Inputs to Collect

Before the advanced attestation working session, collect:

- qURL bootstrap: one-time qURL API key, stable `LAYERV_AGENT_ID`, persistent state path.
- Runtime directory: tmpfs path such as `/dev/shm`, or explicit `LAYERV_NHP_RUNTIME_DIR`.
- Cloud policy: KMS key, steady-state principal, attestation claims, negative-test owner, audit-log evidence location.
- Rollback: whether the customer requires a pre-migration state backup before converting from the `file` provider.

Do not start this optional migration until the platform owner and security owner agree on provider, rollout gates, and rollback ownership.

## Choosing a Provider

Choose AWS Nitro when:

- The workload runs in AWS Nitro Enclaves or a NitroTPM-backed attested runtime.
- The customer can enforce `kms:RecipientAttestation` conditions on the KMS key.
- An unwrap helper can run inside the attested runtime.
- First seal or migration can happen inside the attested runtime or a controlled provisioning window.

Choose GCP Confidential Space when:

- The workload runs in Google Cloud Confidential Space.
- The customer can bind Workload Identity Federation and Cloud KMS IAM to image and service-account claims.
- The container can read the Confidential Space attestation token file.
- The security owner will validate negative IAM tests before production enablement.

Pick one provider per state volume. Switching an existing `file` provider volume to a sealed provider is a one-way migration unless the customer restores a pre-migration backup.

## AWS Nitro Onboarding

`aws-nitro` uses normal KMS encrypt for first seal, then requires AWS KMS Recipient decrypt on unseal. The client expects `CiphertextForRecipient` and fails closed if AWS returns host plaintext.

Prerequisites:

- Customer-managed AWS KMS key in the target region.
- First-seal or migration principal with `kms:Encrypt`.
- Steady-state decrypt principal constrained by Recipient Attestation.
- Unwrap helper inside the attested runtime.

Environment contract:

```sh
LAYERV_KEY_PROVIDER=aws-nitro
LAYERV_AWS_KMS_KEY_ID=arn:aws:kms:us-east-1:111122223333:key/...
LAYERV_AWS_KMS_REGION=us-east-1
LAYERV_AWS_NITRO_ATTESTATION_DOCUMENT_FILE=/run/qurl/attestation.cose
LAYERV_AWS_NITRO_ATTESTATION_DOCUMENT_ENCODING=raw
LAYERV_AWS_NITRO_RECIPIENT_UNWRAP_COMMAND=/opt/qurl/bin/unwrap-recipient
QURL_API_KEY_FILE=/run/secrets/qurl-api-key
LAYERV_AGENT_ID=customer-prod-agent-01
```

Recommended AWS KMS key policy shape:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowQurlAgentFirstSeal",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111122223333:role/qurl-agent-provisioner"
      },
      "Action": "kms:Encrypt",
      "Resource": "*"
    },
    {
      "Sid": "AllowQurlAgentNitroDecrypt",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111122223333:role/qurl-agent-parent"
      },
      "Action": "kms:Decrypt",
      "Resource": "*",
      "Condition": {
        "StringEqualsIgnoreCase": {
          "kms:RecipientAttestation:ImageSha384": "PCR0_OR_IMAGE_SHA384_HEX"
        }
      }
    }
  ]
}
```

AWS implementation rules:

- Treat the sample as a KMS key policy, not an IAM identity policy. In a key policy, `"Resource": "*"` refers to the current key.
- IAM principal ARNs intentionally use an empty region segment, for example `arn:aws:iam::111122223333:role/qurl-agent-parent`, because IAM ARNs are regionless.
- Use `kms:RecipientAttestation:PCR<PCR_ID>` conditions when the customer wants PCR1, PCR2, or custom PCR binding.
- The unwrap command is split on whitespace with no shell or quote parsing.
- The unwrap helper stdout must be exactly 32 raw private-key bytes with no trailing newline.
- AWS KMS Encrypt has no Recipient mode. Run first seal in the attested runtime whenever possible, or use a controlled provisioning window.

## GCP Confidential Space Onboarding

`gcp-confidential-space` checks local Confidential Space claims before KMS calls, but the local parse is not a JWT verifier. WIF/IAM plus Cloud KMS are the real cryptographic release boundary.

> Warning: If the customer does not enforce the same image digest and service account in WIF/IAM, local token claims can pass startup while Cloud KMS still releases the key. Treat the negative IAM test as a production gate.

Environment contract:

```sh
LAYERV_KEY_PROVIDER=gcp-confidential-space
LAYERV_GCP_KMS_KEY_NAME=projects/acme-prod/locations/us/keyRings/qurl/cryptoKeys/agent-identity
LAYERV_GCP_CONFIDENTIAL_SPACE_TOKEN_FILE=/run/container_launcher/attestation_verifier_claims_token
LAYERV_GCP_CONFIDENTIAL_SPACE_IMAGE_DIGEST=sha256:...
LAYERV_GCP_CONFIDENTIAL_SPACE_SERVICE_ACCOUNT=qurl-agent@acme-prod.iam.gserviceaccount.com
QURL_API_KEY_FILE=/run/secrets/qurl-api-key
LAYERV_AGENT_ID=customer-prod-agent-01
```

Recommended WIF and KMS shape:

The `${APPROVED_IMAGE_DIGEST}` placeholders below are shell variables set at the top of the snippet; keep them literal when copying the block.

```sh
APPROVED_IMAGE_DIGEST="sha256:..."

gcloud iam workload-identity-pools providers create-oidc attestation-verifier \
  --location=global \
  --workload-identity-pool=QURL_AGENT_POOL \
  --issuer-uri="https://confidentialcomputing.googleapis.com" \
  --allowed-audiences="https://sts.googleapis.com" \
  --attribute-mapping='google.subject="gcpcs::"+assertion.submods.container.image_digest+"::"+assertion.submods.gce.project_number+"::"+assertion.submods.gce.instance_id,attribute.image_digest=assertion.submods.container.image_digest' \
  --attribute-condition="assertion.swname == 'CONFIDENTIAL_SPACE' && 'STABLE' in assertion.submods.confidential_space.support_attributes && assertion.submods.container.image_digest == '${APPROVED_IMAGE_DIGEST}'"

gcloud kms keys add-iam-policy-binding \
  projects/acme-prod/locations/us/keyRings/qurl/cryptoKeys/agent-identity \
  --member="principalSet://iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/QURL_AGENT_POOL/attribute.image_digest/${APPROVED_IMAGE_DIGEST}" \
  --role=roles/cloudkms.cryptoKeyEncrypterDecrypter
```

GCP implementation rules:

- The default token path is `/run/container_launcher/attestation_verifier_claims_token`.
- Local checks require `swname=CONFIDENTIAL_SPACE`, `dbgstat=disabled-since-boot`, and `STABLE` support attributes.
- Image digest and service account pins must match the customer's WIF attribute mapping and Cloud KMS IAM binding.
- Run a negative test with the wrong image or service account before production.

## Bootstrap and Migration

New deployment:

1. Mount the qURL API key as a file and set `QURL_API_KEY_FILE`.
2. Set a stable `LAYERV_AGENT_ID`.
3. Set the provider-specific environment variables.
4. Start the agent and wait for seal, verify, and tunnel connection.
5. Restart without the API key and confirm reconnect from sealed state.

File-provider migration:

1. Back up the state volume if rollback policy requires it.
2. Set `LAYERV_KEY_PROVIDER` to the sealed provider.
3. Set `LAYERV_ALLOW_KEY_MIGRATION=true` for one restart only.
4. Confirm `private_key.sealed.json` exists.
5. Remove the migration acknowledgement and restart again.

The sidecar writes sealed state only after it proves the selected provider can unseal it. If verification fails, the client exits rather than writing an unusable sealed blob.

## Validation Plan

Capture these before production handoff.

Startup and state:

- Agent starts with the selected provider and fails when required provider env is missing.
- State contains `agent_id` and `private_key.sealed.json`.
- Durable plaintext `private_key` is absent after successful migration.
- Runtime OpenNHP config is materialized only in the runtime directory.

Tunnel behavior:

- Agent registers or loads the expected `LAYERV_AGENT_ID`.
- Routes connect through the qURL tunnel and survive an agent restart.
- Restart without `QURL_API_KEY` or `QURL_API_KEY_FILE` succeeds from sealed state.
- Removing attestation inputs causes startup failure before tunnel connection.

Cloud policy gates:

- AWS: approved attestation decrypt returns `CiphertextForRecipient`, not host plaintext.
- AWS: unapproved image digest or PCR cannot decrypt.
- GCP: approved Confidential Space workload obtains KMS access.
- GCP: non-attested principal, wrong image digest, or wrong service account cannot decrypt.

Useful local checks:

```sh
# Confirm sealed state
ls -la /var/lib/layerv/agent
test -f /var/lib/layerv/agent/private_key.sealed.json
test ! -f /var/lib/layerv/agent/private_key

# Confirm the provider recorded in the sealed blob
grep -m1 -E '"provider"[[:space:]]*:[[:space:]]*"(aws-nitro|gcp-confidential-space)"' \
  /var/lib/layerv/agent/private_key.sealed.json

# Confirm steady-state restart does not need the API key
# Remove QURL_API_KEY / QURL_API_KEY_FILE from compose or service env first.
docker compose up -d --force-recreate qurl-agent
```

## Operations Handoff

Leave the customer with:

- The final provider name and provider-specific env vars.
- KMS key policy, IAM/WIF binding, and attestation claim pins.
- CloudTrail or Cloud Audit Logs query location for KMS operations.
- State-volume backup and rollback notes.
- Troubleshooting notes for missing env, failed attestation, KMS denial, and state drift.

Acceptance checklist:

- Security owner approved cloud KMS policy and negative tests.
- Platform owner confirmed restart behavior without the API key.
- Operations owner accepted troubleshooting and rollback notes.
- LayerV owner confirmed qURL tunnel connectivity and expected agent ID.

## Troubleshooting

Startup says a provider env var is missing:

- Set the provider-specific env vars in this guide. For GCP, image digest and service account are required before the token file is read.

AWS unwrap helper fails or returns the wrong size:

- Verify the helper runs in the attested runtime and writes exactly 32 raw private-key bytes to stdout with no trailing newline.

AWS KMS returns host plaintext:

- Fix the key policy or attestation document. `aws-nitro` intentionally rejects host plaintext and requires `CiphertextForRecipient`.

GCP local checks pass, but KMS decrypt fails:

- Compare local pins to WIF attribute mapping and Cloud KMS IAM binding. The federated principal must match the approved workload.

Agent asks for an API key after restart:

- Restore the correct state directory or intentionally re-bootstrap. The API key is only for first bootstrap or state recovery.

---

*This markdown version is pre-authored for developer and AI-agent consumption. For the full branded experience, visit https://layerv.ai/docs/tunnels/attested-key-providers/.*
