diff --git a/pkg/cosign/kubernetes/webhook/validator.go b/pkg/cosign/kubernetes/webhook/validator.go index e9578ba4aa5..6e2590eb0d4 100644 --- a/pkg/cosign/kubernetes/webhook/validator.go +++ b/pkg/cosign/kubernetes/webhook/validator.go @@ -287,19 +287,25 @@ func validatePolicies(ctx context.Context, ref name.Reference, policies map[stri ret := map[string][]error{} for i := 0; i < len(policies); i++ { - result, ok := <-results - if !ok { - ret["internalerror"] = append(ret["internalerror"], fmt.Errorf("results channel failed to produce a result")) - } - switch { - case len(result.errors) > 0: - ret[result.name] = append(ret[result.name], result.errors...) - case len(result.policyResult.AuthorityMatches) > 0: - policyResults[result.name] = result.policyResult - default: - ret[result.name] = append(ret[result.name], fmt.Errorf("failed to process policy: %s", result.name)) + select { + case <-ctx.Done(): + ret["internalerror"] = append(ret["internalerror"], fmt.Errorf("context was canceled before validation completed")) + case result, ok := <-results: + if !ok { + ret["internalerror"] = append(ret["internalerror"], fmt.Errorf("results channel failed to produce a result")) + continue + } + switch { + case len(result.errors) > 0: + ret[result.name] = append(ret[result.name], result.errors...) + case len(result.policyResult.AuthorityMatches) > 0: + policyResults[result.name] = result.policyResult + default: + ret[result.name] = append(ret[result.name], fmt.Errorf("failed to process policy: %s", result.name)) + } } } + return policyResults, ret } @@ -355,19 +361,24 @@ func ValidatePolicy(ctx context.Context, ref name.Reference, cip webhookcip.Clus // return it. policyResult := &PolicyResult{AuthorityMatches: make(map[string]AuthorityMatch)} for i := 0; i < len(cip.Authorities); i++ { - result, ok := <-results - if !ok { - authorityErrors = append(authorityErrors, fmt.Errorf("results channel failed to produce a result")) - } - switch { - case result.err != nil: - authorityErrors = append(authorityErrors, result.err) - case len(result.signatures) > 0: - policyResult.AuthorityMatches[result.name] = AuthorityMatch{Signatures: result.signatures} - case len(result.attestations) > 0: - policyResult.AuthorityMatches[result.name] = AuthorityMatch{Attestations: result.attestations} - default: - authorityErrors = append(authorityErrors, fmt.Errorf("failed to process authority: %s", result.name)) + select { + case <-ctx.Done(): + authorityErrors = append(authorityErrors, fmt.Errorf("context was canceled before validation completed")) + case result, ok := <-results: + if !ok { + authorityErrors = append(authorityErrors, fmt.Errorf("results channel failed to produce a result")) + continue + } + switch { + case result.err != nil: + authorityErrors = append(authorityErrors, result.err) + case len(result.signatures) > 0: + policyResult.AuthorityMatches[result.name] = AuthorityMatch{Signatures: result.signatures} + case len(result.attestations) > 0: + policyResult.AuthorityMatches[result.name] = AuthorityMatch{Attestations: result.attestations} + default: + authorityErrors = append(authorityErrors, fmt.Errorf("failed to process authority: %s", result.name)) + } } } if len(authorityErrors) > 0 { diff --git a/pkg/cosign/kubernetes/webhook/validator_test.go b/pkg/cosign/kubernetes/webhook/validator_test.go index 79593ef39cd..fe9b996da86 100644 --- a/pkg/cosign/kubernetes/webhook/validator_test.go +++ b/pkg/cosign/kubernetes/webhook/validator_test.go @@ -56,6 +56,12 @@ import ( const ( fulcioRootCert = "-----BEGIN CERTIFICATE-----\nMIICNzCCAd2gAwIBAgITPLBoBQhl1hqFND9S+SGWbfzaRTAKBggqhkjOPQQDAjBo\nMQswCQYDVQQGEwJVSzESMBAGA1UECBMJV2lsdHNoaXJlMRMwEQYDVQQHEwpDaGlw\ncGVuaGFtMQ8wDQYDVQQKEwZSZWRIYXQxDDAKBgNVBAsTA0NUTzERMA8GA1UEAxMI\ndGVzdGNlcnQwHhcNMjEwMzEyMjMyNDQ5WhcNMzEwMjI4MjMyNDQ5WjBoMQswCQYD\nVQQGEwJVSzESMBAGA1UECBMJV2lsdHNoaXJlMRMwEQYDVQQHEwpDaGlwcGVuaGFt\nMQ8wDQYDVQQKEwZSZWRIYXQxDDAKBgNVBAsTA0NUTzERMA8GA1UEAxMIdGVzdGNl\ncnQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQRn+Alyof6xP3GQClSwgV0NFuY\nYEwmKP/WLWr/LwB6LUYzt5v49RlqG83KuaJSpeOj7G7MVABdpIZYWwqAiZV3o2Yw\nZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQU\nT8Jwm6JuVb0dsiuHUROiHOOVHVkwHwYDVR0jBBgwFoAUT8Jwm6JuVb0dsiuHUROi\nHOOVHVkwCgYIKoZIzj0EAwIDSAAwRQIhAJkNZmP6sKA+8EebRXFkBa9DPjacBpTc\nOljJotvKidRhAiAuNrIazKEw2G4dw8x1z6EYk9G+7fJP5m93bjm/JfMBtA==\n-----END CERTIFICATE-----" rekorResponse = "bad response" + + // Random public key (cosign generate-key-pair) 2022-03-18 + authorityKeyCosignPubString = `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENAyijLvRu5QpCPp2uOj8C79ZW1VJ +SID/4H61ZiRzN4nqONzp+ZF22qQTk3MFO3D0/ZKmWHAosIf2pf2GHH7myA== +-----END PUBLIC KEY-----` ) func TestValidatePodSpec(t *testing.T) { @@ -91,11 +97,6 @@ func TestValidatePodSpec(t *testing.T) { } var authorityKeyCosignPub *ecdsa.PublicKey - // Random public key (cosign generate-key-pair) 2022-03-18 - authorityKeyCosignPubString := `-----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENAyijLvRu5QpCPp2uOj8C79ZW1VJ -SID/4H61ZiRzN4nqONzp+ZF22qQTk3MFO3D0/ZKmWHAosIf2pf2GHH7myA== ------END PUBLIC KEY-----` pems := parsePems([]byte(authorityKeyCosignPubString)) if len(pems) > 0 { @@ -1175,11 +1176,6 @@ func TestValidatePolicy(t *testing.T) { } t.Logf("rekorURL: %s", rekorURL.String()) var authorityKeyCosignPub *ecdsa.PublicKey - // Random public key (cosign generate-key-pair) 2022-03-18 - authorityKeyCosignPubString := `-----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENAyijLvRu5QpCPp2uOj8C79ZW1VJ -SID/4H61ZiRzN4nqONzp+ZF22qQTk3MFO3D0/ZKmWHAosIf2pf2GHH7myA== ------END PUBLIC KEY-----` pems := parsePems([]byte(authorityKeyCosignPubString)) if len(pems) > 0 { @@ -1350,3 +1346,57 @@ func validateErrors(t *testing.T, wantErr []string, got []error) { } } } + +func TestValidatePolicyCancelled(t *testing.T) { + var authorityKeyCosignPub *ecdsa.PublicKey + pems := parsePems([]byte(authorityKeyCosignPubString)) + if len(pems) > 0 { + key, _ := x509.ParsePKIXPublicKey(pems[0].Bytes) + authorityKeyCosignPub = key.(*ecdsa.PublicKey) + } else { + t.Errorf("Error parsing authority key from string") + } + // Resolved via crane digest on 2021/09/25 + digest := name.MustParseReference("gcr.io/distroless/static:nonroot@sha256:be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4") + + testContext, cancelFunc := context.WithCancel(context.Background()) + cip := webhookcip.ClusterImagePolicy{ + Authorities: []webhookcip.Authority{{ + Name: "authority-0", + Key: &webhookcip.KeyRef{ + PublicKeys: []crypto.PublicKey{authorityKeyCosignPub}, + }, + }}, + } + wantErrs := []string{"context was canceled before validation completed"} + cancelFunc() + _, gotErrs := ValidatePolicy(testContext, digest, cip) + validateErrors(t, wantErrs, gotErrs) +} + +func TestValidatePoliciesCancelled(t *testing.T) { + var authorityKeyCosignPub *ecdsa.PublicKey + pems := parsePems([]byte(authorityKeyCosignPubString)) + if len(pems) > 0 { + key, _ := x509.ParsePKIXPublicKey(pems[0].Bytes) + authorityKeyCosignPub = key.(*ecdsa.PublicKey) + } else { + t.Errorf("Error parsing authority key from string") + } + // Resolved via crane digest on 2021/09/25 + digest := name.MustParseReference("gcr.io/distroless/static:nonroot@sha256:be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4") + + testContext, cancelFunc := context.WithCancel(context.Background()) + cip := webhookcip.ClusterImagePolicy{ + Authorities: []webhookcip.Authority{{ + Name: "authority-0", + Key: &webhookcip.KeyRef{ + PublicKeys: []crypto.PublicKey{authorityKeyCosignPub}, + }, + }}, + } + wantErrs := []string{"context was canceled before validation completed"} + cancelFunc() + _, gotErrs := validatePolicies(testContext, digest, map[string]webhookcip.ClusterImagePolicy{"testcip": cip}) + validateErrors(t, wantErrs, gotErrs["internalerror"]) +}