Skip to content
Merged
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
37 changes: 37 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright 2026 Edgeless Systems GmbH
# SPDX-License-Identifier: BUSL-1.1
name: ci
on:
workflow_dispatch:
push:
branches:
- main
pull_request:
jobs:
checks:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@21a544727d0c62386e78b4befe52d19ad12692e3 # v14
- name: Run flake checks
run: nix flake check -L
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@21a544727d0c62386e78b4befe52d19ad12692e3 # v14
- name: Run golangci-lint
run: nix run .#lint
govulncheck:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@21a544727d0c62386e78b4befe52d19ad12692e3 # v14
- name: Run govulncheck
run: nix run .#govulncheck
43 changes: 43 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright 2026 Edgeless Systems GmbH
# SPDX-License-Identifier: BUSL-1.1
name: container
on:
workflow_dispatch:
push:
branches:
- main
tags:
- "v*"
permissions:
contents: write
packages: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@21a544727d0c62386e78b4befe52d19ad12692e3 # v14
- name: Log in to ghcr.io
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push versioned tag
if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
run: nix run .#push -- "v$(cat version.txt)"
- name: Push latest tag (main only)
if: github.ref == 'refs/heads/main'
run: nix run .#push -- latest
- name: Render manifest and publish release
if: startsWith(github.ref, 'refs/tags/v')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
nix run .#render-k8s-resources > collateral-proxy.yml
gh release create "${{ github.ref_name }}" \
--title "${{ github.ref_name }}" \
--generate-notes \
collateral-proxy.yml
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/result
/result-*
/vendor
.direnv/
86 changes: 86 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
version: "2"
run:
modules-download-mode: readonly
output:
formats:
tab:
path: stderr
linters:
enable:
# keep-sorted start
- bodyclose
- contextcheck
- copyloopvar
- errchkjson
- errname
- errorlint
- exptostd
- forcetypeassert
- gocheckcompilerdirectives
- gochecknoinits
- godot
- intrange
- misspell
- nilerr
- noctx
- nolintlint
- nosprintfhostport
- predeclared
- promlinter
- reassign
- revive
- sloglint
- testifylint
- unconvert
- unparam
- usestdlibvars
- usetesting
- wastedassign
# keep-sorted end
settings:
testifylint:
disable:
- require-error
revive:
rules:
# These are the recommended rules from
# https://github.com/mgechev/revive/blob/v1.9.0/README.md?plain=1#L419-L441
# without 'package-comments'.
# keep-sorted start
- name: blank-imports
- name: context-as-argument
- name: context-keys-type
- name: dot-imports
- name: empty-block
- name: error-naming
- name: error-return
- name: error-strings
- name: errorf
- name: exported
- name: increment-decrement
- name: indent-error-flow
- name: range
- name: receiver-naming
- name: redefines-builtin-id
- name: superfluous-else
- name: time-naming
- name: unexported-return
- name: unreachable-code
- name: unused-parameter
- name: var-declaration
- name: var-naming
# keep-sorted end
exclusions:
generated: strict
warn-unused: true
presets:
- std-error-handling
issues:
max-issues-per-linter: 0
max-same-issues: 20
formatters:
enable:
- gofumpt
- goimports
exclusions:
generated: strict
91 changes: 91 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
Business Source License 1.1

Parameters

Licensor: Edgeless Systems GmbH
Licensed Work: collateral-proxy
The Licensed Work is (c) Edgeless Systems GmbH
Additional Use Grant: None

Change Date: Four years from the date a MINOR version (SemVer) is published.

Change License: GNU Affero General Public License Version 3 (AGPL-3.0-only)

For information about alternative licensing arrangements for the Software,
please visit: https://www.edgeless.systems/enterprise-support

Notice

License text copyright (c) 2023 MariaDB plc, All Rights Reserved.
“Business Source License” is a trademark of MariaDB plc.

-----------------------------------------------------------------------------

Business Source License 1.1

Terms

The Licensor hereby grants you the right to copy, modify, create derivative
works, redistribute, and make non-production use of the Licensed Work. The
Licensor may make an Additional Use Grant, above, permitting limited
production use.

Effective on the Change Date, or the fourth anniversary of the first publicly
available distribution of a specific version of the Licensed Work under this
License, whichever comes first, the Licensor hereby grants you rights under
the terms of the Change License, and the rights granted in the paragraph
above terminate.

If your use of the Licensed Work does not comply with the requirements
currently in effect as described in this License, you must purchase a
commercial license from the Licensor, its affiliated entities, or authorized
resellers, or you must refrain from using the Licensed Work.

All copies of the original and modified Licensed Work, and derivative works
of the Licensed Work, are subject to this License. This License applies
separately for each version of the Licensed Work and the Change Date may vary
for each version of the Licensed Work released by Licensor.

You must conspicuously display this License on each original or modified copy
of the Licensed Work. If you receive the Licensed Work in original or
modified form from a third party, the terms and conditions set forth in this
License apply to your use of that work.

Any use of the Licensed Work in violation of this License will automatically
terminate your rights under this License for the current and all other
versions of the Licensed Work.

This License does not grant you any right in any trademark or logo of
Licensor or its affiliates (provided that you may use a trademark or logo of
Licensor as expressly required by this License).

TO THE EXTENT PERBUSL-1.1TED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIBUSL-1.1ATION) WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
TITLE.

MariaDB hereby grants you permission to use this License’s text to license
your works, and to refer to it using the trademark “Business Source License”,
as long as you comply with the Covenants of Licensor below.

Covenants of Licensor

In consideration of the right to use this License’s text and the “Business
Source License” name and trademark, Licensor covenants to MariaDB, and to all
other recipients of the licensed work to be provided by Licensor:

1. To specify as the Change License the GPL Version 2.0 or any later version,
or a license that is compatible with GPL Version 2.0 or a later version,
where “compatible” means that software provided under the Change License can
be included in a program with software provided under GPL Version 2.0 or a
later version. Licensor may specify additional Change Licenses without
limitation.

2. To either: (a) specify an additional grant of rights to use that does not
impose any additional restriction on the right granted in this License, as
the Additional Use Grant; or (b) insert the text “None”.

3. To specify a Change Date.

4. Not to modify this License in any other way.
130 changes: 130 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<!--
Copyright 2026 Edgeless Systems GmbH
SPDX-License-Identifier: BUSL-1.1
-->

# collateral-proxy

Read-through caching forward proxy for attestation collateral (AMD KDS, Intel PCS, NVIDIA RIM).

Clients request collateral using the **same paths** the upstream vendors use, but against the proxy's address instead of the vendor host.
The proxy maps the path prefix to the upstream, serves a fresh copy from its cache when it has one, and otherwise fetches, stores, and returns the upstream response.

### Routing

Requests are routed to an upstream by path prefix. Only `GET` is accepted (anything else returns `405`).
Unknown paths return `404`.

| Path prefix | Upstream host | Vendor |
| --------------------- | ------------------------------------------ | ----------------- |
| `/vcek/`, `/vlek/` | `kdsintf.amd.com` | AMD KDS |
| `/sgx/`, `/tdx/` | `api.trustedservices.intel.com` | Intel PCS |
| `/IntelSGX…` | `certificates.trustedservices.intel.com` | Intel SGX Root CA |
| `/v1/rim/` | `rim.attestation.nvidia.com` | NVIDIA RIM |

For a matched request the proxy reconstructs the upstream URL as `https://<host><path>?<query>`.
The path and query string are passed through unchanged, only the host (and scheme) are set by the proxy.

### Request flow

1. Look up the reconstructed upstream URL in the cache.
2. Fresh cache entry exists: return the cached response directly.
3. Stale cache entry exists: fetch the upstream.
- On success, the response is returned to the caller.
- On upstream failure, return stale entry as fallback.
4. No cache entry exists: fetch the upstream.
- On success, the response is returned to the caller.
- On upstream failure, return `502 Bad Gateway`.

Relevant response headers are forwarded to the client.
Only `200 OK` responses from upstream are cached.

### Cache & freshness

Each entry's freshness lifetime is computed at store time, in priority order:

1. CRLs: the `nextUpdate` field parsed from the CRL itself.
2. Otherwise the upstream response's `Cache-Control: max-age` (honoring `no-cache` / `no-store` / `must-understand`).
3. Otherwise a default TTL of 1 hour.

### Endpoints

- `GET /healthz`: returns `ok`, use as a readiness probe.
- `GET /metrics`: Prometheus metrics:
- `collateral_proxy_requests_total{result, document}`:
- `result` is one of `hit`, `miss`, `stale`, `error`, `rejected`
- `document` is one of `crl`, `ak-cert`, `collateral`, `unknown`
- `collateral_proxy_upstream_responses_total{code, document}`: upstream outcomes by HTTP status code (or `error` when the fetch itself failed).

## Usage

### Flags

| Flag | Default | Description |
| ------------------- | --------------------------- | ----------------------------------------- |
| `-addr` | `:80` | Listen address. |
| `-state-dir` | `/var/lib/collateral-proxy` | Directory for on-disk cache state. |
| `-upstream-timeout` | `10s` | Per-request timeout for upstream fetches. |

### Running the container

The published image is `ghcr.io/edgelesssys/collateral-proxy:latest`:

```sh
docker run -p 8080:80 -v collateral-proxy-state:/var/lib/collateral-proxy ghcr.io/edgelesssys/collateral-proxy:latest
```

Mount a persistent volume at `-state-dir` so the cache survives restarts.

### Deploying on Kubernetes

Each release attaches a [`collateral-proxy.yaml`](https://github.com/edgelesssys/collateral-proxy/releases/latest/download/collateral-proxy.yml) asset to its [GitHub Release](https://github.com/edgelesssys/collateral-proxy/releases/latest/).

```sh
curl -fLO https://github.com/edgelesssys/collateral-proxy/releases/latest/download/collateral-proxy.yml
kubectl apply -f collateral-proxy.yml
```

### Pointing clients at the proxy

Clients fetch collateral from the proxy using the vendor's own paths. For example, a VCEK certificate normally fetched from

```
https://kdsintf.amd.com/vcek/v1/Milan/<hwid>?blSPL=...&teeSPL=...
```

is instead fetched from the proxy:

```
http://<proxy-host>/vcek/v1/Milan/<hwid>?blSPL=...&teeSPL=...
```

The proxy preserves the path and query and rewrites only the host, so clients only need their collateral base URL repointed at the proxy.

## Development

- Prerequisites: Nix (flakes) and/or a Go toolchain; `direnv`/`.envrc`.
- Build the binary: `nix build .#collateral-proxy`.
- Build the container image: `nix build .#container`.
- Push the image: `nix run .#push -- [tag]` (defaults to the `:dev`).
- Push the image and render the pinned deployment manifest: `nix run .#render-k8s-resources -- [tag]`.
- Format: `nix fmt`.
- Lint: `nix run .#lint`.
- Vuln scan: `nix run .#govulncheck`.
- Run formatters and tests: `nix flake check`.

## Releasing

1. Bump version in `version.txt`.

2. Push to `main`
```sh
git commit -am "release: v0.X.0"
git push origin release-v0.X.0
```

3. Open a PR and merge to `main`.

4. CI running on main publishes `ghcr.io/edgelesssys/collateral-proxy:v0.X.0` and moves `:latest`.

5. Push the `v0.X.0` tag. CI then publishes a GitHub Release and attaches `collateral-proxy.yaml`, the deployment manifest pinned to `v0.X.0@sha256:<digest>`.
Loading
Loading