Skip to content

Commit dc0574d

Browse files
authored
Merge pull request #3067 from simonbaird/default-key-for-signing-secret
Assume cosign.key as key field in signing secret, and support for passwords. Ref: https://issues.redhat.com/browse/EC-1589
2 parents cea245d + deed361 commit dc0574d

File tree

4 files changed

+92
-7
lines changed

4 files changed

+92
-7
lines changed

internal/utils/private_key.go

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ package utils
1818

1919
import (
2020
"context"
21+
"fmt"
22+
"os"
23+
"strings"
2124

2225
"github.com/spf13/afero"
2326
)
@@ -26,7 +29,43 @@ import (
2629
// This follows the same pattern as cosignSig.PublicKeyFromKeyRef but for private keys.
2730
// Supported formats:
2831
// - File path: "/path/to/private-key.pem"
32+
// - Kubernetes secret: "k8s://namespace/secret-name"
2933
// - Kubernetes secret: "k8s://namespace/secret-name/key-field"
3034
func PrivateKeyFromKeyRef(ctx context.Context, keyRef string, fs afero.Fs) ([]byte, error) {
31-
return KeyFromKeyRef(ctx, keyRef, fs)
35+
// If the key-field is not specified assume it is "cosign.key"
36+
adjustedKeyRef := keyRef
37+
if strings.HasPrefix(keyRef, "k8s://") {
38+
parts := strings.Split(strings.TrimPrefix(keyRef, "k8s://"), "/")
39+
if len(parts) == 2 {
40+
adjustedKeyRef = fmt.Sprintf("%s/cosign.key", keyRef)
41+
}
42+
}
43+
return KeyFromKeyRef(ctx, adjustedKeyRef, fs)
44+
}
45+
46+
// PasswordFromKeyRef resolves a password from either environment variable or a Kubernetes secret reference.
47+
// This provides a unified interface for password resolution similar to PrivateKeyFromKeyRef.
48+
// Supported formats:
49+
// - Environment variable: "" (empty string uses COSIGN_PASSWORD env var)
50+
// - Kubernetes secret: "k8s://namespace/secret-name" (assumes "cosign.password" key)
51+
// - Kubernetes secret: "k8s://namespace/secret-name/key-field" (explicit key field)
52+
func PasswordFromKeyRef(ctx context.Context, keyRef string) ([]byte, error) {
53+
// If keyRef is empty, use environment variable (backward compatibility)
54+
if keyRef == "" {
55+
return []byte(os.Getenv("COSIGN_PASSWORD")), nil
56+
}
57+
58+
// If it's a Kubernetes secret reference
59+
if strings.HasPrefix(keyRef, "k8s://") {
60+
// If the key-field is not specified assume it is "cosign.password"
61+
adjustedKeyRef := keyRef
62+
parts := strings.Split(strings.TrimPrefix(keyRef, "k8s://"), "/")
63+
if len(parts) == 2 {
64+
adjustedKeyRef = fmt.Sprintf("%s/cosign.password", keyRef)
65+
}
66+
return KeyFromKeyRef(ctx, adjustedKeyRef, nil) // fs not needed for k8s secrets
67+
}
68+
69+
// For any other format, treat it as environment variable name
70+
return []byte(os.Getenv(keyRef)), nil
3271
}

internal/utils/private_key_test.go

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,29 @@ func TestPrivateKeyFromKeyRef(t *testing.T) {
6363
expectErr: false,
6464
},
6565
{
66-
name: "k8s secret with multiple keys (no key field specified)",
66+
name: "k8s secret with multiple keys (no key field specified, defaults to cosign.key)",
6767
keyRef: "k8s://test-namespace/multi-key-secret",
6868
setup: func(fs afero.Fs, ctx context.Context) {
6969
// This will be handled in the test loop
7070
},
7171
expectErr: true,
72-
errMsg: "contains multiple keys, please specify the key field",
72+
errMsg: "key field \"cosign.key\" not found in secret",
73+
},
74+
{
75+
name: "k8s secret with default cosign.key field",
76+
keyRef: "k8s://test-namespace/cosign-key-secret",
77+
setup: func(fs afero.Fs, ctx context.Context) {
78+
// This will be handled in the test loop
79+
},
80+
expectErr: false,
81+
},
82+
{
83+
name: "k8s secret with cosign.key among multiple keys (defaults to cosign.key)",
84+
keyRef: "k8s://test-namespace/mixed-secret",
85+
setup: func(fs afero.Fs, ctx context.Context) {
86+
// This will be handled in the test loop
87+
},
88+
expectErr: false,
7389
},
7490
{
7591
name: "invalid k8s format",
@@ -127,6 +143,28 @@ func TestPrivateKeyFromKeyRef(t *testing.T) {
127143
"key2": []byte("key2 content"),
128144
},
129145
})
146+
} else if tt.keyRef == "k8s://test-namespace/cosign-key-secret" {
147+
secrets = append(secrets, &v1.Secret{
148+
ObjectMeta: metav1.ObjectMeta{
149+
Name: "cosign-key-secret",
150+
Namespace: "test-namespace",
151+
},
152+
Data: map[string][]byte{
153+
"cosign.key": []byte("default cosign key content"),
154+
},
155+
})
156+
} else if tt.keyRef == "k8s://test-namespace/mixed-secret" {
157+
secrets = append(secrets, &v1.Secret{
158+
ObjectMeta: metav1.ObjectMeta{
159+
Name: "mixed-secret",
160+
Namespace: "test-namespace",
161+
},
162+
Data: map[string][]byte{
163+
"cosign.key": []byte("mixed secret cosign key content"),
164+
"other-key": []byte("other key content"),
165+
"another-key": []byte("another key content"),
166+
},
167+
})
130168
}
131169

132170
if len(secrets) > 0 {
@@ -158,6 +196,10 @@ func TestPrivateKeyFromKeyRef(t *testing.T) {
158196
assert.Equal(t, []byte("single key content"), keyBytes)
159197
} else if tt.keyRef == "k8s://test-namespace/test-secret/private-key" {
160198
assert.Equal(t, []byte("test private key content"), keyBytes)
199+
} else if tt.keyRef == "k8s://test-namespace/cosign-key-secret" {
200+
assert.Equal(t, []byte("default cosign key content"), keyBytes)
201+
} else if tt.keyRef == "k8s://test-namespace/mixed-secret" {
202+
assert.Equal(t, []byte("mixed secret cosign key content"), keyBytes)
161203
}
162204
}
163205
})

internal/validate/vsa/attest.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"encoding/json"
2424
"fmt"
2525
"io"
26-
"os"
2726
"path/filepath"
2827
"strings"
2928

@@ -67,8 +66,12 @@ func NewSigner(ctx context.Context, keyRef string, fs afero.Fs) (*Signer, error)
6766
return nil, fmt.Errorf("resolve private key %q: %w", keyRef, err)
6867
}
6968

70-
// TODO maybe: Consider another env var for the key password
71-
signerVerifier, err := LoadPrivateKey(keyBytes, []byte(os.Getenv("COSIGN_PASSWORD")))
69+
password, err := utils.PasswordFromKeyRef(ctx, keyRef)
70+
if err != nil {
71+
return nil, fmt.Errorf("resolve private key password: %w", err)
72+
}
73+
74+
signerVerifier, err := LoadPrivateKey(keyBytes, password)
7275
if err != nil {
7376
return nil, fmt.Errorf("load private key: %w", err)
7477
}

internal/validate/vsa/attest_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,8 @@ func TestNewSigner_Comprehensive(t *testing.T) {
446446
Namespace: "test-namespace",
447447
},
448448
Data: map[string][]byte{
449-
"private-key": []byte("test private key content"),
449+
"private-key": []byte("test private key content"),
450+
"cosign.password": []byte("test password"),
450451
},
451452
})
452453
ctx = context.WithValue(ctx, utils.K8sClientKey, client)

0 commit comments

Comments
 (0)