From a4ddf31143f62b1ce80c3c4a64f5262c77729107 Mon Sep 17 00:00:00 2001 From: Rob Nester Date: Thu, 26 Feb 2026 15:24:16 -0500 Subject: [PATCH 1/2] fix ec validate input failing when policy has publicKey When a policy spec includes a publicKey field, ec validate input would fail with 'no check options or sig verifier configured' because the signature verifier isn't initialized for input validation scenarios. Return the publicKey from the policy spec directly when SigVerifier is not initialized. Ref: #1528 Ref: EC-1666 Co-authored-by: Claude Code Signed-off-by: Rob Nester --- .../happy_config_with_public_key.yaml | 7 ++++ features/__snapshots__/validate_input.snap | 32 +++++++++++++++++++ features/validate_input.feature | 24 ++++++++++++++ internal/policy/policy.go | 6 +++- internal/policy/policy_test.go | 8 +++-- 5 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 acceptance/examples/happy_config_with_public_key.yaml 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..f5bae504b 100644 --- a/internal/policy/policy.go +++ b/internal/policy/policy.go @@ -98,8 +98,12 @@ func (p *policy) PublicKeyPEM() ([]byte, error) { if p.Keyless() { return []byte{}, nil } + // If SigVerifier is not initialized but we have PublicKey in the policy spec, + // return it directly. This handles scenarios like "ec validate input" where + // signature verification is not performed but the policy spec may contain a + // publicKey field (fixes issue #1528). if p.checkOpts == nil || p.checkOpts.SigVerifier == nil { - return nil, errors.New("no check options or sig verifier configured") + return []byte(p.PublicKey), nil } pk, err := p.checkOpts.SigVerifier.PublicKey() if err != nil { diff --git a/internal/policy/policy_test.go b/internal/policy/policy_test.go index 51f6a7985..7b0841345 100644 --- a/internal/policy/policy_test.go +++ b/internal/policy/policy_test.go @@ -526,11 +526,13 @@ func TestPublicKeyPEM(t *testing.T) { expectedPublicKey: utils.TestPublicKey, }, { - name: "checkOpts is nil", + // When checkOpts is nil but publicKey is set in the policy spec, + // PublicKeyPEM returns the public key directly (fix for issue #1528) + name: "checkOpts is nil but publicKey in spec", 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, }, { name: "keyless", From c7cd11e51791c7ac089c571db224421f03245441 Mon Sep 17 00:00:00 2001 From: Rob Nester Date: Tue, 3 Mar 2026 16:29:45 -0500 Subject: [PATCH 2/2] Only return PEM from PublicKeyPEM when SigVerifier is nil When SigVerifier was not initialized, PublicKeyPEM() returned the policy's PublicKey verbatim, which could be non-PEM (e.g. a key ref). Validate with pem.Decode and return an error when not valid PEM so callers do not treat non-PEM data as PEM. Ref: #1528 Ref: EC-1666 Co-authored-by: Claude Code Signed-off-by: Rob Nester --- internal/policy/policy.go | 23 ++++++++++++++++++----- internal/policy/policy_test.go | 13 ++++++++++--- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/internal/policy/policy.go b/internal/policy/policy.go index f5bae504b..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,17 +93,23 @@ 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 } - // If SigVerifier is not initialized but we have PublicKey in the policy spec, - // return it directly. This handles scenarios like "ec validate input" where - // signature verification is not performed but the policy spec may contain a - // publicKey field (fixes issue #1528). + // 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 { + 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() @@ -112,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 7b0841345..915f512e6 100644 --- a/internal/policy/policy_test.go +++ b/internal/policy/policy_test.go @@ -526,14 +526,21 @@ func TestPublicKeyPEM(t *testing.T) { expectedPublicKey: utils.TestPublicKey, }, { - // When checkOpts is nil but publicKey is set in the policy spec, - // PublicKeyPEM returns the public key directly (fix for issue #1528) - name: "checkOpts is nil but publicKey in spec", + // 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.TestPublicKeyJSON)) }, 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", newPolicy: func(ctx context.Context) (Policy, error) {