Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add draft for buildkit-gha build type #6

Merged
merged 2 commits into from
Nov 12, 2024
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
27 changes: 27 additions & 0 deletions buildtypes/buildkit-gha/v1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Build Type: BuildKit + GitHub Actions

This is a [SLSA Provenance](https://slsa.dev/provenance/v1)
`buildType` that describes builds for container images which combine the
use of BuildKit v1 and GitHub Actions workflows.

## Description

```jsonc
"buildType": "https://github.com/rancherlabs/slsactl/tree/main/buildtypes/buildkit-gha/v1"
```

## Build Definition

### Internal parameters

All internal parameters are REQUIRED.

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `trigger` | string | The GitHub Action event that caused the build to be executed. |
| `invocationUri` | string | Resource URI for the GitHub action workflow instance. |

### Resolved Dependencies

The resolved dependencies MUST include the source code URI and its `gitCommit`, optionally
a Git tag can be added as an annotation.
62 changes: 62 additions & 0 deletions buildtypes/buildkit-gha/v1/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"buildDefinition": {
"buildType": "https://mobyproject.org/buildkit@v1",
"externalParameters": {
"args": {
"build-arg:VERSION": "v1.3.0"
},
"frontend": "dockerfile.v0",
"locals": [
{
"name": "context"
},
{
"name": "dockerfile"
}
]
},
"internalParameters": {
"trigger": "push",
"invocationUri": "https://github.com/rancher/cis-operator/actions/runs/11725698930/attempts/1"
},
"resolvedDependencies": [
{
"uri": "pkg:docker/docker/buildkit-syft-scanner@stable-1",
"digest": {
"sha256": "176e0869c38aeaede37e594fcf182c91d44391a932e1d71e99ec204873445a33"
}
},
{
"uri": "pkg:docker/rancher/[email protected]?platform=linux%2Famd64",
"digest": {
"sha256": "9872511e4e59256b73cc3d5577c75c4434d883b64f5365cf2fc5c9d8a43323bd"
}
},
{
"uri": "pkg:docker/registry.suse.com/bci/[email protected]?platform=linux%2Famd64",
"digest": {
"sha256": "670d86a82103520f105a97320d3075e5b3d72ccff0b17467ac4277d909ef18cb"
}
},
{
"uri": "https://github.com/rancher/cis-operator",
"digest": {
"gitCommit": "1151c64ed3f84c4bb6ecb310bbe62f0b7a450aad"
},
"annotations": {
"ref": "refs/tags/v1.3.0"
}
}
]
},
"runDetails": {
"builder": {
"id": "https://github.com/rancherlabs/slsactl/tree/main/buildtypes/buildkit-gha/v1"
},
"metadata": {
"invocationID": "hxcpfkp0jwmhk8c1is2x2mi01",
"startedOn": "2024-11-07T15:15:01.764583687Z",
"finishedOn": "2024-11-07T15:15:02.984902536Z"
}
}
}
78 changes: 77 additions & 1 deletion cmd/provenance.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,27 @@ package cmd

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"os"
"strings"

"github.com/google/go-containerregistry/pkg/name"
"github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
v02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
v1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1"
"github.com/rancherlabs/slsactl/internal/provenance"
"github.com/sigstore/cosign/v2/pkg/cosign"
certificate "github.com/sigstore/sigstore-go/pkg/fulcio/certificate"
)

var (
// builderId defines the builder ID when the provenance has been modified.
builderId = "https://github.com/rancherlabs/slsactl/tree/main/buildtypes/buildkit-gha/v1"
// buildKitV1 holds the buildType supported for provenance enrichment.
buildKitV1 = "https://mobyproject.org/buildkit@v1"
)

func provenanceCmd(img, format, platform string) error {
Expand Down Expand Up @@ -46,7 +59,16 @@ func provenanceCmd(img, format, platform string) error {
case "slsav0.2":
err = print(os.Stdout, predicate)
case "slsav1":
provV1 := provenance.ConvertV02ToV1(*predicate)
if predicate.BuildType != buildKitV1 {
return fmt.Errorf("image builtType not supported: %q", predicate.BuildType)
}

override, err := cosignCertData(img)
if err != nil {
return err
}

provV1 := provenance.ConvertV02ToV1(*predicate, override)
err = print(os.Stdout, provV1)

default:
Expand All @@ -65,3 +87,57 @@ func print(w io.Writer, v interface{}) error {
_, err = fmt.Fprintln(w, string(outData))
return err
}

func cosignCertData(img string) (*v1.ProvenancePredicate, error) {
ref, err := name.ParseReference(img, name.StrictValidation)
if err != nil {
return nil, err
}

payloads, err := cosign.FetchSignaturesForReference(context.Background(), ref)
if err != nil {
return nil, fmt.Errorf("failed to fetch image signatures: %w", err)
}

if len(payloads) == 0 {
return nil, fmt.Errorf("no payloads found for image")
}

var inparams provenance.InternalParameters
var commitID, commitRef, repoURL string

for _, ext := range payloads[0].Cert.Extensions {
switch {
case ext.Id.Equal(certificate.OIDSourceRepositoryDigest):
certificate.ParseDERString(ext.Value, &commitID)
case ext.Id.Equal(certificate.OIDSourceRepositoryURI):
certificate.ParseDERString(ext.Value, &repoURL)
case ext.Id.Equal(certificate.OIDSourceRepositoryRef):
certificate.ParseDERString(ext.Value, &commitRef)
case ext.Id.Equal(certificate.OIDBuildTrigger):
certificate.ParseDERString(ext.Value, &inparams.Trigger)
case ext.Id.Equal(certificate.OIDRunInvocationURI):
certificate.ParseDERString(ext.Value, &inparams.InvocationUri)
}
}

override := &v1.ProvenancePredicate{}
override.BuildDefinition.InternalParameters = inparams
deps := []v1.ResourceDescriptor{
{
URI: repoURL,
Digest: common.DigestSet{"gitCommit": commitID},
},
}

if commitRef != "" {
deps[0].Annotations = map[string]interface{}{
"ref": commitRef,
}
}

override.BuildDefinition.ResolvedDependencies = deps
override.RunDetails.Builder.ID = builderId

return override, nil
}
22 changes: 21 additions & 1 deletion internal/provenance/provenance.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import (
v1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1"
)

type InternalParameters struct {
Platform string `json:"platform,omitempty"`
Trigger string `json:"trigger,omitempty"`
InvocationUri string `json:"invocationUri,omitempty"`
}

type BuildKitProvenance02 struct {
LinuxAmd64 *ArchProvenance `json:"linux/amd64,omitempty"`
LinuxArm64 *ArchProvenance `json:"linux/arm64,omitempty"`
Expand All @@ -23,7 +29,7 @@ type ArchProvenanceV1 struct {
SLSA v1.ProvenancePredicate `json:"SLSA,omitempty"`
}

func ConvertV02ToV1(v02Prov v02.ProvenancePredicate) v1.ProvenancePredicate {
func ConvertV02ToV1(v02Prov v02.ProvenancePredicate, override *v1.ProvenancePredicate) v1.ProvenancePredicate {
prov := v1.ProvenancePredicate{
BuildDefinition: v1.ProvenanceBuildDefinition{
BuildType: v02Prov.BuildType,
Expand Down Expand Up @@ -53,5 +59,19 @@ func ConvertV02ToV1(v02Prov v02.ProvenancePredicate) v1.ProvenancePredicate {

prov.BuildDefinition.ResolvedDependencies = deps

if override != nil {
if override.RunDetails.Builder.ID != "" {
prov.RunDetails.Builder.ID = override.RunDetails.Builder.ID
}
if len(override.BuildDefinition.ResolvedDependencies) > 0 {
prov.BuildDefinition.ResolvedDependencies =
append(prov.BuildDefinition.ResolvedDependencies,
override.BuildDefinition.ResolvedDependencies...)
}
if override.BuildDefinition.InternalParameters != nil {
prov.BuildDefinition.InternalParameters = override.BuildDefinition.InternalParameters
}
}

return prov
}
65 changes: 56 additions & 9 deletions internal/provenance/provenance_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package provenance
package provenance_test

import (
"encoding/json"
Expand All @@ -7,8 +7,10 @@ import (

_ "embed"

"github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
v02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
v1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1"
"github.com/rancherlabs/slsactl/internal/provenance"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand All @@ -21,8 +23,6 @@ func TestConvertV02ToV1(t *testing.T) {
err := json.Unmarshal(v02Data, &v02Prov)
require.NoError(t, err, "Failed to unmarshal v0.2 data")

v1Prov := ConvertV02ToV1(v02Prov)

started, _ := time.Parse(time.RFC3339Nano, "2024-07-11T14:49:18.126688014Z")
finished, _ := time.Parse(time.RFC3339Nano, "2024-07-11T14:51:00.499751748Z")

Expand Down Expand Up @@ -51,21 +51,68 @@ func TestConvertV02ToV1(t *testing.T) {
InternalParameters: map[string]interface{}{
"platform": "linux/amd64",
},
ResolvedDependencies: []v1.ResourceDescriptor{
{
URI: "pkg:docker/docker/buildkit-syft-scanner@stable-1",
Digest: common.DigestSet{
"sha256": "176e0869c38aeaede37e594fcf182c91d44391a932e1d71e99ec204873445a33",
},
},
{
URI: "pkg:docker/rancher/[email protected]?platform=linux%2Famd64",
Digest: common.DigestSet{
"sha256": "053f8e16c843695b7a23803fbfdd699a8b9c9fe863a613516e4911a6eba0a4cb",
},
},
{
URI: "pkg:docker/registry.suse.com/bci/[email protected]?platform=linux%2Famd64",
Digest: common.DigestSet{
"sha256": "8f926e98dd809e5fc5971e39df2d88a7dbe4158dcf2c379be658acc67b1beb29",
},
},
{
URI: "pkg:docker/registry.suse.com/bci/[email protected]?platform=linux%2Famd64",
Digest: common.DigestSet{
"sha256": "fdf2b123574c9b00ee19a7009c6b8b11c4e97f3dcb5e27c7ab49d23f8e722d21",
},
},
{
URI: "https://dl.k8s.io/release/v1.28.7/bin/linux/amd64/kubectl",
Digest: common.DigestSet{
"sha256": "aff42d3167685e4d8e86fda0ad9c6ce6ec6c047bc24d608041d54717a18192ba",
},
},
},
},
RunDetails: v1.ProvenanceRunDetails{
Builder: v1.Builder{
ID: "",
},
BuildMetadata: v1.BuildMetadata{
StartedOn: &started,
FinishedOn: &finished,
StartedOn: &started,
FinishedOn: &finished,
InvocationID: "ujss3xdtnmbv38uqh5wwftkpd",
},
Byproducts: []v1.ResourceDescriptor{},
},
}

assert.Equal(t, expectedV1Prov.BuildDefinition.BuildType, v1Prov.BuildDefinition.BuildType, "BuildType mismatch")
assert.Equal(t, expectedV1Prov.RunDetails.Builder.ID, v1Prov.RunDetails.Builder.ID, "Builder ID mismatch")
assert.Equal(t, expectedV1Prov.RunDetails.BuildMetadata.StartedOn, v1Prov.RunDetails.BuildMetadata.StartedOn, "BuildMetadata StartedOn mismatch")
assert.Equal(t, expectedV1Prov.RunDetails.BuildMetadata.FinishedOn, v1Prov.RunDetails.BuildMetadata.FinishedOn, "BuildMetadata FinishedOn mismatch")
v1Prov := provenance.ConvertV02ToV1(v02Prov, nil)
equal(t, expectedV1Prov, v1Prov)

override := &v1.ProvenancePredicate{}
override.RunDetails.Builder.ID = "new-build-id"
expectedV1Prov.RunDetails.Builder.ID = "new-build-id"

v1Prov = provenance.ConvertV02ToV1(v02Prov, override)
equal(t, expectedV1Prov, v1Prov)
}

func equal(t *testing.T, want, got v1.ProvenancePredicate) {
assert.Equal(t, want.BuildDefinition.BuildType, got.BuildDefinition.BuildType, "BuildType mismatch")
assert.Equal(t, want.RunDetails.Builder.ID, got.RunDetails.Builder.ID, "Builder ID mismatch")
assert.Equal(t, want.RunDetails.BuildMetadata.InvocationID, got.RunDetails.BuildMetadata.InvocationID, "BuildMetadata InvocationID mismatch")
assert.Equal(t, want.RunDetails.BuildMetadata.StartedOn, got.RunDetails.BuildMetadata.StartedOn, "BuildMetadata StartedOn mismatch")
assert.Equal(t, want.RunDetails.BuildMetadata.FinishedOn, got.RunDetails.BuildMetadata.FinishedOn, "BuildMetadata FinishedOn mismatch")
assert.Equal(t, want.BuildDefinition.ResolvedDependencies, got.BuildDefinition.ResolvedDependencies, "BuildDefinition ResolvedDependencies mismatch")
}
37 changes: 35 additions & 2 deletions internal/provenance/security-scan.v1
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,48 @@
},
"internalParameters": {
"platform": "linux/amd64"
}
},
"resolvedDependencies": [
{
"digest": {
"sha256": "176e0869c38aeaede37e594fcf182c91d44391a932e1d71e99ec204873445a33"
},
"uri": "pkg:docker/docker/buildkit-syft-scanner@stable-1"
},
{
"digest": {
"sha256": "053f8e16c843695b7a23803fbfdd699a8b9c9fe863a613516e4911a6eba0a4cb"
},
"uri": "pkg:docker/rancher/[email protected]?platform=linux%2Famd64"
},
{
"digest": {
"sha256": "8f926e98dd809e5fc5971e39df2d88a7dbe4158dcf2c379be658acc67b1beb29"
},
"uri": "pkg:docker/registry.suse.com/bci/[email protected]?platform=linux%2Famd64"
},
{
"digest": {
"sha256": "fdf2b123574c9b00ee19a7009c6b8b11c4e97f3dcb5e27c7ab49d23f8e722d21"
},
"uri": "pkg:docker/registry.suse.com/bci/[email protected]?platform=linux%2Famd64"
},
{
"digest": {
"sha256": "aff42d3167685e4d8e86fda0ad9c6ce6ec6c047bc24d608041d54717a18192ba"
},
"uri": "https://dl.k8s.io/release/v1.28.7/bin/linux/amd64/kubectl"
}
]
},
"runDetails": {
"builder": {
"id": ""
},
"metadata": {
"invocationID": "ujss3xdtnmbv38uqh5wwftkpd",
"startedOn": "2024-07-11T14:49:18.126688014Z",
"finishedOn": "2024-07-11T14:51:00.499751748Z"
}
}
}
}