Skip to content
Closed
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
10 changes: 10 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ jobs:
needs: [resolve-env]
environment: ${{ needs.resolve-env.outputs.environment }}
runs-on: macos-26-xlarge
permissions:
contents: write
id-token: write
attestations: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

Expand Down Expand Up @@ -501,6 +505,11 @@ jobs:
wait
echo "DMG uploaded to R2"

- name: Attest build provenance
uses: actions/attest-build-provenance@v4
with:
subject-path: /tmp/eigeninference-bundle-macos-arm64.tar.gz

# --- Register with coordinator ---

- name: Register release with coordinator
Expand Down Expand Up @@ -561,6 +570,7 @@ jobs:
**Runtime hash (vllm-mlx):** `${{ env.RUNTIME_HASH }}`
**Signed by:** Developer ID Application: Eigen Labs, Inc. (SLDQ2GJ6TL)
**Notarized:** Yes
**Build provenance:** Verify with `gh attestation verify eigeninference-bundle-macos-arm64.tar.gz --repo Layr-Labs/d-inference`

### Install
```bash
Expand Down
14 changes: 14 additions & 0 deletions coordinator/cmd/coordinator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
"github.com/eigeninference/coordinator/internal/attestation"
"github.com/eigeninference/coordinator/internal/auth"
"github.com/eigeninference/coordinator/internal/billing"
"github.com/eigeninference/coordinator/internal/buildattest"
"github.com/eigeninference/coordinator/internal/datadog"
"github.com/eigeninference/coordinator/internal/e2e"
"github.com/eigeninference/coordinator/internal/mdm"
Expand Down Expand Up @@ -253,6 +254,19 @@ func main() {
logger.Info("release key configured")
}

// Build provenance attestation policy — verifies GitHub Actions provenance
// for release bundles before accepting them.
attestPolicy := buildattest.PolicyFromEnv()
srv.SetAttestationPolicy(attestPolicy)
if attestPolicy.Enabled() {
logger.Info("build attestation verification enabled",
"required", attestPolicy.Required,
"repo", attestPolicy.TrustedRepo,
)
} else if attestPolicy.Required {
logger.Warn("build attestation is required but GITHUB_ATTESTATION_TOKEN is not set")
}

// Sync known-good provider hashes from active releases in the store.
// Falls back to env vars if no releases exist yet.
srv.SyncBinaryHashes()
Expand Down
42 changes: 42 additions & 0 deletions coordinator/internal/api/release_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package api
import (
"crypto/subtle"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"

"github.com/eigeninference/coordinator/internal/auth"
"github.com/eigeninference/coordinator/internal/buildattest"
"github.com/eigeninference/coordinator/internal/store"
)

Expand Down Expand Up @@ -42,6 +45,45 @@ func (s *Server) handleRegisterRelease(w http.ResponseWriter, r *http.Request) {
return
}

// Verify build provenance attestation if configured.
if s.attestationPolicy.Enabled() || s.attestationPolicy.Required {
bundleHash := release.BundleHash
if bundleHash == "" {
bundleHash = release.BinaryHash // fallback to binary hash
}
if bundleHash != "" {
result, err := buildattest.VerifyRelease(r.Context(), s.attestationPolicy, bundleHash)
if err != nil {
if s.attestationPolicy.Required {
s.logger.Error("release: attestation verification failed", "error", err, "version", release.Version)
writeJSON(w, http.StatusForbidden, errorResponse("attestation_error", fmt.Sprintf("build attestation verification failed: %v", err)))
return
}
s.logger.Warn("release: attestation verification error (non-required)", "error", err, "version", release.Version)
} else if result != nil {
if result.Verified {
s.logger.Info("release: build attestation verified",
"version", release.Version,
"actor", result.Actor,
)
} else if s.attestationPolicy.Required && len(result.Errors) > 0 {
s.logger.Error("release: attestation policy check failed",
"version", release.Version,
"errors", strings.Join(result.Errors, "; "),
)
writeJSON(w, http.StatusForbidden, errorResponse("attestation_error",
fmt.Sprintf("build attestation did not match policy: %s", strings.Join(result.Errors, "; "))))
return
} else if len(result.Warnings) > 0 {
s.logger.Warn("release: attestation warnings",
"version", release.Version,
"warnings", strings.Join(result.Warnings, "; "),
)
}
}
}
}

if err := s.store.SetRelease(&release); err != nil {
s.logger.Error("release: register failed", "error", err)
writeJSON(w, http.StatusInternalServerError, errorResponse("internal_error", "failed to save release"))
Expand Down
11 changes: 11 additions & 0 deletions coordinator/internal/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (

"github.com/eigeninference/coordinator/internal/auth"
"github.com/eigeninference/coordinator/internal/billing"
"github.com/eigeninference/coordinator/internal/buildattest"
"github.com/eigeninference/coordinator/internal/datadog"
"github.com/eigeninference/coordinator/internal/e2e"
"github.com/eigeninference/coordinator/internal/mdm"
Expand Down Expand Up @@ -131,6 +132,11 @@ type Server struct {
// It can only POST /v1/releases — no admin access.
releaseKey string

// attestationPolicy configures build provenance attestation verification
// for new releases. When enabled, the coordinator checks GitHub attestations
// before accepting a release registration.
attestationPolicy buildattest.Policy

// consoleURL is the frontend URL (e.g. "https://console.darkbloom.dev").
// Used for device auth verification_uri so the browser opens the console, not the coordinator.
consoleURL string
Expand Down Expand Up @@ -447,6 +453,11 @@ func (s *Server) SetReleaseKey(key string) {
s.releaseKey = key
}

// SetAttestationPolicy configures the build provenance attestation policy.
func (s *Server) SetAttestationPolicy(p buildattest.Policy) {
s.attestationPolicy = p
}

// SetCoordinatorKey installs the X25519 keypair the coordinator publishes
// for sender-to-coordinator request encryption. Pass nil to disable.
func (s *Server) SetCoordinatorKey(k *e2e.CoordinatorKey) {
Expand Down
81 changes: 81 additions & 0 deletions coordinator/internal/buildattest/github.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package buildattest

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)

const gitHubAPIBase = "https://api.github.com"

// AttestationResponse is the GitHub API response for listing attestations.
type AttestationResponse struct {
Attestations []AttestationEntry `json:"attestations"`
}

// AttestationEntry is a single attestation from the GitHub API.
type AttestationEntry struct {
Bundle json.RawMessage `json:"bundle"`
// Note: The GitHub API also returns repository_id in the attestation,
// but the bundle itself contains the cryptographically signed claims.
}

// httpClient is an interface for testing.
type httpClient interface {
Do(req *http.Request) (*http.Response, error)
}

var defaultClient httpClient = &http.Client{Timeout: 30 * time.Second}

// FetchAttestations retrieves attestations for a given artifact digest from GitHub.
// The digest should be a hex-encoded SHA-256 hash (without the "sha256:" prefix).
func FetchAttestations(ctx context.Context, owner, repo, digest, token string) (*AttestationResponse, error) {
return fetchAttestationsWithClient(ctx, defaultClient, gitHubAPIBase, owner, repo, digest, token)
}

func fetchAttestationsWithClient(ctx context.Context, client httpClient, baseURL, owner, repo, digest, token string) (*AttestationResponse, error) {
url := fmt.Sprintf("%s/repos/%s/%s/attestations/sha256:%s", baseURL, owner, repo, digest)

req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("creating request: %w", err)
}

req.Header.Set("Accept", "application/vnd.github+json")
req.Header.Set("X-GitHub-Api-Version", "2022-11-28")
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}

resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("calling GitHub API: %w", err)
}
defer resp.Body.Close()

body, err := io.ReadAll(io.LimitReader(resp.Body, 10<<20)) // 10 MB limit
if err != nil {
return nil, fmt.Errorf("reading response: %w", err)
}

switch resp.StatusCode {
case http.StatusOK:
// success
case http.StatusNotFound:
return &AttestationResponse{}, nil // no attestations
case http.StatusTooManyRequests:
return nil, fmt.Errorf("GitHub API rate limited (429)")
default:
return nil, fmt.Errorf("GitHub API returned %d: %s", resp.StatusCode, string(body))
}

var result AttestationResponse
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("decoding response: %w", err)
}

return &result, nil
}
79 changes: 79 additions & 0 deletions coordinator/internal/buildattest/policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package buildattest

import (
"os"
"strings"
)

// Policy defines the attestation verification policy.
type Policy struct {
// Required controls whether attestation verification is mandatory.
// When false, verification failures are logged as warnings but releases are accepted.
Required bool

// TrustedRepo is the expected source repository (e.g. "Layr-Labs/d-inference").
TrustedRepo string

// TrustedWorkflow is the expected workflow file path (e.g. ".github/workflows/release.yml").
TrustedWorkflow string

// TrustedActors is the list of GitHub usernames allowed to trigger releases.
TrustedActors []string

// TrustedTriggers is the list of allowed event triggers (e.g.
// ["push", "workflow_dispatch"]).
TrustedTriggers []string

// RequireGitHubHosted requires the build to run on GitHub-hosted runners.
RequireGitHubHosted bool

// GitHubToken is a read-only token for calling the GitHub attestations API.
GitHubToken string
}

// PolicyFromEnv loads attestation policy from environment variables.
func PolicyFromEnv() Policy {
p := Policy{
TrustedRepo: "Layr-Labs/d-inference",
TrustedWorkflow: ".github/workflows/release.yml",
TrustedTriggers: []string{"push", "workflow_dispatch"},
RequireGitHubHosted: true,
}

if v := os.Getenv("ATTESTATION_REQUIRED"); v == "true" || v == "1" {
p.Required = true
}

if v := os.Getenv("TRUSTED_REPO"); v != "" {
p.TrustedRepo = v
}

if v := os.Getenv("TRUSTED_WORKFLOW"); v != "" {
p.TrustedWorkflow = v
}

if v := os.Getenv("TRUSTED_ACTORS"); v != "" {
p.TrustedActors = strings.Split(v, ",")
for i := range p.TrustedActors {
p.TrustedActors[i] = strings.TrimSpace(p.TrustedActors[i])
}
}

if v := os.Getenv("TRUSTED_TRIGGERS"); v != "" {
p.TrustedTriggers = strings.Split(v, ",")
for i := range p.TrustedTriggers {
p.TrustedTriggers[i] = strings.TrimSpace(p.TrustedTriggers[i])
}
}

if v := os.Getenv("GITHUB_ATTESTATION_TOKEN"); v != "" {
p.GitHubToken = v
}

return p
}

// Enabled returns true if attestation verification can be performed.
func (p Policy) Enabled() bool {
return p.GitHubToken != ""
}
Loading
Loading