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
19 changes: 18 additions & 1 deletion .github/workflows/ci-go.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Go CI (format, lint, vet, test, build)

# Go validation on a GitHub-hosted Linux runner. Mirrors `task ci`
# (fmt + lint + vet + test + build). Runs on PRs and pushes to main.
# (fmt + proto lint + generated-code check + lint + vet + test + build). Runs on PRs and pushes to main.

# Skip when a change touches only non-Go files (Markdown, docs, the image
# build scripts, or issue templates). A mixed change still runs, since
Expand Down Expand Up @@ -57,6 +57,23 @@ jobs:
# built with an older Go panics on newer language/std usage.
run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2

- name: Install buf
# task ci lints the node API protos and regenerates gen/go to check it
# is current. The release binary is checked against the SHA-256 its
# release publishes (sha256.txt); bump both together.
env:
BUF_VERSION: 1.73.0
BUF_SHA256: 8f2986298ad08f0cc1bf999b9797b7c383adf32d7edf0f73d6f1e1a701baeac1
run: |
set -euo pipefail
bin="$RUNNER_TEMP/bin"
mkdir -p "$bin"
curl -sSfL -o "$bin/buf" \
"https://github.com/bufbuild/buf/releases/download/v${BUF_VERSION}/buf-Linux-x86_64"
echo "${BUF_SHA256} $bin/buf" | sha256sum -c -
chmod +x "$bin/buf"
echo "$bin" >> "$GITHUB_PATH"

- name: Install swtpm
# The TPM-held RSA CA end-to-end test needs a TPM that implements
# RSA-3072. The in-process simulator stops at RSA-2048, and the test
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ build/out/

# Local design notes (non-tracked).
plan/

# Pinned protoc plugins (task tools)
.bin/
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# AGENTS.md - cryptos
# AGENTS.md - cryptos-node

Guide for AI agents working in this repository. Pair with `CLAUDE.md` (the working agreement and
hook-enforced rules). Keep this file current when the build, layout, or public API changes.
Expand All @@ -10,7 +10,7 @@ Immutable, API-driven, high-assurance PKI operating system. Talos-style: no SSH,
<!-- Fill in: what the project does, what it ships (library, service, action, CLI), and the one or
two things an agent must understand before changing it. -->

## Using cryptos
## Using cryptos-node

<!-- If this project is consumed by others (a library/plugin/action), describe the contract a
consumer must respect: the single entry point, the public surface, required options, and anything
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# CLAUDE.md - cryptos
# CLAUDE.md - cryptos-node

Working agreement for this repository. It was scaffolded from `Bugs5382/project-template`;
the governance below is shared across all repos created that way.
Expand Down Expand Up @@ -87,6 +87,6 @@ for the full action-release sequence.
auto-created `github-pages` environment allows tag refs. Once, alongside enabling Pages
(Settings -> Pages -> Source = GitHub Actions), add a tag policy, then re-run the failed Deploy
job (no need to re-cut the tag):
`gh api -X POST repos/CryptOS-PKI/cryptos/environments/github-pages/deployment-branch-policies -f name='v*' -f type=tag`
`gh api -X POST repos/CryptOS-PKI/cryptos-node/environments/github-pages/deployment-branch-policies -f name='v*' -f type=tag`
- Docusaurus MDX 3: avoid the `## Heading {#custom-id}` explicit-id syntax (it fails to compile);
rely on the auto-generated slugs.
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# cryptos 🧠
# cryptos-node 🧠

> 🔐 The OS / engine for [CryptOS-PKI](https://github.com/CryptOS-PKI) — an immutable, API-driven, high-assurance PKI operating system in the Talos Linux tradition.

Builds a signed Unified Kernel Image (UKI): hardened kernel + Go-based PID 1 + read-only SquashFS rootfs + TPM-unsealed encrypted state partition. A single image boots into a Root, Intermediate, or Issuing CA role based on its machine config. No SSH, no shell, no interactive access. Private keys are TPM-bound and never live on disk in the clear.

> [!WARNING]
> 🚧 **Pre-1.0: any release can change fundamentally.** CryptOS is pre-1.0. Until v1.0.0, any release may change configuration, APIs, on-disk and state formats, trust setup, and upgrade paths, sometimes with no migration path. If you run it in production, you accept that risk. Read [each release's upgrade notes](https://github.com/CryptOS-PKI/cryptos/releases) before you upgrade.
> 🚧 **Pre-1.0: any release can change fundamentally.** CryptOS is pre-1.0. Until v1.0.0, any release may change configuration, APIs, on-disk and state formats, trust setup, and upgrade paths, sometimes with no migration path. If you run it in production, you accept that risk. Read [each release's upgrade notes](https://github.com/CryptOS-PKI/cryptos-node/releases) before you upgrade.

## ✨ Architecture at a glance

Expand All @@ -19,6 +19,8 @@ Builds a signed Unified Kernel Image (UKI): hardened kernel + Go-based PID 1 + r
## 📂 Layout

```
proto/cryptos/node/v1/ # the node API (cryptos.node.v1) this OS serves
gen/go/cryptos/node/v1/ # generated Go stubs (package nodev1); `task generate`, never hand-edited
cmd/
init/ # PID 1 binary; becomes /init in the SquashFS
cryptosctl/ # operator CLI (the only management surface on a standalone node)
Expand Down Expand Up @@ -52,10 +54,11 @@ testdata/configs/ # sample machine configs

## 🛠️ Build + run (dev loop)

Requires Go 1.26.8+ (the `go` line in `go.mod`; an older local Go downloads that toolchain on first use), [`go-task`](https://taskfile.dev), `golangci-lint`, `golic`, and (for integration testing) `qemu-system-x86_64` + `swtpm` + OVMF. `task test` also runs the TPM-held RSA CA end-to-end test against `swtpm` when it is installed, because the in-process TPM simulator implements RSA-2048 only; without `swtpm` that test skips locally and fails in CI.
Requires Go 1.26.8+ (the `go` line in `go.mod`; an older local Go downloads that toolchain on first use), [`go-task`](https://taskfile.dev), `golangci-lint`, `golic`, [`buf`](https://buf.build) (proto lint and codegen), and (for integration testing) `qemu-system-x86_64` + `swtpm` + OVMF. `task test` also runs the TPM-held RSA CA end-to-end test against `swtpm` when it is installed, because the in-process TPM simulator implements RSA-2048 only; without `swtpm` that test skips locally and fails in CI.

```bash
task ci # fmt + lint + vet + test + build (both binaries)
task ci # fmt + proto lint + generated-code check + lint + vet + test + build
task generate # regenerate gen/go from proto/ with the pinned plugins (task tools)
task build # produces bin/init and bin/cryptosctl, stamped with the build identity
task license # re-inject Apache 2.0 headers via golic
task e2e:kind # Linux + docker: cert-manager in kind gets a certificate over ACME
Expand Down Expand Up @@ -102,7 +105,7 @@ Each `v*` tag attaches these to its GitHub Release, all built by `task iso:unsig

GitHub Actions:

- **`ci-go`** ([`ci-go.yml`](.github/workflows/ci-go.yml)) — `task ci` (format, lint, vet, test, build) on every pull request + push to `main`, on a GitHub-hosted Linux runner, with `swtpm` installed for the TPM-held RSA CA test. Draft pull requests are skipped; CI runs when the PR is marked ready.
- **`ci-go`** ([`ci-go.yml`](.github/workflows/ci-go.yml)) — `task ci` (format, proto lint, generated-code check, lint, vet, test, build) on every pull request + push to `main`, on a GitHub-hosted Linux runner, with `swtpm` installed for the TPM-held RSA CA test. Draft pull requests are skipped; CI runs when the PR is marked ready.

After a stacked pull request is retargeted onto `main`, CI starts on its next push, or when it is toggled to draft and back to ready.
- **`ci-image`** ([`ci-image.yml`](.github/workflows/ci-image.yml)) — builds the UKI on a **GitHub-hosted runner** (amd64 on `ubuntu-latest`, arm64 on `ubuntu-24.04-arm`), installing the kernel / `ukify` / `sbsign` toolchain per run. Runs on push to `main`, tags, and manual dispatch; use `workflow_dispatch` on a branch to validate image changes before merging. On `main` it signs with a per-run ephemeral key as a smoke test and uploads nothing. On a `v*` tag it builds the unsigned [release assets](#release-assets) and attaches them to the tag's release (a draft, marked pre-release for `-alpha`/`-beta`/`-rc` tags, if none exists yet); dispatch with `release_assets` builds them without publishing.
Expand Down Expand Up @@ -168,9 +171,12 @@ The image ships no guest tools, so a vSphere "Shut Down Guest OS" or "Restart Gu
2. 🔌 **Phase 2 — Role-aware API + protocol adapters + Fleet Manager.** Root / Intermediate / Issuing role split, ACME / SCEP / EST / WSTEP / RFC 3161 / OCSP / CRL.
3. 🛡️ **Phase 3 — Pool, HA, extensions, isolation, recovery.** 2-node HA pairs (Infoblox-style failover, VRRPv3 VIP), multi-Root topology (configurable depth, default cap 3), Fleet Manager linkage protocol, Talos-style signed late-binding extensions, disaster-recovery escrow.

## 📡 Node API

The gRPC API this OS serves is defined here, in [`proto/cryptos/node/v1`](proto/cryptos/node/v1) (package `cryptos.node.v1`). The Go stubs live under [`gen/go/cryptos/node/v1`](gen/go/cryptos/node/v1) (import `github.com/CryptOS-PKI/cryptos-node/gen/go/cryptos/node/v1`, package `nodev1`), and the Fleet Manager imports them from there. Change a `.proto`, run `task generate`, and commit the regenerated tree in the same change; `task ci` fails when `gen/` is stale. `task proto:breaking` checks a change against `main`.

## 🧭 Companion repos

- 📡 [`api`](https://github.com/CryptOS-PKI/api) — shared `.proto` definitions and generated gRPC stubs.
- 🛰️ [`manager`](https://github.com/CryptOS-PKI/manager) — Fleet Manager backend (optional).
- 🎨 [`web`](https://github.com/CryptOS-PKI/web) — Fleet Manager web frontend (optional, served by `manager/`).

Expand Down
77 changes: 74 additions & 3 deletions Taskfile.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
version: '3'

# Task targets for the CryptOS-PKI cryptos/ module.
# Requires on PATH: go, golangci-lint, golic.
# Task targets for the CryptOS-PKI cryptos-node module.
# Requires on PATH: go, golangci-lint, golic, buf. The Go codegen plugins are
# not taken from PATH: `task tools` installs them at the versions pinned below
# into .bin/, and buf.gen.yaml runs them from there, so every machine
# generates the same bytes.

vars:
# Bump a pin, then run `task generate` and commit the regenerated tree in the
# same change.
PROTOC_GEN_GO_VERSION: v1.34.2
PROTOC_GEN_GO_GRPC_VERSION: v1.5.1
# The Go toolchain that builds the plugins is pinned too: protoc-gen-go
# formats its output with the go/format it was compiled with, so a newer Go
# can change the generated bytes. Kept at the go directive in go.mod.
PLUGIN_GO_TOOLCHAIN: go1.26.8
TOOLS_BIN: '{{.ROOT_DIR}}/.bin'
# The baseline `buf breaking` compares against. Override it to compare
# against another ref, for example BREAKING_AGAINST='.git#branch=origin/main'.
BREAKING_AGAINST: 'https://github.com/CryptOS-PKI/cryptos-node.git#branch=main'

tasks:
default:
Expand Down Expand Up @@ -41,6 +58,58 @@ tasks:
- go build -ldflags "{{.BUILDINFO}}" -o bin/cryptos-install ./cmd/cryptos-install
- go build -ldflags "{{.BUILDINFO}}" -o bin/cryptos-sbkey ./cmd/cryptos-sbkey

proto:fmt:
desc: Format the node API protos
cmds:
- buf format -w

proto:lint:
desc: Lint the node API protos and check their formatting
cmds:
- buf lint
- buf format -d --exit-code

proto:breaking:
desc: Check the node API protos for breaking changes against main (BREAKING_AGAINST overrides)
cmds:
- buf breaking --against '{{.BREAKING_AGAINST}}'

tools:
desc: Install the pinned Go codegen plugins into .bin/
# status reads the Go version and module version stamped into each binary,
# so a .bin/ left over from an older pin gets reinstalled instead of
# silently reused.
status:
- go version {{.TOOLS_BIN}}/protoc-gen-go {{.TOOLS_BIN}}/protoc-gen-go-grpc | awk '$2 != "{{.PLUGIN_GO_TOOLCHAIN}}" {bad=1} END {exit bad || NR != 2}'
- go version -m {{.TOOLS_BIN}}/protoc-gen-go | grep -qE '^[[:space:]]+mod[[:space:]]+google.golang.org/protobuf[[:space:]]+{{.PROTOC_GEN_GO_VERSION}}[[:space:]]'
- go version -m {{.TOOLS_BIN}}/protoc-gen-go-grpc | grep -qE '^[[:space:]]+mod[[:space:]]+google.golang.org/grpc/cmd/protoc-gen-go-grpc[[:space:]]+{{.PROTOC_GEN_GO_GRPC_VERSION}}[[:space:]]'
# GOBIN and GOTOOLCHAIN are set on each command, not in an env: block,
# because Task lets an exported shell variable win over env:.
cmds:
- GOBIN='{{.TOOLS_BIN}}' GOTOOLCHAIN={{.PLUGIN_GO_TOOLCHAIN}} go install google.golang.org/protobuf/cmd/protoc-gen-go@{{.PROTOC_GEN_GO_VERSION}}
- GOBIN='{{.TOOLS_BIN}}' GOTOOLCHAIN={{.PLUGIN_GO_TOOLCHAIN}} go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@{{.PROTOC_GEN_GO_GRPC_VERSION}}

generate:
desc: Generate the Go stubs under gen/go from the protos with the pinned plugins
deps: [tools]
cmds:
- buf generate

generate:verify:
desc: Generate and assert the committed gen/ tree is current
# git status rather than git diff, so a generated file that is new counts
# as stale too instead of passing as untracked.
cmds:
- task: generate
- |
stale="$(git status --porcelain gen/)"
if [ -n "$stale" ]; then
echo "Generated output is out of date. Run 'task generate' and commit the result." >&2
echo "$stale" >&2
git --no-pager diff gen/ >&2
exit 1
fi

tidy:
desc: Tidy the Go module
cmds:
Expand All @@ -58,9 +127,11 @@ tasks:
- golic inject -t apache2 -c "The CryptOS Authors." --license-file

ci:
desc: Full local validation chain (fmt + lint + vet + test + build)
desc: Full local validation chain (fmt + proto lint + generate-and-verify + lint + vet + test + build)
cmds:
- task: fmt
- task: proto:lint
- task: generate:verify
- task: lint
- task: vet
- task: test
Expand Down
16 changes: 16 additions & 0 deletions buf.gen.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# The Go plugins run from .bin/, where `task tools` installs the versions
# pinned in Taskfile.yml. Run `task generate` rather than `buf generate`
# directly, so the pinned plugins are installed first.
version: v2
inputs:
- directory: proto
plugins:
- local: .bin/protoc-gen-go
out: gen/go
opt:
- paths=source_relative
- local: .bin/protoc-gen-go-grpc
out: gen/go
opt:
- paths=source_relative
- require_unimplemented_servers=false
9 changes: 9 additions & 0 deletions buf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
version: v2
modules:
- path: proto
lint:
use:
- STANDARD
breaking:
use:
- FILE
2 changes: 1 addition & 1 deletion build/ci/buildinfo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ set -euo pipefail

here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
root="$(cd "$here/../.." && pwd)"
pkg="github.com/CryptOS-PKI/cryptos/internal/buildinfo"
pkg="github.com/CryptOS-PKI/cryptos-node/internal/buildinfo"

version="${CRYPTOS_VERSION:-$(git -C "$root" describe --tags --always --dirty 2>/dev/null || echo dev)}"
flags="-X $pkg.Version=$version"
Expand Down
4 changes: 2 additions & 2 deletions build/squashfs/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ buildinfo_ldflags="$(bash "$root/build/ci/buildinfo.sh")"
echo "rootfs: build identity: $buildinfo_ldflags"
init_ldflags="-s -w $buildinfo_ldflags"
if [ "$STATEKEY" = "nodeid" ]; then
init_ldflags="$init_ldflags -X github.com/CryptOS-PKI/cryptos/internal/init.StateKeyMode=nodeid"
init_ldflags="$init_ldflags -X github.com/CryptOS-PKI/cryptos-node/internal/init.StateKeyMode=nodeid"
elif [ "$STATEKEY" != "tpm" ]; then
echo "build: unknown STATEKEY=$STATEKEY (want tpm|nodeid)" >&2; exit 1
fi
Expand All @@ -56,7 +56,7 @@ fi
# SB_CERT for this step): such a node is upgraded by reinstalling it.
if [ -n "${SB_CERT:-}" ]; then
release_der_b64="$(openssl x509 -in "$SB_CERT" -outform DER | base64 -w0)"
init_ldflags="$init_ldflags -X github.com/CryptOS-PKI/cryptos/internal/release.CertificateDER=$release_der_b64"
init_ldflags="$init_ldflags -X github.com/CryptOS-PKI/cryptos-node/internal/release.CertificateDER=$release_der_b64"
echo "rootfs: upgrade anchor: stamped from $SB_CERT"
else
echo "rootfs: upgrade anchor: none (SB_CERT unset)"
Expand Down
2 changes: 1 addition & 1 deletion cmd/cryptos-console/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
"io"
"time"

"github.com/CryptOS-PKI/cryptos/internal/console"
"github.com/CryptOS-PKI/cryptos-node/internal/console"
)

// ctrlR is the byte the terminal sends for Ctrl-R; it arms the reset ceremony.
Expand Down
2 changes: 1 addition & 1 deletion cmd/cryptos-console/loop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import (
"testing"
"time"

"github.com/CryptOS-PKI/cryptos/internal/console"
"github.com/CryptOS-PKI/cryptos-node/internal/console"
)

func TestRunRendersSnapshot(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion cmd/cryptos-console/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
"syscall"
"time"

"github.com/CryptOS-PKI/cryptos/internal/console"
"github.com/CryptOS-PKI/cryptos-node/internal/console"
)

func main() {
Expand Down
2 changes: 1 addition & 1 deletion cmd/cryptos-console/reset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
"testing"
"time"

"github.com/CryptOS-PKI/cryptos/internal/console"
"github.com/CryptOS-PKI/cryptos-node/internal/console"
)

// servingSnap returns a snapshot func that always reports an established root
Expand Down
2 changes: 1 addition & 1 deletion cmd/cryptos-install/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
"fmt"
"os"

"github.com/CryptOS-PKI/cryptos/internal/install"
"github.com/CryptOS-PKI/cryptos-node/internal/install"
)

func main() {
Expand Down
2 changes: 1 addition & 1 deletion cmd/cryptos-sbkey/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import (
"path/filepath"
"time"

"github.com/CryptOS-PKI/cryptos/internal/secureboot"
"github.com/CryptOS-PKI/cryptos-node/internal/secureboot"
)

func main() {
Expand Down
2 changes: 1 addition & 1 deletion cmd/cryptos-switchroot/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ limitations under the License.
import (
"os"

"github.com/CryptOS-PKI/cryptos/internal/switchroot"
"github.com/CryptOS-PKI/cryptos-node/internal/switchroot"
)

func main() {
Expand Down
Loading