Skip to content

Commit

Permalink
refactor: attestation uses key providers to generate/retrieve keys
Browse files Browse the repository at this point in the history
instead of directly supporting signing key backup service, attestation
now supports fetching keys via generic key providers. Currently 3
providers are implemented:

* embed
* backup
* get (fetches full key via API)

In the future we can add more providers such as querying keys from
secret manager/etc.

In order to achieve that there is top-level attestation object now:

```
attestation {
    key_provider: "get" # picks which provider to use
    # configure providers
    attestation_key_embed {...}
    attestation_key_backup {...}
    attestation_key_get {...}
}
```

Each provider supports their set of fields such as external providers
might require to configure `auth` which will use existing `auth_config`s.

To make the code simpler to follow as well as to make UX simpler some
changes:

* `chalk setup` does not prompt for password anymore.
  To import existing key password needs to be supplied via
  `CHALK_PASSWORD` env var.
* there are no more `chalk setup load` and `chalk setup gen`
  subcommands. There is only a single `chalk setup` which either loads
  key if the provider supports that or generates new key, again if
  provider supports that. This allowed to remove a lot of complexity
  in the key loading logic.
* all provider logic is in attestation/<provider>.nim
* to avoid name conflict attestation.nim was renamed to
  attestation_api.nim which also matches plugin_api.nim
* attestation_api.nim now:
  * handles key retrieval/generation via provider
  * implements logic to sign/verify signatures (not refactored)
  • Loading branch information
miki725 committed Mar 13, 2024
1 parent e0dd59e commit ef2fb7b
Show file tree
Hide file tree
Showing 31 changed files with 1,355 additions and 1,135 deletions.
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,40 @@

## Main

### Breaking Changes

- Attestation key generation/retrieval was refactored
to use key providers. As such all previous config
values related to signing backup service have changed.

Removed attributes:

- `use_signing_key_backup_service`
- `signing_key_backup_service_url`
- `signing_key_backup_service_auth_config_name`
- `signing_key_backup_service_timeout`
- `signing_key_location`

Instead now each individual key provider can be separately
configured:

```
attestation {
key_provider: "embed" # or "backup" which enables key backup provider
# as previously configured by
# `use_signing_key_backup_service`
attestation_key_embed {
location: "./chalk." # used to be `signing_key_location`
}
attestation_key_backup {
location: "./chalk." # used to be `signing_key_location`
uri: "https://..." # used to be `signing_key_backup_service_url`
auth: "..." # used to be `signing_key_backup_service_auth_config_name`
timeout: << 1 sec >> # used to be `signing_key_backup_service_timeout`
}
}
```

### Fixes

- Fixes a segfault when using secrets backup service
Expand Down
23 changes: 21 additions & 2 deletions configs/connect.c4m
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,32 @@ auth_config crashoverride {
~auth: "jwt"
}

func get_crashoverride_reporting_jwt() {
headers := auth_headers("crashoverride")
if contains(headers, "authorization") {
response := url_post("https://chalk.crashoverride.run/v0.1/key-provider/jwt", "", headers)
json := parse_json(response)
token := get(json, "jwt")
return token
}
return ""
}

auth_config crashoverride_reporting {
~auth: "jwt"
~token: memoize("crashoverride_jwt", func get_crashoverride_reporting_jwt() -> string)
}

attestation {
~key_provider: "get"
}

sink_config crashoverride {
~enabled: true
~sink: "presign"
~priority: 999999
~uri: "https://chalk.crashoverride.run/v0.1/report"
~auth: "crashoverride"
~auth: "crashoverride_reporting"
}

custom_report crashoverride {
Expand All @@ -31,7 +51,6 @@ custom_report crashoverride {
~use_when: ["insert", "build", "exec"]
}

use_signing_key_backup_service = true
docker.wrap_entrypoint = true
run_sbom_tools = true
run_sast_tools = true
Expand Down
2 changes: 2 additions & 0 deletions server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ FROM python:3.11.3-alpine as base
ENV VIRTUAL_ENV=/server.env
ENV PATH=$VIRTUAL_ENV/bin:$PATH

COPY --from=gcr.io/projectsigstore/cosign:v2.1.1 /ko-app/cosign /usr/local/bin/cosign

# -------------------------------------------------------------------

FROM base as deps
Expand Down
63 changes: 60 additions & 3 deletions server/server/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,27 @@
#
# This file is part of Chalk
# (see https://crashoverride.com/docs/chalk)
import asyncio
import dataclasses
import logging.config
from typing import Any, Optional
import pathlib
import secrets
import shutil
import tempfile
from typing import Annotated, Any, Optional

import os
import sqlalchemy
from fastapi import Depends, FastAPI, HTTPException, Request, Response, status
from fastapi.responses import RedirectResponse
from fastapi import (
Body,
Depends,
FastAPI,
HTTPException,
Request,
Response,
status,
)
from fastapi.responses import PlainTextResponse, RedirectResponse
from sqlalchemy.orm import Session

from .__version__ import __version__
Expand Down Expand Up @@ -183,3 +196,47 @@ async def list_reports(
async def list_stats(db: Session = Depends(get_db)) -> list[schemas.Stat]:
chalk_stats = db.query(models.Stat).all()
return [schemas.Stat.model_validate(vars(c)) for c in chalk_stats]


cosign = {}


@app.get("/cosign")
async def get_cosign():
global cosign
if not cosign:
with tempfile.TemporaryDirectory() as _tmp:
password = secrets.token_bytes(16).hex()
tmp = pathlib.Path(_tmp)
process = await asyncio.subprocess.create_subprocess_exec(
shutil.which("cosign"),
"generate-key-pair",
"--output-key-prefix",
"chalk",
env={"COSIGN_PASSWORD": password},
cwd=tmp,
)
await process.wait()
cosign = {
"privateKey": (tmp / "chalk.key").read_text(),
"publicKey": (tmp / "chalk.pub").read_text(),
"password": password,
}
return cosign


backup = {}


@app.get("/backup/{key_id}", response_class=PlainTextResponse)
async def get_backup(key_id: str):
try:
return backup[key_id]
except KeyError:
raise HTTPException(status_code=404)


@app.put("/backup/{key_id}")
async def put_backup(key_id: str, request: Request):
password = await request.body()
backup[key_id] = password
Loading

0 comments on commit ef2fb7b

Please sign in to comment.