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
115 changes: 115 additions & 0 deletions .github/workflows/release-eif.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
name: Release EIF

on:
workflow_dispatch:
inputs:
version:
description: 'Tag version (e.g. v0.0.29)'
required: true

permissions:
contents: write

jobs:
release-eif:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4

- name: Validate version
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail

if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::Invalid version: '$VERSION' (expected vX.Y.Z)"
exit 1
fi

if gh release view "$VERSION" >/dev/null 2>&1; then
echo "::error::Release $VERSION already exists"
exit 1
fi

if git ls-remote --exit-code --tags origin "refs/tags/${VERSION}" >/dev/null 2>&1; then
echo "::error::Tag $VERSION already exists on origin"
exit 1
fi

echo "Version $VERSION validated."

- uses: actions/setup-go@v5
with:
go-version: stable

- name: Install enclave CLI
run: go install github.com/ArkLabsHQ/introspector-enclave/cli/cmd/enclave@latest

- name: Pull Nix Docker image
run: docker pull nixos/nix:2.24.9

- name: Build EIF
run: enclave build

- name: Extract PCR values
id: pcr
run: |
PCR0=$(jq -r '.PCR0 // .pcr0' .enclave/artifacts/pcr.json)
PCR1=$(jq -r '.PCR1 // .pcr1' .enclave/artifacts/pcr.json)
PCR2=$(jq -r '.PCR2 // .pcr2' .enclave/artifacts/pcr.json)
echo "pcr0=${PCR0}" >> "$GITHUB_OUTPUT"
echo "pcr1=${PCR1}" >> "$GITHUB_OUTPUT"
echo "pcr2=${PCR2}" >> "$GITHUB_OUTPUT"
echo "PCR0: ${PCR0:0:32}..."

- name: Build deployment manifest
env:
PCR0: ${{ steps.pcr.outputs.pcr0 }}
PCR1: ${{ steps.pcr.outputs.pcr1 }}
PCR2: ${{ steps.pcr.outputs.pcr2 }}
REPO: ${{ github.repository }}
COMMIT_SHA: ${{ github.sha }}
TAG: ${{ inputs.version }}
run: |
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
jq -n \
--arg version "$TAG" \
--arg pcr0 "$PCR0" \
--arg pcr1 "$PCR1" \
--arg pcr2 "$PCR2" \
--arg timestamp "$TIMESTAMP" \
--arg commit "$COMMIT_SHA" \
--arg repo "$REPO" \
'{version: $version, pcr0: $pcr0, pcr1: $pcr1, pcr2: $pcr2, timestamp: $timestamp, commit: $commit, repo: $repo}' \
> deployment.json

- name: Upload EIF artifact
uses: actions/upload-artifact@v4
with:
name: enclave-eif-${{ inputs.version }}
path: |
.enclave/artifacts/image.eif
.enclave/artifacts/pcr.json
.enclave/artifacts/supervisor
deployment.json
retention-days: 30

- name: Create version release
env:
GH_TOKEN: ${{ github.token }}
PCR0: ${{ steps.pcr.outputs.pcr0 }}
COMMIT_SHA: ${{ github.sha }}
TAG: ${{ inputs.version }}
run: |
gh release create "$TAG" \
--target "$COMMIT_SHA" \
--title "EIF ${TAG}" \
--notes "EIF release ${TAG} from commit ${COMMIT_SHA::8}
PCR0: ${PCR0}" \
.enclave/artifacts/image.eif \
.enclave/artifacts/pcr.json \
.enclave/artifacts/supervisor \
deployment.json
11 changes: 10 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,13 @@
.claude
build/
bin/
docs/plans/
.enclave/
docs/plans/

# Go module vendor dir — only materialized by `enclave/test/` for the
# local-tree Nix EIF build; source of truth is go.mod/go.sum.
vendor/

# enclave-test scratch artifacts.
enclave/test/app/
enclave/test/tofu-*.log
45 changes: 45 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,50 @@ build-all:
lint:
golangci-lint run --fix

# === QEMU enclave integration test ===
# Thin wrappers around `enclave test {build,init,start,down}` from the
# introspector-enclave CLI (must be on $PATH). `make enclave-test` runs
# the full loop: build EIF, scaffold compose + ensure mock-arkd is wired
# in, boot the stack, run integration-test.sh, tear down.
.PHONY: enclave-test enclave-test-build enclave-test-init enclave-test-start enclave-test-down

define MOCK_ARKD_BLOCK

mock-arkd:
build:
context: ../../
dockerfile: enclave/test/mock-arkd/Dockerfile
network_mode: host
environment:
MOCK_ARKD_ADDR: ":8081"
healthcheck:
test: ["CMD-SHELL", "/usr/local/bin/grpcurl -plaintext -max-time 2 localhost:8081 list"]
interval: 5s
timeout: 3s
retries: 5
endef
export MOCK_ARKD_BLOCK

enclave-test: enclave-test-build enclave-test-init enclave-test-start
@./enclave/test/integration-test.sh
@$(MAKE) enclave-test-down

enclave-test-build:
@enclave test build

# `enclave test init` is merge-only-new on the compose file; the grep
# guard keeps the mock-arkd append idempotent across re-runs.
enclave-test-init:
@enclave test init
@if ! grep -q '^ mock-arkd:' enclave/test/docker-compose.yml; then \
echo "$$MOCK_ARKD_BLOCK" >> enclave/test/docker-compose.yml; \
fi

enclave-test-start:
@enclave test start

enclave-test-down:
@enclave test down

format:
@go fmt ./...
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,45 @@ make docker-run
make integrationtest
```

### Enclave integration test

Boots the introspector EIF inside a QEMU-emulated Nitro Enclave with mock AWS
services + mock-arkd, then runs the 6-test driver
(`enclave/test/integration-test.sh`: health, HTTP/2 ALPN, native gRPC `GetInfo`
with attestation, wrong-PCR0 reject, `SubmitTx` with a real PSBT, HTTP/1.1
compat). Requires the `enclave` CLI from
[introspector-enclave](https://github.com/ArkLabsHQ/introspector-enclave) on
`$PATH`, plus Docker.

```bash
enclave test build # auto-picks enclave/enclave_test.yaml
enclave test init # writes enclave/test/docker-compose.yml
```

Append the mock-arkd block once, below the
`# === user services below this line ===` marker (4-space indent matters):

```yaml
mock-arkd:
build:
context: ../../
dockerfile: enclave/test/mock-arkd/Dockerfile
network_mode: host
environment:
MOCK_ARKD_ADDR: ":8081"
healthcheck:
test: ["CMD-SHELL", "/usr/local/bin/grpcurl -plaintext -max-time 2 localhost:8081 list"]
interval: 5s
timeout: 3s
retries: 5
```

```bash
enclave test start # boots the stack, waits for /health 200
./enclave/test/integration-test.sh # runs the 6-test driver
enclave test down # tear down + remove volumes
```

## Supported Opcodes

The following opcodes are supported by the Arkade script engine. They extend Bitcoin Script with additional introspection, data manipulation, and cryptographic operations.
Expand Down
60 changes: 60 additions & 0 deletions enclave/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# enclave/

Reproducible Nitro Enclave (EIF) build for the introspector app.

## Install the `enclave` CLI

```sh
go install github.com/ArkLabsHQ/introspector-enclave/cli/cmd/enclave@latest
```

This puts `enclave` on your `$PATH` (assuming `$GOPATH/bin` is on it).

To pin to a specific runtime release, replace `@latest` with a tag:

```sh
go install github.com/ArkLabsHQ/introspector-enclave/cli/cmd/enclave@v0.0.72
```

## Update the pinned app commit

`enclave.yaml` pins the app to a specific commit via `app.nix_rev`, plus the
matching `nix_hash` and `nix_vendor_hash`. To bump to a new commit:

```sh
enclave setup --commit <commit_hash>
```

This rewrites `app.nix_rev`, `app.nix_hash`, and `app.nix_vendor_hash` in
`enclave.yaml` and regenerates `flake.lock`. Commit both files.

Pass `--force-flake` if you intentionally want to regenerate it from the template
(e.g. after changing `app.language`).

## Build the EIF locally

```sh
enclave build
```

Outputs:

- `.enclave/artifacts/image.eif` — the enclave image
- `.enclave/artifacts/pcr.json` — measurement values (PCR0/1/2)
- `.enclave/artifacts/supervisor` — runtime supervisor binary

The build is fully reproducible — same inputs (commit, hashes, runtime pin)
produce byte-identical artifacts and the same PCR0.

## Cut a tagged release

Releases are dispatched manually via the `Release EIF` workflow:

1. Bump `app.nix_rev` and the runtime pin in `enclave.yaml` if needed.
2. Commit and push to `master`.
3. Run **Actions → Release EIF → Run workflow** and supply a version
(e.g. `v0.1.0`). The workflow validates the version (`vX.Y.Z`, not
already a tag/release), builds the EIF, and publishes a single
immutable release containing `image.eif`, `pcr.json`, `supervisor`,
and `deployment.json`.

49 changes: 49 additions & 0 deletions enclave/enclave.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Enclave configuration
# Edit this file then run: enclave init

name: introspector # App name (used in stack name, EIF name)
version: 0.0.1 # Build version (semver, baked into binary via ldflags)
region: us-east-1 # AWS region
account: "" # AWS account ID (required)
prefix: dev # Deployment prefix (stack = {prefix}Nitro{Name})
instance_type: m6i.xlarge # EC2 instance type
migration_cooldown: "1m" # Cooldown before migration proceeds
is_kms_key_locked: false # false (default): grant AWS root kms:PutKeyPolicy on the locked key — recovery from lockout works by adding a new PCR0 condition.
# true: strict mode — the locked policy is frozen; even root cannot rewrite it. Only the attested enclave can decrypt.

# Runtime coordinates for the enclave supervisor binary.
# The supervisor handles attestation, secrets, PCR extension, and signing
# middleware automatically. Your app is a plain HTTP server with zero runtime imports.
runtime:
rev: "v0.0.76"
hash: "sha256-J/gbggHP7VD2uZT+itYH/x09uATJY2OHgte10N22VLM="
vendor_hash: "sha256-kkgp+uxc9pohZiCnz84nyXroUzsPhhtb0VgGa3dQgUo="

app:
language: "go" # App language: go, nodejs, dotnet, rust
source: nix # "nix" = fetch from GitHub via Nix

# GitHub coordinates for the app to run inside the enclave.
# Your app is a plain HTTP server that listens on ENCLAVE_APP_PORT (default 7074).
# Secrets are passed as environment variables. No runtime imports needed.
nix_owner: "ArkLabsHQ" # GitHub owner (required)
nix_repo: "introspector" # GitHub repo name (required)
nix_rev: "67616e261b6e90a839a250599f8afa6ac52f9264" # Git commit SHA (required)
nix_hash: "sha256-HH04xNAJnmkboWd/iwV5swCQ34Gko7EdlQnltB9G7r4=" # Nix source hash: nix-prefetch-url --unpack (required)
nix_vendor_hash: "sha256-y9AH8Ebus4IEL1YHf0kbCHCczRJJxhQedi7MMbUNYkA=" # Go vendor hash (required)
nix_sub_packages:
- cmd
binary_name: introspector
nix_subdir: "" # Subdirectory for monorepo (e.g. "server")
release_tag: "eif-latest" # GitHub Release tag used by 'enclave tofu --remote'

# INTROSPECTOR_ARKD_URL is injected by tofu at deploy time;
# AWS credentials come from the EC2 role via IMDS.
env:
INTROSPECTOR_PORT: "7074"
INTROSPECTOR_NO_TLS: "true"

secrets:
- name: signing_key
env_var: INTROSPECTOR_SECRET_KEY

47 changes: 47 additions & 0 deletions enclave/enclave_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Test EIF config — `enclave test {build,init,start}` auto-pick this.
# Identical to enclave.yaml except for app.env (localstack + mock-arkd).

name: introspector
version: 0.0.1
region: us-east-1
account: ""
prefix: dev
instance_type: m6i.xlarge
migration_cooldown: "1m"
is_kms_key_locked: false

runtime:
rev: "v0.0.76"
hash: "sha256-J/gbggHP7VD2uZT+itYH/x09uATJY2OHgte10N22VLM="
vendor_hash: "sha256-kkgp+uxc9pohZiCnz84nyXroUzsPhhtb0VgGa3dQgUo="

app:
language: "go"
source: nix

nix_owner: "ArkLabsHQ"
nix_repo: "introspector"
nix_rev: "67616e261b6e90a839a250599f8afa6ac52f9264"
nix_hash: "sha256-HH04xNAJnmkboWd/iwV5swCQ34Gko7EdlQnltB9G7r4="
nix_vendor_hash: "sha256-y9AH8Ebus4IEL1YHf0kbCHCczRJJxhQedi7MMbUNYkA="
nix_sub_packages:
- cmd
binary_name: introspector
nix_subdir: ""
release_tag: "eif-latest"

env:
INTROSPECTOR_PORT: "7074"
INTROSPECTOR_NO_TLS: "true"
INTROSPECTOR_ARKD_URL: "host.containers.internal:8081"
AWS_ENDPOINT_URL_KMS: "http://host.containers.internal:4000"
AWS_ENDPOINT_URL_SSM: "http://host.containers.internal:4566"
AWS_ENDPOINT_URL_STS: "http://host.containers.internal:4566"
AWS_ENDPOINT_URL_S3: "http://host.containers.internal:4566"
AWS_ENDPOINT_URL_LOGS: "http://host.containers.internal:4566"
AWS_ACCESS_KEY_ID: "test"
AWS_SECRET_ACCESS_KEY: "test"

secrets:
- name: signing_key
env_var: INTROSPECTOR_SECRET_KEY
Loading
Loading