Skip to content

Commit

Permalink
KMS Support (#376)
Browse files Browse the repository at this point in the history
* Add support for KMS signing interface
* Add support for AWS KMS
* Add support for GCP KMS
* Add ability to sign attestations with KMS
* Add ability to sign policies with KMS

---------
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
Signed-off-by: John Kjell <john@testifysec.com>
Signed-off-by: Tom Meadows <tom@tmlabs.co.uk>
  • Loading branch information
ChaosInTheCRD committed Feb 16, 2024
1 parent be37eee commit c27a4f5
Show file tree
Hide file tree
Showing 20 changed files with 608 additions and 193 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/verify-docgen.yml
Expand Up @@ -16,5 +16,5 @@ jobs:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
with:
go-version: '1.19.x'
go-version: "1.21.x"
- run: ./docgen/verify.sh
30 changes: 15 additions & 15 deletions .github/workflows/verify-licence.yml
Expand Up @@ -2,23 +2,23 @@ name: Verify License
on:
workflow_dispatch:
push:
branches: ['main', 'release-*']
branches: ["main", "release-*"]
pull_request:
permissions:
contents: read

jobs:
license-check:
name: license boilerplate check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
with:
go-version: '1.18.x'
- name: Install addlicense
run: go install github.com/google/addlicense@v1.1.1
- name: Check license headers
run: |
set -e
addlicense --check -l apache -c 'The Witness Contributors' -v *
license-check:
name: license boilerplate check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
with:
go-version: "1.21.x"
- name: Install addlicense
run: go install github.com/google/addlicense@v1.1.1
- name: Check license headers
run: |
set -e
addlicense --check -l apache -c 'The Witness Contributors' -v *
79 changes: 72 additions & 7 deletions cmd/keyloader.go
Expand Up @@ -22,15 +22,16 @@ import (
"github.com/in-toto/go-witness/cryptoutil"
"github.com/in-toto/go-witness/log"
"github.com/in-toto/go-witness/signer"
"github.com/in-toto/go-witness/signer/kms"
"github.com/in-toto/witness/options"
"github.com/spf13/pflag"
)

// signerProvidersFromFlags looks at all flags that were set by the user to determine which signer providers we should use
func signerProvidersFromFlags(flags *pflag.FlagSet) map[string]struct{} {
signerProviders := make(map[string]struct{})
// providersFromFlags looks at all flags that were set by the user to determine which providers we should use
func providersFromFlags(prefix string, flags *pflag.FlagSet) map[string]struct{} {
providers := make(map[string]struct{})
flags.Visit(func(flag *pflag.Flag) {
if !strings.HasPrefix(flag.Name, "signer-") {
if !strings.HasPrefix(flag.Name, fmt.Sprintf("%s-", prefix)) {
return
}

Expand All @@ -39,14 +40,14 @@ func signerProvidersFromFlags(flags *pflag.FlagSet) map[string]struct{} {
return
}

signerProviders[parts[1]] = struct{}{}
providers[parts[1]] = struct{}{}
})

return signerProviders
return providers
}

// loadSigners loads all signers that appear in the signerProviders set and creates their respective signers, using any options provided in so
func loadSigners(ctx context.Context, so options.SignerOptions, signerProviders map[string]struct{}) ([]cryptoutil.Signer, error) {
func loadSigners(ctx context.Context, so options.SignerOptions, ko options.KMSSignerProviderOptions, signerProviders map[string]struct{}) ([]cryptoutil.Signer, error) {
signers := make([]cryptoutil.Signer, 0)
for signerProvider := range signerProviders {
setters := so[signerProvider]
Expand All @@ -56,6 +57,18 @@ func loadSigners(ctx context.Context, so options.SignerOptions, signerProviders
continue
}

// NOTE: We want to initialze the KMS provider specific options if a KMS signer has been invoked
if ksp, ok := sp.(*kms.KMSSignerProvider); ok {
for _, opt := range ksp.Options {
for _, setter := range ko[opt.ProviderName()] {
sp, err = setter(ksp)
if err != nil {
continue
}
}
}
}

s, err := sp.Signer(ctx)
if err != nil {
log.Errorf("failed to create %v signer: %w", signerProvider, err)
Expand All @@ -71,3 +84,55 @@ func loadSigners(ctx context.Context, so options.SignerOptions, signerProviders

return signers, nil
}

// NOTE: This is a temporary implementation until we have a SignerVerifier interface
// loadVerifiers loads all verifiers that appear in the verifierProviders set and creates their respective verifiers, using any options provided in so
func loadVerifiers(ctx context.Context, so options.VerifierOptions, ko options.KMSVerifierProviderOptions, verifierProviders map[string]struct{}) ([]cryptoutil.Verifier, error) {
verifiers := make([]cryptoutil.Verifier, 0)
for verifierProvider := range verifierProviders {
setters := so[verifierProvider]
sp, err := signer.NewVerifierProvider(verifierProvider, setters...)
if err != nil {
log.Errorf("failed to create %v verifier provider: %w", verifierProvider, err)
continue
}

// NOTE: We want to initialze the KMS provider specific options if a KMS signer has been invoked
if ksp, ok := sp.(*kms.KMSSignerProvider); ok {
for _, opt := range ksp.Options {
pn := opt.ProviderName()
for _, setter := range ko[pn] {
vp, err := setter(ksp)
if err != nil {
continue
}

// NOTE: KMS SignerProvider can also be a VerifierProvider. This is a nasty hack to cast things back in a way that we can add to the loaded verifiers.
// This must be refactored.
kspv, ok := vp.(*kms.KMSSignerProvider)
if !ok {
return nil, fmt.Errorf("provided verifier provider is not a KMS verifier provider")
}

s, err := kspv.Verifier(ctx)
if err != nil {
log.Errorf("failed to create %v verifier: %w", verifierProvider, err)
continue
}
verifiers = append(verifiers, s)
return verifiers, nil
}
}
}

s, err := sp.Verifier(ctx)
if err != nil {
log.Errorf("failed to create %v verifier: %w", verifierProvider, err)
continue
}

verifiers = append(verifiers, s)
}

return verifiers, nil
}
2 changes: 2 additions & 0 deletions cmd/root.go
Expand Up @@ -19,6 +19,8 @@ import (
"os"

"github.com/in-toto/go-witness/log"
_ "github.com/in-toto/go-witness/signer/kms/aws"
_ "github.com/in-toto/go-witness/signer/kms/gcp"
"github.com/in-toto/witness/options"
"github.com/spf13/cobra"
)
Expand Down
6 changes: 3 additions & 3 deletions cmd/root_test.go
Expand Up @@ -63,7 +63,7 @@ func Test_loadSignersKeyPair(t *testing.T) {
},
}

signers, err := loadSigners(context.Background(), signerOptions, map[string]struct{}{"file": {}})
signers, err := loadSigners(context.Background(), signerOptions, options.KMSSignerProviderOptions{}, map[string]struct{}{"file": {}})
require.NoError(t, err)
require.Len(t, signers, 1)
assert.IsType(t, &cryptoutil.RSASigner{}, signers[0])
Expand All @@ -79,7 +79,7 @@ func Test_loadSignersKeyPair(t *testing.T) {
},
}

signers, err := loadSigners(context.Background(), signerOptions, map[string]struct{}{"file": {}})
signers, err := loadSigners(context.Background(), signerOptions, options.KMSSignerProviderOptions{}, map[string]struct{}{"file": {}})
require.Error(t, err)
require.Len(t, signers, 0)
})
Expand All @@ -99,7 +99,7 @@ func Test_loadSignersCertificate(t *testing.T) {
},
}

signers, err := loadSigners(context.Background(), signerOptions, map[string]struct{}{"file": {}})
signers, err := loadSigners(context.Background(), signerOptions, options.KMSSignerProviderOptions{}, map[string]struct{}{"file": {}})
require.NoError(t, err)
require.Len(t, signers, 1)
require.IsType(t, &cryptoutil.X509Signer{}, signers[0])
Expand Down
9 changes: 5 additions & 4 deletions cmd/run.go
Expand Up @@ -35,8 +35,9 @@ import (

func RunCmd() *cobra.Command {
o := options.RunOptions{
AttestorOptSetters: make(map[string][]func(attestation.Attestor) (attestation.Attestor, error)),
SignerOptions: options.SignerOptions{},
AttestorOptSetters: make(map[string][]func(attestation.Attestor) (attestation.Attestor, error)),
SignerOptions: options.SignerOptions{},
KMSSignerProviderOptions: options.KMSSignerProviderOptions{},
}

cmd := &cobra.Command{
Expand All @@ -45,9 +46,9 @@ func RunCmd() *cobra.Command {
SilenceErrors: true,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
signers, err := loadSigners(cmd.Context(), o.SignerOptions, signerProvidersFromFlags(cmd.Flags()))
signers, err := loadSigners(cmd.Context(), o.SignerOptions, o.KMSSignerProviderOptions, providersFromFlags("signer", cmd.Flags()))
if err != nil {
return fmt.Errorf("failed to load signers")
return fmt.Errorf("failed to load signers: %w", err)
}

return runRun(cmd.Context(), o, args, signers...)
Expand Down
2 changes: 1 addition & 1 deletion cmd/run_test.go
Expand Up @@ -83,7 +83,7 @@ func Test_runRunRSACA(t *testing.T) {
},
}

signers, err := loadSigners(context.Background(), signerOptions, map[string]struct{}{"file": {}})
signers, err := loadSigners(context.Background(), signerOptions, options.KMSSignerProviderOptions{}, map[string]struct{}{"file": {}})
require.NoError(t, err)

workingDir := t.TempDir()
Expand Down
5 changes: 3 additions & 2 deletions cmd/sign.go
Expand Up @@ -29,7 +29,8 @@ import (

func SignCmd() *cobra.Command {
so := options.SignOptions{
SignerOptions: options.SignerOptions{},
SignerOptions: options.SignerOptions{},
KMSSignerProviderOptions: options.KMSSignerProviderOptions{},
}

cmd := &cobra.Command{
Expand All @@ -40,7 +41,7 @@ func SignCmd() *cobra.Command {
SilenceUsage: true,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
signers, err := loadSigners(cmd.Context(), so.SignerOptions, signerProvidersFromFlags(cmd.Flags()))
signers, err := loadSigners(cmd.Context(), so.SignerOptions, so.KMSSignerProviderOptions, providersFromFlags("signer", cmd.Flags()))
if err != nil {
return fmt.Errorf("failed to load signer: %w", err)
}
Expand Down
24 changes: 16 additions & 8 deletions cmd/verify.go
Expand Up @@ -33,7 +33,11 @@ import (
)

func VerifyCmd() *cobra.Command {
vo := options.VerifyOptions{}
vo := options.VerifyOptions{
ArchivistaOptions: options.ArchivistaOptions{},
KMSVerifierProviderOptions: options.KMSVerifierProviderOptions{},
VerifierOptions: options.VerifierOptions{},
}
cmd := &cobra.Command{
Use: "verify",
Short: "Verifies a witness policy",
Expand All @@ -42,7 +46,11 @@ func VerifyCmd() *cobra.Command {
SilenceUsage: true,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runVerify(cmd.Context(), vo)
verifiers, err := loadVerifiers(cmd.Context(), vo.VerifierOptions, vo.KMSVerifierProviderOptions, providersFromFlags("verifier", cmd.Flags()))
if err != nil {
return fmt.Errorf("failed to load signer: %w", err)
}
return runVerify(cmd.Context(), vo, verifiers...)
},
}
vo.AddFlags(cmd)
Expand All @@ -55,24 +63,24 @@ const (

// todo: this logic should be broken out and moved to pkg/
// we need to abstract where keys are coming from, etc
func runVerify(ctx context.Context, vo options.VerifyOptions) error {
if vo.KeyPath == "" && len(vo.CAPaths) == 0 {
return fmt.Errorf("must suply public key or ca paths")
func runVerify(ctx context.Context, vo options.VerifyOptions, verifiers ...cryptoutil.Verifier) error {
if vo.KeyPath == "" && len(vo.CAPaths) == 0 && len(verifiers) == 0 {
return fmt.Errorf("must supply either a public key, CA certificates or a verifier")
}

var verifier cryptoutil.Verifier
if vo.KeyPath != "" {
keyFile, err := os.Open(vo.KeyPath)
if err != nil {
return fmt.Errorf("failed to open key file: %w", err)
}
defer keyFile.Close()

verifier, err = cryptoutil.NewVerifierFromReader(keyFile)
v, err := cryptoutil.NewVerifierFromReader(keyFile)
if err != nil {
return fmt.Errorf("failed to create verifier: %w", err)
}

verifiers = append(verifiers, v)
}

inFile, err := os.Open(vo.PolicyFilePath)
Expand Down Expand Up @@ -121,7 +129,7 @@ func runVerify(ctx context.Context, vo options.VerifyOptions) error {
verifiedEvidence, err := witness.Verify(
ctx,
policyEnvelope,
[]cryptoutil.Verifier{verifier},
verifiers,
witness.VerifyWithSubjectDigests(subjects),
witness.VerifyWithCollectionSource(collectionSource),
)
Expand Down
4 changes: 2 additions & 2 deletions cmd/verify_test.go
Expand Up @@ -54,7 +54,7 @@ func TestRunVerifyCA(t *testing.T) {
},
}

signers, err := loadSigners(context.Background(), so, map[string]struct{}{"file": {}})
signers, err := loadSigners(context.Background(), so, options.KMSSignerProviderOptions{}, map[string]struct{}{"file": {}})
require.NoError(t, err)

caBytes, err := os.ReadFile(ca.Name())
Expand Down Expand Up @@ -167,7 +167,7 @@ func TestRunVerifyKeyPair(t *testing.T) {
},
}

signers, err := loadSigners(context.Background(), so, map[string]struct{}{"file": {}})
signers, err := loadSigners(context.Background(), so, options.KMSSignerProviderOptions{}, map[string]struct{}{"file": {}})
require.NoError(t, err)

artifactPath := filepath.Join(workingDir, "test.txt")
Expand Down
45 changes: 36 additions & 9 deletions docs/commands.md
Expand Up @@ -31,6 +31,15 @@ witness run [cmd] [flags]
--signer-fulcio-token string Raw token string to use for authentication to fulcio (cannot be used in conjunction with --fulcio-token-path)
--signer-fulcio-token-path string Path to the file containing a raw token to use for authentication to fulcio (cannot be used in conjunction with --fulcio-token)
--signer-fulcio-url string Fulcio address to sign with
--signer-kms-aws-config-file string The shared configuration file to use with the AWS KMS signer provider
--signer-kms-aws-credentials-file string The shared credentials file to use with the AWS KMS signer provider
--signer-kms-aws-insecure-skip-verify Skip verification of the server's certificate chain and host name
--signer-kms-aws-profile string The shared configuration profile to use with the AWS KMS signer provider
--signer-kms-aws-remote-verify verify signature using AWS KMS remote verification. If false, the public key will be pulled from AWS KMS and verification will take place locally (default true)
--signer-kms-gcp-credentials-file string The credentials file to use with the GCP KMS signer provider
--signer-kms-hashType string The hash type to use for signing (default "sha256")
--signer-kms-keyVersion string The key version to use for signing
--signer-kms-ref string The KMS Reference URI to use for connecting to the KMS service
--signer-spiffe-socket-path string Path to the SPIFFE Workload API Socket
--signer-vault-altnames strings Alt names to use for the generated certificate. All alt names must be allowed by the vault role policy
--signer-vault-commonname string Common name to use for the generated certificate. Must be allowed by the vault role policy
Expand Down Expand Up @@ -85,6 +94,15 @@ witness sign [file] [flags]
--signer-fulcio-token string Raw token string to use for authentication to fulcio (cannot be used in conjunction with --fulcio-token-path)
--signer-fulcio-token-path string Path to the file containing a raw token to use for authentication to fulcio (cannot be used in conjunction with --fulcio-token)
--signer-fulcio-url string Fulcio address to sign with
--signer-kms-aws-config-file string The shared configuration file to use with the AWS KMS signer provider
--signer-kms-aws-credentials-file string The shared credentials file to use with the AWS KMS signer provider
--signer-kms-aws-insecure-skip-verify Skip verification of the server's certificate chain and host name
--signer-kms-aws-profile string The shared configuration profile to use with the AWS KMS signer provider
--signer-kms-aws-remote-verify verify signature using AWS KMS remote verification. If false, the public key will be pulled from AWS KMS and verification will take place locally (default true)
--signer-kms-gcp-credentials-file string The credentials file to use with the GCP KMS signer provider
--signer-kms-hashType string The hash type to use for signing (default "sha256")
--signer-kms-keyVersion string The key version to use for signing
--signer-kms-ref string The KMS Reference URI to use for connecting to the KMS service
--signer-spiffe-socket-path string Path to the SPIFFE Workload API Socket
--signer-vault-altnames strings Alt names to use for the generated certificate. All alt names must be allowed by the vault role policy
--signer-vault-commonname string Common name to use for the generated certificate. Must be allowed by the vault role policy
Expand Down Expand Up @@ -123,15 +141,24 @@ witness verify [flags]
### Options

```
--archivista-server string URL of the Archivista server to store or retrieve attestations (default "https://archivista.testifysec.io")
-f, --artifactfile string Path to the artifact to verify
-a, --attestations strings Attestation files to test against the policy
--enable-archivista Use Archivista to store or retrieve attestations
-h, --help help for verify
-p, --policy string Path to the policy to verify
--policy-ca strings Paths to CA certificates to use for verifying the policy
-k, --publickey string Path to the policy signer's public key
-s, --subjects strings Additional subjects to lookup attestations
--archivista-server string URL of the Archivista server to store or retrieve attestations (default "https://archivista.testifysec.io")
-f, --artifactfile string Path to the artifact to verify
-a, --attestations strings Attestation files to test against the policy
--enable-archivista Use Archivista to store or retrieve attestations
-h, --help help for verify
-p, --policy string Path to the policy to verify
--policy-ca strings Paths to CA certificates to use for verifying the policy
-k, --publickey string Path to the policy signer's public key
-s, --subjects strings Additional subjects to lookup attestations
--verifier-kms-aws-config-file string The shared configuration file to use with the AWS KMS signer provider
--verifier-kms-aws-credentials-file string The shared credentials file to use with the AWS KMS signer provider
--verifier-kms-aws-insecure-skip-verify Skip verification of the server's certificate chain and host name
--verifier-kms-aws-profile string The shared configuration profile to use with the AWS KMS signer provider
--verifier-kms-aws-remote-verify verify signature using AWS KMS remote verification. If false, the public key will be pulled from AWS KMS and verification will take place locally (default true)
--verifier-kms-gcp-credentials-file string The credentials file to use with the GCP KMS signer provider
--verifier-kms-hashType string The hash type used for verifying (default "sha256")
--verifier-kms-keyVersion string The key version to use for signing
--verifier-kms-ref string The KMS Reference URI to use for connecting to the KMS service
```

### Options inherited from parent commands
Expand Down

0 comments on commit c27a4f5

Please sign in to comment.