Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Signed-off-by: Ville Aikas <[email protected]>
  • Loading branch information
vaikas committed Jul 6, 2022
1 parent 27290bc commit 47463fc
Show file tree
Hide file tree
Showing 18 changed files with 755 additions and 29 deletions.
12 changes: 12 additions & 0 deletions config/300-clusterimagepolicy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,12 @@ spec:
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
type: string
static:
type: object
properties:
action:
description: Action defines how to handle a matching policy.
type: string
images:
type: array
items:
Expand Down Expand Up @@ -311,6 +317,12 @@ spec:
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
type: string
static:
type: object
properties:
action:
description: Action defines how to handle a matching policy.
type: string
images:
type: array
items:
Expand Down
8 changes: 8 additions & 0 deletions pkg/apis/policy/v1alpha1/clusterimagepolicy_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ func (authority *Authority) ConvertTo(ctx context.Context, sink *v1beta1.Authori
authority.Keyless.CACert.ConvertTo(ctx, sink.Keyless.CACert)
}
}
if authority.Static != nil {
sink.Static = &v1beta1.StaticRef{
Action: authority.Static.Action,
}
}
return nil
}

Expand Down Expand Up @@ -182,6 +187,9 @@ func (authority *Authority) ConvertFrom(ctx context.Context, source *v1beta1.Aut
authority.Keyless.CACert.ConvertFrom(ctx, source.Keyless.CACert)
}
}
if source.Static != nil {
authority.Static = &StaticRef{Action: source.Static.Action}
}
return nil
}

Expand Down
36 changes: 36 additions & 0 deletions pkg/apis/policy/v1alpha1/clusterimagepolicy_conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,24 @@ func TestConversionRoundTripV1alpha1(t *testing.T) {
},
},
},
}, {name: "key, keyless, and static, regexp",
in: &ClusterImagePolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cip",
},
Spec: ClusterImagePolicySpec{
Images: []ImagePattern{{Glob: "*"}},
Authorities: []Authority{
{Key: &KeyRef{
SecretRef: &v1.SecretReference{Name: "mysecret"}}},
{Keyless: &KeylessRef{
Identities: []Identity{{SubjectRegExp: "subjectregexp", IssuerRegExp: "issuerregexp"}},
CACert: &KeyRef{KMS: "kms", Data: "data", SecretRef: &v1.SecretReference{Name: "secret"}},
}},
{Static: &StaticRef{Action: "pass"}},
},
},
},
}, {name: "key and keyless, regexp",
in: &ClusterImagePolicy{
ObjectMeta: metav1.ObjectMeta{
Expand Down Expand Up @@ -127,6 +145,24 @@ func TestConversionRoundTripV1beta1(t *testing.T) {
Name: "test-cip",
},
},
}, {name: "key, keyless, and static, regexp",
in: &v1beta1.ClusterImagePolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cip",
},
Spec: v1beta1.ClusterImagePolicySpec{
Images: []v1beta1.ImagePattern{{Glob: "*"}},
Authorities: []v1beta1.Authority{
{Key: &v1beta1.KeyRef{
SecretRef: &v1.SecretReference{Name: "mysecret"}}},
{Keyless: &v1beta1.KeylessRef{
Identities: []v1beta1.Identity{{SubjectRegExp: "subjectregexp", IssuerRegExp: "issuerregexp"}},
CACert: &v1beta1.KeyRef{KMS: "kms", Data: "data", SecretRef: &v1.SecretReference{Name: "secret"}},
}},
{Static: &v1beta1.StaticRef{Action: "pass"}},
},
},
},
}}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Expand Down
9 changes: 9 additions & 0 deletions pkg/apis/policy/v1alpha1/clusterimagepolicy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ type Authority struct {
// +optional
Keyless *KeylessRef `json:"keyless,omitempty"`
// +optional
Static *StaticRef `json:"static,omitempty"`
// +optional
Sources []Source `json:"source,omitempty"`
// +optional
CTLog *TLog `json:"ctlog,omitempty"`
Expand All @@ -109,6 +111,13 @@ type KeyRef struct {
KMS string `json:"kms,omitempty"`
}

// StaticRef specifies that signatures / attestations are not validated but
// instead a static policy is applied against matching images.
type StaticRef struct {
// Action defines how to handle a matching policy.
Action string `json:"action"`
}

// Source specifies the location of the signature
type Source struct {
// +optional
Expand Down
54 changes: 41 additions & 13 deletions pkg/apis/policy/v1alpha1/clusterimagepolicy_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,15 @@ import (

const awsKMSPrefix = "awskms://"

// TODO: create constants in to cosign?
var validPredicateTypes = sets.NewString("custom", "slsaprovenance", "spdx", "spdxjson", "cyclonedx", "link", "vuln")
var (
// TODO: create constants in to cosign?
validPredicateTypes = sets.NewString("custom", "slsaprovenance", "spdx", "spdxjson", "cyclonedx", "link", "vuln")

// If a static matches, define the behaviour for it.
// TODO(vaikas): Consider adding a warn which would pass but use
// `warn` as return type for the webhook response.
validStaticRefTypes = sets.NewString("fail", "pass")
)

// Validate implements apis.Validatable
func (c *ClusterImagePolicy) Validate(ctx context.Context) *apis.FieldError {
Expand Down Expand Up @@ -66,11 +73,13 @@ func (image *ImagePattern) Validate(ctx context.Context) *apis.FieldError {

func (authority *Authority) Validate(ctx context.Context) *apis.FieldError {
var errs *apis.FieldError
if authority.Key == nil && authority.Keyless == nil {
errs = errs.Also(apis.ErrMissingOneOf("key", "keyless"))
if authority.Key == nil && authority.Keyless == nil && authority.Static == nil {
errs = errs.Also(apis.ErrMissingOneOf("key", "keyless", "static"))
}
if authority.Key != nil && authority.Keyless != nil {
errs = errs.Also(apis.ErrMultipleOneOf("key", "keyless"))
if (authority.Key != nil && authority.Keyless != nil) ||
(authority.Key != nil && authority.Static != nil) ||
(authority.Keyless != nil && authority.Static != nil) {
errs = errs.Also(apis.ErrMultipleOneOf("key", "keyless", "static"))
}

if authority.Key != nil {
Expand All @@ -79,6 +88,20 @@ func (authority *Authority) Validate(ctx context.Context) *apis.FieldError {
if authority.Keyless != nil {
errs = errs.Also(authority.Keyless.Validate(ctx).ViaField("keyless"))
}
if authority.Static != nil {
errs = errs.Also(authority.Static.Validate(ctx).ViaField("static"))
// Attestations, Sources, or CTLog do not make sense with static policy.
if len(authority.Attestations) > 0 {
errs = errs.Also(apis.ErrMultipleOneOf("static", "attestations"))
}
if len(authority.Sources) > 0 {
errs = errs.Also(apis.ErrMultipleOneOf("static", "source"))
}
if authority.CTLog != nil {
errs = errs.Also(apis.ErrMultipleOneOf("static", "ctlog"))
}

}

for i, source := range authority.Sources {
errs = errs.Also(source.Validate(ctx).ViaFieldIndex("source", i))
Expand All @@ -91,6 +114,17 @@ func (authority *Authority) Validate(ctx context.Context) *apis.FieldError {
return errs
}

func (s *StaticRef) Validate(ctx context.Context) *apis.FieldError {
var errs *apis.FieldError

if s.Action == "" {
errs = errs.Also(apis.ErrMissingField("action"))
} else if !validStaticRefTypes.Has(s.Action) {
errs = errs.Also(apis.ErrInvalidValue(s.Action, "action", "unsupported action"))
}
return errs
}

func (key *KeyRef) Validate(ctx context.Context) *apis.FieldError {
var errs *apis.FieldError

Expand Down Expand Up @@ -167,10 +201,6 @@ func (a *Attestation) Validate(ctx context.Context) *apis.FieldError {
if a.PredicateType == "" {
errs = errs.Also(apis.ErrMissingField("predicateType"))
} else if !validPredicateTypes.Has(a.PredicateType) {
// TODO(vaikas): The above should be using something like:
// if _, ok := options.PredicateTypeMap[a.PrecicateType]; !ok {
// But it causes an import loop. That refactor can be part of
// another PR.
errs = errs.Also(apis.ErrInvalidValue(a.PredicateType, "predicateType", "unsupported precicate type"))
}
errs = errs.Also(a.Policy.Validate(ctx).ViaField("policy"))
Expand Down Expand Up @@ -214,14 +244,12 @@ func (identity *Identity) Validate(ctx context.Context) *apis.FieldError {

// ValidateGlob glob compilation by testing against empty string
func ValidateGlob(g string) *apis.FieldError {
_, err := filepath.Match(g, "")
if err != nil {
if _, err := filepath.Match(g, ""); err != nil {
return apis.ErrInvalidValue(g, apis.CurrentField, fmt.Sprintf("glob is invalid: %v", err))
}
if _, err := glob.Compile(g); err != nil {
return apis.ErrInvalidValue(g, apis.CurrentField, fmt.Sprintf("glob is invalid: %v", err))
}

return nil
}

Expand Down
Loading

0 comments on commit 47463fc

Please sign in to comment.