Skip to content

Commit de889ec

Browse files
authored
feat(signing): support SignServer authentication with client certificate (#1858)
Signed-off-by: Jose I. Paris <[email protected]>
1 parent 413a962 commit de889ec

File tree

5 files changed

+101
-33
lines changed

5 files changed

+101
-33
lines changed

app/cli/cmd/attestation_push.go

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@ import (
2828

2929
func newAttestationPushCmd() *cobra.Command {
3030
var (
31-
pkPath, bundle string
32-
annotationsFlag []string
33-
signServerCAPath string
34-
signServerAuthUser, signServerAuthPass string
35-
bypassPolicyCheck bool
31+
pkPath, bundle string
32+
annotationsFlag []string
33+
signServerCAPath string
34+
// Client certificate for SignServer auth
35+
signServerAuthCertPath string
36+
bypassPolicyCheck bool
3637
)
3738

3839
cmd := &cobra.Command{
@@ -66,7 +67,11 @@ func newAttestationPushCmd() *cobra.Command {
6667
a, err := action.NewAttestationPush(&action.AttestationPushOpts{
6768
ActionsOpts: actionOpts, KeyPath: pkPath, BundlePath: bundle,
6869
CLIVersion: info.Version, CLIDigest: info.Digest,
69-
SignServerCAPath: signServerCAPath, LocalStatePath: attestationLocalStatePath,
70+
LocalStatePath: attestationLocalStatePath,
71+
SignServerOpts: &action.SignServerOpts{
72+
CAPath: signServerCAPath,
73+
AuthClientCertPath: signServerAuthCertPath,
74+
},
7075
})
7176
if err != nil {
7277
return fmt.Errorf("failed to load action: %w", err)
@@ -124,9 +129,8 @@ func newAttestationPushCmd() *cobra.Command {
124129
cmd.Flags().StringVar(&bundle, "bundle", "", "output a Sigstore bundle to the provided path ")
125130
flagAttestationID(cmd)
126131

127-
cmd.Flags().StringVar(&signServerCAPath, "signserver-ca-path", "", "custom CA to be used for SignServer communications")
128-
cmd.Flags().StringVar(&signServerAuthUser, "signserver-auth-user", "", "")
129-
cmd.Flags().StringVar(&signServerAuthPass, "signserver-auth-pass", "", "")
132+
cmd.Flags().StringVar(&signServerCAPath, "signserver-ca-path", "", "custom CA to be used for SignServer TLS connection")
133+
cmd.Flags().StringVar(&signServerAuthCertPath, "signserver-client-cert", "", "path to client certificate in PEM format for authenticated SignServer TLS connection")
130134
cmd.Flags().BoolVar(&bypassPolicyCheck, exceptionFlagName, false, "do not fail this command on policy violations enforcement")
131135

132136
return cmd

app/cli/internal/action/attestation_push.go

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,16 @@ type AttestationPushOpts struct {
3636
*ActionsOpts
3737
KeyPath, CLIVersion, CLIDigest, BundlePath string
3838

39-
SignServerCAPath string
40-
LocalStatePath string
39+
LocalStatePath string
40+
SignServerOpts *SignServerOpts
41+
}
42+
43+
// SignServerOpts holds SignServer integration options
44+
type SignServerOpts struct {
45+
// CA certificate for TLS connection
46+
CAPath string
47+
// (optional) Client cert for mutual TLS authentication
48+
AuthClientCertPath string
4149
}
4250

4351
type AttestationResult struct {
@@ -49,22 +57,22 @@ type AttestationResult struct {
4957
type AttestationPush struct {
5058
*ActionsOpts
5159
keyPath, cliVersion, cliDigest, bundlePath string
52-
signServerCAPath string
5360
localStatePath string
61+
signServerOpts *SignServerOpts
5462
*newCrafterOpts
5563
}
5664

5765
func NewAttestationPush(cfg *AttestationPushOpts) (*AttestationPush, error) {
5866
opts := []crafter.NewOpt{crafter.WithLogger(&cfg.Logger)}
5967
return &AttestationPush{
60-
ActionsOpts: cfg.ActionsOpts,
61-
keyPath: cfg.KeyPath,
62-
cliVersion: cfg.CLIVersion,
63-
cliDigest: cfg.CLIDigest,
64-
bundlePath: cfg.BundlePath,
65-
signServerCAPath: cfg.SignServerCAPath,
66-
localStatePath: cfg.LocalStatePath,
67-
newCrafterOpts: &newCrafterOpts{cpConnection: cfg.CPConnection, opts: opts},
68+
ActionsOpts: cfg.ActionsOpts,
69+
keyPath: cfg.KeyPath,
70+
cliVersion: cfg.CLIVersion,
71+
cliDigest: cfg.CLIDigest,
72+
bundlePath: cfg.BundlePath,
73+
signServerOpts: cfg.SignServerOpts,
74+
localStatePath: cfg.LocalStatePath,
75+
newCrafterOpts: &newCrafterOpts{cpConnection: cfg.CPConnection, opts: opts},
6876
}, nil
6977
}
7078

@@ -148,10 +156,14 @@ func (action *AttestationPush) Run(ctx context.Context, attestationID string, ru
148156
crafter.CraftingState.Attestation.FinishedAt = timestamppb.New(time.Now())
149157
crafter.CraftingState.Attestation.BypassPolicyCheck = bypassPolicyCheck
150158

151-
sig, err := signer.GetSigner(action.keyPath, action.Logger, &signer.Opts{
152-
SignServerCAPath: action.signServerCAPath,
153-
Vaultclient: pb.NewSigningServiceClient(action.CPConnection),
154-
})
159+
signerOpts := &signer.Opts{Vaultclient: pb.NewSigningServiceClient(action.CPConnection)}
160+
if action.signServerOpts != nil {
161+
signerOpts.SignServerOpts = &signer.SignServerOpts{
162+
CAPath: action.signServerOpts.CAPath,
163+
AuthClientCertPath: action.signServerOpts.AuthClientCertPath,
164+
}
165+
}
166+
sig, err := signer.GetSigner(action.keyPath, action.Logger, signerOpts)
155167
if err != nil {
156168
return nil, fmt.Errorf("creating signer: %w", err)
157169
}

docs/docs/guides/signserver/signserver.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ INF push completed
7777
Attestation Digest: sha256:8b247c21e201e1bd1367add9ee8bfd12c5a0866add39225fda6240c0ef10a64e%
7878
```
7979

80+
### Using a TLS Client certificate for authentication
81+
If your SignServer signer worker has been configured for client certificate authentication, you can add the flag `--signserver-client-cert` to the `push` command:
82+
```shell
83+
➜ chainloop att push --key signserver://localhost:8443/PlainSigner --signserver-ca-path ../keyfactor/localhost-chain.pem --signserver-client-cert ../keyfactor/client.pem
84+
```
85+
8086
### Verifying the attestation
8187

8288
Verifying the attestation requires the signing cert and root CA (both provided by your organization out-of-band):

pkg/attestation/signer/signer.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,33 @@ import (
2828
)
2929

3030
type Opts struct {
31-
SignServerCAPath string
32-
Vaultclient pb.SigningServiceClient
31+
SignServerOpts *SignServerOpts
32+
Vaultclient pb.SigningServiceClient
33+
}
34+
35+
type SignServerOpts struct {
36+
// CA certificate for TLS connection
37+
CAPath string
38+
// (optional) Client cert for mutual TLS authentication
39+
AuthClientCertPath string
3340
}
3441

3542
// GetSigner creates a new Signer based on input parameters
3643
func GetSigner(keyPath string, logger zerolog.Logger, opts *Opts) (sigstoresigner.Signer, error) {
3744
var signer sigstoresigner.Signer
3845
if keyPath != "" {
3946
if strings.HasPrefix(keyPath, signserver.ReferenceScheme) {
47+
if opts.SignServerOpts == nil {
48+
// initialize empty opts (no custom CA, no client cert, no passphrase)
49+
opts.SignServerOpts = &SignServerOpts{}
50+
}
4051
host, worker, err := signserver.ParseKeyReference(keyPath)
4152
if err != nil {
4253
return nil, fmt.Errorf("failed to parse key: %w", err)
4354
}
44-
signer = signserver.NewSigner(host, worker, opts.SignServerCAPath)
55+
signer = signserver.NewSigner(host, worker,
56+
signserver.WithCAPath(opts.SignServerOpts.CAPath),
57+
signserver.WithClientCertPath(opts.SignServerOpts.AuthClientCertPath))
4558
} else {
4659
signer = cosign.NewSigner(keyPath, logger)
4760
}

pkg/attestation/signer/signserver/signserver.go

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,35 @@ const ReferenceScheme = "signserver"
3737

3838
// Signer implements a signer for SignServer
3939
type Signer struct {
40-
host, worker, caPath string
40+
host, worker, caPath, clientCertPath string
41+
}
42+
43+
type SignerOpt func(*Signer)
44+
45+
func WithCAPath(caPath string) SignerOpt {
46+
return func(s *Signer) {
47+
s.caPath = caPath
48+
}
49+
}
50+
51+
func WithClientCertPath(clientCertPath string) SignerOpt {
52+
return func(s *Signer) {
53+
s.clientCertPath = clientCertPath
54+
}
4155
}
4256

4357
var _ sigstoresigner.Signer = (*Signer)(nil)
4458

45-
func NewSigner(host, worker, caPath string) *Signer {
46-
return &Signer{
59+
func NewSigner(host, worker string, opts ...SignerOpt) *Signer {
60+
s := &Signer{
4761
host: host,
4862
worker: worker,
49-
caPath: caPath,
5063
}
64+
for _, opt := range opts {
65+
opt(s)
66+
}
67+
68+
return s
5169
}
5270

5371
func (s Signer) PublicKey(_ ...sigstoresigner.PublicKeyOption) (crypto.PublicKey, error) {
@@ -92,15 +110,30 @@ func (s Signer) SignMessage(message io.Reader, _ ...sigstoresigner.SignOption) (
92110
client := &http.Client{}
93111

94112
var caPool *x509.CertPool
113+
var tlsConfig *tls.Config
95114
if s.caPath != "" {
96115
caPool = x509.NewCertPool()
97116
caContents, err := os.ReadFile(s.caPath)
98117
if err != nil {
99118
return nil, fmt.Errorf("failed to read ca cert: %w", err)
100119
}
101120
caPool.AppendCertsFromPEM(caContents)
102-
client.Transport = &http.Transport{
103-
TLSClientConfig: &tls.Config{RootCAs: caPool, MinVersion: tls.VersionTLS12}}
121+
tlsConfig = &tls.Config{RootCAs: caPool, MinVersion: tls.VersionTLS12}
122+
}
123+
124+
if s.clientCertPath != "" {
125+
cert, err := tls.LoadX509KeyPair(s.clientCertPath, s.clientCertPath)
126+
if err != nil {
127+
return nil, fmt.Errorf("failed to load client cert and key: %w", err)
128+
}
129+
if tlsConfig == nil {
130+
tlsConfig = &tls.Config{MinVersion: tls.VersionTLS12}
131+
}
132+
tlsConfig.Certificates = []tls.Certificate{cert}
133+
}
134+
135+
if tlsConfig != nil {
136+
client.Transport = &http.Transport{TLSClientConfig: tlsConfig}
104137
}
105138

106139
res, err := client.Do(req)

0 commit comments

Comments
 (0)