JanssenProject / JanssenProject/jans
feat(jans-cedarling): Cedarling Sidecar Runtime Attestation
- Dominant language
- Java
- Stars
- 648
- Forks
- 174
- Avg merge
- 1d 18h
- Merged PRs (30d)
- 110
Description
**Status:** Design proposal
**Component:** Cedarling Flask sidecar
**Initial target:** Kubernetes Confidential Containers with Trustee
**Related service:** `jans-attestation-token-service`
## 1. Summary
This proposal adds runtime-attestation capabilities to the existing Cedarling sidecar instead of deploying a separate `jans-attestation-agent` container. The sidecar obtains and renews short-lived Attestation Tokens, correlates them with the workload JWT-SVID, and supplies verified attestation claims to Cedarling during authorization.
The remote Jans Attestation Token Service (ATS) remains separate. The Cedarling sidecar collects evidence and consumes Attestation Tokens; it does not authoritatively verify its own environment or issue tokens trusted by other systems.
The MVP targets Confidential Containers and Trustee. A mock provider supports development and CI without confidential-computing hardware.
This design extends the existing [Cedarling sidecar](https://docs.jans.io/stable/cedarling/developer/sidecar/cedarling-sidecar-overview/), a Flask application using `cedarling_python`. It preserves the AuthZEN-compatible `/cedarling/evaluation` endpoint, bootstrap configuration, and current request and response formats.
## 2. Architectural decision
| Responsibility | Owner |
| --- | --- |
| AuthZEN evaluation API | Cedarling sidecar |
| Evidence collection | Cedarling sidecar through an adapter |
| ATS protocol and DPoP | Cedarling sidecar |
| Token renewal and caching | Cedarling sidecar |
| Attestation context injection | Cedarling sidecar |
| Authorization decision | Cedarling engine |
| Evidence verification | Remote ATS and Trustee/vendor verifier |
| Attestation policy | Remote ATS/verifier |
| Attestation Token issuance | Remote ATS |
The key boundary is:
> Cedarling obtains and consumes the Attestation Token. A remote ATS independently verifies evidence and issues the token.
## 3. Goals
- Add attestation as an optional Cedarling sidecar capability.
- Preserve `/cedarling/evaluation` and current AuthZEN behavior.
- Obtain an ATS challenge for a configured audience and policy.
- Collect nonce- and key-bound evidence through Confidential Containers.
- Authenticate to ATS with the workload JWT-SVID and DPoP.
- Validate, cache, and renew the returned token in memory.
- Add trusted attestation facts to Cedarling evaluation context.
- Fail closed when policy requires valid attestation.
- Emit attestation status in decision logs and OpenTelemetry.
- Include a development-only mock provider.
- Package everything in the existing Cedarling sidecar image.
## 4. Non-goals
- Add another workload-local container.
- Authoritatively verify raw TEE evidence inside Cedarling.
- Issue Attestation Tokens or JWT-SVIDs.
- Replace Trustee or a vendor attestation service.
- Change AuthZEN semantics.
- Return Attestation Tokens to API callers.
- Support multiple audiences per instance in the MVP.
- Release secrets directly.
- Implement every TEE interface.
- Persist tokens or raw evidence.
## 5. Architecture
```mermaid
flowchart TD
APP["Application or gateway"] -->|AuthZEN request| CS["Cedarling sidecar"]
CS -->|evidence request| CAA["CoCo Attestation Agent"]
CAA -->|TEE evidence| CS
CS -->|JWT-SVID + DPoP + evidence| ATS["Remote ATS"]
ATS -->|verify| TV["Trustee / vendor verifier"]
ATS -->|Attestation Token| CS
CS -->|tokens + attestation context| CE["Cedarling engine"]
CE -->|decision| APP
```
The application and Cedarling sidecar run inside the same confidential guest or pod sandbox. Reference measurements must cover the protected workload as a whole—not merely the Cedarling container. This includes the application image, Cedarling image, guest, workload policy, configuration, debug state, and relevant TCB.
## 6. Current sidecar compatibility
The current sidecar:
- Is a containerized Flask application using `cedarling_python`.
- Reads bootstrap configuration from `CEDARLING_BOOTSTRAP_CONFIG_FILE`.
- Exposes `/cedarling/evaluation` on port 5000.
- Accepts AuthZEN subject, action, resource, and context.
- Reads JWTs from `subject.properties.tokens`.
- Executes Cedarling multi-issuer authorization.
- Returns a Boolean decision with optional administrative diagnostics.
When attestation is disabled, behavior must remain unchanged. When enabled, the sidecar enriches the evaluation internally; callers do not retrieve or forward the Attestation Token.
## 7. Component design
### 7.1 Attestation manager
One process-level `AttestationManager` starts after Cedarling bootstrap succeeds.
```mermaid
stateDiagram-v2
[*] --> Disabled
Disabled --> Starting: enabled
Starting --> Attesting
Attesting --> Ready: issued
Ready --> Renewing: threshold
Renewing --> Ready: renewed
Attesting --> Degraded: failure
Renewing --> Degraded: expired
Degraded --> Attesting: retry
```
It:
- Coordinates challenge, evidence collection, and token exchange.
- Holds one validated token and parsed claim set in memory.
- Replaces cached state atomically.
- Removes expired state immediately.
- Applies bounded exponential backoff with jitter.
- Exposes an immutable status snapshot to evaluation requests.
The MVP should run one Flask process per container so multiple workers do not independently attest. Each horizontally scaled sidecar pod obtains its own token; tokens are never shared between pods.
### 7.2 ATS client
The client:
1. Requests a challenge for the configured audience and policy.
2. Validates the challenge response.
3. Requests nonce- and public-key-bound evidence.
4. Creates a DPoP proof with the enrolled non-exportable key.
5. Submits evidence with the JWT-SVID.
6. Validates the returned Attestation Token.
It enforces TLS validation, timeouts, response-size limits, and allowed signing algorithms.
### 7.3 Evidence-provider interface
Runtime differences are isolated behind a reusable Rust trait:
```rust
#[async_trait]
pub trait EvidenceProvider: Send + Sync {
fn evidence_type(&self) -> &'static str;
async fn collect(
&self,
nonce: &[u8],
public_key: &[u8],
) -> Result;
}
```
The production MVP adapter uses the Confidential Containers guest attestation interface. The common client treats evidence as opaque; ATS and Trustee interpret it.
### 7.4 Workload-key signer
The sidecar needs signing access to the same non-exportable key bound to the JWT-SVID:
```rust
#[async_trait]
pub trait WorkloadSigner: Send + Sync {
async fn public_jwk(&self) -> Result;
async fn sign(
&self,
algorithm: Algorithm,
input: &[u8],
) -> Result, SignerError>;
}
```
The MVP uses a local signing socket or existing JWT-SVID client interface. The private key never enters the sidecar process.
### 7.5 Python/Rust boundary
The Flask sidecar remains the HTTP and lifecycle host. Security-sensitive functions should be implemented as a reusable Rust module exposed through `cedarling_python`:
```text
Flask routes and lifecycle
↓
cedarling_python
↓
Rust attestation module
├── ATS protocol and DPoP
├── token validation
├── evidence-provider traits
├── signer abstraction
└── renewal state
```
This avoids duplicating JOSE and evidence-handling logic in Python and allows a future Rust-native sidecar to reuse the same implementation.
### 7.6 Mock provider
The mock provider must be excluded from production builds, require an explicit runtime flag, refuse `APP_MODE=production`, emit a persistent warning, and require development-only trust at ATS.
## 8. Attestation and evaluation flow
```mermaid
sequenceDiagram
participant App as Application
participant Sidecar as Cedarling sidecar
participant CoCo as CoCo Agent
participant ATS as Remote ATS
participant Cedar as Cedarling
Sidecar->>ATS: Request challenge
ATS-->>Sidecar: nonce + challenge_id
Sidecar->>CoCo: Evidence (nonce, key digest)
CoCo-->>Sidecar: opaque evidence
Sidecar->>ATS: JWT-SVID + DPoP + evidence
ATS-->>Sidecar: Attestation Token
Sidecar->>Sidecar: Validate and cache
App->>Sidecar: POST /cedarling/evaluation
Sidecar->>Cedar: AuthZEN input + attestation
Cedar-->>Sidecar: ALLOW or DENY
Sidecar-->>App: AuthZEN response
```
Required relationships:
```text
evidence.nonce == challenge.nonce
evidence.public_key == DPoP.public_key
attestation_token.cnf.jkt == jwt_svid.cnf.jkt
attestation_token.sub == jwt_svid.sub
attestation_token.aud == configured audience
```
## 9. Cedarling integration
### 9.1 Server-controlled context
The caller sends the normal AuthZEN request. After validating the token, the sidecar adds server-controlled facts:
```json
{
"jans_attestation": {
"available": true,
"result": "pass",
"tee": "sev-snp",
"debug": false,
"tcb_status": "current",
"policy_id": "production-agents-v4",
"policy_version": 12,
"age_seconds": 8,
"subject": "spiffe://op.example.org/agent/3f9c",
"key_thumbprint": "9XKt..."
}
}
```
Caller-provided fields must never override `jans_attestation`. The token itself may instead be processed through Cedarling's multi-issuer token path if the policy-store schema defines an Attestation Token mapping. The MVP must select one canonical policy representation to prevent conflicting values.
### 9.2 Policy example
```cedar
permit (
principal,
action == Jans::Action::"TransferFunds",
resource
)
when {
context has jans_attestation &&
context.jans_attestation.available &&
context.jans_attestation.result == "pass" &&
context.jans_attestation.debug == false &&
context.jans_attestation.tcb_status == "current" &&
context.jans_attestation.policy_id == "production-agents-v4" &&
context.jans_attestation.age_seconds <= 60
};
```
### 9.3 Fail-closed semantics
If no valid token exists, the sidecar injects `available: false` and still evaluates the request. Cedar policy decides which capabilities require attestation. This lets low-risk operations continue while high-impact actions fail closed.
A `sub` or `cnf.jkt` mismatch is a security event. The sidecar discards the token and injects `available: false`.
### 9.4 AuthZEN response compatibility
The standard response remains:
```json
{"decision": true}
```
When `SIDECAR_DEBUG_RESPONSE=True`, sanitized diagnostics may identify `token_expired` or `attestation_unavailable`. Responses must never include tokens, proofs, raw evidence, measurements, or verifier details.
## 10. Operational endpoints
| Endpoint | Purpose | Exposure |
| --- | --- | --- |
| `/health/live` | Process health | Pod-local probes |
| `/health/ready` | Cedarling readiness; separately reports attestation state | Pod-local probes |
| `/cedarling/attestation/status` | Sanitized state, expiry, provider, policy | Disabled or admin-protected |
| `/metrics` | Prometheus metrics | Management network |
No endpoint returns the cached Attestation Token.
## 11. Configuration
Attestation configuration is separate from the existing Cedarling bootstrap file:
```yaml
enabled: true
ats:
endpoint: https://jans-attestation.jans-trust.svc.cluster.local
issuer: https://op.example.org/jans-attestation
audience: https://payments.example.com
policy_id: production-agents-v4
request_timeout_seconds: 10
identity:
jwt_svid_file: /run/jans-identity/jwt-svid
signer_socket: /run/jans-identity/signer.sock
evidence:
provider: coco
coco_socket: /run/confidential-containers/attestation-agent.sock
token:
renew_before_seconds: 15
minimum_remaining_lifetime_seconds: 5
evaluation:
context_attribute: jans_attestation
```
| Environment variable | Default | Purpose |
| --- | --- | --- |
| `CEDARLING_ATTESTATION_ENABLED` | `false` | Enables the module. |
| `CEDARLING_ATTESTATION_CONFIG_FILE` | unset | Configuration path. |
| `CEDARLING_ATTESTATION_STATUS_ENDPOINT` | `false` | Enables status endpoint. |
If enabled configuration is absent or invalid, startup fails. Unknown fields and insecure combinations are rejected.
## 12. Container and deployment changes
The existing image is extended rather than replaced:
- Build the Rust attestation module into `cedarling_python`.
- Add Python lifecycle and configuration integration.
- Include the CoCo provider in the production MVP.
- Exclude mock code from production tags.
- Retain the entrypoint, port 5000, bootstrap behavior, and evaluation endpoint.
- Continue producing an SBOM and signed image.
Illustrative Kubernetes deployment:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: payment-agent
spec:
runtimeClassName: kata-cc
containers:
- name: application
image: example/payment-agent:1.0
env:
- name: CEDARLING_URL
value: http://127.0.0.1:5000/cedarling/evaluation
- name: cedarling-sidecar
image: ghcr.io/janssenproject/jans/cedarling-flask-sidecar:attestation-mvp
ports:
- containerPort: 5000
env:
- name: APP_MODE
value: production
- name: CEDARLING_BOOTSTRAP_CONFIG_FILE
value: /etc/cedarling/bootstrap.json
- name: CEDARLING_ATTESTATION_ENABLED
value: "true"
- name: CEDARLING_ATTESTATION_CONFIG_FILE
value: /etc/cedarling/attestation.yaml
securityContext:
runAsNonRoot: true
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
volumeMounts:
- name: cedarling-config
mountPath: /etc/cedarling
readOnly: true
- name: coco-attestation
mountPath: /run/confidential-containers
readOnly: true
- name: jans-identity
mountPath: /run/jans-identity
readOnly: true
```
The CoCo mount is illustrative. The Helm chart must use the supported guest interface for the selected Kata/CoCo release rather than standardizing an unverified host path.
Network policy permits Cedarling to reach ATS, DNS, the policy store, trusted issuer metadata/JWKS endpoints, and telemetry. The application should not access the evidence or signing interfaces.
## 13. Failure behavior
| Failure | Behavior |
| --- | --- |
| Attestation disabled | Preserve current sidecar behavior. |
| No confidential runtime | Inject `available: false`; policy decides. |
| Challenge request fails | Retry; retain an unexpired token. |
| Evidence collection fails | Sanitize error; expire closed. |
| ATS rejects evidence | Avoid aggressive retries; mark unavailable. |
| Returned token is invalid | Discard and emit a security event. |
| JWT-SVID expires | Stop renewal; mark unavailable. |
| Signing interface fails | Mark unavailable and retry. |
| ATS is unreachable | Use current token only until expiration. |
| Clock exceeds tolerance | Fail attestation closed. |
| Manager crashes | Evaluate with `available: false` while restarting it. |
Readiness should distinguish Cedarling readiness from attestation availability. Operators may make Kubernetes readiness depend on attestation when every capability requires it.
## 14. Security requirements
- Do not perform authoritative self-verification.
- Run without root, capabilities, or privilege escalation.
- Use a read-only root filesystem.
- Never log tokens, DPoP proofs, evidence, or private keys.
- Allow only configured issuers, audiences, algorithms, and ATS keys.
- Enforce nonce, subject, audience, and key-thumbprint binding.
- Store tokens only in process memory.
- Prevent caller context from overriding attestation facts.
- Limit evidence and response sizes.
- Redact provider failures from AuthZEN responses.
- Disable mock mode in production.
- Ensure measurements cover the application and sidecar together.
- Disable `SIDECAR_DEBUG_RESPONSE` by default in production.
## 15. Observability
Metrics:
- `cedarling_attestation_token_valid`
- `cedarling_attestation_token_seconds_remaining`
- `cedarling_attestation_renewal_total{result}`
- `cedarling_attestation_evidence_collection_seconds{provider}`
- `cedarling_attestation_ats_request_seconds{operation}`
- `cedarling_attestation_failures_total{stage,reason}`
- `cedarling_attestation_evaluations_total{available,decision}`
- `cedarling_attestation_mock_provider_enabled`
Trace challenge acquisition, evidence collection, ATS exchange, token validation, context enrichment, and Cedar evaluation. Decision logs include token `jti`, policy ID/version, attestation age, and result—but not token contents.
## 16. Source structure
```text
jans-cedarling/
├── cedarling/src/attestation/
│ ├── manager.rs
│ ├── ats_client.rs
│ ├── dpop.rs
│ ├── token_validation.rs
│ ├── signer/
│ └── evidence/
│ ├── coco.rs
│ └── mock.rs
├── bindings/cedarling_python/attestation.rs
└── flask-sidecar/main/
├── attestation_lifecycle.py
└── routes/
├── evaluation.py
└── attestation_status.py
```
Cargo features:
```toml
[features]
attestation = []
coco-provider = ["attestation"]
mock-provider = ["attestation"]
```
Default embedded Cedarling builds should not acquire platform dependencies. The sidecar build explicitly enables `attestation` and `coco-provider`.
## 17. Testing
### Compatibility
- Existing bootstrap configuration starts unchanged when disabled.
- Existing AuthZEN requests and responses remain compatible.
- Existing debug-response and multi-issuer behavior remains compatible.
### Unit and integration
- Challenge parsing, DPoP, renewal, backoff, and atomic state.
- Signature, issuer, audience, subject, and `cnf.jkt` checks.
- Caller-context override prevention and error redaction.
- Valid attestation enriches Cedar context.
- Missing attestation permits low-risk but denies required policies.
- Replay, identity mismatch, key mismatch, and expired tokens fail closed.
- ATS key rotation and outage behavior.
- Concurrent evaluation during renewal receives consistent state.
### Confidential-runtime release test
1. Start the application and Cedarling sidecar in a Kata confidential pod.
2. Obtain nonce- and key-bound evidence through CoCo.
3. Obtain and validate an Attestation Token.
4. Evaluate a request through `/cedarling/evaluation`.
5. Confirm Cedarling receives server-controlled attestation facts.
6. Reject the reference value at ATS/Trustee.
7. Confirm the protected capability is denied after token expiry.
## 18. MVP acceptance criteria
- The current Flask sidecar enables attestation through configuration.
- No additional workload-local container is required.
- Disabled mode is backward compatible.
- CoCo produces nonce- and key-bound evidence.
- ATS token `sub` and `cnf.jkt` match the JWT-SVID.
- Cedarling validates, renews, and stores the token only in memory.
- The evaluation endpoint receives server-controlled attestation facts.
- Policies can require TEE type, debug state, TCB status, policy version, and maximum age.
- Token expiry denies protected capabilities without disabling unrelated ones.
- Mock mode cannot run in production.
- Metrics, traces, readiness, and sanitized diagnostics are present.
- Tests cover compatibility, replay, binding mismatch, expiry, and outage.
## 19. Deferred work
- Multiple ATS audiences and policies per sidecar.
- On-demand attestation per evaluation.
- Direct SEV-SNP, TDX, Nitro, TPM, and GPU providers.
- Attestation Token introspection.
- Attestation-gated Trustee/KMS secret release.
- Multi-worker coordination.
- A Rust-native sidecar server.
- Standalone agent packaging for deployments without Cedarling.
## 20. Key design decision
Runtime attestation becomes an optional capability of the Cedarling sidecar, not another container. Cedarling already sits where token bundles, request context, policy, and authorization meet, making it the natural consumer and lifecycle manager for the Attestation Token.
The independent trust boundary remains intact: Confidential Containers produces evidence, remote ATS and Trustee verify it, and Cedarling consumes the signed result when making authorization decisions.
Contributor guide
Assessment
This issue has not been assessed yet.