Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .devcontainer/devcontainer-lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"features": {
"ghcr.io/devcontainers/features/go:1": {
"version": "1.3.4",
"resolved": "ghcr.io/devcontainers/features/go@sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032",
"integrity": "sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032"
},
"ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {
"version": "1.3.1",
"resolved": "ghcr.io/devcontainers/features/kubectl-helm-minikube@sha256:bbe8adf6b37fff8c67412ab0a4579f4c2f30bbaba1d9a5cebd9e38bade54025b",
"integrity": "sha256:bbe8adf6b37fff8c67412ab0a4579f4c2f30bbaba1d9a5cebd9e38bade54025b"
}
}
}
18 changes: 18 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "TEC Operator Dev Container",
"image": "mcr.microsoft.com/devcontainers/rust@sha256:b12e288bacbb5633f9b7655ba1a2cf69dd2fecef662346f2a9754f019af82483",
"features": {
"ghcr.io/devcontainers/features/go:1": {},
"ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {}
},
"postCreateCommand": "curl -sL https://mirror.openshift.com/pub/openshift-v4/clients/ocp/stable/openshift-client-linux.tar.gz | sudo tar xz -C /usr/local/bin oc kubectl && curl -fsSL 'https://azurecliprod.blob.core.windows.net/$root/deb_install.sh' | sudo bash",
"customizations": {
"vscode": {
"settings": {},
"extensions": [
"rust-lang.rust-analyzer",
"golang.go"
]
}
}
}
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# SPDX-License-Identifier: CC0-1.0

[workspace]
members = ["attestation-key-register", "compute-pcrs", "lib", "operator", "register-server", "test_utils", "tests"]
members = ["attestation-key-register", "compute-pcrs", "kbs-event-proxy", "lib", "operator", "register-server", "test_utils", "tests"]
resolver = "3"

[workspace.package]
Expand Down
15 changes: 12 additions & 3 deletions Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ COPY register-server/Cargo.toml register-server/
COPY register-server/src/lib.rs register-server/src/
COPY attestation-key-register/Cargo.toml attestation-key-register/
COPY attestation-key-register/src/lib.rs attestation-key-register/src/
COPY kbs-event-proxy/Cargo.toml kbs-event-proxy/
COPY kbs-event-proxy/src/main.rs kbs-event-proxy/src/

RUN sed -i 's/members = .*/members = ["lib", "operator", "compute-pcrs", "register-server", "attestation-key-register"]/' Cargo.toml && \
RUN sed -i 's/members = .*/members = ["lib", "operator", "compute-pcrs", "register-server", "attestation-key-register", "kbs-event-proxy"]/' Cargo.toml && \
sed -i '/\[dev-dependencies\]/,$d' operator/Cargo.toml && \
sed -i '/\[dev-dependencies\]/,$d' register-server/Cargo.toml && \
sed -i '/trusted-cluster-operator-test-utils/d' lib/Cargo.toml
Expand All @@ -44,13 +46,14 @@ RUN --mount=type=cache,target=/build/target \
RUN --mount=type=cache,target=/build/target \
--mount=type=cache,target=/usr/local/cargo/registry \
if [ "$build_type" = debug ]; then \
cargo build -p operator -p compute-pcrs -p register-server -p attestation-key-register; \
cargo build -p operator -p compute-pcrs -p register-server -p attestation-key-register -p kbs-event-proxy; \
fi

COPY operator/src operator/src
COPY compute-pcrs/src compute-pcrs/src
COPY register-server/src register-server/src
COPY attestation-key-register/src attestation-key-register/src
COPY kbs-event-proxy/src kbs-event-proxy/src

RUN --mount=type=cache,target=/build/target \
--mount=type=cache,target=/usr/local/cargo/registry \
Expand All @@ -61,6 +64,7 @@ RUN --mount=type=cache,target=/build/target \
-p compute-pcrs \
-p register-server \
-p attestation-key-register \
-p kbs-event-proxy \
$release_flag

RUN --mount=type=cache,target=/build/target \
Expand All @@ -70,7 +74,8 @@ RUN --mount=type=cache,target=/build/target \
cp /build/target/${profile_dir}/operator /output/ && \
cp /build/target/${profile_dir}/compute-pcrs /output/ && \
cp /build/target/${profile_dir}/register-server /output/ && \
cp /build/target/${profile_dir}/attestation-key-register /output/
cp /build/target/${profile_dir}/attestation-key-register /output/ && \
cp /build/target/${profile_dir}/kbs-event-proxy /output/

# Distribution stages
FROM ${deployment_base} AS operator
Expand All @@ -87,6 +92,10 @@ COPY --from=builder /output/register-server /usr/bin
EXPOSE 3030
ENTRYPOINT ["/usr/bin/register-server"]

FROM ${deployment_base} AS kbs-event-proxy
COPY --from=builder /output/kbs-event-proxy /usr/bin
EXPOSE 8080
ENTRYPOINT ["/usr/bin/kbs-event-proxy"]

FROM builder AS compute-pcrs-data
RUN rv_line=$(cargo metadata --format-version=1 | jq -r '.packages[] | select(.name == "reference-values") | .source') && \
Expand Down
14 changes: 10 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

.PHONY: all build build-tools crds-rs generate manifests cluster-up cluster-down \
install-trustee install clean fmt-check clippy lint test test-release release-tarball prepare-release \
operator-image compute-pcrs-image reg-server-image attestation-key-register-image image \
push-operator push-compute-pcrs push-reg-server push-attestation-key-register push \
operator-image compute-pcrs-image reg-server-image attestation-key-register-image kbs-event-proxy-image image \
push-operator push-compute-pcrs push-reg-server push-attestation-key-register push-kbs-event-proxy push \

SHELL := /bin/bash

Expand Down Expand Up @@ -53,6 +53,7 @@ OPERATOR_IMAGE ?= $(REGISTRY)/trusted-cluster-operator:$(TAG)
COMPUTE_PCRS_IMAGE=$(REGISTRY)/compute-pcrs:$(TAG)
REG_SERVER_IMAGE=$(REGISTRY)/registration-server:$(TAG)
ATTESTATION_KEY_REGISTER_IMAGE=$(REGISTRY)/attestation-key-register:$(TAG)
KBS_EVENT_PROXY_IMAGE=$(REGISTRY)/kbs-event-proxy:$(TAG)

TRUSTEE_IMAGE ?= quay.io/trusted-execution-clusters/key-broker-service:v0.20.0
TEST_IMAGE ?= quay.io/trusted-execution-clusters/fedora-coreos-kubevirt:20260831
Expand Down Expand Up @@ -109,6 +110,7 @@ manifests: trusted-cluster-gen generate
-pcrs-compute-image $(COMPUTE_PCRS_IMAGE) \
-register-server-image $(REG_SERVER_IMAGE) \
-attestation-key-register-image $(ATTESTATION_KEY_REGISTER_IMAGE) \
-kbs-event-proxy-image $(KBS_EVENT_PROXY_IMAGE) \
-approved-image coreos,$(APPROVED_IMAGE)

cluster-up:
Expand All @@ -134,8 +136,10 @@ reg-server-image:
$(CONTAINER_CLI) build $(IMAGE_BUILD_OPTIONS) --target register-server -t $(REG_SERVER_IMAGE) -f Containerfile .
attestation-key-register-image:
$(CONTAINER_CLI) build $(IMAGE_BUILD_OPTIONS) --target attestation-key-register -t $(ATTESTATION_KEY_REGISTER_IMAGE) -f Containerfile .
kbs-event-proxy-image:
$(CONTAINER_CLI) build $(IMAGE_BUILD_OPTIONS) --target kbs-event-proxy -t $(KBS_EVENT_PROXY_IMAGE) -f Containerfile .

image: operator-image compute-pcrs-image reg-server-image attestation-key-register-image
image: operator-image compute-pcrs-image reg-server-image attestation-key-register-image kbs-event-proxy-image

define push-image
$(CONTAINER_CLI) push $(1) $(PUSH_FLAGS)
Expand All @@ -150,8 +154,10 @@ push-reg-server: reg-server-image
$(call push-image,$(REG_SERVER_IMAGE))
push-attestation-key-register: attestation-key-register-image
$(call push-image,$(ATTESTATION_KEY_REGISTER_IMAGE))
push-kbs-event-proxy: kbs-event-proxy-image
$(call push-image,$(KBS_EVENT_PROXY_IMAGE))

push: push-operator push-compute-pcrs push-reg-server push-attestation-key-register
push: push-operator push-compute-pcrs push-reg-server push-attestation-key-register push-kbs-event-proxy

release-tarball: manifests
tar -cf trusted-execution-operator-$(TAG).tar config
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ The operator relies on Rust crates for its functionality.
- `/register-server`: A server that provides Clevis PINs for key retrieval with random UUIDs.
- `/attestation-key-register`: A server that accepts attestation key registrations from VMs and creates AttestationKey resources.
- `/compute-pcrs`: A program to compute PCR reference values using the [compute-pcrs library](https://github.com/trusted-execution-clusters/compute-pcrs) and insert them into a ConfigMap, run as a Job.
- `/kbs-event-proxy`: A reverse proxy sidecar for KBS that emits Kubernetes events for attestation activity. See [design doc](docs/design/kbs-event-proxy.md).

### Other crates

Expand Down
3 changes: 2 additions & 1 deletion REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ path = [
"examples/*",
"tools/virtctl/go.sum",
"tools/operator-sdk/go.sum",
"scripts/install-kubevirt.sh"
"scripts/install-kubevirt.sh",
".devcontainer/*",
]
SPDX-FileCopyrightText = [
"Alice Frosi <afrosi@redhat.com>",
Expand Down
6 changes: 6 additions & 0 deletions api/trusted-cluster-gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type Args struct {
pcrsComputeImage string
registerServerImage string
attestationKeyRegisterImage string
kbsEventProxyImage string
approvedImages approvedImageSlice
}

Expand All @@ -78,6 +79,7 @@ func main() {
flag.StringVar(&args.pcrsComputeImage, "pcrs-compute-image", "quay.io/trusted-execution-clusters/compute-pcrs:latest", "Container image with the Trusted Execution Clusters compute-pcrs binary")
flag.StringVar(&args.registerServerImage, "register-server-image", "quay.io/trusted-execution-clusters/register-server:latest", "Register server image to use in the deployment")
flag.StringVar(&args.attestationKeyRegisterImage, "attestation-key-register-image", "quay.io/trusted-execution-clusters/attestation-key-register:latest", "Attestation key register image to use in the deployment")
flag.StringVar(&args.kbsEventProxyImage, "kbs-event-proxy-image", "quay.io/trusted-execution-clusters/kbs-event-proxy:latest", "KBS event proxy sidecar image")
flag.Var(&args.approvedImages, "approved-image", "When set, defines an initial approved image. It must be a comma-separated name,image-ref pair. Must be a bootable container image with SHA reference. Can be set multiple times.")
flag.Parse()

Expand Down Expand Up @@ -146,6 +148,10 @@ func generateOperator(args *Args) error {
Name: "RELATED_IMAGE_ATTESTATION_KEY_REGISTER",
Value: args.attestationKeyRegisterImage,
},
{
Name: "RELATED_IMAGE_KBS_EVENT_PROXY",
Value: args.kbsEventProxyImage,
},
},
},
},
Expand Down
128 changes: 128 additions & 0 deletions docs/design/kbs-event-proxy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# KBS Event Proxy

## Overview

The KBS event proxy is a reverse proxy sidecar that runs alongside the KBS (Key Broker Service) container in the Trustee pod. It intercepts RCAR attestation HTTP traffic between nodes and KBS and emits Kubernetes events for attestation activity.

Without the proxy, attestation outcomes are only visible in KBS pod logs. The proxy surfaces these outcomes as first-class Kubernetes events on Machine and TrustedExecutionCluster resources.

## Problem

Trustee has no webhook, callback, or audit log for attestation outcomes. An administrator cannot answer "Did machine X attest successfully?" without reading KBS container logs. Kubernetes events provide a standard, queryable interface for this information.

## Architecture

The proxy runs as a sidecar container in the same pod as KBS. The Kubernetes Service routes external traffic to the proxy on port 8080. The proxy forwards all requests to KBS on localhost port 8081.

```
Nodes --> Service:8080 --> Proxy:8080 --(TLS)--> KBS:8081 (localhost)
|
inspects request/response
|
emits K8s events
```

Both hops use TLS. The proxy terminates external TLS from nodes, then connects to KBS via HTTPS on localhost. Both containers mount the same TLS secret volume.

### Why a reverse proxy

A metrics-based sidecar polls Prometheus counters and sees counter deltas, not individual events. The reverse proxy provides:

- Real-time event emission per attestation attempt
- Distinction between attestation failure (401) and resource policy denial (403)
- Session correlation across the three RCAR protocol steps
- Per-machine event attribution for resource requests

## RCAR Protocol

The RCAR (Remote CoCo Attestation and Retrieval) protocol has three HTTP steps. The proxy tracks sessions via the `kbs-session-id` cookie.

| Step | Endpoint | What the proxy observes |
|---|---|---|
| Auth | `POST /kbs/v0/auth` | TEE type from request body. Session cookie in response. |
| Attest | `POST /kbs/v0/attest` | 200 = attestation passed. Non-200 = failure. Session cookie identifies the session. |
| Resource | `GET /kbs/v0/resource/default/{id}/root` | Machine ID from URL path. 200 = key released. 403 = policy denied. 401 = rejected. |

### Session tracking

The proxy maintains an in-memory HashMap that maps session IDs (from the `kbs-session-id` cookie) to session metadata:

```
session_id -> SessionInfo { tee_type, created }
```

Sessions expire after 5 minutes (matching the KBS session timeout). The proxy cleans up expired sessions after each request.

## Events emitted

| Reason | Event type | Target resource | Trigger |
|---|---|---|---|
| `AttestationSucceeded` | Normal | Machine | Resource endpoint returns 200 for `default/{machine-id}/root` |
| `AttestationFailed` | Warning | TrustedExecutionCluster | Attest endpoint returns non-200 |
| `AttestationFailed` | Warning | Machine | Resource endpoint returns 401 for `default/{machine-id}/root` |
| `ResourcePolicyDenied` | Warning | Machine | Resource endpoint returns 403 for `default/{machine-id}/root` |

The proxy emits `AttestationFailed` on the TrustedExecutionCluster (not on a Machine) at the attest step because the RCAR protocol does not carry a machine identifier at that point. The session carries only the TEE type.

@alicefr alicefr Sep 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now we don't have it yet, but in the future the uuid of the machine should be provided as initdata as part of the attestation. In this way, it should be possible to extrapolate which machine failed the attestation

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we can create a ticket to continue this work when the uuid is provided.


The resource step does carry the machine ID in the URL path. The proxy resolves machine IDs to Machine custom resources via the Kubernetes API.

## Deployment

### Pod spec changes

The operator modifies the Trustee pod spec in `operator/src/trustee.rs`:

1. KBS container listens on `127.0.0.1:8081` (internal only)
2. Proxy container listens on `0.0.0.0:8080` (exposed via Service)
3. Both containers mount the TLS secret volume
4. The pod uses the `trusted-cluster-operator` ServiceAccount for RBAC
5. The proxy receives `CONTROLLER_POD_NAME` via the downward API for event reporting

### Image resolution

The operator resolves the proxy image from the `RELATED_IMAGE_KBS_EVENT_PROXY` environment variable. If unset, it falls back to `{TEC_REGISTRY}/kbs-event-proxy:{COMPONENT_VERSION}`.

### RBAC

The proxy reuses the `trusted-cluster-operator` ServiceAccount. The ClusterRole includes:

- `events.k8s.io` API group: `create`, `patch` (for emitting events via the `events.k8s.io/v1` API)

The proxy also reads Machine and TrustedExecutionCluster resources to resolve object references for event targets. These permissions are already present in the operator's ClusterRole.

## TLS

The proxy accepts invalid TLS certificates when connecting to KBS on localhost. This is safe because the connection stays within the same pod on the loopback interface. The KBS TLS certificate contains the external hostname, not `127.0.0.1`, so strict validation would reject the connection.

The external-facing TLS termination uses the same certificate and key that KBS previously used directly. Nodes see no change in TLS behavior.

## Code structure

The proxy source is in `kbs-event-proxy/src/main.rs`, organized into six sections:

1. **Types and state**: CLI arguments, session info, proxy state with HTTP client, Kubernetes client, event recorder, and session map
2. **Request/response parsing**: Extract session IDs from Cookie and Set-Cookie headers, extract machine IDs from URL paths
3. **Kubernetes object lookups**: Resolve TrustedExecutionCluster and Machine custom resources to ObjectReferences for event targets
4. **RCAR attestation event handlers**: One handler per RCAR step (auth, attest, resource) that inspects the forwarded response and emits events
5. **Reverse proxy core**: Request forwarding, error responses, and the main handler that dispatches to event handlers based on URL path
6. **Entry point**: Client initialization, TLS configuration, and server startup

## Dependencies

The proxy reuses workspace dependencies:

- `axum` and `axum-server`: HTTP server and TLS termination (also used by register-server and attestation-key-register)
- `reqwest`: HTTP client for forwarding requests to KBS
Comment thread
alicefr marked this conversation as resolved.
- `kube` and `k8s-openapi`: Kubernetes API access and event recording
- `trusted-cluster-operator-lib`: Shared types (`Machine`, `record_event`, `get_trusted_execution_cluster`)

## Verification

After deployment, verify events with:

```bash
kubectl get events.events.k8s.io -n <namespace>
kubectl describe machine <machine-name>
```

A successful attestation produces an `AttestationSucceeded` event on the Machine resource. A failed attestation produces an `AttestationFailed` warning on the TrustedExecutionCluster resource.
3 changes: 2 additions & 1 deletion docs/design/operator-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ The operator consists of several interconnected components:
4. **Machine Controller**: Reconciles Machine custom resources representing individual nodes. Part of the *operator* pod
5. **Secret Management**: Generates and manages LUKS. encryption keys and attestation key secrets. Part of the *operator* pod.
6. **Attestation Server and KBS**: [Trustee](https://github.com/confidential-containers/trustee) deployment handle the attestation request, the reference values and secrets.
7. **Reference Values calculation**: calculate the reference values provided by the approved images.
7. **KBS Event Proxy**: Reverse proxy sidecar in the Trustee pod that intercepts attestation traffic and emits Kubernetes events. See [KBS Event Proxy Design](kbs-event-proxy.md).
8. **Reference Values calculation**: calculate the reference values provided by the approved images.

## Architecture Components

Expand Down
25 changes: 25 additions & 0 deletions kbs-event-proxy/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# SPDX-FileCopyrightText: Yair Podemsky <ypodemsk@redhat.com>
#
# SPDX-License-Identifier: CC0-1.0

[package]
name = "kbs-event-proxy"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true

[dependencies]
anyhow.workspace = true
axum.workspace = true
axum-server.workspace = true
clap.workspace = true
env_logger.workspace = true
http.workspace = true
k8s-openapi.workspace = true
kube.workspace = true
log.workspace = true
reqwest = { version = "0.12", default-features = false, features = ["native-tls"] }
serde.workspace = true
serde_json.workspace = true
tokio.workspace = true
trusted-cluster-operator-lib = { path = "../lib" }
Loading