diff --git a/README.md b/README.md index 38119cc..09e9ab7 100644 --- a/README.md +++ b/README.md @@ -90,8 +90,11 @@ uploaders: ### Environment variables Vault Raft Snapshot Agent supports configuration with environment variables. For some common options there are shortcuts defined: - `VAULT_ADDR` configures the url to the vault-server (same as `vault.url`) -- `AWS_ACCESS_KEY_ID` configures the access key for the AWS uploader (same as `uploaders.aws.credentials.key`) -- `SECRET_ACCESS_KEY` configures the access secret for the AWS uploader (same as `uploaders.aws.credentials.secret`) +- `AWS_ACCESS_KEY_ID` configures the access key for the AWS uploader (same as `uploaders.aws.credentials.key`) and AWS EC2 authentication +- `AWS_SECRET_ACCESS_KEY` configures the access secret for the AWS uploader (same as `uploaders.aws.credentials.secret`) and AWS EC2 authentication +- `AWS_SESSION_TOKEN` configures the session-token for AWS EC2 authentication +- `AWS_SHARED_CREDENTIALS_FILE` configures AWS EC2 authentication from a file + Any other option can be set by prefixing `VRSA_` to the uppercased path to the key and replacing `.` with `_`. For example `VRSA_SNAPSHOTS_FREQUENCY=` configures the snapshot-frequency and `VRSA_VAULT_AUTH_TOKEN=` configures the token authentication for vault. @@ -146,20 +149,116 @@ An AppRole allows the snapshot agent to automatically rotate tokens to avoid lon vault: auth: approle: - id: " + role: "" secret: "" ``` ##### Configuration options -- `id` **(required)** - specifies the role_id used to call the Vault API. See the authentication steps below +- `role` **(required)** - specifies the role_id used to call the Vault API. See the authentication steps below - `secret` **(required)** - specifies the secret_id used to call the Vault API - `path` *(default: approle)* - specifies the backend-name used to select the login-endpoint (`auth//login`) To allow the App-Role access to the snapshots you should run the following commands on your vault-cluster: ``` -vault write auth/approle/role/snapshot token_policies="snapshots" -vault read auth/approle/role/snapshot/ -vault write -f auth/approle/role/snapshot/ +vault write auth//role/snapshot token_policies=snapshots +vault read auth//role/snapshot/ +vault write -f auth//role/snapshot/ +``` + +#### AWS authentication + +Uses AWS for authentication (see the [Vault docs](https://developer.hashicorp.com/vault/docs/auth/aws)). + + +##### Minimal configuration +``` +vault: + auth: + aws: + role: "" +``` + +##### Configuration options +- `role` **(required)** - specifies the role used to call the Vault API. See the authentication steps below +- `ec2Nonce` - enables EC2 authentication and sets the required nonce +- `ec2SignatureType` *(default: pkcs7)* - changes the signature-type for EC2 authentication; valid values are `identity`, `pkcs7` and `rs2048` +- `iamServerIdHeader` - specifies the server-id-header when using IAM authtype +- `region` - specifies the aws region to use +- `path` *(default: aws)* - specifies the backend-name used to select the login-endpoint (`auth//login`) + +By default AWS authentication uses the iam authentication type unless `ec2Nonce` is set. The credentials for IAM authentication must be provided via environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN` or `AWS_SHARED_CREDENTIALS_FILE`). While relative paths normally are resolved relative to the configuration-file, `AWS_SHARED_CREDENTIALS_FILE` must be specified as an absolute path. + +To allow the access to the snapshots you should run the following commands on your vault-cluster: +``` +# for ec2 authentication +vault write auth//role/ auth_type=ec2 bound_ami_id= policies=snapshots max_ttl=500h + +# for iam authentication +vault write auth//role/ auth_type=iam bound_iam_principal_arn= policies=snapshots max_ttl=500h +``` + +#### Azure authentication + +Authentication using Azure (see [the Vault docs](https://developer.hashicorp.com/vault/docs/auth/azure)). + + +##### Minimal configuration +``` +vault: + auth: + azure: + role: "" +``` + +##### Configuration options +- `role` **(required)** - specifies the role used to call the Vault API. See the authentication steps below +- `resource` - optional azure resource +- `path` *(default: azure)* - specifies the backend-name used to select the login-endpoint (`auth//login`) + +To allow the access to the snapshots you should run the following commands on your vault-cluster: +``` +vault write auth//role/ \ + policies="snapshots" \ + bound_subscription_ids= \ + bound_resource_groups= +``` + +#### Google Cloud authentication + +Authentication using Google Cloud GCE or IAM authentication (see [the Vault docs](https://developer.hashicorp.com/vault/docs/auth/gcp)). + + +##### Minimal configuration +``` +vault: + auth: + gcp: + role: "" +``` + +##### Configuration options +- `role` **(required)** - specifies the role used to call the Vault API. See the authentication steps below +- `serviceAccountEmail` - activates iam authentication and s specifies the service-account to use +- `path` *(default: gcp)* - specifies the backend-name used to select the login-endpoint (`auth//login`) + +By default Google Cloud authentication uses the gce authentication type unless `serviceAccountEmail` is set. + +To allow the access to the snapshots you should run the following commands on your vault-cluster: +``` +# for iam authentication type +vault write auth//role/ \ + type="iam" \ + policies="snapshots" \ + bound_service_accounts="" + +# for gce authentication type +vault write auth//role/ \ + type="gce" \ + policies="snapshots" \ + bound_projects="" \ + bound_zones="" \ + bound_labels="" \ + bound_service_accounts="" ``` @@ -177,14 +276,39 @@ vault: ##### Configuration options - `role` **(required)** - specifies vault k8s auth role - `path` *(default: kubernetes)* - specifies the backend-name used to select the login-endpoint (`auth//login`) -- `jwtPath` *(default: /var/run/secrets/kubernetes.io/serviceaccount/token)* - specifies the path to the file with the JWT-Token for the kubernetes Service-Account +- `jwtPath` *(default: /var/run/secrets/kubernetes.io/serviceaccount/token)* - specifies the path to the file with the JWT-Token for the kubernetes service-account. You may specify the path relative to the location of the configuration file. To allow kubernetes access to the snapshots you should run the following commands on your vault-cluster: ``` - kubectl -n exec -it -- vault write auth//role/ bound_service_account_names=* bound_service_account_namespaces= policies=snapshots ttl=24h + kubectl -n exec -it -- vault write auth//role/ bound_service_account_names=* bound_service_account_namespaces= policies=snapshots ttl=24h ``` Depending on your setup you can restrict access to specific service-account-names and/or namespaces. +#### LDAP authentication +Authentication using LDAP (see [the Vault docs](https://developer.hashicorp.com/vault/docs/auth/ldap)). + +##### Minimal configuration +``` +vault: + auth: + ldap: + role: "test" +``` + +##### Configuration options +- `username` **(required)** - the username +- `password` **(required)** - the password +- `path` *(default: ldap)* - specifies the backend-name used to select the login-endpoint (`auth//login`) + +To allow access to the snapshots you should run the following commands on your vault-cluster: +``` +# allow access for a specific user +vault write auth//users/ policies=snapshot + +# allow access based on group +vault write auth//groups/ policies=snapshots +``` + #### Token authentication ##### Minimal configuration @@ -198,6 +322,32 @@ vault: - `token` **(required)** - specifies the token used to login +#### User and Password authentication +Authentication using username and password (see [the Vault docs](https://developer.hashicorp.com/vault/docs/auth/userpass)). + +##### Minimal configuration +``` +vault: + auth: + userpass: + username: "" + password: "" +``` + +##### Configuration options +- `username` **(required)** - the username +- `password` **(required)** - the password +- `path` *(default: userpass)* - specifies the backend-name used to select the login-endpoint (`auth//login`) + +To allow access to the snapshots you should run the following commands on your vault-cluster: + +``` +vault write auth//users/ \ + password= \ + policies=snapshots +``` + + ### Snapshot configuration ``` snapshots: @@ -260,7 +410,7 @@ uploaders: secret: ``` - `key` **(required)** - specifies the access key. It's recommended to use the standard `AWS_ACCESS_KEY_ID` env var, though -- `secret` **(required)** - specifies the secret It's recommended to use the standard `SECRET_ACCESS_KEY` env var, though +- `secret` **(required)** - specifies the secret It's recommended to use the standard `AWS_SECRET_ACCESS_KEY` env var, though #### Azure Storage @@ -284,3 +434,4 @@ uploaders: ## Contributors - Vault Raft Snapshot Agent was originally developed by [@Lucretius](https://github.com/Lucretius/vault_raft_snapshot_agent/) - This build contains improvements done by [@Boostport](https://github.com/Boostport/vault_raft_snapshot_agent/) +- support for additional authentication methods based on code from [@alexeiser](https://github.com/Lucretius/vault_raft_snapshot_agent/pull/25) \ No newline at end of file diff --git a/cmd/vault-raft-snapshot-agent/main.go b/cmd/vault-raft-snapshot-agent/main.go index 92341c2..629e073 100644 --- a/cmd/vault-raft-snapshot-agent/main.go +++ b/cmd/vault-raft-snapshot-agent/main.go @@ -9,8 +9,8 @@ Usage: The flags are: - -v, -version - Prints version information and exits + -v, -version + Prints version information and exits The options are: @@ -44,6 +44,12 @@ import ( var Version = "development" var Platform = "linux/amd64" +var snapshotterOptions internal.SnapshotterOptions = internal.SnapshotterOptions{ + ConfigFileName: "snapshots", + ConfigFileSearchPaths: []string{"/etc/vault.d/", "."}, + EnvPrefix: "VRSA", +} + type quietBoolFlag struct { cli.BoolFlag } @@ -99,17 +105,12 @@ Options: } func startSnapshotter(configFile cli.Path) { - config, err := internal.ReadConfig(configFile) - if err != nil { - log.Fatalf("Could not read configuration: %s\n", err) - } - - snapshotter, err := internal.CreateSnapshotter(config) + snapshotterOptions.ConfigFilePath = configFile + snapshotter, err := internal.CreateSnapshotter(snapshotterOptions) if err != nil { - log.Fatalf("Cannot instantiate snapshotter: %s\n", err) + log.Fatalf("Cannot create snapshotter: %s\n", err) } - internal.WatchConfigAndReconfigure(snapshotter) ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/go.mod b/go.mod index cc3c53c..6bd7e5d 100644 --- a/go.mod +++ b/go.mod @@ -28,11 +28,20 @@ require github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 // GCP-Uploader require ( cloud.google.com/go/storage v1.31.0 - google.golang.org/api v0.129.0 + google.golang.org/api v0.140.0 ) // Vault -require github.com/hashicorp/vault/api v1.9.2 +require ( + github.com/hashicorp/vault/api v1.10.0 + github.com/hashicorp/vault/api/auth/approle v0.5.0 + github.com/hashicorp/vault/api/auth/aws v0.5.0 + github.com/hashicorp/vault/api/auth/azure v0.5.0 + github.com/hashicorp/vault/api/auth/gcp v0.5.0 + github.com/hashicorp/vault/api/auth/kubernetes v0.5.0 + github.com/hashicorp/vault/api/auth/ldap v0.5.0 + github.com/hashicorp/vault/api/auth/userpass v0.5.0 +) // helpers require ( @@ -41,18 +50,19 @@ require ( ) // ensure up-to-date versions (because of known vulnerabilities etc.) -require golang.org/x/crypto v0.12.0 // indirect +require golang.org/x/crypto v0.13.0 // indirect // testing require github.com/stretchr/testify v1.8.4 require ( - cloud.google.com/go v0.110.2 // indirect - cloud.google.com/go/compute v1.19.3 // indirect + cloud.google.com/go v0.110.7 // indirect + cloud.google.com/go/compute v1.23.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.0 // indirect + cloud.google.com/go/iam v1.1.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect + github.com/aws/aws-sdk-go v1.45.8 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect @@ -78,18 +88,21 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.5.9 // indirect - github.com/google/s2a-go v0.1.4 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/google/uuid v1.3.1 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect - github.com/googleapis/gax-go/v2 v2.11.0 // indirect + github.com/googleapis/gax-go/v2 v2.12.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.5.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-retryablehttp v0.7.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.4 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/awsutil v0.2.3 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect - github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.5 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect @@ -98,6 +111,7 @@ require ( github.com/mattn/go-isatty v0.0.19 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect @@ -107,17 +121,18 @@ require ( github.com/subosito/gotenv v1.4.2 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/net v0.11.0 // indirect - golang.org/x/oauth2 v0.9.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/net v0.15.0 // indirect + golang.org/x/oauth2 v0.12.0 // indirect + golang.org/x/sync v0.3.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect - google.golang.org/grpc v1.56.1 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb // indirect + google.golang.org/grpc v1.58.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index a15729e..f865e6c 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,20 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA= cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw= +cloud.google.com/go v0.110.7 h1:rJyC7nWRg2jWGZ4wSJ5nY65GTdYJkg0cd/uXb+ACI6o= +cloud.google.com/go v0.110.7/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -27,12 +39,16 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute v1.19.3 h1:DcTwsFgGev/wV5+q8o2fzgcHOaac+DKGC91ZlvpsQds= cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI= +cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= +cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/iam v1.1.0 h1:67gSqaPukx7O8WLLHMa0PNs3EBGd2eE4d+psbO/CO94= cloud.google.com/go/iam v1.1.0/go.mod h1:nxdHjaKfCr7fNYx/HJMM8LgiMugmveWlkatear5gVyk= +cloud.google.com/go/iam v1.1.2 h1:gacbrBdWcoVmGLozRuStX45YKvJtzIjJdAolzUs1sm4= +cloud.google.com/go/iam v1.1.2/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -58,8 +74,13 @@ github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 h1:BWe8a+f/t+7 github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.45.8 h1:QbOMBTuRYx11fBwNSAJuztXmQf47deFz+CVYjakqmRo= +github.com/aws/aws-sdk-go v1.45.8/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc= github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 h1:OPLEkmhXf6xFPiz0bLeDArZIDx1NNS4oJyG4nv3Gct0= @@ -99,9 +120,11 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.21.5/go.mod h1:VC7JDqsqiwXukYEDjoHh9 github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ= github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -111,11 +134,13 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA= github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM= github.com/cweill/gotests v1.6.0 h1:KJx+/p4EweijYzqPb4Y/8umDCip1Cv6hEVyOx0mE9W8= @@ -130,9 +155,12 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= @@ -155,6 +183,7 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM= github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= @@ -172,6 +201,8 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -187,9 +218,11 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -203,6 +236,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -210,6 +245,7 @@ github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPg github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -222,35 +258,58 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM= github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= +github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6/go.mod h1:MpCPSPGLDILGb4JMm94/mMi3YysIqsXzGCzkEZjcjXg= +github.com/hashicorp/go-secure-stdlib/awsutil v0.2.3 h1:AAQ6Vmo/ncfrZYtbpjhO+g0Qt+iNpYtl3UWT1NLmbYY= +github.com/hashicorp/go-secure-stdlib/awsutil v0.2.3/go.mod h1:oKHSQs4ivIfZ3fbXGQOop1XuDfdSb8RIsWTGaAanSfg= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= @@ -258,14 +317,37 @@ github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9 github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2wMvfPJU= +github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= +github.com/hashicorp/go-uuid v1.0.2/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/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/hashicorp/vault/api v1.9.2 h1:YjkZLJ7K3inKgMZ0wzCU9OHqc+UqMQyXsPXnf3Cl2as= github.com/hashicorp/vault/api v1.9.2/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8= +github.com/hashicorp/vault/api v1.10.0 h1:/US7sIjWN6Imp4o/Rj1Ce2Nr5bki/AXi9vAW3p2tOJQ= +github.com/hashicorp/vault/api v1.10.0/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8= +github.com/hashicorp/vault/api/auth/approle v0.5.0 h1:a1TK6VGwYqSAfkmX4y4dJ4WBxMU5dStIZqScW4EPXR8= +github.com/hashicorp/vault/api/auth/approle v0.5.0/go.mod h1:CHOQIA1AZACfjTzHggmyfiOZ+xCSKNRFqe48FTCzH0k= +github.com/hashicorp/vault/api/auth/aws v0.5.0 h1:IKf0W3A2tXEtw9KrooslBWw72Ld63V+fUHkkSmm+2T0= +github.com/hashicorp/vault/api/auth/aws v0.5.0/go.mod h1:U2Y6Ci/kDsUkDTzUXq0OKG2/GQkEtqzQjTY1YYSQFnk= +github.com/hashicorp/vault/api/auth/azure v0.5.0 h1:Dm0/OhSw4xtya61Tq1/YpfmWtqoMC2UFwHEP0uOd1YI= +github.com/hashicorp/vault/api/auth/azure v0.5.0/go.mod h1:wKZ9qRvHg4qcCMFR8N2L++2mhL2hFBzboLzILpIZfAE= +github.com/hashicorp/vault/api/auth/gcp v0.5.0 h1:FIfgeN1Y6VOfIc0hh0LNO5gveiktXrV2EW4JQrJHTrU= +github.com/hashicorp/vault/api/auth/gcp v0.5.0/go.mod h1:ZBqARl2T6MiaR9zmSN5ytgKEkC1An9UAAZuiCqeYCB4= +github.com/hashicorp/vault/api/auth/kubernetes v0.5.0 h1:CXO0fD7M3iCGovP/UApeHhPcH4paDFKcu7AjEXi94rI= +github.com/hashicorp/vault/api/auth/kubernetes v0.5.0/go.mod h1:afrElBIO9Q4sHFVuVWgNevG4uAs1bT2AZFA9aEiI608= +github.com/hashicorp/vault/api/auth/ldap v0.5.0 h1:qY3XiXQhCTvqQqLcM3ygP0QWLxag2W++pmGmuc0b6DU= +github.com/hashicorp/vault/api/auth/ldap v0.5.0/go.mod h1:z4rd9G4pyXBu+EPVvFBXEMhqAuCN5RivclUQSqApDpE= +github.com/hashicorp/vault/api/auth/userpass v0.5.0 h1:u//BC15YJviWSpeTlxsmt96FPULsCF7dYhPHg5oOAzo= +github.com/hashicorp/vault/api/auth/userpass v0.5.0/go.mod h1:TNxl3X6ZaeILi1rfxP/mhGnWuiCiP7SNv2qeZ5aSAMQ= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +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= @@ -275,6 +357,7 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -288,9 +371,17 @@ github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNa github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/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-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= @@ -314,6 +405,7 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -321,6 +413,7 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= @@ -340,6 +433,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/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -359,6 +453,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 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.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -366,6 +461,7 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= @@ -383,8 +479,11 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -408,6 +507,7 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -418,6 +518,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -452,12 +553,21 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -467,8 +577,16 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs= golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw= +golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= +golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -479,12 +597,14 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -493,9 +613,11 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -507,6 +629,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-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -514,33 +637,60 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/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-20210616094352-59db8d763f22/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-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/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-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-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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -591,6 +741,11 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +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.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= @@ -619,8 +774,19 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/api v0.129.0 h1:2XbdjjNfFPXQyufzQVwPf1RRnHH8Den2pfNE2jw7L8w= google.golang.org/api v0.129.0/go.mod h1:dFjiXlanKwWE3612X97llhsoI36FAoIiRj3aTl5b/zE= +google.golang.org/api v0.140.0 h1:CaXNdYOH5oQQI7l6iKTHHiMTdxZca4/02hRg2U8c2hM= +google.golang.org/api v0.140.0/go.mod h1:aGbCiFgtwb2P6badchFbSBUurV6oR5d50Af4iNJtDdI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -629,6 +795,8 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -665,13 +833,39 @@ google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao= google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= +google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb h1:XFBgcDwm7irdHTbz4Zk2h7Mh+eis4nfJEFQFYzJzuIA= +google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM= google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb h1:lK0oleSc7IQsUxO3U5TjL9DWlsxpEBemh+zpB7IqhWI= +google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb h1:Isk1sSH7bovx8Rti2wZK0UZF6oraBDK74uoyLEEVFN0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -690,9 +884,20 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ= google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.58.0 h1:32JY8YpPMSR45K+c3o6b8VL73V+rR8k+DeMIr4vRH8o= +google.golang.org/grpc v1.58.0/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -705,6 +910,7 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/app/vault_raft_snapshot_agent/config.go b/internal/app/vault_raft_snapshot_agent/config.go deleted file mode 100644 index 81a740b..0000000 --- a/internal/app/vault_raft_snapshot_agent/config.go +++ /dev/null @@ -1,76 +0,0 @@ -package vault_raft_snapshot_agent - -import ( - "fmt" - "log" -) - -var parser rattlesnake = newRattlesnake("snapshot", "VRSA", "/etc/vault.d/", ".") - -// ReadConfig reads the configuration file -func ReadConfig(file string) (config SnapshotterConfig, err error) { - config = SnapshotterConfig{} - - err = parser.BindAllEnv( - map[string]string{ - "vault.url": "VAULT_ADDR", - "uploaders.aws.credentials.key": "AWS_ACCESS_KEY_ID", - "uploaders.aws.credentials.secret": "SECRET_ACCESS_KEY", - }, - ) - if err != nil { - return config, fmt.Errorf("could not bind environment-variables: %s", err) - } - - if file != "" { - if err := parser.SetConfigFile(file); err != nil { - return config, err - } - } - - if err := parser.ReadInConfig(); err != nil { - if parser.IsConfigurationNotFoundError(err) { - if file != "" { - return config, err - } else { - log.Printf("Could not find any configuration file, will create configuration based solely on environment...") - } - } else { - return config, err - } - } - - if usedConfigFile := parser.ConfigFileUsed(); usedConfigFile != "" { - log.Printf("Using configuration from %s...\n", usedConfigFile) - } - - if err := parser.Unmarshal(&config); err != nil { - return config, fmt.Errorf("could not unmarshal configuration: %s", err) - } - - if !config.Uploaders.HasUploaders() { - return config, fmt.Errorf("no uploaders configured!") - } - - return config, nil -} - -func WatchConfigAndReconfigure(snapshotter *Snapshotter) <-chan error { - ch := make(chan error, 1) - - parser.OnConfigChange(func() { - config := SnapshotterConfig{} - - if err := parser.Unmarshal(&config); err != nil { - log.Printf("Ignoring configuration change as configuration in %s is invalid: %v\n", parser.ConfigFileUsed(), err) - ch <- err - } else if err := snapshotter.Reconfigure(config); err != nil { - log.Printf("Could not reconfigure snapshotter from %s: %v\n", parser.ConfigFileUsed(), err) - ch <- err - } else { - ch <- nil - } - }) - - return ch -} diff --git a/internal/app/vault_raft_snapshot_agent/config/config.go b/internal/app/vault_raft_snapshot_agent/config/config.go new file mode 100644 index 0000000..b160d5e --- /dev/null +++ b/internal/app/vault_raft_snapshot_agent/config/config.go @@ -0,0 +1,75 @@ +package config + +import ( + "fmt" + "log" +) + +type Parser[T Configuration] struct { + delegate rattlesnake +} + +type Configuration interface { + HasUploaders() bool +} + +func NewParser[T Configuration](envPrefix string, configFilename string, configSearchPaths ...string) Parser[T] { + return Parser[T]{newRattlesnake(envPrefix, configFilename, configSearchPaths...)} +} + +// ReadConfig reads the configuration file +func (p Parser[T]) ReadConfig(config T, file string) error { + err := p.delegate.BindAllEnv( + map[string]string{ + "vault.url": "VAULT_ADDR", + "uploaders.aws.credentials.key": "AWS_ACCESS_KEY_ID", + "uploaders.aws.credentials.secret": "AWS_SECRET_ACCESS_KEY", + }, + ) + if err != nil { + return fmt.Errorf("could not bind environment-variables: %s", err) + } + + if file != "" { + if err := p.delegate.SetConfigFile(file); err != nil { + return err + } + } + + if err := p.delegate.ReadInConfig(); err != nil { + if p.delegate.IsConfigurationNotFoundError(err) { + log.Printf("Could not find any configuration file, will create configuration based solely on environment...") + } else { + return err + } + } + + if usedConfigFile := p.delegate.ConfigFileUsed(); usedConfigFile != "" { + log.Printf("Using configuration from %s...\n", usedConfigFile) + } + + if err := p.delegate.Unmarshal(config); err != nil { + return fmt.Errorf("could not unmarshal configuration: %s", err) + } + + if !config.HasUploaders() { + return fmt.Errorf("no uploaders configured!") + } + + return nil +} + +func (p Parser[T]) OnConfigChange(config T, handler func(config T) error) <-chan error { + ch := make(chan error, 1) + + p.delegate.OnConfigChange(func() { + if err := p.delegate.Unmarshal(config); err != nil { + log.Printf("Ignoring configuration change as configuration in %s is invalid: %v\n", p.delegate.ConfigFileUsed(), err) + ch <- err + } else { + ch <- handler(config) + } + }) + + return ch +} diff --git a/internal/app/vault_raft_snapshot_agent/config/config_test.go b/internal/app/vault_raft_snapshot_agent/config/config_test.go new file mode 100644 index 0000000..777e9f1 --- /dev/null +++ b/internal/app/vault_raft_snapshot_agent/config/config_test.go @@ -0,0 +1,137 @@ +package config + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/test" +) + +type configDataStub struct { + hasUploaders bool + Vault struct { + Url string `validate:"required"` + Test string + } + Uploaders struct { + AWS struct { + Credentials struct { + Key string + Secret string + } + } + } +} + +func (stub configDataStub) HasUploaders() bool { + return stub.hasUploaders +} + +func TestReadConfigBindsEnvVariables(t *testing.T) { + parser := NewParser[*configDataStub]("TEST", "") + + t.Setenv("VAULT_ADDR", "http://from.env:8200") + t.Setenv("AWS_ACCESS_KEY_ID", "env-key") + t.Setenv("AWS_SECRET_ACCESS_KEY", "env-secret") + t.Setenv("TEST_VAULT_TEST", "test") + + + data := configDataStub{hasUploaders: true} + err := parser.ReadConfig(&data, "") + assert.NoError(t, err, "ReadConfig failed unexpectedly") + + assert.Equal(t, os.Getenv("VAULT_ADDR"), data.Vault.Url, "ReadConfig did not bind env-var VAULT_ADDR") + assert.Equal(t, os.Getenv("AWS_ACCESS_KEY_ID"), data.Uploaders.AWS.Credentials.Key, "ReadConfig did not bind env-var AWS_ACCESS_KEY_ID") + assert.Equal(t, os.Getenv("AWS_SECRET_ACCESS_KEY"), data.Uploaders.AWS.Credentials.Secret, "ReadConfig did not bind env-var SECRET_ACCESS_KEY") + assert.Equal(t, os.Getenv("TEST_VAULT_TEST"), data.Vault.Test, "ReadConfig did not bind env-var TEST_VAULT_TEST") +} + +func TestFailsOnMissingConfigFile(t *testing.T) { + parser := NewParser[*configDataStub]("TEST", "") + + t.Setenv("VAULT_ADDR", "http://from.env:8200") + + data := configDataStub{hasUploaders: true} + err := parser.ReadConfig(&data, "./missing.yaml") + assert.Error(t, err, "ReadConfig should fail for missing config-file") +} + +func TestFailsForInvalidConfiguration(t *testing.T) { + parser := NewParser[*configDataStub]("TEST", "") + + data := configDataStub{hasUploaders: true} + err := parser.ReadConfig(&data, "") + assert.Error(t, err, "ReadConfig should fail for invalid configuration") +} + +func TestFailsOnMissingUploaders(t *testing.T) { + parser := NewParser[*configDataStub]("TEST", "") + + t.Setenv("VAULT_ADDR", "http://from.env:8200") + + data := configDataStub{hasUploaders: false} + err := parser.ReadConfig(&data, "") + assert.Error(t, err, "ReadConfig should fail for missing uploaders") +} + +func TestOnConfigChangePassesConfigToHandler(t *testing.T) { + parser := NewParser[*configDataStub]("TEST", "") + + configFile := fmt.Sprintf("%s/config.json", t.TempDir()) + config := configDataStub{hasUploaders: true} + + err := test.WriteFile(t, configFile, "{\"vault\":{\"url\": \"test\"}}") + assert.NoError(t, err, "writing config file failed unexpectedly") + + err = parser.ReadConfig(&config, configFile) + + assert.NoError(t, err, "ReadConfig failed unexpectedly") + assert.Equal(t, "test", config.Vault.Url) + + configCh := make(chan configDataStub, 1) + errCh := parser.OnConfigChange(&configDataStub{hasUploaders: true}, func(c *configDataStub) error { + configCh <- *c + return nil + }) + + err = test.WriteFile(t, configFile, "{\"vault\":{\"url\": \"new\"}}") + assert.NoError(t, err, "writing config file failed unexpectedly") + + assert.NoError(t, <-errCh, "OnConfigChange failed unexpectedly") + + newConfig := <-configCh + assert.Equal(t, "new", newConfig.Vault.Url) + + parser.delegate.OnConfigChange(func() { /* prevent error messages on cleanup */ }) +} + +func TestOnConfigChangeIgnoresInvalidConfiguration(t *testing.T) { + parser := NewParser[*configDataStub]("TEST", "") + + configFile := fmt.Sprintf("%s/config.json", t.TempDir()) + config := configDataStub{hasUploaders: true} + + err := test.WriteFile(t, configFile, "{\"vault\":{\"url\": \"test\"}}") + assert.NoError(t, err, "writing config file failed unexpectedly") + + err = parser.ReadConfig(&config, configFile) + assert.NoError(t, err, "ReadConfig failed unexpectedly") + assert.Equal(t, "test", config.Vault.Url) + + newConfig := configDataStub{hasUploaders: true} + errCh := parser.OnConfigChange(&newConfig, func(c *configDataStub) error { + c.Vault.Url = "new" + return nil + }) + + err = test.WriteFile(t, configFile, "{\"vault\":{}}") + assert.NoError(t, err, "writing config file failed unexpectedly") + + assert.Error(t, <-errCh, "OnConfigChange should fail for invalid configuration") + assert.Equal(t, "", newConfig.Vault.Url) + + parser.delegate.OnConfigChange(func() { /* prevent error messages on cleanup */ }) +} diff --git a/internal/app/vault_raft_snapshot_agent/rattlesnake.go b/internal/app/vault_raft_snapshot_agent/config/rattlesnake.go similarity index 78% rename from internal/app/vault_raft_snapshot_agent/rattlesnake.go rename to internal/app/vault_raft_snapshot_agent/config/rattlesnake.go index b6e9cc2..61b097c 100644 --- a/internal/app/vault_raft_snapshot_agent/rattlesnake.go +++ b/internal/app/vault_raft_snapshot_agent/config/rattlesnake.go @@ -1,10 +1,9 @@ -package vault_raft_snapshot_agent +package config import ( "fmt" - "log" - "os" "path/filepath" + "reflect" "strings" "github.com/creasty/defaults" @@ -15,15 +14,17 @@ import ( "github.com/spf13/viper" ) +type Path string + // a rattlesnake is a viper adapted to our needs ;-) type rattlesnake struct { v *viper.Viper } -func newRattlesnake(configName string, envPrefix string, configPaths ...string) rattlesnake { +func newRattlesnake(envPrefix string, configName string, configPaths ...string) rattlesnake { v := viper.New() - v.SetConfigName(configName) v.SetEnvPrefix(envPrefix) + v.SetConfigName(configName) for _, path := range configPaths { v.AddConfigPath(path) } @@ -66,28 +67,18 @@ func (r rattlesnake) ConfigFileUsed() string { return r.v.ConfigFileUsed() } -func (r rattlesnake) Unmarshal(config interface{}, opts ...viper.DecoderConfigOption) error { +func (r rattlesnake) Unmarshal(config interface{}) error { if err := bindStruct(r.v, config); err != nil { return fmt.Errorf("could not bind env vars for configuration: %s", err) } - wd, err := os.Getwd() - if err != nil { - return fmt.Errorf("could not determine current working directory: %s", err) - } + decodeHook := mapstructure.ComposeDecodeHookFunc( + mapstructure.StringToTimeDurationHookFunc(), + mapstructure.StringToSliceHookFunc(","), + newPathResolverHook(filepath.Dir(r.ConfigFileUsed())), + ) - configDir := filepath.Dir(r.ConfigFileUsed()) - if err := os.Chdir(configDir); err != nil { - return fmt.Errorf("could not switch working-directory to %s to parse configuration: %s", configDir, err) - } - - defer func() { - if err := os.Chdir(wd); err != nil { - log.Fatalf("Could not switch back to working directory %s: %s\n", wd, err) - } - }() - - if err := r.v.Unmarshal(config, opts...); err != nil { + if err := r.v.Unmarshal(config, viper.DecodeHook(decodeHook)); err != nil { return err } @@ -115,6 +106,25 @@ func (r rattlesnake) IsConfigurationNotFoundError(err error) bool { return notfound } +func newPathResolverHook(workdir string) mapstructure.DecodeHookFuncType { + return func(dataType reflect.Type, targetType reflect.Type, data interface{}) (interface{}, error) { + if dataType.Kind() != reflect.String { + return data, nil + } + + if targetType != reflect.TypeOf(Path("")) { + return data, nil + } + + path := data.(string) + if !filepath.IsAbs(path) { + path = filepath.Join(workdir, path) + } + + return Path(filepath.Clean(path)), nil + } +} + // implements automatic unmarshalling from environment variables // see https://github.com/spf13/viper/pull/1429 // can be removed if that pr is merged diff --git a/internal/app/vault_raft_snapshot_agent/config/rattlesnake_test.go b/internal/app/vault_raft_snapshot_agent/config/rattlesnake_test.go new file mode 100644 index 0000000..8ea5194 --- /dev/null +++ b/internal/app/vault_raft_snapshot_agent/config/rattlesnake_test.go @@ -0,0 +1,79 @@ +package config + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/test" +) + +type rattlesnakeConfigStub struct { + Path Path `default:"/test/file"` + Url string `validate:"omitempty,http_url"` +} + +func TestUnmarshalResolvesRelativePaths(t *testing.T) { + rattlesnake := newRattlesnake("test", "TEST") + + wd, err := os.Getwd() + assert.NoError(t, err, "Getwd failed unexpectedly") + + err = rattlesnake.SetConfigFile(fmt.Sprintf("%s/config.yml", wd)) + assert.NoError(t, err, "SetConfigFile failed unexpectedly") + + t.Setenv("TEST_PATH", "./file.ext") + config := rattlesnakeConfigStub{} + err = rattlesnake.Unmarshal(&config) + + assert.NoError(t, err, "Unmarshal failed unexpectedly") + assert.Equal(t, Path(filepath.Clean(fmt.Sprintf("%s/file.ext", wd))), config.Path) +} + +func TestUnmarshalSetsDefaultValues(t *testing.T) { + rattlesnake := newRattlesnake("test", "TEST") + + config := rattlesnakeConfigStub{} + err := rattlesnake.Unmarshal(&config) + + assert.NoError(t, err, "Unmarshal failed unexpectedly") + assert.Equal(t, Path("/test/file"), config.Path) +} + +func TestUnmarshalValidatesValues(t *testing.T) { + rattlesnake := newRattlesnake("test", "TEST") + + t.Setenv("TEST_URL", "not_an_url") + config := rattlesnakeConfigStub{} + err := rattlesnake.Unmarshal(&config) + + assert.Error(t, err, "Unmarshal should fail on validation error") + assert.Equal(t, "not_an_url", config.Url) +} + +func TestOnConfigChangeRunsHandler(t *testing.T) { + rattlesnake := newRattlesnake("test", "TEST") + configFile := fmt.Sprintf("%s/config.yml", t.TempDir()) + + err := rattlesnake.SetConfigFile(configFile) + assert.NoError(t, err, "SetConfigFile failed unexpectedly") + + err = test.WriteFile(t, configFile, "{\"url\": \"http://example.com\"}") + assert.NoError(t, err, "writing config file failed unexpectedly") + + err = rattlesnake.Unmarshal(&rattlesnakeConfigStub{}) + assert.NoError(t, err, "Unmarshal failed unexpectedly") + + changed := make(chan bool, 1) + rattlesnake.OnConfigChange(func() { + changed <- true + }) + + err = test.WriteFile(t, configFile, "{\"url\": \"http://new.com\"}") + assert.NoError(t, err, "writing config file failed unexpectedly") + + assert.True(t, <-changed) +} diff --git a/internal/app/vault_raft_snapshot_agent/config_test.go b/internal/app/vault_raft_snapshot_agent/config_test.go deleted file mode 100644 index 8424e29..0000000 --- a/internal/app/vault_raft_snapshot_agent/config_test.go +++ /dev/null @@ -1,272 +0,0 @@ -package vault_raft_snapshot_agent - -import ( - "errors" - "fmt" - "log" - "os" - "path/filepath" - "runtime" - "testing" - "time" - - "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/upload" - "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/vault" - "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/vault/auth" - "github.com/stretchr/testify/assert" -) - -// allow overiding "default" kubernetes-jwt-path so that tests on ci do not fail -func defaultJwtPath(def string) string { - jwtPath := os.Getenv("VRSA_VAULT_AUTH_KUBERNETES_JWTPATH") - if jwtPath != "" { - return jwtPath - } - - if def != "" { - return def - } - - return "/var/run/secrets/kubernetes.io/serviceaccount/token" -} - -func TestReadEmptyConfig(t *testing.T) { - file := "../../../testdata/empty.yaml" - _, err := ReadConfig(file) - - assert.Error(t, err, `ReadConfig(%s) should return error for empty file`, file) -} - -func TestReadConfigWithInvalidAddr(t *testing.T) { - file := "../../../testdata/invalid-url.yaml" - _, err := ReadConfig(file) - - assert.Error(t, err, `ReadConfig(%s) should return error for config with invalid url`, file) -} - -func TestReadConfigWithoutUploaders(t *testing.T) { - file := "../../../testdata/no-uploaders.yaml" - _, err := ReadConfig(file) - - assert.Error(t, err, `ReadConfig(%s) should return error for config without uploaders`, file) -} - -func TestReadConfigWithInvalidUploader(t *testing.T) { - file := "../../../testdata/invalid-uploader.yaml" - _, err := ReadConfig(file) - - assert.Error(t, err, `ReadConfig(%s) should return error for config with invalid uploader`, file) -} - -func TestReadConfigWithInvalidLocalUploadPath(t *testing.T) { - file := "../../../testdata/invalid-local-upload-path.yaml" - _, err := ReadConfig(file) - - assert.Error(t, err, `ReadConfig(%s) should return error for config with invalid local upload-path`, file) -} - -func TestReadConfigWithInvalidAuth(t *testing.T) { - file := "../../../testdata/invalid-auth.yaml" - _, err := ReadConfig(file) - - assert.Error(t, err, `ReadConfig(%s) should return error for config with invalid auth`, file) -} - -func TestReadCompleteConfig(t *testing.T) { - expectedConfig := SnapshotterConfig{ - Vault: vault.VaultClientConfig{ - Url: "https://example.com:8200", - Insecure: true, - Timeout: 5 * time.Minute, - Auth: auth.AuthConfig{ - AppRole: auth.AppRoleAuthConfig{ - Path: "approle", - Empty: true, - }, - Kubernetes: auth.KubernetesAuthConfig{ - Role: "test-role", - Path: "test-auth", - JWTPath: defaultJwtPath("./jwt"), - }, - }, - }, - Snapshots: SnapshotConfig{ - Frequency: time.Hour * 2, - Retain: 10, - Timeout: time.Minute * 2, - NamePrefix: "test-", - NameSuffix: ".test", - TimestampFormat: "2006-01-02", - }, - Uploaders: upload.UploadersConfig{ - AWS: upload.AWSConfig{ - Endpoint: "test-endpoint", - Region: "test-region", - Bucket: "test-bucket", - KeyPrefix: "test-prefix", - UseServerSideEncryption: true, - ForcePathStyle: true, - Credentials: upload.AWSCredentialsConfig{ - Key: "test-key", - Secret: "test-secret", - }, - }, - Azure: upload.AzureConfig{ - AccountName: "test-account", - AccountKey: "test-key", - ContainerName: "test-container", - CloudDomain: "blob.core.chinacloudapi.cn", - }, - GCP: upload.GCPConfig{ - Bucket: "test-bucket", - }, - Local: upload.LocalConfig{ - Path: ".", - }, - }, - } - - file := "../../../testdata/complete.yaml" - config, err := ReadConfig(file) - - assert.NoError(t, err, "ReadConfig(%s) failed unexpectedly", file) - assert.Equal(t, expectedConfig, config) -} - -func TestReadConfigSetsDefaultValues(t *testing.T) { - expectedConfig := SnapshotterConfig{ - Vault: vault.VaultClientConfig{ - Url: "http://127.0.0.1:8200", - Insecure: false, - Timeout: time.Minute, - Auth: auth.AuthConfig{ - AppRole: auth.AppRoleAuthConfig{ - Path: "approle", - Empty: true, - }, - Kubernetes: auth.KubernetesAuthConfig{ - Role: "test-role", - Path: "kubernetes", - JWTPath: defaultJwtPath(""), - }, - }, - }, - Snapshots: SnapshotConfig{ - Frequency: time.Hour, - Retain: 0, - Timeout: time.Minute, - NamePrefix: "raft-snapshot-", - NameSuffix: ".snap", - TimestampFormat: "2006-01-02T15-04-05Z-0700", - }, - Uploaders: upload.UploadersConfig{ - AWS: upload.AWSConfig{ - Credentials: upload.AWSCredentialsConfig{Empty: true}, - Empty: true, - }, - Azure: upload.AzureConfig{ - CloudDomain: "blob.core.windows.net", - Empty: true, - }, - GCP: upload.GCPConfig{Empty: true}, - Local: upload.LocalConfig{ - Path: ".", - }, - }, - } - - file := "../../../testdata/defaults.yaml" - config, err := ReadConfig(file) - - assert.NoError(t, err, "ReadConfig(%s) failed unexpectedly", file) - assert.Equal(t, expectedConfig, config) -} - -func TestReadConfigBindsEnvVariables(t *testing.T) { - t.Setenv("VAULT_ADDR", "http://from.env:8200") - t.Setenv("AWS_ACCESS_KEY_ID", "env-key") - t.Setenv("SECRET_ACCESS_KEY", "env-secret") - t.Setenv("VRSA_VAULT_AUTH_KUBERNETES_ROLE", "test") - t.Setenv("VRSA_VAULT_AUTH_KUBERNETES_JWTPATH", "./jwt") - - file := "../../../testdata/envvars.yaml" - config, err := ReadConfig(file) - assert.NoError(t, err, "ReadConfig(%s) failed unexpectedly", file) - - assert.Equal(t, os.Getenv("VAULT_ADDR"), config.Vault.Url, "ReadConfig did not bind env-var VAULT_ADDR") - assert.Equal(t, os.Getenv("AWS_ACCESS_KEY_ID"), config.Uploaders.AWS.Credentials.Key, "ReadConfig did not bind env-var AWS_ACCESS_KEY_ID") - assert.Equal(t, os.Getenv("SECRET_ACCESS_KEY"), config.Uploaders.AWS.Credentials.Secret, "ReadConfig did not bind env-var SECRET_ACCESS_KEY") - assert.Equal(t, os.Getenv("VRSA_VAULT_AUTH_KUBERNETES_JWTPATH"), config.Vault.Auth.Kubernetes.JWTPath, "ReadConfig did not bind env-var VRSA_VAULT_AUTH_KUBERNETES_JWTPATH") - -} - -func TestWatchAndReConfigure(t *testing.T) { - tempDir := t.TempDir() - file1 := "../../../testdata/watch-and-reconfigure1.yaml" - file2 := "../../../testdata/watch-and-reconfigure2.yaml" - configFile := fmt.Sprintf("%s/config.yaml", tempDir) - - err := copyFile(t, "../../../testdata/jwt", fmt.Sprintf("%s/jwt", tempDir)) - assert.NoError(t, err, "could not copy file jwt-file") - - err = copyFile(t, file1, configFile) - assert.NoError(t, err, "could not copy file %s", file1) - - config, err := ReadConfig(configFile) - assert.NoError(t, err, "could not read config-file %s", file1) - - snapshotter, err := CreateSnapshotter(config) - assert.NoError(t, err, "could not create snapshotter") - assert.Equal(t, 30*time.Second, snapshotter.config.Frequency) - - reconfigured := WatchConfigAndReconfigure(snapshotter) - - errs := make(chan error, 1) - go func() { - errs <- copyFile(t, file2, configFile) - }() - - assert.NoError(t, <-errs, "could not copy file %s", file2) - assert.NoError(t, <-reconfigured) - assert.Equal(t, time.Minute, snapshotter.config.Frequency) - - parser.OnConfigChange(func() { /* prevent error messages on cleanup */ }) -} - -func copyFile(t *testing.T, source string, dest string) error { - t.Helper() - - in, err := os.ReadFile(source) - if err != nil { - return err - } - - if runtime.GOOS != "windows" { - tmpFile := fmt.Sprintf("%s.tmp", dest) - if err := os.WriteFile(tmpFile, in, 0644); err != nil { - return err - } - - return os.Rename(tmpFile, dest) - } else { - return os.WriteFile(dest, in, 0644) - } -} - -func init() { - jwtPath := defaultJwtPath("") - if err := os.MkdirAll(filepath.Dir(jwtPath), 0777); err != nil && !errors.Is(err, os.ErrExist) { - log.Fatalf("could not create directorys for jwt-file %s: %v", jwtPath, err) - } - - file, err := os.OpenFile(defaultJwtPath(""), os.O_RDWR|os.O_CREATE, 0666) - if err != nil { - log.Fatalf("could not create jwt-file %s: %v", jwtPath, err) - } - - file.Close() - - if err != nil { - log.Fatalf("could not read jwt-file %s: %v", jwtPath, err) - } -} diff --git a/internal/app/vault_raft_snapshot_agent/snapshotter.go b/internal/app/vault_raft_snapshot_agent/snapshotter.go index 90d2c37..173571a 100644 --- a/internal/app/vault_raft_snapshot_agent/snapshotter.go +++ b/internal/app/vault_raft_snapshot_agent/snapshotter.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/config" "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/upload" "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/vault" "go.uber.org/multierr" @@ -20,6 +21,10 @@ type SnapshotterConfig struct { Uploaders upload.UploadersConfig } +func (c SnapshotterConfig) HasUploaders() bool { + return !(c.Uploaders.AWS.Empty && c.Uploaders.Azure.Empty && c.Uploaders.GCP.Empty && c.Uploaders.Local.Empty) +} + type SnapshotConfig struct { Frequency time.Duration `default:"1h"` Retain int @@ -29,24 +34,62 @@ type SnapshotConfig struct { TimestampFormat string `default:"2006-01-02T15-04-05Z-0700"` } +type SnapshotterOptions struct { + ConfigFileName string + ConfigFileSearchPaths []string + ConfigFilePath string + EnvPrefix string +} + type Snapshotter struct { lock sync.Mutex - client *vault.VaultClient + client snapshotterVaultAPI uploaders []upload.Uploader config SnapshotConfig lastSnapshot time.Time snapshotTimer *time.Timer } -func CreateSnapshotter(config SnapshotterConfig) (*Snapshotter, error) { +type snapshotterVaultAPI interface { + TakeSnapshot(ctx context.Context, writer io.Writer) error +} + +func CreateSnapshotter(options SnapshotterOptions) (*Snapshotter, error) { + c := SnapshotterConfig{} + parser := config.NewParser[*SnapshotterConfig](options.ConfigFileName, options.EnvPrefix, options.ConfigFileSearchPaths...) + + if err := parser.ReadConfig(&c, options.ConfigFilePath); err != nil { + return nil, err + } + + snapshotter, err := createSnapshotter(c) + if err != nil { + return nil, err + } + + parser.OnConfigChange( + &SnapshotterConfig{}, + func(config *SnapshotterConfig) error { + if err := snapshotter.reconfigure(*config); err != nil { + log.Printf("could not reconfigure snapshotter: %s\n", err) + return err + } + return nil + }, + ) + + return snapshotter, nil +} + +func createSnapshotter(config SnapshotterConfig) (*Snapshotter, error) { snapshotter := &Snapshotter{} - err := snapshotter.Reconfigure(config) + err := snapshotter.reconfigure(config) return snapshotter, err } -func (s *Snapshotter) Reconfigure(config SnapshotterConfig) error { - client, err := vault.CreateClient(config.Vault) +func (s *Snapshotter) reconfigure(config SnapshotterConfig) error { + client, err := vault.CreateVaultClient(config.Vault) if err != nil { return err } @@ -60,7 +103,7 @@ func (s *Snapshotter) Reconfigure(config SnapshotterConfig) error { return nil } -func (s *Snapshotter) Configure(config SnapshotConfig, client *vault.VaultClient, uploaders []upload.Uploader) { +func (s *Snapshotter) Configure(config SnapshotConfig, client snapshotterVaultAPI, uploaders []upload.Uploader) { s.lock.Lock() defer s.lock.Unlock() diff --git a/internal/app/vault_raft_snapshot_agent/snapshotter_config_test.go b/internal/app/vault_raft_snapshot_agent/snapshotter_config_test.go new file mode 100644 index 0000000..2c00e87 --- /dev/null +++ b/internal/app/vault_raft_snapshot_agent/snapshotter_config_test.go @@ -0,0 +1,228 @@ +package vault_raft_snapshot_agent + +import ( + "errors" + "log" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/config" + "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/upload" + "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/vault" + "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/vault/auth" + "github.com/stretchr/testify/assert" +) + +// allow overiding "default" kubernetes-jwt-path so that tests on ci do not fail +func defaultJwtPath(def string) string { + jwtPath := os.Getenv("VRSA_VAULT_AUTH_KUBERNETES_JWTPATH") + if jwtPath != "" { + return jwtPath + } + + if def != "" { + return def + } + + return "/var/run/secrets/kubernetes.io/serviceaccount/token" +} + +func relativeTo(configFile string, file string) config.Path { + if !filepath.IsAbs(file) && !strings.HasPrefix(file, "/") { + file = filepath.Join(filepath.Dir(configFile), file) + } + + if !filepath.IsAbs(file) && !strings.HasPrefix(file, "/") { + file, _ = filepath.Abs(file) + file = filepath.Clean(file) + } + + return config.Path(file) +} + +func TestReadCompleteConfig(t *testing.T) { + configFile := "../../../testdata/complete.yaml" + + expectedConfig := SnapshotterConfig{ + Vault: vault.VaultClientConfig{ + Url: "https://example.com:8200", + Insecure: true, + Timeout: 5 * time.Minute, + Auth: auth.AuthConfig{ + AppRole: auth.AppRoleAuthConfig{ + Path: "test-approle-path", + RoleId: "test-approle", + SecretId: "test-approle-secret", + }, + AWS: auth.AWSAuthConfig{ + Path: "test-aws-path", + Role: "test-aws-role", + Region: "test-region", + EC2Nonce: "test-nonce", + EC2SignatureType: auth.AWS_EC2_RSA2048, + }, + Azure: auth.AzureAuthConfig{ + Path: "test-azure-path", + Role: "test-azure-role", + Resource: "test-resource", + }, + GCP: auth.GCPAuthConfig{ + Path: "test-gcp-path", + Role: "test-gcp-role", + ServiceAccountEmail: "test@example.com", + }, + Kubernetes: auth.KubernetesAuthConfig{ + Role: "test-kubernetes-role", + Path: "test-kubernetes-path", + JWTPath: relativeTo(configFile, defaultJwtPath("./jwt")), + }, + LDAP: auth.LDAPAuthConfig{ + Path: "test-ldap-path", + Username: "test-ldap-user", + Password: "test-ldap-pass", + }, + Token: "test-token", + UserPass: auth.UserPassAuthConfig{ + Path: "test-userpass-path", + Username: "test-user", + Password: "test-pass", + }, + }, + }, + Snapshots: SnapshotConfig{ + Frequency: time.Hour * 2, + Retain: 10, + Timeout: time.Minute * 2, + NamePrefix: "test-", + NameSuffix: ".test", + TimestampFormat: "2006-01-02", + }, + Uploaders: upload.UploadersConfig{ + AWS: upload.AWSConfig{ + Endpoint: "test-endpoint", + Region: "test-region", + Bucket: "test-bucket", + KeyPrefix: "test-prefix", + UseServerSideEncryption: true, + ForcePathStyle: true, + Credentials: upload.AWSCredentialsConfig{ + Key: "test-key", + Secret: "test-secret", + }, + }, + Azure: upload.AzureConfig{ + AccountName: "test-account", + AccountKey: "test-key", + ContainerName: "test-container", + CloudDomain: "blob.core.chinacloudapi.cn", + }, + GCP: upload.GCPConfig{ + Bucket: "test-bucket", + }, + Local: upload.LocalConfig{ + Path: ".", + }, + }, + } + + data := SnapshotterConfig{} + parser := config.NewParser[*SnapshotterConfig]("VRSA", "") + err := parser.ReadConfig(&data, configFile) + + assert.NoError(t, err, "ReadConfig(%s) failed unexpectedly", configFile) + assert.Equal(t, expectedConfig, data) +} + +func TestReadConfigSetsDefaultValues(t *testing.T) { + configFile := "../../../testdata/defaults.yaml" + + expectedConfig := SnapshotterConfig{ + Vault: vault.VaultClientConfig{ + Url: "http://127.0.0.1:8200", + Insecure: false, + Timeout: time.Minute, + Auth: auth.AuthConfig{ + AppRole: auth.AppRoleAuthConfig{ + Path: "approle", + Empty: true, + }, + AWS: auth.AWSAuthConfig{ + Path: "aws", + EC2SignatureType: auth.AWS_EC2_PKCS7, + Empty: true, + }, + Azure: auth.AzureAuthConfig{ + Path: "azure", + Empty: true, + }, + GCP: auth.GCPAuthConfig{ + Path: "gcp", + Empty: true, + }, + Kubernetes: auth.KubernetesAuthConfig{ + Role: "test-role", + Path: "kubernetes", + JWTPath: relativeTo(configFile, defaultJwtPath("")), + }, + LDAP: auth.LDAPAuthConfig{ + Path: "ldap", + Empty: true, + }, + UserPass: auth.UserPassAuthConfig{ + Path: "userpass", + Empty: true, + }, + }, + }, + Snapshots: SnapshotConfig{ + Frequency: time.Hour, + Retain: 0, + Timeout: time.Minute, + NamePrefix: "raft-snapshot-", + NameSuffix: ".snap", + TimestampFormat: "2006-01-02T15-04-05Z-0700", + }, + Uploaders: upload.UploadersConfig{ + AWS: upload.AWSConfig{ + Credentials: upload.AWSCredentialsConfig{Empty: true}, + Empty: true, + }, + Azure: upload.AzureConfig{ + CloudDomain: "blob.core.windows.net", + Empty: true, + }, + GCP: upload.GCPConfig{Empty: true}, + Local: upload.LocalConfig{ + Path: ".", + }, + }, + } + + data := SnapshotterConfig{} + parser := config.NewParser[*SnapshotterConfig]("VRSA", "") + err := parser.ReadConfig(&data, configFile) + + assert.NoError(t, err, "ReadConfig(%s) failed unexpectedly", configFile) + assert.Equal(t, expectedConfig, data) +} + +func init() { + jwtPath := defaultJwtPath("") + if err := os.MkdirAll(filepath.Dir(jwtPath), 0777); err != nil && !errors.Is(err, os.ErrExist) { + log.Fatalf("could not create directorys for jwt-file %s: %v", jwtPath, err) + } + + file, err := os.OpenFile(jwtPath, os.O_RDWR|os.O_CREATE, 0666) + if err != nil { + log.Fatalf("could not create jwt-file %s: %v", jwtPath, err) + } + + file.Close() + + if err != nil { + log.Fatalf("could not read jwt-file %s: %v", jwtPath, err) + } +} diff --git a/internal/app/vault_raft_snapshot_agent/snapshotter_test.go b/internal/app/vault_raft_snapshot_agent/snapshotter_test.go index 2ef98ca..49ca364 100644 --- a/internal/app/vault_raft_snapshot_agent/snapshotter_test.go +++ b/internal/app/vault_raft_snapshot_agent/snapshotter_test.go @@ -9,22 +9,22 @@ import ( "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/upload" "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/vault" - "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/vault/auth" "github.com/stretchr/testify/assert" ) func TestSnapshotterLocksTakeSnapshot(t *testing.T) { - clientAPIStub := snapshotterVaultClientAPIStub{ + clientAPIStub := &clientVaultAPIStub{ leader: true, snapshotRuntime: time.Millisecond * 500, } + uploaderStub := uploaderStub{} config := SnapshotConfig{ Timeout: clientAPIStub.snapshotRuntime * 3, } snapshotter := Snapshotter{} - snapshotter.Configure(config, vault.NewClient("http://127.0.0.1:8200", &clientAPIStub, nil), []upload.Uploader{&uploaderStub}) + snapshotter.Configure(config, newClient(clientAPIStub), []upload.Uploader{&uploaderStub}) start := time.Now() @@ -48,10 +48,11 @@ func TestSnapshotterLocksTakeSnapshot(t *testing.T) { } func TestSnapshotterLocksConfigure(t *testing.T) { - clientAPIStub := snapshotterVaultClientAPIStub{ + clientAPIStub := &clientVaultAPIStub{ leader: true, snapshotRuntime: time.Millisecond * 500, } + uploaderStub := uploaderStub{} config := SnapshotConfig{ Timeout: clientAPIStub.snapshotRuntime * 3, @@ -63,7 +64,7 @@ func TestSnapshotterLocksConfigure(t *testing.T) { } snapshotter := Snapshotter{} - snapshotter.Configure(config, vault.NewClient("http://127.0.0.1:8200", &clientAPIStub, nil), []upload.Uploader{&uploaderStub}) + snapshotter.Configure(config, newClient(clientAPIStub), []upload.Uploader{&uploaderStub}) start := time.Now() @@ -77,7 +78,7 @@ func TestSnapshotterLocksConfigure(t *testing.T) { go func() { <-running - snapshotter.Configure(newConfig, vault.NewClient("http://127.0.0.1:8200", &clientAPIStub, nil), []upload.Uploader{&uploaderStub}) + snapshotter.Configure(newConfig, newClient(clientAPIStub), []upload.Uploader{&uploaderStub}) errs <- nil }() @@ -96,17 +97,18 @@ func TestSnapshotterLocksConfigure(t *testing.T) { } func TestSnapshotterAbortsAfterTimeout(t *testing.T) { - clientAPIStub := snapshotterVaultClientAPIStub{ + clientAPIStub := &clientVaultAPIStub{ leader: true, snapshotRuntime: time.Second * 5, } + uploaderStub := uploaderStub{} config := SnapshotConfig{ Timeout: time.Second, } snapshotter := Snapshotter{} - snapshotter.Configure(config, vault.NewClient("http://127.0.0.1:8200", &clientAPIStub, nil), []upload.Uploader{&uploaderStub}) + snapshotter.Configure(config, newClient(clientAPIStub), []upload.Uploader{&uploaderStub}) start := time.Now() @@ -123,16 +125,17 @@ func TestSnapshotterAbortsAfterTimeout(t *testing.T) { } func TestSnapshotterFailsIfSnapshottingFails(t *testing.T) { - clientAPIStub := snapshotterVaultClientAPIStub{ + clientAPIStub := &clientVaultAPIStub{ leader: false, } + uploaderStub := uploaderStub{} config := SnapshotConfig{ Timeout: time.Second, } snapshotter := Snapshotter{} - snapshotter.Configure(config, vault.NewClient("http://127.0.0.1:8200", &clientAPIStub, nil), []upload.Uploader{&uploaderStub}) + snapshotter.Configure(config, newClient(clientAPIStub), []upload.Uploader{&uploaderStub}) _, err := snapshotter.TakeSnapshot(context.Background()) @@ -141,10 +144,11 @@ func TestSnapshotterFailsIfSnapshottingFails(t *testing.T) { } func TestSnapshotterUploadsDataFromSnapshot(t *testing.T) { - clientAPIStub := snapshotterVaultClientAPIStub{ + clientAPIStub := &clientVaultAPIStub{ leader: true, snapshotData: "test-snapshot", } + uploaderStub := uploaderStub{} config := SnapshotConfig{ Timeout: time.Second, @@ -154,7 +158,7 @@ func TestSnapshotterUploadsDataFromSnapshot(t *testing.T) { } snapshotter := Snapshotter{} - snapshotter.Configure(config, vault.NewClient("http://127.0.0.1:8200", &clientAPIStub, nil), []upload.Uploader{&uploaderStub}) + snapshotter.Configure(config, newClient(clientAPIStub), []upload.Uploader{&uploaderStub}) _, err := snapshotter.TakeSnapshot(context.Background()) @@ -167,10 +171,11 @@ func TestSnapshotterUploadsDataFromSnapshot(t *testing.T) { } func TestSnapshotterContinuesUploadingIfUploadFails(t *testing.T) { - clientAPIStub := snapshotterVaultClientAPIStub{ + clientAPIStub := &clientVaultAPIStub{ leader: true, snapshotData: "test-snapshot", } + uploaderStub1 := uploaderStub{ uploadFails: true, } @@ -183,7 +188,7 @@ func TestSnapshotterContinuesUploadingIfUploadFails(t *testing.T) { } snapshotter := Snapshotter{} - snapshotter.Configure(config, vault.NewClient("http://127.0.0.1:8200", &clientAPIStub, nil), []upload.Uploader{&uploaderStub1, &uploaderStub2}) + snapshotter.Configure(config, newClient(clientAPIStub), []upload.Uploader{&uploaderStub1, &uploaderStub2}) _, err := snapshotter.TakeSnapshot(context.Background()) assert.Error(t, err, "TakeSnaphot did not fail although one of the uploaders failed") @@ -193,7 +198,8 @@ func TestSnapshotterContinuesUploadingIfUploadFails(t *testing.T) { } func TestSnapshotterResetsTimer(t *testing.T) { - clientAPIStub := snapshotterVaultClientAPIStub{leader: true} + clientAPIStub := &clientVaultAPIStub{leader: true} + uploaderStub := uploaderStub{} config := SnapshotConfig{ @@ -201,7 +207,7 @@ func TestSnapshotterResetsTimer(t *testing.T) { } snapshotter := Snapshotter{} - snapshotter.Configure(config, vault.NewClient("http://127.0.0.1:8200", &clientAPIStub, nil), []upload.Uploader{&uploaderStub}) + snapshotter.Configure(config, newClient(clientAPIStub), []upload.Uploader{&uploaderStub}) start := time.Now() timer, err := snapshotter.TakeSnapshot(context.Background()) @@ -220,7 +226,8 @@ func TestSnapshotterResetsTimer(t *testing.T) { } func TestSnapshotterResetsTimerOnError(t *testing.T) { - clientAPIStub := snapshotterVaultClientAPIStub{leader: false} + clientAPIStub := &clientVaultAPIStub{leader: false} + uploaderStub := uploaderStub{} config := SnapshotConfig{ @@ -228,7 +235,7 @@ func TestSnapshotterResetsTimerOnError(t *testing.T) { } snapshotter := Snapshotter{} - snapshotter.Configure(config, vault.NewClient("http://127.0.0.1:8200", &clientAPIStub, nil), []upload.Uploader{&uploaderStub}) + snapshotter.Configure(config, newClient(clientAPIStub), []upload.Uploader{&uploaderStub}) start := time.Now() timer, err := snapshotter.TakeSnapshot(context.Background()) @@ -246,7 +253,8 @@ func TestSnapshotterResetsTimerOnError(t *testing.T) { } func TestSnapshotterUpdatesTimerOnConfigureForGreaterFrequency(t *testing.T) { - clientAPIStub := snapshotterVaultClientAPIStub{leader: false} + clientAPIStub := &clientVaultAPIStub{leader: false} + uploaderStub := uploaderStub{} config := SnapshotConfig{ @@ -254,7 +262,7 @@ func TestSnapshotterUpdatesTimerOnConfigureForGreaterFrequency(t *testing.T) { } snapshotter := Snapshotter{} - snapshotter.Configure(config, vault.NewClient("http://127.0.0.1:8200", &clientAPIStub, nil), []upload.Uploader{&uploaderStub}) + snapshotter.Configure(config, newClient(clientAPIStub), []upload.Uploader{&uploaderStub}) start := time.Now() timer, _ := snapshotter.TakeSnapshot(context.Background()) @@ -263,7 +271,7 @@ func TestSnapshotterUpdatesTimerOnConfigureForGreaterFrequency(t *testing.T) { Frequency: time.Second * 2, } - snapshotter.Configure(newConfig, vault.NewClient("http://127.0.0.1:8200", &clientAPIStub, nil), []upload.Uploader{&uploaderStub}) + snapshotter.Configure(newConfig, newClient(clientAPIStub), []upload.Uploader{&uploaderStub}) for { <-timer.C @@ -276,7 +284,8 @@ func TestSnapshotterUpdatesTimerOnConfigureForGreaterFrequency(t *testing.T) { } func TestSnapshotterUpdatesTimerOnConfigureForLesserFrequency(t *testing.T) { - clientAPIStub := snapshotterVaultClientAPIStub{leader: false} + clientAPIStub := &clientVaultAPIStub{leader: false} + uploaderStub := uploaderStub{} config := SnapshotConfig{ @@ -284,7 +293,7 @@ func TestSnapshotterUpdatesTimerOnConfigureForLesserFrequency(t *testing.T) { } snapshotter := Snapshotter{} - snapshotter.Configure(config, vault.NewClient("http://127.0.0.1:8200", &clientAPIStub, nil), []upload.Uploader{&uploaderStub}) + snapshotter.Configure(config, newClient(clientAPIStub), []upload.Uploader{&uploaderStub}) start := time.Now() timer, _ := snapshotter.TakeSnapshot(context.Background()) @@ -293,7 +302,7 @@ func TestSnapshotterUpdatesTimerOnConfigureForLesserFrequency(t *testing.T) { Frequency: time.Millisecond * 500, } - snapshotter.Configure(newConfig, vault.NewClient("http://127.0.0.1:8200", &clientAPIStub, nil), []upload.Uploader{&uploaderStub}) + snapshotter.Configure(newConfig, newClient(clientAPIStub), []upload.Uploader{&uploaderStub}) for { <-timer.C @@ -306,7 +315,8 @@ func TestSnapshotterUpdatesTimerOnConfigureForLesserFrequency(t *testing.T) { } func TestSnapshotterTriggersTimerOnConfigureForLesserFrequency(t *testing.T) { - clientAPIStub := snapshotterVaultClientAPIStub{leader: false} + clientAPIStub := &clientVaultAPIStub{leader: false} + uploaderStub := uploaderStub{} config := SnapshotConfig{ @@ -314,7 +324,7 @@ func TestSnapshotterTriggersTimerOnConfigureForLesserFrequency(t *testing.T) { } snapshotter := Snapshotter{} - snapshotter.Configure(config, vault.NewClient("http://127.0.0.1:8200", &clientAPIStub, nil), []upload.Uploader{&uploaderStub}) + snapshotter.Configure(config, newClient(clientAPIStub), []upload.Uploader{&uploaderStub}) timer, _ := snapshotter.TakeSnapshot(context.Background()) time.Sleep(time.Millisecond * 500) @@ -324,10 +334,10 @@ func TestSnapshotterTriggersTimerOnConfigureForLesserFrequency(t *testing.T) { } start := time.Now() - snapshotter.Configure(newConfig, vault.NewClient("http://127.0.0.1:8200", &clientAPIStub, nil), []upload.Uploader{&uploaderStub}) + snapshotter.Configure(newConfig, newClient(clientAPIStub), []upload.Uploader{&uploaderStub}) for { - + <-timer.C break } @@ -336,13 +346,27 @@ func TestSnapshotterTriggersTimerOnConfigureForLesserFrequency(t *testing.T) { assert.Equal(t, newConfig.Frequency, snapshotter.config.Frequency) } -type snapshotterVaultClientAPIStub struct { +func newClient(api *clientVaultAPIStub) *vault.VaultClient[any, clientVaultAPIAuthStub] { + return vault.NewVaultClient[any](api, clientVaultAPIAuthStub{}, time.Time{}) +} + +type clientVaultAPIAuthStub struct{} + +func (stub clientVaultAPIAuthStub) Login(ctx context.Context, api any) (time.Duration, error) { + return 0, nil +} + +type clientVaultAPIStub struct { leader bool snapshotRuntime time.Duration snapshotData string } -func (stub *snapshotterVaultClientAPIStub) TakeSnapshot(ctx context.Context, writer io.Writer) error { +func (stub *clientVaultAPIStub) Address() string { + return "test" +} + +func (stub *clientVaultAPIStub) TakeSnapshot(ctx context.Context, writer io.Writer) error { if stub.snapshotData != "" { if _, err := writer.Write([]byte(stub.snapshotData)); err != nil { return err @@ -357,12 +381,12 @@ func (stub *snapshotterVaultClientAPIStub) TakeSnapshot(ctx context.Context, wri return nil } -func (stub *snapshotterVaultClientAPIStub) IsLeader() (bool, error) { +func (stub *clientVaultAPIStub) IsLeader() (bool, error) { return stub.leader, nil } -func (stub *snapshotterVaultClientAPIStub) AuthAPI() auth.VaultAuthAPI { - return nil +func (stub *clientVaultAPIStub) RefreshAuth(ctx context.Context, auth clientVaultAPIAuthStub) (time.Duration, error) { + return auth.Login(ctx, nil) } type uploaderStub struct { diff --git a/internal/app/vault_raft_snapshot_agent/test/test_helpers.go b/internal/app/vault_raft_snapshot_agent/test/test_helpers.go new file mode 100644 index 0000000..7b73839 --- /dev/null +++ b/internal/app/vault_raft_snapshot_agent/test/test_helpers.go @@ -0,0 +1,23 @@ +package test + +import ( + "fmt" + "os" + "runtime" + "testing" +) + +func WriteFile(t *testing.T, dest string, contents string) error { + t.Helper() + + if runtime.GOOS != "windows" { + tmpFile := fmt.Sprintf("%s.tmp", dest) + if err := os.WriteFile(tmpFile, []byte(contents), 0644); err != nil { + return err + } + + return os.Rename(tmpFile, dest) + } else { + return os.WriteFile(dest, []byte(contents), 0644) + } +} diff --git a/internal/app/vault_raft_snapshot_agent/upload/uploaders.go b/internal/app/vault_raft_snapshot_agent/upload/uploaders.go index 4f15947..e4a97fb 100644 --- a/internal/app/vault_raft_snapshot_agent/upload/uploaders.go +++ b/internal/app/vault_raft_snapshot_agent/upload/uploaders.go @@ -15,10 +15,6 @@ type UploadersConfig struct { Local LocalConfig `default:"{\"Empty\": true}" mapstructure:"local"` } -func (c UploadersConfig) HasUploaders() bool { - return !(c.AWS.Empty && c.Azure.Empty && c.GCP.Empty && c.Local.Empty) -} - type Uploader interface { Destination() string Upload(ctx context.Context, snapshot io.Reader, prefix string, timestamp string, suffix string, retain int) error diff --git a/internal/app/vault_raft_snapshot_agent/vault/api.go b/internal/app/vault_raft_snapshot_agent/vault/api.go deleted file mode 100644 index e5e0b8a..0000000 --- a/internal/app/vault_raft_snapshot_agent/vault/api.go +++ /dev/null @@ -1,92 +0,0 @@ -package vault - -import ( - "context" - "encoding/json" - "fmt" - "io" - "path" - "time" - - "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/vault/auth" - "github.com/hashicorp/vault/api" -) - -func newVaultAPIImpl(address string, insecure bool, timeout time.Duration) (*vaultAPIImpl, error) { - apiConfig := api.DefaultConfig() - apiConfig.Address = address - apiConfig.HttpClient.Timeout = timeout - - tlsConfig := &api.TLSConfig{ - Insecure: insecure, - } - - if err := apiConfig.ConfigureTLS(tlsConfig); err != nil { - return nil, err - } - - client, err := api.NewClient(apiConfig) - if err != nil { - return nil, err - } - - return &vaultAPIImpl{ - client, - &vaultAuthAPIImpl{ - client, - }, - }, nil -} - -type vaultAPIImpl struct { - client *api.Client - authAPI *vaultAuthAPIImpl -} - -func (impl *vaultAPIImpl) TakeSnapshot(ctx context.Context, writer io.Writer) error { - return impl.client.Sys().RaftSnapshotWithContext(ctx, writer) -} - -func (impl *vaultAPIImpl) IsLeader() (bool, error) { - leader, err := impl.client.Sys().Leader() - if err != nil { - return false, err - } - - return leader.IsSelf, nil -} - -func (impl *vaultAPIImpl) AuthAPI() auth.VaultAuthAPI { - return impl.authAPI -} - -type vaultAuthAPIImpl struct { - client *api.Client -} - -func (impl *vaultAuthAPIImpl) LoginToBackend(authPath string, credentials map[string]interface{}) (leaseDuration time.Duration, err error) { - resp, err := impl.client.Logical().Write(path.Clean("auth/"+authPath+"/login"), credentials) - if err != nil { - return 0, err - } - - impl.client.SetToken(resp.Auth.ClientToken) - return time.Duration(resp.Auth.LeaseDuration), nil -} - -func (impl *vaultAuthAPIImpl) LoginWithToken(token string) (leaseDuration time.Duration, err error) { - impl.client.SetToken(token) - info, err := impl.client.Auth().Token().LookupSelf() - if err != nil { - impl.client.ClearToken() - return 0, err - } - - ttl, err := info.Data["ttl"].(json.Number).Int64() - if err != nil { - impl.client.ClearToken() - return 0, fmt.Errorf("error converting ttl to int: %s", err) - } - - return time.Duration(ttl), nil -} diff --git a/internal/app/vault_raft_snapshot_agent/vault/auth/approle.go b/internal/app/vault_raft_snapshot_agent/vault/auth/approle.go index 85a7ef5..02682f9 100644 --- a/internal/app/vault_raft_snapshot_agent/vault/auth/approle.go +++ b/internal/app/vault_raft_snapshot_agent/vault/auth/approle.go @@ -1,21 +1,26 @@ package auth +import ( + "github.com/hashicorp/vault/api/auth/approle" +) + type AppRoleAuthConfig struct { Path string `default:"approle"` - RoleId string `mapstructure:"id" validate:"required_if=Empty false"` + RoleId string `mapstructure:"role" validate:"required_if=Empty false"` SecretId string `mapstructure:"secret" validate:"required_if=Empty false"` - Empty bool + Empty bool } -func createAppRoleAuth(config AppRoleAuthConfig) authBackend { - return authBackend{ - name: "AppRole", - path: config.Path, - credentialsFactory: func() (map[string]interface{}, error) { - return map[string]interface{}{ - "role_id": config.RoleId, - "secret_id": config.SecretId, - }, nil - }, +func createAppRoleAuth(config AppRoleAuthConfig) (authMethod, error) { + auth, err := approle.NewAppRoleAuth( + config.RoleId, + &approle.SecretID{FromString: config.SecretId}, + approle.WithMountPath(config.Path), + ) + + if err != nil { + return authMethod{}, err } + + return authMethod{auth}, nil } diff --git a/internal/app/vault_raft_snapshot_agent/vault/auth/approle_test.go b/internal/app/vault_raft_snapshot_agent/vault/auth/approle_test.go index 51fff54..3b42cf5 100644 --- a/internal/app/vault_raft_snapshot_agent/vault/auth/approle_test.go +++ b/internal/app/vault_raft_snapshot_agent/vault/auth/approle_test.go @@ -1,55 +1,29 @@ package auth import ( - "errors" "testing" - "time" + + "github.com/hashicorp/vault/api/auth/approle" "github.com/stretchr/testify/assert" ) -func TestCreateDefaultAppRoleAuth(t *testing.T) { - authPath := "test" - expectedRoleId := "testRoleId" - expectedSecretId := "testSecretId" - +func TestCreateAppRoleAuth(t *testing.T) { config := AppRoleAuthConfig{ - Path: authPath, - RoleId: expectedRoleId, - SecretId: expectedSecretId, + RoleId: "test-role", + SecretId: "test-secret", + Path: "test-path", } - authApiStub := appRoleVaultAuthApiStub{} - - auth := createAppRoleAuth(config) - _, err := auth.Refresh(&authApiStub) - - assert.NoError(t, err, "auth-refresh failed unexpectedly") - assertAppRoleAuthValues(t, authPath, expectedRoleId, expectedSecretId, auth, authApiStub) -} - -func assertAppRoleAuthValues(t *testing.T, expectedAuthPath string, expectedRoleId string, expectedSecretId string, auth authBackend, api appRoleVaultAuthApiStub) { - t.Helper() + expectedAuthMethod, err := approle.NewAppRoleAuth( + config.RoleId, + &approle.SecretID{FromString: config.SecretId}, + approle.WithMountPath(config.Path), + ) + assert.NoError(t, err, "NewAppRoleAuth failed unexpectedly") - assert.Equal(t, "AppRole", auth.name) - assert.Equal(t, expectedAuthPath, api.path) - assert.Equal(t, expectedRoleId, api.roleId) - assert.Equal(t, expectedSecretId, api.secretId) -} - -type appRoleVaultAuthApiStub struct { - path string - roleId string - secretId string -} - -func (stub *appRoleVaultAuthApiStub) LoginToBackend(path string, credentials map[string]interface{}) (leaseDuration time.Duration, err error) { - stub.path = path - stub.roleId = credentials["role_id"].(string) - stub.secretId = credentials["secret_id"].(string) - return 0, nil -} + auth, err := createAppRoleAuth(config) + assert.NoError(t, err, "createAppRoleAuth failed unexpectedly") -func (stub *appRoleVaultAuthApiStub) LoginWithToken(token string) (leaseDuration time.Duration, err error) { - return 0, errors.New("not implemented") + assert.Equal(t, expectedAuthMethod, auth.delegate) } diff --git a/internal/app/vault_raft_snapshot_agent/vault/auth/auth.go b/internal/app/vault_raft_snapshot_agent/vault/auth/auth.go index 90f1f77..256dd9b 100644 --- a/internal/app/vault_raft_snapshot_agent/vault/auth/auth.go +++ b/internal/app/vault_raft_snapshot_agent/vault/auth/auth.go @@ -1,32 +1,59 @@ package auth import ( + "context" + "fmt" "time" + + "github.com/hashicorp/vault/api" ) type AuthConfig struct { AppRole AppRoleAuthConfig `default:"{\"Empty\": true}"` + AWS AWSAuthConfig `default:"{\"Empty\": true}"` + Azure AzureAuthConfig `default:"{\"Empty\": true}"` + GCP GCPAuthConfig `default:"{\"Empty\": true}"` Kubernetes KubernetesAuthConfig `default:"{\"Empty\": true}"` + LDAP LDAPAuthConfig `default:"{\"Empty\": true}"` + UserPass UserPassAuthConfig `default:"{\"Empty\": true}"` Token string } -type VaultAuthAPI interface { - LoginToBackend(path string, credentials map[string]interface{}) (leaseDuration time.Duration, err error) - LoginWithToken(token string) (leaseDuration time.Duration, err error) +type auth[C any] interface { + Login(ctx context.Context, client C) (time.Duration, error) } -type Auth interface { - Refresh(api VaultAuthAPI) (time.Time, error) +type authMethod struct { + delegate api.AuthMethod } -func CreateAuth(config AuthConfig) Auth { +func CreateVaultAuth(config AuthConfig) (auth[*api.Client], error) { if !config.AppRole.Empty { return createAppRoleAuth(config.AppRole) + } else if !config.AWS.Empty { + return createAWSAuth(config.AWS) + } else if !config.Azure.Empty { + return createAzureAuth(config.Azure) + } else if !config.GCP.Empty { + return createGCPAuth(config.GCP) } else if !config.Kubernetes.Empty { return createKubernetesAuth(config.Kubernetes) + } else if !config.LDAP.Empty { + return createLDAPAuth(config.LDAP) + } else if !config.UserPass.Empty { + return createUserPassAuth(config.UserPass) } else if config.Token != "" { - return createTokenAuth(config.Token) + return createTokenAuth(config.Token), nil } else { - return nil + return nil, fmt.Errorf("unknown authenticatin method") } } + +func (wrapper authMethod) Login(ctx context.Context, client *api.Client) (time.Duration, error) { + secret, err := wrapper.delegate.Login(ctx, client) + if err != nil { + return time.Duration(0), err + } + + return time.Duration(secret.LeaseDuration), nil +} diff --git a/internal/app/vault_raft_snapshot_agent/vault/auth/aws.go b/internal/app/vault_raft_snapshot_agent/vault/auth/aws.go new file mode 100644 index 0000000..5d4ba95 --- /dev/null +++ b/internal/app/vault_raft_snapshot_agent/vault/auth/aws.go @@ -0,0 +1,63 @@ +package auth + +import ( + "fmt" + + "github.com/hashicorp/vault/api/auth/aws" +) + +type AWSSignatureType string + +const ( + AWS_EC2_PKCS7 AWSSignatureType = "pkcs7" + AWS_ECS_IDENTITY AWSSignatureType = "identity" + AWS_EC2_RSA2048 AWSSignatureType = "rsa2048" +) + +type AWSAuthConfig struct { + Path string `default:"aws"` + Role string + Region string + EC2Nonce string + EC2SignatureType AWSSignatureType `default:"pkcs7"` + IAMServerIDHeader string + Empty bool +} + +func createAWSAuth(config AWSAuthConfig) (authMethod, error) { + var loginOpts = []aws.LoginOption{aws.WithMountPath(config.Path)} + + if config.EC2Nonce != "" { + loginOpts = append(loginOpts, aws.WithNonce(config.EC2Nonce), aws.WithEC2Auth()) + switch config.EC2SignatureType { + case "": + case AWS_EC2_PKCS7: + case AWS_ECS_IDENTITY: + loginOpts = append(loginOpts, aws.WithIdentitySignature()) + case AWS_EC2_RSA2048: + loginOpts = append(loginOpts, aws.WithRSA2048Signature()) + default: + return authMethod{}, fmt.Errorf("unknown signature-type %s", config.EC2SignatureType) + } + } else { + loginOpts = append(loginOpts, aws.WithIAMAuth()) + if config.IAMServerIDHeader != "" { + loginOpts = append(loginOpts, aws.WithIAMServerIDHeader(config.IAMServerIDHeader)) + } + } + + if config.Region != "" { + loginOpts = append(loginOpts, aws.WithRegion(config.Region)) + } + + if config.Role != "" { + loginOpts = append(loginOpts, aws.WithRole(config.Role)) + } + + auth, err := aws.NewAWSAuth(loginOpts...) + if err != nil { + return authMethod{}, err + } + + return authMethod{auth}, nil +} diff --git a/internal/app/vault_raft_snapshot_agent/vault/auth/aws_test.go b/internal/app/vault_raft_snapshot_agent/vault/auth/aws_test.go new file mode 100644 index 0000000..c1ea8a8 --- /dev/null +++ b/internal/app/vault_raft_snapshot_agent/vault/auth/aws_test.go @@ -0,0 +1,98 @@ +package auth + +import ( + "testing" + + "github.com/hashicorp/vault/api/auth/aws" + + "github.com/stretchr/testify/assert" +) + +func TestCreateAWSIAMAuth(t *testing.T) { + t.Setenv("AWS_ACCESS_KEY_ID", "test-id") + t.Setenv("AWS_SECRET_ACCESS_KEY", "test-key") + t.Setenv("AWS_SESSION_TOKEN", "test-token") + + config := AWSAuthConfig{ + Role: "test-role", + IAMServerIDHeader: "test-header", + Region: "test-region", + Path: "test-path", + } + + expectedAuthMethod, err := aws.NewAWSAuth( + aws.WithRole(config.Role), + aws.WithIAMAuth(), + aws.WithIAMServerIDHeader(config.IAMServerIDHeader), + aws.WithRegion(config.Region), + aws.WithMountPath(config.Path), + ) + assert.NoError(t, err, "NewAWSAuth failed unexpectedly") + + auth, err := createAWSAuth(config) + assert.NoError(t, err, "createAWSAuth failed unexpectedly") + + assert.Equal(t, expectedAuthMethod, auth.delegate) +} + +func TestCreateAWSEC2DefaultAuth(t *testing.T) { + config := AWSAuthConfig{ + Role: "test-role", + EC2Nonce: "test-nonce", + Region: "test-region", + Path: "test-path", + } + + expectedAuthMethod, err := aws.NewAWSAuth( + aws.WithRole(config.Role), + aws.WithEC2Auth(), + aws.WithNonce(config.EC2Nonce), + aws.WithPKCS7Signature(), + aws.WithRegion(config.Region), + aws.WithMountPath(config.Path), + ) + assert.NoError(t, err, "NewAWSAuth failed unexpectedly") + + auth, err := createAWSAuth(config) + assert.NoError(t, err, "createAWSAuth failed unexpectedly") + + assert.Equal(t, expectedAuthMethod, auth.delegate) +} + +func TestCreateAWSEC2RSA2048Auth(t *testing.T) { + config := AWSAuthConfig{ + Role: "test-role", + EC2Nonce: "test-nonce", + EC2SignatureType: "rsa2048", + Region: "test-region", + Path: "test-path", + } + + expectedAuthMethod, err := aws.NewAWSAuth( + aws.WithRole(config.Role), + aws.WithEC2Auth(), + aws.WithNonce(config.EC2Nonce), + aws.WithRSA2048Signature(), + aws.WithRegion(config.Region), + aws.WithMountPath(config.Path), + ) + assert.NoError(t, err, "NewAWSAuth failed unexpectedly") + + auth, err := createAWSAuth(config) + assert.NoError(t, err, "createAWSAuth failed unexpectedly") + + assert.Equal(t, expectedAuthMethod, auth.delegate) +} + +func TestCreateAWSEC2AuthFailsForUnknownSignatureType(t *testing.T) { + config := AWSAuthConfig{ + Role: "test-role", + EC2Nonce: "test-nonce", + EC2SignatureType: "unknown", + Region: "test-region", + Path: "test-path", + } + + _, err := createAWSAuth(config) + assert.Error(t, err, "createAWSAuth did not fail for unknown signature type") +} diff --git a/internal/app/vault_raft_snapshot_agent/vault/auth/azure.go b/internal/app/vault_raft_snapshot_agent/vault/auth/azure.go new file mode 100644 index 0000000..f793a26 --- /dev/null +++ b/internal/app/vault_raft_snapshot_agent/vault/auth/azure.go @@ -0,0 +1,27 @@ +package auth + +import ( + "github.com/hashicorp/vault/api/auth/azure" +) + +type AzureAuthConfig struct { + Path string `default:"azure"` + Role string `validate:"required_if=Empty false"` + Resource string + Empty bool +} + +func createAzureAuth(config AzureAuthConfig) (authMethod, error) { + var loginOpts = []azure.LoginOption{azure.WithMountPath(config.Path)} + + if config.Resource != "" { + loginOpts = append(loginOpts, azure.WithResource(config.Resource)) + } + + auth, err := azure.NewAzureAuth(config.Role, loginOpts...) + if err != nil { + return authMethod{}, err + } + + return authMethod{auth}, nil +} diff --git a/internal/app/vault_raft_snapshot_agent/vault/auth/azure_test.go b/internal/app/vault_raft_snapshot_agent/vault/auth/azure_test.go new file mode 100644 index 0000000..67b053e --- /dev/null +++ b/internal/app/vault_raft_snapshot_agent/vault/auth/azure_test.go @@ -0,0 +1,29 @@ +package auth + +import ( + "testing" + + "github.com/hashicorp/vault/api/auth/azure" + + "github.com/stretchr/testify/assert" +) + +func TestCreateAzureAuth(t *testing.T) { + config := AzureAuthConfig{ + Role: "test-role", + Resource: "test-resource", + Path: "test-path", + } + + expectedAuthMethod, err := azure.NewAzureAuth( + config.Role, + azure.WithResource(config.Resource), + azure.WithMountPath(config.Path), + ) + assert.NoError(t, err, "NewAzureAuth failed unexpectedly") + + auth, err := createAzureAuth(config) + assert.NoError(t, err, "createAzureAuth failed unexpectedly") + + assert.Equal(t, expectedAuthMethod, auth.delegate) +} diff --git a/internal/app/vault_raft_snapshot_agent/vault/auth/backend.go b/internal/app/vault_raft_snapshot_agent/vault/auth/backend.go deleted file mode 100644 index 403b558..0000000 --- a/internal/app/vault_raft_snapshot_agent/vault/auth/backend.go +++ /dev/null @@ -1,26 +0,0 @@ -package auth - -import ( - "fmt" - "time" -) - -type authBackend struct { - name string - path string - credentialsFactory func() (map[string]interface{}, error) -} - -func (b authBackend) Refresh(api VaultAuthAPI) (time.Time, error) { - credentials, err := b.credentialsFactory() - if err != nil { - return time.Now(), fmt.Errorf("error creating credentials for auth-backend %s: %s", b.name, err) - } - - leaseDuration, err := api.LoginToBackend(b.path, credentials) - if err != nil { - return time.Now(), fmt.Errorf("error logging into vault using auth-backend %s: %s", b.name, err) - } - - return time.Now().Add((time.Second * leaseDuration) / 2), nil -} diff --git a/internal/app/vault_raft_snapshot_agent/vault/auth/backend_test.go b/internal/app/vault_raft_snapshot_agent/vault/auth/backend_test.go deleted file mode 100644 index edc1577..0000000 --- a/internal/app/vault_raft_snapshot_agent/vault/auth/backend_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package auth - -import ( - "errors" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestAuthBackendFailsIfAuthCredentialsFactoryFails(t *testing.T) { - authApiStub := backendVaultAuthApiStub{} - - auth := authBackend{ - credentialsFactory: func() (map[string]interface{}, error) { - return map[string]interface{}{}, errors.New("could not create credentials") - }, - } - - _, err := auth.Refresh(&authApiStub) - - assert.Error(t, err, "auth backend did not fail when credentials-factory failed") - assert.False(t, authApiStub.triedToLogin, "auth backend did try to login although credentials-factory failed") -} - -func TestAuthBackendFailsIfLoginFails(t *testing.T) { - authApiStub := backendVaultAuthApiStub{loginFails: true} - auth := authBackend{ - credentialsFactory: func() (map[string]interface{}, error) { - return map[string]interface{}{}, nil - }, - } - - _, err := auth.Refresh(&authApiStub) - - assert.Error(t, err, "auth backend did not fail when login failed") - assert.True(t, authApiStub.triedToLogin, "auth backend did not try to login") -} - -func TestAuthBackendPassesPathAndLoginCredentials(t *testing.T) { - authApiStub := backendVaultAuthApiStub{} - authPath := "test" - expectedCredentials := map[string]interface{}{ - "key": "value", - } - - auth := authBackend{ - path: authPath, - credentialsFactory: func() (map[string]interface{}, error) { - return expectedCredentials, nil - }, - } - - _, err := auth.Refresh(&authApiStub) - - assert.NoError(t, err, "auth backend failed unexpectedly") - assert.Equal(t, authPath, authApiStub.authPath) - assert.Equal(t, expectedCredentials, authApiStub.loginCredentials) -} - -func TestBackendAuthReturnsExpirationBasedOnLoginLeaseDuration(t *testing.T) { - authApiStub := backendVaultAuthApiStub{leaseDuration: time.Minute} - - auth := authBackend{ - credentialsFactory: func() (map[string]interface{}, error) { - return map[string]interface{}{}, nil - }, - } - - expiration, err := auth.Refresh(&authApiStub) - assert.NoError(t, err, "auth backend failed unexpectedly") - - expectedExpiration := time.Now().Add((time.Second * authApiStub.leaseDuration) / 2) - assert.WithinDuration(t, expectedExpiration, expiration, time.Millisecond) -} - -type backendVaultAuthApiStub struct { - loginFails bool - triedToLogin bool - authPath string - loginCredentials map[string]interface{} - leaseDuration time.Duration -} - -func (stub *backendVaultAuthApiStub) LoginToBackend(path string, credentials map[string]interface{}) (leaseDuration time.Duration, err error) { - stub.triedToLogin = true - stub.authPath = path - stub.loginCredentials = credentials - if stub.loginFails { - return 0, errors.New("login failed") - } else { - return stub.leaseDuration, nil - } -} - -func (stub *backendVaultAuthApiStub) LoginWithToken(token string) (leaseDuration time.Duration, err error) { - return 0, errors.New("not implemented") -} diff --git a/internal/app/vault_raft_snapshot_agent/vault/auth/gcp.go b/internal/app/vault_raft_snapshot_agent/vault/auth/gcp.go new file mode 100644 index 0000000..d88bba2 --- /dev/null +++ b/internal/app/vault_raft_snapshot_agent/vault/auth/gcp.go @@ -0,0 +1,29 @@ +package auth + +import ( + "github.com/hashicorp/vault/api/auth/gcp" +) + +type GCPAuthConfig struct { + Path string `default:"gcp"` + Role string `validate:"required_if=Empty false"` + ServiceAccountEmail string + Empty bool +} + +func createGCPAuth(config GCPAuthConfig) (authMethod, error) { + var loginOpts = []gcp.LoginOption{gcp.WithMountPath(config.Path)} + + if config.ServiceAccountEmail != "" { + loginOpts = append(loginOpts, gcp.WithIAMAuth(config.ServiceAccountEmail)) + } else { + loginOpts = append(loginOpts, gcp.WithGCEAuth()) + } + + auth, err := gcp.NewGCPAuth(config.Role, loginOpts...) + if err != nil { + return authMethod{}, err + } + + return authMethod{auth}, nil +} diff --git a/internal/app/vault_raft_snapshot_agent/vault/auth/gcp_test.go b/internal/app/vault_raft_snapshot_agent/vault/auth/gcp_test.go new file mode 100644 index 0000000..6d24cff --- /dev/null +++ b/internal/app/vault_raft_snapshot_agent/vault/auth/gcp_test.go @@ -0,0 +1,48 @@ +package auth + +import ( + "testing" + + "github.com/hashicorp/vault/api/auth/gcp" + + "github.com/stretchr/testify/assert" +) + +func TestCreateGCPGCEAuth(t *testing.T) { + config := GCPAuthConfig{ + Role: "test-role", + Path: "test-path", + } + + expectedAuthMethod, err := gcp.NewGCPAuth( + config.Role, + gcp.WithGCEAuth(), + gcp.WithMountPath("test-path"), + ) + assert.NoError(t, err, "NewGCPAuth failed unexpectedly") + + auth, err := createGCPAuth(config) + assert.NoError(t, err, "createGCPAuth failed unexpectedly") + + assert.Equal(t, expectedAuthMethod, auth.delegate) +} + +func TestCreateGCPIAMAuth(t *testing.T) { + config := GCPAuthConfig{ + Role: "test-role", + ServiceAccountEmail: "test@email.com", + Path: "test-path", + } + + expectedAuthMethod, err := gcp.NewGCPAuth( + config.Role, + gcp.WithIAMAuth(config.ServiceAccountEmail), + gcp.WithMountPath("test-path"), + ) + assert.NoError(t, err, "NewGCPAuth failed unexpectedly") + + auth, err := createGCPAuth(config) + assert.NoError(t, err, "createGCPAuth failed unexpectedly") + + assert.Equal(t, expectedAuthMethod, auth.delegate) +} diff --git a/internal/app/vault_raft_snapshot_agent/vault/auth/kubernetes.go b/internal/app/vault_raft_snapshot_agent/vault/auth/kubernetes.go index 619aef1..f241f86 100644 --- a/internal/app/vault_raft_snapshot_agent/vault/auth/kubernetes.go +++ b/internal/app/vault_raft_snapshot_agent/vault/auth/kubernetes.go @@ -1,31 +1,27 @@ package auth import ( - "fmt" - "os" + "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/config" + "github.com/hashicorp/vault/api/auth/kubernetes" ) type KubernetesAuthConfig struct { - Path string `default:"kubernetes"` - Role string `validate:"required_if=Empty false"` - JWTPath string `default:"/var/run/secrets/kubernetes.io/serviceaccount/token" validate:"omitempty,file,required_if=Empty false"` + Path string `default:"kubernetes"` + Role string `validate:"required_if=Empty false"` + JWTPath config.Path `default:"/var/run/secrets/kubernetes.io/serviceaccount/token" validate:"omitempty,file,required_if=Empty false"` Empty bool } -func createKubernetesAuth(config KubernetesAuthConfig) authBackend { - return authBackend{ - name: "Kubernetes", - path: config.Path, - credentialsFactory: func() (map[string]interface{}, error) { - jwt, err := os.ReadFile(config.JWTPath) - if err != nil { - return map[string]interface{}{}, fmt.Errorf("unable to read jwt from %s: %s", config.JWTPath, err) - } +func createKubernetesAuth(config KubernetesAuthConfig) (authMethod, error) { + var loginOpts = []kubernetes.LoginOption{ + kubernetes.WithMountPath(config.Path), + kubernetes.WithServiceAccountTokenPath(string(config.JWTPath)), + } - return map[string]interface{}{ - "role": config.Role, - "jwt": string(jwt), - }, nil - }, + auth, err := kubernetes.NewKubernetesAuth(config.Role, loginOpts...) + if err != nil { + return authMethod{}, err } + + return authMethod{auth}, nil } diff --git a/internal/app/vault_raft_snapshot_agent/vault/auth/kubernetes_test.go b/internal/app/vault_raft_snapshot_agent/vault/auth/kubernetes_test.go index 6ae9e21..02131fe 100644 --- a/internal/app/vault_raft_snapshot_agent/vault/auth/kubernetes_test.go +++ b/internal/app/vault_raft_snapshot_agent/vault/auth/kubernetes_test.go @@ -1,109 +1,37 @@ package auth import ( - "bufio" - "errors" - "io" - "os" - "path/filepath" + "fmt" "testing" - "time" + + "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/config" + "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/test" + + "github.com/hashicorp/vault/api/auth/kubernetes" "github.com/stretchr/testify/assert" ) func TestCreateKubernetesAuth(t *testing.T) { - authPath := "test" - jwtPath := os.TempDir() + "/kubernetes" - expectedRole := "testRole" - expectedJwt, createdFile := createJwtFile(t, jwtPath, "testSecret") - if createdFile { - defer os.Remove(jwtPath) - } - + jwtPath := fmt.Sprintf("%s/jwt", t.TempDir()) config := KubernetesAuthConfig{ - Path: authPath, - Role: expectedRole, - JWTPath: jwtPath, - } - - authApiStub := kubernetesVaultAuthApiStub{} - - auth := createKubernetesAuth(config) - _, err := auth.Refresh(&authApiStub) - - assert.NoError(t, err, "auth-refresh failed unexpectedly") - assertKubernetesAuthValues(t, authPath, expectedRole, expectedJwt, auth, authApiStub) -} - -func TestCreateKubernetesAuthWithMissingJwtPath(t *testing.T) { - authPath := "test" - customJwtPath := "./does/not/exist" - expectedRole := "testRole" - - config := KubernetesAuthConfig{ - Path: authPath, - Role: expectedRole, - JWTPath: customJwtPath, - } - - authApiStub := kubernetesVaultAuthApiStub{} - - auth := createKubernetesAuth(config) - - _, err := auth.Refresh(&authApiStub) - assert.Errorf(t, err, "kubernetes auth refresh does not fail when jwt-file is missing") -} - -func assertKubernetesAuthValues(t *testing.T, expectedAuthPath string, expectedRole string, expectedJwt string, auth authBackend, api kubernetesVaultAuthApiStub) { - assert.Equal(t, "Kubernetes", auth.name) - assert.Equal(t, expectedAuthPath, api.path) - assert.Equal(t, expectedRole, api.role) - assert.Equal(t, expectedJwt, api.jwt) -} - -type kubernetesVaultAuthApiStub struct { - path string - role string - jwt string -} - -func (stub *kubernetesVaultAuthApiStub) LoginToBackend(path string, credentials map[string]interface{}) (leaseDuration time.Duration, err error) { - stub.path = path - stub.role = credentials["role"].(string) - stub.jwt = credentials["jwt"].(string) - return 0, nil -} - -func (stub *kubernetesVaultAuthApiStub) LoginWithToken(token string) (leaseDuration time.Duration, err error) { - return 0, errors.New("not implemented") -} - -func createJwtFile(t *testing.T, path string, defaultJwt string) (jwt string, created bool) { - t.Helper() - - if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil && !errors.Is(err, os.ErrExist) { - t.Fatalf("could not create directorys for jwt-file %s: %v", path, err) + Role: "test-role", + JWTPath: config.Path(jwtPath), + Path: "test-path", } - file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0666) - if err != nil { - t.Fatalf("could not create jwt-file %s: %v", path, err) - } + err := test.WriteFile(t, jwtPath, "test") + assert.NoError(t, err, "could not write jwt-file") - defer file.Close() + expectedAuthMethod, err := kubernetes.NewKubernetesAuth( + config.Role, + kubernetes.WithMountPath(config.Path), + kubernetes.WithServiceAccountTokenPath(string(config.JWTPath)), + ) + assert.NoError(t, err, "NewKubernetesAuth failed unexpectedly") - content, err := io.ReadAll(bufio.NewReader(file)) - if err != nil { - t.Fatalf("could not read jwt-file %s: %v", path, err) - } + auth, err := createKubernetesAuth(config) + assert.NoError(t, err, "createKubernetesAuth failed unexpectedly") - if len(content) > 0 { - return string(content), false - } else { - if _, err := file.Write([]byte(defaultJwt)); err != nil { - t.Fatalf("could not write expected secret to %s: %v", path, err) - } - return defaultJwt, true - } + assert.Equal(t, expectedAuthMethod, auth.delegate) } diff --git a/internal/app/vault_raft_snapshot_agent/vault/auth/ldap.go b/internal/app/vault_raft_snapshot_agent/vault/auth/ldap.go new file mode 100644 index 0000000..80244f0 --- /dev/null +++ b/internal/app/vault_raft_snapshot_agent/vault/auth/ldap.go @@ -0,0 +1,26 @@ +package auth + +import ( + "github.com/hashicorp/vault/api/auth/ldap" +) + +type LDAPAuthConfig struct { + Path string `default:"ldap"` + Username string `validate:"required_if=Empty false"` + Password string `validate:"required_if=Empty false"` + Empty bool +} + +func createLDAPAuth(config LDAPAuthConfig) (authMethod, error) { + auth, err := ldap.NewLDAPAuth( + config.Username, + &ldap.Password{FromString: config.Password}, + ldap.WithMountPath(config.Path), + ) + + if err != nil { + return authMethod{}, err + } + + return authMethod{auth}, nil +} diff --git a/internal/app/vault_raft_snapshot_agent/vault/auth/ldap_test.go b/internal/app/vault_raft_snapshot_agent/vault/auth/ldap_test.go new file mode 100644 index 0000000..82442f1 --- /dev/null +++ b/internal/app/vault_raft_snapshot_agent/vault/auth/ldap_test.go @@ -0,0 +1,29 @@ +package auth + +import ( + "testing" + + "github.com/hashicorp/vault/api/auth/ldap" + + "github.com/stretchr/testify/assert" +) + +func TestCreateLDAPAuth(t *testing.T) { + config := LDAPAuthConfig{ + Username: "test-user", + Password: "test-password", + Path: "test-path", + } + + expectedAuthMethod, err := ldap.NewLDAPAuth( + config.Username, + &ldap.Password{FromString: config.Password}, + ldap.WithMountPath(config.Path), + ) + assert.NoError(t, err, "NewLDAPAuth failed unexpectedly") + + auth, err := createLDAPAuth(config) + assert.NoError(t, err, "createLDAPAuth failed unexpectedly") + + assert.Equal(t, expectedAuthMethod, auth.delegate) +} diff --git a/internal/app/vault_raft_snapshot_agent/vault/auth/token.go b/internal/app/vault_raft_snapshot_agent/vault/auth/token.go index 6d5ec48..2b175d4 100644 --- a/internal/app/vault_raft_snapshot_agent/vault/auth/token.go +++ b/internal/app/vault_raft_snapshot_agent/vault/auth/token.go @@ -1,25 +1,62 @@ package auth -import ( +import( + "context" + "encoding/json" "fmt" "time" + + "github.com/hashicorp/vault/api" ) type tokenAuth struct { token string } +type tokenAuthAPI interface { + SetToken(token string) + LookupToken() (*api.Secret, error) + ClearToken() +} + func createTokenAuth(token string) tokenAuth { - return tokenAuth{ - token, - } + return tokenAuth{token} } -func (a tokenAuth) Refresh(api VaultAuthAPI) (time.Time, error) { - leaseDuration, err := api.LoginWithToken(a.token) +func (auth tokenAuth) Login(ctx context.Context, client *api.Client) (time.Duration, error) { + return auth.login(tokenAuthImpl{client}) +} + +func (auth tokenAuth) login(authAPI tokenAuthAPI) (time.Duration, error) { + authAPI.SetToken(auth.token) + info, err := authAPI.LookupToken() + if err != nil { + authAPI.ClearToken() + return 0, err + } + + ttl, err := info.Data["ttl"].(json.Number).Int64() if err != nil { - return time.Now(), fmt.Errorf("error logging in with token: %s", err) + authAPI.ClearToken() + return 0, fmt.Errorf("error converting ttl to int: %s", err) } - return time.Now().Add((time.Second * leaseDuration) / 2), nil + return time.Duration(ttl), nil + +} + +type tokenAuthImpl struct { + client *api.Client +} + +func (impl tokenAuthImpl) SetToken(token string) { + impl.client.SetToken(token) +} + +func (impl tokenAuthImpl) LookupToken() (*api.Secret, error) { + return impl.client.Auth().Token().LookupSelf() } + +func (impl tokenAuthImpl) ClearToken() { + impl.client.ClearToken() +} \ No newline at end of file diff --git a/internal/app/vault_raft_snapshot_agent/vault/auth/token_test.go b/internal/app/vault_raft_snapshot_agent/vault/auth/token_test.go index 0067eb3..29a96be 100644 --- a/internal/app/vault_raft_snapshot_agent/vault/auth/token_test.go +++ b/internal/app/vault_raft_snapshot_agent/vault/auth/token_test.go @@ -2,9 +2,12 @@ package auth import ( "errors" + "encoding/json" + "fmt" "testing" "time" + "github.com/hashicorp/vault/api" "github.com/stretchr/testify/assert" ) @@ -14,7 +17,7 @@ func TestCreateTokenAuth(t *testing.T) { auth := createTokenAuth(expectedToken) - _, err := auth.Refresh(&authApiStub) + _, err := auth.login(&authApiStub) assert.NoError(t, err, "token-auth failed unexpectedly") assert.Equal(t, expectedToken, authApiStub.token) @@ -24,38 +27,46 @@ func TestTokenAuthFailsIfLoginFails(t *testing.T) { authApiStub := tokenVaultAuthApiStub{loginFails: true} auth := createTokenAuth("test") - _, err := auth.Refresh(&authApiStub) + _, err := auth.login(&authApiStub) assert.Error(t, err, "token-auth did not report error although login failed!") } func TestTokenAuthReturnsExpirationBasedOnLoginLeaseDuration(t *testing.T) { - authApiStub := tokenVaultAuthApiStub{leaseDuration: time.Minute} + authApiStub := tokenVaultAuthApiStub{leaseDuration: 60} auth := createTokenAuth("test") - expiration, err := auth.Refresh(&authApiStub) + leaseDuration, err := auth.login(&authApiStub) assert.NoErrorf(t, err, "token-auth failed unexpectedly") - expectedExpiration := time.Now().Add((time.Second * authApiStub.leaseDuration) / 2) - assert.WithinDuration(t, expectedExpiration, expiration, time.Millisecond) + expectedDuration := time.Duration(authApiStub.leaseDuration) + assert.Equal(t, expectedDuration, leaseDuration, time.Millisecond) } type tokenVaultAuthApiStub struct { token string loginFails bool - leaseDuration time.Duration + leaseDuration int64 } -func (stub *tokenVaultAuthApiStub) LoginToBackend(path string, credentials map[string]interface{}) (leaseDuration time.Duration, err error) { - return 0, errors.New("not implemented") +func (stub *tokenVaultAuthApiStub) SetToken(token string) { + stub.token = token } -func (stub *tokenVaultAuthApiStub) LoginWithToken(token string) (leaseDuration time.Duration, err error) { - stub.token = token +func (stub *tokenVaultAuthApiStub) ClearToken() { + stub.token = "" +} + +func (stub *tokenVaultAuthApiStub) LookupToken() (*api.Secret, error) { if stub.loginFails { - return 0, errors.New("login failed") + return &api.Secret{}, errors.New("lookup failed") } - return stub.leaseDuration, nil + + return &api.Secret{ + Data: map[string]interface{}{ + "ttl": json.Number(fmt.Sprint(stub.leaseDuration)), + }, + }, nil } diff --git a/internal/app/vault_raft_snapshot_agent/vault/auth/userpass.go b/internal/app/vault_raft_snapshot_agent/vault/auth/userpass.go new file mode 100644 index 0000000..b771016 --- /dev/null +++ b/internal/app/vault_raft_snapshot_agent/vault/auth/userpass.go @@ -0,0 +1,26 @@ +package auth + +import ( + "github.com/hashicorp/vault/api/auth/userpass" +) + +type UserPassAuthConfig struct { + Path string `default:"userpass"` + Username string `validate:"required_if=Empty false"` + Password string `validate:"required_if=Empty false"` + Empty bool +} + +func createUserPassAuth(config UserPassAuthConfig) (authMethod, error) { + auth, err := userpass.NewUserpassAuth( + config.Username, + &userpass.Password{FromString: config.Password}, + userpass.WithMountPath(config.Path), + ) + + if err != nil { + return authMethod{}, err + } + + return authMethod{auth}, nil +} diff --git a/internal/app/vault_raft_snapshot_agent/vault/auth/userpass_test.go b/internal/app/vault_raft_snapshot_agent/vault/auth/userpass_test.go new file mode 100644 index 0000000..4ee77a0 --- /dev/null +++ b/internal/app/vault_raft_snapshot_agent/vault/auth/userpass_test.go @@ -0,0 +1,29 @@ +package auth + +import ( + "testing" + + "github.com/hashicorp/vault/api/auth/userpass" + + "github.com/stretchr/testify/assert" +) + +func TestCreateUserpassAuth(t *testing.T) { + config := UserPassAuthConfig{ + Username: "test-user", + Password: "test-password", + Path: "test-path", + } + + expectedAuthMethod, err := userpass.NewUserpassAuth( + config.Username, + &userpass.Password{FromString: config.Password}, + userpass.WithMountPath(config.Path), + ) + assert.NoError(t, err, "NewUserPassAuth failed unexpectedly") + + auth, err := createUserPassAuth(config) + assert.NoError(t, err, "createUserpassAuth failed unexpectedly") + + assert.Equal(t, expectedAuthMethod, auth.delegate) +} diff --git a/internal/app/vault_raft_snapshot_agent/vault/client.go b/internal/app/vault_raft_snapshot_agent/vault/client.go index 645c341..792c2d6 100644 --- a/internal/app/vault_raft_snapshot_agent/vault/client.go +++ b/internal/app/vault_raft_snapshot_agent/vault/client.go @@ -7,65 +7,132 @@ import ( "time" "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/vault/auth" + "github.com/hashicorp/vault/api" ) type VaultClientConfig struct { - Url string `default:"http://127.0.0.1:8200" validate:"required,http_url"` - Timeout time.Duration `default:"60s"` + Url string `default:"http://127.0.0.1:8200" validate:"required,http_url"` + Timeout time.Duration `default:"60s"` Insecure bool Auth auth.AuthConfig } -type VaultClientAPI interface { +// public implementation of the client communicating with vault +// to authenticate and take snapshots +type VaultClient[C any, A clientVaultAPIAuth[C]] struct { + api clientVaultAPI[C, A] + auth A + authExpiration time.Time +} + +// internal definition of vault-api used by VaultClient +type clientVaultAPI[C any, A clientVaultAPIAuth[C]] interface { + Address() string TakeSnapshot(ctx context.Context, writer io.Writer) error IsLeader() (bool, error) - AuthAPI() auth.VaultAuthAPI + RefreshAuth(ctx context.Context, auth A) (time.Duration, error) +} + +// internal definition of vault-api used for authentication +type clientVaultAPIAuth[C any] interface { + Login(ctx context.Context, client C) (time.Duration, error) } -type VaultClient struct { - Url string - api VaultClientAPI - auth auth.Auth - tokenExpiration time.Time +// internal implementation of the vault-api using a real vault-api-client +type clientVaultAPIImpl struct { + client *api.Client } -func CreateClient(config VaultClientConfig) (*VaultClient, error) { - api, err := newVaultAPIImpl(config.Url, config.Insecure, config.Timeout) +// creates a VaultClient using an api-implementation delegation to a real vault-api-client +func CreateVaultClient(config VaultClientConfig) (*VaultClient[*api.Client, clientVaultAPIAuth[*api.Client]], error) { + impl, err := newClientVaultAPIImpl(config.Url, config.Insecure, config.Timeout) + if err != nil { + return nil, err + } + + auth, err := auth.CreateVaultAuth(config.Auth) if err != nil { return nil, err } - return NewClient(config.Url, api, auth.CreateAuth(config.Auth)), nil + return NewVaultClient[*api.Client, clientVaultAPIAuth[*api.Client]](impl, auth, time.Time{}), nil } -func NewClient(address string, api VaultClientAPI, auth auth.Auth) *VaultClient { - return &VaultClient{address, api, auth, time.Time{}} +// creates a VaultClient using the given api-implementation and auth +// this function should only be used in tests! +func NewVaultClient[C any, A clientVaultAPIAuth[C]](api clientVaultAPI[C, A], auth A, tokenExpiration time.Time) *VaultClient[C, A] { + return &VaultClient[C, A]{api, auth, tokenExpiration} } -func (c *VaultClient) TakeSnapshot(ctx context.Context, writer io.Writer) error { - if err := c.refreshAuth(); err != nil { +func (c *VaultClient[C, A]) TakeSnapshot(ctx context.Context, writer io.Writer) error { + if err := c.refreshAuth(ctx); err != nil { return err } leader, err := c.api.IsLeader() if err != nil { - return fmt.Errorf("unable to determine leader status for %s: %v", c.Url, err) + return fmt.Errorf("unable to determine leader status for %s: %v", c.api.Address(), err) } if !leader { - return fmt.Errorf("%s is not vault-leader-node", c.Url) + return fmt.Errorf("%s is not vault-leader-node", c.api.Address()) } return c.api.TakeSnapshot(ctx, writer) } -func (c *VaultClient) refreshAuth() error { - if c.auth != nil && c.tokenExpiration.Before(time.Now()) { - tokenExpiration, err := c.auth.Refresh(c.api.AuthAPI()) +func (c *VaultClient[C, A]) refreshAuth(ctx context.Context) error { + if c.authExpiration.Before(time.Now()) { + leaseDuration, err := c.api.RefreshAuth(ctx, c.auth) if err != nil { return fmt.Errorf("could not refresh auth: %s", err) } - c.tokenExpiration = tokenExpiration + c.authExpiration = time.Now().Add(leaseDuration / 2) } return nil } + +// creates a api-implementation using a real vault-api-client +func newClientVaultAPIImpl(address string, insecure bool, timeout time.Duration) (clientVaultAPIImpl, error) { + apiConfig := api.DefaultConfig() + apiConfig.Address = address + apiConfig.HttpClient.Timeout = timeout + + tlsConfig := &api.TLSConfig{ + Insecure: insecure, + } + + if err := apiConfig.ConfigureTLS(tlsConfig); err != nil { + return clientVaultAPIImpl{}, err + } + + client, err := api.NewClient(apiConfig) + if err != nil { + return clientVaultAPIImpl{}, err + } + + return clientVaultAPIImpl{ + client, + }, nil +} + +func (impl clientVaultAPIImpl) Address() string { + return impl.client.Address() +} + +func (impl clientVaultAPIImpl) TakeSnapshot(ctx context.Context, writer io.Writer) error { + return impl.client.Sys().RaftSnapshotWithContext(ctx, writer) +} + +func (impl clientVaultAPIImpl) IsLeader() (bool, error) { + leader, err := impl.client.Sys().Leader() + if err != nil { + return false, err + } + + return leader.IsSelf, nil +} + +func (impl clientVaultAPIImpl) RefreshAuth(ctx context.Context, auth clientVaultAPIAuth[*api.Client]) (time.Duration, error) { + return auth.Login(ctx, impl.client) +} diff --git a/internal/app/vault_raft_snapshot_agent/vault/client_test.go b/internal/app/vault_raft_snapshot_agent/vault/client_test.go index 2bb54e2..c643347 100644 --- a/internal/app/vault_raft_snapshot_agent/vault/client_test.go +++ b/internal/app/vault_raft_snapshot_agent/vault/client_test.go @@ -9,22 +9,21 @@ import ( "testing" "time" - "github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/vault/auth" "github.com/stretchr/testify/assert" ) func TestClientRefreshesAuthAfterTokenExpires(t *testing.T) { - auth := &authStub{ - tokenExpiration: time.Now(), + auth := &clientVaultAPIAuthStub{ + leaseDuration: time.Minute, } - client := VaultClient{ - api: &clientAPIStub{ + client := NewVaultClient[any, *clientVaultAPIAuthStub]( + &clientVaultAPIStub{ leader: true, }, - auth: auth, - tokenExpiration: time.Now().Add(time.Second * 1), - } + auth, + time.Now().Add(time.Second*1), + ) _ = client.TakeSnapshot(context.Background(), bufio.NewWriter(&bytes.Buffer{})) @@ -38,33 +37,34 @@ func TestClientRefreshesAuthAfterTokenExpires(t *testing.T) { } func TestClientDoesNotTakeSnapshotIfAuthRefreshFails(t *testing.T) { - authStub := &authStub{} - clientApi := &clientAPIStub{ + authStub := &clientVaultAPIAuthStub{} + clientApi := &clientVaultAPIStub{ leader: true, } - client := VaultClient{ - api: clientApi, - auth: authStub, - tokenExpiration: time.Now().Add(time.Second * -1), - } + initalAuthExpiration := time.Now().Add(time.Second * -1) + client := NewVaultClient[any, *clientVaultAPIAuthStub]( + clientApi, + authStub, + initalAuthExpiration, + ) err := client.TakeSnapshot(context.Background(), bufio.NewWriter(&bytes.Buffer{})) assert.Error(t, err, "TakeSnapshot() returned no error although auth-refresh failed") - assert.NotEqual(t, authStub.tokenExpiration, client.tokenExpiration, "TakeSnapshot() refreshed token-expiration although auth-refresh failed") + assert.Equal(t, initalAuthExpiration, client.authExpiration, "TakeSnapshot() refreshed auth-expiration although auth-refresh failed") assert.False(t, clientApi.snapshotTaken, "TakeSnapshot() took snapshot although aut-refresh failed") } func TestClientOnlyTakesSnaphotWhenLeader(t *testing.T) { - clientApi := &clientAPIStub{ + clientApi := &clientVaultAPIStub{ leader: false, } - client := VaultClient{ - api: clientApi, - auth: nil, - tokenExpiration: time.Now(), - } + client := NewVaultClient[any, *clientVaultAPIAuthStub]( + clientApi, + &clientVaultAPIAuthStub{}, + time.Now().Add(time.Minute), + ) ctx := context.Background() writer := bufio.NewWriter(&bytes.Buffer{}) @@ -84,26 +84,26 @@ func TestClientOnlyTakesSnaphotWhenLeader(t *testing.T) { } func TestClientDoesNotTakeSnapshotIfLeaderCheckFails(t *testing.T) { - authStub := &authStub{} - api := &clientAPIStub{ + authStub := &clientVaultAPIAuthStub{} + api := &clientVaultAPIStub{ sysLeaderFails: true, leader: true, } - client := VaultClient{ - api: api, - auth: nil, - tokenExpiration: time.Now(), - } + client := NewVaultClient[any, *clientVaultAPIAuthStub]( + api, + authStub, + time.Now(), + ) err := client.TakeSnapshot(context.Background(), bufio.NewWriter(&bytes.Buffer{})) assert.Error(t, err, "TakeSnapshot() reported success or returned no error when leader-check failed") assert.False(t, api.snapshotTaken, "TakeSnapshot() took snapshot when leader-check failed") - assert.NotEqual(t, authStub.tokenExpiration, client.tokenExpiration) + assert.NotEqual(t, authStub.leaseDuration, client.authExpiration) } -func assertAuthRefresh(t *testing.T, refreshed bool, client VaultClient, auth *authStub) { +func assertAuthRefresh(t *testing.T, refreshed bool, client *VaultClient[any, *clientVaultAPIAuthStub], auth *clientVaultAPIAuthStub) { t.Helper() if auth.refreshed != refreshed { @@ -115,26 +115,12 @@ func assertAuthRefresh(t *testing.T, refreshed bool, client VaultClient, auth *a } } - if refreshed && client.tokenExpiration != auth.tokenExpiration { - t.Fatalf("client did not accept tokenExpiration from auth! client: %v, auth: %v", client.tokenExpiration, auth.tokenExpiration) - } -} - -type authStub struct { - tokenExpiration time.Time - refreshed bool -} - -func (a *authStub) Refresh(api auth.VaultAuthAPI) (time.Time, error) { - a.refreshed = true - var err error - if a.tokenExpiration.IsZero() { - err = errors.New("refresh of auth failed") + if refreshed { + assert.WithinDuration(t, time.Now().Add(auth.leaseDuration/2), client.authExpiration, time.Second, "client did not refresh auth-expiration!") } - return a.tokenExpiration, err } -type clientAPIStub struct { +type clientVaultAPIStub struct { leader bool sysLeaderFails bool snapshotTaken bool @@ -142,14 +128,18 @@ type clientAPIStub struct { snapshotWriter io.Writer } -func (stub *clientAPIStub) TakeSnapshot(ctx context.Context, writer io.Writer) error { +func (stub *clientVaultAPIStub) Address() string { + return "test" +} + +func (stub *clientVaultAPIStub) TakeSnapshot(ctx context.Context, writer io.Writer) error { stub.snapshotTaken = true stub.snapshotContext = ctx stub.snapshotWriter = writer return nil } -func (stub *clientAPIStub) IsLeader() (bool, error) { +func (stub *clientVaultAPIStub) IsLeader() (bool, error) { if stub.sysLeaderFails { return false, errors.New("leader-Check failed") } @@ -157,6 +147,20 @@ func (stub *clientAPIStub) IsLeader() (bool, error) { return stub.leader, nil } -func (stub *clientAPIStub) AuthAPI() auth.VaultAuthAPI { - return nil +func (stub *clientVaultAPIStub) RefreshAuth(ctx context.Context, auth *clientVaultAPIAuthStub) (time.Duration, error) { + return auth.Login(ctx, nil) +} + +type clientVaultAPIAuthStub struct { + leaseDuration time.Duration + refreshed bool +} + +func (a *clientVaultAPIAuthStub) Login(ctx context.Context, api any) (time.Duration, error) { + a.refreshed = true + var err error + if a.leaseDuration <= 0 { + err = errors.New("refresh of auth failed") + } + return a.leaseDuration, err } diff --git a/testdata/complete.yaml b/testdata/complete.yaml index 711730b..72ab197 100644 --- a/testdata/complete.yaml +++ b/testdata/complete.yaml @@ -3,10 +3,37 @@ vault: insecure: true timeout: 5m auth: + approle: + role: "test-approle" + secret: "test-approle-secret" + path: "test-approle-path" + aws: + role: "test-aws-role" + region: "test-region" + ec2nonce: "test-nonce" + ec2signaturetype: "rsa2048" + path: "test-aws-path" + azure: + role: "test-azure-role" + resource: "test-resource" + path: "test-azure-path" + gcp: + role: "test-gcp-role" + serviceAccountEmail: "test@example.com" + path: "test-gcp-path" kubernetes: - role: "test-role" - path: "test-auth" + role: "test-kubernetes-role" + path: "test-kubernetes-path" jwtPath: "./jwt" + ldap: + username: "test-ldap-user" + password: "test-ldap-pass" + path: "test-ldap-path" + token: "test-token" + userpass: + username: "test-user" + password: "test-pass" + path: "test-userpass-path" snapshots: frequency: "2h" retain: 10 diff --git a/testdata/invalid-auth.yaml b/testdata/invalid-auth.yaml index 2976991..c2bbba7 100644 --- a/testdata/invalid-auth.yaml +++ b/testdata/invalid-auth.yaml @@ -1,7 +1,7 @@ vault: auth: approle: - id: "test" + role: "test" snapshots: frequency: "1h" uploaders: