Skip to content

Commit 9b2e103

Browse files
committed
Better vsa signing secrets with passwords support
In the PR for https://issues.redhat.com/browse/EC-1586 I began creating the signing secret using the same kind of GitOps job that Konflux uses for the Chains signing secret. As a side-effect of that, we now have a signing key with a password, which I think doesn't quite work as it should. Rather than create the secret without a password, let's improve things a little so we can use the password conveniently. Note: This would be simpler if we stopped supporting the signing secret in a file, with the COSIGN_PASSWORD, but I think maybe we wanna keep that since it might be useful in non-Konflux environments. Ref: https://issues.redhat.com/browse/EC-1589
1 parent ea7bd24 commit 9b2e103

File tree

3 files changed

+48
-4
lines changed

3 files changed

+48
-4
lines changed

internal/utils/private_key.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package utils
1919
import (
2020
"context"
2121
"fmt"
22+
"os"
2223
"strings"
2324

2425
"github.com/spf13/afero"
@@ -41,3 +42,30 @@ func PrivateKeyFromKeyRef(ctx context.Context, keyRef string, fs afero.Fs) ([]by
4142
}
4243
return KeyFromKeyRef(ctx, adjustedKeyRef, fs)
4344
}
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, passwordRef string) ([]byte, error) {
53+
// If passwordRef is empty, use environment variable (backward compatibility)
54+
if passwordRef == "" {
55+
return []byte(os.Getenv("COSIGN_PASSWORD")), nil
56+
}
57+
58+
// If it's a Kubernetes secret reference
59+
if strings.HasPrefix(passwordRef, "k8s://") {
60+
// If the key-field is not specified assume it is "cosign.password"
61+
adjustedPasswordRef := passwordRef
62+
parts := strings.Split(strings.TrimPrefix(passwordRef, "k8s://"), "/")
63+
if len(parts) == 2 {
64+
adjustedPasswordRef = fmt.Sprintf("%s/cosign.password", passwordRef)
65+
}
66+
return KeyFromKeyRef(ctx, adjustedPasswordRef, 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(passwordRef)), nil
71+
}

internal/validate/vsa/attest.go

Lines changed: 18 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,24 @@ 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+
// For Kubernetes secret keys, derive the password reference from the key reference
70+
var passwordRef string
71+
if strings.HasPrefix(keyRef, "k8s://") {
72+
// Convert k8s://namespace/secret-name/cosign.key to k8s://namespace/secret-name/cosign.password
73+
// or k8s://namespace/secret-name to k8s://namespace/secret-name/cosign.password
74+
parts := strings.Split(strings.TrimPrefix(keyRef, "k8s://"), "/")
75+
if len(parts) >= 2 {
76+
passwordRef = fmt.Sprintf("k8s://%s/%s/cosign.password", parts[0], parts[1])
77+
}
78+
}
79+
// If passwordRef is empty, PasswordFromKeyRef will fall back to COSIGN_PASSWORD env var
80+
81+
password, err := utils.PasswordFromKeyRef(ctx, passwordRef)
82+
if err != nil {
83+
return nil, fmt.Errorf("resolve private key password: %w", err)
84+
}
85+
86+
signerVerifier, err := LoadPrivateKey(keyBytes, password)
7287
if err != nil {
7388
return nil, fmt.Errorf("load private key: %w", err)
7489
}

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)