diff --git a/go.mod b/go.mod index 169822e..c7fe612 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/minio/pkg v1.1.14 github.com/open-policy-agent/frameworks/constraint v0.0.0-20211029184625-8b4a99a9a65a github.com/sigstore/cosign v1.4.1 + github.com/sigstore/sigstore v1.0.2-0.20211203233310-c8e7f70eab4e gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ) @@ -149,7 +150,6 @@ require ( github.com/shibumi/go-pathspec v1.2.0 // indirect github.com/sigstore/fulcio v0.1.2-0.20211207184413-f4746cc4ff3d // indirect github.com/sigstore/rekor v0.3.1-0.20211203233407-3278f72b78bd // indirect - github.com/sigstore/sigstore v1.0.2-0.20211203233310-c8e7f70eab4e // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/spf13/afero v1.6.0 // indirect diff --git a/provider.go b/provider.go index cb5863b..4cc3c3d 100644 --- a/provider.go +++ b/provider.go @@ -16,13 +16,17 @@ package main import ( "context" + "encoding/base64" "encoding/json" "flag" "fmt" "io/ioutil" "net/http" + "os" + "strings" "github.com/google/go-containerregistry/pkg/name" + "github.com/in-toto/in-toto-golang/in_toto" "github.com/open-policy-agent/frameworks/constraint/pkg/externaldata" "github.com/minio/pkg/wildcard" @@ -33,6 +37,7 @@ import ( "github.com/sigstore/cosign/pkg/cosign/pkcs11key" "github.com/sigstore/cosign/pkg/oci" sigs "github.com/sigstore/cosign/pkg/signature" + "github.com/sigstore/sigstore/pkg/signature/payload" ) const ( @@ -105,8 +110,8 @@ func validate(cfg *Config) func(w http.ResponseWriter, req *http.Request) { } type checkedMetadata struct { - ImageSignatures []oci.Signature `json:"imageSignatures"` - AttestationSignatures []oci.Signature `json:"attestationSignatures"` + ImageSignatures []payload.SimpleContainerImage `json:"imageSignatures"` + AttestationSignatures []in_toto.Statement `json:"attestationSignatures"` } func verifyImageSignatures(ctx context.Context, key string, verifiers []Verifier) (*checkedMetadata, error) { @@ -148,7 +153,7 @@ func verifyImageSignatures(ctx context.Context, key string, verifiers []Verifier return nil, fmt.Errorf("parseReference: %v", err) } - var metadata *checkedMetadata + metadata := &checkedMetadata{} checkedSignatures, bundleVerified, err := cosign.VerifyImageSignatures(ctx, ref, co) if err != nil { @@ -163,7 +168,10 @@ func verifyImageSignatures(ctx context.Context, key string, verifiers []Verifier return nil, fmt.Errorf("no valid signatures found for %s", key) } - metadata.ImageSignatures = checkedSignatures + metadata.ImageSignatures, err = formatSignaturePayloads(checkedSignatures) + if err != nil { + return nil, fmt.Errorf("formatSignaturePayloads: %v", err) + } fmt.Println("signature verified for: ", key) fmt.Printf("%d number of valid signatures found for %s, found signatures: %v\n", len(checkedSignatures), key, checkedSignatures) @@ -179,7 +187,12 @@ func verifyImageSignatures(ctx context.Context, key string, verifiers []Verifier return nil, fmt.Errorf("no valid attestations found for: %s", key) } - metadata.AttestationSignatures = checkedAttestations + AttestationPayloads, err := formatAttestations(checkedAttestations) + if err != nil { + return nil, fmt.Errorf("formatAttestations: %v", err) + } + + metadata.AttestationSignatures = AttestationPayloads fmt.Println("attestation verified for: ", key) fmt.Printf("%d number of valid attestations found for %s, found attestations: %v\n", len(checkedAttestations), key, checkedAttestations) @@ -191,6 +204,80 @@ func verifyImageSignatures(ctx context.Context, key string, verifiers []Verifier return nil, fmt.Errorf("no verifier found for: %s", key) } +// formatAttestations takes the payload within an Attestation and base64 decodes it, returning it as an in-toto statement +func formatAttestations(verifiedAttestations []oci.Signature) ([]in_toto.Statement, error) { + + decodedAttestations := make([]in_toto.Statement, len(verifiedAttestations)) + + for i, att := range verifiedAttestations { + p, err := att.Payload() + if err != nil { + fmt.Fprintf(os.Stderr, "error fetching payload: %v", err) + return nil, err + } + + var pm map[string]interface{} + json.Unmarshal(p, &pm) + + payload := strings.Trim(fmt.Sprintf("%v", pm["payload"]), "\"") + + statementRaw, err := base64.StdEncoding.DecodeString(payload) + if err != nil { + fmt.Fprintf(os.Stderr, "error decoding payload: %v", err) + } + + var statement in_toto.Statement + if err := json.Unmarshal(statementRaw, &statement); err != nil { + return nil, err + } + + decodedAttestations[i] = statement + } + + return decodedAttestations, nil + +} + +// formatPayload converts the signature into a payload to be sent back to gatekeeper +func formatSignaturePayloads(verifiedSignatures []oci.Signature) ([]payload.SimpleContainerImage, error) { + + var outputKeys []payload.SimpleContainerImage + + for _, sig := range verifiedSignatures { + p, err := sig.Payload() + if err != nil { + fmt.Fprintf(os.Stderr, "error fetching payload: %v", err) + return nil, err + } + + ss := payload.SimpleContainerImage{} + if err := json.Unmarshal(p, &ss); err != nil { + fmt.Println("error decoding the payload:", err.Error()) + return nil, err + } + + if cert, err := sig.Cert(); err == nil && cert != nil { + if ss.Optional == nil { + ss.Optional = make(map[string]interface{}) + } + ss.Optional["Subject"] = sigs.CertSubject(cert) + if issuerURL := sigs.CertIssuerExtension(cert); issuerURL != "" { + ss.Optional["Issuer"] = issuerURL + } + } + if bundle, err := sig.Bundle(); err == nil && bundle != nil { + if ss.Optional == nil { + ss.Optional = make(map[string]interface{}) + } + ss.Optional["Bundle"] = bundle + } + + outputKeys = append(outputKeys, ss) + } + + return outputKeys, nil +} + // sendResponse sends back the response to Gatekeeper. func sendResponse(results *[]externaldata.Item, systemErr string, w http.ResponseWriter) { response := externaldata.ProviderResponse{