├── .github ├── dependabot.yml └── workflows │ ├── pr-check.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── backend.go ├── backend_test.go ├── boundary_account.go ├── client.go ├── cmd └── vault-plugin-secrets-boundary │ └── main.go ├── go.mod ├── go.sum ├── path_config.go ├── path_config_test.go ├── path_credentials.go ├── path_credentials_test.go ├── path_roles.go └── path_roles_test.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/pr-check.yml: -------------------------------------------------------------------------------- 1 | name: plugin-test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | test: 12 | name: Build & Run Tests 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: ^1.13 20 | 21 | - name: Check out code into the Go module directory 22 | uses: actions/checkout@v2 23 | 24 | - name: Get dependencies 25 | run: | 26 | go get -v -t -d ./... 27 | if [ -f Gopkg.toml ]; then 28 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 29 | dep ensure 30 | fi 31 | 32 | - name: Build 33 | run: make build 34 | 35 | - name: Test 36 | run: make test 37 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: plugin-release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | branches: 8 | - main 9 | permissions: 10 | contents: write 11 | jobs: 12 | build: 13 | name: Build 14 | runs-on: ubuntu-latest 15 | steps: 16 | 17 | - name: Set up Go 1.x 18 | uses: actions/setup-go@v2 19 | with: 20 | go-version: ^1.18 21 | 22 | - name: Check out code into the Go module directory 23 | uses: actions/checkout@v2 24 | 25 | - name: Fetch all tags 26 | run: git fetch --force --tags 27 | 28 | - name: Get dependencies 29 | run: | 30 | go get -v -t -d ./... 31 | if [ -f Gopkg.toml ]; then 32 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 33 | dep ensure 34 | fi 35 | 36 | - name: Build 37 | run: make build 38 | 39 | goreleaser: 40 | runs-on: ubuntu-latest 41 | 42 | steps: 43 | - 44 | name: Checkout 45 | uses: actions/checkout@v2 46 | with: 47 | fetch-depth: 0 48 | - 49 | name: Set up Go 50 | uses: actions/setup-go@v2 51 | with: 52 | go-version: 1.18 53 | - 54 | name: Run GoReleaser 55 | uses: goreleaser/goreleaser-action@v2 56 | with: 57 | distribution: goreleaser 58 | version: latest 59 | args: release --rm-dist 60 | workdir: ./cmd/vault-plugin-secrets-boundary 61 | env: 62 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vault/plugins/* 2 | .vscode 3 | .idea 4 | /bin/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 10 | 11 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 12 | 13 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 14 | 15 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 16 | 17 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 18 | 19 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 20 | 21 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 22 | 23 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 24 | 25 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 26 | 27 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 28 | 29 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 30 | 31 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 32 | 33 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 34 | 35 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 36 | You must cause any modified files to carry prominent notices stating that You changed the files; and 37 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 38 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 39 | 40 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 41 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 42 | 43 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 44 | 45 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 46 | 47 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 48 | 49 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 50 | 51 | END OF TERMS AND CONDITIONS 52 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | name = boundary 2 | plugin_type = secrets-engine 3 | version = 1.0.2 4 | 5 | build: 6 | go build -o vault/plugins/boundary cmd/vault-plugin-secrets-boundary/main.go 7 | test: 8 | go test -v 9 | multi_build: 10 | @echo "" 11 | @echo "Compile Provider" 12 | 13 | # Clear the output 14 | rm -rf ./bin 15 | 16 | GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o ./bin/linux_arm64/vault-plugin-$(name)-$(plugin_type)_v$(version) cmd/vault-plugin-secrets-$(name)/main.go 17 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o ./bin/linux_amd64/vault-plugin-$(name)-$(plugin_type)_v$(version) cmd/vault-plugin-secrets-$(name)/main.go 18 | GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -o ./bin/darwin_arm64/vault-plugin-$(name)-$(plugin_type)_v$(version) cmd/vault-plugin-secrets-$(name)/main.go 19 | GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -o ./bin/darwin_amd64/vault-plugin-$(name)-$(plugin_type)_v$(version) cmd/vault-plugin-secrets-$(name)/main.go 20 | GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o ./bin/windows_amd64/vault-plugin-$(name)-$(plugin_type)_v$(version).exe cmd/vault-plugin-secrets-$(name)/main.go 21 | GOOS=windows GOARCH=386 CGO_ENABLED=0 go build -o ./bin/windows_386/vault-plugin-$(name)-$(plugin_type)_v$(version).exe cmd/vault-plugin-secrets-$(name)/main.go 22 | zip: 23 | pwd 24 | zip -j ./bin/vault-plugin-$(name)-$(plugin_type)_v$(version)_linux_arm64.zip ./bin/linux_arm64/vault-plugin-$(name)-$(plugin_type)_v$(version) 25 | zip -j ./bin/vault-plugin-$(name)-$(plugin_type)_v$(version)_linux_amd64.zip ./bin/linux_amd64/vault-plugin-$(name)-$(plugin_type)_v$(version) 26 | zip -j ./bin/vault-plugin-$(name)-$(plugin_type)_v$(version)_darwin_arm64.zip ./bin/linux_arm64/vault-plugin-$(name)-$(plugin_type)_v$(version) 27 | zip -j ./bin/vault-plugin-$(name)-$(plugin_type)_v$(version)_darwin_amd64.zip ./bin/linux_arm64/vault-plugin-$(name)-$(plugin_type)_v$(version) 28 | zip -j ./bin/vault-plugin-$(name)-$(plugin_type)_v$(version)_windows_amd64.zip ./bin/windows_amd64/vault-plugin-$(name)-$(plugin_type)_v$(version).exe 29 | zip -j ./bin/vault-plugin-$(name)-$(plugin_type)_v$(version)_windows_386.zip ./bin/windows_386/vault-plugin-$(name)-$(plugin_type)_v$(version).exe 30 | ls -lha ./bin 31 | shasum: 32 | cd bin/; shasum -a 256 *.zip > vault-plugin-$(name)-$(plugin_type)_v$(version)_SHA256SUMS 33 | gpg: 34 | gpg --detach-sign ./bin/vault-plugin-$(name)-$(plugin_type)_v$(version)_SHA256SUMS 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Boundary Secrets Engine for HashiCorp Vault 2 | 3 | The Boundary secrets engine creates Boundary Workers, and additionally, generates user and account credentials dynamically based on configured permissions and scopes. This means that services that need to access a Boundary scope no longer need to hardcode credentials and Boundary workers can be ephemeral. 4 | 5 | With every service accessing Boundary with unique credentials, auditing is much easier in threat modelled scenarios. 6 | 7 | Vault makes use both of its own internal revocation system to delete Boundary users and accounts when generating Boundary credentials to ensure that users and accounts become invalid within a reasonable time of the lease expiring. 8 | 9 | Additionally, Vault can remove workers that it has created, thereby removing the controller led auth token 10 | 11 | ## Setup for User credentials 12 | 13 | Most secrets engines must be configured in advance before they can perform their functions. These steps are usually completed by an operator or configuration management tool. 14 | 15 | 16 | 1. Enable secrets engine: 17 | 18 | 19 | ```shell 20 | vault secrets enable boundary 21 | ``` 22 | 23 | By default, the secrets engine will mount at the name of the engine. To enable the secrets engine at a different path, use the -path argument. 24 | 25 | 26 | 2. Configure the credentials that Vault uses to communicate with Boundary to generate credentials: 27 | ```shell 28 | vault write boundary/config \ 29 | addr=http://localhost:9200 \ 30 | login_name=admin \ 31 | password=password \ 32 | auth_method_id=ampw_1234567890 33 | ``` 34 | It is important that the Vault user have the permissions to manage users and accounts at all scope levels. 35 | 36 | 3. Configure a role that maps a name in Vault to a Boundary scope and roles: 37 | 38 | ```shell 39 | vault write boundary/role/my-role \ 40 | ttl=180 \ 41 | max_ttl=360 \ 42 | auth_method_id=ampw_1234567890 \ 43 | boundary_roles=r_cwRmglckUr \ 44 | role_type=user \ 45 | scope_id=global 46 | 47 | ``` 48 | 49 | By writing to the roles/my-role path we are defining the my-role role. This role will be created by evaluating the given `auth_method_id`, `boundary_roles`, `scope_id`, `ttl` and `max_ttl` statements. Credentials generated against this role will be created at the specified scope, using the specified auth method, and will have the specified boundary roles assigned for the duration of the ttl specified. You can read more about [Boundary's Identity and Access Management domain.](https://www.hashicorp.com/blog/understanding-the-boundary-identity-and-access-management-model) 50 | 51 | ## Usage 52 | 53 | After the secrets engine is configured and a user/machine has a Vault token with the proper permission, it can generate credentials. 54 | 55 | 1. Generate a new credential by reading from the /creds endpoint with the name of the role: 56 | ```shell 57 | vault read boundary/creds/my-role 58 | ``` 59 | 60 | ### Worker auth tokens 61 | 62 | Configuring a worker role is slightly different to a user role. The example below shows a worker role being configured: 63 | 64 | ```shell 65 | vault write boundary/role/worker \ 66 | ttl=180 \ 67 | max_ttl=360 \ 68 | role_type=worker \ 69 | scope_id=global 70 | ``` 71 | 72 | A worker can then be generated using the following command: 73 | 74 | ```shell 75 | vault read boundary/creds/worker worker_name="local worker" 76 | ``` 77 | 78 | An optional description can be added to the worker using the `description` parameter. 79 | 80 | ```shell 81 | vault read boundary/creds/worker worker_name="local worker" description="Local worker for testing purposes" 82 | ``` 83 | 84 | ## API 85 | 86 | ### Setup 87 | 88 | 1. Enable secrets engine 89 | 90 | Sample request 91 | 92 | ```shell 93 | curl \ 94 | -X POST \ 95 | --header "X-Vault-Token: ..." \ 96 | http://127.0.0.1:8200/v1/sys/mounts 97 | ``` 98 | 99 | Sample payload 100 | 101 | ```json 102 | { 103 | "type": "boundary" 104 | } 105 | ``` 106 | 107 | 2. Configure the credentials that Vault uses to communicate with Boundary to generate credentials: 108 | 109 | Sample request 110 | ```shell 111 | curl \ 112 | -X POST \ 113 | --header "X-Vault-Token: ..." \ 114 | http://127.0.0.1:8200/v1/boundary/config 115 | ``` 116 | 117 | Sample payload 118 | ```json 119 | { 120 | "addr": "http://localhost:9200", 121 | "login_name": "vault-admin", 122 | "password": "...", 123 | "auth_method_id": "ampw_1234567890" 124 | } 125 | ``` 126 | 127 | 3. Configure a role that maps a name in Vault to a Boundary scope and roles: 128 | 129 | Sample request 130 | ```shell 131 | curl \ 132 | -X POST \ 133 | --header "X-Vault-Token: ..." \ 134 | http://127.0.0.1:8200/v1/boundary/role/my-role 135 | ``` 136 | 137 | Sample payload 138 | ```json 139 | { 140 | "ttl": 180, 141 | "max_ttl": 360, 142 | "auth_method_id": "ampw_1234567890", 143 | "credential_type": "userpass", 144 | "boundary_roles": "r_cbvEFZbN1S,r_r8mxdp7zOp", 145 | "role_type": "user", 146 | "scope_id": "global" 147 | } 148 | ``` 149 | 150 | ### Usage 151 | 152 | 1. Generate a new credential by reading from the /creds endpoint with the name of the role: 153 | 154 | Sample request 155 | ```shell 156 | curl \ 157 | -X GET \ 158 | --header "X-Vault-Token: ..." \ 159 | http://127.0.0.1:8200/v1/boundary/creds/my-role 160 | ``` 161 | 162 | Sample response 163 | ```json 164 | { 165 | "request_id": "ed281bc6-182d-a15e-d700-8c2e64897010", 166 | "lease_id": "boundary/creds/my-role/pH9CfQcAmE9va6CwQKOEPBsx", 167 | "renewable": true, 168 | "lease_duration": 180, 169 | "data": { 170 | "account_id": "acctpw_Haufl3nWxH", 171 | "auth_method_id": "ampw_1234567890", 172 | "boundary_roles": "r_CSuslu0w1X,r_S0OqRsecY6", 173 | "login_name": "vault-role-my-role-fudjntgy", 174 | "password": "2QW7U03mXr614895", 175 | "user_id": "u_sKom7Pxa1v" 176 | }, 177 | "wrap_info": null, 178 | "warnings": null, 179 | "auth": null 180 | } 181 | ``` 182 | 183 | ## Terraform 184 | 185 | ### Setup 186 | 187 | 1. Enable secrets engine: 188 | 189 | ```hcl 190 | resource "vault_mount" "boundary" { 191 | path = "boundary" 192 | type = "boundary" 193 | description = "This is the boundary secrets engine" 194 | } 195 | ``` 196 | 197 | 2. Configure the credentials that Vault uses to communicate with Boundary to generate credentials: 198 | 199 | ```hcl 200 | resource "vault_generic_endpoint" "boundary_config" { 201 | depends_on = [ 202 | vault_mount.boundary 203 | ] 204 | 205 | path = "boundary/config" 206 | ignore_absent_fields = true 207 | 208 | data_json = < 0 { 173 | // resp.Secret.TTL = roleEntry.TTL 174 | //} 175 | //if roleEntry.MaxTTL > 0 { 176 | // resp.Secret.MaxTTL = roleEntry.MaxTTL 177 | //} 178 | // 179 | //return resp, nil 180 | 181 | ttlRaw, ok := req.Secret.InternalData["ttl"] 182 | if !ok { 183 | return nil, fmt.Errorf("secret is missing ttl internal data") 184 | } 185 | maxTtlRaw, ok := req.Secret.InternalData["max_ttl"] 186 | if !ok { 187 | return nil, fmt.Errorf("secret is missing max_ttl internal data") 188 | } 189 | 190 | resp := &logical.Response{Secret: req.Secret} 191 | ttl := time.Duration(ttlRaw.(float64)) * time.Second 192 | maxTtl := time.Duration(maxTtlRaw.(float64)) * time.Second 193 | 194 | if ttl > 0 { 195 | resp.Secret.TTL = ttl 196 | } 197 | if maxTtl > 0 { 198 | resp.Secret.MaxTTL = maxTtl 199 | } 200 | 201 | return resp, nil 202 | } 203 | 204 | func (b *boundaryBackend) workerRenew(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 205 | ttlRaw, ok := req.Secret.InternalData["ttl"] 206 | if !ok { 207 | return nil, fmt.Errorf("secret is missing ttl internal data") 208 | } 209 | maxTtlRaw, ok := req.Secret.InternalData["max_ttl"] 210 | if !ok { 211 | return nil, fmt.Errorf("secret is missing max_ttl internal data") 212 | } 213 | 214 | resp := &logical.Response{Secret: req.Secret} 215 | ttl := time.Duration(ttlRaw.(float64)) * time.Second 216 | maxTtl := time.Duration(maxTtlRaw.(float64)) * time.Second 217 | 218 | if ttl > 0 { 219 | resp.Secret.TTL = ttl 220 | } 221 | if maxTtl > 0 { 222 | resp.Secret.MaxTTL = maxTtl 223 | } 224 | 225 | return resp, nil 226 | } 227 | 228 | // createToken calls the Boundary client and creates a new Boundary account 229 | func createAccount(ctx context.Context, c *boundaryClient, role string, authMethodID string, boundaryRoles string, scopeId string) (*boundaryAccount, error) { 230 | const op = "createAccount" 231 | if c.tokenIsExpired() { 232 | if err := c.authenticate(); err != nil { 233 | return nil, fmt.Errorf("%s: unable to authenticate client: %w", op, err) 234 | } 235 | } 236 | // Accounts client 237 | aClient := accounts.NewClient(c.Client) 238 | 239 | // Setting up the loginName using role_id + randomly generated string 240 | loginNamePostfix, err := password.Generate(8, 0, 0, true, false) 241 | if err != nil { 242 | log.Fatal(err) 243 | } 244 | loginName := `vault-role-` + role + `-` + loginNamePostfix 245 | 246 | var accountOpts []accounts.Option 247 | accountOpts = append(accountOpts, accounts.WithPasswordAccountLoginName(loginName)) 248 | accountOpts = append(accountOpts, accounts.WithName(loginName)) 249 | 250 | // Generating a password 251 | accountPassword, err := password.Generate(16, 10, 0, false, false) 252 | if err != nil { 253 | log.Fatal(err) 254 | } 255 | 256 | accountOpts = append(accountOpts, accounts.WithPasswordAccountPassword(accountPassword)) 257 | 258 | // Creating an account 259 | acr, err := aClient.Create(ctx, authMethodID, accountOpts...) 260 | if err != nil { 261 | 262 | return nil, err 263 | } 264 | 265 | uclient := users.NewClient(c.Client) 266 | 267 | var userOpts []users.Option 268 | 269 | userOpts = append(userOpts, users.WithName(loginName)) 270 | 271 | ucr, err := uclient.Create(ctx, scopeId, userOpts...) 272 | if err != nil { 273 | 274 | return nil, err 275 | } 276 | var accountList []string 277 | accountList = append(accountList, acr.Item.Id) 278 | _, err = uclient.AddAccounts(ctx, ucr.Item.Id, ucr.Item.Version, accountList) 279 | if err != nil { 280 | 281 | return nil, err 282 | } 283 | 284 | var principalIds []string 285 | principalIds = append(principalIds, ucr.Item.Id) 286 | 287 | rClient := roles.NewClient(c.Client) 288 | 289 | var boundaryRoleIds []string 290 | var rolesList []string 291 | 292 | rolesList = strings.Split(boundaryRoles, ",") 293 | 294 | var boundaryRoleIdsString string 295 | 296 | for _, s := range rolesList { 297 | 298 | var opts []roles.Option 299 | version, err := rClient.Read(ctx, s, opts...) 300 | if err != nil { 301 | return nil, err 302 | } 303 | 304 | rcr, err := rClient.AddPrincipals(ctx, s, version.Item.Version, principalIds, opts...) 305 | if err != nil { 306 | return nil, err 307 | } 308 | 309 | boundaryRoleIds = append(boundaryRoleIds, rcr.Item.Id) 310 | } 311 | boundaryRoleIdsString = strings.Join(boundaryRoleIds, ",") 312 | 313 | return &boundaryAccount{ 314 | AccountId: acr.Item.Id, 315 | LoginName: acr.Item.Name, 316 | Password: accountPassword, 317 | AuthMethodId: acr.Item.AuthMethodId, 318 | BoundaryRoles: boundaryRoleIdsString, 319 | UserId: ucr.Item.Id, 320 | }, nil 321 | } 322 | 323 | // deleteToken calls the boundary client to remove account 324 | func deleteToken(ctx context.Context, c *boundaryClient, accountId string, userId string) error { 325 | const op = "deleteToken" 326 | if c.tokenIsExpired() { 327 | if err := c.authenticate(); err != nil { 328 | return fmt.Errorf("%s: unable to authenticate client: %w", op, err) 329 | } 330 | } 331 | ucr := users.NewClient(c.Client) 332 | var userOpts []users.Option 333 | _, err := ucr.Delete(ctx, userId, userOpts...) 334 | if err != nil { 335 | return err 336 | } 337 | 338 | acr := accounts.NewClient(c.Client) 339 | 340 | var opts []accounts.Option 341 | _, err = acr.Delete(ctx, accountId, opts...) 342 | if err != nil { 343 | return err 344 | } 345 | 346 | return nil 347 | } 348 | 349 | func createWorker(ctx context.Context, c *boundaryClient, scopeId string, workerName string, description string) (*boundaryWorker, error) { 350 | const op = "createWorker" 351 | if c.tokenIsExpired() { 352 | if err := c.authenticate(); err != nil { 353 | return nil, fmt.Errorf("%s: unable to authenticate client: %w", op, err) 354 | } 355 | } 356 | wcl := workers.NewClient(c.Client) 357 | var workerOpts []workers.Option 358 | workerOpts = append(workerOpts, workers.WithAutomaticVersioning(true)) 359 | workerOpts = append(workerOpts, workers.WithDescription(description)) 360 | workerOpts = append(workerOpts, workers.WithName(workerName)) 361 | wcr, err := wcl.CreateControllerLed(ctx, scopeId, workerOpts...) 362 | if err != nil { 363 | return nil, err 364 | } 365 | 366 | return &boundaryWorker{ 367 | WorkerId: wcr.Item.Id, 368 | ActivationToken: wcr.Item.ControllerGeneratedActivationToken, 369 | WorkerName: wcr.Item.Name, 370 | Description: wcr.Item.Description, 371 | }, nil 372 | } 373 | 374 | func deleteWorker(ctx context.Context, c *boundaryClient, workerId string) error { 375 | const op = "deleteWorker" 376 | if c.tokenIsExpired() { 377 | if err := c.authenticate(); err != nil { 378 | return fmt.Errorf("%s: unable to authenticate client: %w", op, err) 379 | } 380 | } 381 | wcl := workers.NewClient(c.Client) 382 | var workerOpts []workers.Option 383 | 384 | _, err := wcl.Delete(ctx, workerId, workerOpts...) 385 | if err != nil { 386 | return err 387 | } 388 | 389 | return nil 390 | } 391 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package boundarysecrets 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "time" 8 | 9 | boundary "github.com/hashicorp/boundary/api" 10 | "github.com/hashicorp/boundary/api/authmethods" 11 | ) 12 | 13 | const ( 14 | tokenExpirationTimeAttribName = "expiration_time" 15 | loginNameAttribName = "login_name" 16 | passwordAttribName = "password" 17 | tokenAttribName = "token" 18 | ) 19 | 20 | type boundaryClient struct { 21 | *boundary.Client 22 | tokenExp time.Time 23 | config *boundaryConfig 24 | } 25 | 26 | // newClient creates a new boundary client and authenticates it with the 27 | // provided configuration 28 | func newClient(config *boundaryConfig) (*boundaryClient, error) { 29 | 30 | if config == nil { 31 | return nil, errors.New("client configuration was nil") 32 | } 33 | 34 | if config.LoginName == "" { 35 | return nil, errors.New("login name was not defined") 36 | } 37 | 38 | if config.Password == "" { 39 | return nil, errors.New("password was not defined") 40 | } 41 | 42 | if config.Addr == "" { 43 | return nil, errors.New("boundary address was not defined") 44 | } 45 | 46 | if config.AuthMethodId == "" { 47 | return nil, errors.New("auth-method ID was not defined") 48 | } 49 | 50 | cfg := boundary.Config{ 51 | Addr: config.Addr, 52 | } 53 | 54 | client, err := boundary.NewClient(&cfg) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | c := &boundaryClient{ 60 | Client: client, 61 | config: config, 62 | } 63 | 64 | if err := c.authenticate(); err != nil { 65 | return nil, fmt.Errorf("unable to authenticate new client: %w", err) 66 | } 67 | 68 | return c, nil 69 | } 70 | 71 | // authenticate authenticates the client with the provided configuration. The 72 | // client and configuration must not be nil. The client's token expiration time 73 | // and token are set as a result of a successful authentication. 74 | func (c *boundaryClient) authenticate() error { 75 | switch { 76 | case c.Client == nil: 77 | return errors.New("client was nil") 78 | case c.config == nil: 79 | return errors.New("client configuration was nil") 80 | } 81 | credentials := map[string]interface{}{ 82 | loginNameAttribName: c.config.LoginName, 83 | passwordAttribName: c.config.Password, 84 | } 85 | 86 | amClient := authmethods.NewClient(c.Client) 87 | 88 | authenticationResult, err := amClient.Authenticate(context.Background(), c.config.AuthMethodId, "login", credentials) 89 | if err != nil { 90 | return err 91 | } 92 | 93 | c.Client.SetToken(fmt.Sprint(authenticationResult.Attributes[tokenAttribName])) 94 | 95 | rawExp, ok := authenticationResult.Attributes[tokenExpirationTimeAttribName] 96 | if !ok { 97 | return errors.New("expiration_time was not defined") 98 | } 99 | timeString, ok := rawExp.(string) 100 | if !ok { 101 | return errors.New("expiration_time was not a string") 102 | } 103 | parsedTime, err := time.Parse(time.RFC3339, timeString) 104 | if err != nil { 105 | return err 106 | } 107 | c.tokenExp = parsedTime 108 | 109 | return nil 110 | } 111 | 112 | // tokenIsExpired returns true if the client's token has expired. We provide a 113 | // 1 minute buffer to ensure that the token is not expired when we use it. 114 | func (c *boundaryClient) tokenIsExpired() bool { 115 | return time.Now().After(c.tokenExp.Add(-1 * time.Minute)) 116 | } 117 | -------------------------------------------------------------------------------- /cmd/vault-plugin-secrets-boundary/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | boundary "github.com/devopsrob/vault-plugin-boundary-secrets-engine" 5 | "github.com/hashicorp/go-hclog" 6 | "github.com/hashicorp/vault/api" 7 | "github.com/hashicorp/vault/sdk/plugin" 8 | "os" 9 | ) 10 | 11 | func main() { 12 | apiClientMeta := &api.PluginAPIClientMeta{} 13 | flags := apiClientMeta.FlagSet() 14 | flags.Parse(os.Args[1:]) 15 | 16 | tlsConfig := apiClientMeta.GetTLSConfig() 17 | tlsProviderFunc := api.VaultPluginTLSProvider(tlsConfig) 18 | 19 | err := plugin.Serve(&plugin.ServeOpts{ 20 | BackendFactoryFunc: boundary.Factory, 21 | TLSProviderFunc: tlsProviderFunc, 22 | }) 23 | if err != nil { 24 | logger := hclog.New(&hclog.LoggerOptions{}) 25 | 26 | logger.Error("plugin shutting down", "error", err) 27 | os.Exit(1) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/devopsrob/vault-plugin-boundary-secrets-engine 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/fatih/color v1.13.0 // indirect 7 | github.com/frankban/quicktest v1.14.0 // indirect 8 | github.com/go-test/deep v1.0.4 // indirect 9 | github.com/hashicorp/boundary/api v0.0.34 10 | github.com/hashicorp/go-hclog v1.0.0 11 | github.com/hashicorp/go-retryablehttp v0.7.0 // indirect 12 | github.com/hashicorp/go-secure-stdlib/parseutil v0.1.2 // indirect 13 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect 14 | github.com/hashicorp/go-version v1.3.0 // indirect 15 | github.com/hashicorp/vault/api v1.3.1 16 | github.com/hashicorp/vault/sdk v0.3.0 17 | github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect 18 | github.com/jhump/protoreflect v1.9.1-0.20210817181203-db1a327a393e // indirect 19 | github.com/mattn/go-colorable v0.1.12 // indirect 20 | github.com/mitchellh/copystructure v1.2.0 // indirect 21 | github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 // indirect 22 | github.com/sethvargo/go-password v0.2.0 23 | github.com/stretchr/objx v0.2.0 // indirect 24 | github.com/stretchr/testify v1.7.0 25 | golang.org/x/sys v0.1.0 // indirect 26 | golang.org/x/text v0.3.8 // indirect 27 | google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83 // indirect 28 | gopkg.in/yaml.v3 v3.0.0 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= 5 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 6 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 7 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 8 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 9 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 10 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 11 | github.com/armon/go-metrics v0.3.9 h1:O2sNqxBdvq8Eq5xmzljcYzAORli6RWCvEym4cJf9m18= 12 | github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= 13 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 14 | github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= 15 | github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 16 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 17 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 18 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 19 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 20 | github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= 21 | github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= 22 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 23 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 24 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 25 | github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= 26 | github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= 27 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 28 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 29 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 30 | github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 31 | github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 32 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 33 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 34 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 35 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 36 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 37 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 38 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 39 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 40 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 41 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= 42 | github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= 43 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 44 | github.com/evanphx/json-patch/v5 v5.5.0 h1:bAmFiUJ+o0o2B4OiTFeE3MqCOtyo+jjPP9iZ0VRxYUc= 45 | github.com/evanphx/json-patch/v5 v5.5.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= 46 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 47 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 48 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 49 | github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= 50 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 51 | github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= 52 | github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU= 53 | github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss= 54 | github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= 55 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 56 | github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= 57 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 58 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 59 | github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q= 60 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 61 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 62 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 63 | github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 64 | github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= 65 | github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 66 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 67 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 68 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 69 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 70 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 71 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 72 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 73 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 74 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 75 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 76 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 77 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 78 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 79 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 80 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 81 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 82 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 83 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 84 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 85 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 86 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 87 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 88 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 89 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 90 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 91 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 92 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 93 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 94 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= 95 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 96 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 97 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 98 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 99 | github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= 100 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 101 | github.com/hashicorp/boundary/api v0.0.34 h1:A5G6XtJu6eG8A0sB5alS1fr9Z0jZHZBIqJRF8E5nnI4= 102 | github.com/hashicorp/boundary/api v0.0.34/go.mod h1:yroNrsyCVCSEKnrbyDeyS2iO0aMq7BTYtAptZWgIb60= 103 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 104 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 105 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 106 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 107 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 108 | github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 109 | github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 110 | github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= 111 | github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= 112 | github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= 113 | github.com/hashicorp/go-hclog v1.0.0 h1:bkKf0BeBXcSYa7f5Fyi9gMuQ8gNsxeiNpZjR6VxNZeo= 114 | github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= 115 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 116 | github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= 117 | github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 118 | github.com/hashicorp/go-kms-wrapping/entropy v0.1.0 h1:xuTi5ZwjimfpvpL09jDE71smCBRpnF5xfo871BSX4gs= 119 | github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g= 120 | github.com/hashicorp/go-kms-wrapping/v2 v2.0.1 h1:ktrFhOtcRRXuW3os8/Raqn+6XetfYaGLJ3DfTh25nGs= 121 | github.com/hashicorp/go-kms-wrapping/v2 v2.0.1/go.mod h1:9hlMEpnScgVjT7ZckErAsz90ieyDcHSuP+KmjvMysqw= 122 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 123 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 124 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 125 | github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= 126 | github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= 127 | github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= 128 | github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= 129 | github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= 130 | github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4= 131 | github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= 132 | github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= 133 | github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= 134 | github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= 135 | github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 h1:cCRo8gK7oq6A2L6LICkUZ+/a5rLiRXFMf1Qd4xSwxTc= 136 | github.com/hashicorp/go-secure-stdlib/mlock v0.1.1/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I= 137 | github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= 138 | github.com/hashicorp/go-secure-stdlib/parseutil v0.1.2 h1:Tz6v3Jb2DRnDCfifRSjYKG0m8dLdNq6bcDkB41en7nw= 139 | github.com/hashicorp/go-secure-stdlib/parseutil v0.1.2/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= 140 | github.com/hashicorp/go-secure-stdlib/password v0.1.1/go.mod h1:9hH302QllNwu1o2TGYtSk8I8kTAN0ca1EHpwhm5Mmzo= 141 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= 142 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= 143 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= 144 | github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1/go.mod h1:l8slYwnJA26yBz+ErHpp2IRCLr0vuOMGBORIz4rRiAs= 145 | github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= 146 | github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= 147 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 148 | github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= 149 | github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 150 | github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 151 | github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= 152 | github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 153 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 154 | github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= 155 | github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 156 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 157 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 158 | github.com/hashicorp/vault/api v1.3.1 h1:pkDkcgTh47PRjY1NEFeofqR4W/HkNUi9qIakESO2aRM= 159 | github.com/hashicorp/vault/api v1.3.1/go.mod h1:QeJoWxMFt+MsuWcYhmwRLwKEXrjwAFFywzhptMsTIUw= 160 | github.com/hashicorp/vault/sdk v0.3.0 h1:kR3dpxNkhh/wr6ycaJYqp6AFT/i2xaftbfnwZduTKEY= 161 | github.com/hashicorp/vault/sdk v0.3.0/go.mod h1:aZ3fNuL5VNydQk8GcLJ2TV8YCRVvyaakYkhZRoVuhj0= 162 | github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= 163 | github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= 164 | github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= 165 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 166 | github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= 167 | github.com/jhump/protoreflect v1.9.1-0.20210817181203-db1a327a393e h1:Yb4fEGk+GtBSNuvy5rs0ZJt/jtopc/z9azQaj3xbies= 168 | github.com/jhump/protoreflect v1.9.1-0.20210817181203-db1a327a393e/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= 169 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 170 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 171 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 172 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 173 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 174 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 175 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 176 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 177 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 178 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 179 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 180 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 181 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 182 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 183 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 184 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 185 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 186 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 187 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 188 | github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= 189 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 190 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 191 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 192 | github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= 193 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 194 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 195 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 196 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 197 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 198 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 199 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= 200 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 201 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 202 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 203 | github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 204 | github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= 205 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 206 | github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 207 | github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 208 | github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= 209 | github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 210 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 211 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= 212 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 213 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 214 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 215 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 216 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 217 | github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 218 | github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 219 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 220 | github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso= 221 | github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= 222 | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= 223 | github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= 224 | github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 225 | github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= 226 | github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 227 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 228 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 229 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 230 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 231 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 232 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 233 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 234 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 235 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 236 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 237 | github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= 238 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 239 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 240 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 241 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 242 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 243 | github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= 244 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 245 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 246 | github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= 247 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 248 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 249 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 250 | github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 h1:Ha8xCaq6ln1a+R91Km45Oq6lPXj2Mla6CRJYcuV2h1w= 251 | github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= 252 | github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 253 | github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= 254 | github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= 255 | github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI= 256 | github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE= 257 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 258 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 259 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 260 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 261 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 262 | github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= 263 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 264 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 265 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 266 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 267 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 268 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 269 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 270 | github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= 271 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 272 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 273 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 274 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 275 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 276 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 277 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 278 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 279 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 280 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 281 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 282 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 283 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 284 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 285 | golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000 h1:SL+8VVnkqyshUSz5iNnXtrBQzvFF2SkROm6t5RczFAE= 286 | golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 287 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 288 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 289 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 290 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 291 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 292 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 293 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 294 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 295 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 296 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 297 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 298 | golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 299 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 300 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 301 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 302 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 303 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 304 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 305 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 306 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 307 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 308 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 309 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 310 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 311 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 312 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 313 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 314 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= 315 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 316 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 317 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 318 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 319 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 320 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 321 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 322 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 323 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 324 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 325 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 326 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 327 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 328 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 329 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 330 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 331 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 332 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 333 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 334 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 335 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 336 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 337 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 338 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 339 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 340 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 341 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 342 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 343 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 344 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 345 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 346 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 347 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 348 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 349 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 350 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 351 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 352 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 353 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 354 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 355 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 356 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 357 | golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= 358 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 359 | golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 360 | golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= 361 | golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 362 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 363 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 364 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 365 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 366 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 367 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 368 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 369 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 370 | golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 371 | golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 372 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 373 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 374 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 375 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 376 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 377 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 378 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 379 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 380 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 381 | google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 382 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 383 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 384 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 385 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 386 | google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83 h1:3V2dxSZpz4zozWWUq36vUxXEKnSYitEH2LdsAx+RUmg= 387 | google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= 388 | google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 389 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 390 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 391 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 392 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 393 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 394 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 395 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 396 | google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 397 | google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= 398 | google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E= 399 | google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= 400 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 401 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 402 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 403 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 404 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 405 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 406 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 407 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 408 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 409 | google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 410 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 411 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 412 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 413 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 414 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 415 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 416 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 417 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 418 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 419 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 420 | gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= 421 | gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 422 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 423 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 424 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 425 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 426 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 427 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 428 | gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= 429 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 430 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 431 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 432 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 433 | -------------------------------------------------------------------------------- /path_config.go: -------------------------------------------------------------------------------- 1 | package boundarysecrets 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/hashicorp/vault/sdk/framework" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | ) 12 | 13 | const ( 14 | configStoragePath = "config" 15 | ) 16 | 17 | // boundaryConfig includes the minimum configuration 18 | // required to instantiate a new boundary client. 19 | type boundaryConfig struct { 20 | LoginName string `json:"login_name"` 21 | Password string `json:"password"` 22 | Addr string `json:"addr"` 23 | AuthMethodId string `json:"auth_method_id"` 24 | } 25 | 26 | // pathConfig extends the Vault API with a `/config` 27 | // endpoint for the backend. You can choose whether 28 | // or not certain attributes should be displayed, 29 | // required, and named. For example, password 30 | // is marked as sensitive and will not be output 31 | // when you read the configuration. 32 | func pathConfig(b *boundaryBackend) *framework.Path { 33 | return &framework.Path{ 34 | Pattern: "config", 35 | Fields: map[string]*framework.FieldSchema{ 36 | "login_name": { 37 | Type: framework.TypeString, 38 | Description: "The Boundary Login Name that Vault will use to manage Boundary", 39 | Required: true, 40 | DisplayAttrs: &framework.DisplayAttributes{ 41 | Name: "Login Name", 42 | Sensitive: false, 43 | }, 44 | }, 45 | "password": { 46 | Type: framework.TypeString, 47 | Description: "The password of the user that Vault will use to manage Boundary", 48 | Required: true, 49 | DisplayAttrs: &framework.DisplayAttributes{ 50 | Name: "Password", 51 | Sensitive: true, 52 | }, 53 | }, 54 | "addr": { 55 | Type: framework.TypeString, 56 | Description: "The address of the Boundary controller", 57 | Required: true, 58 | DisplayAttrs: &framework.DisplayAttributes{ 59 | Name: "Addr", 60 | Sensitive: false, 61 | }, 62 | }, 63 | "auth_method_id": { 64 | Type: framework.TypeString, 65 | Description: "The ID of the Boundary auth-method Vault will use to sign in", 66 | Required: true, 67 | DisplayAttrs: &framework.DisplayAttributes{ 68 | Name: "Auth-method ID", 69 | Sensitive: false, 70 | }, 71 | }, 72 | }, 73 | Operations: map[logical.Operation]framework.OperationHandler{ 74 | logical.ReadOperation: &framework.PathOperation{ 75 | Callback: b.pathConfigRead, 76 | }, 77 | logical.CreateOperation: &framework.PathOperation{ 78 | Callback: b.pathConfigWrite, 79 | }, 80 | logical.UpdateOperation: &framework.PathOperation{ 81 | Callback: b.pathConfigWrite, 82 | }, 83 | logical.DeleteOperation: &framework.PathOperation{ 84 | Callback: b.pathConfigDelete, 85 | }, 86 | }, 87 | ExistenceCheck: b.pathConfigExistenceCheck, 88 | HelpSynopsis: pathConfigHelpSynopsis, 89 | HelpDescription: pathConfigHelpDescription, 90 | } 91 | } 92 | 93 | // pathConfigExistenceCheck verifies if the configuration exists. 94 | func (b *boundaryBackend) pathConfigExistenceCheck(ctx context.Context, req *logical.Request, data *framework.FieldData) (bool, error) { 95 | out, err := req.Storage.Get(ctx, req.Path) 96 | if err != nil { 97 | return false, fmt.Errorf("existence check failed: %w", err) 98 | } 99 | 100 | return out != nil, nil 101 | } 102 | 103 | func getConfig(ctx context.Context, s logical.Storage) (*boundaryConfig, error) { 104 | entry, err := s.Get(ctx, configStoragePath) 105 | if err != nil { 106 | return nil, err 107 | } 108 | 109 | if entry == nil { 110 | return nil, nil 111 | } 112 | 113 | config := new(boundaryConfig) 114 | if err := entry.DecodeJSON(&config); err != nil { 115 | return nil, fmt.Errorf("error reading root configuration: %w", err) 116 | } 117 | 118 | // return the config, we are done 119 | return config, nil 120 | } 121 | 122 | func (b *boundaryBackend) pathConfigRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 123 | config, err := getConfig(ctx, req.Storage) 124 | if err != nil { 125 | return nil, err 126 | } 127 | 128 | return &logical.Response{ 129 | Data: map[string]interface{}{ 130 | "login_name": config.LoginName, 131 | "addr": config.Addr, 132 | "auth_method_id": config.AuthMethodId, 133 | }, 134 | }, nil 135 | } 136 | 137 | func (b *boundaryBackend) pathConfigWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 138 | config, err := getConfig(ctx, req.Storage) 139 | if err != nil { 140 | return nil, err 141 | } 142 | 143 | createOperation := (req.Operation == logical.CreateOperation) 144 | 145 | if config == nil { 146 | if !createOperation { 147 | return nil, errors.New("config not found during update operation") 148 | } 149 | config = new(boundaryConfig) 150 | } 151 | 152 | if login_name, ok := data.GetOk("login_name"); ok { 153 | config.LoginName = login_name.(string) 154 | } else if !ok && createOperation { 155 | return nil, fmt.Errorf("missing login_name in configuration") 156 | } 157 | 158 | if addr, ok := data.GetOk("addr"); ok { 159 | config.Addr = addr.(string) 160 | } else if !ok && createOperation { 161 | return nil, fmt.Errorf("missing addr in configuration") 162 | } 163 | 164 | if password, ok := data.GetOk("password"); ok { 165 | config.Password = password.(string) 166 | } else if !ok && createOperation { 167 | return nil, fmt.Errorf("missing password in configuration") 168 | } 169 | 170 | if auth_method_id, ok := data.GetOk("auth_method_id"); ok { 171 | if strings.HasPrefix(auth_method_id.(string), "ampw_") { 172 | config.AuthMethodId = auth_method_id.(string) 173 | } else { 174 | return nil, fmt.Errorf("invalid auth_method_id type. Must be password auth method type") 175 | } 176 | } else if !ok && createOperation { 177 | return nil, fmt.Errorf("missing auth_method_id in configuration ") 178 | } 179 | 180 | entry, err := logical.StorageEntryJSON(configStoragePath, config) 181 | if err != nil { 182 | return nil, err 183 | } 184 | 185 | if err := req.Storage.Put(ctx, entry); err != nil { 186 | return nil, err 187 | } 188 | 189 | b.reset() 190 | 191 | return nil, nil 192 | } 193 | 194 | func (b *boundaryBackend) pathConfigDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 195 | err := req.Storage.Delete(ctx, configStoragePath) 196 | 197 | if err == nil { 198 | b.reset() 199 | } 200 | 201 | return nil, err 202 | } 203 | 204 | // pathConfigHelpSynopsis summarizes the help text for the configuration 205 | const pathConfigHelpSynopsis = `Configure the Boundary backend.` 206 | 207 | // pathConfigHelpDescription describes the help text for the configuration 208 | const pathConfigHelpDescription = ` 209 | The Boundary secret backend requires credentials for managing 210 | Users, Groups, Grants and Roles. 211 | You must sign up with a Login name and password and 212 | specify the Boundary address and Auth-method ID 213 | before using this secrets backend. 214 | ` 215 | -------------------------------------------------------------------------------- /path_config_test.go: -------------------------------------------------------------------------------- 1 | package boundarysecrets 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/hashicorp/vault/sdk/logical" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | const ( 13 | loginName = "admin" 14 | Password = "password" 15 | addr = "http://localhost:9200" 16 | authMethodId = "ampw_1234567890" 17 | ) 18 | 19 | // TestConfig mocks the creation, read, update, and delete 20 | // of the backend configuration for Boundary. 21 | func TestConfig(t *testing.T) { 22 | b, reqStorage := getTestBackend(t) 23 | 24 | t.Run("Test Configuration", func(t *testing.T) { 25 | err := testConfigCreate(t, b, reqStorage, map[string]interface{}{ 26 | "login_name": loginName, 27 | "password": Password, 28 | "addr": addr, 29 | "auth_method_id": authMethodId, 30 | }) 31 | 32 | assert.NoError(t, err) 33 | 34 | err = testConfigRead(t, b, reqStorage, map[string]interface{}{ 35 | "login_name": loginName, 36 | "auth_method_id": authMethodId, 37 | "addr": addr, 38 | }) 39 | 40 | assert.NoError(t, err) 41 | 42 | err = testConfigUpdate(t, b, reqStorage, map[string]interface{}{ 43 | "login_name": loginName, 44 | "auth_method_id": "ampw_0987654321", 45 | "addr": "http://boundary:9200", 46 | }) 47 | 48 | assert.NoError(t, err) 49 | 50 | err = testConfigRead(t, b, reqStorage, map[string]interface{}{ 51 | "login_name": loginName, 52 | "auth_method_id": "ampw_0987654321", 53 | "addr": "http://boundary:9200", 54 | }) 55 | 56 | assert.NoError(t, err) 57 | 58 | err = testConfigDelete(t, b, reqStorage) 59 | 60 | assert.NoError(t, err) 61 | }) 62 | } 63 | 64 | func testConfigDelete(t *testing.T, b logical.Backend, s logical.Storage) error { 65 | resp, err := b.HandleRequest(context.Background(), &logical.Request{ 66 | Operation: logical.DeleteOperation, 67 | Path: configStoragePath, 68 | Storage: s, 69 | }) 70 | 71 | if err != nil { 72 | return err 73 | } 74 | 75 | if resp != nil && resp.IsError() { 76 | return resp.Error() 77 | } 78 | return nil 79 | } 80 | 81 | func testConfigCreate(t *testing.T, b logical.Backend, s logical.Storage, d map[string]interface{}) error { 82 | resp, err := b.HandleRequest(context.Background(), &logical.Request{ 83 | Operation: logical.CreateOperation, 84 | Path: configStoragePath, 85 | Data: d, 86 | Storage: s, 87 | }) 88 | 89 | if err != nil { 90 | return err 91 | } 92 | 93 | if resp != nil && resp.IsError() { 94 | return resp.Error() 95 | } 96 | return nil 97 | } 98 | 99 | func testConfigUpdate(t *testing.T, b logical.Backend, s logical.Storage, d map[string]interface{}) error { 100 | resp, err := b.HandleRequest(context.Background(), &logical.Request{ 101 | Operation: logical.UpdateOperation, 102 | Path: configStoragePath, 103 | Data: d, 104 | Storage: s, 105 | }) 106 | 107 | if err != nil { 108 | return err 109 | } 110 | 111 | if resp != nil && resp.IsError() { 112 | return resp.Error() 113 | } 114 | return nil 115 | } 116 | 117 | func testConfigRead(t *testing.T, b logical.Backend, s logical.Storage, expected map[string]interface{}) error { 118 | resp, err := b.HandleRequest(context.Background(), &logical.Request{ 119 | Operation: logical.ReadOperation, 120 | Path: configStoragePath, 121 | Storage: s, 122 | }) 123 | 124 | if err != nil { 125 | return err 126 | } 127 | 128 | if resp == nil && expected == nil { 129 | return nil 130 | } 131 | 132 | if resp.IsError() { 133 | return resp.Error() 134 | } 135 | 136 | if len(expected) != len(resp.Data) { 137 | return fmt.Errorf("read data mismatch (expected %d values, got %d)", len(expected), len(resp.Data)) 138 | } 139 | 140 | for k, expectedV := range expected { 141 | actualV, ok := resp.Data[k] 142 | 143 | if !ok { 144 | return fmt.Errorf(`expected data["%s"] = %v but was not included in read output"`, k, expectedV) 145 | } else if expectedV != actualV { 146 | return fmt.Errorf(`expected data["%s"] = %v, instead got %v"`, k, expectedV, actualV) 147 | } 148 | } 149 | 150 | return nil 151 | } 152 | -------------------------------------------------------------------------------- /path_credentials.go: -------------------------------------------------------------------------------- 1 | package boundarysecrets 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/hashicorp/vault/sdk/framework" 9 | "github.com/hashicorp/vault/sdk/logical" 10 | ) 11 | 12 | // pathCredentials extends the Vault API with a `/creds` 13 | // endpoint for a role. You can choose whether 14 | // or not certain attributes should be displayed, 15 | // required, and named. 16 | func pathCredentials(b *boundaryBackend) *framework.Path { 17 | return &framework.Path{ 18 | Pattern: "creds/" + framework.GenericNameRegex("name"), 19 | Fields: map[string]*framework.FieldSchema{ 20 | "name": { 21 | Type: framework.TypeLowerCaseString, 22 | Description: "Name of the role", 23 | Required: true, 24 | }, 25 | "worker_name": { 26 | Type: framework.TypeString, 27 | Description: "Name of Boundary Worker", 28 | Required: false, 29 | }, 30 | "description": { 31 | Type: framework.TypeString, 32 | Description: "Short description of the worker", 33 | Required: false, 34 | }, 35 | }, 36 | Callbacks: map[logical.Operation]framework.OperationFunc{ 37 | logical.ReadOperation: b.pathCredentialsRead, 38 | logical.UpdateOperation: b.pathCredentialsRead, 39 | }, 40 | HelpSynopsis: pathCredentialsHelpSyn, 41 | HelpDescription: pathCredentialsHelpDesc, 42 | } 43 | } 44 | 45 | // pathCredentialsRead creates a new HashiCups token each time it is called if a 46 | // role exists. 47 | func (b *boundaryBackend) pathCredentialsRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 48 | roleName := d.Get("name").(string) 49 | 50 | workerName := d.Get("worker_name").(string) 51 | workerDescription := d.Get("description").(string) 52 | 53 | if workerDescription == "" { 54 | workerDescription = "Generated by Vault" 55 | } 56 | 57 | roleEntry, err := b.getRole(ctx, req.Storage, roleName) 58 | if err != nil { 59 | return nil, fmt.Errorf("error retrieving role: %w", err) 60 | } 61 | 62 | if roleEntry == nil { 63 | return nil, errors.New("error retrieving role: role is nil") 64 | } 65 | 66 | return b.createUserCreds(ctx, req, roleEntry, workerName, workerDescription) 67 | } 68 | 69 | // createUserCreds creates a new HashiCups token to store into the Vault backend, generates 70 | // a response with the secrets information, and checks the TTL and MaxTTL attributes. 71 | func (b *boundaryBackend) createUserCreds(ctx context.Context, req *logical.Request, role *boundaryRoleEntry, workerName string, workerDescription string) (*logical.Response, error) { 72 | var resp *logical.Response 73 | 74 | roleTtl := role.TTL 75 | roleMaxTtl := role.MaxTTL 76 | 77 | roleType := role.RoleType 78 | 79 | switch roleType { 80 | case "user": 81 | 82 | account, err := b.createAccount(ctx, req.Storage, role) 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | // The response is divided into two objects (1) internal data and (2) data. 88 | // If you want to reference any information in your code, you need to 89 | // store it in internal data! 90 | resp = b.Secret(Account).Response(map[string]interface{}{ 91 | "account_id": account.AccountId, 92 | "boundary_roles": account.BoundaryRoles, 93 | "user_id": account.UserId, 94 | "auth_method_id": account.AuthMethodId, 95 | "password": account.Password, 96 | "login_name": account.LoginName, 97 | }, map[string]interface{}{ 98 | "account_id": account.AccountId, 99 | "user_id": account.UserId, 100 | "ttl": roleTtl, 101 | "max_ttl": roleMaxTtl, 102 | }) 103 | case "worker": 104 | worker, err := b.createWorker(ctx, req.Storage, role, workerName, workerDescription) 105 | if err != nil { 106 | return logical.ErrorResponse("unable to create worker, error:", err), nil 107 | //return nil, err 108 | } 109 | 110 | resp = b.Secret(Worker).Response(map[string]interface{}{ 111 | "worker_id": worker.WorkerId, 112 | "worker_name": worker.WorkerName, 113 | "activation_token": worker.ActivationToken, 114 | }, map[string]interface{}{ 115 | "worker_id": worker.WorkerId, 116 | "worker_name": worker.WorkerName, 117 | "ttl": roleTtl, 118 | "max_ttl": roleMaxTtl, 119 | }) 120 | 121 | } 122 | 123 | if role.TTL > 0 { 124 | resp.Secret.TTL = role.TTL 125 | } 126 | 127 | if role.MaxTTL > 0 { 128 | resp.Secret.MaxTTL = role.MaxTTL 129 | } 130 | 131 | return resp, nil 132 | } 133 | 134 | // createAccount uses the Boundary client to create a new account 135 | func (b *boundaryBackend) createAccount(ctx context.Context, s logical.Storage, roleEntry *boundaryRoleEntry) (*boundaryAccount, error) { 136 | client, err := b.getClient(ctx, s) 137 | if err != nil { 138 | return nil, err 139 | } 140 | 141 | var token *boundaryAccount 142 | 143 | token, err = createAccount(ctx, client, roleEntry.Name, roleEntry.AuthMethodID, roleEntry.BoundaryRoles, roleEntry.ScopeId) 144 | if err != nil { 145 | return nil, fmt.Errorf("error creating Boundary Account: %w", err) 146 | } 147 | 148 | if token == nil { 149 | return nil, errors.New("error creating Boundary Account") 150 | } 151 | 152 | return token, nil 153 | 154 | } 155 | 156 | func (b *boundaryBackend) createWorker(ctx context.Context, s logical.Storage, roleEntry *boundaryRoleEntry, workerName string, description string) (*boundaryWorker, error) { 157 | client, err := b.getClient(ctx, s) 158 | if err != nil { 159 | return nil, err 160 | } 161 | 162 | var worker *boundaryWorker 163 | 164 | worker, err = createWorker(ctx, client, roleEntry.ScopeId, workerName, description) 165 | if err != nil { 166 | return nil, fmt.Errorf("error creating Boundary worker auth token: %w", err) 167 | } 168 | 169 | if worker == nil { 170 | return nil, errors.New("error creating Boundary worker auth token") 171 | } 172 | 173 | return worker, nil 174 | 175 | } 176 | 177 | const pathCredentialsHelpSyn = ` 178 | Generate a Boundary account or worker from a specific Vault role. 179 | ` 180 | 181 | const pathCredentialsHelpDesc = ` 182 | This path generates a Boundary account 183 | based on a particular role. 184 | ` 185 | -------------------------------------------------------------------------------- /path_credentials_test.go: -------------------------------------------------------------------------------- 1 | package boundarysecrets 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | "time" 8 | 9 | log "github.com/hashicorp/go-hclog" 10 | "github.com/hashicorp/vault/sdk/helper/logging" 11 | "github.com/hashicorp/vault/sdk/logical" 12 | ) 13 | 14 | // newAcceptanceTestEnv creates a test environment for credentials 15 | func newAcceptanceTestEnv() (*testEnv, error) { 16 | ctx := context.Background() 17 | 18 | maxLease, _ := time.ParseDuration("60s") 19 | defaultLease, _ := time.ParseDuration("30s") 20 | conf := &logical.BackendConfig{ 21 | System: &logical.StaticSystemView{ 22 | DefaultLeaseTTLVal: defaultLease, 23 | MaxLeaseTTLVal: maxLease, 24 | }, 25 | Logger: logging.NewVaultLogger(log.Debug), 26 | } 27 | b, err := Factory(ctx, conf) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return &testEnv{ 32 | LoginName: os.Getenv(envVarBoundaryLoginName), 33 | Password: os.Getenv(envVarBoundaryPassword), 34 | Addr: os.Getenv(envVarBoundaryAddr), 35 | Backend: b, 36 | Context: ctx, 37 | Storage: &logical.InmemStorage{}, 38 | }, nil 39 | } 40 | 41 | // TestAcceptanceUserToken tests a series of steps to make 42 | // sure the role and token creation work correctly. 43 | func TestAcceptanceUserToken(t *testing.T) { 44 | if !runAcceptanceTests { 45 | t.SkipNow() 46 | } 47 | 48 | acceptanceTestEnv, err := newAcceptanceTestEnv() 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | 53 | t.Run("add config", acceptanceTestEnv.AddConfig) 54 | t.Run("add user token role", acceptanceTestEnv.AddUserTokenRole) 55 | t.Run("read user token cred", acceptanceTestEnv.ReadUserToken) 56 | t.Run("read user token cred", acceptanceTestEnv.ReadUserToken) 57 | t.Run("cleanup user tokens", acceptanceTestEnv.CleanupUserTokens) 58 | } 59 | -------------------------------------------------------------------------------- /path_roles.go: -------------------------------------------------------------------------------- 1 | package boundarysecrets 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/hashicorp/vault/sdk/framework" 7 | "github.com/hashicorp/vault/sdk/logical" 8 | "time" 9 | ) 10 | 11 | type boundaryRoleEntry struct { 12 | AuthMethodID string `json:"auth_method_id"` 13 | Name string `json:"name"` 14 | ScopeId string `json:"scope_id"` 15 | BoundaryRoles string `json:"boundary_roles"` 16 | TTL time.Duration `json:"ttl"` 17 | MaxTTL time.Duration `json:"max_ttl"` 18 | RoleType string `json:"role_type"` 19 | } 20 | 21 | func (r *boundaryRoleEntry) toResponseData() map[string]interface{} { 22 | respData := map[string]interface{}{ 23 | "ttl": r.TTL.Seconds(), 24 | "max_ttl": r.MaxTTL.Seconds(), 25 | "boundary_roles": r.BoundaryRoles, 26 | "name": r.Name, 27 | "auth_method_id": r.AuthMethodID, 28 | "scope_id": r.ScopeId, 29 | "role_type": r.RoleType, 30 | } 31 | return respData 32 | } 33 | 34 | func pathRole(b *boundaryBackend) []*framework.Path { 35 | return []*framework.Path{ 36 | { 37 | Pattern: "role/" + framework.GenericNameRegex("name"), 38 | Fields: map[string]*framework.FieldSchema{ 39 | "name": { 40 | Type: framework.TypeLowerCaseString, 41 | Description: "Name of Role", 42 | Required: true, 43 | }, 44 | "boundary_roles": { 45 | Type: framework.TypeString, // This should be a list of Boundary roles 46 | Description: "List of Boundary roles to be assigned to generated users.", 47 | Required: false, 48 | }, 49 | "ttl": { 50 | Type: framework.TypeDurationSecond, 51 | Description: "Default lease for generated credentials. If not set or set to 0, will use system default.", 52 | }, 53 | "max_ttl": { 54 | Type: framework.TypeDurationSecond, 55 | Description: "Maximum time for role. If not set or set to 0, will use system default.", 56 | }, 57 | "scope_id": { 58 | Type: framework.TypeString, // Boundary scope ID under which Users will be created 59 | Description: "Boundary scope ID of the Vault generated user", 60 | }, 61 | "auth_method_id": { 62 | Type: framework.TypeString, // Boundary auth method ID that the account is created under 63 | Description: "Boundary auth method ID that the account is created under", 64 | }, 65 | "role_type": { 66 | Type: framework.TypeLowerCaseString, 67 | Description: "Must be either `user` or `worker` type", 68 | Required: true, 69 | }, 70 | }, 71 | Operations: map[logical.Operation]framework.OperationHandler{ 72 | logical.ReadOperation: &framework.PathOperation{ 73 | Callback: b.pathRolesRead, 74 | }, 75 | logical.CreateOperation: &framework.PathOperation{ 76 | Callback: b.pathRolesWrite, 77 | }, 78 | logical.UpdateOperation: &framework.PathOperation{ 79 | Callback: b.pathRolesWrite, 80 | }, 81 | logical.DeleteOperation: &framework.PathOperation{ 82 | Callback: b.pathRolesDelete, 83 | }, 84 | }, 85 | HelpSynopsis: pathRoleHelpSynopsis, 86 | HelpDescription: pathRoleHelpDescription, 87 | }, 88 | { 89 | Pattern: "role/?$", 90 | Operations: map[logical.Operation]framework.OperationHandler{ 91 | logical.ListOperation: &framework.PathOperation{ 92 | Callback: b.pathRolesList, 93 | }, 94 | }, 95 | HelpSynopsis: pathRoleListHelpSynopsis, 96 | HelpDescription: pathRoleListHelpDescription, 97 | }, 98 | } 99 | } 100 | 101 | const ( 102 | pathRoleHelpSynopsis = `Manages the Vault role for generating Boundary users.` 103 | pathRoleHelpDescription = ` 104 | This path allows you to read and write roles used to generate Boundary users. 105 | ` 106 | pathRoleListHelpSynopsis = `List the existing roles in Boundary backend` 107 | pathRoleListHelpDescription = `Roles will be listed by the role name.` 108 | ) 109 | 110 | func (b *boundaryBackend) getRole(ctx context.Context, s logical.Storage, name string) (*boundaryRoleEntry, error) { 111 | if name == "" { 112 | return nil, fmt.Errorf("missing role name") 113 | } 114 | 115 | entry, err := s.Get(ctx, "role/"+name) 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | if entry == nil { 121 | return nil, nil 122 | } 123 | 124 | var role boundaryRoleEntry 125 | 126 | if err := entry.DecodeJSON(&role); err != nil { 127 | return nil, err 128 | } 129 | return &role, nil 130 | } 131 | 132 | func (b *boundaryBackend) pathRolesRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 133 | entry, err := b.getRole(ctx, req.Storage, d.Get("name").(string)) 134 | if err != nil { 135 | return nil, err 136 | } 137 | 138 | if entry == nil { 139 | return nil, nil 140 | } 141 | 142 | return &logical.Response{ 143 | Data: entry.toResponseData(), 144 | }, nil 145 | } 146 | 147 | func setRole(ctx context.Context, s logical.Storage, name string, roleEntry *boundaryRoleEntry) error { 148 | entry, err := logical.StorageEntryJSON("role/"+name, roleEntry) 149 | if err != nil { 150 | return err 151 | } 152 | 153 | if entry == nil { 154 | return fmt.Errorf("failed to create storage entry for role") 155 | } 156 | 157 | if err := s.Put(ctx, entry); err != nil { 158 | return err 159 | } 160 | 161 | return nil 162 | } 163 | 164 | func (b *boundaryBackend) pathRolesWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 165 | name, ok := d.GetOk("name") 166 | if !ok { 167 | return logical.ErrorResponse("missing role name"), nil 168 | } 169 | 170 | roleEntry, err := b.getRole(ctx, req.Storage, name.(string)) 171 | if err != nil { 172 | return nil, err 173 | } 174 | 175 | if roleEntry == nil { 176 | roleEntry = &boundaryRoleEntry{} 177 | } 178 | 179 | createOperation := req.Operation == logical.CreateOperation 180 | 181 | if name, ok := d.GetOk("name"); ok { 182 | roleEntry.Name = name.(string) 183 | } else if !ok && createOperation { 184 | return nil, fmt.Errorf("missing name of role") 185 | } 186 | 187 | var roleType string 188 | 189 | if rt, ok := d.GetOk("role_type"); ok { 190 | roleType = rt.(string) 191 | roleEntry.RoleType = roleType 192 | } else if !ok && createOperation { 193 | return nil, fmt.Errorf("missing role type. must be either `user` or `worker`") 194 | } 195 | 196 | if roleType != "user" && roleType != "worker" { 197 | return logical.ErrorResponse("must be set to either `user` or `worker`"), nil 198 | } 199 | 200 | var boundaryRoles interface{} 201 | 202 | // Check there is a list of boundary roles 203 | if boundaryRoles, ok = d.GetOk("boundary_roles"); ok { 204 | roleEntry.BoundaryRoles = boundaryRoles.(string) 205 | } 206 | 207 | if roleType == "user" && boundaryRoles == nil { 208 | return nil, fmt.Errorf("missing boundary_roles in role") 209 | } 210 | 211 | // Check there is an auth method id for user role 212 | 213 | var authMethodID interface{} 214 | if authMethodID, ok = d.GetOk("auth_method_id"); ok { 215 | roleEntry.AuthMethodID = authMethodID.(string) 216 | } 217 | 218 | if roleType == "user" && authMethodID == "" { 219 | return nil, fmt.Errorf("missing auth_method_id in role") 220 | } 221 | 222 | // Check there is a scope id 223 | if scopeId, ok := d.GetOk("scope_id"); ok { 224 | roleEntry.ScopeId = scopeId.(string) 225 | } else if !ok && createOperation { 226 | return nil, fmt.Errorf("missing scope_id in role") 227 | } 228 | 229 | if ttlRaw, ok := d.GetOk("ttl"); ok { 230 | roleEntry.TTL = time.Duration(ttlRaw.(int)) * time.Second 231 | } else if createOperation { 232 | roleEntry.TTL = time.Duration(d.Get("ttl").(int)) * time.Second 233 | } 234 | 235 | if maxTTLRaw, ok := d.GetOk("max_ttl"); ok { 236 | roleEntry.MaxTTL = time.Duration(maxTTLRaw.(int)) * time.Second 237 | } else if createOperation { 238 | roleEntry.MaxTTL = time.Duration(d.Get("max_ttl").(int)) * time.Second 239 | } 240 | 241 | if roleEntry.MaxTTL != 0 && roleEntry.TTL > roleEntry.MaxTTL { 242 | return logical.ErrorResponse("ttl cannot be greater than max_ttl"), nil 243 | } 244 | 245 | if err := setRole(ctx, req.Storage, name.(string), roleEntry); err != nil { 246 | return nil, err 247 | } 248 | 249 | return nil, nil 250 | } 251 | 252 | func (b *boundaryBackend) pathRolesDelete(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 253 | err := req.Storage.Delete(ctx, "role/"+d.Get("name").(string)) 254 | if err != nil { 255 | return nil, fmt.Errorf("error deleting boundary role: %w", err) 256 | } 257 | 258 | return nil, nil 259 | } 260 | 261 | func (b *boundaryBackend) pathRolesList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 262 | entries, err := req.Storage.List(ctx, "role/") 263 | if err != nil { 264 | return nil, err 265 | } 266 | 267 | return logical.ListResponse(entries), nil 268 | } 269 | -------------------------------------------------------------------------------- /path_roles_test.go: -------------------------------------------------------------------------------- 1 | package boundarysecrets 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | "testing" 7 | 8 | "github.com/hashicorp/vault/sdk/logical" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | const ( 13 | roleName = "testboundary" 14 | auth_method_id = "ampw_1234567890" 15 | boundary_roles = "r_cbvEFZbN1S,r_r8mxdp7zOp" 16 | scope_id = "global" 17 | credential_type = "userpass" 18 | testTTL = int64(120) 19 | testMaxTTL = int64(3600) 20 | roleType = "user" 21 | workerRoleName = "testboundaryworker" 22 | ) 23 | 24 | // TestUserRole uses a mock backend to check 25 | // role create, read, update, and delete. 26 | func TestUserRole(t *testing.T) { 27 | b, s := getTestBackend(t) 28 | 29 | t.Run("List All Roles", func(t *testing.T) { 30 | for i := 1; i <= 10; i++ { 31 | _, err := testTokenRoleCreate(t, b, s, 32 | roleName+strconv.Itoa(i), 33 | map[string]interface{}{ 34 | "boundary_roles": boundary_roles, 35 | "scope_id": scope_id, 36 | "user_credential_type": credential_type, 37 | "auth_method_id": auth_method_id, 38 | "ttl": testTTL, 39 | "max_ttl": testMaxTTL, 40 | "role_type": roleType, 41 | }) 42 | require.NoError(t, err) 43 | } 44 | 45 | resp, err := testTokenRoleList(t, b, s) 46 | require.NoError(t, err) 47 | require.Len(t, resp.Data["keys"].([]string), 10) 48 | }) 49 | 50 | t.Run("Create User Role - pass", func(t *testing.T) { 51 | resp, err := testTokenRoleCreate(t, b, s, roleName, map[string]interface{}{ 52 | "boundary_roles": boundary_roles, 53 | "scope_id": scope_id, 54 | "user_credential_type": credential_type, 55 | "auth_method_id": auth_method_id, 56 | "ttl": testTTL, 57 | "max_ttl": testMaxTTL, 58 | //"login_name": loginName, 59 | "role_type": roleType, 60 | }) 61 | 62 | require.Nil(t, err) 63 | require.Nil(t, resp.Error()) 64 | require.Nil(t, resp) 65 | }) 66 | 67 | t.Run("Read User Role", func(t *testing.T) { 68 | resp, err := testTokenRoleRead(t, b, s) 69 | 70 | require.Nil(t, err) 71 | require.Nil(t, resp.Error()) 72 | require.NotNil(t, resp) 73 | require.Equal(t, resp.Data["boundary_roles"], boundary_roles) 74 | require.Equal(t, resp.Data["auth_method_id"], auth_method_id) 75 | require.Equal(t, resp.Data["scope_id"], scope_id) 76 | //require.Equal(t, resp.Data["credential_type"], credential_type) 77 | }) 78 | t.Run("Update User Role", func(t *testing.T) { 79 | resp, err := testTokenRoleUpdate(t, b, s, map[string]interface{}{ 80 | "ttl": "1m", 81 | "max_ttl": "5h", 82 | "boundary_roles": "r_bauDEYaM2R", 83 | "scope_id": "0_1234567890", 84 | "auth_method_id": "ampw_0987654321", 85 | "role_type": roleType, 86 | }) 87 | 88 | require.Nil(t, err) 89 | require.Nil(t, resp.Error()) 90 | require.Nil(t, resp) 91 | }) 92 | 93 | t.Run("Re-read User Role", func(t *testing.T) { 94 | resp, err := testTokenRoleRead(t, b, s) 95 | 96 | require.Nil(t, err) 97 | require.Nil(t, resp.Error()) 98 | require.NotNil(t, resp) 99 | require.Equal(t, resp.Data["boundary_roles"], "r_bauDEYaM2R") 100 | require.Equal(t, resp.Data["scope_id"], "0_1234567890") 101 | require.Equal(t, resp.Data["auth_method_id"], "ampw_0987654321") 102 | require.Equal(t, resp.Data["ttl"], float64(60)) 103 | require.Equal(t, resp.Data["max_ttl"], float64(18000)) 104 | }) 105 | 106 | t.Run("Delete User Role", func(t *testing.T) { 107 | _, err := testTokenRoleDelete(t, b, s) 108 | 109 | require.NoError(t, err) 110 | }) 111 | } 112 | 113 | func TestWorkerRole(t *testing.T) { 114 | b, s := getTestBackend(t) 115 | 116 | t.Run("List All Roles", func(t *testing.T) { 117 | for i := 1; i <= 10; i++ { 118 | _, err := testTokenRoleCreate(t, b, s, 119 | workerRoleName+strconv.Itoa(i), 120 | map[string]interface{}{ 121 | "scope_id": scope_id, 122 | "ttl": testTTL, 123 | "max_ttl": testMaxTTL, 124 | "role_type": "worker", 125 | }) 126 | require.NoError(t, err) 127 | } 128 | 129 | resp, err := testTokenRoleList(t, b, s) 130 | require.NoError(t, err) 131 | require.Len(t, resp.Data["keys"].([]string), 10) 132 | }) 133 | 134 | t.Run("Create Worker Role - pass", func(t *testing.T) { 135 | resp, err := testTokenRoleCreate(t, b, s, roleName, map[string]interface{}{ 136 | "scope_id": scope_id, 137 | "ttl": testTTL, 138 | "max_ttl": testMaxTTL, 139 | "role_type": "worker", 140 | }) 141 | 142 | require.Nil(t, err) 143 | require.Nil(t, resp.Error()) 144 | require.Nil(t, resp) 145 | }) 146 | 147 | t.Run("Read Worker Role", func(t *testing.T) { 148 | resp, err := testTokenRoleRead(t, b, s) 149 | 150 | require.Nil(t, err) 151 | require.Nil(t, resp.Error()) 152 | require.NotNil(t, resp) 153 | require.Equal(t, resp.Data["scope_id"], scope_id) 154 | //require.Equal(t, resp.Data["credential_type"], credential_type) 155 | }) 156 | t.Run("Update Worker Role", func(t *testing.T) { 157 | resp, err := testTokenRoleUpdate(t, b, s, map[string]interface{}{ 158 | "ttl": "1m", 159 | "max_ttl": "5h", 160 | "scope_id": "0_0987654321", 161 | "role_type": "worker", 162 | }) 163 | 164 | require.Nil(t, err) 165 | require.Nil(t, resp.Error()) 166 | require.Nil(t, resp) 167 | }) 168 | 169 | t.Run("Re-read Worker Role", func(t *testing.T) { 170 | resp, err := testTokenRoleRead(t, b, s) 171 | 172 | require.Nil(t, err) 173 | require.Nil(t, resp.Error()) 174 | require.NotNil(t, resp) 175 | require.Equal(t, resp.Data["scope_id"], "0_0987654321") 176 | require.Equal(t, resp.Data["ttl"], float64(60)) 177 | require.Equal(t, resp.Data["max_ttl"], float64(18000)) 178 | }) 179 | 180 | t.Run("Delete Worker Role", func(t *testing.T) { 181 | _, err := testTokenRoleDelete(t, b, s) 182 | 183 | require.NoError(t, err) 184 | }) 185 | } 186 | 187 | // Utility function to create a role while, returning any response (including errors) 188 | func testTokenRoleCreate(t *testing.T, b *boundaryBackend, s logical.Storage, name string, d map[string]interface{}) (*logical.Response, error) { 189 | t.Helper() 190 | resp, err := b.HandleRequest(context.Background(), &logical.Request{ 191 | Operation: logical.CreateOperation, 192 | Path: "role/" + name, 193 | Data: d, 194 | Storage: s, 195 | }) 196 | 197 | if err != nil { 198 | return nil, err 199 | } 200 | 201 | return resp, nil 202 | } 203 | 204 | // Utility function to update a role while, returning any response (including errors) 205 | func testTokenRoleUpdate(t *testing.T, b *boundaryBackend, s logical.Storage, d map[string]interface{}) (*logical.Response, error) { 206 | t.Helper() 207 | resp, err := b.HandleRequest(context.Background(), &logical.Request{ 208 | Operation: logical.UpdateOperation, 209 | Path: "role/" + roleName, 210 | Data: d, 211 | Storage: s, 212 | }) 213 | 214 | if err != nil { 215 | return nil, err 216 | } 217 | 218 | if resp != nil && resp.IsError() { 219 | t.Fatal(resp.Error()) 220 | } 221 | return resp, nil 222 | } 223 | 224 | // Utility function to read a role and return any errors 225 | func testTokenRoleRead(t *testing.T, b *boundaryBackend, s logical.Storage) (*logical.Response, error) { 226 | t.Helper() 227 | return b.HandleRequest(context.Background(), &logical.Request{ 228 | Operation: logical.ReadOperation, 229 | Path: "role/" + roleName, 230 | Storage: s, 231 | }) 232 | } 233 | 234 | // Utility function to list roles and return any errors 235 | func testTokenRoleList(t *testing.T, b *boundaryBackend, s logical.Storage) (*logical.Response, error) { 236 | t.Helper() 237 | return b.HandleRequest(context.Background(), &logical.Request{ 238 | Operation: logical.ListOperation, 239 | Path: "role/", 240 | Storage: s, 241 | }) 242 | } 243 | 244 | // Utility function to delete a role and return any errors 245 | func testTokenRoleDelete(t *testing.T, b *boundaryBackend, s logical.Storage) (*logical.Response, error) { 246 | t.Helper() 247 | return b.HandleRequest(context.Background(), &logical.Request{ 248 | Operation: logical.DeleteOperation, 249 | Path: "role/" + roleName, 250 | Storage: s, 251 | }) 252 | } 253 | --------------------------------------------------------------------------------