Skip to content

Commit

Permalink
Add draft for buildkit-gha build type
Browse files Browse the repository at this point in the history
Signed-off-by: Paulo Gomes <[email protected]>
  • Loading branch information
pjbgf committed Nov 12, 2024
1 parent 09d6708 commit 96865a2
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 10 deletions.
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
}
26 changes: 18 additions & 8 deletions internal/provenance/provenance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ func TestConvertV02ToV1(t *testing.T) {
err := json.Unmarshal(v02Data, &v02Prov)
require.NoError(t, err, "Failed to unmarshal v0.2 data")

v1Prov := provenance.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 @@ -99,10 +97,22 @@ func TestConvertV02ToV1(t *testing.T) {
},
}

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.InvocationID, v1Prov.RunDetails.BuildMetadata.InvocationID, "BuildMetadata InvocationID 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")
assert.Equal(t, expectedV1Prov.BuildDefinition.ResolvedDependencies, v1Prov.BuildDefinition.ResolvedDependencies, "BuildDefinition ResolvedDependencies 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")
}

0 comments on commit 96865a2

Please sign in to comment.