Skip to content

Commit

Permalink
feat(signing): support SignServer authentication with client certific…
Browse files Browse the repository at this point in the history
…ate (chainloop-dev#1858)

Signed-off-by: Jose I. Paris <[email protected]>
  • Loading branch information
jiparis authored Feb 26, 2025
1 parent 413a962 commit de889ec
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 33 deletions.
22 changes: 13 additions & 9 deletions app/cli/cmd/attestation_push.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ import (

func newAttestationPushCmd() *cobra.Command {
var (
pkPath, bundle string
annotationsFlag []string
signServerCAPath string
signServerAuthUser, signServerAuthPass string
bypassPolicyCheck bool
pkPath, bundle string
annotationsFlag []string
signServerCAPath string
// Client certificate for SignServer auth
signServerAuthCertPath string
bypassPolicyCheck bool
)

cmd := &cobra.Command{
Expand Down Expand Up @@ -66,7 +67,11 @@ func newAttestationPushCmd() *cobra.Command {
a, err := action.NewAttestationPush(&action.AttestationPushOpts{
ActionsOpts: actionOpts, KeyPath: pkPath, BundlePath: bundle,
CLIVersion: info.Version, CLIDigest: info.Digest,
SignServerCAPath: signServerCAPath, LocalStatePath: attestationLocalStatePath,
LocalStatePath: attestationLocalStatePath,
SignServerOpts: &action.SignServerOpts{
CAPath: signServerCAPath,
AuthClientCertPath: signServerAuthCertPath,
},
})
if err != nil {
return fmt.Errorf("failed to load action: %w", err)
Expand Down Expand Up @@ -124,9 +129,8 @@ func newAttestationPushCmd() *cobra.Command {
cmd.Flags().StringVar(&bundle, "bundle", "", "output a Sigstore bundle to the provided path ")
flagAttestationID(cmd)

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

return cmd
Expand Down
42 changes: 27 additions & 15 deletions app/cli/internal/action/attestation_push.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,16 @@ type AttestationPushOpts struct {
*ActionsOpts
KeyPath, CLIVersion, CLIDigest, BundlePath string

SignServerCAPath string
LocalStatePath string
LocalStatePath string
SignServerOpts *SignServerOpts
}

// SignServerOpts holds SignServer integration options
type SignServerOpts struct {
// CA certificate for TLS connection
CAPath string
// (optional) Client cert for mutual TLS authentication
AuthClientCertPath string
}

type AttestationResult struct {
Expand All @@ -49,22 +57,22 @@ type AttestationResult struct {
type AttestationPush struct {
*ActionsOpts
keyPath, cliVersion, cliDigest, bundlePath string
signServerCAPath string
localStatePath string
signServerOpts *SignServerOpts
*newCrafterOpts
}

func NewAttestationPush(cfg *AttestationPushOpts) (*AttestationPush, error) {
opts := []crafter.NewOpt{crafter.WithLogger(&cfg.Logger)}
return &AttestationPush{
ActionsOpts: cfg.ActionsOpts,
keyPath: cfg.KeyPath,
cliVersion: cfg.CLIVersion,
cliDigest: cfg.CLIDigest,
bundlePath: cfg.BundlePath,
signServerCAPath: cfg.SignServerCAPath,
localStatePath: cfg.LocalStatePath,
newCrafterOpts: &newCrafterOpts{cpConnection: cfg.CPConnection, opts: opts},
ActionsOpts: cfg.ActionsOpts,
keyPath: cfg.KeyPath,
cliVersion: cfg.CLIVersion,
cliDigest: cfg.CLIDigest,
bundlePath: cfg.BundlePath,
signServerOpts: cfg.SignServerOpts,
localStatePath: cfg.LocalStatePath,
newCrafterOpts: &newCrafterOpts{cpConnection: cfg.CPConnection, opts: opts},
}, nil
}

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

sig, err := signer.GetSigner(action.keyPath, action.Logger, &signer.Opts{
SignServerCAPath: action.signServerCAPath,
Vaultclient: pb.NewSigningServiceClient(action.CPConnection),
})
signerOpts := &signer.Opts{Vaultclient: pb.NewSigningServiceClient(action.CPConnection)}
if action.signServerOpts != nil {
signerOpts.SignServerOpts = &signer.SignServerOpts{
CAPath: action.signServerOpts.CAPath,
AuthClientCertPath: action.signServerOpts.AuthClientCertPath,
}
}
sig, err := signer.GetSigner(action.keyPath, action.Logger, signerOpts)
if err != nil {
return nil, fmt.Errorf("creating signer: %w", err)
}
Expand Down
6 changes: 6 additions & 0 deletions docs/docs/guides/signserver/signserver.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ INF push completed
Attestation Digest: sha256:8b247c21e201e1bd1367add9ee8bfd12c5a0866add39225fda6240c0ef10a64e%
```

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

### Verifying the attestation

Verifying the attestation requires the signing cert and root CA (both provided by your organization out-of-band):
Expand Down
19 changes: 16 additions & 3 deletions pkg/attestation/signer/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,33 @@ import (
)

type Opts struct {
SignServerCAPath string
Vaultclient pb.SigningServiceClient
SignServerOpts *SignServerOpts
Vaultclient pb.SigningServiceClient
}

type SignServerOpts struct {
// CA certificate for TLS connection
CAPath string
// (optional) Client cert for mutual TLS authentication
AuthClientCertPath string
}

// GetSigner creates a new Signer based on input parameters
func GetSigner(keyPath string, logger zerolog.Logger, opts *Opts) (sigstoresigner.Signer, error) {
var signer sigstoresigner.Signer
if keyPath != "" {
if strings.HasPrefix(keyPath, signserver.ReferenceScheme) {
if opts.SignServerOpts == nil {
// initialize empty opts (no custom CA, no client cert, no passphrase)
opts.SignServerOpts = &SignServerOpts{}
}
host, worker, err := signserver.ParseKeyReference(keyPath)
if err != nil {
return nil, fmt.Errorf("failed to parse key: %w", err)
}
signer = signserver.NewSigner(host, worker, opts.SignServerCAPath)
signer = signserver.NewSigner(host, worker,
signserver.WithCAPath(opts.SignServerOpts.CAPath),
signserver.WithClientCertPath(opts.SignServerOpts.AuthClientCertPath))
} else {
signer = cosign.NewSigner(keyPath, logger)
}
Expand Down
45 changes: 39 additions & 6 deletions pkg/attestation/signer/signserver/signserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,35 @@ const ReferenceScheme = "signserver"

// Signer implements a signer for SignServer
type Signer struct {
host, worker, caPath string
host, worker, caPath, clientCertPath string
}

type SignerOpt func(*Signer)

func WithCAPath(caPath string) SignerOpt {
return func(s *Signer) {
s.caPath = caPath
}
}

func WithClientCertPath(clientCertPath string) SignerOpt {
return func(s *Signer) {
s.clientCertPath = clientCertPath
}
}

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

func NewSigner(host, worker, caPath string) *Signer {
return &Signer{
func NewSigner(host, worker string, opts ...SignerOpt) *Signer {
s := &Signer{
host: host,
worker: worker,
caPath: caPath,
}
for _, opt := range opts {
opt(s)
}

return s
}

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

var caPool *x509.CertPool
var tlsConfig *tls.Config
if s.caPath != "" {
caPool = x509.NewCertPool()
caContents, err := os.ReadFile(s.caPath)
if err != nil {
return nil, fmt.Errorf("failed to read ca cert: %w", err)
}
caPool.AppendCertsFromPEM(caContents)
client.Transport = &http.Transport{
TLSClientConfig: &tls.Config{RootCAs: caPool, MinVersion: tls.VersionTLS12}}
tlsConfig = &tls.Config{RootCAs: caPool, MinVersion: tls.VersionTLS12}
}

if s.clientCertPath != "" {
cert, err := tls.LoadX509KeyPair(s.clientCertPath, s.clientCertPath)
if err != nil {
return nil, fmt.Errorf("failed to load client cert and key: %w", err)
}
if tlsConfig == nil {
tlsConfig = &tls.Config{MinVersion: tls.VersionTLS12}
}
tlsConfig.Certificates = []tls.Certificate{cert}
}

if tlsConfig != nil {
client.Transport = &http.Transport{TLSClientConfig: tlsConfig}
}

res, err := client.Do(req)
Expand Down

0 comments on commit de889ec

Please sign in to comment.