From ddf37fadd202bd2a12221d0adaf23b076e054e59 Mon Sep 17 00:00:00 2001 From: Dzianis Andreyenka Date: Thu, 14 Sep 2023 21:43:33 +0200 Subject: [PATCH] hashicorp vault support (#439) * Implement Signatory vault interface for Hashicorp Vault Signed-off-by: Dzianis Andreyenka * Add Hashicorp Vault config sample Signed-off-by: Dzianis Andreyenka * Update README with Hashicorp Vault info Signed-off-by: Dzianis Andreyenka * update website with Hashicorp Vault info Signed-off-by: Dzianis Andreyenka * Docs fix Signed-off-by: Dzianis Andreyenka * secret id without expiration Signed-off-by: Dzianis Andreyenka * init HCP for signatory CLI Signed-off-by: Dzianis Andreyenka * Remove unused vault interaction methods Signed-off-by: Dzianis Andreyenka --------- Signed-off-by: Dzianis Andreyenka --- README.md | 4 +- cmd/signatory-cli/main.go | 1 + cmd/signatory/main.go | 1 + docs/hashicorp_vault.md | 115 ++++++++++++++ docs/start.md | 4 +- go.mod | 17 +++ go.sum | 76 ++++++++++ pkg/vault/hashicorp/vault.go | 214 +++++++++++++++++++++++++++ pkg/vault/hashicorp/vault_transit.go | 110 ++++++++++++++ signatory.yaml | 8 + website/sidebars.js | 1 + 11 files changed, 549 insertions(+), 2 deletions(-) create mode 100644 docs/hashicorp_vault.md create mode 100644 pkg/vault/hashicorp/vault.go create mode 100644 pkg/vault/hashicorp/vault_transit.go diff --git a/README.md b/README.md index a83a8954..5a7b15e8 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Signatory receives requests to sign Tezos operations. These operations may be co Signatory will inspect the operations and assert that the operation request is in line with Signatory's policy. If the operation passes the policy rules, Signatory will then have a signature produced using the appropriate backend system. -Signatory operators can choose from AWS, Azure or Google Cloud KMS systems, or self-hosted wallets such as the YubiHSM2 or a Ledger Hardware wallet. +Signatory operators can choose from AWS, Azure or Google Cloud KMS systems, or self-hosted solutions such as the YubiHSM2, Hashicorp Vault or Ledger Hardware wallet. ### Observability @@ -70,6 +70,7 @@ In the first year of the Tezos network operation, there was anecdotal evidence t | Google Cloud KMS | ✅ | | AWS KMS | ✅ | | Ledger Nano S/S+ (Baking only) | ✅ | +| Hashicorp Vault | ✅ | ### Tezos Address Types @@ -84,6 +85,7 @@ In Tezos, you can infer the signing algorithm from the first three characters of | | tz1 | tz2 | tz3 | | ---------------- | --- | --- | --- | +| Hashicorp Vault | ✅ | ❌ | ❌ | | Google Cloud KMS | ❌ | ❌ | ✅ | | AWS KMS | ❌ | ✅ | ✅ | | Azure KMS | ❌ | ✅ | ✅ | diff --git a/cmd/signatory-cli/main.go b/cmd/signatory-cli/main.go index c94f5eaa..3b50f530 100644 --- a/cmd/signatory-cli/main.go +++ b/cmd/signatory-cli/main.go @@ -16,6 +16,7 @@ import ( _ "github.com/ecadlabs/signatory/pkg/vault/azure" _ "github.com/ecadlabs/signatory/pkg/vault/cloudkms" _ "github.com/ecadlabs/signatory/pkg/vault/file" + _ "github.com/ecadlabs/signatory/pkg/vault/hashicorp" _ "github.com/ecadlabs/signatory/pkg/vault/ledger" _ "github.com/ecadlabs/signatory/pkg/vault/mem" _ "github.com/ecadlabs/signatory/pkg/vault/yubi" diff --git a/cmd/signatory/main.go b/cmd/signatory/main.go index 96f8f1e3..ae9048f0 100644 --- a/cmd/signatory/main.go +++ b/cmd/signatory/main.go @@ -15,6 +15,7 @@ import ( _ "github.com/ecadlabs/signatory/pkg/vault/azure" _ "github.com/ecadlabs/signatory/pkg/vault/cloudkms" _ "github.com/ecadlabs/signatory/pkg/vault/file" + _ "github.com/ecadlabs/signatory/pkg/vault/hashicorp" _ "github.com/ecadlabs/signatory/pkg/vault/ledger" _ "github.com/ecadlabs/signatory/pkg/vault/mem" _ "github.com/ecadlabs/signatory/pkg/vault/yubi" diff --git a/docs/hashicorp_vault.md b/docs/hashicorp_vault.md new file mode 100644 index 00000000..191d2e57 --- /dev/null +++ b/docs/hashicorp_vault.md @@ -0,0 +1,115 @@ +--- +id: hashicorp_vault +title: HashicorpVault +--- + +# Hashicorp Vault + +The goal of this guide is to configure Signatory to use an Hashicorp Vault as a signing backend. + +## **Vault setup** + +Run Vault server or use dedicated cloud instance (HCP Vault) for secrets management + +### **Install vault** + +https://developer.hashicorp.com/vault/downloads + +### **Run Dev Server** + +```sh +docker run --cap-add=IPC_LOCK -d --name=dev-vault vault +``` + +### **Configure Vault Address and Token** + +Set the Vault address and obtain the root token provided when starting the Vault server. + +```sh +export VAULT_ADDR=http://127.0.0.1:8200 +export VAULT_TOKEN={{root_token}} +``` + +### **Enable Transit Secrets Engine** + +Enable the Transit secrets engine which is used to manage cryptographic functions. + +```sh +vault secrets enable transit +``` + +### **Create Transit Key** + +Create a new encryption key in the transit secrets engine. + +```sh +vault write -f transit/keys/{{my-key}} type="ed25519" +``` + +### **Create Policy for Transit** + +Create a policy that allows reading and writing keys in the transit engine. + +```hcl +# transit-policy.hcl +path "transit/*" { + capabilities = ["read", "create", "update", "list"] +} +``` + +### **Create AppRole Authentication** + +Enable and configure the AppRole authentication method. + +```sh +vault auth enable approle +``` + +### **Create AppRole Role** + +Create a role for the AppRole authentication method. This role associates a set of policies with the AppRole. + +```sh +vault write auth/approle/role/my-approle \ + secret_id_ttl=0m \ + token_ttl=20m \ + token_max_ttl=30m \ + token_policies="transit-policy" + +``` + +### **Fetch Role ID and Secret ID** + +Fetch the Role ID and Secret ID of the AppRole. + +```sh +vault read auth/approle/role/{{my-approle}}/role-id +vault write -f auth/approle/role/{{my-approle}}/secret-id +``` + +## **Backend configuration** + +### **Configuration parameters** + +||||| +|--- |--- |--- |--- | +|Name|Type|Required|Description| +|address|URL|✅|Vault URL| +|roleID|UUID|✅|AppRole identifier| +|secretID|UUID|✅|AppRole credential| +|transitConfig.mountPoint|string|✅|Path to the transit secret engine| +|tlsCaCert|string|OPTIONAL|tlsCaCert is the path to a PEM-encoded CA cert file to use to verify the Vault server SSL certificate.| +|tlsClientCert|string|OPTIONAL|tlsClientCert is the path to the certificate for Vault communication| +|tlsClientKey|string|OPTIONAL|tlsClientKey is the path to the private key for Vault communication| + + +Example: + +```yaml +address: "http://127.0.0.1:8200" +roleID: "5970e31e-132b-d624-f3eb-10d1fcdd3fab" +secretID: "aa9c4a24-c7f1-a278-a9db-bac58273fe7c" +transitConfig: + mountPoint: "transit/" +``` + diff --git a/docs/start.md b/docs/start.md index 63f3b9ef..4f55bc19 100644 --- a/docs/start.md +++ b/docs/start.md @@ -8,13 +8,14 @@ sidebar_label: Getting Started [azure]: https://docs.microsoft.com/en-us/azure/key-vault/ [gcp]: https://cloud.google.com/security-key-management [yubi]: https://www.yubico.com/products/hardware-security-module/ +[hashicorp]: https://developer.hashicorp.com/vault/docs/secrets/transit ## What is Signatory Signatory is a remote signing daemon that allows Tezos bakers to sign endorsement and baking operations with various key-management systems. -Signatory currently supports [AWS KMS][aws], [Azure Key Vault][azure], [GCP Key Management][gcp], [YubiHSM][yubi], and for development/prototyping purposes, Signatory can sign with a [local private key](localsecret.md). +Signatory currently supports [AWS KMS][aws], [Azure Key Vault][azure], [GCP Key Management][gcp], [YubiHSM][yubi], [Hashicorp Vault][hashicorp], and for development/prototyping purposes, Signatory can sign with a [local private key](localsecret.md). The goal of the Signatory service is to make key management as secure as possible in a Cloud and on-premise HSM context. @@ -149,6 +150,7 @@ tezos: * [Azure Key Vault](azure_kms.md) * [GCP Key Management](gcp_kms.md) * [YubiHSM2](yubihsm.md) +* [Hashicorp Vault](hashicorp_vault.md) --- diff --git a/go.mod b/go.mod index 44bc91b2..96b80734 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,8 @@ require ( github.com/google/tink/go v1.7.0 github.com/google/uuid v1.3.1 github.com/gorilla/mux v1.8.0 + github.com/hashicorp/vault/api v1.9.2 + github.com/hashicorp/vault/api/auth/approle v0.4.1 github.com/karalabe/hid v1.0.0 github.com/prometheus/client_golang v1.16.0 github.com/segmentio/ksuid v1.0.4 @@ -29,13 +31,28 @@ require ( cloud.google.com/go/compute v1.23.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.2 // indirect + github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/go-jose/go-jose/v3 v3.0.0 // indirect github.com/google/s2a-go v0.1.5 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-retryablehttp v0.6.6 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/text v0.2.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect golang.org/x/sync v0.3.0 // indirect + golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect google.golang.org/genproto v0.0.0-20230807174057-1744710a1577 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230807174057-1744710a1577 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577 // indirect diff --git a/go.sum b/go.sum index 64794005..d9cc8da9 100644 --- a/go.sum +++ b/go.sum @@ -12,10 +12,14 @@ cloud.google.com/go/kms v1.15.1 h1:HUC3fAoepH3RpcQXiJhXWWYizjQ5r7YjI7SO9ZbHf9s= cloud.google.com/go/kms v1.15.1/go.mod h1:c9J991h5DTl+kg7gi3MYomh12YEENGrf48ee/N/2CDM= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.44.328 h1:WBwlf8ym9SDQ/GTIBO9eXyvwappKJyOetWJKl4mT7ZU= github.com/aws/aws-sdk-go v1.44.328/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certusone/yubihsm-go v0.3.0 h1:mB1m5ZDSqX88xR2Kwq25vGOQKa4SV/polPTRpIr6/6Q= github.com/certusone/yubihsm-go v0.3.0/go.mod h1:4TofNVV4saOz2gjxT0xJ1Bt7KuSgMRN5Frhw/OpAb94= @@ -48,9 +52,13 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= +github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -59,6 +67,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM= github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= +github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= @@ -91,6 +101,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/s2a-go v0.1.5 h1:8IYp3w9nysqv3JH+NJgXJzGbDHzLOTj43BmSkp+O7qg= @@ -107,6 +118,35 @@ github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qK github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.16.2 h1:K4ev2ib4LdQETX5cSZBG0DVLk1jwGqSPXBjdah3veNs= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= +github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/vault/api v1.9.2 h1:YjkZLJ7K3inKgMZ0wzCU9OHqc+UqMQyXsPXnf3Cl2as= +github.com/hashicorp/vault/api v1.9.2/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8= +github.com/hashicorp/vault/api/auth/approle v0.4.1 h1:NElpX7DZ2uaLGwY+leWXHUqw9tepsYkcHvIowgIZteI= +github.com/hashicorp/vault/api/auth/approle v0.4.1/go.mod h1:rlI2VbmuHkptRun7DngpxOSvRC+JuITqAs/Z09pUucU= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -121,11 +161,28 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -139,6 +196,9 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -150,7 +210,9 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -163,10 +225,12 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -190,6 +254,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -203,9 +269,14 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -215,11 +286,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -229,8 +302,11 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= diff --git a/pkg/vault/hashicorp/vault.go b/pkg/vault/hashicorp/vault.go new file mode 100644 index 00000000..ffb3a79e --- /dev/null +++ b/pkg/vault/hashicorp/vault.go @@ -0,0 +1,214 @@ +package hashicorp + +import ( + "context" + "crypto/ed25519" + "encoding/base64" + "errors" + "fmt" + "net/url" + + "github.com/ecadlabs/signatory/pkg/config" + "github.com/ecadlabs/signatory/pkg/crypt" + "github.com/ecadlabs/signatory/pkg/vault" + "github.com/hashicorp/vault/api" + auth "github.com/hashicorp/vault/api/auth/approle" + "gopkg.in/yaml.v3" +) + +// Config contains Hashcorp Vault backend configuration +type Config struct { + Address string `yaml:"address"` + RoleID string `yaml:"roleID"` + SecretID string `yaml:"secretID"` + TLSCaCert string `yaml:"tlsCaCert"` + TLSClientCert string `yaml:"tlsClientCert"` + TLSClientKey string `yaml:"tlsClientKey"` + *TransitConfig `yaml:"transitConfig"` +} + +type Vault struct { + client *api.Client + RoleID string + SecretID string + transitCfg *TransitConfig +} + +// vaultKey represents a key stored in Hashcorp Vault +type vaultKey struct { + id string + pub crypt.PublicKey +} + +// PublicKey returns encoded public key +func (k *vaultKey) PublicKey() crypt.PublicKey { + return k.pub +} + +// ID returnd a unique key ID +func (k *vaultKey) ID() string { + return k.id +} + +type iterator struct { + ctx context.Context + v *Vault + keyList []string + index int +} + +func init() { + vault.RegisterVault("hashicorpvault", func(ctx context.Context, node *yaml.Node) (vault.Vault, error) { + var conf Config + if node == nil || node.Kind == 0 { + return nil, errors.New("(HashicorpVault): config is missing") + } + if err := node.Decode(&conf); err != nil { + return nil, err + } + + if err := config.Validator().Struct(&conf); err != nil { + return nil, err + } + + return New(ctx, &conf) + }) +} + +// New creates new Hashicorp Vault backend +func New(ctx context.Context, cfg *Config) (*Vault, error) { + vaultConfig := &api.Config{ + Address: cfg.Address, + } + + // verify if address is https + parsedurl, err := url.Parse(cfg.Address) + if err != nil { + return nil, fmt.Errorf("unable to parse vault address: %w", err) + } + if parsedurl.Scheme == "https" { + tlsCfg := api.TLSConfig{ + CACert: cfg.TLSCaCert, + ClientCert: cfg.TLSClientCert, + ClientKey: cfg.TLSClientKey, + } + if err := vaultConfig.ConfigureTLS(&tlsCfg); err != nil { + return nil, fmt.Errorf("unable to configure TLS: %w", err) + } + } + + client, err := api.NewClient(vaultConfig) + if err != nil { + return nil, err + } + + vault := &Vault{ + client: client, + transitCfg: cfg.TransitConfig, + RoleID: cfg.RoleID, + SecretID: cfg.SecretID, + } + + if err = vault.login(); err != nil { + return nil, err + } + + return vault, nil +} + +// Name returns backend name +func (v *Vault) Name() string { + return "HASHICORP_VAULT" +} + +func (v *Vault) login() error { + appRoleAuth, err := auth.NewAppRoleAuth(v.RoleID, &auth.SecretID{FromString: v.SecretID}) + if err != nil { + return fmt.Errorf("unable to initialize AppRole auth method: %w", err) + } + + authInfo, err := v.client.Auth().Login(context.Background(), appRoleAuth) + if err != nil { + return fmt.Errorf("unable to login to AppRole auth method: %w", err) + } + if authInfo == nil { + return fmt.Errorf("no auth info was returned after login") + } + + return nil +} + +// ListPublicKeys returns a list of keys stored under the backend +func (v *Vault) ListPublicKeys(ctx context.Context) vault.StoredKeysIterator { + return &iterator{ + ctx: ctx, + v: v, + } +} + +func (i *iterator) Next() (key vault.StoredKey, err error) { + if i.keyList == nil { + i.keyList, err = i.v.Transit().ListKeys() + if err != nil { + return nil, err + } + } + if i.index == len(i.keyList) { + // end of the list + return nil, vault.ErrDone + } + + key, err = i.v.GetPublicKey(i.ctx, i.keyList[i.index]) + i.index += 1 + + if err != nil { + return nil, err + } + + return key, nil +} + +func (v *Vault) GetPublicKey(ctx context.Context, keyID string) (vault.StoredKey, error) { + wrappingPubKeyString, err := v.Transit().GetKeyWithContext(ctx, keyID) + if err != nil { + return nil, err + } + + pubKeyBytes, err := base64.StdEncoding.DecodeString(wrappingPubKeyString) + if err != nil { + return nil, err + } + + // Ensure the public key length is correct for EdDSA (32 bytes) + if len(pubKeyBytes) != ed25519.PublicKeySize { + return nil, fmt.Errorf("invalid public key length") + } + + // Convert the bytes to an Ed25519 public key + eddsaPublicKey := ed25519.PublicKey(pubKeyBytes) + + cryptPubKey, err := crypt.NewPublicKeyFrom(eddsaPublicKey) + if err != nil { + return nil, err + } + + return &vaultKey{ + id: keyID, + pub: cryptPubKey, + }, nil +} + +func (v *Vault) SignMessage(ctx context.Context, message []byte, key vault.StoredKey) (crypt.Signature, error) { + digest := crypt.DigestFunc(message) + + sout, err := v.Transit().Sign(key.ID(), digest[:], &SignOpts{Hash: "sha2-256", Preshashed: false}) + if err != nil { + return nil, err + } + + sig, err := crypt.NewSignatureFromBytes(sout, key.PublicKey()) + if err != nil { + return nil, err + } + return sig, nil +} diff --git a/pkg/vault/hashicorp/vault_transit.go b/pkg/vault/hashicorp/vault_transit.go new file mode 100644 index 00000000..9ce223ee --- /dev/null +++ b/pkg/vault/hashicorp/vault_transit.go @@ -0,0 +1,110 @@ +package hashicorp + +import ( + "context" + "encoding/base64" + "fmt" + "strings" + + "github.com/ecadlabs/signatory/pkg/vault" + "github.com/hashicorp/vault/api" +) + +type TransitConfig struct { + MountPoint string `yaml:"mountPoint"` +} + +type Transit struct { + c *api.Client + cfg *TransitConfig +} + +type SignOpts struct { + Preshashed bool + Hash string +} + +func (v *Vault) Transit() *Transit { + v.login() + return &Transit{c: v.client, cfg: v.transitCfg} +} + +func (t *Transit) ListKeys() ([]string, error) { + var res []string + s, err := t.c.Logical().List(fmt.Sprintf("%s/keys", t.cfg.MountPoint)) + if err != nil { + return res, err + } + if s == nil { + return res, fmt.Errorf("no key was returned") + } + + keys, ok := s.Data["keys"].([]interface{}) + if !ok { + return res, fmt.Errorf("failed to parse keys") + } + + // excluding 'import' path as it's not a key and used for storing imported keys + res = make([]string, 0, len(keys)-1) + for _, key := range keys { + keyStr := key.(string) + if keyStr == "import/" { + continue + } + res = append(res, keyStr) + } + + return res, nil +} + +func (t *Transit) GetKeyWithContext(ctx context.Context, keyID string) (string, error) { + s, err := t.c.Logical().ReadWithContext(ctx, fmt.Sprintf("%s/keys/%s", t.cfg.MountPoint, keyID)) + if err != nil { + return "", err + } + if s == nil { + return "", fmt.Errorf("no key was returned") + } + + var pubKeyStr string + + switch s.Data["type"].(string) { + case "ed25519": + keys, ok := s.Data["keys"].(map[string]interface{}) + if !ok { + return "", fmt.Errorf("failed to parse keys") + } + + k := keys["1"].(map[string]interface{}) + pubKeyStr, ok = k["public_key"].(string) + if !ok { + return "", fmt.Errorf("failed to parse public key") + } + default: + return "", vault.ErrKey + } + + return pubKeyStr, nil +} + +func (t *Transit) Sign(keyName string, input []byte, opts *SignOpts) ([]byte, error) { + s, err := t.c.Logical().Write(fmt.Sprintf("%s/sign/%s", t.cfg.MountPoint, keyName), map[string]interface{}{ + "input": base64.StdEncoding.EncodeToString(input), + "prehashed": opts.Preshashed, + "hash_algorithm": opts.Hash, + }) + if err != nil { + return nil, err + } + if s == nil { + return nil, fmt.Errorf("no signature was returned") + } + + splitted := strings.Split(s.Data["signature"].(string), ":") + signature, err := base64.StdEncoding.DecodeString(splitted[len(splitted)-1]) + if err != nil { + return nil, fmt.Errorf("failed to decode signature") + } + + return signature, nil +} diff --git a/signatory.yaml b/signatory.yaml index d9d07528..4a9e1274 100644 --- a/signatory.yaml +++ b/signatory.yaml @@ -40,6 +40,14 @@ vaults: address: localhost:12345 password: password auth_key_id: 1 + hashicorp: + driver: hashicorpvault + config: + address: "http://127.0.0.1:8200" + roleID: "5970e31e-132b-d624-f3eb-10d1fcdd3fab" + secretID: "aa9c4a24-c7f1-a278-a9db-bac58273fe7c" + transitConfig: + mountPoint: "transit/" # List enabled public keys hashes here tezos: diff --git a/website/sidebars.js b/website/sidebars.js index cda70bd5..05f88d2e 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -28,6 +28,7 @@ const sidebars = { 'azure_kms', 'gcp_kms', 'aws_kms', + 'hashicorp_vault', 'ledger', 'cli', 'remote_policy',