mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-01-11 15:39:33 -05:00
internal: use go-kms-wrapping for KMS backends (#1012)
* Replace external KMS backend logic for AWS, Azure, and GCP with go-kms-wrapping * Move kms client setup config into its own package for easier parsing * Update kms integration flag naming * Error if nil storage is passed to external KMS --------- Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
parent
68ce23b909
commit
3a7b829107
@ -155,13 +155,10 @@ func newStubKMS(setupErr, getDEKErr error) kmsFactory {
|
||||
}
|
||||
|
||||
type stubKMS struct {
|
||||
kms.CloudKMS
|
||||
getDEKErr error
|
||||
}
|
||||
|
||||
func (s *stubKMS) CreateKEK(ctx context.Context, keyID string, kek []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stubKMS) GetDEK(ctx context.Context, dekID string, dekSize int) ([]byte, error) {
|
||||
if s.getDEKErr != nil {
|
||||
return nil, s.getDEKErr
|
||||
|
22
go.mod
22
go.mod
@ -41,7 +41,6 @@ require (
|
||||
cloud.google.com/go/storage v1.28.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.9.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights v1.0.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.1.0
|
||||
@ -55,7 +54,6 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.24.0
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.20.1
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.83.0
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.20.1
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.30.1
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.2
|
||||
github.com/aws/smithy-go v1.13.5
|
||||
@ -67,9 +65,11 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.11.1
|
||||
github.com/google/go-tpm v0.3.3
|
||||
github.com/google/go-tpm-tools v0.3.10
|
||||
github.com/google/tink/go v1.7.0
|
||||
github.com/googleapis/gax-go/v2 v2.7.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||
github.com/hashicorp/go-kms-wrapping/wrappers/awskms/v2 v2.0.1
|
||||
github.com/hashicorp/go-kms-wrapping/wrappers/azurekeyvault/v2 v2.0.1
|
||||
github.com/hashicorp/go-kms-wrapping/wrappers/gcpckms/v2 v2.0.2
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/hashicorp/hc-install v0.4.0
|
||||
github.com/hashicorp/terraform-exec v0.17.3
|
||||
@ -117,11 +117,20 @@ require (
|
||||
cloud.google.com/go/iam v0.8.0 // indirect
|
||||
cloud.google.com/go/longrunning v0.3.0 // indirect
|
||||
code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c // indirect
|
||||
github.com/Azure/azure-sdk-for-go v67.3.0+incompatible // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.1.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.28 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v0.7.0 // indirect
|
||||
github.com/BurntSushi/toml v1.1.0 // indirect
|
||||
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
||||
@ -131,6 +140,7 @@ require (
|
||||
github.com/Masterminds/squirrel v1.5.3 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.180 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 // indirect
|
||||
@ -157,6 +167,7 @@ require (
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20210303052042-6bc126869bf4 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/dnaeon/go-vcr v1.2.0 // indirect
|
||||
github.com/docker/cli v20.10.20+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
@ -208,8 +219,12 @@ require (
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-hclog v1.3.1 // indirect
|
||||
github.com/hashicorp/go-kms-wrapping/v2 v2.0.6
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
@ -233,6 +248,7 @@ require (
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
|
71
go.sum
71
go.sum
@ -88,14 +88,13 @@ github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiU
|
||||
github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v67.3.0+incompatible h1:QEvenaO+Y9ShPeCWsSAtolzVUcb0T0tPeek5TDsovuM=
|
||||
github.com/Azure/azure-sdk-for-go v67.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.0 h1:VuHAcMq8pU1IWNT/m5yRaGqbK0BiQKHT8X4DTp9CHdI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.0/go.mod h1:tZoQYdDZNOiIjdSn0dVWVfl0NEPGOJqVLzSrcFk4Is0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.0 h1:t/W5MYAuQy81cvM8VUNfRLzhtKpXhVUAN7Cd7KVbTyc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.0/go.mod h1:NBanQUfSWiWn3QEpWDTCU0IjBECKOYvl2R8xdRtMtiM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 h1:+5VZ72z0Qan5Bog5C+ZkgSqUbeVUd9wgtHOrIKuc5b8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.9.0 h1:TOFrNxfjslms5nLLIMjW7N0+zSALX4KiGsptmpb16AA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.9.0/go.mod h1:EAyXOW1F6BTJPiK2pDvmnvxOHPxoTYWoqBeIlql+QhI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0 h1:82w8tzLcOwDP/Q35j/wEBPt0n0kVC3cjtPdD62G8UAk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0/go.mod h1:S78i9yTr4o/nXlH76bKjGUye9Z2wSxO5Tz7GoDr4vfI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 h1:Lg6BW0VPmCwcMlvOviL3ruHFO+H9tZNqscK0AeuFjGM=
|
||||
@ -120,8 +119,30 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg6
|
||||
github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
|
||||
github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM=
|
||||
github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.21 h1:jjQnVFXPfekaqb8vIsv2G1lxshoW+oGv4MDlhRtnYZk=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.21/go.mod h1:zua7mBUaCc5YnSLKYgGJR/w5ePdMDA6H56upLsHzA9U=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 h1:P6bYXFoao05z5uhOQzbC3Qd8JqF3jUoocoTeIxkp2cA=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 h1:w77/uPk80ZET2F+AfQExZyEWtn+0Rk/uw17m9fv5Ajc=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6/go.mod h1:piCfgPho7BiIDdEQ1+g4VmKyD5y+p/XtSNqE6Hc4QD0=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU=
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
|
||||
github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac=
|
||||
github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
|
||||
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
|
||||
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v0.7.0 h1:VgSJlZH5u0k2qxSpqyghcFQKmvYckj46uymKK5XzkBM=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v0.7.0/go.mod h1:BDJ5qMFKx9DugEg3+uQSDCdbYPr5s9vBTrL9P8TpqOU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
@ -203,7 +224,10 @@ github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN
|
||||
github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.44.180 h1:VLZuAHI9fa/3WME5JjpVjcPCNfpGHVMiHx8sLHWhMgI=
|
||||
github.com/aws/aws-sdk-go v1.44.180/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||
github.com/aws/aws-sdk-go-v2 v1.17.3 h1:shN7NlnVzvDUgPQ+1rLMSxY8OWRNDRYtiqe0p/PgrhY=
|
||||
github.com/aws/aws-sdk-go-v2 v1.17.3/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
|
||||
@ -241,8 +265,6 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 h1:5C6XgTViS
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21/go.mod h1:lRToEJsn+DRA9lW4O9L9+/3hjTkUzlzyzHqn8MTds5k=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.21 h1:vY5siRXvW5TrOKm2qKEf9tliBfdLxdfy0i02LOcmqUo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.21/go.mod h1:WZvNXT1XuH8dnJM0HvOlvk+RNn7NbAPvA/ACO0QarSc=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.20.1 h1:3/aZ1EqvVzu8Ska+AmEFvbCjV12GXfVtNqKeluhEYpo=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.20.1/go.mod h1:13sjgMH7Xu4e46+0BEDhSnNh+cImHSYS5PpBjV3oXcU=
|
||||
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.14.1 h1:mlEuylFZuEoJa1XXcZUlYRKp8aGADI97mC5tCpN/n8E=
|
||||
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.14.1/go.mod h1:NjPeUP8L8V1lN1ik1Znb0cEnIgGA3Upt/UFSzwBLC6o=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.30.1 h1:kIgvVY7PHx4gIb0na/Q9gTWJWauTwhKdaqJjX8PkIY8=
|
||||
@ -353,6 +375,8 @@ github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mz
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
|
||||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||
github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk=
|
||||
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
@ -563,6 +587,8 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
@ -686,8 +712,6 @@ github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w=
|
||||
github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM=
|
||||
github.com/google/trillian v1.3.14-0.20210409160123-c5ea3abd4a41/go.mod h1:1dPv0CUjNQVFEDuAUFhZql16pw/VlPgaX8qj+g5pVzQ=
|
||||
github.com/google/trillian v1.3.14-0.20210511103300-67b5f349eefa/go.mod h1:s4jO3Ai4NSvxucdvqUHON0bCqJyoya32eNw6XJwsmNc=
|
||||
github.com/google/trillian v1.4.0/go.mod h1:1Bja2nEgMDlEJWWRXBUemSPG9qYw84ZYX2gHRVHlR+g=
|
||||
@ -750,8 +774,18 @@ github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng
|
||||
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/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-hclog v1.3.1 h1:vDwF1DFNZhntP4DAjuTpOw3uEgMUpXh1pB5fW9DqHpo=
|
||||
github.com/hashicorp/go-hclog v1.3.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-kms-wrapping/v2 v2.0.6 h1:szfBtjzqyJ/sjOAOGM1XIGnzkLFSAbCqgrNZXBqojMY=
|
||||
github.com/hashicorp/go-kms-wrapping/v2 v2.0.6/go.mod h1:sDQAfwJGv25uGPZA04x87ERglCG6avnRcBT9wYoMII8=
|
||||
github.com/hashicorp/go-kms-wrapping/wrappers/awskms/v2 v2.0.1 h1:WxpTuafkDjdeeu0Xtk9y3m9YAJhfFMb8+y6eTnxvV8A=
|
||||
github.com/hashicorp/go-kms-wrapping/wrappers/awskms/v2 v2.0.1/go.mod h1:3D5UB9fjot4oUTYGQ5gGmhLJKreyLZeI0XB+NxcLTKs=
|
||||
github.com/hashicorp/go-kms-wrapping/wrappers/azurekeyvault/v2 v2.0.1 h1:6joKpqCFveaNMEwC3qna67usws6DjdxqfCuQEHSM0aM=
|
||||
github.com/hashicorp/go-kms-wrapping/wrappers/azurekeyvault/v2 v2.0.1/go.mod h1:sDmsWR/W2LqwU217o32RzdHMb/FywGLF72PVIhpZ3hE=
|
||||
github.com/hashicorp/go-kms-wrapping/wrappers/gcpckms/v2 v2.0.2 h1:p6q7ox2/atVl2Brmn45oGJ25iSL4jduMOOuHPXJy+m0=
|
||||
github.com/hashicorp/go-kms-wrapping/wrappers/gcpckms/v2 v2.0.2/go.mod h1:YRtkersQ2N3iHlPDG5B3xBQtBsNZ3bjmlCwnrl26jVE=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
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=
|
||||
@ -760,10 +794,14 @@ github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6 h1:W9WN8p6moV1fjKLkeqEgkAMu5rauy9QeYDAmIaPuuiA=
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6/go.mod h1:MpCPSPGLDILGb4JMm94/mMi3YysIqsXzGCzkEZjcjXg=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||
@ -816,6 +854,7 @@ github.com/jhump/protoreflect v1.8.2/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8
|
||||
github.com/jhump/protoreflect v1.9.0/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
@ -923,7 +962,9 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
|
||||
@ -932,6 +973,7 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
@ -1254,6 +1296,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
||||
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.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
@ -1321,6 +1364,7 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI=
|
||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE=
|
||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY=
|
||||
@ -1442,10 +1486,12 @@ golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWP
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@ -1487,6 +1533,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -1550,6 +1597,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
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.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@ -1588,6 +1637,7 @@ golang.org/x/sync v0.0.0-20200930132711-30421366ff76/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -1620,6 +1670,7 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -1640,6 +1691,7 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -1679,19 +1731,24 @@ golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
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.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -1703,6 +1760,7 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@ -1790,6 +1848,7 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4=
|
||||
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
30
hack/go.mod
30
hack/go.mod
@ -57,15 +57,21 @@ require (
|
||||
cloud.google.com/go/iam v0.8.0 // indirect
|
||||
cloud.google.com/go/kms v1.8.0 // indirect
|
||||
cloud.google.com/go/storage v1.28.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v67.3.0+incompatible // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.9.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.6.1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v0.7.0 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.28 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/BurntSushi/toml v1.1.0 // indirect
|
||||
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
@ -76,6 +82,7 @@ require (
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.3 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.180 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.17.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.10 // indirect
|
||||
@ -90,7 +97,6 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.22 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.20.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.30.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.12.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0 // indirect
|
||||
@ -108,6 +114,7 @@ require (
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20210303052042-6bc126869bf4 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/docker/cli v20.10.20+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/docker v20.10.22+incompatible // indirect
|
||||
@ -155,7 +162,6 @@ require (
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/logger v1.1.1 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/tink/go v1.7.0 // indirect
|
||||
github.com/google/trillian v1.5.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect
|
||||
@ -166,8 +172,15 @@ require (
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-hclog v1.3.1 // indirect
|
||||
github.com/hashicorp/go-kms-wrapping/v2 v2.0.6 // indirect
|
||||
github.com/hashicorp/go-kms-wrapping/wrappers/awskms/v2 v2.0.1 // indirect
|
||||
github.com/hashicorp/go-kms-wrapping/wrappers/azurekeyvault/v2 v2.0.1 // indirect
|
||||
github.com/hashicorp/go-kms-wrapping/wrappers/gcpckms/v2 v2.0.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hashicorp/hc-install v0.4.0 // indirect
|
||||
github.com/hashicorp/terraform-exec v0.17.3 // indirect
|
||||
@ -183,7 +196,6 @@ require (
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.15.12 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
@ -198,6 +210,7 @@ require (
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
@ -216,7 +229,6 @@ require (
|
||||
github.com/pborman/uuid v1.2.0 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pjbgf/sha1cd v0.2.3 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.13.0 // indirect
|
||||
|
75
hack/go.sum
75
hack/go.sum
@ -84,18 +84,13 @@ github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuE
|
||||
github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
|
||||
github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v67.3.0+incompatible h1:QEvenaO+Y9ShPeCWsSAtolzVUcb0T0tPeek5TDsovuM=
|
||||
github.com/Azure/azure-sdk-for-go v67.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.0 h1:VuHAcMq8pU1IWNT/m5yRaGqbK0BiQKHT8X4DTp9CHdI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.0/go.mod h1:tZoQYdDZNOiIjdSn0dVWVfl0NEPGOJqVLzSrcFk4Is0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.0 h1:t/W5MYAuQy81cvM8VUNfRLzhtKpXhVUAN7Cd7KVbTyc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.0/go.mod h1:NBanQUfSWiWn3QEpWDTCU0IjBECKOYvl2R8xdRtMtiM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 h1:+5VZ72z0Qan5Bog5C+ZkgSqUbeVUd9wgtHOrIKuc5b8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.9.0 h1:TOFrNxfjslms5nLLIMjW7N0+zSALX4KiGsptmpb16AA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.9.0/go.mod h1:EAyXOW1F6BTJPiK2pDvmnvxOHPxoTYWoqBeIlql+QhI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0 h1:82w8tzLcOwDP/Q35j/wEBPt0n0kVC3cjtPdD62G8UAk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0/go.mod h1:S78i9yTr4o/nXlH76bKjGUye9Z2wSxO5Tz7GoDr4vfI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 h1:Lg6BW0VPmCwcMlvOviL3ruHFO+H9tZNqscK0AeuFjGM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.6.1 h1:YvQv9Mz6T8oR5ypQOL6erY0Z5t71ak1uHV4QFokCOZk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.6.1/go.mod h1:c6WvOhtmjNUWbLfOG1qxM/q0SPvQNSVJvolm+C52dIU=
|
||||
github.com/Azure/azure-service-bus-go v0.9.1/go.mod h1:yzBx6/BUGfjfeqbRZny9AQIbIe3AcV9WZbAdpkoXOa0=
|
||||
@ -103,8 +98,33 @@ github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxw
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
|
||||
github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM=
|
||||
github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.21 h1:jjQnVFXPfekaqb8vIsv2G1lxshoW+oGv4MDlhRtnYZk=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.21/go.mod h1:zua7mBUaCc5YnSLKYgGJR/w5ePdMDA6H56upLsHzA9U=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 h1:P6bYXFoao05z5uhOQzbC3Qd8JqF3jUoocoTeIxkp2cA=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 h1:w77/uPk80ZET2F+AfQExZyEWtn+0Rk/uw17m9fv5Ajc=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6/go.mod h1:piCfgPho7BiIDdEQ1+g4VmKyD5y+p/XtSNqE6Hc4QD0=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU=
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
|
||||
github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac=
|
||||
github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
|
||||
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
|
||||
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v0.7.0 h1:VgSJlZH5u0k2qxSpqyghcFQKmvYckj46uymKK5XzkBM=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v0.7.0/go.mod h1:BDJ5qMFKx9DugEg3+uQSDCdbYPr5s9vBTrL9P8TpqOU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
@ -190,7 +210,10 @@ github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi
|
||||
github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.44.180 h1:VLZuAHI9fa/3WME5JjpVjcPCNfpGHVMiHx8sLHWhMgI=
|
||||
github.com/aws/aws-sdk-go v1.44.180/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||
github.com/aws/aws-sdk-go-v2 v1.17.3 h1:shN7NlnVzvDUgPQ+1rLMSxY8OWRNDRYtiqe0p/PgrhY=
|
||||
github.com/aws/aws-sdk-go-v2 v1.17.3/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
|
||||
@ -220,8 +243,6 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 h1:5C6XgTViS
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21/go.mod h1:lRToEJsn+DRA9lW4O9L9+/3hjTkUzlzyzHqn8MTds5k=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.21 h1:vY5siRXvW5TrOKm2qKEf9tliBfdLxdfy0i02LOcmqUo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.21/go.mod h1:WZvNXT1XuH8dnJM0HvOlvk+RNn7NbAPvA/ACO0QarSc=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.20.1 h1:3/aZ1EqvVzu8Ska+AmEFvbCjV12GXfVtNqKeluhEYpo=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.20.1/go.mod h1:13sjgMH7Xu4e46+0BEDhSnNh+cImHSYS5PpBjV3oXcU=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.30.1 h1:kIgvVY7PHx4gIb0na/Q9gTWJWauTwhKdaqJjX8PkIY8=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.30.1/go.mod h1:L2l2/q76teehcW7YEsgsDjqdsDTERJeX3nOMIFlgGUE=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.12.0 h1:/2gzjhQowRLarkkBOGPXSRnb8sQ2RVsjdG1C/UliK/c=
|
||||
@ -327,6 +348,8 @@ github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mz
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
|
||||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||
github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk=
|
||||
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||
github.com/docker/cli v20.10.20+incompatible h1:lWQbHSHUFs7KraSN2jOJK7zbMS2jNCHI4mt4xUFUVQ4=
|
||||
@ -539,6 +562,8 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
@ -675,8 +700,6 @@ github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w=
|
||||
github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM=
|
||||
github.com/google/trillian v1.3.11/go.mod h1:0tPraVHrSDkA3BO6vKX67zgLXs6SsOAbHEivX+9mPgw=
|
||||
github.com/google/trillian v1.3.14-0.20210409160123-c5ea3abd4a41/go.mod h1:1dPv0CUjNQVFEDuAUFhZql16pw/VlPgaX8qj+g5pVzQ=
|
||||
github.com/google/trillian v1.3.14-0.20210511103300-67b5f349eefa/go.mod h1:s4jO3Ai4NSvxucdvqUHON0bCqJyoya32eNw6XJwsmNc=
|
||||
@ -742,8 +765,18 @@ github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng
|
||||
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/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-hclog v1.3.1 h1:vDwF1DFNZhntP4DAjuTpOw3uEgMUpXh1pB5fW9DqHpo=
|
||||
github.com/hashicorp/go-hclog v1.3.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-kms-wrapping/v2 v2.0.6 h1:szfBtjzqyJ/sjOAOGM1XIGnzkLFSAbCqgrNZXBqojMY=
|
||||
github.com/hashicorp/go-kms-wrapping/v2 v2.0.6/go.mod h1:sDQAfwJGv25uGPZA04x87ERglCG6avnRcBT9wYoMII8=
|
||||
github.com/hashicorp/go-kms-wrapping/wrappers/awskms/v2 v2.0.1 h1:WxpTuafkDjdeeu0Xtk9y3m9YAJhfFMb8+y6eTnxvV8A=
|
||||
github.com/hashicorp/go-kms-wrapping/wrappers/awskms/v2 v2.0.1/go.mod h1:3D5UB9fjot4oUTYGQ5gGmhLJKreyLZeI0XB+NxcLTKs=
|
||||
github.com/hashicorp/go-kms-wrapping/wrappers/azurekeyvault/v2 v2.0.1 h1:6joKpqCFveaNMEwC3qna67usws6DjdxqfCuQEHSM0aM=
|
||||
github.com/hashicorp/go-kms-wrapping/wrappers/azurekeyvault/v2 v2.0.1/go.mod h1:sDmsWR/W2LqwU217o32RzdHMb/FywGLF72PVIhpZ3hE=
|
||||
github.com/hashicorp/go-kms-wrapping/wrappers/gcpckms/v2 v2.0.2 h1:p6q7ox2/atVl2Brmn45oGJ25iSL4jduMOOuHPXJy+m0=
|
||||
github.com/hashicorp/go-kms-wrapping/wrappers/gcpckms/v2 v2.0.2/go.mod h1:YRtkersQ2N3iHlPDG5B3xBQtBsNZ3bjmlCwnrl26jVE=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
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=
|
||||
@ -752,10 +785,14 @@ github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6 h1:W9WN8p6moV1fjKLkeqEgkAMu5rauy9QeYDAmIaPuuiA=
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6/go.mod h1:MpCPSPGLDILGb4JMm94/mMi3YysIqsXzGCzkEZjcjXg=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||
@ -809,6 +846,7 @@ github.com/jhump/protoreflect v1.8.2/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8
|
||||
github.com/jhump/protoreflect v1.9.0/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
@ -917,7 +955,9 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
|
||||
@ -961,6 +1001,7 @@ github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFW
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
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-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
|
||||
@ -1069,7 +1110,6 @@ github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi
|
||||
github.com/pjbgf/sha1cd v0.2.3 h1:uKQP/7QOzNtKYH7UTohZLcjF5/55EnTw0jO/Ru4jZwI=
|
||||
github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@ -1252,6 +1292,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
||||
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.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
@ -1450,6 +1491,7 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
||||
@ -1559,6 +1601,7 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
@ -1652,6 +1695,7 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -1687,22 +1731,24 @@ golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
@ -1711,6 +1757,7 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR
|
||||
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.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
|
@ -1,37 +1,79 @@
|
||||
# constellation-kms-client
|
||||
# Key Management Service backend implementation
|
||||
|
||||
This library provides an interface for the key management services used with constellation.
|
||||
It's intendet for the Constellation CSI Plugins and the CLI.
|
||||
This library provides an interface for the key management services used by Constellation.
|
||||
Its intended to be used for secure managing for data encryption keys and other symmetric secrets.
|
||||
|
||||
## KMS
|
||||
## [kms](./kms/)
|
||||
|
||||
The Cloud KMS is where we store our key encryption key (KEK).
|
||||
The KMS should be initiated by the CLI and provided with a key release policy.
|
||||
The CSP Plugin can request to encrypt data encryption keys (DEK) with the KEK to safely store the DEKs in persistent storage.
|
||||
The [kms](../kms/) package interacts with the Cloud KMS APIs.
|
||||
Support is planed for:
|
||||
A Key Management Service (KMS) is where we store our key encryption key (KEK).
|
||||
|
||||
* AWS KMS
|
||||
* GCP CKM
|
||||
* Azure Key Vault
|
||||
We differentiate between two cases:
|
||||
|
||||
## Storage
|
||||
* cluster KMS (cKMS):
|
||||
|
||||
The Constellation cluster itself holds the master secret (KEK) and manages key derivation.
|
||||
The KEK is generated by an admin on `constellation init`.
|
||||
Once send to the cluster, the KEK never leaves the confidential computing context.
|
||||
As keys are only derived on demand, no DEK is ever persisted to memory by the cKMS.
|
||||
|
||||
* external KMS (eKMS):
|
||||
|
||||
An external KMS solution is used to hold and manage the KEK.
|
||||
DEKs are encrypted and persisted to cloud storage solutions.
|
||||
An admin is required to set up and configure the KMS before use.
|
||||
|
||||
### KMS Credentials
|
||||
|
||||
This section covers how credentials are used by the KMS plugins.
|
||||
|
||||
#### AWS KMS
|
||||
|
||||
The client requires the region the KMS is located, an access key ID, and an access key secret.
|
||||
Read the [access key documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) for more details.
|
||||
|
||||
The IAM role requires the following permissions on the key:
|
||||
|
||||
* `kms:DescribeKey`
|
||||
* `kms:Encrypt`
|
||||
* `kms:Decrypt`
|
||||
|
||||
#### Azure Key Vault / Azure managed HSM
|
||||
|
||||
Authorization for Azure Key Vault happens through the use of manged identities.
|
||||
The managed identity used by the client needs the following permissions on the KEK:
|
||||
|
||||
* `keys/get`
|
||||
* `keys/wrapKey`
|
||||
* `keys/unwrapKey`
|
||||
|
||||
The client is set up using the tenant ID, client ID, and client secret tuple.
|
||||
Further, the vault type is chosen to configure whether or not the Key Vault is a managed HSM.
|
||||
|
||||
### Google KMS
|
||||
|
||||
Providing credentials to your application for Google's Cloud KMS h
|
||||
|
||||
Note that the service account used for authentication requires the following permissions:
|
||||
|
||||
* `cloudkms.cryptoKeyVersions.get`
|
||||
* `cloudkms.cryptoKeyVersions.useToDecrypt`
|
||||
* `cloudkms.cryptoKeyVersions.useToEncrypt`
|
||||
|
||||
## [storage](./storage/)
|
||||
|
||||
Storage is where the CSI Plugin stores the encrypted DEKs.
|
||||
Currently planned are:
|
||||
|
||||
Supported are:
|
||||
|
||||
* In-memory (used for testing only)
|
||||
* AWS S3, SSP
|
||||
* GCP GCS
|
||||
* Azure Blob
|
||||
|
||||
# Credentials
|
||||
### Storage Credentials
|
||||
|
||||
Each Plugin requires credentials to authenticate itself to a CSP.
|
||||
|
||||
## Storage
|
||||
|
||||
This section covers how credentials are used by the storage plugins.
|
||||
|
||||
### AWS S3 Bucket
|
||||
|
||||
To use the AWS S3 Bucket plugin, you need to have an existing [AWS account](https://aws.amazon.com/de/premiumsupport/knowledge-center/create-and-activate-aws-account/).
|
||||
@ -138,71 +180,3 @@ credentialFile := "/path/to/service-account-file.json"
|
||||
opts := option.WithCredentialsFile(credentialFile)
|
||||
store, err := storage.NewGoogleCloudStorage(context.TODO(), "myProject", "myBucket", nil, opts)
|
||||
```
|
||||
|
||||
## Key Managment Service
|
||||
|
||||
This section covers how credentials are used by the KMS plugins.
|
||||
|
||||
### AWS KMS
|
||||
|
||||
To use the AWS KMS, you need to have an existing [AWS account](https://aws.amazon.com/de/premiumsupport/knowledge-center/create-and-activate-aws-account/).
|
||||
|
||||
For authentication, you have to pass a config file to the plugin. As with the AWS S3 Bucket, you can use the config package to retrieve the data for the config file from your local AWS directory. Follow the steps listed in the [AWS S3 Bucket](#aws-s3-bucket) section on how to build the config file automatically.
|
||||
|
||||
To create the client:
|
||||
|
||||
```Go
|
||||
cfg, err := config.LoadDefaultConfig(context.TODO())
|
||||
awsClient := kms.NewFromConfig(cfg)
|
||||
client := kmsAws.New(awsClient, nil)
|
||||
```
|
||||
|
||||
### Azure Key Vault
|
||||
|
||||
To use the Azure Key Vault, you need to first [create a new key vault](https://docs.microsoft.com/en-us/azure/key-vault/general/quick-create-portal) or use an existing vault.
|
||||
|
||||
The implementation uses `NewDefaultAzureCredential` to load credentials. If you application is running on Azure infrastructure, credentials will be loaded using [managed identities](https://docs.microsoft.com/en-us/azure/developer/go/azure-sdk-authentication?tabs=bash#managed-identity). Otherwise you can use [environment variables to configure the client](https://docs.microsoft.com/en-us/azure/developer/go/azure-sdk-authentication?tabs=bash#-option-1-define-environment-variables).
|
||||
|
||||
To create the client for Azure KMS:
|
||||
|
||||
```Go
|
||||
client, err := kmsAzure.New(context.TODO(), "myVault", kmsAz.DefaultCloud, nil, nil)
|
||||
```
|
||||
|
||||
To create the client for Azure manged HSM KMS:
|
||||
|
||||
```Go
|
||||
client, err := kmsAzure.NewHSM(context.TODO(), "myHSM", nil, nil)
|
||||
```
|
||||
|
||||
### Google KMS
|
||||
|
||||
To use the Google Key Management Service, you need to first enable the KMS API and [create a key ring](https://cloud.google.com/kms/docs/quickstart#key_rings_and_keys) to use with the plugin or use an existing key ring.
|
||||
|
||||
Providing credentials to your application for Google's Cloud Key Managment Service happens the same way as described in [Google Cloud Storage](#google-cloud-storage).
|
||||
|
||||
Note that the service account used for authentication requires the following permissions:
|
||||
|
||||
* `cloudkms.cryptoKeyVersions.create`
|
||||
* `cloudkms.cryptoKeyVersions.update`
|
||||
* `cloudkms.cryptoKeyVersions.useToDecrypt`
|
||||
* `cloudkms.cryptoKeyVersions.useToEncrypt`
|
||||
* `cloudkms.importJobs.create`
|
||||
* `cloudkms.importJobs.get`
|
||||
* `cloudkms.importJobs.useToImport`
|
||||
|
||||
Once your credentials are set up you can create your client:
|
||||
|
||||
* With automatic credential selection:
|
||||
|
||||
```Go
|
||||
client, err := kmsGcp.New("myProject", "global", "myKeyRing", nil, kmspb.ProtectionLevel_SOFTWARE)
|
||||
```
|
||||
|
||||
* With manual credential selection:
|
||||
|
||||
```Go
|
||||
credentialFile := "/path/to/service-account-file.json"
|
||||
opts := option.WithCredentialsFile(credentialFile)
|
||||
client, err := kmsGcp.New("myProject", "global", "myKeyRing", nil, kmspb.ProtectionLevel_SOFTWARE, opts)
|
||||
```
|
@ -9,271 +9,49 @@ package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
awsconfig "github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/service/kms"
|
||||
"github.com/aws/aws-sdk-go-v2/service/kms/types"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/config"
|
||||
kmsInterface "github.com/edgelesssys/constellation/v2/internal/kms/kms"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms/util"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms/internal"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
awskms "github.com/hashicorp/go-kms-wrapping/wrappers/awskms/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// DEKContext is used as the encryption context in AWS KMS.
|
||||
DEKContext = "aws:ebs:id"
|
||||
)
|
||||
|
||||
// ClientAPI satisfies the Amazons KMS client's methods we need.
|
||||
// This allows us to mock the actual client, see https://aws.github.io/aws-sdk-go-v2/docs/unit-testing/
|
||||
type ClientAPI interface {
|
||||
CreateAlias(ctx context.Context, params *kms.CreateAliasInput, optFns ...func(*kms.Options)) (*kms.CreateAliasOutput, error)
|
||||
CreateKey(ctx context.Context, params *kms.CreateKeyInput, optFns ...func(*kms.Options)) (*kms.CreateKeyOutput, error)
|
||||
Decrypt(ctx context.Context, params *kms.DecryptInput, optFns ...func(*kms.Options)) (*kms.DecryptOutput, error)
|
||||
DeleteAlias(ctx context.Context, params *kms.DeleteAliasInput, optFns ...func(*kms.Options)) (*kms.DeleteAliasOutput, error)
|
||||
DescribeKey(ctx context.Context, params *kms.DescribeKeyInput, optFns ...func(*kms.Options)) (*kms.DescribeKeyOutput, error)
|
||||
Encrypt(ctx context.Context, params *kms.EncryptInput, optFns ...func(*kms.Options)) (*kms.EncryptOutput, error)
|
||||
GenerateDataKey(ctx context.Context, params *kms.GenerateDataKeyInput, optFns ...func(*kms.Options)) (*kms.GenerateDataKeyOutput, error)
|
||||
GenerateDataKeyWithoutPlaintext(ctx context.Context, params *kms.GenerateDataKeyWithoutPlaintextInput, optFns ...func(*kms.Options)) (*kms.GenerateDataKeyWithoutPlaintextOutput, error)
|
||||
GetParametersForImport(ctx context.Context, params *kms.GetParametersForImportInput, optFns ...func(*kms.Options)) (*kms.GetParametersForImportOutput, error)
|
||||
ImportKeyMaterial(ctx context.Context, params *kms.ImportKeyMaterialInput, optFns ...func(*kms.Options)) (*kms.ImportKeyMaterialOutput, error)
|
||||
PutKeyPolicy(ctx context.Context, params *kms.PutKeyPolicyInput, optFns ...func(*kms.Options)) (*kms.PutKeyPolicyOutput, error)
|
||||
ScheduleKeyDeletion(ctx context.Context, params *kms.ScheduleKeyDeletionInput, optFns ...func(*kms.Options)) (*kms.ScheduleKeyDeletionOutput, error)
|
||||
}
|
||||
|
||||
// KeyPolicyProducer allows to have callbacks for generating key policies at runtime.
|
||||
type KeyPolicyProducer interface {
|
||||
// CreateKeyPolicy returns a key policy for a given key ID.
|
||||
CreateKeyPolicy(keyID string) (string, error)
|
||||
}
|
||||
|
||||
// KMSClient implements the CloudKMS interface for AWS.
|
||||
type KMSClient struct {
|
||||
awsClient ClientAPI
|
||||
policyProducer KeyPolicyProducer
|
||||
storage kmsInterface.Storage
|
||||
kekID string
|
||||
kms *internal.KMSClient
|
||||
}
|
||||
|
||||
// New creates and initializes a new KMSClient for AWS.
|
||||
//
|
||||
// The parameter client needs to be initialized with valid AWS credentials (https://aws.github.io/aws-sdk-go-v2/docs/getting-started).
|
||||
// If storage is nil, the default MemMapStorage is used.
|
||||
func New(ctx context.Context, policyProducer KeyPolicyProducer, store kmsInterface.Storage, kekID string, optFns ...func(*awsconfig.LoadOptions) error) (*KMSClient, error) {
|
||||
func New(ctx context.Context, store kmsInterface.Storage, cfg uri.AWSConfig) (*KMSClient, error) {
|
||||
if store == nil {
|
||||
store = storage.NewMemMapStorage()
|
||||
return nil, errors.New("no storage backend provided for KMS")
|
||||
}
|
||||
|
||||
cfg, err := awsconfig.LoadDefaultConfig(ctx, optFns...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
wrapper := awskms.NewWrapper()
|
||||
if _, err := wrapper.SetConfig(
|
||||
ctx,
|
||||
wrapping.WithKeyId(cfg.KeyName),
|
||||
awskms.WithRegion(cfg.Region),
|
||||
awskms.WithAccessKey(cfg.AccessKeyID),
|
||||
awskms.WithSecretKey(cfg.AccessKey),
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("setting AWS KMS config: %w", err)
|
||||
}
|
||||
client := kms.NewFromConfig(cfg)
|
||||
|
||||
return &KMSClient{
|
||||
awsClient: client,
|
||||
policyProducer: policyProducer,
|
||||
storage: store,
|
||||
kekID: kekID,
|
||||
kms: &internal.KMSClient{
|
||||
Storage: store,
|
||||
Wrapper: wrapper,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateKEK creates a new KEK with the given key material and policy. If successful, the key can be referenced by keyID in the KMS in accordance to the policy.
|
||||
// https://docs.aws.amazon.com/kms/latest/developerguide/importing-keys.html
|
||||
func (c *KMSClient) CreateKEK(ctx context.Context, keyID string, key []byte) error {
|
||||
alias := "alias/" + keyID
|
||||
|
||||
// Check whether key with keyID already exists
|
||||
describeKeyInput := &kms.DescribeKeyInput{
|
||||
KeyId: aws.String(alias),
|
||||
}
|
||||
// If the keyID parameter is used for a key in the AWS KMS, the response includes the keyID generated by AWS at creation
|
||||
var awsGeneratedKeyID string
|
||||
newKeyCreationNeeded := true
|
||||
var nfe *types.NotFoundException
|
||||
describeKeyOutput, err := c.awsClient.DescribeKey(ctx, describeKeyInput)
|
||||
if err == nil {
|
||||
// The request is valid and a key with the keyID exists
|
||||
awsGeneratedKeyID = *describeKeyOutput.KeyMetadata.KeyId
|
||||
newKeyCreationNeeded = false
|
||||
} else if !errors.As(err, &nfe) {
|
||||
return err
|
||||
}
|
||||
|
||||
// If it is not needed to create a new key, the steps to create the key and the alias can be skipped
|
||||
if newKeyCreationNeeded {
|
||||
// specifies that the key should be empty at creation and the material will be imported afterward
|
||||
origin := types.OriginTypeExternal
|
||||
if len(key) == 0 {
|
||||
origin = types.OriginTypeAwsKms
|
||||
}
|
||||
// Creates new AWS KMS key with empty key material
|
||||
var tags []types.Tag
|
||||
for tagKey, tagValue := range config.KmsTags {
|
||||
tags = append(tags, types.Tag{
|
||||
TagKey: aws.String(tagKey),
|
||||
TagValue: aws.String(tagValue),
|
||||
})
|
||||
}
|
||||
createKeyInput := &kms.CreateKeyInput{
|
||||
Description: aws.String("Constellation Key Encryption Key"),
|
||||
Origin: origin,
|
||||
Tags: tags,
|
||||
}
|
||||
kekMetadata, err := c.awsClient.CreateKey(ctx, createKeyInput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Use the keyId of the created key in the following
|
||||
awsGeneratedKeyID = *kekMetadata.KeyMetadata.KeyId
|
||||
|
||||
// Creates Alias for the KEK, so the key can be accessed by specifying the keyID
|
||||
createAliasInput := &kms.CreateAliasInput{
|
||||
AliasName: aws.String(alias),
|
||||
TargetKeyId: &awsGeneratedKeyID,
|
||||
}
|
||||
if _, err = c.awsClient.CreateAlias(ctx, createAliasInput); err != nil {
|
||||
c.tryCleanUpResources(ctx, newKeyCreationNeeded, awsGeneratedKeyID, alias)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Only import key if the key is not empty
|
||||
if len(key) != 0 {
|
||||
// Retrieves token and public AWS key to encrypt the KEK for transmitting to AWS KMS
|
||||
getImportParameterInput := &kms.GetParametersForImportInput{
|
||||
KeyId: &awsGeneratedKeyID,
|
||||
// if supported, it is recommended to use 'RSAES_OAEP_SHA_256': https://docs.aws.amazon.com/kms/latest/developerguide/importing-keys-get-public-key-and-token.html
|
||||
WrappingAlgorithm: types.AlgorithmSpecRsaesOaepSha256,
|
||||
WrappingKeySpec: types.WrappingKeySpecRsa2048,
|
||||
}
|
||||
getParametersForImportOutput, err := c.awsClient.GetParametersForImport(ctx, getImportParameterInput)
|
||||
if err != nil {
|
||||
c.tryCleanUpResources(ctx, newKeyCreationNeeded, awsGeneratedKeyID, alias)
|
||||
return err
|
||||
}
|
||||
|
||||
// Encrypt the private key with the public key provided by AWS KMS
|
||||
// From the AWS KMS get-public-key documentation:
|
||||
// The value is a DER-encoded X.509 public key, also known as SubjectPublicKeyInfo (SPKI), as defined in RFC 5280.
|
||||
// When you use the HTTP API or the Amazon Web Services CLI, the value is Base64-encoded. Otherwise, it is not Base64-encoded.
|
||||
publicKey, err := util.ParseDERtoPublicKeyRSA(getParametersForImportOutput.PublicKey)
|
||||
if err != nil {
|
||||
c.tryCleanUpResources(ctx, newKeyCreationNeeded, awsGeneratedKeyID, alias)
|
||||
return err
|
||||
}
|
||||
encryptedKEK, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, publicKey, key, nil)
|
||||
if err != nil {
|
||||
c.tryCleanUpResources(ctx, newKeyCreationNeeded, awsGeneratedKeyID, alias)
|
||||
return err
|
||||
}
|
||||
|
||||
// Pushes the key material for the created KEK to AWS KMS
|
||||
// In case the key has already key material, the importKeyMaterial operation only succeeds if the newly imported key material matches the previous imported one
|
||||
// Otherwise it responds with a IncorrectKeyMaterialException
|
||||
importKeyMaterialInput := &kms.ImportKeyMaterialInput{
|
||||
EncryptedKeyMaterial: encryptedKEK,
|
||||
ImportToken: getParametersForImportOutput.ImportToken,
|
||||
KeyId: &awsGeneratedKeyID,
|
||||
ExpirationModel: types.ExpirationModelTypeKeyMaterialDoesNotExpire,
|
||||
}
|
||||
if _, err = c.awsClient.ImportKeyMaterial(ctx, importKeyMaterialInput); err != nil {
|
||||
c.tryCleanUpResources(ctx, newKeyCreationNeeded, awsGeneratedKeyID, alias)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Pushes key policy for KEK
|
||||
// Since the default policy of the KEK does not allow decryption for an IAM role, one has to include that in the key policy when importing the KEK.
|
||||
// Decryption is needed for retrieving the DEKs from the storage.
|
||||
policy, err := c.policyProducer.CreateKeyPolicy(awsGeneratedKeyID)
|
||||
if err != nil {
|
||||
c.tryCleanUpResources(ctx, newKeyCreationNeeded, awsGeneratedKeyID, alias)
|
||||
return err
|
||||
}
|
||||
putKeyPolicyInput := &kms.PutKeyPolicyInput{
|
||||
KeyId: &awsGeneratedKeyID,
|
||||
Policy: &policy,
|
||||
PolicyName: aws.String("default"),
|
||||
}
|
||||
if _, err = c.awsClient.PutKeyPolicy(ctx, putKeyPolicyInput); err != nil {
|
||||
c.tryCleanUpResources(ctx, newKeyCreationNeeded, awsGeneratedKeyID, alias)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDEK returns the DEK for dekID and kekID from the KMS.
|
||||
// GetDEK fetches an encrypted Data Encryption Key from storage and decrypts it using a KEK stored in AWS KMS.
|
||||
func (c *KMSClient) GetDEK(ctx context.Context, keyID string, dekSize int) ([]byte, error) {
|
||||
// The KEK should be identified by its alias. The alias always has the same scheme: 'alias/<kekId>'
|
||||
kekID := "alias/" + c.kekID
|
||||
|
||||
// If a key for keyID exists in the storage, decrypt the key using the KEK.
|
||||
dek, err := c.decryptDEKFromStorage(ctx, kekID, keyID)
|
||||
if err == nil {
|
||||
return dek, nil
|
||||
}
|
||||
if !errors.Is(err, storage.ErrDEKUnset) {
|
||||
return nil, err
|
||||
}
|
||||
return c.putNewDEKToStorage(ctx, kekID, keyID, dekSize)
|
||||
return c.kms.GetDEK(ctx, keyID, dekSize)
|
||||
}
|
||||
|
||||
func (c *KMSClient) decryptDEKFromStorage(ctx context.Context, kekID, keyID string) ([]byte, error) {
|
||||
encryptedKey, err := c.storage.Get(ctx, keyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decryptInput := &kms.DecryptInput{
|
||||
CiphertextBlob: encryptedKey,
|
||||
EncryptionContext: map[string]string{DEKContext: keyID},
|
||||
KeyId: &kekID,
|
||||
}
|
||||
decryptOutput, err := c.awsClient.Decrypt(ctx, decryptInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decryptOutput.Plaintext, nil
|
||||
}
|
||||
|
||||
func (c *KMSClient) putNewDEKToStorage(ctx context.Context, kekID, keyID string, dekSize int) ([]byte, error) {
|
||||
// GenerateDataKey always generates a new unique key, even if the input stays the same.
|
||||
input := &kms.GenerateDataKeyInput{
|
||||
KeyId: &kekID,
|
||||
// The encryption context is used for encryption. It must be the same when decrypting the ciphertext output
|
||||
EncryptionContext: map[string]string{DEKContext: keyID}, // https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#encrypt_context
|
||||
NumberOfBytes: aws.Int32(int32(dekSize)),
|
||||
}
|
||||
output, err := c.awsClient.GenerateDataKey(ctx, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// store encrypted key in storage
|
||||
if err := c.storage.Put(ctx, keyID, output.CiphertextBlob); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return output.Plaintext, nil
|
||||
}
|
||||
|
||||
func (c *KMSClient) tryCleanUpResources(ctx context.Context, generatedNewKey bool, awsGeneratedKeyID, alias string) {
|
||||
if !generatedNewKey {
|
||||
return
|
||||
}
|
||||
// Delete Alias
|
||||
deleteAliasInput := &kms.DeleteAliasInput{
|
||||
AliasName: &alias,
|
||||
}
|
||||
_, _ = c.awsClient.DeleteAlias(ctx, deleteAliasInput) // Might fail, ignoring the error.
|
||||
|
||||
// Delete Key
|
||||
scheduleKeyDeletionInput := &kms.ScheduleKeyDeletionInput{
|
||||
KeyId: &awsGeneratedKeyID,
|
||||
PendingWindowInDays: aws.Int32(7),
|
||||
}
|
||||
_, _ = c.awsClient.ScheduleKeyDeletion(ctx, scheduleKeyDeletionInput) // Might fail, ignoring the error.
|
||||
}
|
||||
// Close is a no-op for AWS.
|
||||
func (c *KMSClient) Close() {}
|
||||
|
@ -1,520 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package aws
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/kms"
|
||||
"github.com/aws/aws-sdk-go-v2/service/kms/types"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/config"
|
||||
kmsInterface "github.com/edgelesssys/constellation/v2/internal/kms/kms"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
goleak.VerifyTestMain(m,
|
||||
// https://github.com/census-instrumentation/opencensus-go/issues/1262
|
||||
goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"),
|
||||
)
|
||||
}
|
||||
|
||||
const (
|
||||
kekLen = 32
|
||||
importedPublicKeyLen = 2048
|
||||
)
|
||||
|
||||
// fakeAWSClient mocks the AWS KMS client.
|
||||
type fakeAWSClient struct {
|
||||
kekPool map[string][]byte
|
||||
keyIDCount int
|
||||
keyPairStruct *rsa.PrivateKey
|
||||
}
|
||||
|
||||
// GenerateDataKey creates a new DEK.
|
||||
func (m *fakeAWSClient) GenerateDataKey(ctx context.Context, params *kms.GenerateDataKeyInput, optFns ...func(*kms.Options)) (*kms.GenerateDataKeyOutput, error) {
|
||||
if params.KeyId == nil {
|
||||
return nil, fmt.Errorf("Missing paramerter KeyId")
|
||||
}
|
||||
|
||||
kekID := *params.KeyId
|
||||
if _, ok := m.kekPool[kekID]; !ok {
|
||||
return nil, kmsInterface.ErrKEKUnknown
|
||||
}
|
||||
|
||||
var keyLen int32
|
||||
if params.NumberOfBytes != nil {
|
||||
keyLen = *params.NumberOfBytes
|
||||
} else {
|
||||
switch params.KeySpec {
|
||||
case types.DataKeySpecAes128:
|
||||
keyLen = 128
|
||||
case types.DataKeySpecAes256:
|
||||
keyLen = 256
|
||||
}
|
||||
}
|
||||
|
||||
dek := make([]byte, keyLen)
|
||||
|
||||
// should not be random, but dependent on the context and the kekId
|
||||
contextKeyID := params.EncryptionContext[DEKContext]
|
||||
i := 0
|
||||
for i < kekLen/2 {
|
||||
dek[i] = contextKeyID[i%len(contextKeyID)]
|
||||
i++
|
||||
}
|
||||
for i < kekLen {
|
||||
dek[i] = []byte(*params.KeyId)[i%len(*params.KeyId)]
|
||||
i++
|
||||
}
|
||||
output := &kms.GenerateDataKeyOutput{
|
||||
CiphertextBlob: dek,
|
||||
KeyId: &kekID,
|
||||
Plaintext: dek,
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// Listed in order called by CreateKEK
|
||||
|
||||
// DescribeKey takes an alias and searches in the kekPool whether this alias exists.
|
||||
// If the alias exists, return the keyId the alias refers to (identifiable via the same key value). Otherwise, indicate that the key was not found.
|
||||
func (m *fakeAWSClient) DescribeKey(ctx context.Context, params *kms.DescribeKeyInput, optFns ...func(*kms.Options)) (*kms.DescribeKeyOutput, error) {
|
||||
if aliasKeyValue, ok := m.kekPool[*params.KeyId]; ok {
|
||||
awsGeneratedKeyID := m.getIDPartnerByKeyValue(*params.KeyId, aliasKeyValue)
|
||||
if awsGeneratedKeyID == "" {
|
||||
return nil, errors.New("could not determine id partner")
|
||||
}
|
||||
describeKeyOutput := &kms.DescribeKeyOutput{
|
||||
KeyMetadata: &types.KeyMetadata{
|
||||
KeyId: &awsGeneratedKeyID,
|
||||
},
|
||||
}
|
||||
return describeKeyOutput, nil
|
||||
}
|
||||
return nil, &types.NotFoundException{Message: aws.String("not found exception error")}
|
||||
}
|
||||
|
||||
// CreateKey stores a key in fakeKMSStore.
|
||||
func (m *fakeAWSClient) CreateKey(ctx context.Context, params *kms.CreateKeyInput, optFns ...func(*kms.Options)) (*kms.CreateKeyOutput, error) {
|
||||
m.keyIDCount++
|
||||
kekID := strconv.Itoa(m.keyIDCount)
|
||||
|
||||
if _, ok := m.kekPool[kekID]; ok {
|
||||
return nil, errors.New("Key with this id already exists")
|
||||
}
|
||||
// Use m.keyIdCount to make the key values unique for the search in describeKey
|
||||
kekValue := make([]byte, kekLen)
|
||||
for i := 0; i < kekLen; i++ {
|
||||
kekValue[i] = byte(m.keyIDCount)
|
||||
}
|
||||
m.kekPool[kekID] = kekValue
|
||||
|
||||
keyOutput := &kms.CreateKeyOutput{
|
||||
KeyMetadata: &types.KeyMetadata{
|
||||
KeyId: &kekID,
|
||||
},
|
||||
}
|
||||
return keyOutput, nil
|
||||
}
|
||||
|
||||
// CreateAlias changes keyId value to alias.
|
||||
func (m *fakeAWSClient) CreateAlias(ctx context.Context, params *kms.CreateAliasInput, optFns ...func(*kms.Options)) (*kms.CreateAliasOutput, error) {
|
||||
kekValue, ok := m.kekPool[*params.TargetKeyId]
|
||||
if !ok {
|
||||
return nil, kmsInterface.ErrKEKUnknown
|
||||
}
|
||||
// Copy the key bytes of the KEK to a new entry using the alias as keyId.
|
||||
// As with the KMS, each key can be addressed using either its alias or the incremented keyId generated during creation
|
||||
m.kekPool[*params.AliasName] = kekValue
|
||||
return &kms.CreateAliasOutput{}, nil
|
||||
}
|
||||
|
||||
// GetParametersForImport returns an empty public key and empty import token.
|
||||
func (m *fakeAWSClient) GetParametersForImport(ctx context.Context, params *kms.GetParametersForImportInput, optFns ...func(*kms.Options)) (*kms.GetParametersForImportOutput, error) {
|
||||
var err error
|
||||
m.keyPairStruct, err = rsa.GenerateKey(rand.Reader, importedPublicKeyLen)
|
||||
if err != nil {
|
||||
return nil, errors.New("Error generating Key Pair")
|
||||
}
|
||||
var pki any = &m.keyPairStruct.PublicKey
|
||||
publicKeyBytes, err := x509.MarshalPKIXPublicKey(pki)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
importToken := make([]byte, 1)
|
||||
getParametersForImportOutput := &kms.GetParametersForImportOutput{
|
||||
PublicKey: publicKeyBytes,
|
||||
ImportToken: importToken,
|
||||
}
|
||||
return getParametersForImportOutput, nil
|
||||
}
|
||||
|
||||
// ImportKeyMaterial stores the encrypted KEK in the KMS.
|
||||
func (m *fakeAWSClient) ImportKeyMaterial(ctx context.Context, params *kms.ImportKeyMaterialInput, optFns ...func(*kms.Options)) (*kms.ImportKeyMaterialOutput, error) {
|
||||
if _, ok := m.kekPool[*params.KeyId]; !ok {
|
||||
return nil, kmsInterface.ErrKEKUnknown
|
||||
}
|
||||
|
||||
// decrypt the encryptedKeyMaterial generated using RSAES_OAEP_SHA_256 to get the imported key bytes
|
||||
kekValue, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, m.keyPairStruct, params.EncryptedKeyMaterial, nil)
|
||||
if err != nil {
|
||||
return nil, errors.New("Error decrypting the wrapped key")
|
||||
}
|
||||
|
||||
// Set imported key bytes for keyId and alias
|
||||
currentKeyValue := m.kekPool[*params.KeyId]
|
||||
aliasID := m.getIDPartnerByKeyValue(*params.KeyId, currentKeyValue)
|
||||
if aliasID == "" {
|
||||
return nil, errors.New("could not determine id partner")
|
||||
}
|
||||
m.kekPool[aliasID] = kekValue
|
||||
m.kekPool[*params.KeyId] = kekValue
|
||||
|
||||
return &kms.ImportKeyMaterialOutput{}, nil
|
||||
}
|
||||
|
||||
// PutKeyPolicy sets the policy for an existing KEK in the kms.
|
||||
func (m *fakeAWSClient) PutKeyPolicy(ctx context.Context, params *kms.PutKeyPolicyInput, optFns ...func(*kms.Options)) (*kms.PutKeyPolicyOutput, error) {
|
||||
return &kms.PutKeyPolicyOutput{}, nil
|
||||
}
|
||||
|
||||
// Decrypt performs decryption.
|
||||
// Since the keys are saved in plain text during testing, decrypt returns the saved value.
|
||||
func (m *fakeAWSClient) Decrypt(ctx context.Context, params *kms.DecryptInput, optFns ...func(*kms.Options)) (*kms.DecryptOutput, error) {
|
||||
decryptOutput := &kms.DecryptOutput{
|
||||
Plaintext: params.CiphertextBlob,
|
||||
}
|
||||
return decryptOutput, nil
|
||||
}
|
||||
|
||||
// getIDPartnerByKeyValue returns a the alternative name of a key, if it exists.
|
||||
// For every key there should exist an alias after successful execution of CreateKEK.
|
||||
// Both key and its alias are stored with the same key value.
|
||||
// For consistency, they should be updated simultaneously, since they must store the same key value.
|
||||
func (m *fakeAWSClient) getIDPartnerByKeyValue(keyID1 string, keyValue1 []byte) string {
|
||||
// Search for the keyId with the same keyValue the input key has
|
||||
for keyID2, keyValue2 := range m.kekPool {
|
||||
if bytes.Equal(keyValue2, keyValue1) && keyID2 != keyID1 {
|
||||
return keyID2
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// DeleteAlias is a stub.
|
||||
func (m *fakeAWSClient) DeleteAlias(ctx context.Context, params *kms.DeleteAliasInput, optFns ...func(*kms.Options)) (*kms.DeleteAliasOutput, error) {
|
||||
return &kms.DeleteAliasOutput{}, nil
|
||||
}
|
||||
|
||||
// ScheduleKeyDeletion is a stub.
|
||||
func (m *fakeAWSClient) ScheduleKeyDeletion(ctx context.Context, params *kms.ScheduleKeyDeletionInput, optFns ...func(*kms.Options)) (*kms.ScheduleKeyDeletionOutput, error) {
|
||||
return &kms.ScheduleKeyDeletionOutput{}, nil
|
||||
}
|
||||
|
||||
// GenerateDataKeyWithoutPlaintext is a stub.
|
||||
func (m *fakeAWSClient) GenerateDataKeyWithoutPlaintext(ctx context.Context, params *kms.GenerateDataKeyWithoutPlaintextInput, optFns ...func(*kms.Options)) (*kms.GenerateDataKeyWithoutPlaintextOutput, error) {
|
||||
return nil, errors.New("Not implemented")
|
||||
}
|
||||
|
||||
// Encrypt is a stub.
|
||||
func (m *fakeAWSClient) Encrypt(ctx context.Context, params *kms.EncryptInput, optFns ...func(*kms.Options)) (*kms.EncryptOutput, error) {
|
||||
return nil, errors.New("Not implemented")
|
||||
}
|
||||
|
||||
type stubKeyPolicyProducer struct {
|
||||
createKeyPolicyErr error
|
||||
}
|
||||
|
||||
// CreateKeyPolicy creates a key policy.
|
||||
func (m *stubKeyPolicyProducer) CreateKeyPolicy(keyID string) (string, error) {
|
||||
return "", m.createKeyPolicyErr
|
||||
}
|
||||
|
||||
func TestAWSKMSClient(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
awsClient := &fakeAWSClient{kekPool: make(map[string][]byte, 2)}
|
||||
|
||||
testKEK1ID := "testKEK1"
|
||||
testKEK1 := []byte("test KEK")
|
||||
testKEK2ID := "testKEK2"
|
||||
testKEK2 := []byte("more test KEK")
|
||||
client := &KMSClient{
|
||||
awsClient: awsClient,
|
||||
policyProducer: &stubKeyPolicyProducer{},
|
||||
storage: storage.NewMemMapStorage(),
|
||||
kekID: testKEK1ID,
|
||||
}
|
||||
|
||||
awsClient.keyIDCount = -1
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// try to get a DEK before setting the KEK
|
||||
_, err := client.GetDEK(ctx, "volume01", config.SymmetricKeyLength)
|
||||
assert.Error(err)
|
||||
assert.ErrorIs(err, kmsInterface.ErrKEKUnknown)
|
||||
|
||||
// test CreateKEK method
|
||||
assert.NoError(client.CreateKEK(ctx, testKEK1ID, testKEK1))
|
||||
assert.Equal(testKEK1, awsClient.kekPool[strconv.Itoa(awsClient.keyIDCount)])
|
||||
|
||||
// make sure that CreateKEK is idempotent
|
||||
assert.NoError(client.CreateKEK(ctx, testKEK1ID, testKEK1))
|
||||
assert.Equal(testKEK1, awsClient.kekPool[strconv.Itoa(awsClient.keyIDCount)])
|
||||
|
||||
// test setting a second KEK
|
||||
assert.NoError(client.CreateKEK(ctx, testKEK2ID, testKEK2))
|
||||
assert.Equal(testKEK2, awsClient.kekPool[strconv.Itoa(awsClient.keyIDCount)])
|
||||
|
||||
// test GetDEK method
|
||||
dek1, err := client.GetDEK(ctx, "volume01", config.SymmetricKeyLength)
|
||||
assert.NoError(err)
|
||||
dek2, err := client.GetDEK(ctx, "volume02", config.SymmetricKeyLength)
|
||||
assert.NoError(err)
|
||||
|
||||
// make sure that GetDEK is idempotent
|
||||
dek1Copy, err := client.GetDEK(ctx, "volume01", config.SymmetricKeyLength)
|
||||
assert.NoError(err)
|
||||
assert.Equal(dek1, dek1Copy)
|
||||
dek2Copy, err := client.GetDEK(ctx, "volume02", config.SymmetricKeyLength)
|
||||
assert.NoError(err)
|
||||
assert.Equal(dek2, dek2Copy)
|
||||
}
|
||||
|
||||
func TestCreateKEK(t *testing.T) {
|
||||
someErr := errors.New("error")
|
||||
importKey := []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
||||
importPubKey, _ := pem.Decode([]byte(`-----BEGIN PUBLIC KEY-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAu+OepfHCTiTi27nkTGke
|
||||
dn+AIkiM1AIWWDwqfqG85aNulcj60mGQGXIYV8LoEVkyKOhYBIUmJUaVczB4ltqq
|
||||
ZhR7l46RQw2vnv+XiUmfK555d4ZDInyjTusO69hE6tkuYKdXLlG1HzcrhJ254LE2
|
||||
wXtE1Yf9DygOsWet+S32gmpfH2whUY1mRTdwW4zoY4c3qtmmWImhVVNr6qR8Z95X
|
||||
Y49EteCoNIomQNEZH7EnMlBsh34L7doOsckh1aTvQcrJorQSrBkWKbdV6kvuBKZp
|
||||
fLK0DZiOh9BwZCZANtOqgH3V+AuNk338iON8eKCFRjoiQ40YGM6xKH3E6PHVnuKt
|
||||
uIO0MPvE0qdV8Lvs+nCCrvwP5sJKZuciM40ioEO1pV1y3491xIxYhx3OfN4gg2h8
|
||||
cgdKob/R8qwxqTrfceO36FBFb1vXCUApsm5oy6WxmUtIUgoYhK+6JYpVWDyOJYwP
|
||||
iMJhdJA65n2ZliN8NxEhsaFoMgw76BOiD0wkt/CKPmNbOm5MGS3/fiZCt6A6u3cn
|
||||
Ubhn4tvjy/q5XzVqZtBeoseW2TyyrsAN53LBkSqag5tG/264CQDigQ6Y/OADOE2x
|
||||
n08MyrFHIL/wFMscOvJo7c2Eo4EW1yXkEkAy5tF5PZgnfRObakj4gdqPeq18FNzc
|
||||
Y+t5OxL3kL15VzY1Ob0d5cMCAwEAAQ==
|
||||
-----END PUBLIC KEY-----`))
|
||||
|
||||
testCases := map[string]struct {
|
||||
client *stubAWSClient
|
||||
policyProducer KeyPolicyProducer
|
||||
importKey []byte
|
||||
cleanupRequired bool
|
||||
wantErr bool
|
||||
}{
|
||||
"create new kek successful": {
|
||||
client: &stubAWSClient{createKeyID: "key-id"},
|
||||
policyProducer: &stubKeyPolicyProducer{},
|
||||
},
|
||||
"CreateKeyPolicy fails on existing": {
|
||||
client: &stubAWSClient{},
|
||||
policyProducer: &stubKeyPolicyProducer{createKeyPolicyErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
"CreateKeyPolicy fails on new": {
|
||||
client: &stubAWSClient{describeKeyErr: &types.NotFoundException{}},
|
||||
policyProducer: &stubKeyPolicyProducer{createKeyPolicyErr: someErr},
|
||||
cleanupRequired: true,
|
||||
wantErr: true,
|
||||
},
|
||||
"PutKeyPolicy fails on new": {
|
||||
client: &stubAWSClient{
|
||||
describeKeyErr: &types.NotFoundException{},
|
||||
putKeyPolicyErr: someErr,
|
||||
createKeyID: "key-id",
|
||||
},
|
||||
policyProducer: &stubKeyPolicyProducer{},
|
||||
cleanupRequired: true,
|
||||
wantErr: true,
|
||||
},
|
||||
"CreateAlias fails on new": {
|
||||
client: &stubAWSClient{
|
||||
describeKeyErr: &types.NotFoundException{},
|
||||
createAliasErr: someErr,
|
||||
createKeyID: "key-id",
|
||||
},
|
||||
policyProducer: &stubKeyPolicyProducer{},
|
||||
cleanupRequired: true,
|
||||
wantErr: true,
|
||||
},
|
||||
"CreateKey fails on new": {
|
||||
client: &stubAWSClient{describeKeyErr: &types.NotFoundException{}, createKeyErr: someErr},
|
||||
policyProducer: &stubKeyPolicyProducer{},
|
||||
wantErr: true,
|
||||
},
|
||||
"DescribeKey fails": {
|
||||
client: &stubAWSClient{describeKeyErr: someErr},
|
||||
policyProducer: &stubKeyPolicyProducer{},
|
||||
wantErr: true,
|
||||
},
|
||||
"DescribeKey fails with not found error": {
|
||||
client: &stubAWSClient{describeKeyErr: &types.NotFoundException{}},
|
||||
policyProducer: &stubKeyPolicyProducer{},
|
||||
},
|
||||
"import kek successful": {
|
||||
client: &stubAWSClient{getParametersForImportPubKey: importPubKey.Bytes},
|
||||
policyProducer: &stubKeyPolicyProducer{},
|
||||
importKey: importKey,
|
||||
},
|
||||
"GetParametersForImport fails on new": {
|
||||
client: &stubAWSClient{
|
||||
describeKeyErr: &types.NotFoundException{},
|
||||
getParametersForImportErr: someErr,
|
||||
createKeyID: "key-id",
|
||||
},
|
||||
policyProducer: &stubKeyPolicyProducer{},
|
||||
importKey: importKey,
|
||||
cleanupRequired: true,
|
||||
wantErr: true,
|
||||
},
|
||||
"ImportKeyMaterial fails on new": {
|
||||
client: &stubAWSClient{
|
||||
describeKeyErr: &types.NotFoundException{},
|
||||
importKeyMaterialErr: someErr,
|
||||
createKeyID: "key-id",
|
||||
},
|
||||
policyProducer: &stubKeyPolicyProducer{},
|
||||
importKey: importKey,
|
||||
cleanupRequired: true,
|
||||
wantErr: true,
|
||||
},
|
||||
"GetParametersForImport fails on existing": {
|
||||
client: &stubAWSClient{getParametersForImportErr: someErr},
|
||||
policyProducer: &stubKeyPolicyProducer{},
|
||||
importKey: importKey,
|
||||
wantErr: true,
|
||||
},
|
||||
"ImportKeyMaterial fails on existing": {
|
||||
client: &stubAWSClient{importKeyMaterialErr: someErr},
|
||||
policyProducer: &stubKeyPolicyProducer{},
|
||||
importKey: importKey,
|
||||
wantErr: true,
|
||||
},
|
||||
"errors during cleanup don't stop execution": {
|
||||
client: &stubAWSClient{
|
||||
describeKeyErr: &types.NotFoundException{},
|
||||
deleteAliasErr: someErr,
|
||||
createKeyID: "key-id",
|
||||
},
|
||||
policyProducer: &stubKeyPolicyProducer{createKeyPolicyErr: someErr},
|
||||
cleanupRequired: true,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
client := KMSClient{
|
||||
awsClient: tc.client,
|
||||
storage: storage.NewMemMapStorage(),
|
||||
policyProducer: tc.policyProducer,
|
||||
}
|
||||
|
||||
err := client.CreateKEK(context.Background(), "test-key", tc.importKey)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
if tc.cleanupRequired {
|
||||
assert.True(tc.client.cleanUpCalled, "failed to clean up")
|
||||
} else {
|
||||
assert.False(tc.client.cleanUpCalled, "cleaned up when not necessary")
|
||||
}
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type stubAWSClient struct {
|
||||
cleanUpCalled bool
|
||||
createAliasErr error
|
||||
createKeyErr error
|
||||
createKeyID string
|
||||
decryptErr error
|
||||
deleteAliasErr error
|
||||
describeKeyErr error
|
||||
encryptErr error
|
||||
generateDataKeyErr error
|
||||
generateDataKeyWithoutPlaintextErr error
|
||||
getParametersForImportErr error
|
||||
getParametersForImportPubKey []byte
|
||||
importKeyMaterialErr error
|
||||
putKeyPolicyErr error
|
||||
scheduleKeyDeletionErr error
|
||||
}
|
||||
|
||||
func (s *stubAWSClient) CreateAlias(ctx context.Context, params *kms.CreateAliasInput, optFns ...func(*kms.Options)) (*kms.CreateAliasOutput, error) {
|
||||
return &kms.CreateAliasOutput{}, s.createAliasErr
|
||||
}
|
||||
|
||||
func (s *stubAWSClient) CreateKey(ctx context.Context, params *kms.CreateKeyInput, optFns ...func(*kms.Options)) (*kms.CreateKeyOutput, error) {
|
||||
return &kms.CreateKeyOutput{KeyMetadata: &types.KeyMetadata{KeyId: aws.String(s.createKeyID)}}, s.createKeyErr
|
||||
}
|
||||
|
||||
func (s *stubAWSClient) Decrypt(ctx context.Context, params *kms.DecryptInput, optFns ...func(*kms.Options)) (*kms.DecryptOutput, error) {
|
||||
return &kms.DecryptOutput{}, s.decryptErr
|
||||
}
|
||||
|
||||
func (s *stubAWSClient) DeleteAlias(ctx context.Context, params *kms.DeleteAliasInput, optFns ...func(*kms.Options)) (*kms.DeleteAliasOutput, error) {
|
||||
return &kms.DeleteAliasOutput{}, s.deleteAliasErr
|
||||
}
|
||||
|
||||
func (s *stubAWSClient) DescribeKey(ctx context.Context, params *kms.DescribeKeyInput, optFns ...func(*kms.Options)) (*kms.DescribeKeyOutput, error) {
|
||||
return &kms.DescribeKeyOutput{KeyMetadata: &types.KeyMetadata{KeyId: params.KeyId}}, s.describeKeyErr
|
||||
}
|
||||
|
||||
func (s *stubAWSClient) Encrypt(ctx context.Context, params *kms.EncryptInput, optFns ...func(*kms.Options)) (*kms.EncryptOutput, error) {
|
||||
return &kms.EncryptOutput{}, s.encryptErr
|
||||
}
|
||||
|
||||
func (s *stubAWSClient) GenerateDataKey(ctx context.Context, params *kms.GenerateDataKeyInput, optFns ...func(*kms.Options)) (*kms.GenerateDataKeyOutput, error) {
|
||||
return &kms.GenerateDataKeyOutput{}, s.generateDataKeyErr
|
||||
}
|
||||
|
||||
func (s *stubAWSClient) GenerateDataKeyWithoutPlaintext(ctx context.Context, params *kms.GenerateDataKeyWithoutPlaintextInput, optFns ...func(*kms.Options)) (*kms.GenerateDataKeyWithoutPlaintextOutput, error) {
|
||||
return &kms.GenerateDataKeyWithoutPlaintextOutput{}, s.generateDataKeyWithoutPlaintextErr
|
||||
}
|
||||
|
||||
func (s *stubAWSClient) GetParametersForImport(ctx context.Context, params *kms.GetParametersForImportInput, optFns ...func(*kms.Options)) (*kms.GetParametersForImportOutput, error) {
|
||||
return &kms.GetParametersForImportOutput{
|
||||
PublicKey: s.getParametersForImportPubKey,
|
||||
}, s.getParametersForImportErr
|
||||
}
|
||||
|
||||
func (s *stubAWSClient) ImportKeyMaterial(ctx context.Context, params *kms.ImportKeyMaterialInput, optFns ...func(*kms.Options)) (*kms.ImportKeyMaterialOutput, error) {
|
||||
return &kms.ImportKeyMaterialOutput{}, s.importKeyMaterialErr
|
||||
}
|
||||
|
||||
func (s *stubAWSClient) PutKeyPolicy(ctx context.Context, params *kms.PutKeyPolicyInput, optFns ...func(*kms.Options)) (*kms.PutKeyPolicyOutput, error) {
|
||||
return &kms.PutKeyPolicyOutput{}, s.putKeyPolicyErr
|
||||
}
|
||||
|
||||
func (s *stubAWSClient) ScheduleKeyDeletion(ctx context.Context, params *kms.ScheduleKeyDeletionInput, optFns ...func(*kms.Options)) (*kms.ScheduleKeyDeletionOutput, error) {
|
||||
s.cleanUpCalled = true
|
||||
return &kms.ScheduleKeyDeletionOutput{}, s.scheduleKeyDeletionErr
|
||||
}
|
@ -9,154 +9,51 @@ package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms/util"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms/internal"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
|
||||
azurekeyvault "github.com/hashicorp/go-kms-wrapping/wrappers/azurekeyvault/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
vaultPrefix = "https://"
|
||||
// DefaultCloud is the suffix for the default Vault URL.
|
||||
DefaultCloud VaultSuffix = ".vault.azure.net/"
|
||||
// ChinaCloud is the suffix for Vaults in Azure China Cloud.
|
||||
ChinaCloud VaultSuffix = ".vault.azure.cn/"
|
||||
// USGovCloud is the suffix for Vaults in Azure US Government Cloud.
|
||||
USGovCloud VaultSuffix = ".vault.usgovcloudapi.net/"
|
||||
// GermanCloud is the suffix for Vaults in Azure German Cloud.
|
||||
GermanCloud VaultSuffix = ".vault.microsoftazure.de/"
|
||||
)
|
||||
|
||||
// VaultSuffix is the suffix added to a Vault name to create a valid Vault URL.
|
||||
type VaultSuffix string
|
||||
|
||||
type kmsClientAPI interface {
|
||||
SetSecret(ctx context.Context, secretName string, parameters azsecrets.SetSecretParameters, options *azsecrets.SetSecretOptions) (azsecrets.SetSecretResponse, error)
|
||||
GetSecret(ctx context.Context, secretName string, version string, options *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error)
|
||||
}
|
||||
|
||||
// KMSClient implements the CloudKMS interface for Azure Key Vault.
|
||||
type KMSClient struct {
|
||||
client kmsClientAPI
|
||||
storage kms.Storage
|
||||
kekID string
|
||||
}
|
||||
|
||||
// Opts are optional settings for AKV clients.
|
||||
type Opts struct {
|
||||
Credentials *azidentity.DefaultAzureCredentialOptions
|
||||
Keys *azkeys.ClientOptions
|
||||
Secrets *azsecrets.ClientOptions
|
||||
kms *internal.KMSClient
|
||||
}
|
||||
|
||||
// New initializes a KMS client for Azure Key Vault.
|
||||
func New(ctx context.Context, vaultName string, vaultType VaultSuffix, store kms.Storage, kekID string, opts *Opts) (*KMSClient, error) {
|
||||
if opts == nil {
|
||||
opts = &Opts{}
|
||||
}
|
||||
cred, err := azidentity.NewDefaultAzureCredential(opts.Credentials)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading credentials: %w", err)
|
||||
}
|
||||
client, err := azsecrets.NewClient(vaultPrefix+vaultName+string(vaultType), cred, opts.Secrets)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating azure secrets client: %w", err)
|
||||
}
|
||||
|
||||
// `azsecrets.NewClient()` does not error if the vault is non existent
|
||||
// Test here if we can reach the vault, and error otherwise
|
||||
pager := client.NewListSecretsPager(nil)
|
||||
if _, err := pager.NextPage(ctx); err != nil {
|
||||
return nil, fmt.Errorf("AKV not reachable: %w", err)
|
||||
}
|
||||
|
||||
func New(ctx context.Context, store kms.Storage, cfg uri.AzureConfig) (*KMSClient, error) {
|
||||
if store == nil {
|
||||
store = storage.NewMemMapStorage()
|
||||
return nil, errors.New("no storage backend provided for KMS")
|
||||
}
|
||||
return &KMSClient{client: client, storage: store, kekID: kekID}, nil
|
||||
|
||||
wrapper := azurekeyvault.NewWrapper()
|
||||
if _, err := wrapper.SetConfig(
|
||||
ctx,
|
||||
azurekeyvault.WithTenantId(cfg.TenantID),
|
||||
azurekeyvault.WithClientId(cfg.ClientID),
|
||||
azurekeyvault.WithClientSecret(cfg.ClientSecret),
|
||||
azurekeyvault.WithResource(string(cfg.VaultType)),
|
||||
azurekeyvault.WithVaultName(cfg.VaultName),
|
||||
azurekeyvault.WithKeyName(cfg.KeyName),
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("setting Azure Key Vault config: %w", err)
|
||||
}
|
||||
|
||||
return &KMSClient{
|
||||
kms: &internal.KMSClient{
|
||||
Storage: store,
|
||||
Wrapper: wrapper,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateKEK saves a new Key Encryption Key using Azure Key Vault.
|
||||
//
|
||||
// Keys are saved as software protected secrets.
|
||||
// If no key material is provided, a new random 32 Byte key is generated and imported to the Vault.
|
||||
func (c *KMSClient) CreateKEK(ctx context.Context, keyID string, key []byte) error {
|
||||
if len(key) == 0 {
|
||||
var err error
|
||||
key, err = util.GetRandomKey(config.SymmetricKeyLength)
|
||||
if err != nil {
|
||||
return fmt.Errorf("key generation: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Saving symmetric keys in Azure Key Vault requires encoding them to base64
|
||||
secretValue := azsecrets.SetSecretParameters{
|
||||
Value: to.Ptr(base64.StdEncoding.EncodeToString(key)),
|
||||
ContentType: to.Ptr("KeyEncryptionKey"),
|
||||
Tags: toAzureTags(config.KmsTags),
|
||||
}
|
||||
_, err := c.client.SetSecret(ctx, keyID, secretValue, &azsecrets.SetSecretOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("importing KEK to Azure Key Vault: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDEK decrypts a DEK from storage.
|
||||
// GetDEK fetches an encrypted Data Encryption Key from storage and decrypts it using a KEK stored in Azure Key Vault.
|
||||
func (c *KMSClient) GetDEK(ctx context.Context, keyID string, dekSize int) ([]byte, error) {
|
||||
kek, err := c.getKEK(ctx, c.kekID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading KEK from key vault: %w", err)
|
||||
}
|
||||
|
||||
encryptedDEK, err := c.storage.Get(ctx, keyID)
|
||||
if err != nil {
|
||||
if !errors.Is(err, storage.ErrDEKUnset) {
|
||||
return nil, fmt.Errorf("loading encrypted DEK from storage: %w", err)
|
||||
}
|
||||
|
||||
// If the DEK does not exist we generate a new random DEK and save it to storage
|
||||
newDEK, err := util.GetRandomKey(dekSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("key generation: %w", err)
|
||||
}
|
||||
return newDEK, c.putDEK(ctx, keyID, kek, newDEK)
|
||||
}
|
||||
|
||||
// Azure Key Vault does not support crypto operations with secrets, so we do the unwrapping ourselves
|
||||
return util.UnwrapAES(encryptedDEK, kek)
|
||||
return c.kms.GetDEK(ctx, keyID, dekSize)
|
||||
}
|
||||
|
||||
// putDEK encrypts a DEK and saves it to storage.
|
||||
func (c *KMSClient) putDEK(ctx context.Context, keyID string, kek, plainDEK []byte) error {
|
||||
// Azure Key Vault does not support crypto operations with secrets, so we do the wrapping ourselves
|
||||
encryptedDEK, err := util.WrapAES(plainDEK, kek)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypting DEK: %w", err)
|
||||
}
|
||||
return c.storage.Put(ctx, keyID, encryptedDEK)
|
||||
}
|
||||
|
||||
// getKEK loads a Key Encryption Key from Azure Key Vault.
|
||||
func (c *KMSClient) getKEK(ctx context.Context, kekID string) ([]byte, error) {
|
||||
res, err := c.client.GetSecret(ctx, kekID, "", nil)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "SecretNotFound") {
|
||||
return nil, kms.ErrKEKUnknown
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Keys are saved in base64, decode them
|
||||
return base64.StdEncoding.DecodeString(*res.Value)
|
||||
}
|
||||
// Close is a no-op for Azure.
|
||||
func (c *KMSClient) Close() {}
|
||||
|
@ -1,167 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
goleak.VerifyTestMain(m,
|
||||
// https://github.com/census-instrumentation/opencensus-go/issues/1262
|
||||
goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"),
|
||||
)
|
||||
}
|
||||
|
||||
type stubAzureClient struct {
|
||||
setSecretCalled bool
|
||||
setSecretErr error
|
||||
getSecretErr error
|
||||
secret []byte
|
||||
}
|
||||
|
||||
func (s *stubAzureClient) SetSecret(ctx context.Context, secretName string, parameters azsecrets.SetSecretParameters, options *azsecrets.SetSecretOptions) (azsecrets.SetSecretResponse, error) {
|
||||
s.setSecretCalled = true
|
||||
return azsecrets.SetSecretResponse{}, s.setSecretErr
|
||||
}
|
||||
|
||||
func (s *stubAzureClient) GetSecret(ctx context.Context, secretName string, version string, options *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error) {
|
||||
return azsecrets.GetSecretResponse{
|
||||
SecretBundle: azsecrets.SecretBundle{
|
||||
Value: to.Ptr(base64.StdEncoding.EncodeToString(s.secret)),
|
||||
},
|
||||
}, s.getSecretErr
|
||||
}
|
||||
|
||||
func TestKMSCreateKEK(t *testing.T) {
|
||||
someErr := errors.New("error")
|
||||
importKey := []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
||||
|
||||
testCases := map[string]struct {
|
||||
client *stubAzureClient
|
||||
importKey []byte
|
||||
wantErr bool
|
||||
}{
|
||||
"create new kek successful": {
|
||||
client: &stubAzureClient{},
|
||||
},
|
||||
"import kek successful": {
|
||||
client: &stubAzureClient{},
|
||||
importKey: importKey,
|
||||
},
|
||||
"SetSecret fails on new": {
|
||||
client: &stubAzureClient{setSecretErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
"SetSecret fails on import": {
|
||||
client: &stubAzureClient{setSecretErr: someErr},
|
||||
importKey: importKey,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
client := &KMSClient{
|
||||
client: tc.client,
|
||||
}
|
||||
|
||||
err := client.CreateKEK(context.Background(), "test-key", tc.importKey)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.True(tc.client.setSecretCalled)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKMSGetDEK(t *testing.T) {
|
||||
someErr := errors.New("error")
|
||||
wrapKey := []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
||||
|
||||
testCases := map[string]struct {
|
||||
client kmsClientAPI
|
||||
storage kms.Storage
|
||||
wantErr bool
|
||||
}{
|
||||
"successful for new key": {
|
||||
client: &stubAzureClient{secret: wrapKey},
|
||||
storage: storage.NewMemMapStorage(),
|
||||
},
|
||||
"successful for existing key": {
|
||||
// test keys taken from `kms/util/crypto_test.go`
|
||||
client: &stubAzureClient{secret: []byte{0xD6, 0x8A, 0xED, 0xF5, 0xDB, 0x89, 0x95, 0x66, 0xA9, 0xFF, 0xD9, 0x31, 0x27, 0x4E, 0x30, 0x2D, 0x21, 0xA9, 0x46, 0x21, 0x16, 0x6C, 0x16, 0x17, 0xD1, 0x96, 0x5D, 0xB2, 0xE9, 0x0E, 0x96, 0xD1}},
|
||||
storage: &stubStorage{key: []byte{0x14, 0x48, 0xC4, 0xEA, 0x4B, 0x4B, 0xCA, 0xE4, 0x5A, 0xD4, 0xCC, 0xE3, 0xF7, 0xDD, 0xD5, 0x78, 0xA5, 0xA9, 0xEF, 0x9A, 0x93, 0x36, 0x09, 0xD6, 0x23, 0x01, 0xF5, 0x5F, 0xE1, 0x20, 0xDD, 0xFC, 0xBC, 0xF3, 0xA9, 0x67, 0x8B, 0x89, 0x54, 0x96}},
|
||||
},
|
||||
"Get from storage fails": {
|
||||
client: &stubAzureClient{},
|
||||
storage: &stubStorage{getErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
"Put to storage fails": {
|
||||
client: &stubAzureClient{secret: wrapKey},
|
||||
storage: &stubStorage{
|
||||
getErr: storage.ErrDEKUnset,
|
||||
putErr: someErr,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"GetSecret fails": {
|
||||
client: &stubAzureClient{getSecretErr: someErr},
|
||||
storage: storage.NewMemMapStorage(),
|
||||
wantErr: true,
|
||||
},
|
||||
"GetSecret fails with unknown kek": {
|
||||
client: &stubAzureClient{getSecretErr: errors.New("SecretNotFound")},
|
||||
storage: storage.NewMemMapStorage(),
|
||||
wantErr: true,
|
||||
},
|
||||
"key wrapping fails": {
|
||||
client: &stubAzureClient{secret: []byte{0x1}},
|
||||
storage: storage.NewMemMapStorage(),
|
||||
wantErr: true,
|
||||
},
|
||||
"key unwrapping fails": {
|
||||
client: &stubAzureClient{secret: wrapKey},
|
||||
storage: &stubStorage{key: []byte{0x1}},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
client := KMSClient{
|
||||
client: tc.client,
|
||||
storage: tc.storage,
|
||||
}
|
||||
|
||||
dek, err := client.GetDEK(context.Background(), "volume-01", 32)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.Len(dek, 32)
|
||||
assert.NoError(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,171 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms/util"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
|
||||
)
|
||||
|
||||
type hsmClientAPI interface {
|
||||
CreateKey(ctx context.Context, name string, parameters azkeys.CreateKeyParameters, options *azkeys.CreateKeyOptions) (azkeys.CreateKeyResponse, error)
|
||||
ImportKey(ctx context.Context, name string, parameters azkeys.ImportKeyParameters, options *azkeys.ImportKeyOptions) (azkeys.ImportKeyResponse, error)
|
||||
GetKey(ctx context.Context, name string, version string, options *azkeys.GetKeyOptions) (azkeys.GetKeyResponse, error)
|
||||
UnwrapKey(ctx context.Context, name string, version string, parameters azkeys.KeyOperationsParameters, options *azkeys.UnwrapKeyOptions) (azkeys.UnwrapKeyResponse, error)
|
||||
WrapKey(ctx context.Context, name string, version string, parameters azkeys.KeyOperationsParameters, options *azkeys.WrapKeyOptions) (azkeys.WrapKeyResponse, error)
|
||||
}
|
||||
|
||||
// HSMDefaultCloud is the suffix for HSM Vaults.
|
||||
const HSMDefaultCloud VaultSuffix = ".managedhsm.azure.net/"
|
||||
|
||||
// HSMClient implements the CloudKMS interface for Azure managed HSM.
|
||||
type HSMClient struct {
|
||||
credentials azcore.TokenCredential
|
||||
client hsmClientAPI
|
||||
storage kms.Storage
|
||||
vaultURL string
|
||||
kekID string
|
||||
}
|
||||
|
||||
// NewHSM initializes a KMS client for Azure manged HSM Key Vault.
|
||||
func NewHSM(ctx context.Context, vaultName string, store kms.Storage, kekID string, opts *Opts) (*HSMClient, error) {
|
||||
if opts == nil {
|
||||
opts = &Opts{}
|
||||
}
|
||||
cred, err := azidentity.NewDefaultAzureCredential(opts.Credentials)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading credentials: %w", err)
|
||||
}
|
||||
|
||||
vaultURL := vaultPrefix + vaultName + string(HSMDefaultCloud)
|
||||
client, err := azkeys.NewClient(vaultURL, cred, opts.Keys)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating azure key vault client: %w", err)
|
||||
}
|
||||
|
||||
// `azkeys.NewClient()` does not error if the vault is non existent
|
||||
// Test here if we can reach the vault, and error otherwise
|
||||
pager := client.NewListKeysPager(&azkeys.ListKeysOptions{MaxResults: to.Ptr[int32](2)})
|
||||
if _, err := pager.NextPage(ctx); err != nil {
|
||||
return nil, fmt.Errorf("HSM not reachable: %w", err)
|
||||
}
|
||||
|
||||
if store == nil {
|
||||
store = storage.NewMemMapStorage()
|
||||
}
|
||||
|
||||
return &HSMClient{
|
||||
vaultURL: vaultURL,
|
||||
client: client,
|
||||
credentials: cred,
|
||||
storage: store,
|
||||
kekID: kekID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateKEK creates a new Key Encryption Key using Azure managed HSM.
|
||||
//
|
||||
// If no key material is provided, a new key is generated by the HSM, otherwise the key material is used to import the key.
|
||||
func (c *HSMClient) CreateKEK(ctx context.Context, keyID string, key []byte) error {
|
||||
if len(key) == 0 {
|
||||
if _, err := c.client.CreateKey(ctx, keyID, azkeys.CreateKeyParameters{
|
||||
Kty: to.Ptr(azkeys.JSONWebKeyTypeOctHSM),
|
||||
KeySize: to.Ptr[int32](config.SymmetricKeyLength * 8),
|
||||
Tags: toAzureTags(config.KmsTags),
|
||||
}, &azkeys.CreateKeyOptions{}); err != nil {
|
||||
return fmt.Errorf("creating new KEK: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
jwk := azkeys.JSONWebKey{
|
||||
K: key,
|
||||
KeyOps: []*string{
|
||||
to.Ptr("wrapKey"),
|
||||
to.Ptr("unwrapKey"),
|
||||
},
|
||||
Kty: to.Ptr(azkeys.JSONWebKeyTypeOctHSM),
|
||||
}
|
||||
importParams := azkeys.ImportKeyParameters{
|
||||
HSM: to.Ptr(true),
|
||||
KeyAttributes: &azkeys.KeyAttributes{
|
||||
Enabled: to.Ptr(true),
|
||||
},
|
||||
Tags: toAzureTags(config.KmsTags),
|
||||
Key: &jwk,
|
||||
}
|
||||
|
||||
if _, err := c.client.ImportKey(ctx, keyID, importParams, &azkeys.ImportKeyOptions{}); err != nil {
|
||||
return fmt.Errorf("importing KEK to Azure HSM: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDEK loads an encrypted DEK from storage and unwraps it using an HSM-backed key.
|
||||
func (c *HSMClient) GetDEK(ctx context.Context, keyID string, dekSize int) ([]byte, error) {
|
||||
encryptedDEK, err := c.storage.Get(ctx, keyID)
|
||||
if err != nil {
|
||||
if !errors.Is(err, storage.ErrDEKUnset) {
|
||||
return nil, fmt.Errorf("loading encrypted DEK from storage: %w", err)
|
||||
}
|
||||
|
||||
// If the DEK does not exist we generate a new random DEK and save it to storage
|
||||
newDEK, err := util.GetRandomKey(dekSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("key generation: %w", err)
|
||||
}
|
||||
if err := c.putDEK(ctx, c.kekID, keyID, newDEK); err != nil {
|
||||
return nil, fmt.Errorf("creating new DEK: %w", err)
|
||||
}
|
||||
|
||||
return newDEK, nil
|
||||
}
|
||||
|
||||
params := azkeys.KeyOperationsParameters{
|
||||
Algorithm: to.Ptr(azkeys.JSONWebKeyEncryptionAlgorithmA256KW),
|
||||
Value: encryptedDEK,
|
||||
}
|
||||
res, err := c.client.UnwrapKey(ctx, c.kekID, "", params, &azkeys.UnwrapKeyOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unwrapping key: %w", err)
|
||||
}
|
||||
|
||||
return res.Result, nil
|
||||
}
|
||||
|
||||
// putDEK wraps a key using an HSM-backed key and saves it to storage.
|
||||
func (c *HSMClient) putDEK(ctx context.Context, kekID, keyID string, plainDEK []byte) error {
|
||||
params := azkeys.KeyOperationsParameters{
|
||||
Algorithm: to.Ptr(azkeys.JSONWebKeyEncryptionAlgorithmA256KW),
|
||||
Value: plainDEK,
|
||||
}
|
||||
res, err := c.client.WrapKey(ctx, kekID, "", params, &azkeys.WrapKeyOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("wrapping key: %w", err)
|
||||
}
|
||||
|
||||
return c.storage.Put(ctx, keyID, res.Result)
|
||||
}
|
||||
|
||||
// toAzureTags converts a map of tags to map of tag pointers.
|
||||
func toAzureTags(tags map[string]string) map[string]*string {
|
||||
tagsOut := make(map[string]*string)
|
||||
for k, v := range tags {
|
||||
tagsOut[k] = to.Ptr(v)
|
||||
}
|
||||
return tagsOut
|
||||
}
|
@ -1,220 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type stubHSMClient struct {
|
||||
keyCreated bool
|
||||
createOCTKeyErr error
|
||||
importKeyErr error
|
||||
getKeyErr error
|
||||
keyID string
|
||||
unwrapKeyErr error
|
||||
unwrapKeyResult []byte
|
||||
wrapKeyErr error
|
||||
}
|
||||
|
||||
func (s *stubHSMClient) CreateKey(ctx context.Context, name string, parameters azkeys.CreateKeyParameters, options *azkeys.CreateKeyOptions) (azkeys.CreateKeyResponse, error) {
|
||||
s.keyCreated = true
|
||||
return azkeys.CreateKeyResponse{}, s.createOCTKeyErr
|
||||
}
|
||||
|
||||
func (s *stubHSMClient) ImportKey(ctx context.Context, name string, parameters azkeys.ImportKeyParameters, options *azkeys.ImportKeyOptions) (azkeys.ImportKeyResponse, error) {
|
||||
s.keyCreated = true
|
||||
return azkeys.ImportKeyResponse{}, s.importKeyErr
|
||||
}
|
||||
|
||||
func (s *stubHSMClient) GetKey(ctx context.Context, name string, version string, options *azkeys.GetKeyOptions) (azkeys.GetKeyResponse, error) {
|
||||
return azkeys.GetKeyResponse{
|
||||
KeyBundle: azkeys.KeyBundle{
|
||||
Key: &azkeys.JSONWebKey{
|
||||
KID: to.Ptr(azkeys.ID(s.keyID)),
|
||||
},
|
||||
},
|
||||
}, s.getKeyErr
|
||||
}
|
||||
|
||||
func (s *stubHSMClient) UnwrapKey(ctx context.Context, name string, version string, parameters azkeys.KeyOperationsParameters, options *azkeys.UnwrapKeyOptions) (azkeys.UnwrapKeyResponse, error) {
|
||||
return azkeys.UnwrapKeyResponse{
|
||||
KeyOperationResult: azkeys.KeyOperationResult{
|
||||
Result: s.unwrapKeyResult,
|
||||
},
|
||||
}, s.unwrapKeyErr
|
||||
}
|
||||
|
||||
func (s *stubHSMClient) WrapKey(ctx context.Context, name string, version string, parameters azkeys.KeyOperationsParameters, options *azkeys.WrapKeyOptions) (azkeys.WrapKeyResponse, error) {
|
||||
return azkeys.WrapKeyResponse{}, s.wrapKeyErr
|
||||
}
|
||||
|
||||
type stubStorage struct {
|
||||
key []byte
|
||||
getErr error
|
||||
putErr error
|
||||
}
|
||||
|
||||
func (s *stubStorage) Get(context.Context, string) ([]byte, error) {
|
||||
return s.key, s.getErr
|
||||
}
|
||||
|
||||
func (s *stubStorage) Put(context.Context, string, []byte) error {
|
||||
return s.putErr
|
||||
}
|
||||
|
||||
func TestHSMCreateKEK(t *testing.T) {
|
||||
someErr := errors.New("error")
|
||||
importKey := []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
||||
|
||||
testCases := map[string]struct {
|
||||
client *stubHSMClient
|
||||
importKey []byte
|
||||
wantErr bool
|
||||
}{
|
||||
"create new kek successful": {
|
||||
client: &stubHSMClient{},
|
||||
},
|
||||
"CreateOCTKey fails": {
|
||||
client: &stubHSMClient{createOCTKeyErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
"import key successful": {
|
||||
client: &stubHSMClient{},
|
||||
importKey: importKey,
|
||||
},
|
||||
"ImportKey fails": {
|
||||
client: &stubHSMClient{importKeyErr: someErr},
|
||||
importKey: importKey,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
client := HSMClient{
|
||||
client: tc.client,
|
||||
storage: storage.NewMemMapStorage(),
|
||||
}
|
||||
|
||||
err := client.CreateKEK(context.Background(), "test-key", tc.importKey)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.True(tc.client.keyCreated)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHSMGetNewDEK(t *testing.T) {
|
||||
someErr := errors.New("error")
|
||||
keyID := "https://test.managedhsm.azure.net/keys/test-key/test-key-version"
|
||||
|
||||
testCases := map[string]struct {
|
||||
client hsmClientAPI
|
||||
storage kms.Storage
|
||||
wantErr bool
|
||||
}{
|
||||
"successful": {
|
||||
client: &stubHSMClient{keyID: keyID},
|
||||
storage: storage.NewMemMapStorage(),
|
||||
},
|
||||
"Get from storage fails": {
|
||||
client: &stubHSMClient{keyID: keyID},
|
||||
storage: &stubStorage{getErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
"Put to storage fails": {
|
||||
client: &stubHSMClient{keyID: keyID},
|
||||
storage: &stubStorage{
|
||||
getErr: storage.ErrDEKUnset,
|
||||
putErr: someErr,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"WrapKey fails": {
|
||||
client: &stubHSMClient{keyID: keyID, wrapKeyErr: someErr},
|
||||
storage: storage.NewMemMapStorage(),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
client := HSMClient{
|
||||
client: tc.client,
|
||||
storage: tc.storage,
|
||||
}
|
||||
|
||||
dek, err := client.GetDEK(context.Background(), "volume-01", 32)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.Len(dek, 32)
|
||||
assert.NoError(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHSMGetExistingDEK(t *testing.T) {
|
||||
someErr := errors.New("error")
|
||||
keyVersion := "https://test.managedhsm.azure.net/keys/test-key/test-key-version"
|
||||
testKey := []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
||||
|
||||
testCases := map[string]struct {
|
||||
client hsmClientAPI
|
||||
wantErr bool
|
||||
}{
|
||||
"successful": {
|
||||
client: &stubHSMClient{keyID: keyVersion, unwrapKeyResult: testKey},
|
||||
},
|
||||
"UnwrapKey fails": {
|
||||
client: &stubHSMClient{keyID: keyVersion, unwrapKeyErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
keyID := "volume-01"
|
||||
storage := storage.NewMemMapStorage()
|
||||
require.NoError(storage.Put(context.Background(), keyID, testKey))
|
||||
|
||||
client := HSMClient{
|
||||
client: tc.client,
|
||||
storage: storage,
|
||||
}
|
||||
|
||||
dek, err := client.GetDEK(context.Background(), keyID, len(testKey))
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.Len(dek, len(testKey))
|
||||
assert.NoError(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -40,12 +40,6 @@ func New(key []byte, salt []byte) (*KMS, error) {
|
||||
return &KMS{masterKey: key, salt: salt}, nil
|
||||
}
|
||||
|
||||
// CreateKEK sets the ClusterKMS masterKey.
|
||||
func (c *KMS) CreateKEK(ctx context.Context, keyID string, kek []byte) error {
|
||||
c.masterKey = kek
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDEK derives a key from the KMS masterKey.
|
||||
func (c *KMS) GetDEK(ctx context.Context, dekID string, dekSize int) ([]byte, error) {
|
||||
if len(c.masterKey) == 0 {
|
||||
@ -53,3 +47,6 @@ func (c *KMS) GetDEK(ctx context.Context, dekID string, dekSize int) ([]byte, er
|
||||
}
|
||||
return crypto.DeriveKey(c.masterKey, c.salt, []byte(dekID), uint(dekSize))
|
||||
}
|
||||
|
||||
// Close is a no-op for cKMS.
|
||||
func (c *KMS) Close() {}
|
||||
|
@ -103,8 +103,7 @@ func TestVectorsHKDF(t *testing.T) {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
assert.NoError(err)
|
||||
require.NoError(kms.CreateKEK(context.Background(), "", tc.kek))
|
||||
require.NoError(err)
|
||||
|
||||
out, err := kms.GetDEK(context.Background(), tc.dekID, int(tc.dekSize))
|
||||
require.NoError(err)
|
||||
|
@ -8,328 +8,63 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
Package gcp implements a KMS backend for Google Cloud KMS.
|
||||
|
||||
The following permissions are required for the service account used to authenticate with GCP:
|
||||
|
||||
- cloudkms.cryptoKeyVersions.create
|
||||
|
||||
- cloudkms.cryptoKeyVersions.update
|
||||
|
||||
- cloudkms.cryptoKeyVersions.useToDecrypt
|
||||
|
||||
- cloudkms.cryptoKeyVersions.useToEncrypt
|
||||
|
||||
- cloudkms.importJobs.create
|
||||
|
||||
- cloudkms.importJobs.get
|
||||
|
||||
- cloudkms.importJobs.useToImport
|
||||
- cloudkms.cryptoKeys.get
|
||||
- cloudkms.cryptoKeys.encrypt
|
||||
- cloudkms.cryptoKeys.decrypt
|
||||
*/
|
||||
package gcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
kms "cloud.google.com/go/kms/apiv1"
|
||||
"cloud.google.com/go/kms/apiv1/kmspb"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/config"
|
||||
kmsInterface "github.com/edgelesssys/constellation/v2/internal/kms/kms"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms/util"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
|
||||
"github.com/googleapis/gax-go/v2"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms/internal"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
|
||||
gcpckms "github.com/hashicorp/go-kms-wrapping/wrappers/gcpckms/v2"
|
||||
)
|
||||
|
||||
type clientAPI interface {
|
||||
io.Closer
|
||||
CreateCryptoKey(context.Context, *kmspb.CreateCryptoKeyRequest, ...gax.CallOption) (*kmspb.CryptoKey, error)
|
||||
CreateImportJob(context.Context, *kmspb.CreateImportJobRequest, ...gax.CallOption) (*kmspb.ImportJob, error)
|
||||
Decrypt(context.Context, *kmspb.DecryptRequest, ...gax.CallOption) (*kmspb.DecryptResponse, error)
|
||||
Encrypt(context.Context, *kmspb.EncryptRequest, ...gax.CallOption) (*kmspb.EncryptResponse, error)
|
||||
GetKeyRing(context.Context, *kmspb.GetKeyRingRequest, ...gax.CallOption) (*kmspb.KeyRing, error)
|
||||
ImportCryptoKeyVersion(context.Context, *kmspb.ImportCryptoKeyVersionRequest, ...gax.CallOption) (*kmspb.CryptoKeyVersion, error)
|
||||
UpdateCryptoKeyPrimaryVersion(context.Context, *kmspb.UpdateCryptoKeyPrimaryVersionRequest, ...gax.CallOption) (*kmspb.CryptoKey, error)
|
||||
GetImportJob(context.Context, *kmspb.GetImportJobRequest, ...gax.CallOption) (*kmspb.ImportJob, error)
|
||||
}
|
||||
|
||||
// KMSClient implements the CloudKMS interface for Google Cloud Platform.
|
||||
type KMSClient struct {
|
||||
projectID string
|
||||
locationID string
|
||||
keyRingID string
|
||||
newClient func(ctx context.Context, opts ...option.ClientOption) (clientAPI, error)
|
||||
waitBackoffLimit int
|
||||
storage kmsInterface.Storage
|
||||
protectionLevel kmspb.ProtectionLevel
|
||||
kekID string
|
||||
opts []gax.CallOption
|
||||
kms *internal.KMSClient
|
||||
client io.Closer
|
||||
}
|
||||
|
||||
// New initializes a KMS client for Google Cloud Platform.
|
||||
func New(ctx context.Context, projectID, locationID, keyRingID string, store kmsInterface.Storage, protectionLvl kmspb.ProtectionLevel, kekID string, opts ...gax.CallOption) (*KMSClient, error) {
|
||||
func New(ctx context.Context, store kmsInterface.Storage, cfg uri.GCPConfig) (*KMSClient, error) {
|
||||
if store == nil {
|
||||
store = storage.NewMemMapStorage()
|
||||
return nil, errors.New("no storage backend provided for KMS")
|
||||
}
|
||||
|
||||
if protectionLvl != kmspb.ProtectionLevel_SOFTWARE && protectionLvl != kmspb.ProtectionLevel_HSM {
|
||||
protectionLvl = kmspb.ProtectionLevel_SOFTWARE
|
||||
wrapper := gcpckms.NewWrapper()
|
||||
if _, err := wrapper.SetConfig(
|
||||
context.Background(),
|
||||
gcpckms.WithProject(cfg.ProjectID),
|
||||
gcpckms.WithRegion(cfg.Location),
|
||||
gcpckms.WithKeyRing(cfg.KeyRing),
|
||||
gcpckms.WithCryptoKey(cfg.KeyName),
|
||||
gcpckms.WithCredentials(cfg.CredentialsPath),
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("setting GCP KMS config: %w", err)
|
||||
}
|
||||
|
||||
c := &KMSClient{
|
||||
projectID: projectID,
|
||||
locationID: locationID,
|
||||
keyRingID: keyRingID,
|
||||
newClient: keyManagementClientFactory,
|
||||
waitBackoffLimit: 10,
|
||||
storage: store,
|
||||
protectionLevel: protectionLvl,
|
||||
kekID: kekID,
|
||||
opts: opts,
|
||||
}
|
||||
|
||||
// test if the KMS can be reached with the given configuration
|
||||
if err := c.testConnection(ctx); err != nil {
|
||||
return nil, fmt.Errorf("testing connection to GCP KMS: %w", err)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// CreateKEK creates a new Key Encryption Key using Google Key Management System.
|
||||
//
|
||||
// If no key material is provided, a new key is generated by Google's KMS, otherwise the key material is used to import the key.
|
||||
func (c *KMSClient) CreateKEK(ctx context.Context, keyID string, key []byte) error {
|
||||
client, err := c.newClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
if len(key) == 0 {
|
||||
_, err := c.createNewKEK(ctx, keyID, client, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating new KEK in Google KMS: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = c.importKEK(ctx, keyID, key, client)
|
||||
if err != nil {
|
||||
return fmt.Errorf("importing KEK to Google KMS: %w", err)
|
||||
}
|
||||
return nil
|
||||
return &KMSClient{
|
||||
kms: &internal.KMSClient{
|
||||
Storage: store,
|
||||
Wrapper: wrapper,
|
||||
},
|
||||
client: wrapper.Client(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetDEK fetches an encrypted Data Encryption Key from storage and decrypts it using a KEK stored in Google's KMS.
|
||||
func (c *KMSClient) GetDEK(ctx context.Context, keyID string, dekSize int) ([]byte, error) {
|
||||
client, err := c.newClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
encryptedDEK, err := c.storage.Get(ctx, keyID)
|
||||
if err != nil {
|
||||
if !errors.Is(err, storage.ErrDEKUnset) {
|
||||
return nil, fmt.Errorf("loading encrypted DEK from storage: %w", err)
|
||||
}
|
||||
|
||||
// If the DEK does not exist we generate a new random DEK and save it to storage
|
||||
newDEK, err := util.GetRandomKey(dekSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("key generation: %w", err)
|
||||
}
|
||||
return newDEK, c.putDEK(ctx, client, c.kekID, keyID, newDEK)
|
||||
}
|
||||
|
||||
request := &kmspb.DecryptRequest{
|
||||
Name: fmt.Sprintf("projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s", c.projectID, c.locationID, c.keyRingID, c.kekID),
|
||||
Ciphertext: encryptedDEK,
|
||||
}
|
||||
|
||||
res, err := client.Decrypt(ctx, request, c.opts...)
|
||||
if err != nil {
|
||||
if status.Convert(err).Code() == codes.NotFound {
|
||||
return nil, kmsInterface.ErrKEKUnknown
|
||||
}
|
||||
return nil, fmt.Errorf("decrypting DEK: %w", err)
|
||||
}
|
||||
|
||||
return res.GetPlaintext(), nil
|
||||
return c.kms.GetDEK(ctx, keyID, dekSize)
|
||||
}
|
||||
|
||||
// putDEK encrypts a Data Encryption Key using a KEK stored in Google's KMS and saves it to storage.
|
||||
func (c *KMSClient) putDEK(ctx context.Context, client clientAPI, kekID, keyID string, plainDEK []byte) error {
|
||||
request := &kmspb.EncryptRequest{
|
||||
Name: fmt.Sprintf("projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s", c.projectID, c.locationID, c.keyRingID, kekID),
|
||||
Plaintext: plainDEK,
|
||||
}
|
||||
|
||||
res, err := client.Encrypt(ctx, request, c.opts...)
|
||||
if err != nil {
|
||||
if status.Convert(err).Code() == codes.NotFound {
|
||||
return kmsInterface.ErrKEKUnknown
|
||||
}
|
||||
return fmt.Errorf("encrypting DEK: %w", err)
|
||||
}
|
||||
|
||||
return c.storage.Put(ctx, keyID, res.Ciphertext)
|
||||
}
|
||||
|
||||
// createNewKEK creates a new symmetric Crypto Key in Google's KMS.
|
||||
func (c *KMSClient) createNewKEK(ctx context.Context, keyID string, client clientAPI, importOnly bool) (*kmspb.CryptoKey, error) {
|
||||
request := &kmspb.CreateCryptoKeyRequest{
|
||||
Parent: fmt.Sprintf("projects/%s/locations/%s/keyRings/%s", c.projectID, c.locationID, c.keyRingID),
|
||||
CryptoKeyId: keyID,
|
||||
CryptoKey: &kmspb.CryptoKey{
|
||||
Purpose: kmspb.CryptoKey_ENCRYPT_DECRYPT,
|
||||
Labels: map[string]string{
|
||||
"created-by": "constellation-kms-client",
|
||||
"component": "constellation-kek",
|
||||
},
|
||||
VersionTemplate: &kmspb.CryptoKeyVersionTemplate{
|
||||
ProtectionLevel: c.protectionLevel,
|
||||
Algorithm: kmspb.CryptoKeyVersion_GOOGLE_SYMMETRIC_ENCRYPTION,
|
||||
},
|
||||
ImportOnly: importOnly,
|
||||
},
|
||||
SkipInitialVersionCreation: importOnly,
|
||||
}
|
||||
|
||||
return client.CreateCryptoKey(ctx, request, c.opts...)
|
||||
}
|
||||
|
||||
// importKEK imports a symmetric Crypto Key to Google's KMS-
|
||||
//
|
||||
// Keys in the Google KMS can not be removed, only disabled and/or key material destroyed.
|
||||
// Since we create the initial key with `SkipInitialVersionCreation=true`, no key material is created and we do not perform any cleanup on failure.
|
||||
func (c *KMSClient) importKEK(ctx context.Context, keyID string, key []byte, client clientAPI) (*kmspb.CryptoKey, error) {
|
||||
// we need an empty crypto key to import into
|
||||
parentKey, err := c.createNewKEK(ctx, keyID, client, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create import job
|
||||
jobName := fmt.Sprintf("import-job-%s", keyID)
|
||||
request := &kmspb.CreateImportJobRequest{
|
||||
Parent: fmt.Sprintf("projects/%s/locations/%s/keyRings/%s", c.projectID, c.locationID, c.keyRingID),
|
||||
ImportJobId: jobName,
|
||||
ImportJob: &kmspb.ImportJob{
|
||||
ImportMethod: kmspb.ImportJob_RSA_OAEP_4096_SHA1_AES_256,
|
||||
ProtectionLevel: c.protectionLevel,
|
||||
},
|
||||
}
|
||||
_, err = client.CreateImportJob(ctx, request, c.opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
impRes, ok := c.waitBackoff(ctx, jobName, client)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("import job was not active after %d tries, giving up", c.waitBackoffLimit)
|
||||
}
|
||||
|
||||
// Wrap the to be imported key using a public RSA key from the created import job and an ephemeral AES as specified here: https://cloud.google.com/kms/docs/wrapping-a-key
|
||||
wrappingPublicKey, err := util.ParsePEMtoPublicKeyRSA([]byte(impRes.PublicKey.GetPem()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wrappedKey, err := wrapCryptoKey(key, wrappingPublicKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wrapping public key: %w", err)
|
||||
}
|
||||
|
||||
// Perform the actual key import
|
||||
importReq := &kmspb.ImportCryptoKeyVersionRequest{
|
||||
Parent: parentKey.GetName(),
|
||||
Algorithm: kmspb.CryptoKeyVersion_GOOGLE_SYMMETRIC_ENCRYPTION,
|
||||
ImportJob: fmt.Sprintf("projects/%s/locations/%s/keyRings/%s/importJobs/%s", c.projectID, c.locationID, c.keyRingID, jobName),
|
||||
WrappedKeyMaterial: &kmspb.ImportCryptoKeyVersionRequest_RsaAesWrappedKey{
|
||||
RsaAesWrappedKey: wrappedKey,
|
||||
},
|
||||
}
|
||||
res, err := client.ImportCryptoKeyVersion(ctx, importReq, c.opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set the imported key as the primary key version
|
||||
newVersion := strings.Split(res.GetName(), "/")
|
||||
updateRequest := &kmspb.UpdateCryptoKeyPrimaryVersionRequest{
|
||||
Name: parentKey.GetName(),
|
||||
CryptoKeyVersionId: newVersion[len(newVersion)-1], // We only need the Version ID of the imported key, not the full resource name
|
||||
}
|
||||
return client.UpdateCryptoKeyPrimaryVersion(ctx, updateRequest, c.opts...)
|
||||
}
|
||||
|
||||
// waitBackoff is a utility function to wait for the creation of an import job.
|
||||
func (c *KMSClient) waitBackoff(ctx context.Context, jobName string, client clientAPI) (*kmspb.ImportJob, bool) {
|
||||
for i := 0; i < c.waitBackoffLimit; i++ {
|
||||
res, err := client.GetImportJob(ctx, &kmspb.GetImportJobRequest{
|
||||
Name: fmt.Sprintf("projects/%s/locations/%s/keyRings/%s/importJobs/%s", c.projectID, c.locationID, c.keyRingID, jobName),
|
||||
}, c.opts...)
|
||||
if (err == nil) && (res.State == kmspb.ImportJob_ACTIVE) {
|
||||
return res, true
|
||||
}
|
||||
|
||||
// wait for increasingly longer time until we either reach the preset limit or get an active job
|
||||
time.Sleep(time.Second * 5 * time.Duration(i))
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// testConnection checks if the KMS is reachable with the given configuration.
|
||||
func (c *KMSClient) testConnection(ctx context.Context) error {
|
||||
client, err := c.newClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
if _, err := client.GetKeyRing(ctx, &kmspb.GetKeyRingRequest{
|
||||
Name: fmt.Sprintf("projects/%s/locations/%s/keyRings/%s", c.projectID, c.locationID, c.keyRingID),
|
||||
}); err != nil {
|
||||
return fmt.Errorf("GCP KMS not reachable: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func keyManagementClientFactory(ctx context.Context, opts ...option.ClientOption) (clientAPI, error) {
|
||||
return kms.NewKeyManagementClient(ctx, opts...)
|
||||
}
|
||||
|
||||
// wrapCryptoKey wraps a key for import using a public RSA key, see: https://cloud.google.com/kms/docs/wrapping-a-key
|
||||
func wrapCryptoKey(key []byte, wrapKeyRSA *rsa.PublicKey) ([]byte, error) {
|
||||
// Enforce 256bit key length
|
||||
if len(key) != config.SymmetricKeyLength {
|
||||
return nil, fmt.Errorf("invalid key size: want [%d], got [%d]", config.SymmetricKeyLength, len(key))
|
||||
}
|
||||
// create random 256bit AES wrapping key
|
||||
wrapKeyAES := make([]byte, config.SymmetricKeyLength)
|
||||
if _, err := rand.Read(wrapKeyAES); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Perform CKM_AES_KEY_WRAP_PAD to wrap the key
|
||||
wrappedKey, err := util.WrapAES(key, wrapKeyAES)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Encrypt the ephemeral AES key with the KMS provided wrapping key
|
||||
// Google KMS requires RSAES-OAEP with SHA-1 and an empty label
|
||||
encWrapKeyAES, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, wrapKeyRSA, wrapKeyAES, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return append(encWrapKeyAES, wrappedKey...), nil
|
||||
// Close closes the KMS client.
|
||||
func (c *KMSClient) Close() {
|
||||
_ = c.client.Close()
|
||||
}
|
||||
|
@ -1,389 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package gcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"cloud.google.com/go/kms/apiv1/kmspb"
|
||||
kmsInterface "github.com/edgelesssys/constellation/v2/internal/kms/kms"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms/util"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
|
||||
"github.com/googleapis/gax-go/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/goleak"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
goleak.VerifyTestMain(m,
|
||||
// https://github.com/census-instrumentation/opencensus-go/issues/1262
|
||||
goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"),
|
||||
)
|
||||
}
|
||||
|
||||
var testKeyRSA = `-----BEGIN PUBLIC KEY-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAu+OepfHCTiTi27nkTGke
|
||||
dn+AIkiM1AIWWDwqfqG85aNulcj60mGQGXIYV8LoEVkyKOhYBIUmJUaVczB4ltqq
|
||||
ZhR7l46RQw2vnv+XiUmfK555d4ZDInyjTusO69hE6tkuYKdXLlG1HzcrhJ254LE2
|
||||
wXtE1Yf9DygOsWet+S32gmpfH2whUY1mRTdwW4zoY4c3qtmmWImhVVNr6qR8Z95X
|
||||
Y49EteCoNIomQNEZH7EnMlBsh34L7doOsckh1aTvQcrJorQSrBkWKbdV6kvuBKZp
|
||||
fLK0DZiOh9BwZCZANtOqgH3V+AuNk338iON8eKCFRjoiQ40YGM6xKH3E6PHVnuKt
|
||||
uIO0MPvE0qdV8Lvs+nCCrvwP5sJKZuciM40ioEO1pV1y3491xIxYhx3OfN4gg2h8
|
||||
cgdKob/R8qwxqTrfceO36FBFb1vXCUApsm5oy6WxmUtIUgoYhK+6JYpVWDyOJYwP
|
||||
iMJhdJA65n2ZliN8NxEhsaFoMgw76BOiD0wkt/CKPmNbOm5MGS3/fiZCt6A6u3cn
|
||||
Ubhn4tvjy/q5XzVqZtBeoseW2TyyrsAN53LBkSqag5tG/264CQDigQ6Y/OADOE2x
|
||||
n08MyrFHIL/wFMscOvJo7c2Eo4EW1yXkEkAy5tF5PZgnfRObakj4gdqPeq18FNzc
|
||||
Y+t5OxL3kL15VzY1Ob0d5cMCAwEAAQ==
|
||||
-----END PUBLIC KEY-----`
|
||||
|
||||
type stubGCPClient struct {
|
||||
createErr error
|
||||
createCryptoKeyCalled bool
|
||||
createCryptoKeyErr error
|
||||
createImportJobErr error
|
||||
decryptResponse []byte
|
||||
decryptErr error
|
||||
encryptErr error
|
||||
getKeyRingErr error
|
||||
importCryptoKeyVersionErr error
|
||||
updateCryptoKeyPrimaryVersionCalled bool
|
||||
updateCryptoKeyPrimaryVersionErr error
|
||||
getImportJobErr error
|
||||
getImportJobResponse *kmspb.ImportJob
|
||||
}
|
||||
|
||||
func newStubGCPClientFactory(stub *stubGCPClient) func(ctx context.Context, opts ...option.ClientOption) (clientAPI, error) {
|
||||
return func(ctx context.Context, opts ...option.ClientOption) (clientAPI, error) {
|
||||
return stub, stub.createErr
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stubGCPClient) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stubGCPClient) CreateCryptoKey(ctx context.Context, req *kmspb.CreateCryptoKeyRequest, opts ...gax.CallOption) (*kmspb.CryptoKey, error) {
|
||||
s.createCryptoKeyCalled = true
|
||||
return &kmspb.CryptoKey{}, s.createCryptoKeyErr
|
||||
}
|
||||
|
||||
func (s *stubGCPClient) CreateImportJob(ctx context.Context, req *kmspb.CreateImportJobRequest, opts ...gax.CallOption) (*kmspb.ImportJob, error) {
|
||||
return &kmspb.ImportJob{}, s.createImportJobErr
|
||||
}
|
||||
|
||||
func (s *stubGCPClient) Decrypt(ctx context.Context, req *kmspb.DecryptRequest, opts ...gax.CallOption) (*kmspb.DecryptResponse, error) {
|
||||
return &kmspb.DecryptResponse{Plaintext: s.decryptResponse}, s.decryptErr
|
||||
}
|
||||
|
||||
func (s *stubGCPClient) Encrypt(ctx context.Context, req *kmspb.EncryptRequest, opts ...gax.CallOption) (*kmspb.EncryptResponse, error) {
|
||||
return &kmspb.EncryptResponse{}, s.encryptErr
|
||||
}
|
||||
|
||||
func (s *stubGCPClient) GetKeyRing(ctx context.Context, req *kmspb.GetKeyRingRequest, opts ...gax.CallOption) (*kmspb.KeyRing, error) {
|
||||
return &kmspb.KeyRing{}, s.getKeyRingErr
|
||||
}
|
||||
|
||||
func (s *stubGCPClient) ImportCryptoKeyVersion(ctx context.Context, req *kmspb.ImportCryptoKeyVersionRequest, opts ...gax.CallOption) (*kmspb.CryptoKeyVersion, error) {
|
||||
return &kmspb.CryptoKeyVersion{}, s.importCryptoKeyVersionErr
|
||||
}
|
||||
|
||||
func (s *stubGCPClient) UpdateCryptoKeyPrimaryVersion(ctx context.Context, req *kmspb.UpdateCryptoKeyPrimaryVersionRequest, opts ...gax.CallOption) (*kmspb.CryptoKey, error) {
|
||||
s.updateCryptoKeyPrimaryVersionCalled = true
|
||||
return &kmspb.CryptoKey{}, s.updateCryptoKeyPrimaryVersionErr
|
||||
}
|
||||
|
||||
func (s *stubGCPClient) GetImportJob(ctx context.Context, req *kmspb.GetImportJobRequest, opts ...gax.CallOption) (*kmspb.ImportJob, error) {
|
||||
return s.getImportJobResponse, s.getImportJobErr
|
||||
}
|
||||
|
||||
type stubStorage struct {
|
||||
key []byte
|
||||
getErr error
|
||||
putErr error
|
||||
}
|
||||
|
||||
func (s *stubStorage) Get(context.Context, string) ([]byte, error) {
|
||||
return s.key, s.getErr
|
||||
}
|
||||
|
||||
func (s *stubStorage) Put(context.Context, string, []byte) error {
|
||||
return s.putErr
|
||||
}
|
||||
|
||||
func TestCreateKEK(t *testing.T) {
|
||||
someErr := errors.New("error")
|
||||
importKey := []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
||||
|
||||
testCases := map[string]struct {
|
||||
client *stubGCPClient
|
||||
importKey []byte
|
||||
wantErr bool
|
||||
}{
|
||||
"create new kek successful": {
|
||||
client: &stubGCPClient{},
|
||||
},
|
||||
"import kek successful": {
|
||||
client: &stubGCPClient{
|
||||
getImportJobResponse: &kmspb.ImportJob{
|
||||
PublicKey: &kmspb.ImportJob_WrappingPublicKey{
|
||||
Pem: testKeyRSA,
|
||||
},
|
||||
State: kmspb.ImportJob_ACTIVE,
|
||||
},
|
||||
},
|
||||
importKey: importKey,
|
||||
},
|
||||
"CreateCryptoKey fails": {
|
||||
client: &stubGCPClient{createCryptoKeyErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
"CreatCryptoKey fails on import": {
|
||||
client: &stubGCPClient{
|
||||
createCryptoKeyErr: someErr,
|
||||
getImportJobResponse: &kmspb.ImportJob{
|
||||
PublicKey: &kmspb.ImportJob_WrappingPublicKey{
|
||||
Pem: testKeyRSA,
|
||||
},
|
||||
State: kmspb.ImportJob_ACTIVE,
|
||||
},
|
||||
},
|
||||
importKey: importKey,
|
||||
wantErr: true,
|
||||
},
|
||||
"CreateImportJob fails": {
|
||||
client: &stubGCPClient{createImportJobErr: someErr},
|
||||
importKey: importKey,
|
||||
wantErr: true,
|
||||
},
|
||||
"ImportCryptoKeyVersion fails": {
|
||||
client: &stubGCPClient{
|
||||
getImportJobResponse: &kmspb.ImportJob{
|
||||
PublicKey: &kmspb.ImportJob_WrappingPublicKey{
|
||||
Pem: testKeyRSA,
|
||||
},
|
||||
State: kmspb.ImportJob_ACTIVE,
|
||||
},
|
||||
importCryptoKeyVersionErr: someErr,
|
||||
},
|
||||
importKey: importKey,
|
||||
wantErr: true,
|
||||
},
|
||||
"UpdateCryptoKeyPrimaryVersion fails": {
|
||||
client: &stubGCPClient{
|
||||
getImportJobResponse: &kmspb.ImportJob{
|
||||
PublicKey: &kmspb.ImportJob_WrappingPublicKey{
|
||||
Pem: testKeyRSA,
|
||||
},
|
||||
State: kmspb.ImportJob_ACTIVE,
|
||||
},
|
||||
updateCryptoKeyPrimaryVersionErr: someErr,
|
||||
},
|
||||
importKey: importKey,
|
||||
wantErr: true,
|
||||
},
|
||||
"GetImportJob fails during waitBackoff": {
|
||||
client: &stubGCPClient{getImportJobErr: someErr},
|
||||
importKey: importKey,
|
||||
wantErr: true,
|
||||
},
|
||||
"GetImportJob returns no key": {
|
||||
client: &stubGCPClient{
|
||||
getImportJobResponse: &kmspb.ImportJob{
|
||||
State: kmspb.ImportJob_ACTIVE,
|
||||
},
|
||||
},
|
||||
importKey: importKey,
|
||||
wantErr: true,
|
||||
},
|
||||
"waitBackoff times out": {
|
||||
client: &stubGCPClient{
|
||||
getImportJobResponse: &kmspb.ImportJob{
|
||||
PublicKey: &kmspb.ImportJob_WrappingPublicKey{
|
||||
Pem: testKeyRSA,
|
||||
},
|
||||
State: kmspb.ImportJob_PENDING_GENERATION,
|
||||
},
|
||||
},
|
||||
importKey: importKey,
|
||||
wantErr: true,
|
||||
},
|
||||
"creating client fails": {
|
||||
client: &stubGCPClient{createErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
client := &KMSClient{
|
||||
projectID: "test-project",
|
||||
locationID: "global",
|
||||
keyRingID: "test-ring",
|
||||
newClient: newStubGCPClientFactory(tc.client),
|
||||
protectionLevel: kmspb.ProtectionLevel_SOFTWARE,
|
||||
waitBackoffLimit: 1,
|
||||
}
|
||||
|
||||
err := client.CreateKEK(context.Background(), "test-key", tc.importKey)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
if len(tc.importKey) != 0 {
|
||||
assert.True(tc.client.updateCryptoKeyPrimaryVersionCalled)
|
||||
} else {
|
||||
assert.True(tc.client.createCryptoKeyCalled)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDEK(t *testing.T) {
|
||||
someErr := errors.New("error")
|
||||
testKey := []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
||||
|
||||
testCases := map[string]struct {
|
||||
client *stubGCPClient
|
||||
storage kmsInterface.Storage
|
||||
wantErr bool
|
||||
}{
|
||||
"GetDEK successful for new key": {
|
||||
client: &stubGCPClient{},
|
||||
storage: &stubStorage{getErr: storage.ErrDEKUnset},
|
||||
},
|
||||
"GetDEK successful for existing key": {
|
||||
client: &stubGCPClient{decryptResponse: testKey},
|
||||
storage: &stubStorage{key: testKey},
|
||||
},
|
||||
"Get from storage fails": {
|
||||
client: &stubGCPClient{},
|
||||
storage: &stubStorage{getErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
"Encrypt fails": {
|
||||
client: &stubGCPClient{encryptErr: someErr},
|
||||
storage: &stubStorage{getErr: storage.ErrDEKUnset},
|
||||
wantErr: true,
|
||||
},
|
||||
"Encrypt fails with notfound error": {
|
||||
client: &stubGCPClient{encryptErr: status.Error(codes.NotFound, "error")},
|
||||
storage: &stubStorage{getErr: storage.ErrDEKUnset},
|
||||
wantErr: true,
|
||||
},
|
||||
"Put to storage fails": {
|
||||
client: &stubGCPClient{},
|
||||
storage: &stubStorage{
|
||||
getErr: storage.ErrDEKUnset,
|
||||
putErr: someErr,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"Decrypt fails": {
|
||||
client: &stubGCPClient{decryptErr: someErr},
|
||||
storage: &stubStorage{key: testKey},
|
||||
wantErr: true,
|
||||
},
|
||||
"Decrypt fails with notfound error": {
|
||||
client: &stubGCPClient{decryptErr: status.Error(codes.NotFound, "error")},
|
||||
storage: &stubStorage{key: testKey},
|
||||
wantErr: true,
|
||||
},
|
||||
"creating client fails": {
|
||||
client: &stubGCPClient{createErr: someErr},
|
||||
storage: &stubStorage{getErr: storage.ErrDEKUnset},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
client := &KMSClient{
|
||||
projectID: "test-project",
|
||||
locationID: "global",
|
||||
keyRingID: "test-ring",
|
||||
newClient: newStubGCPClientFactory(tc.client),
|
||||
protectionLevel: kmspb.ProtectionLevel_SOFTWARE,
|
||||
waitBackoffLimit: 1,
|
||||
storage: tc.storage,
|
||||
}
|
||||
|
||||
dek, err := client.GetDEK(context.Background(), "volume-01", 32)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Len(dek, 32)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnection(t *testing.T) {
|
||||
someErr := errors.New("error")
|
||||
testCases := map[string]struct {
|
||||
client *stubGCPClient
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
client: &stubGCPClient{},
|
||||
},
|
||||
"newClient fails": {
|
||||
client: &stubGCPClient{createErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
"GetKeyRing fails": {
|
||||
client: &stubGCPClient{getKeyRingErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
client := &KMSClient{
|
||||
projectID: "test-project",
|
||||
locationID: "global",
|
||||
keyRingID: "test-ring",
|
||||
newClient: newStubGCPClientFactory(tc.client),
|
||||
protectionLevel: kmspb.ProtectionLevel_SOFTWARE,
|
||||
waitBackoffLimit: 1,
|
||||
}
|
||||
|
||||
err := client.testConnection(context.Background())
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapCryptoKey(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
rsaPub, err := util.ParsePEMtoPublicKeyRSA([]byte(testKeyRSA))
|
||||
assert.NoError(err)
|
||||
|
||||
res, err := wrapCryptoKey([]byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), rsaPub)
|
||||
assert.NoError(err)
|
||||
assert.Equal(552, len(res))
|
||||
|
||||
_, err = wrapCryptoKey([]byte{0x1}, rsaPub)
|
||||
assert.Error(err)
|
||||
}
|
82
internal/kms/kms/internal/internal.go
Normal file
82
internal/kms/kms/internal/internal.go
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/*
|
||||
Package internal implements the CloudKMS interface using go-kms-wrapping.
|
||||
|
||||
Adding support for a new KMS that is supported by go-kms-wrapping,
|
||||
simply requires implementing a New function that initializes a KMSClient.
|
||||
*/
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/crypto"
|
||||
kmsInterface "github.com/edgelesssys/constellation/v2/internal/kms/kms"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
)
|
||||
|
||||
type kmsWrapper interface {
|
||||
Decrypt(context.Context, *wrapping.BlobInfo, ...wrapping.Option) ([]byte, error)
|
||||
Encrypt(context.Context, []byte, ...wrapping.Option) (*wrapping.BlobInfo, error)
|
||||
}
|
||||
|
||||
// KMSClient implements the CloudKMS interface using go-kms-wrapping.
|
||||
type KMSClient struct {
|
||||
Storage kmsInterface.Storage
|
||||
Wrapper kmsWrapper
|
||||
}
|
||||
|
||||
// GetDEK fetches an encrypted Data Encryption Key from storage.
|
||||
// If the key exists, it is decrypted and returned.
|
||||
// If no such key exists, a new one is generated, encrypted and saved to storage.
|
||||
func (c *KMSClient) GetDEK(ctx context.Context, keyID string, dekSize int) ([]byte, error) {
|
||||
encryptedDEK, err := c.Storage.Get(ctx, keyID)
|
||||
if err != nil {
|
||||
if !errors.Is(err, storage.ErrDEKUnset) {
|
||||
return nil, fmt.Errorf("loading encrypted DEK from storage: %w", err)
|
||||
}
|
||||
|
||||
// If the DEK does not exist we generate a new random DEK and save it to storage
|
||||
newDEK, err := crypto.GenerateRandomBytes(dekSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("key generation: %w", err)
|
||||
}
|
||||
return newDEK, c.putDEK(ctx, keyID, newDEK)
|
||||
}
|
||||
|
||||
wrappedKey := &wrapping.BlobInfo{}
|
||||
if err := json.Unmarshal(encryptedDEK, wrappedKey); err != nil {
|
||||
return nil, fmt.Errorf("unmarshaling wrapped DEK: %w", err)
|
||||
}
|
||||
|
||||
dek, err := c.Wrapper.Decrypt(ctx, wrappedKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypting DEK: %w", err)
|
||||
}
|
||||
|
||||
return dek, nil
|
||||
}
|
||||
|
||||
// putDEK encrypts a Data Encryption Key and saves it to storage.
|
||||
func (c *KMSClient) putDEK(ctx context.Context, keyID string, plainDEK []byte) error {
|
||||
wrappedKey, err := c.Wrapper.Encrypt(ctx, plainDEK)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypting DEK: %w", err)
|
||||
}
|
||||
|
||||
encryptedDEK, err := json.Marshal(wrappedKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshaling wrapped DEK: %w", err)
|
||||
}
|
||||
|
||||
return c.Storage.Put(ctx, keyID, encryptedDEK)
|
||||
}
|
146
internal/kms/kms/internal/internal_test.go
Normal file
146
internal/kms/kms/internal/internal_test.go
Normal file
@ -0,0 +1,146 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
cloudkms "cloud.google.com/go/kms/apiv1"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/go-kms-wrapping/wrappers/gcpckms/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/goleak"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
goleak.VerifyTestMain(m,
|
||||
// https://github.com/census-instrumentation/opencensus-go/issues/1262
|
||||
goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"),
|
||||
)
|
||||
}
|
||||
|
||||
type stubWrapper struct {
|
||||
decryptErr error
|
||||
decryptResponse []byte
|
||||
encryptErr error
|
||||
encryptResponse *wrapping.BlobInfo
|
||||
}
|
||||
|
||||
func (s *stubWrapper) Decrypt(context.Context, *wrapping.BlobInfo, ...wrapping.Option) ([]byte, error) {
|
||||
return s.decryptResponse, s.decryptErr
|
||||
}
|
||||
|
||||
func (s *stubWrapper) Encrypt(context.Context, []byte, ...wrapping.Option) (*wrapping.BlobInfo, error) {
|
||||
return s.encryptResponse, s.encryptErr
|
||||
}
|
||||
|
||||
func (s *stubWrapper) Client() *cloudkms.KeyManagementClient {
|
||||
return &cloudkms.KeyManagementClient{}
|
||||
}
|
||||
|
||||
type stubStorage struct {
|
||||
key []byte
|
||||
getErr error
|
||||
putErr error
|
||||
}
|
||||
|
||||
func (s *stubStorage) Get(context.Context, string) ([]byte, error) {
|
||||
return s.key, s.getErr
|
||||
}
|
||||
|
||||
func (s *stubStorage) Put(context.Context, string, []byte) error {
|
||||
return s.putErr
|
||||
}
|
||||
|
||||
func TestGetDEK(t *testing.T) {
|
||||
someErr := errors.New("failed")
|
||||
testKey := []byte("00112233445566778899aabbccddeeff")
|
||||
savedTestKey, err := json.Marshal(&wrapping.BlobInfo{
|
||||
Ciphertext: []byte("encrypted-dek"),
|
||||
Iv: []byte("iv"),
|
||||
KeyInfo: &wrapping.KeyInfo{
|
||||
Mechanism: gcpckms.GcpCkmsEnvelopeAesGcmEncrypt,
|
||||
KeyId: "kek-id",
|
||||
WrappedKey: []byte("dek-encryption-key"),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := map[string]struct {
|
||||
wrapper *stubWrapper
|
||||
storage *stubStorage
|
||||
wantErr bool
|
||||
}{
|
||||
"GetDEK successful for new key": {
|
||||
wrapper: &stubWrapper{},
|
||||
storage: &stubStorage{getErr: storage.ErrDEKUnset},
|
||||
},
|
||||
"GetDEK successful for existing key": {
|
||||
wrapper: &stubWrapper{decryptResponse: testKey},
|
||||
storage: &stubStorage{key: savedTestKey},
|
||||
},
|
||||
"Get from storage fails": {
|
||||
wrapper: &stubWrapper{},
|
||||
storage: &stubStorage{getErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
"Encrypt fails": {
|
||||
wrapper: &stubWrapper{encryptErr: someErr},
|
||||
storage: &stubStorage{getErr: storage.ErrDEKUnset},
|
||||
wantErr: true,
|
||||
},
|
||||
"Encrypt fails with notfound error": {
|
||||
wrapper: &stubWrapper{encryptErr: status.Error(codes.NotFound, "error")},
|
||||
storage: &stubStorage{getErr: storage.ErrDEKUnset},
|
||||
wantErr: true,
|
||||
},
|
||||
"Put to storage fails": {
|
||||
wrapper: &stubWrapper{},
|
||||
storage: &stubStorage{
|
||||
getErr: storage.ErrDEKUnset,
|
||||
putErr: someErr,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"Decrypt fails": {
|
||||
wrapper: &stubWrapper{decryptErr: someErr},
|
||||
storage: &stubStorage{key: savedTestKey},
|
||||
wantErr: true,
|
||||
},
|
||||
"Decrypt fails with notfound error": {
|
||||
wrapper: &stubWrapper{decryptErr: status.Error(codes.NotFound, "error")},
|
||||
storage: &stubStorage{key: savedTestKey},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
client := &KMSClient{
|
||||
Wrapper: tc.wrapper,
|
||||
Storage: tc.storage,
|
||||
}
|
||||
|
||||
dek, err := client.GetDEK(context.Background(), "volume-01", 32)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Len(dek, 32)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -9,16 +9,15 @@ package kms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// CloudKMS enables using cloud base Key Management Services.
|
||||
type CloudKMS interface {
|
||||
// CreateKEK creates a new KEK with the given key material, if provided. If successful, the key can be referenced by keyID in the KMS in accordance to the policy.
|
||||
CreateKEK(ctx context.Context, keyID string, kek []byte) error
|
||||
// GetDEK returns the DEK for dekID and kekID from the KMS.
|
||||
// If the DEK does not exist, a new one is created and saved to storage.
|
||||
GetDEK(ctx context.Context, dekID string, dekSize int) ([]byte, error)
|
||||
// Close closes any open connection on the KMS client.
|
||||
Close()
|
||||
}
|
||||
|
||||
// Storage provides an abstract interface for the storage backend used for DEKs.
|
||||
@ -28,6 +27,3 @@ type Storage interface {
|
||||
// Put saves a DEK to the storage by key ID.
|
||||
Put(context.Context, string, []byte) error
|
||||
}
|
||||
|
||||
// ErrKEKUnknown is an error raised by unknown KEK in the KMS.
|
||||
var ErrKEKUnknown = errors.New("requested KEK not found")
|
||||
|
@ -1,77 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
// Package util provides utility functions for the KMS backends.
|
||||
package util
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/google/tink/go/kwp/subtle"
|
||||
)
|
||||
|
||||
// WrapAES performs AES Key Wrap with Padding as specified in RFC 5649: https://datatracker.ietf.org/doc/html/rfc5649
|
||||
//
|
||||
// Key sizes are limited to 16 and 32 Bytes.
|
||||
func WrapAES(key []byte, wrapKeyAES []byte) ([]byte, error) {
|
||||
wrapper, err := subtle.NewKWP(wrapKeyAES)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return wrapper.Wrap(key)
|
||||
}
|
||||
|
||||
// UnwrapAES decrypts data wrapped with AES Key Wrap with Padding as specified in RFC 5649: https://datatracker.ietf.org/doc/html/rfc5649
|
||||
//
|
||||
// Key sizes are limited to 16 and 32 Bytes.
|
||||
func UnwrapAES(encryptedKey []byte, wrapKeyAES []byte) ([]byte, error) {
|
||||
wrapper, err := subtle.NewKWP(wrapKeyAES)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wrapper.Unwrap(encryptedKey)
|
||||
}
|
||||
|
||||
// ParsePEMtoPublicKeyRSA parses a public RSA key from bytes to *rsa.PublicKey.
|
||||
func ParsePEMtoPublicKeyRSA(pkPEM []byte) (*rsa.PublicKey, error) {
|
||||
pkDER, _ := pem.Decode(pkPEM)
|
||||
if pkDER == nil {
|
||||
return nil, fmt.Errorf("did not find any PEM data")
|
||||
}
|
||||
if pkDER.Type != "PUBLIC KEY" {
|
||||
return nil, fmt.Errorf("invalid PEM format: want [PUBLIC KEY], got [%s]", pkDER.Type)
|
||||
}
|
||||
return ParseDERtoPublicKeyRSA(pkDER.Bytes)
|
||||
}
|
||||
|
||||
// ParseDERtoPublicKeyRSA parses a PKIX, ASN.1 DER RSA public key from []byte to *rsa.PublicKey.
|
||||
func ParseDERtoPublicKeyRSA(pkDER []byte) (*rsa.PublicKey, error) {
|
||||
key, err := x509.ParsePKIXPublicKey(pkDER)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch t := key.(type) {
|
||||
case *rsa.PublicKey:
|
||||
return t, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid key type: want [*rsa.PublicKey], got [%v]", reflect.TypeOf(t))
|
||||
}
|
||||
}
|
||||
|
||||
// GetRandomKey reads length bytes from getrandom(2) if available, /dev/urandom otherwise.
|
||||
func GetRandomKey(length int) ([]byte, error) {
|
||||
key := make([]byte, length)
|
||||
if _, err := rand.Read(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key, nil
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
goleak.VerifyTestMain(m)
|
||||
}
|
||||
|
||||
func TestWrapKeyAES(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
testKEK := []byte{0xD6, 0x8A, 0xED, 0xF5, 0xDB, 0x89, 0x95, 0x66, 0xA9, 0xFF, 0xD9, 0x31, 0x27, 0x4E, 0x30, 0x2D, 0x21, 0xA9, 0x46, 0x21, 0x16, 0x6C, 0x16, 0x17, 0xD1, 0x96, 0x5D, 0xB2, 0xE9, 0x0E, 0x96, 0xD1}
|
||||
testDEK := []byte{0xCB, 0x6E, 0x4B, 0x05, 0x92, 0x6C, 0xE7, 0x38, 0x0C, 0x46, 0x47, 0x06, 0x83, 0xDE, 0x20, 0xFB, 0x73, 0xAA, 0x87, 0xC1, 0x97, 0xE3, 0x7C, 0xE4, 0xF4, 0x0B, 0x96, 0x8D, 0xC5, 0x88, 0xB6, 0xDF}
|
||||
wantWrap := []byte{0x14, 0x48, 0xC4, 0xEA, 0x4B, 0x4B, 0xCA, 0xE4, 0x5A, 0xD4, 0xCC, 0xE3, 0xF7, 0xDD, 0xD5, 0x78, 0xA5, 0xA9, 0xEF, 0x9A, 0x93, 0x36, 0x09, 0xD6, 0x23, 0x01, 0xF5, 0x5F, 0xE1, 0x20, 0xDD, 0xFC, 0xBC, 0xF3, 0xA9, 0x67, 0x8B, 0x89, 0x54, 0x96}
|
||||
res, err := WrapAES(testDEK, testKEK)
|
||||
assert.NoError(err)
|
||||
assert.Equal(wantWrap, res)
|
||||
|
||||
// Decrypt the Key
|
||||
res, err = UnwrapAES(res, testKEK)
|
||||
assert.NoError(err)
|
||||
assert.Equal(testDEK, res)
|
||||
|
||||
// Target key length is enforced to be at least 128 bit
|
||||
smallKey := []byte{0x46, 0x6f, 0x72, 0x50, 0x61, 0x73, 0x69}
|
||||
_, err = WrapAES(smallKey, testKEK)
|
||||
assert.Error(err)
|
||||
|
||||
// Wrapping key length is enforced to be 128 or 256 bit
|
||||
key192 := []byte{0x58, 0x40, 0xdf, 0x6e, 0x29, 0xb0, 0x2a, 0xf1, 0xab, 0x49, 0x3b, 0x70, 0x5b, 0xf1, 0x6e, 0xa1, 0xae, 0x83, 0x38, 0xf4, 0xdc, 0xc1, 0x76, 0xa8}
|
||||
_, err = WrapAES(testDEK, key192)
|
||||
assert.Error(err)
|
||||
|
||||
// Make sure we can wrap large keys. For example AES-XTS uses 512 bit keys
|
||||
largeKey := []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
||||
_, err = WrapAES(largeKey, testKEK)
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
func TestParsePEM(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
testKeyRSA := `-----BEGIN PUBLIC KEY-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAu+OepfHCTiTi27nkTGke
|
||||
dn+AIkiM1AIWWDwqfqG85aNulcj60mGQGXIYV8LoEVkyKOhYBIUmJUaVczB4ltqq
|
||||
ZhR7l46RQw2vnv+XiUmfK555d4ZDInyjTusO69hE6tkuYKdXLlG1HzcrhJ254LE2
|
||||
wXtE1Yf9DygOsWet+S32gmpfH2whUY1mRTdwW4zoY4c3qtmmWImhVVNr6qR8Z95X
|
||||
Y49EteCoNIomQNEZH7EnMlBsh34L7doOsckh1aTvQcrJorQSrBkWKbdV6kvuBKZp
|
||||
fLK0DZiOh9BwZCZANtOqgH3V+AuNk338iON8eKCFRjoiQ40YGM6xKH3E6PHVnuKt
|
||||
uIO0MPvE0qdV8Lvs+nCCrvwP5sJKZuciM40ioEO1pV1y3491xIxYhx3OfN4gg2h8
|
||||
cgdKob/R8qwxqTrfceO36FBFb1vXCUApsm5oy6WxmUtIUgoYhK+6JYpVWDyOJYwP
|
||||
iMJhdJA65n2ZliN8NxEhsaFoMgw76BOiD0wkt/CKPmNbOm5MGS3/fiZCt6A6u3cn
|
||||
Ubhn4tvjy/q5XzVqZtBeoseW2TyyrsAN53LBkSqag5tG/264CQDigQ6Y/OADOE2x
|
||||
n08MyrFHIL/wFMscOvJo7c2Eo4EW1yXkEkAy5tF5PZgnfRObakj4gdqPeq18FNzc
|
||||
Y+t5OxL3kL15VzY1Ob0d5cMCAwEAAQ==
|
||||
-----END PUBLIC KEY-----`
|
||||
|
||||
notAKey := []byte(`-----BEGIN FOO-----
|
||||
dGVzdA==
|
||||
-----END FOO-----`)
|
||||
ecKey := []byte(`-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQx3ShpceYTZD5lnCEMExflsyMZRa
|
||||
vCYPhiEFmekMeuHsjC2HnRPA7++9Rq4+IwqKdh6+Ok9kADkyAqtckTj6lg==
|
||||
-----END PUBLIC KEY-----`)
|
||||
|
||||
_, err := ParsePEMtoPublicKeyRSA(nil)
|
||||
assert.Error(err)
|
||||
|
||||
_, err = ParsePEMtoPublicKeyRSA(notAKey)
|
||||
assert.Error(err)
|
||||
|
||||
_, err = ParsePEMtoPublicKeyRSA(ecKey)
|
||||
assert.Error(err)
|
||||
|
||||
_, err = ParsePEMtoPublicKeyRSA([]byte(testKeyRSA))
|
||||
assert.NoError(err)
|
||||
}
|
@ -20,22 +20,20 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"cloud.google.com/go/kms/apiv1/kmspb"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms/aws"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms/azure"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms/cluster"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms/gcp"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
|
||||
)
|
||||
|
||||
// Well known endpoints for KMS services.
|
||||
const (
|
||||
AWSKMSURI = "kms://aws?keyPolicy=%s&kekID=%s"
|
||||
AzureKMSURI = "kms://azure-kms?name=%s&type=%s&kekID=%s"
|
||||
AzureHSMURI = "kms://azure-hsm?name=%s&kekID=%s"
|
||||
AzureKMSURI = "kms://azure?tenantID=%s&clientID=%s&clientSecret=%s&name=%s&type=%s&kekID=%s"
|
||||
GCPKMSURI = "kms://gcp?project=%s&location=%s&keyRing=%s&protectionLvl=%s&kekID=%s"
|
||||
ClusterKMSURI = "kms://cluster-kms?key=%s&salt=%s"
|
||||
AWSS3URI = "storage://aws?bucket=%s"
|
||||
@ -118,52 +116,45 @@ func getStore(ctx context.Context, storageURI string) (kms.Storage, error) {
|
||||
|
||||
// getKMS creates a KMS client with the given key store and depending on the given parameters.
|
||||
func getKMS(ctx context.Context, kmsURI string, store kms.Storage) (kms.CloudKMS, error) {
|
||||
uri, err := url.Parse(kmsURI)
|
||||
url, err := url.Parse(kmsURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if uri.Scheme != "kms" {
|
||||
return nil, fmt.Errorf("invalid KMS URI: invalid scheme: %s", uri.Scheme)
|
||||
if url.Scheme != "kms" {
|
||||
return nil, fmt.Errorf("invalid KMS URI: invalid scheme: %s", url.Scheme)
|
||||
}
|
||||
|
||||
switch uri.Host {
|
||||
switch url.Host {
|
||||
case "aws":
|
||||
poliyProducer, kekID, err := getAWSKMSConfig(uri)
|
||||
cfg, err := uri.DecodeAWSConfigFromURI(kmsURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("invalid AWS KMS URI: %w", err)
|
||||
}
|
||||
return aws.New(ctx, poliyProducer, store, kekID)
|
||||
return aws.New(ctx, store, cfg)
|
||||
|
||||
case "azure-kms":
|
||||
vaultName, vaultType, kekID, err := getAzureKMSConfig(uri)
|
||||
case "azure":
|
||||
cfg, err := uri.DecodeAzureConfigFromURI(kmsURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("invalid Azure Key Vault URI: %w", err)
|
||||
}
|
||||
return azure.New(ctx, vaultName, azure.VaultSuffix(vaultType), store, kekID, nil)
|
||||
|
||||
case "azure-hsm":
|
||||
vaultName, kekID, err := getAzureHSMConfig(uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return azure.NewHSM(ctx, vaultName, store, kekID, nil)
|
||||
return azure.New(ctx, store, cfg)
|
||||
|
||||
case "gcp":
|
||||
project, location, keyRing, protectionLvl, kekID, err := getGCPKMSConfig(uri)
|
||||
cfg, err := uri.DecodeGCPConfigFromURI(kmsURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("invalid GCP KMS URI: %w", err)
|
||||
}
|
||||
return gcp.New(ctx, project, location, keyRing, store, kmspb.ProtectionLevel(protectionLvl), kekID)
|
||||
return gcp.New(ctx, store, cfg)
|
||||
|
||||
case "cluster-kms":
|
||||
masterSecret, err := getClusterKMSConfig(uri)
|
||||
cfg, err := uri.DecodeMasterSecretFromURI(kmsURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cluster.New(masterSecret.Key, masterSecret.Salt)
|
||||
return cluster.New(cfg.Key, cfg.Salt)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown KMS type: %s", uri.Host)
|
||||
return nil, fmt.Errorf("unknown KMS type: %s", url.Host)
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,40 +189,6 @@ func getAWSKMSConfig(uri *url.URL) (*defaultPolicyProducer, string, error) {
|
||||
return &defaultPolicyProducer{policy: r[0]}, string(kekID), err
|
||||
}
|
||||
|
||||
func getAzureKMSConfig(uri *url.URL) (string, string, string, error) {
|
||||
r, err := getConfig(uri.Query(), []string{"name", "type", "kekID"})
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("getting config: %w", err)
|
||||
}
|
||||
if len(r) != 3 {
|
||||
return "", "", "", fmt.Errorf("expected 3 KmsURI args, got %d", len(r))
|
||||
}
|
||||
|
||||
kekID, err := base64.URLEncoding.DecodeString(r[2])
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("parsing kekID from kmsUri: %w", err)
|
||||
}
|
||||
|
||||
return r[0], r[1], string(kekID), err
|
||||
}
|
||||
|
||||
func getAzureHSMConfig(uri *url.URL) (string, string, error) {
|
||||
r, err := getConfig(uri.Query(), []string{"name", "kekID"})
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("getting config: %w", err)
|
||||
}
|
||||
if len(r) != 2 {
|
||||
return "", "", fmt.Errorf("expected 2 KmsURI args, got %d", len(r))
|
||||
}
|
||||
|
||||
kekID, err := base64.URLEncoding.DecodeString(r[1])
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("parsing kekID from kmsUri: %w", err)
|
||||
}
|
||||
|
||||
return r[0], string(kekID), err
|
||||
}
|
||||
|
||||
func getAzureBlobConfig(uri *url.URL) (string, string, error) {
|
||||
r, err := getConfig(uri.Query(), []string{"container", "connectionString"})
|
||||
if err != nil {
|
||||
@ -240,55 +197,11 @@ func getAzureBlobConfig(uri *url.URL) (string, string, error) {
|
||||
return r[0], r[1], nil
|
||||
}
|
||||
|
||||
func getGCPKMSConfig(uri *url.URL) (project string, location string, keyRing string, protectionLvl int32, kekID string, err error) {
|
||||
r, err := getConfig(uri.Query(), []string{"project", "location", "keyRing", "protectionLvl", "kekID"})
|
||||
if err != nil {
|
||||
return "", "", "", 0, "", err
|
||||
}
|
||||
|
||||
if len(r) != 5 {
|
||||
return "", "", "", 0, "", fmt.Errorf("expected 5 KmsURI args, got %d", len(r))
|
||||
}
|
||||
|
||||
kekIDByte, err := base64.URLEncoding.DecodeString(r[4])
|
||||
if err != nil {
|
||||
return "", "", "", 0, "", fmt.Errorf("parsing kekID from kmsUri: %w", err)
|
||||
}
|
||||
|
||||
protectionLvl32, err := strconv.ParseInt(r[3], 10, 32)
|
||||
if err != nil {
|
||||
return "", "", "", 0, "", err
|
||||
}
|
||||
return r[0], r[1], r[2], int32(protectionLvl32), string(kekIDByte), nil
|
||||
}
|
||||
|
||||
func getGCPStorageConfig(uri *url.URL) (string, string, error) {
|
||||
r, err := getConfig(uri.Query(), []string{"project", "bucket"})
|
||||
return r[0], r[1], err
|
||||
}
|
||||
|
||||
func getClusterKMSConfig(uri *url.URL) (MasterSecret, error) {
|
||||
r, err := getConfig(uri.Query(), []string{"key", "salt"})
|
||||
if err != nil {
|
||||
return MasterSecret{}, err
|
||||
}
|
||||
|
||||
if len(r) != 2 {
|
||||
return MasterSecret{}, fmt.Errorf("expected 2 KmsURI args, got %d", len(r))
|
||||
}
|
||||
|
||||
key, err := base64.URLEncoding.DecodeString(r[0])
|
||||
if err != nil {
|
||||
return MasterSecret{}, fmt.Errorf("parsing key from kmsUri: %w", err)
|
||||
}
|
||||
salt, err := base64.URLEncoding.DecodeString(r[1])
|
||||
if err != nil {
|
||||
return MasterSecret{}, fmt.Errorf("parsing salt from kmsUri: %w", err)
|
||||
}
|
||||
|
||||
return MasterSecret{Key: key, Salt: salt}, nil
|
||||
}
|
||||
|
||||
// getConfig parses url query values, returning a map of the requested values.
|
||||
// Returns an error if a key has no value.
|
||||
// This function MUST always return a slice of the same length as len(keys).
|
||||
|
@ -90,11 +90,7 @@ func TestGetKMS(t *testing.T) {
|
||||
wantErr: true,
|
||||
},
|
||||
"azure kms": {
|
||||
uri: fmt.Sprintf(AzureKMSURI, "", "", ""),
|
||||
wantErr: true,
|
||||
},
|
||||
"azure hsm": {
|
||||
uri: fmt.Sprintf(AzureHSMURI, "", ""),
|
||||
uri: fmt.Sprintf(AzureKMSURI, "", "", "", "", "", ""),
|
||||
wantErr: true,
|
||||
},
|
||||
"gcp kms": {
|
||||
@ -175,49 +171,6 @@ func TestGetAzureBlobConfig(t *testing.T) {
|
||||
assert.Equal(connStr, rConnStr)
|
||||
}
|
||||
|
||||
func TestGetGCPKMSConfig(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
project := "test-project"
|
||||
location := "global"
|
||||
keyRing := "test-ring"
|
||||
protectionLvl := "2"
|
||||
kekID := base64.URLEncoding.EncodeToString([]byte(constellationKekID))
|
||||
uri, err := url.Parse(fmt.Sprintf(GCPKMSURI, project, location, keyRing, protectionLvl, kekID))
|
||||
require.NoError(err)
|
||||
rProject, rLocation, rKeyRing, rProtectionLvl, rKekID, err := getGCPKMSConfig(uri)
|
||||
require.NoError(err)
|
||||
assert.Equal(project, rProject)
|
||||
assert.Equal(location, rLocation)
|
||||
assert.Equal(keyRing, rKeyRing)
|
||||
assert.Equal(int32(2), rProtectionLvl)
|
||||
assert.Equal(constellationKekID, rKekID)
|
||||
|
||||
uri, err = url.Parse(fmt.Sprintf(GCPKMSURI, project, location, keyRing, "invalid", kekID))
|
||||
require.NoError(err)
|
||||
_, _, _, _, _, err = getGCPKMSConfig(uri)
|
||||
assert.Error(err)
|
||||
}
|
||||
|
||||
func TestGetClusterKMSConfig(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
expectedSalt := []byte{
|
||||
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
|
||||
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
|
||||
}
|
||||
|
||||
masterSecretIn := MasterSecret{Key: []byte("key"), Salt: expectedSalt}
|
||||
uri, err := url.Parse(masterSecretIn.EncodeToURI())
|
||||
require.NoError(err)
|
||||
|
||||
masterSecretOut, err := getClusterKMSConfig(uri)
|
||||
assert.NoError(err)
|
||||
assert.Equal(expectedSalt, masterSecretOut.Salt)
|
||||
}
|
||||
|
||||
func TestGetConfig(t *testing.T) {
|
||||
const testURI = "test://config?name=test-name&data=test-data&value=test-value"
|
||||
|
||||
|
134
internal/kms/test/aws_test.go
Normal file
134
internal/kms/test/aws_test.go
Normal file
@ -0,0 +1,134 @@
|
||||
//go:build integration
|
||||
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms/aws"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAwsStorage(t *testing.T) {
|
||||
if !*runAwsStorage {
|
||||
t.Skip("Skipping AWS storage test")
|
||||
}
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
bucketID := strings.ToLower(addSuffix("test-bucket"))
|
||||
|
||||
// create bucket
|
||||
store, err := storage.NewAWSS3Storage(ctx, bucketID, func(*s3.Options) {})
|
||||
require.NoError(err)
|
||||
|
||||
testDEK1 := []byte("test DEK")
|
||||
testDEK2 := []byte("more test DEK")
|
||||
|
||||
// request unset value
|
||||
_, err = store.Get(ctx, "test:input")
|
||||
assert.Error(err)
|
||||
|
||||
// test Put method
|
||||
assert.NoError(store.Put(ctx, "volume01", testDEK1))
|
||||
assert.NoError(store.Put(ctx, "volume02", testDEK2))
|
||||
|
||||
// make sure values have been set
|
||||
val, err := store.Get(ctx, "volume01")
|
||||
assert.NoError(err)
|
||||
assert.Equal(testDEK1, val)
|
||||
val, err = store.Get(ctx, "volume02")
|
||||
assert.NoError(err)
|
||||
assert.Equal(testDEK2, val)
|
||||
|
||||
_, err = store.Get(ctx, "invalid:key")
|
||||
assert.Error(err)
|
||||
assert.ErrorIs(err, storage.ErrDEKUnset)
|
||||
|
||||
cleanUpBucket(ctx, require, bucketID, func(*s3.Options) {})
|
||||
}
|
||||
|
||||
func cleanUpBucket(ctx context.Context, require *require.Assertions, bucketID string, optFns ...func(*s3.Options)) {
|
||||
cfg, err := config.LoadDefaultConfig(ctx)
|
||||
require.NoError(err)
|
||||
client := s3.NewFromConfig(cfg, optFns...)
|
||||
|
||||
// List all objects of the bucket
|
||||
listObjectsInput := &s3.ListObjectsV2Input{
|
||||
Bucket: &bucketID,
|
||||
}
|
||||
output, _ := client.ListObjectsV2(ctx, listObjectsInput)
|
||||
var objects []string
|
||||
var i int32
|
||||
for i = 0; i < output.KeyCount; i++ {
|
||||
objects = append(objects, *output.Contents[i].Key)
|
||||
}
|
||||
// Delete all objects of the bucket
|
||||
require.NoError(cleanUpObjects(ctx, client, bucketID, objects))
|
||||
|
||||
// Delete the bucket
|
||||
deleteBucketInput := &s3.DeleteBucketInput{
|
||||
Bucket: &bucketID,
|
||||
}
|
||||
_, err = client.DeleteBucket(ctx, deleteBucketInput)
|
||||
require.NoError(err)
|
||||
}
|
||||
|
||||
func cleanUpObjects(ctx context.Context, client *s3.Client, bucketID string, objectsToDelete []string) error {
|
||||
var objectsIdentifier []types.ObjectIdentifier
|
||||
for _, object := range objectsToDelete {
|
||||
objectsIdentifier = append(objectsIdentifier, types.ObjectIdentifier{Key: func(s string) *string { return &s }(object)})
|
||||
}
|
||||
deleteObjectsInput := &s3.DeleteObjectsInput{
|
||||
Bucket: &bucketID,
|
||||
Delete: &types.Delete{Objects: objectsIdentifier},
|
||||
}
|
||||
_, err := client.DeleteObjects(ctx, deleteObjectsInput)
|
||||
return err
|
||||
}
|
||||
|
||||
func TestAwsKms(t *testing.T) {
|
||||
if !*runAwsKms {
|
||||
t.Skip("Skipping AWS KMS test")
|
||||
}
|
||||
|
||||
if *kekID == "" || *awsAccessKeyID == "" || *awsAccessKey == "" || *awsRegion == "" {
|
||||
flag.Usage()
|
||||
t.Fatal("Required flags not set: --aws-access-key-id, --aws-access-key, --aws-region, --kek-id")
|
||||
}
|
||||
|
||||
require := require.New(t)
|
||||
|
||||
store := storage.NewMemMapStorage()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
cfg := uri.AWSConfig{
|
||||
KeyName: *kekID,
|
||||
Region: *awsRegion,
|
||||
AccessKeyID: *awsAccessKeyID,
|
||||
AccessKey: *awsAccessKey,
|
||||
}
|
||||
kmsClient, err := aws.New(ctx, store, cfg)
|
||||
require.NoError(err)
|
||||
|
||||
runKMSTest(t, kmsClient)
|
||||
}
|
111
internal/kms/test/azure_test.go
Normal file
111
internal/kms/test/azure_test.go
Normal file
@ -0,0 +1,111 @@
|
||||
//go:build integration
|
||||
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms/azure"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAzureStorage(t *testing.T) {
|
||||
if !*runAzStorage {
|
||||
t.Skip("Skipping Azure storage test")
|
||||
}
|
||||
if *azConnectionString == "" || *azContainer == "" {
|
||||
flag.Usage()
|
||||
t.Fatal("Required flags not set: --az-connection-string, --az-container")
|
||||
}
|
||||
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
store, err := storage.NewAzureStorage(ctx, *azConnectionString, *azContainer, nil)
|
||||
require.NoError(err)
|
||||
|
||||
testData := []byte("Constellation test data")
|
||||
testName := "constellation-test"
|
||||
|
||||
err = store.Put(ctx, testName, testData)
|
||||
assert.NoError(err)
|
||||
|
||||
got, err := store.Get(ctx, testName)
|
||||
assert.NoError(err)
|
||||
assert.Equal(testData, got)
|
||||
|
||||
_, err = store.Get(ctx, addSuffix("does-not-exist"))
|
||||
assert.ErrorIs(err, storage.ErrDEKUnset)
|
||||
}
|
||||
|
||||
func TestAzureKeyKMS(t *testing.T) {
|
||||
if !*runAzKms {
|
||||
t.Skip("Skipping Azure Key Vault test")
|
||||
}
|
||||
|
||||
if *kekID == "" || *azClientID == "" || *azClientSecret == "" || *azTenantID == "" || *azVaultName == "" {
|
||||
flag.Usage()
|
||||
t.Fatal("Required flags not set: --az-tenant-id, --az-client-id, --az-client-secret, --az-vault-name, --kek-id")
|
||||
}
|
||||
require := require.New(t)
|
||||
|
||||
store := storage.NewMemMapStorage()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
cfg := uri.AzureConfig{
|
||||
TenantID: *azTenantID,
|
||||
ClientID: *azClientID,
|
||||
ClientSecret: *azClientSecret,
|
||||
VaultName: *azVaultName,
|
||||
VaultType: uri.DefaultCloud,
|
||||
KeyName: *kekID,
|
||||
}
|
||||
kmsClient, err := azure.New(ctx, store, cfg)
|
||||
require.NoError(err)
|
||||
|
||||
runKMSTest(t, kmsClient)
|
||||
}
|
||||
|
||||
func TestAzureKeyHSM(t *testing.T) {
|
||||
if !*runAzHsm {
|
||||
t.Skip("Skipping Azure HSM test")
|
||||
}
|
||||
|
||||
if *kekID == "" || *azClientID == "" || *azClientSecret == "" || *azTenantID == "" || *azVaultName == "" {
|
||||
flag.Usage()
|
||||
t.Fatal("Required flags not set: --az-tenant-id, --az-client-id, --az-client-secret, --az-vault-name, --kek-id")
|
||||
}
|
||||
require := require.New(t)
|
||||
|
||||
store := storage.NewMemMapStorage()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
cfg := uri.AzureConfig{
|
||||
TenantID: *azTenantID,
|
||||
ClientID: *azClientID,
|
||||
ClientSecret: *azClientSecret,
|
||||
VaultName: *azVaultName,
|
||||
VaultType: uri.HSMDefaultCloud,
|
||||
KeyName: *kekID,
|
||||
}
|
||||
kmsClient, err := azure.New(ctx, store, cfg)
|
||||
require.NoError(err)
|
||||
|
||||
runKMSTest(t, kmsClient)
|
||||
}
|
81
internal/kms/test/gcp_test.go
Normal file
81
internal/kms/test/gcp_test.go
Normal file
@ -0,0 +1,81 @@
|
||||
//go:build integration
|
||||
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms/gcp"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGCPKMS(t *testing.T) {
|
||||
if !*runGcpKms {
|
||||
t.Skip("Skipping Google KMS test")
|
||||
}
|
||||
if *gcpProjectID == "" || *gcpLocation == "" || *gcpKeyRing == "" || *kekID == "" {
|
||||
flag.Usage()
|
||||
t.Fatal("Required flags not set: --gcp-project, --gcp-location, --gcp-keyring, --kek-id")
|
||||
}
|
||||
require := require.New(t)
|
||||
|
||||
store := storage.NewMemMapStorage()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
cfg := uri.GCPConfig{
|
||||
CredentialsPath: *gcpCredentialsPath,
|
||||
ProjectID: *gcpProjectID,
|
||||
Location: *gcpLocation,
|
||||
KeyRing: *gcpKeyRing,
|
||||
KeyName: *kekID,
|
||||
}
|
||||
kmsClient, err := gcp.New(ctx, store, cfg)
|
||||
require.NoError(err)
|
||||
defer kmsClient.Close()
|
||||
|
||||
runKMSTest(t, kmsClient)
|
||||
}
|
||||
|
||||
func TestGcpStorage(t *testing.T) {
|
||||
if !*runGcpStorage {
|
||||
t.Skip("Skipping Google Storage test")
|
||||
}
|
||||
|
||||
if *gcpProjectID == "" || *gcpBucket == "" {
|
||||
flag.Usage()
|
||||
t.Fatal("Required flags not set: --gcp-project, --gcp-bucket ")
|
||||
}
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
store, err := storage.NewGoogleCloudStorage(ctx, *gcpProjectID, *gcpBucket, nil)
|
||||
assert.NoError(err)
|
||||
|
||||
testData := []byte("Constellation test data")
|
||||
testName := "constellation-test"
|
||||
|
||||
err = store.Put(ctx, testName, testData)
|
||||
assert.NoError(err)
|
||||
|
||||
got, err := store.Get(ctx, testName)
|
||||
assert.NoError(err)
|
||||
assert.Equal(testData, got)
|
||||
|
||||
_, err = store.Get(ctx, addSuffix("does-not-exist"))
|
||||
assert.ErrorIs(err, storage.ErrDEKUnset)
|
||||
}
|
92
internal/kms/test/integration_test.go
Normal file
92
internal/kms/test/integration_test.go
Normal file
@ -0,0 +1,92 @@
|
||||
//go:build integration
|
||||
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
// Package test provides integration tests for KMS and storage backends.
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
kekID = flag.String("kek-id", "", "ID of the key to use for KMS test.")
|
||||
|
||||
runAwsStorage = flag.Bool("aws-storage", false, "set to run AWS S3 Bucket Storage test")
|
||||
runAwsKms = flag.Bool("aws-kms", false, "set to run AWS KMS test")
|
||||
awsRegion = flag.String("aws-region", "us-east-1", "Region to use for AWS tests. Required for AWS KMS test.")
|
||||
awsAccessKeyID = flag.String("aws-access-key-id", "", "ID of the Access key to use for AWS tests. Required for AWS KMS test.")
|
||||
awsAccessKey = flag.String("aws-access-key", "", "Access key to use for AWS tests. Required for AWS KMS test.")
|
||||
|
||||
azConnectionString = flag.String("az-storage-conn", "", "Connection string for Azure storage account. Required for Azure storage test.")
|
||||
azContainer = flag.String("az-container", "constellation-test-storage", "Container to save test data to. Required for Azure storage test.")
|
||||
runAzStorage = flag.Bool("az-storage", false, "set to run Azure Storage test")
|
||||
runAzKms = flag.Bool("az-kms", false, "set to run Azure KMS test")
|
||||
runAzHsm = flag.Bool("az-hsm", false, "set to run Azure HSM test")
|
||||
azVaultName = flag.String("az-vault-name", "", "Name of the Azure Key Vault to use. Required for Azure KMS/HSM test.")
|
||||
azTenantID = flag.String("az-tenant-id", "", "Tenant ID to use for Azure tests. Required for Azure KMS/HSM test.")
|
||||
azClientID = flag.String("az-client-id", "", "Client ID to use for Azure tests. Required for Azure KMS/HSM test.")
|
||||
azClientSecret = flag.String("az-client-secret", "", "Client secret to use for Azure tests. Required for Azure KMS/HSM test.")
|
||||
|
||||
runGcpKms = flag.Bool("gcp-kms", false, "set to run Google KMS test")
|
||||
runGcpStorage = flag.Bool("gcp-storage", false, "set to run Google Storage test")
|
||||
gcpCredentialsPath = flag.String("gcp-credentials-path", "", "Path to a credentials file. Optional for Google KMS and Google storage test.")
|
||||
gcpBucket = flag.String("gcp-bucket", "", "Bucket to save test data to. Required for Google Storage test.")
|
||||
gcpProjectID = flag.String("gcp-project", "", "Project ID to use for Google tests. Required for Google KMS and Google storage test.")
|
||||
gcpKeyRing = flag.String("gcp-keyring", "", "Key ring to use for Google KMS test. Required for Google KMS test.")
|
||||
gcpLocation = flag.String("gcp-location", "global", "Location of the keyring. Required for Google KMS test.")
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
rand.Seed(time.Now().Unix())
|
||||
flag.Parse()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func runKMSTest(t *testing.T, kms kms.CloudKMS) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
dekName := "test-dek"
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
res, err := kms.GetDEK(ctx, dekName, config.SymmetricKeyLength)
|
||||
require.NoError(err)
|
||||
t.Logf("DEK 1: %x\n", res)
|
||||
|
||||
res2, err := kms.GetDEK(ctx, dekName, config.SymmetricKeyLength)
|
||||
require.NoError(err)
|
||||
assert.Equal(res, res2)
|
||||
t.Logf("DEK 2: %x\n", res2)
|
||||
|
||||
res3, err := kms.GetDEK(ctx, addSuffix(dekName), config.SymmetricKeyLength)
|
||||
require.NoError(err)
|
||||
assert.Len(res3, config.SymmetricKeyLength)
|
||||
assert.NotEqual(res, res3)
|
||||
t.Logf("DEK 3: %x\n", res3)
|
||||
}
|
||||
|
||||
func addSuffix(s string) string {
|
||||
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
b := make([]rune, 5)
|
||||
for i := range b {
|
||||
b[i] = letters[rand.Intn(len(letters))]
|
||||
}
|
||||
return s + "-" + string(b)
|
||||
}
|
321
internal/kms/uri/uri.go
Normal file
321
internal/kms/uri/uri.go
Normal file
@ -0,0 +1,321 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/*
|
||||
Package uri provides URIs and parsing logic for KMS and storage URIs.
|
||||
|
||||
The URI for a KMS is of the form:
|
||||
|
||||
kms://<provider>?<provider-specific-query-parameters>
|
||||
|
||||
The URI for a storage is of the form:
|
||||
|
||||
storage://<provider>/<provider-specific-query-parameters>
|
||||
|
||||
A URI contains all information necessary to connect to the KMS or storage.
|
||||
*/
|
||||
package uri
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultCloud is the URL for the default Vault URL.
|
||||
DefaultCloud VaultBaseURL = "vault.azure.net"
|
||||
// ChinaCloud is the URL for Vaults in Azure China Cloud.
|
||||
ChinaCloud VaultBaseURL = "vault.azure.cn"
|
||||
// USGovCloud is the URL for Vaults in Azure US Government Cloud.
|
||||
USGovCloud VaultBaseURL = "vault.usgovcloudapi.net"
|
||||
// GermanCloud is the URL for Vaults in Azure German Cloud.
|
||||
GermanCloud VaultBaseURL = "vault.microsoftazure.de"
|
||||
// HSMDefaultCloud is the URL for HSM Vaults.
|
||||
HSMDefaultCloud VaultBaseURL = "managedhsm.azure.net"
|
||||
)
|
||||
|
||||
// VaultBaseURL is the base URL of the vault.
|
||||
// It defines what type of key vault is used.
|
||||
type VaultBaseURL string
|
||||
|
||||
// Well known endpoints for KMS services.
|
||||
const (
|
||||
awsKMSURI = "kms://aws?region=%s&accessKeyID=%s&acccessKey=%skeyName=%s"
|
||||
azureKMSURI = "kms://azure?tenantID=%s&clientID=%s&clientSecret=%s&vaultName=%s&vaultType=%s&keyName=%s"
|
||||
gcpKMSURI = "kms://gcp?project=%s&location=%s&keyRing=%s&credentialsPath=%s&keyName=%s"
|
||||
clusterKMSURI = "kms://cluster-kms?key=%s&salt=%s"
|
||||
awsS3URI = "storage://aws?bucket=%s"
|
||||
azureBlobURI = "storage://azure?container=%s&connectionString=%s"
|
||||
gcpStorageURI = "storage://gcp?projects=%s&bucket=%s"
|
||||
NoStoreURI = "storage://no-store"
|
||||
)
|
||||
|
||||
// MasterSecret holds the master key and salt for deriving keys.
|
||||
type MasterSecret struct {
|
||||
Key []byte `json:"key"`
|
||||
Salt []byte `json:"salt"`
|
||||
}
|
||||
|
||||
// EncodeToURI returns a URI encoding the master secret.
|
||||
func (m MasterSecret) EncodeToURI() string {
|
||||
return fmt.Sprintf(
|
||||
clusterKMSURI,
|
||||
base64.URLEncoding.EncodeToString(m.Key),
|
||||
base64.URLEncoding.EncodeToString(m.Salt),
|
||||
)
|
||||
}
|
||||
|
||||
// DecodeMasterSecretFromURI decodes a master secret from a URI.
|
||||
func DecodeMasterSecretFromURI(uri string) (MasterSecret, error) {
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return MasterSecret{}, err
|
||||
}
|
||||
|
||||
if u.Scheme != "kms" {
|
||||
return MasterSecret{}, fmt.Errorf("invalid scheme: %q", u.Scheme)
|
||||
}
|
||||
if u.Host != "cluster-kms" {
|
||||
return MasterSecret{}, fmt.Errorf("invalid host: %q", u.Host)
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
key, err := getBase64QueryParameter(q, "key")
|
||||
if err != nil {
|
||||
return MasterSecret{}, err
|
||||
}
|
||||
salt, err := getBase64QueryParameter(q, "salt")
|
||||
if err != nil {
|
||||
return MasterSecret{}, err
|
||||
}
|
||||
return MasterSecret{
|
||||
Key: key,
|
||||
Salt: salt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AWSConfig is the configuration to authenticate with AWS KMS.
|
||||
type AWSConfig struct {
|
||||
KeyName string
|
||||
Region string
|
||||
AccessKeyID string
|
||||
AccessKey string
|
||||
}
|
||||
|
||||
// DecodeAWSConfigFromURI decodes an AWS configuration from a URI.
|
||||
func DecodeAWSConfigFromURI(uri string) (AWSConfig, error) {
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return AWSConfig{}, err
|
||||
}
|
||||
|
||||
if u.Scheme != "kms" {
|
||||
return AWSConfig{}, fmt.Errorf("invalid scheme: %q", u.Scheme)
|
||||
}
|
||||
if u.Host != "aws" {
|
||||
return AWSConfig{}, fmt.Errorf("invalid host: %q", u.Host)
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
keyName, err := getQueryParameter(q, "keyName")
|
||||
if err != nil {
|
||||
return AWSConfig{}, err
|
||||
}
|
||||
region, err := getQueryParameter(q, "region")
|
||||
if err != nil {
|
||||
return AWSConfig{}, err
|
||||
}
|
||||
accessKeyID, err := getQueryParameter(q, "accessKeyID")
|
||||
if err != nil {
|
||||
return AWSConfig{}, err
|
||||
}
|
||||
accessKey, err := getQueryParameter(q, "accessKey")
|
||||
if err != nil {
|
||||
return AWSConfig{}, err
|
||||
}
|
||||
|
||||
return AWSConfig{
|
||||
KeyName: keyName,
|
||||
Region: region,
|
||||
AccessKeyID: accessKeyID,
|
||||
AccessKey: accessKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EncodeToURI returns a URI encoding the AWS configuration.
|
||||
func (c AWSConfig) EncodeToURI() string {
|
||||
return fmt.Sprintf(
|
||||
awsKMSURI,
|
||||
c.Region,
|
||||
c.AccessKeyID,
|
||||
c.AccessKey,
|
||||
c.KeyName,
|
||||
)
|
||||
}
|
||||
|
||||
// AzureConfig is the configuration to authenticate with Azure Key Vault.
|
||||
type AzureConfig struct {
|
||||
TenantID string
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
KeyName string
|
||||
VaultName string
|
||||
VaultType VaultBaseURL
|
||||
}
|
||||
|
||||
// DecodeAzureConfigFromURI decodes an Azure configuration from a URI.
|
||||
func DecodeAzureConfigFromURI(uri string) (AzureConfig, error) {
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return AzureConfig{}, err
|
||||
}
|
||||
|
||||
if u.Scheme != "kms" {
|
||||
return AzureConfig{}, fmt.Errorf("invalid scheme: %q", u.Scheme)
|
||||
}
|
||||
if u.Host != "azure" {
|
||||
return AzureConfig{}, fmt.Errorf("invalid host: %q", u.Host)
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
tenantID, err := getQueryParameter(q, "tenantID")
|
||||
if err != nil {
|
||||
return AzureConfig{}, err
|
||||
}
|
||||
clientID, err := getQueryParameter(q, "clientID")
|
||||
if err != nil {
|
||||
return AzureConfig{}, err
|
||||
}
|
||||
clientSecret, err := getQueryParameter(q, "clientSecret")
|
||||
if err != nil {
|
||||
return AzureConfig{}, err
|
||||
}
|
||||
vaultName, err := getQueryParameter(q, "vaultName")
|
||||
if err != nil {
|
||||
return AzureConfig{}, err
|
||||
}
|
||||
vaultType, err := getQueryParameter(q, "vaultType")
|
||||
if err != nil {
|
||||
return AzureConfig{}, err
|
||||
}
|
||||
keyName, err := getQueryParameter(q, "keyName")
|
||||
if err != nil {
|
||||
return AzureConfig{}, err
|
||||
}
|
||||
|
||||
return AzureConfig{
|
||||
TenantID: tenantID,
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
VaultName: vaultName,
|
||||
VaultType: VaultBaseURL(vaultType),
|
||||
KeyName: keyName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EncodeToURI returns a URI encoding the Azure configuration.
|
||||
func (a AzureConfig) EncodeToURI() string {
|
||||
return fmt.Sprintf(
|
||||
azureKMSURI,
|
||||
url.QueryEscape(a.TenantID),
|
||||
url.QueryEscape(a.ClientID),
|
||||
url.QueryEscape(a.ClientSecret),
|
||||
url.QueryEscape(a.VaultName),
|
||||
url.QueryEscape(string(a.VaultType)),
|
||||
url.QueryEscape(a.KeyName),
|
||||
)
|
||||
}
|
||||
|
||||
// GCPConfig is the configuration to authenticate with GCP KMS.
|
||||
type GCPConfig struct {
|
||||
// CredentialsPath is the path to a credentials file of a service account used to authorize against the KMS.
|
||||
CredentialsPath string
|
||||
ProjectID string
|
||||
Location string
|
||||
KeyRing string
|
||||
KeyName string
|
||||
}
|
||||
|
||||
// DecodeGCPConfigFromURI decodes a GCP configuration from a URI.
|
||||
func DecodeGCPConfigFromURI(uri string) (GCPConfig, error) {
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return GCPConfig{}, err
|
||||
}
|
||||
|
||||
if u.Scheme != "kms" {
|
||||
return GCPConfig{}, fmt.Errorf("invalid scheme: %q", u.Scheme)
|
||||
}
|
||||
if u.Host != "gcp" {
|
||||
return GCPConfig{}, fmt.Errorf("invalid host: %q", u.Host)
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
credentials, err := getQueryParameter(q, "credentials")
|
||||
if err != nil {
|
||||
return GCPConfig{}, err
|
||||
}
|
||||
projectID, err := getQueryParameter(q, "projectID")
|
||||
if err != nil {
|
||||
return GCPConfig{}, err
|
||||
}
|
||||
location, err := getQueryParameter(q, "location")
|
||||
if err != nil {
|
||||
return GCPConfig{}, err
|
||||
}
|
||||
keyRing, err := getQueryParameter(q, "keyRing")
|
||||
if err != nil {
|
||||
return GCPConfig{}, err
|
||||
}
|
||||
keyName, err := getQueryParameter(q, "keyName")
|
||||
if err != nil {
|
||||
return GCPConfig{}, err
|
||||
}
|
||||
|
||||
return GCPConfig{
|
||||
CredentialsPath: credentials,
|
||||
ProjectID: projectID,
|
||||
Location: location,
|
||||
KeyRing: keyRing,
|
||||
KeyName: keyName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EncodeToURI returns a URI encoding the GCP configuration.
|
||||
func (g GCPConfig) EncodeToURI() string {
|
||||
return fmt.Sprintf(
|
||||
gcpKMSURI,
|
||||
url.QueryEscape(g.ProjectID),
|
||||
url.QueryEscape(g.Location),
|
||||
url.QueryEscape(g.KeyRing),
|
||||
url.QueryEscape(g.CredentialsPath),
|
||||
url.QueryEscape(g.KeyName),
|
||||
)
|
||||
}
|
||||
|
||||
// getBase64QueryParameter returns the url-base64-decoded value for the given key from the query parameters.
|
||||
func getBase64QueryParameter(q url.Values, key string) ([]byte, error) {
|
||||
value, err := getQueryParameter(q, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return base64.URLEncoding.DecodeString(value)
|
||||
}
|
||||
|
||||
// getQueryParameter returns the unescaped value for the given key from the query parameters.
|
||||
func getQueryParameter(q url.Values, key string) (string, error) {
|
||||
value := q.Get(key)
|
||||
if value == "" {
|
||||
return "", fmt.Errorf("missing query parameter %q", key)
|
||||
}
|
||||
|
||||
value, err := url.QueryUnescape(value)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to unescape value for key: %q", key)
|
||||
}
|
||||
return value, nil
|
||||
}
|
@ -61,6 +61,7 @@ func main() {
|
||||
if err != nil {
|
||||
log.With(zap.Error(err)).Fatalf("Failed to setup KMS")
|
||||
}
|
||||
defer conKMS.Close()
|
||||
|
||||
if err := server.New(log.Named("keyService"), conKMS).Run(*port); err != nil {
|
||||
log.With(zap.Error(err)).Fatalf("Failed to run key-service server")
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms"
|
||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||
"github.com/edgelesssys/constellation/v2/keyservice/keyserviceproto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -53,6 +54,7 @@ func TestGetDataKey(t *testing.T) {
|
||||
}
|
||||
|
||||
type stubKMS struct {
|
||||
kms.CloudKMS
|
||||
masterKey []byte
|
||||
derivedKey []byte
|
||||
deriveKeyErr error
|
||||
|
@ -1,225 +0,0 @@
|
||||
//go:build integration
|
||||
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/service/kms"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
kmsconfig "github.com/edgelesssys/constellation/v2/internal/kms/config"
|
||||
awsInterface "github.com/edgelesssys/constellation/v2/internal/kms/kms/aws"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var defaultPolicyWithDecryption = `{
|
||||
"Version" : "2012-10-17",
|
||||
"Id" : "updated-policy",
|
||||
"Statement" : [ {
|
||||
"Sid" : "Enable IAM User Permissions",
|
||||
"Effect" : "Allow",
|
||||
"Principal" : {
|
||||
"AWS" : "*"
|
||||
},
|
||||
"Action" : [
|
||||
"kms:CreateAlias",
|
||||
"kms:CreateKey",
|
||||
"kms:Decrypt",
|
||||
"kms:DeleteAlias",
|
||||
"kms:DescribeKey",
|
||||
"kms:Encrypt",
|
||||
"kms:GenerateDataKey",
|
||||
"kms:GenerateDataKeyWithoutPlaintext",
|
||||
"kms:GetKeyPolicy",
|
||||
"kms:GetParametersForImport",
|
||||
"kms:ImportKeyMaterial",
|
||||
"kms:PutKeyPolicy",
|
||||
"kms:ScheduleKeyDeletion"
|
||||
],
|
||||
"Resource" : "*"
|
||||
} ]
|
||||
}`
|
||||
|
||||
func TestAwsStorage(t *testing.T) {
|
||||
if !*runAwsStorage {
|
||||
t.Skip("Skipping AWS storage test")
|
||||
}
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
bucketID := strings.ToLower(addSuffix("test-bucket"))
|
||||
|
||||
// create bucket
|
||||
store, err := storage.NewAWSS3Storage(ctx, bucketID, func(*s3.Options) {})
|
||||
require.NoError(err)
|
||||
|
||||
testDEK1 := []byte("test DEK")
|
||||
testDEK2 := []byte("more test DEK")
|
||||
|
||||
// request unset value
|
||||
_, err = store.Get(ctx, "test:input")
|
||||
assert.Error(err)
|
||||
|
||||
// test Put method
|
||||
assert.NoError(store.Put(ctx, "volume01", testDEK1))
|
||||
assert.NoError(store.Put(ctx, "volume02", testDEK2))
|
||||
|
||||
// make sure values have been set
|
||||
val, err := store.Get(ctx, "volume01")
|
||||
assert.NoError(err)
|
||||
assert.Equal(testDEK1, val)
|
||||
val, err = store.Get(ctx, "volume02")
|
||||
assert.NoError(err)
|
||||
assert.Equal(testDEK2, val)
|
||||
|
||||
_, err = store.Get(ctx, "invalid:key")
|
||||
assert.Error(err)
|
||||
assert.ErrorIs(err, storage.ErrDEKUnset)
|
||||
|
||||
cleanUpBucket(ctx, require, bucketID, func(*s3.Options) {})
|
||||
}
|
||||
|
||||
func cleanUpBucket(ctx context.Context, require *require.Assertions, bucketID string, optFns ...func(*s3.Options)) {
|
||||
cfg, err := config.LoadDefaultConfig(ctx)
|
||||
require.NoError(err)
|
||||
client := s3.NewFromConfig(cfg, optFns...)
|
||||
|
||||
// List all objects of the bucket
|
||||
listObjectsInput := &s3.ListObjectsV2Input{
|
||||
Bucket: &bucketID,
|
||||
}
|
||||
output, _ := client.ListObjectsV2(ctx, listObjectsInput)
|
||||
var objects []string
|
||||
var i int32
|
||||
for i = 0; i < output.KeyCount; i++ {
|
||||
objects = append(objects, *output.Contents[i].Key)
|
||||
}
|
||||
// Delete all objects of the bucket
|
||||
require.NoError(cleanUpObjects(ctx, client, bucketID, objects))
|
||||
|
||||
// Delete the bucket
|
||||
deleteBucketInput := &s3.DeleteBucketInput{
|
||||
Bucket: &bucketID,
|
||||
}
|
||||
_, err = client.DeleteBucket(ctx, deleteBucketInput)
|
||||
require.NoError(err)
|
||||
}
|
||||
|
||||
func cleanUpObjects(ctx context.Context, client *s3.Client, bucketID string, objectsToDelete []string) error {
|
||||
var objectsIdentifier []types.ObjectIdentifier
|
||||
for _, object := range objectsToDelete {
|
||||
objectsIdentifier = append(objectsIdentifier, types.ObjectIdentifier{Key: aws.String(object)})
|
||||
}
|
||||
deleteObjectsInput := &s3.DeleteObjectsInput{
|
||||
Bucket: &bucketID,
|
||||
Delete: &types.Delete{Objects: objectsIdentifier},
|
||||
}
|
||||
_, err := client.DeleteObjects(ctx, deleteObjectsInput)
|
||||
return err
|
||||
}
|
||||
|
||||
func TestAwsKms(t *testing.T) {
|
||||
if !*runAwsKms {
|
||||
t.Skip("Skipping AWS KMS test")
|
||||
}
|
||||
require := require.New(t)
|
||||
assert := assert.New(t)
|
||||
|
||||
newKEKId1 := addSuffix("test-key-")
|
||||
newKEKId2 := addSuffix("test-key-")
|
||||
require.NotEqual(newKEKId1, newKEKId2)
|
||||
var keyPolicyProducer createKeyPolicyFunc
|
||||
|
||||
client, err := awsInterface.New(context.Background(), &keyPolicyProducer, nil, newKEKId1)
|
||||
require.NoError(err)
|
||||
|
||||
privateKEK1 := []byte(strings.Repeat("1234", 8))
|
||||
privateKEK2 := []byte(strings.Repeat("5678", 8))
|
||||
privateKEK3 := make([]byte, 0)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
// test setting first KEK
|
||||
assert.NoError(client.CreateKEK(ctx, newKEKId1, privateKEK1))
|
||||
|
||||
// make sure that CreateKEK is idempotent
|
||||
assert.NoError(client.CreateKEK(ctx, newKEKId1, privateKEK1))
|
||||
|
||||
// make sure you can not overwrite KEK with different key material
|
||||
assert.NoError(client.CreateKEK(ctx, newKEKId1, privateKEK2))
|
||||
|
||||
// make sure that GetDEK is idempotent
|
||||
volumeKey1, err := client.GetDEK(ctx, "volume01", kmsconfig.SymmetricKeyLength)
|
||||
require.NoError(err)
|
||||
volumeKey1Copy, err := client.GetDEK(ctx, "volume01", kmsconfig.SymmetricKeyLength)
|
||||
require.NoError(err)
|
||||
assert.Equal(volumeKey1, volumeKey1Copy)
|
||||
|
||||
// test setting a second DEK
|
||||
volumeKey2, err := client.GetDEK(ctx, "volume02", kmsconfig.SymmetricKeyLength)
|
||||
require.NoError(err)
|
||||
assert.NotEqual(volumeKey1, volumeKey2)
|
||||
|
||||
// make sure AWS KMS generates KEK when calling CreateKEK with empty key
|
||||
assert.NoError(client.CreateKEK(ctx, newKEKId2, privateKEK3))
|
||||
|
||||
// make sure that CreateKEK is idempotent
|
||||
assert.NoError(client.CreateKEK(ctx, newKEKId2, privateKEK3))
|
||||
|
||||
// test setting a DEK with AWS KMS generated KEK
|
||||
volumeKey3, err := client.GetDEK(ctx, "volume03", kmsconfig.SymmetricKeyLength)
|
||||
require.NoError(err)
|
||||
assert.NotEqual(volumeKey1, volumeKey3)
|
||||
|
||||
cleanUp(ctx, assert, require, newKEKId1)
|
||||
cleanUp(ctx, assert, require, newKEKId2)
|
||||
}
|
||||
|
||||
func cleanUp(ctx context.Context, assert *assert.Assertions, require *require.Assertions, alias string) {
|
||||
cfg, err := config.LoadDefaultConfig(ctx)
|
||||
require.NoError(err)
|
||||
awsClient := kms.NewFromConfig(cfg)
|
||||
require.NotNil(awsClient)
|
||||
|
||||
describeKeyInput := &kms.DescribeKeyInput{
|
||||
KeyId: aws.String("alias/" + alias),
|
||||
}
|
||||
describeKeyOutput, err := awsClient.DescribeKey(ctx, describeKeyInput)
|
||||
assert.NoError(err)
|
||||
deleteAliasInput := &kms.DeleteAliasInput{
|
||||
AliasName: aws.String("alias/" + alias),
|
||||
}
|
||||
_, err = awsClient.DeleteAlias(ctx, deleteAliasInput)
|
||||
assert.NoError(err)
|
||||
scheduleKeyDeletionInput := &kms.ScheduleKeyDeletionInput{
|
||||
KeyId: describeKeyOutput.KeyMetadata.KeyId,
|
||||
PendingWindowInDays: aws.Int32(7),
|
||||
}
|
||||
_, err = awsClient.ScheduleKeyDeletion(ctx, scheduleKeyDeletionInput)
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
type createKeyPolicyFunc struct{}
|
||||
|
||||
func (fn *createKeyPolicyFunc) CreateKeyPolicy(keyID string) (string, error) {
|
||||
policy := defaultPolicyWithDecryption
|
||||
policy = strings.Replace(policy, "<AWSAccountId>", "795746500882", 2)
|
||||
return policy, nil
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
//go:build integration
|
||||
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms/azure"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
azVaultName = "edgeless-constellation-t"
|
||||
azHSMName = "edgeless-constellation-h"
|
||||
azContainerName = "constellation-test-storage"
|
||||
)
|
||||
|
||||
func TestAzureStorage(t *testing.T) {
|
||||
if !*runAzStorage {
|
||||
t.Skip("Skipping Azure storage test")
|
||||
}
|
||||
if *azConnectionString == "" {
|
||||
t.Fatal("Connection string for Azure storage must be set using the '-azStorageConn' flag")
|
||||
}
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
store, err := storage.NewAzureStorage(ctx, *azConnectionString, azContainerName, nil)
|
||||
require.NoError(err)
|
||||
|
||||
testData := []byte("Constellation test data")
|
||||
testName := "constellation-test"
|
||||
|
||||
err = store.Put(ctx, testName, testData)
|
||||
assert.NoError(err)
|
||||
|
||||
got, err := store.Get(ctx, testName)
|
||||
assert.NoError(err)
|
||||
assert.Equal(testData, got)
|
||||
|
||||
_, err = store.Get(ctx, addSuffix("does-not-exist"))
|
||||
assert.ErrorIs(err, storage.ErrDEKUnset)
|
||||
}
|
||||
|
||||
func TestAzureKeyVault(t *testing.T) {
|
||||
if !*runAzKms {
|
||||
t.Skip("Skipping Azure Key Vault test")
|
||||
}
|
||||
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
store := storage.NewMemMapStorage()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
kekName := "test-kek"
|
||||
client, err := azure.New(ctx, azVaultName, azure.DefaultCloud, store, kekName, nil)
|
||||
require.NoError(err)
|
||||
|
||||
dekName := "test-dek"
|
||||
|
||||
assert.NoError(client.CreateKEK(ctx, kekName, nil))
|
||||
|
||||
res, err := client.GetDEK(ctx, dekName, config.SymmetricKeyLength)
|
||||
assert.NoError(err)
|
||||
|
||||
res2, err := client.GetDEK(ctx, dekName, config.SymmetricKeyLength)
|
||||
assert.NoError(err)
|
||||
assert.Equal(res, res2)
|
||||
|
||||
res3, err := client.GetDEK(ctx, addSuffix(dekName), config.SymmetricKeyLength)
|
||||
assert.NoError(err)
|
||||
assert.Len(res3, config.SymmetricKeyLength)
|
||||
assert.NotEqual(res, res3)
|
||||
}
|
||||
|
||||
func TestAzureHSM(t *testing.T) {
|
||||
if !*runAzHsm {
|
||||
t.Skip("Skipping Azure HSM test")
|
||||
}
|
||||
|
||||
// This test requires an actively running Azure HSM
|
||||
// Since the HSMs are quiet expensive, you will have to create one manually to run this test
|
||||
// See: https://docs.microsoft.com/en-us/azure/key-vault/managed-hsm/quick-create-cli
|
||||
// Don't forget to remove the HSM after testing: az keyvault purge --hsm-name <HSM>
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
store := storage.NewMemMapStorage()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
kekName := "test-kek"
|
||||
client, err := azure.NewHSM(ctx, azHSMName, store, kekName, nil)
|
||||
require.NoError(err)
|
||||
|
||||
dekName := "test-dek"
|
||||
importedKek := "test-kek-import"
|
||||
kekData := []byte{0x52, 0xFD, 0xFC, 0x07, 0x21, 0x82, 0x65, 0x4F, 0x16, 0x3F, 0x5F, 0x0F, 0x9A, 0x62, 0x1D, 0x72, 0x95, 0x66, 0xC7, 0x4D, 0x10, 0x03, 0x7C, 0x4D, 0x7B, 0xBB, 0x04, 0x07, 0xD1, 0xE2, 0xC6, 0x49}
|
||||
|
||||
assert.NoError(client.CreateKEK(ctx, importedKek, kekData))
|
||||
|
||||
assert.NoError(client.CreateKEK(ctx, kekName, nil))
|
||||
|
||||
res, err := client.GetDEK(ctx, dekName, config.SymmetricKeyLength)
|
||||
require.NoError(err)
|
||||
assert.NotNil(res)
|
||||
|
||||
res2, err := client.GetDEK(ctx, dekName, config.SymmetricKeyLength)
|
||||
require.NoError(err)
|
||||
assert.Equal(res, res2)
|
||||
|
||||
res3, err := client.GetDEK(ctx, addSuffix(dekName), config.SymmetricKeyLength)
|
||||
require.NoError(err)
|
||||
assert.Len(res3, config.SymmetricKeyLength)
|
||||
assert.NotEqual(res, res3)
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
//go:build integration
|
||||
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/kms/apiv1/kmspb"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/kms/gcp"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
gcpBucket = "constellation-test-bucket"
|
||||
gcpProjectID = "constellation-kms-integration-test"
|
||||
gcpKeyRing = "test-ring"
|
||||
gcpLocation = "global"
|
||||
)
|
||||
|
||||
func TestCreateGcpKEK(t *testing.T) {
|
||||
if !*runGcpKms {
|
||||
t.Skip("Skipping Google KMS key creation test")
|
||||
}
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
store := storage.NewMemMapStorage()
|
||||
|
||||
kekName := addSuffix("test-kek")
|
||||
dekName := "test-dek"
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
kmsClient, err := gcp.New(ctx, gcpProjectID, gcpLocation, gcpKeyRing, store, kmspb.ProtectionLevel_SOFTWARE, kekName)
|
||||
require.NoError(err)
|
||||
|
||||
// Key name is random, but there is a chance we try to create a key that already exists, in that case the test fails
|
||||
assert.NoError(kmsClient.CreateKEK(ctx, kekName, nil))
|
||||
|
||||
res, err := kmsClient.GetDEK(ctx, dekName, config.SymmetricKeyLength)
|
||||
assert.NoError(err)
|
||||
|
||||
res2, err := kmsClient.GetDEK(ctx, dekName, config.SymmetricKeyLength)
|
||||
assert.NoError(err)
|
||||
assert.Equal(res, res2)
|
||||
|
||||
res3, err := kmsClient.GetDEK(ctx, addSuffix(dekName), config.SymmetricKeyLength)
|
||||
assert.NoError(err)
|
||||
assert.Len(res3, config.SymmetricKeyLength)
|
||||
assert.NotEqual(res, res3)
|
||||
}
|
||||
|
||||
func TestImportGcpKEK(t *testing.T) {
|
||||
if !*runGcpKms {
|
||||
t.Skip("Skipping Google KMS key import test")
|
||||
}
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
store := storage.NewMemMapStorage()
|
||||
|
||||
kekName := addSuffix("test-kek")
|
||||
kekData := []byte{0x52, 0xFD, 0xFC, 0x07, 0x21, 0x82, 0x65, 0x4F, 0x16, 0x3F, 0x5F, 0x0F, 0x9A, 0x62, 0x1D, 0x72, 0x95, 0x66, 0xC7, 0x4D, 0x10, 0x03, 0x7C, 0x4D, 0x7B, 0xBB, 0x04, 0x07, 0xD1, 0xE2, 0xC6, 0x49}
|
||||
dekName := "test-dek"
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
kmsClient, err := gcp.New(ctx, gcpProjectID, gcpLocation, gcpKeyRing, store, kmspb.ProtectionLevel_SOFTWARE, kekName)
|
||||
require.NoError(err)
|
||||
|
||||
assert.NoError(kmsClient.CreateKEK(ctx, kekName, kekData))
|
||||
|
||||
res, err := kmsClient.GetDEK(ctx, dekName, config.SymmetricKeyLength)
|
||||
assert.NoError(err)
|
||||
|
||||
res2, err := kmsClient.GetDEK(ctx, dekName, config.SymmetricKeyLength)
|
||||
assert.NoError(err)
|
||||
assert.Equal(res, res2)
|
||||
}
|
||||
|
||||
func TestGcpStorage(t *testing.T) {
|
||||
if !*runGcpStorage {
|
||||
t.Skip("Skipping Google Storage test")
|
||||
}
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
store, err := storage.NewGoogleCloudStorage(ctx, gcpProjectID, gcpBucket, nil)
|
||||
assert.NoError(err)
|
||||
|
||||
testData := []byte("Constellation test data")
|
||||
testName := "constellation-test"
|
||||
|
||||
err = store.Put(ctx, testName, testData)
|
||||
assert.NoError(err)
|
||||
|
||||
got, err := store.Get(ctx, testName)
|
||||
assert.NoError(err)
|
||||
assert.Equal(testData, got)
|
||||
|
||||
_, err = store.Get(ctx, addSuffix("does-not-exist"))
|
||||
assert.ErrorIs(err, storage.ErrDEKUnset)
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
//go:build integration
|
||||
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
// Package test provides integration tests for KMS and storage backends.
|
||||
package test
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
runAwsStorage = flag.Bool("awsStorage", false, "set to run AWS S3 Bucket Storage test")
|
||||
runAwsKms = flag.Bool("awsKms", false, "set to run AWS KMS test")
|
||||
azConnectionString = flag.String("azStorageConn", "", "connection string for Azure storage account. Required for Azure storage test.")
|
||||
runAzStorage = flag.Bool("azStorage", false, "set to run Azure Storage test")
|
||||
runAzKms = flag.Bool("azKms", false, "set to run Azure KMS test")
|
||||
runAzHsm = flag.Bool("azHsm", false, "set to run Azure HSM test")
|
||||
runGcpKms = flag.Bool("gcpKms", false, "set to run Google KMS test")
|
||||
runGcpStorage = flag.Bool("gcpStorage", false, "set to run Google Storage test")
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
rand.Seed(time.Now().Unix())
|
||||
flag.Parse()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func addSuffix(s string) string {
|
||||
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
b := make([]rune, 5)
|
||||
for i := range b {
|
||||
b[i] = letters[rand.Intn(len(letters))]
|
||||
}
|
||||
return s + "-" + string(b)
|
||||
}
|
@ -29,14 +29,12 @@ require (
|
||||
sigs.k8s.io/controller-runtime v0.13.1
|
||||
)
|
||||
|
||||
require github.com/onsi/ginkgo/v2 v2.7.0 // indirect
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.27 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.28 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
@ -75,6 +73,7 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.7.0 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
|
@ -42,6 +42,7 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/Azure/azure-sdk-for-go v67.3.0+incompatible h1:QEvenaO+Y9ShPeCWsSAtolzVUcb0T0tPeek5TDsovuM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.0 h1:VuHAcMq8pU1IWNT/m5yRaGqbK0BiQKHT8X4DTp9CHdI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.0/go.mod h1:tZoQYdDZNOiIjdSn0dVWVfl0NEPGOJqVLzSrcFk4Is0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.0 h1:t/W5MYAuQy81cvM8VUNfRLzhtKpXhVUAN7Cd7KVbTyc=
|
||||
@ -56,11 +57,11 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.1.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A=
|
||||
github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U=
|
||||
github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM=
|
||||
github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.21 h1:jjQnVFXPfekaqb8vIsv2G1lxshoW+oGv4MDlhRtnYZk=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.21/go.mod h1:zua7mBUaCc5YnSLKYgGJR/w5ePdMDA6H56upLsHzA9U=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
||||
@ -397,7 +398,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/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-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
Loading…
Reference in New Issue
Block a user