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:
Daniel Weiße 2023-02-08 12:03:54 +01:00 committed by GitHub
parent 68ce23b909
commit 3a7b829107
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 1319 additions and 3121 deletions

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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

View File

@ -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=

View File

@ -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)
```

View File

@ -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() {}

View File

@ -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
}

View File

@ -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() {}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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() {}

View File

@ -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)

View File

@ -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()
}

View File

@ -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)
}

View 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)
}

View 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)
}
})
}
}

View File

@ -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")

View File

@ -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
}

View File

@ -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)
}

View File

@ -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).

View File

@ -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"

View 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)
}

View 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)
}

View 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)
}

View 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
View 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
}

View File

@ -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")

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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

View File

@ -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=