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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 7 additions & 7 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.24"
go-version: "1.25"

- name: Install tkn CLI
run: |
Expand Down Expand Up @@ -249,7 +249,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.24"
go-version: "1.25"

- name: Install operator-sdk
run: |
Expand Down Expand Up @@ -304,7 +304,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.24"
go-version: "1.25"

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
Expand Down Expand Up @@ -342,7 +342,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.24"
go-version: "1.25"

- name: Build CLI for ARM64
run: |
Expand All @@ -368,7 +368,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.24"
go-version: "1.25"

- name: Build CLI for AMD64
run: |
Expand All @@ -394,7 +394,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.24"
go-version: "1.25"

- name: Build CLI for darwin/arm64
run: |
Expand Down Expand Up @@ -424,7 +424,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.24"
go-version: "1.25"

- name: Download all CLI artifacts
uses: actions/download-artifact@v8
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:
workflow_dispatch:

env:
GO_VERSION: '1.24'
GO_VERSION: '1.25'
KIND_VERSION: v0.24.0
KUBECTL_VERSION: v1.31.0
TEKTON_VERSION: v1.9.1
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:
workflow_dispatch:

env:
GO_VERSION: '1.24'
GO_VERSION: '1.25'

jobs:
lint:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.24"
go-version: "1.25"

- name: Login to Quay.io
uses: docker/login-action@v4
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ pull-secret*
*~

catalog/automotive-dev-operator.yaml
bundle/
/bundle/
_output/
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ARG BUILDPLATFORM
FROM --platform=$BUILDPLATFORM registry.access.redhat.com/ubi9/go-toolset:1.24.6 AS builder
FROM --platform=$BUILDPLATFORM registry.access.redhat.com/ubi9/go-toolset:1.25.9 AS builder
ARG TARGETOS=linux
ARG TARGETARCH

Expand Down
13 changes: 13 additions & 0 deletions api/v1alpha1/operatorconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,7 @@ type OperatorConfigSpec struct {
}

// OSBuildsConfig defines configuration for OS build operations
// +kubebuilder:validation:XValidation:rule="!has(self.taskBundleVerify) || !self.taskBundleVerify || has(self.taskBundleCosignKeyRef)",message="taskBundleCosignKeyRef is required when taskBundleVerify is true"
type OSBuildsConfig struct {
// Enabled determines if Tekton tasks for OS builds should be deployed
// +kubebuilder:default=true
Expand Down Expand Up @@ -636,6 +637,18 @@ type OSBuildsConfig struct {
// +optional
TaskBundleRef string `json:"taskBundleRef,omitempty"`

// TaskBundleVerify enables cosign signature verification for Tekton Bundles.
// When true, all bundle refs (from OperatorConfig or user-provided --task-bundle-ref)
// must be signed with the key in TaskBundleCosignKeyRef.
// +optional
TaskBundleVerify bool `json:"taskBundleVerify,omitempty"`

// TaskBundleCosignKeyRef references a ConfigMap key containing the cosign public key (PEM-encoded)
// used to verify Tekton Bundle signatures.
// Required when TaskBundleVerify is true.
// +optional
TaskBundleCosignKeyRef *corev1.ConfigMapKeySelector `json:"taskBundleCosignKeyRef,omitempty"`

// DefaultBuildTTL is the default time-to-live for builds (all phases, including in-progress).
// Builds are automatically deleted after this duration. Uses Go duration format (e.g. "24h", "72h").
// Set to "0" to disable expiry by default.
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

114 changes: 111 additions & 3 deletions cmd/caib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,10 @@ caib image build <manifest.aib.yml> [flags]
| `--flash-cmd` | | Override flash command (default: from OperatorConfig target mapping) |
| `--lease-duration` | `03:00:00` | Device lease duration for flash (HH:MM:SS) |
| `--lease` | | Existing Jumpstarter lease name (mutually exclusive with `--lease-duration`) |
| `--secure` | `false` | Resolve tasks from signed Tekton Bundle |
| `--secure` | `false` | Resolve tasks from signed Tekton Bundle (requires OperatorConfig `taskBundleRef`) |
| `--reproducible` | `false` | Save RPMs, manifest, and task bundle as OCI referrers for future reproduction (requires `--secure`) |
| `--task-bundle-ref` | | Digest-pinned Tekton bundle ref for reproducible rebuild (e.g. `quay.io/org/tasks@sha256:abc...`) |
| `--restore-sources` | | OCI image ref from prior build — restores archived sources for exact reproducible rebuild |
| `--ttl` | | Time-to-live for the build (e.g. `24h`, `72h`; empty=server default, `0`=no expiry) |

**Examples:**
Expand Down Expand Up @@ -218,12 +221,15 @@ caib image disk <container-ref> [flags]
| `--flash-cmd` | | Override flash command (default: from OperatorConfig target mapping) |
| `--lease-duration` | `03:00:00` | Device lease duration for flash (HH:MM:SS) |
| `--lease` | | Existing Jumpstarter lease name (mutually exclusive with `--lease-duration`) |
| `--secure` | `false` | Resolve tasks from signed Tekton Bundle |
| `--secure` | `false` | Resolve tasks from signed Tekton Bundle (requires OperatorConfig `taskBundleRef`) |
| `--task-bundle-ref` | | Digest-pinned Tekton bundle ref for reproducible rebuild (e.g. `quay.io/org/tasks@sha256:abc...`) |
Comment thread
coderabbitai[bot] marked this conversation as resolved.
| `--ttl` | | Time-to-live for the build (e.g. `24h`, `72h`) |
| `--internal-registry` | `false` | Push to OpenShift internal registry |
| `--image-name` | (build name) | Override image name in internal registry |
| `--image-tag` | `disk` | Override tag in internal registry |

> **Note:** `--reproducible` and `--restore-sources` are not supported by `image disk`. This command creates a disk image from an existing container rather than performing a full build, so source archival and reproducibility tracking do not apply. Use `image build` or `image build-dev` for reproducible builds.

**Examples:**

```bash
Expand Down Expand Up @@ -279,7 +285,10 @@ caib image build-dev <manifest.aib.yml> [flags]
| `--flash-cmd` | | Override flash command (default: from OperatorConfig target mapping) |
| `--lease-duration` | `03:00:00` | Device lease duration for flash (HH:MM:SS) |
| `--lease` | | Existing Jumpstarter lease name (mutually exclusive with `--lease-duration`) |
| `--secure` | `false` | Resolve tasks from signed Tekton Bundle |
| `--secure` | `false` | Resolve tasks from signed Tekton Bundle (requires OperatorConfig `taskBundleRef`) |
| `--reproducible` | `false` | Save RPMs, manifest, and task bundle as OCI referrers for future reproduction (requires `--secure`) |
| `--task-bundle-ref` | | Digest-pinned Tekton bundle ref for reproducible rebuild (e.g. `quay.io/org/tasks@sha256:abc...`) |
| `--restore-sources` | | OCI image ref from prior build — restores archived sources for exact reproducible rebuild |
| `--ttl` | | Time-to-live for the build (e.g. `24h`, `72h`) |
| `--internal-registry` | `false` | Push to OpenShift internal registry |
| `--image-name` | (build name) | Override image name in internal registry |
Expand Down Expand Up @@ -479,6 +488,40 @@ caib image logs <build-name> [flags]
| `--server` | `$CAIB_SERVER` | Build API server URL |
| `--token` | `$CAIB_TOKEN` | Bearer token |

### image inspect

Show build provenance and reproducibility info for an OCI artifact. Reads manifest annotations and OCI referrers to display the exact build parameters and a command to reproduce the build.

```bash
caib image inspect <oci-registry-reference> [flags]
```

| Flag | Default | Description |
|------|---------|-------------|
| `--registry-auth-file` | | Path to Docker/Podman auth file for registry authentication |
| `-o`, `--output-dir` | | Download referrer artifacts (manifest, RPMs, osbuild manifest) to this directory |

Discovered referrer types:

| Artifact Type | Description |
|---------------|-------------|
| `application/vnd.automotive.manifest.v1+yaml` | Original AIB manifest used for the build |
| `application/vnd.automotive.sources.v1+tar+gzip` | Archived RPMs and build inputs |
| `application/vnd.osbuild.manifest.v1+json` | Resolved osbuild manifest |

**Examples:**

```bash
# Show build provenance
caib image inspect quay.io/org/my-os:v1

# Show provenance and download artifacts for reproduction
caib image inspect quay.io/org/my-os:v1 -o ./rebuild/

# Inspect with explicit auth file
caib image inspect quay.io/org/my-os:v1 --registry-auth-file ~/.config/containers/auth.json
```

### image token

Request a fresh, short-lived registry token (valid ~4 hours) for a completed build that used `--internal-registry`. Can be used with podman, skopeo, or any OCI-compatible tool.
Expand Down Expand Up @@ -527,6 +570,71 @@ caib image cancel <build-name> [flags]
| Use case | OTA-updatable systems | Development/standalone disk images |
| Mode | Always `bootc` | `image` or `package` |

## Secure & Reproducible Builds

The `--secure` and `--reproducible` flags enable supply-chain security and build reproducibility.

### Secure builds (`--secure`)

When `--secure` is set, the build resolves Tekton tasks from a digest-pinned, cosign-signed bundle instead of using the operator's default tasks. This ensures the build pipeline itself is verified and tamper-proof.

**Requirements:**
- OperatorConfig must have `osBuilds.taskBundleRef` set to a digest-pinned bundle (e.g. `quay.io/org/tasks@sha256:...`)
- If `osBuilds.taskBundleVerify` is `true`, a cosign public key must be configured via `osBuilds.taskBundleCosignKeyRef` (a ConfigMap key reference)

**OperatorConfig setup:**

```yaml
apiVersion: automotive.sdv.cloud.redhat.com/v1alpha1
kind: OperatorConfig
metadata:
name: config
spec:
osBuilds:
taskBundleRef: "quay.io/centos-automotive-suite/automotive-dev-operator-bundle@sha256:..."
taskBundleVerify: true
taskBundleCosignKeyRef:
name: cosign-public-key # ConfigMap name
key: cosign.pub # Key within the ConfigMap
```

Create the ConfigMap with your cosign public key:

```bash
kubectl create configmap cosign-public-key \
--from-file=cosign.pub=hack/cosign.pub
```

### Reproducible builds (`--reproducible`)

When `--reproducible` is set (requires `--secure`), the build archives its inputs as OCI referrer artifacts alongside the output image:

- **AIB manifest** — the exact manifest used
- **Build sources** — RPMs and other inputs (tar.gz)
- **osbuild manifest** — the resolved osbuild pipeline definition

These artifacts enable exact rebuild reproduction. Use `caib image inspect` to view them and get a rebuild command.

### Rebuilding from a previous build

```bash
# 1. Download the manifest and metadata for local inspection
caib image inspect quay.io/org/my-os:v1 -o ./rebuild/

# 2. Rebuild using the downloaded manifest; --restore-sources tells the build
# to fetch archived RPMs/inputs from the OCI registry at build time
caib image build ./rebuild/manifest.aib.yml \
--secure \
--reproducible \
--task-bundle-ref quay.io/org/tasks@sha256:abc... \
--restore-sources quay.io/org/my-os:v1@sha256:def... \
--push quay.io/org/my-os:v2
```

Key flags for reproduction:
- `--task-bundle-ref` pins the exact Tekton bundle used in the original build
- `--restore-sources` tells the build to fetch archived RPMs and inputs from the original build's OCI referrers at build time (the build pod pulls from the registry, not from your local download)

## Authentication

The CLI automatically detects authentication in this order:
Expand Down
21 changes: 16 additions & 5 deletions cmd/caib/inspectcmd/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,12 +238,23 @@ func discoverReferrers(ociRef, digest string, sysCtx *types.SystemContext) ([]re
Username: sysCtx.DockerAuthConfig.Username,
Password: sysCtx.DockerAuthConfig.Password,
})
} else if sysCtx.AuthFilePath != "" {
store, err := credentials.NewFileStore(sysCtx.AuthFilePath)
if err != nil {
return nil, fmt.Errorf("load auth file %s: %w", sysCtx.AuthFilePath, err)
} else {
authFilePath := sysCtx.AuthFilePath
if authFilePath == "" {
for _, candidate := range registryauth.FileCandidates() {
if _, err := os.Stat(candidate); err == nil {
authFilePath = candidate
break
}
}
}
if authFilePath != "" {
store, err := credentials.NewFileStore(authFilePath)
if err != nil {
return nil, fmt.Errorf("load auth file %s: %w", authFilePath, err)
}
authClient.Credential = credentials.Credential(store)
}
authClient.Credential = credentials.Credential(store)
}
if sysCtx.DockerInsecureSkipTLSVerify == types.OptionalBoolTrue {
authClient.Client = &http.Client{
Expand Down
5 changes: 3 additions & 2 deletions cmd/caib/registryauth/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ func authFileHasAnyCredentials(content []byte) (bool, error) {
return false, nil
}

func registryAuthFileCandidates() []string {
// FileCandidates returns candidate auth file paths in priority order.
func FileCandidates() []string {
candidates := make([]string, 0, 4)
seen := map[string]struct{}{}
add := func(path string) {
Expand Down Expand Up @@ -138,7 +139,7 @@ func LoadAuthFileForRegistry(
}

var errs []string
for _, candidate := range registryAuthFileCandidates() {
for _, candidate := range FileCandidates() {
content, err := os.ReadFile(candidate)
if err != nil {
if !os.IsNotExist(err) && !os.IsPermission(err) {
Expand Down
Loading
Loading