2024-04-29 12:52:39 -05:00
package attest
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"testing"
2024-08-21 18:01:11 +01:00
"github.com/distribution/reference"
2024-09-02 16:17:50 +01:00
"github.com/docker/attest/attestation"
"github.com/docker/attest/config"
2024-05-22 14:49:23 +01:00
"github.com/docker/attest/internal/test"
2024-09-02 16:17:50 +01:00
"github.com/docker/attest/oci"
"github.com/docker/attest/policy"
"github.com/docker/attest/tuf"
2024-05-22 14:49:23 +01:00
intoto "github.com/in-toto/in-toto-golang/in_toto"
2024-04-29 12:52:39 -05:00
"github.com/stretchr/testify/assert"
2024-05-22 14:49:23 +01:00
"github.com/stretchr/testify/require"
2024-09-05 14:08:55 +01:00
"sigs.k8s.io/yaml"
2024-04-29 12:52:39 -05:00
)
2024-09-05 14:08:55 +01:00
var (
ExampleAttestation = filepath . Join ( "test" , "testdata" , "example_attestation.json" )
LocalKeysPolicy = filepath . Join ( "test" , "testdata" , "local-policy-real" )
)
2024-04-29 12:52:39 -05:00
2024-06-21 11:29:16 +01:00
const (
LinuxAMD64 = "linux/amd64"
)
2024-04-29 12:52:39 -05:00
func TestVerifyAttestations ( t * testing . T ) {
ex , err := os . ReadFile ( ExampleAttestation )
assert . NoError ( t , err )
2024-07-31 15:48:00 +01:00
env := new ( attestation . Envelope )
2024-04-29 12:52:39 -05:00
err = json . Unmarshal ( ex , env )
assert . NoError ( t , err )
2024-08-12 14:49:52 -05:00
resolver := & attestation . MockResolver {
2024-04-29 12:52:39 -05:00
Envs : [ ] * attestation . Envelope { env } ,
}
testCases := [ ] struct {
name string
policyEvaluationError error
expectedError error
} {
{ "policy ok" , nil , nil } ,
{ "policy error" , fmt . Errorf ( "policy error" ) , fmt . Errorf ( "policy evaluation failed: policy error" ) } ,
}
2024-08-29 17:43:45 +01:00
ctx := context . Background ( )
2024-04-29 12:52:39 -05:00
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
2024-05-02 14:46:21 +01:00
mockPE := policy . MockPolicyEvaluator {
2024-08-12 14:49:52 -05:00
EvaluateFunc : func ( _ context . Context , _ attestation . Resolver , _ * policy . Policy , _ * policy . Input ) ( * policy . Result , error ) {
2024-05-02 14:46:21 +01:00
return policy . AllowedResult ( ) , tc . policyEvaluationError
2024-04-29 12:52:39 -05:00
} ,
}
2024-08-29 17:43:45 +01:00
_ , err := VerifyAttestations ( ctx , resolver , & mockPE , & policy . Policy { ResolvedName : "" } )
2024-04-29 12:52:39 -05:00
if tc . expectedError != nil {
2024-05-22 14:49:23 +01:00
if assert . Error ( t , err ) {
assert . Equal ( t , tc . expectedError . Error ( ) , err . Error ( ) )
}
2024-04-29 12:52:39 -05:00
} else {
assert . NoError ( t , err )
}
} )
}
}
2024-05-22 14:49:23 +01:00
func TestVSA ( t * testing . T ) {
ctx , signer := test . Setup ( t )
// setup an image with signed attestations
outputLayout := test . CreateTempDir ( t , "" , TestTempDir )
2024-07-16 10:05:17 +01:00
opts := & attestation . SigningOptions { }
2024-09-02 16:17:50 +01:00
attIdx , err := oci . IndexFromPath ( test . UnsignedTestImage ( ) )
2024-05-22 14:49:23 +01:00
assert . NoError ( t , err )
2024-07-05 09:29:14 +01:00
signedManifests , err := SignStatements ( ctx , attIdx . Index , signer , opts )
require . NoError ( t , err )
signedIndex := attIdx . Index
2024-07-16 10:05:17 +01:00
signedIndex , err = attestation . UpdateIndexImages ( signedIndex , signedManifests )
2024-07-05 09:29:14 +01:00
require . NoError ( t , err )
2024-05-22 14:49:23 +01:00
// output signed attestations
2024-08-05 13:24:58 -05:00
spec , err := oci . ParseImageSpec ( oci . LocalPrefix + outputLayout , oci . WithPlatform ( LinuxAMD64 ) )
require . NoError ( t , err )
2024-08-08 14:23:46 -05:00
err = oci . SaveIndex ( [ ] * oci . ImageSpec { spec } , signedIndex , attIdx . Name )
2024-05-22 14:49:23 +01:00
assert . NoError ( t , err )
// mocked vsa query should pass
2024-08-01 15:35:15 +01:00
policyOpts := & policy . Options {
2024-08-05 13:24:58 -05:00
LocalPolicyDir : PassPolicyDir ,
AttestationStyle : config . AttestationStyleAttached ,
2024-08-28 09:53:52 +01:00
DisableTUF : true ,
2024-05-22 14:49:23 +01:00
}
2024-08-05 13:24:58 -05:00
results , err := Verify ( ctx , spec , policyOpts )
2024-05-22 14:49:23 +01:00
require . NoError ( t , err )
assert . Equal ( t , OutcomeSuccess , results . Outcome )
assert . Empty ( t , results . Violations )
2024-06-17 17:28:22 +01:00
if assert . NotNil ( t , results . Input ) {
assert . Equal ( t , "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620" , results . Input . Digest )
2024-08-21 18:01:11 +01:00
assert . NotNil ( t , results . Input . Tag )
2024-06-17 17:28:22 +01:00
}
2024-05-22 14:49:23 +01:00
assert . Equal ( t , intoto . StatementInTotoV01 , results . VSA . Type )
assert . Equal ( t , attestation . VSAPredicateType , results . VSA . PredicateType )
assert . Len ( t , results . VSA . Subject , 1 )
require . IsType ( t , attestation . VSAPredicate { } , results . VSA . Predicate )
2024-08-01 15:35:15 +01:00
attestationPredicate , ok := results . VSA . Predicate . ( attestation . VSAPredicate )
require . True ( t , ok )
2024-05-22 14:49:23 +01:00
assert . Equal ( t , "PASSED" , attestationPredicate . VerificationResult )
assert . Equal ( t , "docker-official-images" , attestationPredicate . Verifier . ID )
assert . Equal ( t , [ ] string { "SLSA_BUILD_LEVEL_3" } , attestationPredicate . VerifiedLevels )
2024-08-14 12:32:51 -05:00
assert . Equal ( t , PassPolicyDir + "/policy.rego" , attestationPredicate . Policy . DownloadLocation )
assert . Equal ( t , "https://docker.com/official/policy/v0.1" , attestationPredicate . Policy . URI )
2024-08-14 09:16:24 -05:00
assert . Equal ( t , map [ string ] string { "sha256" : "d71d6b8f49fcba1295b16f5394dd5863a14e4277eb663d66d8c48e392509afe0" } , attestationPredicate . Policy . Digest )
2024-05-22 14:49:23 +01:00
}
func TestVerificationFailure ( t * testing . T ) {
ctx , signer := test . Setup ( t )
// setup an image with signed attestations
outputLayout := test . CreateTempDir ( t , "" , TestTempDir )
2024-07-16 10:05:17 +01:00
opts := & attestation . SigningOptions { }
2024-09-02 16:17:50 +01:00
attIdx , err := oci . IndexFromPath ( test . UnsignedTestImage ( ) )
2024-05-22 14:49:23 +01:00
assert . NoError ( t , err )
2024-07-05 09:29:14 +01:00
signedManifests , err := SignStatements ( ctx , attIdx . Index , signer , opts )
require . NoError ( t , err )
signedIndex := attIdx . Index
2024-07-16 10:05:17 +01:00
signedIndex , err = attestation . UpdateIndexImages ( signedIndex , signedManifests , attestation . WithReplacedLayers ( true ) )
2024-07-05 09:29:14 +01:00
require . NoError ( t , err )
2024-05-22 14:49:23 +01:00
// output signed attestations
2024-08-05 13:24:58 -05:00
spec , err := oci . ParseImageSpec ( oci . LocalPrefix + outputLayout , oci . WithPlatform ( LinuxAMD64 ) )
require . NoError ( t , err )
2024-08-08 14:23:46 -05:00
err = oci . SaveIndex ( [ ] * oci . ImageSpec { spec } , signedIndex , attIdx . Name )
2024-05-22 14:49:23 +01:00
assert . NoError ( t , err )
2024-06-06 09:59:32 +01:00
// mocked vsa query should fail
2024-08-01 15:35:15 +01:00
policyOpts := & policy . Options {
2024-08-05 13:24:58 -05:00
LocalPolicyDir : FailPolicyDir ,
AttestationStyle : config . AttestationStyleAttached ,
2024-08-28 09:53:52 +01:00
DisableTUF : true ,
2024-05-22 14:49:23 +01:00
}
2024-08-05 13:24:58 -05:00
results , err := Verify ( ctx , spec , policyOpts )
2024-05-22 14:49:23 +01:00
require . NoError ( t , err )
assert . Equal ( t , OutcomeFailure , results . Outcome )
assert . Len ( t , results . Violations , 1 )
violation := results . Violations [ 0 ]
assert . Equal ( t , "missing_attestation" , violation . Type )
assert . Equal ( t , "Attestation missing for subject" , violation . Description )
assert . Nil ( t , violation . Attestation )
assert . Equal ( t , intoto . StatementInTotoV01 , results . VSA . Type )
assert . Equal ( t , attestation . VSAPredicateType , results . VSA . PredicateType )
assert . Len ( t , results . VSA . Subject , 1 )
require . IsType ( t , attestation . VSAPredicate { } , results . VSA . Predicate )
2024-08-01 15:35:15 +01:00
attestationPredicate , ok := results . VSA . Predicate . ( attestation . VSAPredicate )
require . True ( t , ok )
2024-05-22 14:49:23 +01:00
assert . Equal ( t , "FAILED" , attestationPredicate . VerificationResult )
assert . Equal ( t , "docker-official-images" , attestationPredicate . Verifier . ID )
assert . Equal ( t , [ ] string { "SLSA_BUILD_LEVEL_3" } , attestationPredicate . VerifiedLevels )
2024-08-14 12:32:51 -05:00
assert . Equal ( t , FailPolicyDir + "/policy.rego" , attestationPredicate . Policy . DownloadLocation )
assert . Equal ( t , "https://docker.com/official/policy/v0.1" , attestationPredicate . Policy . URI )
2024-08-14 09:16:24 -05:00
assert . Equal ( t , map [ string ] string { "sha256" : "ad045e1bd7cd602d90196acf68f2c57d7b51565d59e6e30e30d94ae86aa16201" } , attestationPredicate . Policy . Digest )
2024-05-22 14:49:23 +01:00
}
2024-06-06 09:59:32 +01:00
2024-07-12 17:09:41 +01:00
func TestSignVerify ( t * testing . T ) {
2024-06-06 09:59:32 +01:00
ctx , signer := test . Setup ( t )
// setup an image with signed attestations
outputLayout := test . CreateTempDir ( t , "" , TestTempDir )
2024-09-05 14:08:55 +01:00
keys , err := test . GenKeyMetadata ( signer )
require . NoError ( t , err )
config := struct {
Keys [ ] * attestation . KeyMetadata ` json:"keys" `
} {
Keys : [ ] * attestation . KeyMetadata { keys } ,
}
keysYaml , err := yaml . Marshal ( config )
require . NoError ( t , err )
// write keysYaml to config.yaml in LocalKeysPolicy.
err = os . WriteFile ( filepath . Join ( LocalKeysPolicy , "config.yaml" ) , keysYaml , 0 o600 )
require . NoError ( t , err )
2024-06-06 09:59:32 +01:00
testCases := [ ] struct {
2024-08-28 09:53:52 +01:00
name string
signTL bool
policyDir string
imageName string
2024-08-23 09:33:30 +01:00
expectedNonSuccess Outcome
2024-06-06 09:59:32 +01:00
} {
2024-07-12 17:09:41 +01:00
{ name : "happy path" , signTL : true , policyDir : PassNoTLPolicyDir } ,
{ name : "sign tl, verify no tl" , signTL : true , policyDir : PassPolicyDir } ,
{ name : "no tl" , signTL : false , policyDir : PassPolicyDir } ,
2024-09-05 14:08:55 +01:00
{ name : "mirror" , signTL : false , policyDir : PassMirrorPolicyDir , imageName : "mirror.org/library/test-image:test" } ,
{ name : "mirror no match" , signTL : false , policyDir : PassMirrorPolicyDir , imageName : "incorrect.org/library/test-image:test" , expectedNonSuccess : OutcomeNoPolicy } ,
2024-08-21 18:01:11 +01:00
{ name : "verify inputs" , signTL : false , policyDir : InputsPolicyDir } ,
2024-09-05 14:08:55 +01:00
{ name : "mirror with verification" , signTL : false , policyDir : LocalKeysPolicy , imageName : "mirror.org/library/test-image:test" } ,
2024-06-06 09:59:32 +01:00
}
2024-09-02 16:17:50 +01:00
attIdx , err := oci . IndexFromPath ( test . UnsignedTestImage ( ) )
2024-06-06 09:59:32 +01:00
assert . NoError ( t , err )
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
opts := & attestation . SigningOptions {
2024-09-05 14:08:55 +01:00
SkipTL : ! tc . signTL ,
2024-06-06 09:59:32 +01:00
}
2024-07-05 09:29:14 +01:00
signedManifests , err := SignStatements ( ctx , attIdx . Index , signer , opts )
require . NoError ( t , err )
signedIndex := attIdx . Index
2024-07-16 10:05:17 +01:00
signedIndex , err = attestation . UpdateIndexImages ( signedIndex , signedManifests , attestation . WithReplacedLayers ( true ) )
2024-07-05 09:29:14 +01:00
require . NoError ( t , err )
2024-07-12 17:09:41 +01:00
imageName := tc . imageName
if imageName == "" {
imageName = attIdx . Name
}
2024-06-06 09:59:32 +01:00
// output signed attestations
2024-08-05 13:24:58 -05:00
spec , err := oci . ParseImageSpec ( oci . LocalPrefix + outputLayout , oci . WithPlatform ( LinuxAMD64 ) )
require . NoError ( t , err )
2024-08-08 14:23:46 -05:00
err = oci . SaveIndex ( [ ] * oci . ImageSpec { spec } , signedIndex , imageName )
2024-08-05 13:24:58 -05:00
require . NoError ( t , err )
2024-06-06 09:59:32 +01:00
2024-08-01 15:35:15 +01:00
policyOpts := & policy . Options {
2024-06-06 09:59:32 +01:00
LocalPolicyDir : tc . policyDir ,
2024-08-28 09:53:52 +01:00
DisableTUF : true ,
2024-09-05 14:08:55 +01:00
Debug : true ,
2024-06-06 09:59:32 +01:00
}
2024-08-05 13:24:58 -05:00
results , err := Verify ( ctx , spec , policyOpts )
2024-08-23 09:33:30 +01:00
require . NoError ( t , err )
if tc . expectedNonSuccess != "" {
assert . Equal ( t , tc . expectedNonSuccess , results . Outcome )
2024-07-12 17:09:41 +01:00
return
}
2024-09-05 14:08:55 +01:00
if results . Outcome == OutcomeFailure {
t . Logf ( "Violations: %v" , results . Violations )
}
2024-06-06 09:59:32 +01:00
assert . Equal ( t , OutcomeSuccess , results . Outcome )
2024-07-12 17:09:41 +01:00
platform , err := oci . ParsePlatform ( LinuxAMD64 )
require . NoError ( t , err )
2024-08-21 18:01:11 +01:00
ref , err := reference . ParseNormalizedNamed ( attIdx . Name )
require . NoError ( t , err )
expectedPURL , _ , err := oci . RefToPURL ( ref , platform )
2024-07-12 17:09:41 +01:00
require . NoError ( t , err )
2024-08-01 15:35:15 +01:00
assert . Equal ( t , expectedPURL , results . Input . PURL )
2024-06-06 09:59:32 +01:00
} )
}
}
2024-08-23 09:33:30 +01:00
func TestDefaultOptions ( t * testing . T ) {
testCases := [ ] struct {
name string
tufOpts * tuf . ClientOptions
localTargetsDir string
attestationStyle config . AttestationStyle
referrersRepo string
expectedError string
2024-08-28 09:53:52 +01:00
disableTuf bool
localPolicyDir string
2024-08-23 09:33:30 +01:00
} {
{ name : "empty" } ,
{ name : "tufClient provided" , tufOpts : & tuf . ClientOptions { MetadataSource : "a" , TargetsSource : "b" } } ,
{ name : "localTargetsDir provided" , localTargetsDir : test . CreateTempDir ( t , "" , TestTempDir ) } ,
{ name : "attestationStyle provided" , attestationStyle : config . AttestationStyleAttached } ,
{ name : "referrersRepo provided" , referrersRepo : "referrers" } ,
{ name : "referrersRepo provided with attached" , referrersRepo : "referrers" , attestationStyle : config . AttestationStyleAttached , expectedError : "referrers repo specified but attestation source not set to referrers" } ,
2024-08-28 09:53:52 +01:00
{ name : "tuf disabled and no local-policy-dir" , disableTuf : true , expectedError : "local policy dir must be set if not using TUF" } ,
{ name : "tuf disabled but options set" , disableTuf : true , tufOpts : & tuf . ClientOptions { MetadataSource : "a" , TargetsSource : "b" } , localPolicyDir : "foo" , expectedError : "TUF client options set but TUF disabled" } ,
2024-08-23 09:33:30 +01:00
}
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
defaultTargets , err := defaultLocalTargetsDir ( )
require . NoError ( t , err )
opts := & policy . Options {
TUFClientOptions : tc . tufOpts ,
LocalTargetsDir : tc . localTargetsDir ,
AttestationStyle : tc . attestationStyle ,
ReferrersRepo : tc . referrersRepo ,
2024-08-28 09:53:52 +01:00
DisableTUF : tc . disableTuf ,
LocalPolicyDir : tc . localPolicyDir ,
2024-08-23 09:33:30 +01:00
}
err = populateDefaultOptions ( opts )
if tc . expectedError != "" {
require . Error ( t , err )
assert . Equal ( t , tc . expectedError , err . Error ( ) )
return
}
require . NoError ( t , err )
if tc . localTargetsDir != "" {
assert . Equal ( t , tc . localTargetsDir , opts . LocalTargetsDir )
} else {
assert . Equal ( t , defaultTargets , opts . LocalTargetsDir )
}
if tc . attestationStyle != "" {
assert . Equal ( t , tc . attestationStyle , opts . AttestationStyle )
} else {
assert . Equal ( t , config . AttestationStyleReferrers , opts . AttestationStyle )
}
if tc . tufOpts != nil {
assert . Equal ( t , tc . tufOpts , opts . TUFClientOptions )
} else {
assert . NotNil ( t , opts . TUFClientOptions )
}
if tc . referrersRepo != "" {
assert . Equal ( t , tc . referrersRepo , opts . ReferrersRepo )
} else {
assert . Empty ( t , opts . ReferrersRepo )
}
} )
}
}