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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
225 changes: 118 additions & 107 deletions app/cli/api/attestation/v1/crafting_state.pb.go

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions app/cli/api/attestation/v1/crafting_state.pb.validate.go

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

3 changes: 3 additions & 0 deletions app/cli/api/attestation/v1/crafting_state.proto
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ message Attestation {
map<string, string> env_vars = 6;
string runner_url = 7;
workflowcontract.v1.CraftingSchema.Runner.RunnerType runner_type = 8;

// SHA1 of the environment where the attestation was executed
string sha1_commit = 9;
}

// Intermediate information that will get stored in the system while the run is being executed
Expand Down
25 changes: 19 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ require (
go.uber.org/zap v1.25.0
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63
golang.org/x/oauth2 v0.11.0
golang.org/x/term v0.11.0
golang.org/x/term v0.12.0
google.golang.org/api v0.138.0
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/grpc v1.57.0
Expand All @@ -83,26 +83,38 @@ require (
dario.cat/mergo v1.0.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
github.com/acomagu/bufpipe v1.0.4 // indirect
github.com/anchore/go-struct-converter v0.0.0-20230627203149-c72ef8859ca9 // indirect
github.com/cockroachdb/apd/v3 v3.2.0 // indirect
github.com/cpuguy83/dockercfg v0.3.1 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/pprof v0.0.0-20221103000818-d260c55eee4c // indirect
github.com/google/renameio/v2 v2.0.0 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/package-url/packageurl-go v0.1.1 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/xattr v0.4.9 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/skeema/knownhosts v1.2.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
go.opentelemetry.io/otel/metric v1.17.0 // indirect
gopkg.in/go-jose/go-jose.v2 v2.6.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)

require (
Expand Down Expand Up @@ -146,6 +158,7 @@ require (
github.com/fsouza/fake-gcs-server v1.45.2
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/go-chi/chi v4.1.2+incompatible // indirect
github.com/go-git/go-git/v5 v5.9.0
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
github.com/go-kratos/aegis v0.2.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect
Expand Down Expand Up @@ -254,14 +267,14 @@ require (
go.opentelemetry.io/otel v1.17.0 // indirect
go.opentelemetry.io/otel/trace v1.17.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/crypto v0.13.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/net v0.15.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect
golang.org/x/tools v0.13.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
Expand Down
69 changes: 53 additions & 16 deletions go.sum

Large diffs are not rendered by default.

66 changes: 60 additions & 6 deletions internal/attestation/crafter/crafter.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import (
schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
"github.com/chainloop-dev/chainloop/internal/attestation/crafter/materials"
"github.com/chainloop-dev/chainloop/internal/casclient"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/rs/zerolog"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/timestamppb"
Expand All @@ -42,6 +44,7 @@ type Crafter struct {
statePath string
CraftingState *api.CraftingState
Runner supportedRunner
workingDir string
}

var ErrAttestationStateNotLoaded = errors.New("crafting state not loaded")
Expand All @@ -61,14 +64,22 @@ func WithLogger(l *zerolog.Logger) NewOpt {
}
}

func WithWorkingDirPath(path string) NewOpt {
return func(c *Crafter) {
c.workingDir = path
}
}

// Create a completely new crafter
func NewCrafter(opts ...NewOpt) *Crafter {
noopLogger := zerolog.Nop()
defaultStatePath := filepath.Join(os.TempDir(), "chainloop_attestation.tmp.json")

cw, _ := os.Getwd()
c := &Crafter{
logger: &noopLogger,
statePath: defaultStatePath,
logger: &noopLogger,
statePath: defaultStatePath,
workingDir: cw,
}

for _, opt := range opts {
Expand Down Expand Up @@ -175,9 +186,13 @@ func LoadSchema(pathOrURI string) (*schemaapi.CraftingSchema, error) {
// Initialize the temporary file with the content of the schema
func (c *Crafter) initCraftingStateFile(schema *schemaapi.CraftingSchema, wf *api.WorkflowMetadata, dryRun bool, runnerType schemaapi.CraftingSchema_Runner_RunnerType, jobURL string) error {
// Generate Crafting state
state := initialCraftingState(schema, wf, dryRun, runnerType, jobURL)
state, err := initialCraftingState(c.workingDir, schema, wf, dryRun, runnerType, jobURL)
if err != nil {
return fmt.Errorf("initializing crafting state: %w", err)
}

if err := persistCraftingState(state, c.statePath); err != nil {
return err
return fmt.Errorf("failed to persist crafting state: %w", err)
}

c.logger.Debug().Str("path", c.statePath).Msg("created state file")
Expand Down Expand Up @@ -222,7 +237,45 @@ func (c *Crafter) LoadCraftingState() error {
return nil
}

func initialCraftingState(schema *schemaapi.CraftingSchema, wf *api.WorkflowMetadata, dryRun bool, runnerType schemaapi.CraftingSchema_Runner_RunnerType, jobURL string) *api.CraftingState {
// Returns the current directory git commit hash if possible
// If we are not in a git repo it will return an empty string
func gracefulGitRepoHead(path string) (string, error) {
repo, err := git.PlainOpenWithOptions(path, &git.PlainOpenOptions{
// walk up the directory tree until we find a git repo
DetectDotGit: true,
})

if err != nil {
if errors.Is(err, git.ErrRepositoryNotExists) {
return "", nil
}

return "", fmt.Errorf("opening repository: %w", err)
}

head, err := repo.Head()
if err != nil {
if errors.Is(err, plumbing.ErrReferenceNotFound) {
return "", nil
}
return "", fmt.Errorf("finding repo head: %w", err)
}

commit, err := repo.CommitObject(head.Hash())
if err != nil {
return "", fmt.Errorf("finding head commit: %w", err)
}

return commit.Hash.String(), nil
}

func initialCraftingState(cwd string, schema *schemaapi.CraftingSchema, wf *api.WorkflowMetadata, dryRun bool, runnerType schemaapi.CraftingSchema_Runner_RunnerType, jobURL string) (*api.CraftingState, error) {
// Get git commit hash
commitHash, err := gracefulGitRepoHead(cwd)
if err != nil {
return nil, fmt.Errorf("getting git commit hash: %w", err)
}

// Generate Crafting state
return &api.CraftingState{
InputSchema: schema,
Expand All @@ -231,9 +284,10 @@ func initialCraftingState(schema *schemaapi.CraftingSchema, wf *api.WorkflowMeta
Workflow: wf,
RunnerType: runnerType,
RunnerUrl: jobURL,
Sha1Commit: commitHash,
},
DryRun: dryRun,
}
}, nil
}

func persistCraftingState(craftState *api.CraftingState, stateFilePath string) error {
Expand Down
63 changes: 58 additions & 5 deletions internal/attestation/crafter/crafter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@ package crafter_test
import (
"fmt"
"os"
"path/filepath"
"testing"
"time"

v1 "github.com/chainloop-dev/chainloop/app/cli/api/attestation/v1"
schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
"github.com/chainloop-dev/chainloop/internal/attestation/crafter"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"

Expand All @@ -33,21 +37,34 @@ type crafterSuite struct {
suite.Suite
// initOpts
workflowMetadata *v1.WorkflowMetadata
repoPath string
repoHead string
}

func (s *crafterSuite) TestInit() {
testCases := []struct {
name string
contractPath string
workingDir string
workflowMetadata *v1.WorkflowMetadata
wantErr bool
wantRepoDigest bool
dryRun bool
}{
{
name: "happy path",
name: "happy path inside a git repo",
contractPath: "testdata/contracts/empty_generic.yaml",
workflowMetadata: s.workflowMetadata,
dryRun: true,
workingDir: s.repoPath,
wantRepoDigest: true,
},
{
name: "happy path outside a git repo",
contractPath: "testdata/contracts/empty_generic.yaml",
workflowMetadata: s.workflowMetadata,
workingDir: s.T().TempDir(),
dryRun: true,
},
{
name: "missing metadata",
Expand All @@ -67,11 +84,13 @@ func (s *crafterSuite) TestInit() {
workflowMetadata: s.workflowMetadata,
wantErr: false,
dryRun: true,
workingDir: s.T().TempDir(),
},
{
name: "with annotations",
contractPath: "testdata/contracts/with_material_annotations.yaml",
workflowMetadata: s.workflowMetadata,
workingDir: s.T().TempDir(),
dryRun: true,
},
}
Expand All @@ -83,7 +102,7 @@ func (s *crafterSuite) TestInit() {

// Make sure that the tests context indicate that we are not in a CI
// this makes the github action runner context to fail
c, err := newInitializedCrafter(s.T(), tc.contractPath, tc.workflowMetadata, tc.dryRun)
c, err := newInitializedCrafter(s.T(), tc.contractPath, tc.workflowMetadata, tc.dryRun, tc.workingDir)
if tc.wantErr {
s.Error(err)
return
Expand All @@ -100,6 +119,10 @@ func (s *crafterSuite) TestInit() {
DryRun: tc.dryRun,
}

if tc.wantRepoDigest {
want.Attestation.Sha1Commit = s.repoHead
}

// reset to nil to easily compare them
s.NotNil(c.CraftingState.Attestation.InitializedAt)
c.CraftingState.Attestation.InitializedAt = nil
Expand All @@ -122,14 +145,19 @@ type testingCrafter struct {
statePath string
}

func newInitializedCrafter(t *testing.T, contractPath string, wfMeta *v1.WorkflowMetadata, dryRun bool) (*testingCrafter, error) {
func newInitializedCrafter(t *testing.T, contractPath string, wfMeta *v1.WorkflowMetadata, dryRun bool, workingDir string) (*testingCrafter, error) {
contract, err := crafter.LoadSchema(contractPath)
if err != nil {
return nil, err
}

statePath := fmt.Sprintf("%s/attestation.json", t.TempDir())
c := crafter.NewCrafter(crafter.WithStatePath(statePath))
opts := []crafter.NewOpt{crafter.WithStatePath(statePath)}
if workingDir != "" {
opts = append(opts, crafter.WithWorkingDirPath(workingDir))
}

c := crafter.NewCrafter(opts...)
if err = c.Init(&crafter.InitOpts{SchemaV1: contract, WfInfo: wfMeta, DryRun: dryRun}); err != nil {
return nil, err
}
Expand Down Expand Up @@ -313,7 +341,7 @@ func (s *crafterSuite) TestResolveEnvVars() {
}
}

c, err := newInitializedCrafter(s.T(), "testdata/contracts/with_env_vars.yaml", &v1.WorkflowMetadata{}, true)
c, err := newInitializedCrafter(s.T(), "testdata/contracts/with_env_vars.yaml", &v1.WorkflowMetadata{}, true, "")
require.NoError(s.T(), err)

err = c.ResolveEnvVars(tc.strict)
Expand Down Expand Up @@ -371,6 +399,31 @@ func (s *crafterSuite) SetupTest() {
}

s.T().Setenv("CI", "")

s.repoPath = s.T().TempDir()
repo, err := git.PlainInit(s.repoPath, false)
require.NoError(s.T(), err)
wt, err := repo.Worktree()
require.NoError(s.T(), err)

filename := filepath.Join(s.repoPath, "example-git-file")
if err = os.WriteFile(filename, []byte("hello world!"), 0600); err != nil {
require.NoError(s.T(), err)
}

_, err = wt.Add("example-git-file")
require.NoError(s.T(), err)

h, err := wt.Commit("test commit", &git.CommitOptions{
Author: &object.Signature{
Name: "John Doe",
Email: "john@doe.org",
When: time.Now(),
},
})
require.NoError(s.T(), err)

s.repoHead = h.String()
}

func TestSuite(t *testing.T) {
Expand Down
Loading