Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions image/signature/policy_eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ type PolicyRequirement interface {
// WARNING: This validates signatures and the manifest, but does not download or validate the
// layers. Users must validate that the layers match their expected digests.
isRunningImageAllowed(ctx context.Context, image private.UnparsedImage) (bool, error)

// verifiesSignatures returns true if and only if the requirement performs cryptographic
// signature verification on the entire contents of the image before allowing it.
verifiesSignatures() bool
}

// PolicyReferenceMatch specifies a set of image identities accepted in PolicyRequirement.
Expand All @@ -79,8 +83,9 @@ type PolicyReferenceMatch interface {
// PolicyContext encapsulates a policy and possible cached state
// for speeding up its evaluation.
type PolicyContext struct {
Policy *Policy
state policyContextState // Internal consistency checking
Policy *Policy
state policyContextState // Internal consistency checking
requireSigned bool
}

// policyContextState is used internally to verify the users are not misusing a PolicyContext.
Expand Down Expand Up @@ -132,6 +137,13 @@ func policyIdentityLogName(ref types.ImageReference) string {
return ref.Transport().Name() + ":" + ref.PolicyConfigurationIdentity()
}

// RequireSignatureVerification modifies policy requirement handling. If passed
// `true`, at least one policy requirement which performs signature verification
// on the entire image contents must be present.
func (pc *PolicyContext) RequireSignatureVerification(val bool) {
pc.requireSigned = val
}

// requirementsForImageRef selects the appropriate requirements for ref.
func (pc *PolicyContext) requirementsForImageRef(ref types.ImageReference) PolicyRequirements {
// Do we have a PolicyTransportScopes for this transport?
Expand Down Expand Up @@ -278,6 +290,7 @@ func (pc *PolicyContext) IsRunningImageAllowed(ctx context.Context, publicImage
return false, PolicyRequirementError("List of verification policy requirements must not be empty")
}

wasSignatureVerified := false
for reqNumber, req := range reqs {
// FIXME: supply state
allowed, err := req.isRunningImageAllowed(ctx, image)
Expand All @@ -286,7 +299,15 @@ func (pc *PolicyContext) IsRunningImageAllowed(ctx context.Context, publicImage
return false, err
}
logrus.Debugf(" Requirement %d: allowed", reqNumber)
if req.verifiesSignatures() {
wasSignatureVerified = true
}
}

if pc.requireSigned && !wasSignatureVerified {
return false, PolicyRequirementError(fmt.Sprintf("No signature verification policy found for image %s", policyIdentityLogName(image.Reference())))
}

// We have tested that len(reqs) != 0, so at least one req must have explicitly allowed this image.
logrus.Debugf("Overall: allowed")
return true, nil
Expand Down
4 changes: 4 additions & 0 deletions image/signature/policy_eval_baselayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ func (pr *prSignedBaseLayer) isRunningImageAllowed(ctx context.Context, image pr
logrus.Errorf("signedBaseLayer not implemented yet!")
return false, PolicyRequirementError("signedBaseLayer not implemented yet!")
}

func (pr *prSignedBaseLayer) verifiesSignatures() bool {
return false
}
4 changes: 4 additions & 0 deletions image/signature/policy_eval_signedby.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,7 @@ func (pr *prSignedBy) isRunningImageAllowed(ctx context.Context, image private.U
}
return false, summary
}

func (pr *prSignedBy) verifiesSignatures() bool {
return true
}
4 changes: 4 additions & 0 deletions image/signature/policy_eval_sigstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,3 +432,7 @@ func (pr *prSigstoreSigned) isRunningImageAllowed(ctx context.Context, image pri
}
return false, summary
}

func (pr *prSigstoreSigned) verifiesSignatures() bool {
return true
}
8 changes: 8 additions & 0 deletions image/signature/policy_eval_simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,18 @@ func (pr *prInsecureAcceptAnything) isRunningImageAllowed(ctx context.Context, i
return true, nil
}

func (pr *prInsecureAcceptAnything) verifiesSignatures() bool {
return false
}

func (pr *prReject) isSignatureAuthorAccepted(ctx context.Context, image private.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
return sarRejected, nil, PolicyRequirementError(fmt.Sprintf("Any signatures for image %s are rejected by policy.", transports.ImageName(image.Reference())))
}

func (pr *prReject) isRunningImageAllowed(ctx context.Context, image private.UnparsedImage) (bool, error) {
return false, PolicyRequirementError(fmt.Sprintf("Running image %s is rejected by policy.", transports.ImageName(image.Reference())))
}

func (pr *prReject) verifiesSignatures() bool {
return false
}
74 changes: 74 additions & 0 deletions image/signature/policy_eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,3 +500,77 @@ func assertRunningRejectedPolicyRequirement(t *testing.T, allowed bool, err erro
assertRunningRejected(t, allowed, err)
assert.IsType(t, PolicyRequirementError(""), err)
}

func TestPolicyContextSetSignatureVerification(t *testing.T) {
pc, err := NewPolicyContext(&Policy{Default: PolicyRequirements{NewPRReject()}})
require.NoError(t, err)
defer func() {
err := pc.Destroy()
require.NoError(t, err)
}()

// Test default value is false
assert.False(t, pc.requireSigned)

// Test setting to true
pc.RequireSignatureVerification(true)
assert.True(t, pc.requireSigned)

// Test setting back to false
pc.RequireSignatureVerification(false)
assert.False(t, pc.requireSigned)
}

func TestPolicyContextIsRunningImageAllowedWithRequireSigned(t *testing.T) {
pc, err := NewPolicyContext(&Policy{
Default: PolicyRequirements{NewPRReject()},
Transports: map[string]PolicyTransportScopes{
"docker": {
"docker.io/testing/manifest:insecureOnly": {
NewPRInsecureAcceptAnything(),
},
"docker.io/testing/manifest:insecureWithOther": {
NewPRInsecureAcceptAnything(),
xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, "fixtures/public-key.gpg", NewPRMMatchRepository()),
},
"docker.io/testing/manifest:signedOnly": {
xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, "fixtures/public-key.gpg", NewPRMMatchRepository()),
},
},
},
})
require.NoError(t, err)
defer func() {
err := pc.Destroy()
require.NoError(t, err)
}()

// Test with requireSigned=false (default behavior)
// insecureAcceptAnything should be accepted
img := pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:insecureOnly")
res, err := pc.IsRunningImageAllowed(context.Background(), img)
assertRunningAllowed(t, res, err)

// Test with rejectInsecure=true
pc.RequireSignatureVerification(true)

// insecureAcceptAnything only: should be rejected
img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:insecureOnly")
res, err = pc.IsRunningImageAllowed(context.Background(), img)
assertRunningRejectedPolicyRequirement(t, res, err)

// insecureAcceptAnything + signed requirement: first requirement has no effect, second is secure and valid
img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:insecureWithOther")
res, err = pc.IsRunningImageAllowed(context.Background(), img)
assertRunningAllowed(t, res, err)

// signed requirement only: should work normally
img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:signedOnly")
res, err = pc.IsRunningImageAllowed(context.Background(), img)
assertRunningAllowed(t, res, err)

// Test with unsigned image and insecureAcceptAnything + signed requirement: first requirement has no effect, second is secure but rejects
img = pcImageMock(t, "fixtures/dir-img-unsigned", "testing/manifest:insecureWithOther")
res, err = pc.IsRunningImageAllowed(context.Background(), img)
assertRunningRejectedPolicyRequirement(t, res, err)
}
Loading