Skip to content

Commit

Permalink
fix: fix sigstore#1930 for AWS KMS formats
Browse files Browse the repository at this point in the history
Signed-off-by: Ville Aikas <vaikas@chainguard.dev>
  • Loading branch information
vaikas committed Jun 2, 2022
1 parent ae90c74 commit 492ac63
Show file tree
Hide file tree
Showing 8 changed files with 276 additions and 0 deletions.
1 change: 1 addition & 0 deletions pkg/apis/policy/v1alpha1/clusterimagepolicy_types.go
Expand Up @@ -104,6 +104,7 @@ type KeyRef struct {
// +optional
Data string `json:"data,omitempty"`
// KMS contains the KMS url of the public key
// Supported formats differ based on the KMS system used.
// +optional
KMS string `json:"kms,omitempty"`
}
Expand Down
44 changes: 44 additions & 0 deletions pkg/apis/policy/v1alpha1/clusterimagepolicy_validation.go
Expand Up @@ -17,13 +17,18 @@ package v1alpha1
import (
"context"
"fmt"
"net"
"path/filepath"
"regexp"
"strings"

"github.com/aws/aws-sdk-go/aws/arn"
"github.com/sigstore/cosign/pkg/apis/utils"
"knative.dev/pkg/apis"
)

const awsKMSPrefix = "awskms://"

// Validate implements apis.Validatable
func (c *ClusterImagePolicy) Validate(ctx context.Context) *apis.FieldError {
return c.Spec.Validate(ctx).ViaField("spec")
Expand Down Expand Up @@ -54,6 +59,7 @@ func (image *ImagePattern) Validate(ctx context.Context) *apis.FieldError {
}

errs = errs.Also(ValidateGlob(image.Glob).ViaField("glob"))

return errs
}

Expand Down Expand Up @@ -104,6 +110,11 @@ func (key *KeyRef) Validate(ctx context.Context) *apis.FieldError {
} else if key.KMS != "" && key.SecretRef != nil {
errs = errs.Also(apis.ErrMultipleOneOf("data", "kms", "secretref"))
}
if key.KMS != "" {
if strings.HasPrefix(key.KMS, awsKMSPrefix) {
errs = errs.Also(validateAWSKMS(key.KMS).ViaField("kms"))
}
}
return errs
}

Expand Down Expand Up @@ -209,3 +220,36 @@ func ValidateRegex(regex string) *apis.FieldError {

return nil
}

// validateAWSKMS validates that the KMS conforms to AWS
// KMS format:
// awskms://$ENDPOINT/$KEYID
// Where:
// $ENDPOINT is optional
// $KEYID is either the key ARN or an alias ARN
// Reasoning for only supporting these formats is that other
// formats require additional configuration via ENV variables.
func validateAWSKMS(kms string) *apis.FieldError {
parts := strings.Split(kms, "/")
if len(parts) < 4 {
return apis.ErrInvalidValue(kms, apis.CurrentField, "malformed AWS KMS format, should be: 'awskms://$ENDPOINT/$KEYID'")
}
endpoint := parts[2]
// missing endpoint is fine, only validate if not empty
if endpoint != "" {
_, _, err := net.SplitHostPort(endpoint)
if err != nil {
return apis.ErrInvalidValue(kms, apis.CurrentField, fmt.Sprintf("malformed endpoint: %s", err))
}
}
keyID := parts[3]
arn, err := arn.Parse(keyID)
if err != nil {
return apis.ErrInvalidValue(kms, apis.CurrentField, fmt.Sprintf("failed to parse either key or alias arn: %s", err))
}
// Only support key or alias ARN.
if arn.Resource != "key" && arn.Resource != "alias" {
return apis.ErrInvalidValue(kms, apis.CurrentField, fmt.Sprintf("Got ARN: %+v Resource: %s", arn, arn.Resource))
}
return nil
}
63 changes: 63 additions & 0 deletions pkg/apis/policy/v1alpha1/clusterimagepolicy_validation_test.go
Expand Up @@ -710,3 +710,66 @@ func TestIdentitiesValidation(t *testing.T) {
})
}
}

func TestAWSKMSValidation(t *testing.T) {
tests := []struct {
name string
expectErr bool
errorString string
kms string
}{
{
name: "malformed, only 2 slashes ",
expectErr: true,
errorString: "invalid value: awskms://1234abcd-12ab-34cd-56ef-1234567890ab: kms\nmalformed AWS KMS format, should be: 'awskms://$ENDPOINT/$KEYID'",
kms: "awskms://1234abcd-12ab-34cd-56ef-1234567890ab",
},
{
name: "fails with invalid host",
expectErr: true,
errorString: "invalid value: awskms://localhost:::4566/alias/exampleAlias: kms\nmalformed endpoint: address localhost:::4566: too many colons in address",
kms: "awskms://localhost:::4566/alias/exampleAlias",
},
{
name: "fails with non-arn alias",
expectErr: true,
errorString: "invalid value: awskms://localhost:4566/alias/exampleAlias: kms\nfailed to parse either key or alias arn: arn: invalid prefix",
kms: "awskms://localhost:4566/alias/exampleAlias",
},
{
name: "Should fail when arn is invalid",
expectErr: true,
errorString: "invalid value: awskms://localhost:4566/arn:sonotvalid: kms\nfailed to parse either key or alias arn: arn: not enough sections",
kms: "awskms://localhost:4566/arn:sonotvalid",
},
{
name: "works with valid arn key and endpoint",
kms: "awskms://localhost:4566/arn:aws:kms:us-east-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab",
},
{
name: "works with valid arn key and no endpoint",
kms: "awskms:///arn:aws:kms:us-east-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab",
},
{
name: "works with valid arn alias and endpoint",
kms: "awskms://localhost:4566/arn:aws:kms:us-east-2:111122223333:alias/ExampleAlias",
},
{
name: "works with valid arn alias and no endpoint",
kms: "awskms:///arn:aws:kms:us-east-2:111122223333:alias/ExampleAlias",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
keyRef := KeyRef{KMS: test.kms}
err := keyRef.Validate(context.TODO())
if test.expectErr {
require.NotNil(t, err)
require.EqualError(t, err, test.errorString)
} else {
require.Nil(t, err)
}
})
}

}
1 change: 1 addition & 0 deletions pkg/apis/policy/v1beta1/clusterimagepolicy_types.go
Expand Up @@ -104,6 +104,7 @@ type KeyRef struct {
// +optional
Data string `json:"data,omitempty"`
// KMS contains the KMS url of the public key
// Supported formats differ based on the KMS system used.
// +optional
KMS string `json:"kms,omitempty"`
}
Expand Down
43 changes: 43 additions & 0 deletions pkg/apis/policy/v1beta1/clusterimagepolicy_validation.go
Expand Up @@ -17,13 +17,18 @@ package v1beta1
import (
"context"
"fmt"
"net"
"path/filepath"
"regexp"
"strings"

"github.com/aws/aws-sdk-go/aws/arn"
"github.com/sigstore/cosign/pkg/apis/utils"
"knative.dev/pkg/apis"
)

const awsKMSPrefix = "awskms://"

// Validate implements apis.Validatable
func (c *ClusterImagePolicy) Validate(ctx context.Context) *apis.FieldError {
return c.Spec.Validate(ctx).ViaField("spec")
Expand Down Expand Up @@ -105,6 +110,11 @@ func (key *KeyRef) Validate(ctx context.Context) *apis.FieldError {
} else if key.KMS != "" && key.SecretRef != nil {
errs = errs.Also(apis.ErrMultipleOneOf("data", "kms", "secretref"))
}
if key.KMS != "" {
if strings.HasPrefix(key.KMS, awsKMSPrefix) {
errs = errs.Also(validateAWSKMS(key.KMS).ViaField("kms"))
}
}
return errs
}

Expand Down Expand Up @@ -211,3 +221,36 @@ func ValidateRegex(regex string) *apis.FieldError {

return nil
}

// validateAWSKMS validates that the KMS conforms to AWS
// KMS format:
// awskms://$ENDPOINT/$KEYID
// Where:
// $ENDPOINT is optional
// $KEYID is either the key ARN or an alias ARN
// Reasoning for only supporting these formats is that other
// formats require additional configuration via ENV variables.
func validateAWSKMS(kms string) *apis.FieldError {
parts := strings.Split(kms, "/")
if len(parts) < 4 {
return apis.ErrInvalidValue(kms, apis.CurrentField, "malformed AWS KMS format, should be: 'awskms://$ENDPOINT/$KEYID'")
}
endpoint := parts[2]
// missing endpoint is fine, only validate if not empty
if endpoint != "" {
_, _, err := net.SplitHostPort(endpoint)
if err != nil {
return apis.ErrInvalidValue(kms, apis.CurrentField, fmt.Sprintf("malformed endpoint: %s", err))
}
}
keyID := parts[3]
arn, err := arn.Parse(keyID)
if err != nil {
return apis.ErrInvalidValue(kms, apis.CurrentField, fmt.Sprintf("failed to parse either key or alias arn: %s", err))
}
// Only support key or alias ARN.
if arn.Resource != "key" && arn.Resource != "alias" {
return apis.ErrInvalidValue(kms, apis.CurrentField, fmt.Sprintf("Got ARN: %+v Resource: %s", arn, arn.Resource))
}
return nil
}
62 changes: 62 additions & 0 deletions pkg/apis/policy/v1beta1/clusterimagepolicy_validation_test.go
Expand Up @@ -676,3 +676,65 @@ func TestIdentitiesValidation(t *testing.T) {
})
}
}

func TestAWSKMSValidation(t *testing.T) {
tests := []struct {
name string
expectErr bool
errorString string
kms string
}{
{
name: "malformed, only 2 slashes ",
expectErr: true,
errorString: "invalid value: awskms://1234abcd-12ab-34cd-56ef-1234567890ab: kms\nmalformed AWS KMS format, should be: 'awskms://$ENDPOINT/$KEYID'",
kms: "awskms://1234abcd-12ab-34cd-56ef-1234567890ab",
},
{
name: "fails with invalid host",
expectErr: true,
errorString: "invalid value: awskms://localhost:::4566/alias/exampleAlias: kms\nmalformed endpoint: address localhost:::4566: too many colons in address",
kms: "awskms://localhost:::4566/alias/exampleAlias",
},
{
name: "fails with non-arn alias",
expectErr: true,
errorString: "invalid value: awskms://localhost:4566/alias/exampleAlias: kms\nfailed to parse either key or alias arn: arn: invalid prefix",
kms: "awskms://localhost:4566/alias/exampleAlias",
},
{
name: "Should fail when arn is invalid",
expectErr: true,
errorString: "invalid value: awskms://localhost:4566/arn:sonotvalid: kms\nfailed to parse either key or alias arn: arn: not enough sections",
kms: "awskms://localhost:4566/arn:sonotvalid",
},
{
name: "works with valid arn key and endpoint",
kms: "awskms://localhost:4566/arn:aws:kms:us-east-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab",
},
{
name: "works with valid arn key and no endpoint",
kms: "awskms:///arn:aws:kms:us-east-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab",
},
{
name: "works with valid arn alias and endpoint",
kms: "awskms://localhost:4566/arn:aws:kms:us-east-2:111122223333:alias/ExampleAlias",
},
{
name: "works with valid arn alias and no endpoint",
kms: "awskms:///arn:aws:kms:us-east-2:111122223333:alias/ExampleAlias",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
keyRef := KeyRef{KMS: test.kms}
err := keyRef.Validate(context.TODO())
if test.expectErr {
require.NotNil(t, err)
require.EqualError(t, err, test.errorString)
} else {
require.Nil(t, err)
}
})
}
}
33 changes: 33 additions & 0 deletions test/testdata/policy-controller/invalid/invalid-keyref-awskms.yaml
@@ -0,0 +1,33 @@
# Copyright 2022 The Sigstore Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http:#www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
---
apiVersion: policy.sigstore.dev/v1alpha1
kind: ClusterImagePolicy
metadata:
name: image-policy
spec:
images:
- glob: images.*
- key:
# keyid is not supported
kms: "awskms:///1234abcd-12ab-34cd-56ef-1234567890ab"
- key:
# keyid with hostname is still not supported
kms: "awskms://localhost:4566/1234abcd-12ab-34cd-56ef-1234567890ab"
- key:
# alias is not supported
kms: "awskms:///alias/ExampleAlias"
= key:
# alias is not supported, even if you give a hostname
kms: "awskms://localhost:4566/alias/ExampleAlias"
29 changes: 29 additions & 0 deletions test/testdata/policy-controller/valid/valid-keyref-awskms.yaml
@@ -0,0 +1,29 @@
# Copyright 2022 The Sigstore Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http:#www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
---
apiVersion: policy.sigstore.dev/v1alpha1
kind: ClusterImagePolicy
metadata:
name: image-policy
spec:
images:
- glob: images.*
- key:
kms: "awskms:///arn:aws:kms:us-east-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
- key:
kms: "awskms://localhost:4566/arn:aws:kms:us-east-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
- key:
kms: "awskms:///arn:aws:kms:us-east-2:111122223333:alias/ExampleAlias"
= key:
kms: "awskms://localhost:4566/arn:aws:kms:us-east-2:111122223333:alias/ExampleAlias"

0 comments on commit 492ac63

Please sign in to comment.