diff --git a/cmd/sign_off.go b/cmd/sign_off.go new file mode 100644 index 000000000..19868d223 --- /dev/null +++ b/cmd/sign_off.go @@ -0,0 +1,119 @@ +// Copyright 2022 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "log" + + appstudioshared "github.com/redhat-appstudio/managed-gitops/appstudio-shared/apis/appstudio.redhat.com/v1alpha1" + "github.com/spf13/cobra" + + "github.com/hacbs-contract/ec-cli/internal/applicationsnapshot" + "github.com/hacbs-contract/ec-cli/internal/image" +) + +func signOffCmd() *cobra.Command { + var data = struct { + imageRef string + publicKey string + filePath string + input string + spec *appstudioshared.ApplicationSnapshotSpec + }{ + imageRef: "", + publicKey: "", + } + cmd := &cobra.Command{ + Use: "sign-off", + Short: "Capture signed off signatures from a source (github repo, Jira)", + Long: `Supported sign off sources are commits captured from a git repo and jira issues. + The git sources return a signed off value and the git commit. The jira issue is + a TODO, but will return the Jira issue with any sign off values.`, + + PreRunE: func(cmd *cobra.Command, args []string) error { + spec, err := applicationsnapshot.DetermineInputSpec(data.filePath, data.input, data.imageRef) + if err != nil { + return err + } + + data.spec = spec + + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + for _, comp := range data.spec.Components { + err := validate(cmd.Context(), comp.ContainerImage, data.publicKey) + if err != nil { + log.Println(err) + continue + } + } + return nil + }, + } + + cmd.Flags().StringVar(&data.publicKey, "public-key", "", "Public key") + cmd.Flags().StringVar(&data.imageRef, "image-ref", data.imageRef, "The OCI repo to fetch the attestation from.") + cmd.Flags().StringVarP(&data.filePath, "file-path", "f", data.filePath, "Path to ApplicationSnapshot JSON file") + cmd.Flags().StringVarP(&data.input, "json-input", "j", data.input, "ApplicationSnapshot JSON string") + + return cmd +} + +func validate(ctx context.Context, imageRef, publicKey string) error { + imageValidator, err := image.NewImageValidator(ctx, imageRef, publicKey, "") + if err != nil { + return err + } + + validatedImage, err := imageValidator.ValidateImage(ctx) + if err != nil { + return err + } + + for _, att := range validatedImage.Attestations { + signoffSource, err := att.NewSignOffSource() + if err != nil { + return err + } + if signoffSource == nil { + return errors.New("there is no signoff source in attestation") + } + + signOff, err := signoffSource.GetSignOff() + if err != nil { + return err + } + + if signOff != nil { + payload, err := json.Marshal(signOff) + if err != nil { + return err + } + fmt.Println(string(payload)) + } + } + return nil +} + +func init() { + rootCmd.AddCommand(signOffCmd()) +} diff --git a/cmd/validate_image.go b/cmd/validate_image.go index b11aea1b3..3e0d81dd7 100644 --- a/cmd/validate_image.go +++ b/cmd/validate_image.go @@ -18,7 +18,6 @@ package cmd import ( "context" - "encoding/json" "errors" "fmt" "io/ioutil" @@ -26,13 +25,10 @@ import ( "github.com/hashicorp/go-multierror" appstudioshared "github.com/redhat-appstudio/managed-gitops/appstudio-shared/apis/appstudio.redhat.com/v1alpha1" - log "github.com/sirupsen/logrus" - "github.com/spf13/afero" "github.com/spf13/cobra" "github.com/hacbs-contract/ec-cli/internal/applicationsnapshot" "github.com/hacbs-contract/ec-cli/internal/output" - "github.com/hacbs-contract/ec-cli/internal/utils" ) type imageValidationFunc func(ctx context.Context, imageRef, policyConfiguration, publicKey, rekorURL string) (*output.Output, error) @@ -72,7 +68,7 @@ instance in strict mode: ec validate image --file-path my-app.yaml --public-key my-key.pem --rekor-url https://rekor.example.org --strict`, PreRunE: func(cmd *cobra.Command, args []string) error { - s, err := determineInputSpec(data.filePath, data.input, data.imageRef) + s, err := applicationsnapshot.DetermineInputSpec(data.filePath, data.input, data.imageRef) if err != nil { return err } @@ -173,53 +169,3 @@ ec validate image --file-path my-app.yaml --public-key my-key.pem --rekor-url ht } return cmd } - -func determineInputSpec(filePath string, input string, imageRef string) (*appstudioshared.ApplicationSnapshotSpec, error) { - var appSnapshot appstudioshared.ApplicationSnapshotSpec - - // read ApplicationSnapshot provided as a file - if len(filePath) > 0 { - content, err := afero.ReadFile(utils.AppFS, filePath) - if err != nil { - log.Debugf("Problem reading application snapshot from file %s", filePath) - return nil, err - } - - err = json.Unmarshal(content, &appSnapshot) - if err != nil { - log.Debugf("Problem parsing application snapshot from file %s", filePath) - return nil, err - } - - log.Debugf("Read application snapshot from file %s", filePath) - return &appSnapshot, nil - } - - // read ApplicationSnapshot provided as a string - if len(input) > 0 { - // Unmarshall json into struct, exit on failure - if err := json.Unmarshal([]byte(input), &appSnapshot); err != nil { - log.Debugf("Problem parsing application snapshot from input param %s", input) - return nil, err - } - - log.Debug("Read application snapshot from input param") - return &appSnapshot, nil - } - - // create ApplicationSnapshot with a single image - if len(imageRef) > 0 { - log.Debugf("Generating application snapshot from imageRef %s", imageRef) - return &appstudioshared.ApplicationSnapshotSpec{ - Components: []appstudioshared.ApplicationSnapshotComponent{ - { - Name: "Unnamed", - ContainerImage: imageRef, - }, - }, - }, nil - } - - log.Debug("No application snapshot available") - return nil, errors.New("neither ApplicationSnapshot nor image reference provided to validate") -} diff --git a/cmd/validate_image_test.go b/cmd/validate_image_test.go index d604393fd..794536ae9 100644 --- a/cmd/validate_image_test.go +++ b/cmd/validate_image_test.go @@ -26,6 +26,7 @@ import ( appstudioshared "github.com/redhat-appstudio/managed-gitops/appstudio-shared/apis/appstudio.redhat.com/v1alpha1" "github.com/stretchr/testify/assert" + "github.com/hacbs-contract/ec-cli/internal/applicationsnapshot" "github.com/hacbs-contract/ec-cli/internal/output" ) @@ -136,7 +137,7 @@ func Test_determineInputSpec(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { - s, err := determineInputSpec(c.arguments.filePath, c.arguments.input, c.arguments.imageRef) + s, err := applicationsnapshot.DetermineInputSpec(c.arguments.filePath, c.arguments.input, c.arguments.imageRef) if c.err != "" { assert.EqualError(t, err, c.err) } diff --git a/internal/applicationsnapshot/input.go b/internal/applicationsnapshot/input.go new file mode 100644 index 000000000..074dd2725 --- /dev/null +++ b/internal/applicationsnapshot/input.go @@ -0,0 +1,61 @@ +package applicationsnapshot + +import ( + "encoding/json" + "errors" + + "github.com/hacbs-contract/ec-cli/internal/utils" + appstudioshared "github.com/redhat-appstudio/managed-gitops/appstudio-shared/apis/appstudio.redhat.com/v1alpha1" + log "github.com/sirupsen/logrus" + "github.com/spf13/afero" +) + +func DetermineInputSpec(filePath string, input string, imageRef string) (*appstudioshared.ApplicationSnapshotSpec, error) { + var appSnapshot appstudioshared.ApplicationSnapshotSpec + + // read ApplicationSnapshot provided as a file + if len(filePath) > 0 { + content, err := afero.ReadFile(utils.AppFS, filePath) + if err != nil { + log.Debugf("Problem reading application snapshot from file %s", filePath) + return nil, err + } + + err = json.Unmarshal(content, &appSnapshot) + if err != nil { + log.Debugf("Problem parsing application snapshot from file %s", filePath) + return nil, err + } + + log.Debugf("Read application snapshot from file %s", filePath) + return &appSnapshot, nil + } + + // read ApplicationSnapshot provided as a string + if len(input) > 0 { + // Unmarshall json into struct, exit on failure + if err := json.Unmarshal([]byte(input), &appSnapshot); err != nil { + log.Debugf("Problem parsing application snapshot from input param %s", input) + return nil, err + } + + log.Debug("Read application snapshot from input param") + return &appSnapshot, nil + } + + // create ApplicationSnapshot with a single image + if len(imageRef) > 0 { + log.Debugf("Generating application snapshot from imageRef %s", imageRef) + return &appstudioshared.ApplicationSnapshotSpec{ + Components: []appstudioshared.ApplicationSnapshotComponent{ + { + Name: "Unnamed", + ContainerImage: imageRef, + }, + }, + }, nil + } + + log.Debug("No application snapshot available") + return nil, errors.New("neither ApplicationSnapshot nor image reference provided to validate") +} diff --git a/internal/image/build.go b/internal/image/build.go new file mode 100644 index 000000000..33c84c5a8 --- /dev/null +++ b/internal/image/build.go @@ -0,0 +1,177 @@ +// Copyright 2022 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package image + +import ( + "fmt" + "net/mail" + "regexp" + "strings" + + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/storage/memory" +) + +var gitClone = git.Clone + +type invocation struct { + ConfigSource map[string]interface{} `json:"configSource"` + Parameters map[string]string `json:"parameters"` + Environment map[string]interface{} `json:"environment"` +} + +type materials struct { + Uri string `json:"sha"` + Digest map[string]string `json:"digest"` +} + +type predicate struct { + Invocation invocation `json:"invocation"` + BuildType string `json:"buildType"` + Metadata map[string]interface{} `json:"metadata"` + Builder map[string]interface{} `json:"builder"` + BuildConfig map[string]interface{} `json:"buildConfig"` + Materials []materials `json:"materials"` +} + +type attestation struct { + Predicate predicate `json:"predicate"` + PredicateType string `json:"predicateType"` + Subject []map[string]interface{} `json:"subject"` + Type string `json:"_type"` +} + +type commitSignOff struct { + source string + commitSha string +} + +// there can be multiple sign off sources (git commit, tag and jira issues) +type signOffSource interface { + GetSignOff() (*signOffSignature, error) +} + +type commit struct { + Sha string `json:"sha"` + Author string `json:"author"` + Date string `json:"date"` + Message string `json:"message"` +} + +type signOffSignature struct { + Body interface{} `json:"body"` + Signatures []string `json:"signatures"` +} + +// From an attestation, find the signOff source (commit, tag, jira) +func (a *attestation) NewSignOffSource() (signOffSource, error) { + // the signoff source can be determined by looking into the attestation. + // the attestation can have an env var or something that this can key off of + + commitSha := a.getBuildCommitSha() + repo := a.getBuildSCM() + if commitSha != "" && repo != "" { + return &commitSignOff{ + source: a.getBuildSCM(), + commitSha: commitSha, + }, nil + } + return nil, nil +} + +// get the last commit used for the component build +func (a *attestation) getBuildCommitSha() string { + //return "6c1f093c0c197add71579d392da8a79a984fcd62" + if len(a.Predicate.Materials) == 1 { + return a.Predicate.Materials[0].Digest["sha1"] + } + return "" +} + +// the git url used for the component build +func (a *attestation) getBuildSCM() string { + //return "https://github.com/joejstuart/ec-cli.git" + if len(a.Predicate.Materials) == 1 { + return a.Predicate.Materials[0].Uri + } + return "" +} + +// returns the signOff signature and body of the source +func (c *commitSignOff) GetSignOff() (*signOffSignature, error) { + commit, err := getCommitSource(c) + if err != nil { + return nil, err + } + + return &signOffSignature{ + Body: commit, + Signatures: captureCommitSignOff(commit.Message), + }, nil +} + +// get the build commit source for use in GetSignOff +func getCommitSource(data *commitSignOff) (*commit, error) { + repo, err := gitClone(memory.NewStorage(), nil, &git.CloneOptions{ + URL: data.source, + }) + + if err != nil { + return nil, err + } + + gitCommit, err := repo.CommitObject(plumbing.NewHash(data.commitSha)) + if err != nil { + return nil, err + } + + return &commit{ + Sha: gitCommit.Hash.String(), + Author: fmt.Sprintf("%s <%s>", gitCommit.Author.Name, gitCommit.Author.Email), + Date: gitCommit.Author.When.String(), + Message: gitCommit.Message, + }, nil + +} + +// parse a commit and capture signatures +func captureCommitSignOff(message string) []string { + var capturedSignatures []string + signatureHeader := "Signed-off-by:" + // loop over each line of the commit message looking for "Signed-off-by:" + for _, line := range strings.Split(message, "\n") { + regex := fmt.Sprintf("^%s", signatureHeader) + match, _ := regexp.MatchString(regex, line) + // if there's a match, split on "Signed-off-by:", then capture each signature after + if match { + results := strings.Split(line, signatureHeader) + signatures, err := mail.ParseAddressList(results[len(results)-1]) + if err != nil { + continue + } + for _, signature := range signatures { + capturedSignatures = append(capturedSignatures, signature.Address) + } + } + } + + if len(capturedSignatures) > 0 { + return capturedSignatures + } + return []string{} +} diff --git a/internal/image/build_test.go b/internal/image/build_test.go new file mode 100644 index 000000000..8ff04929f --- /dev/null +++ b/internal/image/build_test.go @@ -0,0 +1,179 @@ +// Copyright 2022 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package image + +import ( + "fmt" + "reflect" + "testing" + + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/storage" + "github.com/go-git/go-git/v5/storage/memory" + "github.com/stretchr/testify/assert" +) + +func paramsInput(input string) attestation { + params := materials{} + if input == "good-commit" { + params.Digest = map[string]string{ + "sha": "6c1f093c0c197add71579d392da8a79a984fcd62", + } + params.Uri = "https://github.com/joejstuart/ec-cli.git" + } else if input == "bad-commit" { + params.Uri = "" + } else if input == "bad-git" { + params.Uri = "" + } else if input == "good-git" { + params.Uri = "https://github.com/joejstuart/ec-cli.git" + params.Digest = map[string]string{ + "sha": "6c1f093c0c197add71579d392da8a79a984fcd62", + } + } + + materials := []materials{ + params, + } + + pred := predicate{ + Materials: materials, + } + att := attestation{ + Predicate: pred, + } + + return att +} + +func Test_AttestationSignoffSource_commit(t *testing.T) { + tests := []struct { + input attestation + want signOffSource + }{ + { + paramsInput("good-commit"), + &commitSignOff{ + source: "https://github.com/joejstuart/ec-cli.git", + commitSha: "6c1f093c0c197add71579d392da8a79a984fcd62", + }, + }, + { + paramsInput("bad-commit"), + nil, + }, + } + + for i, tc := range tests { + t.Run(fmt.Sprintf("AttestationSignoffSource=%d", i), func(t *testing.T) { + got, _ := tc.input.NewSignOffSource() + if !assert.ObjectsAreEqual(tc.want, got) { + t.Fatalf("got %v; want %v", got, tc.want) + } else { + t.Logf("Success !") + } + }) + } +} + +func Test_GetBuildCommitSha(t *testing.T) { + tests := []struct { + input attestation + want string + }{ + {paramsInput("good-commit"), "6c1f093c0c197add71579d392da8a79a984fcd62"}, + {paramsInput("bad-commit"), ""}, + } + + for i, tc := range tests { + t.Run(fmt.Sprintf("GetBuildCommitSha=%d", i), func(t *testing.T) { + got := tc.input.getBuildCommitSha() + if got != tc.want { + t.Fatalf("got %v; want %v", got, tc.want) + } else { + t.Logf("Success !") + } + }) + } +} + +func Test_GetBuildSCM(t *testing.T) { + tests := []struct { + input attestation + want string + }{ + {paramsInput("good-git"), "https://github.com/joejstuart/ec-cli.git"}, + {paramsInput("bad-git"), ""}, + } + + for i, tc := range tests { + t.Run(fmt.Sprintf("GetBuildSCM=%d", i), func(t *testing.T) { + got := tc.input.getBuildSCM() + if got != tc.want { + t.Fatalf("got %v; want %v", got, tc.want) + } else { + t.Logf("Success !") + } + }) + } +} + +func Test_GetSignOff(t *testing.T) { + tests := []struct { + input *commitSignOff + want *signOffSignature + }{ + { + &commitSignOff{ + source: "https://github.com/joejstuart/ec-cli.git", + commitSha: "6c1f093c0c197add71579d392da8a79a984fcd62", + }, + &signOffSignature{ + Body: commit{ + Sha: "6c1f093c0c197add71579d392da8a79a984fcd62", + Author: "ec RedHat ", + Date: "Wed July 6 5:28:33 2022 -0500", + }, + Signatures: []string{"jstuart@redhat.com"}, + }, + }, + } + + savedClone := gitClone + defer func() { gitClone = savedClone }() + + gitClone = func(s storage.Storer, worktree billy.Filesystem, o *git.CloneOptions) (*git.Repository, error) { + return &git.Repository{ + Storer: memory.NewStorage(), + }, nil + } + + for i, tc := range tests { + t.Run(fmt.Sprintf("GetSignoffSource=%d", i), func(t *testing.T) { + signOff, err := tc.input.GetSignOff() + if err != nil { + t.Logf("error: %v", err) + } else if reflect.TypeOf(signOff) != reflect.TypeOf(tc.want) { + t.Fatalf("got %v want %v", signOff, tc.want) + } else if signOff.Signatures[0] != tc.want.Signatures[0] { + t.Fatalf("got %v want %v", signOff.Signatures, tc.want.Signatures) + } else { + t.Logf("Success!") + } + }) + } +} diff --git a/internal/image/image.go b/internal/image/image.go new file mode 100644 index 000000000..d808975c9 --- /dev/null +++ b/internal/image/image.go @@ -0,0 +1,142 @@ +// Copyright 2022 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package image + +import ( + "context" + "encoding/json" + "errors" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/sigstore/cosign/cmd/cosign/cli/rekor" + "github.com/sigstore/cosign/pkg/cosign" + "github.com/sigstore/cosign/pkg/oci" + "github.com/sigstore/cosign/pkg/policy" + "github.com/sigstore/cosign/pkg/signature" +) + +type imageValidator struct { + reference name.Reference + checkOpts cosign.CheckOpts + attestations []oci.Signature +} + +type validatedImage struct { + Reference name.Reference + Attestations []attestation + Signatures []oci.Signature +} + +// NewImageValidator constructs a new imageValidator with the provided parameters +func NewImageValidator(ctx context.Context, image string, publicKey string, rekorURL string) (*imageValidator, error) { + ref, err := name.ParseReference(image) + if err != nil { + return nil, err + } + + verifier, err := signature.PublicKeyFromKeyRef(ctx, publicKey) + if err != nil { + return nil, err + } + + checkOpts := cosign.CheckOpts{} + checkOpts.SigVerifier = verifier + + if rekorURL != "" { + rekorClient, err := rekor.NewClient(rekorURL) + if err != nil { + return nil, err + } + + checkOpts.RekorClient = rekorClient + } + + return &imageValidator{ + reference: ref, + checkOpts: checkOpts, + }, nil +} + +func (i *imageValidator) ValidateImageSignature(ctx context.Context) error { + // TODO check what to do with _, _ + _, _, err := cosign.VerifyImageSignatures(ctx, i.reference, &i.checkOpts) + + return err +} + +func (i *imageValidator) ValidateAttestationSignature(ctx context.Context) error { + // TODO check what to do with _ + attestations, _, err := cosign.VerifyImageAttestations(ctx, i.reference, &i.checkOpts) + if err != nil { + return err + } + + i.attestations = attestations + + return nil +} + +func (i *imageValidator) ValidateImage(ctx context.Context) (*validatedImage, error) { + signatures, _, err := cosign.VerifyImageSignatures(ctx, i.reference, &i.checkOpts) + if err != nil { + return nil, err + } + + attestations, _, err := cosign.VerifyImageAttestations(ctx, i.reference, &i.checkOpts) + attStatements := make([]attestation, 0, len(attestations)) + for _, att := range attestations { + attStatement, err := SignatureToAttestation(ctx, att) + if err != nil { + return nil, err + } + attStatements = append(attStatements, attStatement) + + } + if err != nil { + return nil, err + } + + return &validatedImage{ + i.reference, + attStatements, + signatures, + }, nil + +} + +func SignatureToAttestation(ctx context.Context, signature oci.Signature) (attestation, error) { + var att attestation + payload, err := policy.AttestationToPayloadJSON(ctx, "slsaprovenance", signature) + if err != nil { + return attestation{}, err + } + + if len(payload) == 0 { + return attestation{}, errors.New("predicate (slsaprovenance) did not match the attestation.") + } + + err = json.Unmarshal(payload, &att) + if err != nil { + return attestation{}, err + } + + return att, nil +} + +func (i *imageValidator) Attestations() []oci.Signature { + return i.attestations +} diff --git a/internal/image/validate.go b/internal/image/validate.go index 8874312d8..fdda09207 100644 --- a/internal/image/validate.go +++ b/internal/image/validate.go @@ -18,6 +18,11 @@ package image import ( "context" + "encoding/json" + "errors" + "io/ioutil" + "os" + "path" conftestOutput "github.com/open-policy-agent/conftest/output" log "github.com/sirupsen/logrus" @@ -73,6 +78,27 @@ func ValidateImage(ctx context.Context, imageRef, policyConfiguration, publicKey return nil, err } + attStatements := make([]attestation, 0, len(a.Attestations())) + for _, att := range a.Attestations() { + attStatement, err := SignatureToAttestation(ctx, att) + if err != nil { + return nil, err + } + attStatements = append(attStatements, attStatement) + } + + inputDir, err := os.MkdirTemp("", "ec_input.*") + if err != nil { + log.Debug("Problem making temp dir!") + return nil, err + } + inputJSONPath := path.Join(inputDir, "commit.json") + signatures, err := writeCommitData(attStatements) + payloadJson, _ := json.Marshal(signatures) + _ = ioutil.WriteFile(inputJSONPath, payloadJson, 0644) + + inputs = append(inputs, inputJSONPath) + results, err := a.Evaluator.Evaluate(ctx, inputs) if err != nil { @@ -85,3 +111,27 @@ func ValidateImage(ctx context.Context, imageRef, policyConfiguration, publicKey return out, nil } + +func writeCommitData(attestations []attestation) ([]*signOffSignature, error) { + payloads := make([]*signOffSignature, 0, len(attestations)) + for _, att := range attestations { + signoffSource, err := att.NewSignOffSource() + if err != nil { + return nil, err + } + if signoffSource == nil { + return nil, errors.New("there is no signoff source in attestation") + } + + signOff, err := signoffSource.GetSignOff() + if err != nil { + return nil, err + } + + if signOff != nil { + payloads = append(payloads, signOff) + } + } + return payloads, nil + +}