Skip to content

Commit

Permalink
Refactor SCAI generator APIs into pkg/ (#38)
Browse files Browse the repository at this point in the history
* Refactor SCAI generator APIs into pkg/

Signed-off-by: Marcela Melara <[email protected]>

* Add docstrings, address review comments

Signed-off-by: Marcela Melara <[email protected]>

---------

Signed-off-by: Marcela Melara <[email protected]>
  • Loading branch information
marcelamelara authored Apr 5, 2024
1 parent 51fc87b commit 78eb564
Show file tree
Hide file tree
Showing 16 changed files with 169 additions and 93 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ $(VENVDIR):
py-venv: $(VENVDIR) $(PYTHON_DIR)

go-mod:
cd ./go && go build && go install
cd ./scai-gen && go build && go install

clean:
@echo REMOVE SCAI VENV AND PYTHON LIB DIRS
Expand Down
6 changes: 3 additions & 3 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ For information on how to use our CLI tools in [Python] or [Go] environments,
please refer to their instructions.

[in-toto Statement]: https://github.com/in-toto/attestation/blob/main/spec/v1/statement.md
[Resource Descriptors]: https://github.com/in-toto/attestation/blob/main/spec/v1/resource_descriptor.md
[Attribute Assertions]: https://github.com/in-toto/attestation/blob/main/protos/in_toto_attestation/predicates/scai/v0/scai.proto#L16
[ResourceDescriptors]: https://github.com/in-toto/attestation/blob/main/spec/v1/resource_descriptor.md
[AttributeAssertions]: https://github.com/in-toto/attestation/blob/main/protos/in_toto_attestation/predicates/scai/v0/scai.proto#L16
[Report]: https://github.com/in-toto/attestation/blob/main/protos/in_toto_attestation/predicates/scai/v0/scai.proto#L28
[SCAI specification]: https://github.com/in-toto/attestation/blob/main/spec/predicates/scai.md
[Go]: ../go/README.md
[Go]: ../scai-gen/README.md
[Python]: ../python/README.md
15 changes: 4 additions & 11 deletions scai-gen/cmd/assert.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package cmd
import (
"fmt"

"github.com/in-toto/scai-demos/scai-gen/fileio"
"github.com/in-toto/scai-demos/scai-gen/pkg/fileio"
"github.com/in-toto/scai-demos/scai-gen/pkg/generators"

scai "github.com/in-toto/attestation/go/predicates/scai/v0"
ita "github.com/in-toto/attestation/go/v1"
"github.com/spf13/cobra"
"google.golang.org/protobuf/types/known/structpb"
Expand Down Expand Up @@ -94,16 +94,9 @@ func genAttrAssertion(_ *cobra.Command, args []string) error {
}
}

aa := &scai.AttributeAssertion{
Attribute: attribute,
Target: target,
Conditions: conditions,
Evidence: evidence,
}

err := aa.Validate()
aa, err := generators.NewSCAIAssertion(attribute, target, conditions, evidence)
if err != nil {
return fmt.Errorf("invalid SCAI attribute assertion: %w", err)
return fmt.Errorf("unable to generate SCAI attribute assertion: %w", err)
}

return fileio.WritePbToFile(aa, outFile, false)
Expand Down
4 changes: 2 additions & 2 deletions scai-gen/cmd/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"path/filepath"
"strings"

"github.com/in-toto/scai-demos/scai-gen/fileio"
"github.com/in-toto/scai-demos/scai-gen/policy"
"github.com/in-toto/scai-demos/scai-gen/pkg/fileio"
"github.com/in-toto/scai-demos/scai-gen/pkg/policy"

"github.com/in-toto/attestation-verifier/verifier"
scai "github.com/in-toto/attestation/go/predicates/scai/v0"
Expand Down
67 changes: 8 additions & 59 deletions scai-gen/cmd/rd.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
package cmd

import (
"encoding/hex"
"fmt"
"os"
"strings"

"github.com/in-toto/scai-demos/scai-gen/fileio"
"github.com/in-toto/scai-demos/scai-gen/policy"
"github.com/in-toto/scai-demos/scai-gen/pkg/fileio"
"github.com/in-toto/scai-demos/scai-gen/pkg/generators"

ita "github.com/in-toto/attestation/go/v1"
"github.com/spf13/cobra"
"google.golang.org/protobuf/types/known/structpb"
)
Expand Down Expand Up @@ -163,41 +159,15 @@ func genRdFromFile(_ *cobra.Command, args []string) error {
}

filename := args[0]
fileBytes, err := os.ReadFile(filename)
if err != nil {
return fmt.Errorf("error reading resource file: %w", err)
}

var content []byte
if withContent {
content = fileBytes
}

sha256Digest := hex.EncodeToString(policy.GenSHA256(fileBytes))

rdName := filename
if len(name) > 0 {
rdName = name
}

annotations, err := readAnnotations(annotationsFile)
if err != nil {
return fmt.Errorf("error reading annotations file: %w", err)
}

rd := &ita.ResourceDescriptor{
Name: rdName,
Uri: uri,
Digest: map[string]string{"sha256": strings.ToLower(sha256Digest)},
Content: content,
DownloadLocation: downloadLocation,
MediaType: mediaType,
Annotations: annotations,
return fmt.Errorf("unable to read annotations file: %w", err)
}

err = rd.Validate()
rd, err := generators.NewRdForFile(filename, name, uri, hashAlg, withContent, mediaType, downloadLocation, annotations)
if err != nil {
return fmt.Errorf("invalid resource descriptor: %w", err)
return fmt.Errorf("unable to generate RD: %w", err)
}

return fileio.WritePbToFile(rd, outFile, false)
Expand All @@ -211,35 +181,14 @@ func genRdForRemote(_ *cobra.Command, args []string) error {

remoteURI := args[0]

digestSet := make(map[string]string)
if len(digest) > 0 {
// the in-toto spec expects a hex-encoded string in DigestSets
// https://github.com/in-toto/attestation/blob/main/spec/v1/digest_set.md
_, err := hex.DecodeString(digest)
if err != nil {
return fmt.Errorf("digest is not valid hex-encoded string: %w", err)
}

// we can assume that we have both variables set at this point
digestSet = map[string]string{hashAlg: strings.ToLower(digest)}
}

annotations, err := readAnnotations(annotationsFile)
if err != nil {
return fmt.Errorf("error reading annotations file: %w", err)
}

rd := &ita.ResourceDescriptor{
Name: name,
Uri: remoteURI,
Digest: digestSet,
DownloadLocation: downloadLocation,
Annotations: annotations,
return fmt.Errorf("unable to read annotations file: %w", err)
}

err = rd.Validate()
rd, err := generators.NewRdForRemote(remoteURI, name, hashAlg, digest, downloadLocation, annotations)
if err != nil {
return fmt.Errorf("invalid resource descriptor: %w", err)
return fmt.Errorf("unable to generate RD: %w", err)
}

return fileio.WritePbToFile(rd, outFile, false)
Expand Down
22 changes: 6 additions & 16 deletions scai-gen/cmd/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ package cmd
import (
"fmt"

"github.com/in-toto/scai-demos/scai-gen/fileio"
"github.com/in-toto/scai-demos/scai-gen/pkg/fileio"
"github.com/in-toto/scai-demos/scai-gen/pkg/generators"

scai "github.com/in-toto/attestation/go/predicates/scai/v0"
ita "github.com/in-toto/attestation/go/v1"
Expand Down Expand Up @@ -87,14 +88,10 @@ func genAttrReport(_ *cobra.Command, args []string) error {
}

// first, generate the SCAI Report
ar := &scai.AttributeReport{
Attributes: attrAsserts,
Producer: producer,
}

err := ar.Validate()
ar, err := generators.NewSCAIReport(attrAsserts, producer)
if err != nil {
return fmt.Errorf("invalid SCAI attribute report: %w", err)
return fmt.Errorf("unable to generate SCAI Report: %w", err)
}

// then, plug the Report into an in-toto Statement
Expand All @@ -118,16 +115,9 @@ func genAttrReport(_ *cobra.Command, args []string) error {
return err
}

statement := &ita.Statement{
Type: ita.StatementTypeUri,
Subject: []*ita.ResourceDescriptor{subject},
PredicateType: "https://in-toto.io/attestation/scai/attribute-report/v0.2",
Predicate: reportStruct,
}

err = statement.Validate()
statement, err := generators.NewStatement([]*ita.ResourceDescriptor{subject}, "https://in-toto.io/attestation/scai/attribute-report/v0.2", reportStruct)
if err != nil {
return fmt.Errorf("invalid in-toto Statement: %w", err)
return fmt.Errorf("unable to generate in-toto Statement: %w", err)
}

return fileio.WritePbToFile(statement, outFile, prettyPrint)
Expand Down
2 changes: 1 addition & 1 deletion scai-gen/cmd/sigstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"context"
"fmt"

"github.com/in-toto/scai-demos/scai-gen/fileio"
"github.com/in-toto/scai-demos/scai-gen/pkg/fileio"

ita "github.com/in-toto/attestation/go/v1"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio"
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
43 changes: 43 additions & 0 deletions scai-gen/pkg/generators/scai.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package generators

import (
"fmt"

scai "github.com/in-toto/attestation/go/predicates/scai/v0"
ita "github.com/in-toto/attestation/go/v1"
"google.golang.org/protobuf/types/known/structpb"
)

// Generates a SCAI v0 AttributeAssertion struct.
// Throws an error if the resulting AttributeAssertion does not meet the spec.
func NewSCAIAssertion(attribute string, target *ita.ResourceDescriptor, conditions *structpb.Struct, evidence *ita.ResourceDescriptor) (*scai.AttributeAssertion, error) {
aa := &scai.AttributeAssertion{
Attribute: attribute,
Target: target,
Conditions: conditions,
Evidence: evidence,
}

err := aa.Validate()
if err != nil {
return nil, fmt.Errorf("invalid SCAI attribute assertion: %w", err)
}

return aa, nil
}

// Generates a SCAI v0 AttributeReport struct to be used as an in-toto attestation predicate.
// Throws an error if the resulting AttributeReport does not meet the spec.
func NewSCAIReport(attrAssertions []*scai.AttributeAssertion, producer *ita.ResourceDescriptor) (*scai.AttributeReport, error) {
ar := &scai.AttributeReport{
Attributes: attrAssertions,
Producer: producer,
}

err := ar.Validate()
if err != nil {
return nil, fmt.Errorf("invalid SCAI attribute report: %w", err)
}

return ar, nil
}
101 changes: 101 additions & 0 deletions scai-gen/pkg/generators/v1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package generators

import (
"encoding/hex"
"fmt"
"os"
"strings"

"github.com/in-toto/scai-demos/scai-gen/pkg/policy"

ita "github.com/in-toto/attestation/go/v1"
"google.golang.org/protobuf/types/known/structpb"
)

// Generates an in-toto Attestation Framework v1 ResourceDescriptor for a local file, including its digest (default sha256).
// Throws an error if the resulting ResourceDescriptor does not meet the spec.
func NewRdForFile(filename, name, uri, hashAlg string, withContent bool, mediaType, downloadLocation string, annotations *structpb.Struct) (*ita.ResourceDescriptor, error) {
fileBytes, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("error reading resource file: %w", err)
}

var content []byte
if withContent {
content = fileBytes
}

var digest string
var alg string
if hashAlg == "sha256" || hashAlg == "" {
digest = hex.EncodeToString(policy.GenSHA256(fileBytes))
alg = "sha256"
} else {
return nil, fmt.Errorf("hash algorithm %s not supported", hashAlg)
}

rdName := filename
if len(name) > 0 {
rdName = name
}

rd := &ita.ResourceDescriptor{
Name: rdName,
Uri: uri,
Digest: map[string]string{alg: strings.ToLower(digest)},
Content: content,
DownloadLocation: downloadLocation,
MediaType: mediaType,
Annotations: annotations,
}

err = rd.Validate()
if err != nil {
return nil, fmt.Errorf("invalid resource descriptor: %w", err)
}

return rd, nil
}

// Generates an in-toto Attestation Framework v1 ResourceDescriptor for a remote resource identified by a name or URI).
// Does not check if the URI resolves to a valid remote location.
// Throws an error if the resulting ResourceDescriptor does not meet the spec.
func NewRdForRemote(name, uri, hashAlg, digest, downloadLocation string, annotations *structpb.Struct) (*ita.ResourceDescriptor, error) {
digestSet := make(map[string]string)
if len(hashAlg) > 0 && len(digest) > 0 {
digestSet = map[string]string{hashAlg: strings.ToLower(digest)}
}

rd := &ita.ResourceDescriptor{
Name: name,
Uri: uri,
Digest: digestSet,
DownloadLocation: downloadLocation,
Annotations: annotations,
}

err := rd.Validate()
if err != nil {
return nil, fmt.Errorf("invalid resource descriptor: %w", err)
}

return rd, nil
}

// Generates an in-toto Attestation Framework v1 Statement including a given predicate.
// Throws an error if the resulting Statement does not meet the spec.
func NewStatement(subjects []*ita.ResourceDescriptor, predicateType string, predicate *structpb.Struct) (*ita.Statement, error) {
statement := &ita.Statement{
Type: ita.StatementTypeUri,
Subject: subjects,
PredicateType: predicateType,
Predicate: predicate,
}

err := statement.Validate()
if err != nil {
return nil, fmt.Errorf("invalid in-toto Statement: %w", err)
}

return statement, nil
}
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 comments on commit 78eb564

Please sign in to comment.