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
164 changes: 164 additions & 0 deletions .github/workflows/acr-control-plane-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
name: ACR Control Plane Release

on:
push:
tags:
- "v*.*.*"

permissions:
contents: read

env:
CONTROL_PLANE_DIR: implementations/acr-control-plane
COMPLIANCE_OUTPUT_DIR: dist/compliance
PACKAGE_BASENAME: acr-control-plane-compliance-package-${{ github.ref_name }}

jobs:
docker-publish:
name: Publish ${{ matrix.service }} image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
attestations: write
strategy:
matrix:
include:
- service: gateway
dockerfile: Dockerfile
- service: killswitch
dockerfile: Dockerfile.killswitch

steps:
- uses: actions/checkout@v4

- name: Normalize image name
id: image
shell: bash
run: |
repo_lc="$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')"
echo "uri=ghcr.io/${repo_lc}/acr-${{ matrix.service }}" >> "${GITHUB_OUTPUT}"

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ steps.image.outputs.uri }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha,prefix=sha-

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Install Cosign
uses: sigstore/cosign-installer@v4.1.0

- name: Build and push ${{ matrix.service }}
id: build
uses: docker/build-push-action@v6
with:
context: ${{ env.CONTROL_PLANE_DIR }}
file: ${{ env.CONTROL_PLANE_DIR }}/${{ matrix.dockerfile }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Attest build provenance
uses: actions/attest-build-provenance@v3
with:
subject-name: ${{ steps.image.outputs.uri }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true

- name: Sign image with GitHub OIDC
run: cosign sign --yes "${IMAGE_URI}@${DIGEST}"
env:
IMAGE_URI: ${{ steps.image.outputs.uri }}
DIGEST: ${{ steps.build.outputs.digest }}

compliance-package:
name: Build signed compliance package
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
attestations: write
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install Cosign
uses: sigstore/cosign-installer@v4.1.0

- name: Build compliance package
run: |
python scripts/build_compliance_package.py \
--implementation-dir . \
--version "${GITHUB_REF_NAME}" \
--source-ref "${GITHUB_SHA}" \
--output-dir "../../${COMPLIANCE_OUTPUT_DIR}"
working-directory: ${{ env.CONTROL_PLANE_DIR }}

- name: Sign compliance package tarball
run: |
cosign sign-blob --yes \
--bundle "${COMPLIANCE_OUTPUT_DIR}/${PACKAGE_BASENAME}.sigstore.json" \
"${COMPLIANCE_OUTPUT_DIR}/${PACKAGE_BASENAME}.tar.gz"

- name: Attest compliance package provenance
uses: actions/attest-build-provenance@v3
with:
subject-path: ${{ env.COMPLIANCE_OUTPUT_DIR }}/${{ env.PACKAGE_BASENAME }}.tar.gz

- name: Upload compliance release assets
uses: actions/upload-artifact@v4
with:
name: acr-control-plane-compliance-assets
path: |
${{ env.COMPLIANCE_OUTPUT_DIR }}/${{ env.PACKAGE_BASENAME }}.tar.gz
${{ env.COMPLIANCE_OUTPUT_DIR }}/${{ env.PACKAGE_BASENAME }}.manifest.json
${{ env.COMPLIANCE_OUTPUT_DIR }}/${{ env.PACKAGE_BASENAME }}.sha256
${{ env.COMPLIANCE_OUTPUT_DIR }}/${{ env.PACKAGE_BASENAME }}.sigstore.json
retention-days: 30

github-release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs: [docker-publish, compliance-package]
permissions:
contents: write
steps:
- uses: actions/checkout@v4

- name: Download compliance assets
uses: actions/download-artifact@v4
with:
name: acr-control-plane-compliance-assets
path: dist/release-assets

- name: Create release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
make_latest: true
files: |
dist/release-assets/${{ env.PACKAGE_BASENAME }}.tar.gz
dist/release-assets/${{ env.PACKAGE_BASENAME }}.manifest.json
dist/release-assets/${{ env.PACKAGE_BASENAME }}.sha256
dist/release-assets/${{ env.PACKAGE_BASENAME }}.sigstore.json
3 changes: 3 additions & 0 deletions implementations/acr-control-plane/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ WEBHOOK_URL=
# Receivers must verify this header before trusting the payload.
# Minimum 32 random bytes (hex). Leave blank to skip signing.
WEBHOOK_HMAC_SECRET=
# Minimum 32 random bytes (hex). Used to sign audit/evidence bundles.
AUDIT_SIGNING_SECRET=dev_audit_signing_secret_change_me

# ── OpenTelemetry ─────────────────────────────────────────────────────────────
# Leave blank to disable OTLP trace export (e.g. http://localhost:4318).
Expand Down Expand Up @@ -106,6 +108,7 @@ EXECUTOR_TIMEOUT_SECONDS=8.0
# Supported backends:
# local : write bundles to the local filesystem
# s3 : write bundles to S3 or an S3-compatible object store
REQUIRE_BUNDLE_AUTH=true
POLICY_BUNDLE_BACKEND=local
POLICY_BUNDLE_LOCAL_DIR=./var/policy_bundles
# Required when POLICY_BUNDLE_BACKEND=s3
Expand Down
3 changes: 2 additions & 1 deletion implementations/acr-control-plane/.env.production.example
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ EXECUTOR_INTEGRATIONS_JSON=
WEBHOOK_URL=
OTEL_EXPORTER_OTLP_ENDPOINT=

REQUIRE_BUNDLE_AUTH=false
POLICY_BUNDLE_BACKEND=s3
POLICY_BUNDLE_S3_BUCKET=CHANGE_ME
POLICY_BUNDLE_S3_PREFIX=acr/policy-bundles
POLICY_BUNDLE_S3_REGION=us-east-1
POLICY_BUNDLE_S3_ENDPOINT_URL=
POLICY_BUNDLE_PUBLIC_BASE_URL=
POLICY_BUNDLE_PUBLIC_BASE_URL=https://acr.example.com
10 changes: 10 additions & 0 deletions implementations/acr-control-plane/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ With the included stack, you can:

The included sample agent shows the control plane denying unsafe actions and escalating high-risk ones.

There is now an official [SDK and adapter guide](docs/sdk.md) covering:

- a Python SDK
- a LangGraph/LangChain-style tool guard
- a TypeScript SDK

There is also a runnable [protected executor example](examples/protected_executor/README.md) that verifies both `X-ACR-Execution-Token` and `X-ACR-Brokered-Credential`, so downstream services can reject direct-bypass requests that were not explicitly authorized by the gateway.

For workflow builders and orchestration tools, there is now an explicit [orchestrator integration guide](docs/orchestrators.md) plus an [n8n reference example](examples/n8n/README.md) showing how to put ACR underneath the workflow layer instead of relying on optional user behavior.
Expand Down Expand Up @@ -212,6 +218,10 @@ Current highlights:
- the repo includes a dedicated security workflow for CodeQL, Semgrep, Gitleaks, Trivy, and SBOM generation
- production secret generation and secret-hygiene checks are in place
- brokered downstream credential minting is implemented
- release provenance and signing guidance now live in [docs/provenance-and-verification.md](docs/provenance-and-verification.md)
- the enterprise review artifacts now live in [docs/compliance/README.md](docs/compliance/README.md)
- the blessed production deployment path now lives in [deploy/k8s/overlays/production/README.md](deploy/k8s/overlays/production/README.md)
- official Python and TypeScript SDKs now live in [docs/sdk.md](docs/sdk.md)
- the next major gap is reliability semantics under dependency failure

## Architecture
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ data:
KILLSWITCH_URL: "http://acr-killswitch:8443"
OTEL_SERVICE_NAME: "acr-control-plane"
EXECUTE_ALLOWED_ACTIONS: "true"
REQUIRE_BUNDLE_AUTH: "false"
POLICY_BUNDLE_BACKEND: "s3"
POLICY_BUNDLE_S3_BUCKET: "change-me"
POLICY_BUNDLE_S3_PREFIX: "acr/policy-bundles"
POLICY_BUNDLE_S3_REGION: "us-east-1"
POLICY_BUNDLE_PUBLIC_BASE_URL: "https://acr.example.com"
OIDC_ENABLED: "true"
OIDC_ISSUER: "https://login.example.com/"
OIDC_CLIENT_ID: "acr-control-plane"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ stringData:
OPERATOR_SESSION_SECRET: "change-me-32-bytes-minimum"
OIDC_CLIENT_SECRET: "change-me"
WEBHOOK_HMAC_SECRET: "change-me-32-bytes-minimum"
AUDIT_SIGNING_SECRET: "change-me-32-bytes-minimum"
EXECUTOR_HMAC_SECRET: "change-me-32-bytes-minimum"
EXECUTOR_CREDENTIAL_SECRET: "change-me-32-bytes-minimum"
FINANCE_EXECUTOR_API_KEY: "change-me"
EMAIL_EXECUTOR_API_KEY: "change-me"
TICKET_EXECUTOR_API_KEY: "change-me"
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ resources:
- namespace.yaml
- rbac.yaml
- gateway-configmap.yaml
- gateway-secret.example.yaml
- gateway-deployment.yaml
- gateway-service.yaml
- killswitch-deployment.yaml
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Blessed Production Deployment

This overlay is the supported production deployment path for the ACR control plane.

It assumes:

- Kubernetes as the runtime platform
- External Secrets Operator for application secret material
- managed PostgreSQL and managed Redis
- OIDC-first operator authentication
- S3 or an S3-compatible object store for policy bundle publishing
- ingress-nginx plus cert-manager for north-south traffic
- internal executors reachable only on private networks

It is opinionated on purpose. This path exists so platform and security teams have one deployment model to review, harden, and automate.

## What This Overlay Changes

Relative to `deploy/k8s/base`, this overlay:

- removes the example secret from the runtime path and expects a real `ExternalSecret`
- keeps bundle delivery on object storage and disables bundle auth so OPA can poll directly
- assumes OIDC is the normal operator login path
- replaces the base gateway and kill-switch network policy with a production egress allowlist
- adds namespace pod-security labels
- adds rollout settings like `minReadySeconds`, `progressDeadlineSeconds`, and topology spreading
- points workloads at release images rather than local `acr-gateway:1.0.0` tags

## Before You Apply It

1. Create a `ClusterSecretStore` or `SecretStore` for your secret manager.
2. Edit [external-secret.yaml](external-secret.yaml):
- `secretStoreRef.name`
- remote secret keys and property names
3. Edit [networkpolicy-production.yaml](networkpolicy-production.yaml):
- replace all `203.0.113.x/32` placeholder CIDRs
- add or remove executor namespaces as needed
4. Edit [gateway-configmap-patch.yaml](gateway-configmap-patch.yaml):
- hostnames
- object-storage details
- OIDC details
- OTLP endpoint if used
5. Edit [ingress-patch.yaml](ingress-patch.yaml):
- public host
- TLS secret
6. Make sure your cluster can pull the release images:
- mirror them into your internal registry, or
- create a registry pull secret if your GHCR packages are private
7. Replace the example release tags in [kustomization.yaml](kustomization.yaml) with your promoted image digests before production promotion.

## Build and Review

```bash
kubectl kustomize deploy/k8s/overlays/production
```

## Apply

```bash
kubectl apply -k deploy/k8s/overlays/production
```

## Post-Deploy Validation

- `kubectl -n acr-system get externalsecret,secret`
- `kubectl -n acr-system get pods`
- `kubectl -n acr-system rollout status deploy/acr-gateway`
- `kubectl -n acr-system rollout status deploy/acr-killswitch`
- `kubectl -n acr-system rollout status deploy/acr-opa`
- `curl https://acr.example.com/acr/health`
- `curl https://acr.example.com/acr/ready`
- validate OIDC login, policy activation, approval flow, evidence export, and executor reachability

## Why This Is The Blessed Path

This is the path that closes the earlier gaps:

- no runtime application of example secrets
- one explicit secrets-management model
- one explicit network-enforcement story
- object storage instead of local bundle state
- one ingress and rollout model for production

If you diverge from this overlay, treat it as a custom platform variant and review it accordingly.
Loading
Loading