diff --git a/acceptance/examples/happy_config_with_public_key.yaml b/acceptance/examples/happy_config_with_public_key.yaml new file mode 100644 index 000000000..7dad13d4f --- /dev/null +++ b/acceptance/examples/happy_config_with_public_key.yaml @@ -0,0 +1,7 @@ +--- +# Policy config that includes a publicKey - for testing issue #1528 +# ec validate input should work even when publicKey is specified +publicKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERhr8Zj4dZW67zucg8fDr11M4lmRp\nzN6SIcIjkvH39siYg1DkCoa2h2xMUZ10ecbM3/ECqvBV55YwQ2rcIEa7XQ==\n-----END PUBLIC KEY-----" +sources: + - policy: + - "git::https://${GITHOST}/git/happy-day-policy.git" diff --git a/features/__snapshots__/validate_input.snap b/features/__snapshots__/validate_input.snap index 55abc1eed..149c21ded 100755 --- a/features/__snapshots__/validate_input.snap +++ b/features/__snapshots__/validate_input.snap @@ -139,3 +139,35 @@ success: true [multiple data source top level key map merging:stderr - 1] --- + +[valid policy URL with publicKey in policy config:stdout - 1] +{ + "success": true, + "filepaths": [ + { + "filepath": "pipeline_definition.yaml", + "violations": [], + "warnings": [], + "successes": null, + "success": true, + "success-count": 1 + } + ], + "policy": { + "sources": [ + { + "policy": [ + "git::https://${GITHOST}/git/happy-day-policy.git" + ] + } + ], + "publicKey": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERhr8Zj4dZW67zucg8fDr11M4lmRp\nzN6SIcIjkvH39siYg1DkCoa2h2xMUZ10ecbM3/ECqvBV55YwQ2rcIEa7XQ==\n-----END PUBLIC KEY-----" + }, + "ec-version": "${EC_VERSION}", + "effective-time": "${TIMESTAMP}" +} +--- + +[valid policy URL with publicKey in policy config:stderr - 1] + +--- diff --git a/features/validate_input.feature b/features/validate_input.feature index 4312bef3b..7dd0bce91 100644 --- a/features/validate_input.feature +++ b/features/validate_input.feature @@ -27,6 +27,30 @@ Feature: validate input Then the exit status should be 0 Then the output should match the snapshot + # Test for issue #1528: ec validate input should work when policy has publicKey + Scenario: valid policy URL with publicKey in policy config + Given a git repository named "happy-day-config-with-public-key" with + | policy.yaml | examples/happy_config_with_public_key.yaml | + Given a git repository named "happy-day-policy" with + | main.rego | examples/happy_day.rego | + Given a pipeline definition file named "pipeline_definition.yaml" containing + """ + --- + apiVersion: tekton.dev/v1 + kind: Pipeline + metadata: + name: basic-build + spec: + tasks: + - name: appstudio-init + taskRef: + name: init + version: "0.1" + """ + When ec command is run with "validate input --file pipeline_definition.yaml --policy git::https://${GITHOST}/git/happy-day-config-with-public-key.git --output json" + Then the exit status should be 0 + Then the output should match the snapshot + Scenario: valid policy URL with text output Given a git repository named "happy-day-config" with | policy.yaml | examples/happy_config.yaml | diff --git a/internal/policy/policy.go b/internal/policy/policy.go index d21cd8e53..37632e6e2 100644 --- a/internal/policy/policy.go +++ b/internal/policy/policy.go @@ -20,6 +20,7 @@ import ( "context" "crypto" _ "embed" + "encoding/pem" "errors" "fmt" "os" @@ -92,14 +93,24 @@ type policy struct { skipImageSigCheck bool } -// PublicKeyPEM returns the PublicKey in PEM format. +// PublicKeyPEM returns the PublicKey in PEM format. When SigVerifier is not +// initialized, the policy's PublicKey is returned only if it is already valid +// PEM; otherwise an error is returned so callers do not treat non-PEM data as PEM. func (p *policy) PublicKeyPEM() ([]byte, error) { // Public key is not involved when using keyless verification if p.Keyless() { return []byte{}, nil } + // When SigVerifier is not initialized, return the policy's PublicKey only if + // it is valid PEM (e.g. "ec validate input" with publicKey in policy.yaml). if p.checkOpts == nil || p.checkOpts.SigVerifier == nil { - return nil, errors.New("no check options or sig verifier configured") + if p.PublicKey == "" { + return []byte{}, nil + } + if !isPEM([]byte(p.PublicKey)) { + return nil, errors.New("public key is not in PEM format and signature verifier is not initialized") + } + return []byte(p.PublicKey), nil } pk, err := p.checkOpts.SigVerifier.PublicKey() if err != nil { @@ -108,6 +119,12 @@ func (p *policy) PublicKeyPEM() ([]byte, error) { return cryptoutils.MarshalPublicKeyToPEM(pk) } +// isPEM reports whether data contains at least one valid PEM block. +func isPEM(data []byte) bool { + block, _ := pem.Decode(data) + return block != nil +} + func (p *policy) CheckOpts() (*cosign.CheckOpts, error) { if p.checkOpts == nil { return nil, errors.New("no check options configured") diff --git a/internal/policy/policy_test.go b/internal/policy/policy_test.go index 51f6a7985..915f512e6 100644 --- a/internal/policy/policy_test.go +++ b/internal/policy/policy_test.go @@ -526,11 +526,20 @@ func TestPublicKeyPEM(t *testing.T) { expectedPublicKey: utils.TestPublicKey, }, { - name: "checkOpts is nil", + // When checkOpts is nil but publicKey in spec is valid PEM, return it (e.g. "ec validate input"). + name: "checkOpts is nil but publicKey in spec is PEM", newPolicy: func(ctx context.Context) (Policy, error) { - return NewInertPolicy(ctx, fmt.Sprintf(`{"publicKey": "%s"}`, utils.TestPublicKey)) + return NewInertPolicy(ctx, fmt.Sprintf(`{"publicKey": %s}`, utils.TestPublicKeyJSON)) }, - err: "no check options or sig verifier configured", + expectedPublicKey: utils.TestPublicKey, + }, + { + // When checkOpts is nil and publicKey is not PEM, return error to uphold method contract. + name: "checkOpts is nil and publicKey in spec is not PEM", + newPolicy: func(ctx context.Context) (Policy, error) { + return NewInertPolicy(ctx, `{"publicKey": "not-pem-key-ref-or-path"}`) + }, + err: "public key is not in PEM format and signature verifier is not initialized", }, { name: "keyless",