Skip to content

Commit

Permalink
add slsa level inference
Browse files Browse the repository at this point in the history
Signed-off-by: Ramon Petgrave <[email protected]>
  • Loading branch information
ramonpetgrave64 committed Jul 9, 2024
1 parent 475962b commit a1068c4
Show file tree
Hide file tree
Showing 3 changed files with 270 additions and 7 deletions.
1 change: 1 addition & 0 deletions errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,5 @@ var (
ErrorEmptyRequiredField = errors.New("empty value in required field")
ErrorMismatchResourceURI = errors.New("resource URI does not match")
ErrorMismatchVerifierID = errors.New("verifier ID does not match")
ErrorInvalidSLSALevel = errors.New("invalid SLSA level")
)
75 changes: 68 additions & 7 deletions verifiers/internal/vsa/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package vsa
import (
"context"
"fmt"
"strconv"
"strings"

"github.com/secure-systems-lab/go-securesystemslib/dsse"
Expand Down Expand Up @@ -189,27 +190,87 @@ func matchResourceURI(vsa *vsa10.VSA, vsaOpts *options.VSAOpts) error {

// confirmVerificationResult checks that the policy verification result is "PASSED".
func confirmVerificationResult(vsa *vsa10.VSA) error {
if normalizeString(vsa.Predicate.VerificationResult) != "PASSED" {
if vsa.Predicate.VerificationResult != "PASSED" {
return fmt.Errorf("%w: verification result is not Passed: %s", serrors.ErrorInvalidVerificationResult, vsa.Predicate.VerificationResult)
}
return nil
}

// matchVerifiedLevels checks if the verified levels in the VSA match the expected values.
func matchVerifiedLevels(vsa *vsa10.VSA, vsaOpts *options.VSAOpts) error {
vsaLevels := make(map[string]bool)
// check for SLSA track levels
wantedSLSALevels, err := extractSLSALevels(vsaOpts.ExpectedVerifiedLevels)
if err != nil {
return err
}
gotSLSALevels, err := extractSLSALevels(&vsa.Predicate.VerifiedLevels)
if err != nil {
return err
}
for track, expectedMinLSLSALevel := range wantedSLSALevels {
if vsaLevel, exists := gotSLSALevels[track]; !exists {
return fmt.Errorf("%w: expected SLSA level not found: %s", serrors.ErrorMismatchVerifiedLevels, track)
} else if vsaLevel < expectedMinLSLSALevel {
return fmt.Errorf("%w: expected SLSA level %s to be at least %d, got %d", serrors.ErrorMismatchVerifiedLevels, track, expectedMinLSLSALevel, vsaLevel)
}
}

// check for non-SLSA track levels
nonSLSAVSALevels := make(map[string]bool)
for _, level := range vsa.Predicate.VerifiedLevels {
vsaLevels[level] = true
if isSLSATRACKLevel(level) {
continue
}
nonSLSAVSALevels[level] = true
}
for _, expectedLevel := range *vsaOpts.ExpectedVerifiedLevels {
if _, ok := vsaLevels[normalizeString(expectedLevel)]; !ok {
if isSLSATRACKLevel(expectedLevel) {
continue
}
if _, ok := nonSLSAVSALevels[expectedLevel]; !ok {
return fmt.Errorf("%w: expected verified level not found: %s", serrors.ErrorMismatchVerifiedLevels, expectedLevel)
}
}
return nil
}

// normalizeString normalizes a string by trimming whitespace and converting to uppercase.
func normalizeString(s string) string {
return strings.TrimSpace(strings.ToUpper(s))
// isSLSATRACKLevel checks if the level is an SLSA track level.
// SLSA track levels are of the form SLSA_<track>_LEVEL_<level>, e.g., SLSA_BUILD_LEVEL_2.
func isSLSATRACKLevel(level string) bool {
return strings.HasPrefix(level, "SLSA_")
}

// extractSLSALevels extracts the SLSA levels from the verified levels.
// It returns a map of track to the highest level found, e.g.,
// SLSA_BUILD_LEVEL_2, SLSA_SOURCE_LEVEL_3 ->
//
// {
// "BUILD": 2,
// "SOURCE": 3,
// }
func extractSLSALevels(trackLevels *[]string) (map[string]int, error) {
vsaSLSATrackLadder := make(map[string]int)
for _, trackLevel := range *trackLevels {
if !strings.HasPrefix(trackLevel, "SLSA_") {
continue
}
parts := strings.SplitN(trackLevel, "_", 4)
if len(parts) != 4 {
return nil, fmt.Errorf("%w: invalid SLSA level: %s", serrors.ErrorInvalidSLSALevel, trackLevel)
}
if parts[2] != "LEVEL" {
return nil, fmt.Errorf("%w: invalid SLSA level: %s", serrors.ErrorInvalidSLSALevel, trackLevel)
}
track := parts[1]
level, err := strconv.Atoi(parts[3])
if err != nil {
return nil, fmt.Errorf("%w: invalid SLSA level: %s", serrors.ErrorInvalidSLSALevel, trackLevel)
}
if currentLevel, exists := vsaSLSATrackLadder[track]; exists {
vsaSLSATrackLadder[track] = max(currentLevel, level)
} else {
vsaSLSATrackLadder[track] = level
}
}
return vsaSLSATrackLadder, nil
}
201 changes: 201 additions & 0 deletions verifiers/internal/vsa/verifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,28 @@ func Test_matchExpectedValues(t *testing.T) {
ExpectedVerifierID: goodVSAOpts.ExpectedVerifierID,
},
},
{
name: "success: expected lower SLSA level",
vsa: goodVSA,
opts: &options.VSAOpts{
ExpectedDigests: goodVSAOpts.ExpectedDigests,
ExpectedResourceURI: goodVSAOpts.ExpectedResourceURI,
ExpectedVerifiedLevels: &[]string{"SLSA_BUILD_LEVEL_1"},
ExpectedVerifierID: goodVSAOpts.ExpectedVerifierID,
},
},
// failure cases
{
name: "expected higher SLSA level",
vsa: goodVSA,
opts: &options.VSAOpts{
ExpectedDigests: goodVSAOpts.ExpectedDigests,
ExpectedResourceURI: goodVSAOpts.ExpectedResourceURI,
ExpectedVerifiedLevels: &[]string{"SLSA_BUILD_LEVEL_3"},
ExpectedVerifierID: goodVSAOpts.ExpectedVerifierID,
},
err: serrors.ErrorMismatchVerifiedLevels,
},
{
name: "failure empty digests",
vsa: &vsa10.VSA{
Expand Down Expand Up @@ -460,6 +481,186 @@ func Test_matchExpectedValues(t *testing.T) {
}
}

func Test_matchVerifiedLevels(t *testing.T) {
t.Parallel()

tests := []struct {
name string
vsa *vsa10.VSA
vsaOpts *options.VSAOpts
err error
}{
// success cases
{
name: "success: equal levels",
vsa: &vsa10.VSA{
Predicate: vsa10.Predicate{
VerifiedLevels: []string{"SLSA_BUILD_LEVEL_1", "SLSA_SOURCE_LEVEL_2", "BCID_L1"},
},
},
vsaOpts: &options.VSAOpts{
ExpectedVerifiedLevels: &[]string{"SLSA_BUILD_LEVEL_1", "SLSA_SOURCE_LEVEL_2", "BCID_L1"},
},
},
{
name: "success: expected lower SLSA level",
vsa: &vsa10.VSA{
Predicate: vsa10.Predicate{
VerifiedLevels: []string{"SLSA_BUILD_LEVEL_1", "SLSA_SOURCE_LEVEL_2", "BCID_L1"},
},
},
vsaOpts: &options.VSAOpts{
ExpectedVerifiedLevels: &[]string{"SLSA_BUILD_LEVEL_0", "SLSA_SOURCE_LEVEL_2", "BCID_L1"},
},
},
{
name: "success: unspecified verifiedLevels",
vsa: &vsa10.VSA{
Predicate: vsa10.Predicate{
VerifiedLevels: []string{"SLSA_BUILD_LEVEL_1", "SLSA_SOURCE_LEVEL_2", "BCID_L1"},
},
},
vsaOpts: &options.VSAOpts{
ExpectedVerifiedLevels: &[]string{},
},
},
{
name: "success: no SLSA levels",
vsa: &vsa10.VSA{
Predicate: vsa10.Predicate{
VerifiedLevels: []string{"BCID_L1"},
},
},
vsaOpts: &options.VSAOpts{
ExpectedVerifiedLevels: &[]string{},
},
},
// failure cases
{
name: "failure: expected higher SLSA level",
vsa: &vsa10.VSA{
Predicate: vsa10.Predicate{
VerifiedLevels: []string{"SLSA_BUILD_LEVEL_1", "SLSA_SOURCE_LEVEL_2", "BCID_L1"},
},
},
vsaOpts: &options.VSAOpts{
ExpectedVerifiedLevels: &[]string{"SLSA_BUILD_LEVEL_2", "SLSA_SOURCE_LEVEL_2", "BCID_L1"},
},
err: serrors.ErrorMismatchVerifiedLevels,
},
{
name: "failure: missing a expected SLSA track",
vsa: &vsa10.VSA{
Predicate: vsa10.Predicate{
VerifiedLevels: []string{"SLSA_BUILD_LEVEL_2", "BCID_L1"},
},
},
vsaOpts: &options.VSAOpts{
ExpectedVerifiedLevels: &[]string{"SLSA_BUILD_LEVEL_2", "SLSA_SOURCE_LEVEL_2", "BCID_L1"},
},
err: serrors.ErrorMismatchVerifiedLevels,
},
{
name: "failure: missing a expected non-SLSA track",
vsa: &vsa10.VSA{
Predicate: vsa10.Predicate{
VerifiedLevels: []string{"SLSA_BUILD_LEVEL_2"},
},
},
vsaOpts: &options.VSAOpts{
ExpectedVerifiedLevels: &[]string{"SLSA_BUILD_LEVEL_2", "BCID_L1"},
},
err: serrors.ErrorMismatchVerifiedLevels,
},
}

for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

err := matchVerifiedLevels(tc.vsa, tc.vsaOpts)

if diff := cmp.Diff(tc.err, err, cmpopts.EquateErrors()); diff != "" {
t.Errorf("unexpected error (-want +got): \n%s", diff)
}
})
}
}

func Test_extractSLSALevels(t *testing.T) {
t.Parallel()

tests := []struct {
name string
levels *[]string
want map[string]int
err error
}{
{
name: "success",
levels: &[]string{
"SLSA_BUILD_LEVEL_1",
"SLSA_SOURCE_LEVEL_2",
},
want: map[string]int{
"BUILD": 1,
"SOURCE": 2,
},
},
{
name: "success: empty",
levels: &[]string{},
want: map[string]int{},
},
{
name: "failure: invalid level number",
levels: &[]string{
"SLSA_BUILD_LEVEL_X",
},
err: serrors.ErrorInvalidSLSALevel,
},
{
name: "failure: invalid level text",
levels: &[]string{
"SLSA_BUILD_L_1",
},
err: serrors.ErrorInvalidSLSALevel,
},
{
name: "failure: no level number",
levels: &[]string{
"SLSA_BUILD_LEVEL_",
},
err: serrors.ErrorInvalidSLSALevel,
},
{
name: "failure: no last underscore",
levels: &[]string{
"SLSA_BUILD_LEVEL",
},
err: serrors.ErrorInvalidSLSALevel,
},
}

for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

got, err := extractSLSALevels(tc.levels)

if diff := cmp.Diff(tc.want, got, cmpopts.EquateComparable()); diff != "" {
t.Errorf("unexpected VSA (-want +got): \n%s", diff)
}

if diff := cmp.Diff(tc.err, err, cmpopts.EquateErrors()); diff != "" {
t.Errorf("unexpected error (-want +got): \n%s", diff)
}
})
}
}

func mustEncodeAttestationString(attestationString string) string {
dst := &bytes.Buffer{}
if err := json.Compact(dst, []byte(attestationString)); err != nil {
Expand Down

0 comments on commit a1068c4

Please sign in to comment.