Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c0d90a4ce | ||
|
|
ede5377188 | ||
|
|
1a83d0e4d1 | ||
|
|
96c688b879 | ||
|
|
f58bb092e2 | ||
|
|
e891c7ef2e | ||
|
|
55d8778edf | ||
|
|
bd9cc97362 | ||
|
|
340b6d29bf | ||
|
|
af1b81038b | ||
|
|
4bff2f69e9 | ||
|
|
312b22a1a7 | ||
|
|
f9819533db | ||
|
|
fb8180fe09 | ||
|
|
68c1db497f | ||
|
|
a162554f4f | ||
|
|
5e20d1005e | ||
|
|
cfc57d5e25 | ||
|
|
79951499ec | ||
|
|
f95954c83d | ||
|
|
1882beff3c | ||
|
|
807db1bef1 | ||
|
|
24bd9521f4 | ||
|
|
ef38f78c46 | ||
|
|
6255122fba | ||
|
|
622fca1c0e | ||
|
|
8aa93ccb69 | ||
|
|
1b96f89728 | ||
|
|
e90db9895c | ||
|
|
7ae01cc6e0 | ||
|
|
a134747539 | ||
|
|
893b150ec9 | ||
|
|
a61a13193c | ||
|
|
292204caf0 | ||
|
|
a2e04949be | ||
|
|
8fad44cc94 | ||
|
|
e0fb0a44e0 | ||
|
|
767a9ce1e6 | ||
|
|
f1d8cbf6e6 | ||
|
|
072c046b67 | ||
|
|
82ae1e3cd7 | ||
|
|
14bc6a4453 | ||
|
|
dc3aa09688 | ||
|
|
d6fb6d4c16 | ||
|
|
7164883b1b | ||
|
|
7f600f1cd5 | ||
|
|
145c91305c | ||
|
|
deb02086e3 | ||
|
|
445398286f | ||
|
|
66aad543f6 | ||
|
|
b491b4d123 | ||
|
|
bf34f12c56 | ||
|
|
0f4126d7d0 | ||
|
|
41a58f4db5 | ||
|
|
56f858006e | ||
|
|
48672fd050 | ||
|
|
5859c55f43 | ||
|
|
fe704abbfc | ||
|
|
e734090217 | ||
|
|
f9b5452b84 | ||
|
|
531dfa486a | ||
|
|
0bbc96fc11 | ||
|
|
f88fe43c12 | ||
|
|
48694a6bab |
No files matched your search
@@ -1,3 +1,6 @@
|
||||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
version: 2.1
|
||||
jobs:
|
||||
build:
|
||||
@@ -6,10 +9,6 @@ jobs:
|
||||
image: 'ubuntu-2004:202107-02'
|
||||
working_directory: ~/terraform-provider-watchtower
|
||||
steps:
|
||||
- add_ssh_keys:
|
||||
fingerprints:
|
||||
# hc-github-team-secure-boundary github user,TF_ACC=1 ~/.go/bin/go test ./... -v $(TESTARGS) -timeout 120m to read private modules
|
||||
- "d4:7f:b2:f7:6f:4d:e7:21:cc:d2:b7:6a:8b:68:e6:d4"
|
||||
- checkout
|
||||
- run:
|
||||
name: "Install go"
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
schema_version = 1
|
||||
|
||||
project {
|
||||
license = "MPL-2.0"
|
||||
|
||||
copyright_year = 2020
|
||||
|
||||
# (OPTIONAL) A list of globs that should not have copyright or license headers.
|
||||
# Supports doublestar glob patterns for more flexibility in defining which
|
||||
# files or folders should be ignored
|
||||
# Default: []
|
||||
header_ignore = [
|
||||
"examples/**.tf",
|
||||
"examples/**.sh",
|
||||
]
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
# See GitHub's docs for more information on this file:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
version: 2
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.JIRA_SYNC_GITHUB_TOKEN }}
|
||||
|
||||
- name: Login
|
||||
uses: atlassian/gajira-login@v2.0.0
|
||||
uses: atlassian/gajira-login@v3.0.1
|
||||
env:
|
||||
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
|
||||
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
|
||||
@@ -74,14 +74,14 @@ jobs:
|
||||
|
||||
- name: Close ticket
|
||||
if: (github.event.action == 'closed' || github.event.action == 'deleted') && steps.search.outputs.issue
|
||||
uses: atlassian/gajira-transition@v2.0.2
|
||||
uses: atlassian/gajira-transition@v3.0.1
|
||||
with:
|
||||
issue: ${{ steps.search.outputs.issue }}
|
||||
transition: Done
|
||||
|
||||
- name: Reopen ticket
|
||||
if: github.event.action == 'reopened' && steps.search.outputs.issue
|
||||
uses: atlassian/gajira-transition@v2.0.2
|
||||
uses: atlassian/gajira-transition@v3.0.1
|
||||
with:
|
||||
issue: ${{ steps.search.outputs.issue }}
|
||||
transition: "To Do"
|
||||
+1
-1
@@ -35,4 +35,4 @@ website/vendor
|
||||
*.winfile eol=crlf
|
||||
|
||||
# Compilation outputs
|
||||
/plugins/kms/assets/boundary-plugin*
|
||||
/plugins/kms/assets/*/*/boundary-plugin*
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.18.1
|
||||
1.20
|
||||
+4
-1
@@ -1,3 +1,6 @@
|
||||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
archives:
|
||||
- files:
|
||||
# Ensure only built binary is archived
|
||||
@@ -7,7 +10,7 @@ archives:
|
||||
before:
|
||||
hooks:
|
||||
- 'go mod download'
|
||||
- './scripts/plugins.sh'
|
||||
- './scripts/plugins-all.sh'
|
||||
builds:
|
||||
- # Binary naming only required for Terraform CLI 0.12
|
||||
binary: '{{ .ProjectName }}_v{{ .Version }}_x5'
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
url_source_repository = "https://github.com/hashicorp/terraform-provider-boundary"
|
||||
url_license = "https://github.com/hashicorp/terraform-provider-boundary/blob/main/LICENSE"
|
||||
+131
-1
@@ -1,3 +1,133 @@
|
||||
# Boundary Terraform Provider CHANGELOG
|
||||
|
||||
Canonical reference for changes, improvements, and bugfixes for the Boundary Terraform provider.
|
||||
|
||||
## 1.1.7 (May 12, 2023)
|
||||
|
||||
### Bug Fix
|
||||
* Fix default auth method with recovery kms
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/405))
|
||||
|
||||
## 1.1.6 (May 5, 2023)
|
||||
|
||||
### New and Improved
|
||||
* Add support for using default auth method if no auth method ID is provided for provider
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/385))
|
||||
* Fix typo in Managed Group resource page
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/370))
|
||||
|
||||
### Bug Fix
|
||||
* Force new resource on credential_type change
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/389))
|
||||
|
||||
## 1.1.5 (April 21, 2023)
|
||||
|
||||
### New and Improved
|
||||
* Add support for credential store vault worker filters ([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/375))
|
||||
|
||||
### Bug Fix
|
||||
* Allow users to set OIDC maxAge value to 0 to require immediate reauth ([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/364))
|
||||
|
||||
## 1.1.4 (February 15, 2023)
|
||||
|
||||
### New and Improved
|
||||
* Add support for worker egress and ingress filters ([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/319))
|
||||
* Add support for vault ssh certificate credential libraries ([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/320))
|
||||
* Add support for targets with address configurations ([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/308))
|
||||
|
||||
## 1.1.3 (November 29, 2022)
|
||||
|
||||
### New and Improved
|
||||
|
||||
* Add support for a workers as a resource
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/293)).
|
||||
|
||||
## 1.1.2 (October 18, 2022)
|
||||
|
||||
### New and Improved
|
||||
|
||||
* Add support for setting mapping overrides for vault credential libraries
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/287)).
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Improve error message when authenticating to boundary
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/290)).
|
||||
* Set state before returning an error when creating a resource
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/289))
|
||||
|
||||
## 1.1.1 (October 12, 2022)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* The plugin cleanup function is being called before the entire Terraform workflow is complete.
|
||||
([Issue](https://github.com/hashicorp/terraform-provider-boundary/issues/282)),
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/285)).
|
||||
|
||||
## 1.1.0 (October 4, 2022)
|
||||
|
||||
### New and Improved
|
||||
|
||||
* Add support for JSON credentials
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/271)).
|
||||
* Add support for setting the plugin execution directory from the config
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/280)).
|
||||
|
||||
### Deprecations/Changes
|
||||
|
||||
* Fix panic resulting from expired Vault credential store tokens
|
||||
([Issue](https://github.com/hashicorp/terraform-provider-boundary/issues/279),
|
||||
[PR](https://github.com/hashicorp/terraform-provider-boundary/pull/277)).
|
||||
* Remove `application_credential_source_ids` of the `target` resource which was deprecated
|
||||
in 1.0.12 ([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/273)).
|
||||
* Remove `default_role` from the `role` resource, this schema was never supported and was
|
||||
included mistakenly ([Issue](https://github.com/hashicorp/terraform-provider-boundary/issues/130),
|
||||
[PR](https://github.com/hashicorp/terraform-provider-boundary/pull/269)).
|
||||
|
||||
## 1.0.12 (September 13, 2022)
|
||||
|
||||
### New and Improved
|
||||
|
||||
* Add support for SSH targets
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/264)).
|
||||
|
||||
### Deprecations/Changes
|
||||
|
||||
* Deprecate `application_credential_source_ids` of the `target` resource
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/260)).
|
||||
|
||||
## 1.0.11 (August 26, 2022)
|
||||
|
||||
### New and Improved
|
||||
|
||||
* Add support for SSH private key credentials
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/257)).
|
||||
* Add support for credential type in Vault libraries
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/257)).
|
||||
|
||||
## 1.0.10 (August 10, 2022)
|
||||
|
||||
### New and Improved
|
||||
|
||||
* Adds support for static credential stores
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/236)).
|
||||
* Adds support for username password credentials
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/242)).
|
||||
|
||||
## 1.0.9 (June 6, 2022)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* The bug fix released in 1.0.8 to resolve the `plugin is nil` error only worked for
|
||||
Linux AMD64. This was due to a build issue where the plugin binaries were only built for
|
||||
Linux AMD64. Other platforms would receive an error similar to:
|
||||
|
||||
Error: error reading wrappers from "recovery_kms_hcl":
|
||||
Error configuring kms: error fetching kms plugin rpc client:
|
||||
fork/exec boundary-plugin-kms-awskms.gz: exec format error
|
||||
([Issue](https://github.com/hashicorp/terraform-provider-boundary/issues/209)),
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/216)).
|
||||
|
||||
## 1.0.8 (June 1, 2022)
|
||||
|
||||
### Bug Fixes
|
||||
@@ -110,4 +240,4 @@ Update provider to handle new domain errors ([PR](https://github.com/hashicorp/t
|
||||
|
||||
## 0.1.0 (October 14, 2020)
|
||||
|
||||
Initial release!
|
||||
Initial release!
|
||||
+4
-1
@@ -1,4 +1,7 @@
|
||||
FROM golang:1.18
|
||||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
FROM golang:1.20
|
||||
|
||||
WORKDIR /go/src/terraform-provider-boundary
|
||||
COPY . .
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
Copyright (c) 2020 HashiCorp, Inc.
|
||||
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ install-go: ~/.go/bin/go
|
||||
./ci/goinstall.sh
|
||||
|
||||
dev:
|
||||
./scripts/plugins.sh
|
||||
GOOS=${GOOS} GOARCH=${GOARCH} ./scripts/plugins.sh
|
||||
mkdir -p $(INSTALL_PATH)
|
||||
go build -o $(INSTALL_PATH)/terraform-provider-boundary main.go
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ Requirements
|
||||
------------
|
||||
|
||||
- [Terraform](https://www.terraform.io/downloads.html) >= 0.12.x
|
||||
- [Go](https://golang.org/doc/install) >= 1.18
|
||||
- [Go](https://golang.org/doc/install) >= 1.20
|
||||
|
||||
Building The Provider
|
||||
---------------------
|
||||
|
||||
+4
-1
@@ -1,7 +1,10 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
set -e
|
||||
|
||||
VERSION="1.18.1"
|
||||
VERSION="1.19.1"
|
||||
|
||||
[ -z "$GOROOT" ] && GOROOT="$HOME/.go"
|
||||
[ -z "$GOPATH" ] && GOPATH="$HOME/go"
|
||||
|
||||
@@ -20,6 +20,19 @@ provider "boundary" {
|
||||
password_auth_method_login_name = "myuser" # changeme
|
||||
password_auth_method_password = "passpass" # changeme
|
||||
}
|
||||
|
||||
provider "boundary" {
|
||||
addr = "http://127.0.0.1:9200"
|
||||
password_auth_method_login_name = "myuser"
|
||||
password_auth_method_password = "passpass"
|
||||
}
|
||||
|
||||
provider "boundary" {
|
||||
addr = "http://127.0.0.1:9200"
|
||||
password_auth_method_login_name = "myuser"
|
||||
password_auth_method_password = "passpass"
|
||||
scope_id = "s_1234567890"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
@@ -34,6 +47,8 @@ provider "boundary" {
|
||||
- `auth_method_id` (String) The auth method ID e.g. ampw_1234567890
|
||||
- `password_auth_method_login_name` (String) The auth method login name for password-style auth methods
|
||||
- `password_auth_method_password` (String) The auth method password for password-style auth methods
|
||||
- `plugin_execution_dir` (String) Specifies a directory that the Boundary provider can use to write and execute its built-in plugins.
|
||||
- `recovery_kms_hcl` (String) Can be a heredoc string or a path on disk. If set, the string/file will be parsed as HCL and used with the recovery KMS mechanism. While this is set, it will override any other authentication information; the KMS mechanism will always be used. See Boundary's KMS docs for examples: https://boundaryproject.io/docs/configuration/kms
|
||||
- `scope_id` (String) The scope ID for the default auth method.
|
||||
- `tls_insecure` (Boolean) When set to true, does not validate the Boundary API endpoint certificate
|
||||
- `token` (String) The Boundary token to use, as a string or path on disk containing just the string. If set, the token read here will be used in place of authenticating with the auth method specified in "auth_method_id", although the recovery KMS mechanism will still override this. Can also be set with the BOUNDARY_TOKEN environment variable.
|
||||
@@ -3,12 +3,12 @@
|
||||
page_title: "boundary_account Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
Deprecated: use resource_account_password instead.
|
||||
Deprecated: use boundary_account_password instead.
|
||||
---
|
||||
|
||||
# boundary_account (Resource)
|
||||
|
||||
Deprecated: use `resource_account_password` instead.
|
||||
Deprecated: use `boundary_account_password` instead.
|
||||
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ Deprecated: use `resource_account_password` instead.
|
||||
- `description` (String) The account description.
|
||||
- `login_name` (String) The login name for this account.
|
||||
- `name` (String) The account name. Defaults to the resource name.
|
||||
- `password` (String) The account password. Only set on create, changes will not be reflected when updating account.
|
||||
- `password` (String, Sensitive) The account password. Only set on create, changes will not be reflected when updating account.
|
||||
|
||||
### Read-Only
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ resource "boundary_account_password" "jeff" {
|
||||
- `description` (String) The account description.
|
||||
- `login_name` (String) The login name for this account.
|
||||
- `name` (String) The account name. Defaults to the resource name.
|
||||
- `password` (String) The account password. Only set on create, changes will not be reflected when updating account.
|
||||
- `password` (String, Sensitive) The account password. Only set on create, changes will not be reflected when updating account.
|
||||
|
||||
### Read-Only
|
||||
|
||||
|
||||
@@ -27,14 +27,14 @@ The OIDC auth method resource allows you to configure a Boundary auth_method_oid
|
||||
- `callback_url` (String) The URL that should be provided to the IdP for callbacks.
|
||||
- `claims_scopes` (List of String) Claims scopes.
|
||||
- `client_id` (String) The client ID assigned to this auth method from the provider.
|
||||
- `client_secret` (String) The secret key assigned to this auth method from the provider. Once set, only the hash will be kept and the original value can be removed from configuration.
|
||||
- `client_secret` (String, Sensitive) The secret key assigned to this auth method from the provider. Once set, only the hash will be kept and the original value can be removed from configuration.
|
||||
- `client_secret_hmac` (String) The HMAC of the client secret returned by the Boundary controller, which is used for comparison after initial setting of the value.
|
||||
- `description` (String) The auth method description.
|
||||
- `disable_discovered_config_validation` (Boolean) Disables validation logic ensuring that the OIDC provider's information from its discovery endpoint matches the information here. The validation is only performed at create or update time.
|
||||
- `idp_ca_certs` (List of String) A list of CA certificates to trust when validating the IdP's token signatures.
|
||||
- `is_primary_for_scope` (Boolean) When true, makes this auth method the primary auth method for the scope in which it resides. The primary auth method for a scope means the the user will be automatically created when they login using an OIDC account.
|
||||
- `is_primary_for_scope` (Boolean) When true, makes this auth method the primary auth method for the scope in which it resides. The primary auth method for a scope means the user will be automatically created when they login using an OIDC account.
|
||||
- `issuer` (String) The issuer corresponding to the provider, which must match the issuer field in generated tokens.
|
||||
- `max_age` (Number) The max age to provide to the provider, indicating how much time is allowed to have passed since the last authentication before the user is challenged again.
|
||||
- `max_age` (Number) The max age to provide to the provider, indicating how much time is allowed to have passed since the last authentication before the user is challenged again. A value of 0 sets an immediate requirement for all users to reauthenticate, and an unset maxAge results in a Terraform value of -1 and the default TTL of the chosen OIDC will be used.
|
||||
- `name` (String) The auth method name. Defaults to the resource name.
|
||||
- `signing_algorithms` (List of String) Allowed signing algorithms for the provider's issued tokens.
|
||||
- `state` (String) Can be one of 'inactive', 'active-private', or 'active-public'. Currently automatically set to active-public.
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_credential_json Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The json credential resource allows you to congiure a credential using a json object.
|
||||
---
|
||||
|
||||
# boundary_credential_json (Resource)
|
||||
|
||||
The json credential resource allows you to congiure a credential using a json object.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "global scope"
|
||||
scope_id = "global"
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_credential_store_static" "example" {
|
||||
name = "example_static_credential_store"
|
||||
description = "My first static credential store!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_credential_json" "example" {
|
||||
name = "example_json"
|
||||
description = "My first json credential!"
|
||||
credential_store_id = boundary_credential_store_static.example.id
|
||||
object = file("~/object.json") # change to valid json file
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `credential_store_id` (String) The credential store in which to save this json credential.
|
||||
- `object` (String, Sensitive) The object for the this json credential. Either values encoded with the "jsonencode" function, pre-escaped JSON string, or a file
|
||||
|
||||
### Optional
|
||||
|
||||
- `description` (String) The description of this json credential.
|
||||
- `name` (String) The name of this json credential. Defaults to the resource name.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `id` (String) The ID of this json credential.
|
||||
- `object_hmac` (String) The object hmac.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_credential_json.example_json <my-id>
|
||||
```
|
||||
@@ -56,6 +56,33 @@ resource "boundary_credential_library_vault" "bar" {
|
||||
}
|
||||
EOT
|
||||
}
|
||||
|
||||
resource "boundary_credential_library_vault" "baz" {
|
||||
name = "baz"
|
||||
description = "vault username password credential with mapping overrides"
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/baz" # change to Vault backend path
|
||||
http_method = "GET"
|
||||
credential_type = "username_password"
|
||||
credential_mapping_overrides = {
|
||||
password_attribute = "alternative_password_label"
|
||||
username_attribute = "alternative_username_label"
|
||||
}
|
||||
}
|
||||
|
||||
resource "boundary_credential_library_vault" "quz" {
|
||||
name = "quz"
|
||||
description = "vault ssh private key credential with mapping overrides"
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/quz" # change to Vault backend path
|
||||
http_method = "GET"
|
||||
credential_type = "ssh_private_key"
|
||||
credential_mapping_overrides = {
|
||||
private_key_attribute = "alternative_key_label"
|
||||
private_key_passphrase_attribute = "alternative_passphrase_label"
|
||||
username_attribute = "alternative_username_label"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
@@ -68,6 +95,8 @@ EOT
|
||||
|
||||
### Optional
|
||||
|
||||
- `credential_mapping_overrides` (Map of String) The credential mapping override.
|
||||
- `credential_type` (String) The type of credential the library generates.
|
||||
- `description` (String) The Vault credential library description.
|
||||
- `http_method` (String) The HTTP method the library uses when requesting credentials from Vault. Defaults to 'GET'
|
||||
- `http_request_body` (String) The body of the HTTP request the library sends to Vault when requesting credentials. Only valid if `http_method` is set to `POST`.
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_credential_library_vault_ssh_certificate Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The credential library for Vault resource allows you to configure a Boundary credential library for Vault.
|
||||
---
|
||||
|
||||
# boundary_credential_library_vault_ssh_certificate (Resource)
|
||||
|
||||
The credential library for Vault resource allows you to configure a Boundary credential library for Vault.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "My first scope!"
|
||||
scope_id = "global"
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_credential_store_vault" "foo" {
|
||||
name = "foo"
|
||||
description = "My first Vault credential store!"
|
||||
address = "http://127.0.0.1:8200" # change to Vault address
|
||||
token = "s.0ufRo6XEGU2jOqnIr7OlFYP5" # change to valid Vault token
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_credential_library_vault_ssh_certificate" "foo" {
|
||||
name = "foo"
|
||||
description = "My first Vault SSH certificate credential library!"
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "ssh/sign/foo" # change to correct Vault endpoint and role
|
||||
username = "foo" # change to valid username
|
||||
}
|
||||
|
||||
resource "boundary_credential_library_vault_ssh_certificate" "bar" {
|
||||
name = "bar"
|
||||
description = "My second Vault SSH certificate credential library!"
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "ssh/sign/foo" # change to correct Vault endpoint and role
|
||||
username = "foo"
|
||||
key_type = "ecdsa"
|
||||
key_bits = 384
|
||||
|
||||
extensions = {
|
||||
permit-pty = ""
|
||||
}
|
||||
}
|
||||
|
||||
resource "boundary_credential_library_vault_ssh_certificate" "baz" {
|
||||
name = "baz"
|
||||
description = "vault "
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "ssh/issue/foo" # change to correct Vault endpoint and role
|
||||
username = "foo"
|
||||
key_type = "rsa"
|
||||
key_bits = 4096
|
||||
|
||||
extensions = {
|
||||
permit-pty = ""
|
||||
permit-X11-forwarding = ""
|
||||
}
|
||||
|
||||
critical_options = {
|
||||
force-command = "/bin/some_script"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `credential_store_id` (String) The ID of the credential store that this library belongs to.
|
||||
- `path` (String) The path in Vault to request credentials from.
|
||||
- `username` (String) The username to use with the certificate returned by the library.
|
||||
|
||||
### Optional
|
||||
|
||||
- `critical_options` (Map of String) Specifies a map of the critical options that the certificate should be signed for.
|
||||
- `description` (String) The Vault credential library description.
|
||||
- `extensions` (Map of String) Specifies a map of the extensions that the certificate should be signed for.
|
||||
- `key_bits` (Number) Specifies the number of bits to use for the generated keys.
|
||||
- `key_id` (String) Specifies the key id a certificate should have.
|
||||
- `key_type` (String) Specifies the desired key type; must be ed25519, ecdsa, or rsa.
|
||||
- `name` (String) The Vault credential library name. Defaults to the resource name.
|
||||
- `ttl` (String) Specifies the requested time to live for a certificate returned from the library.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `id` (String) The ID of the Vault credential library.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_credential_library_vault_ssh_certificate.foo <my-id>
|
||||
```
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_credential_ssh_private_key Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The SSH private key credential resource allows you to configure a credential using a username, private key and optional passphrase.
|
||||
---
|
||||
|
||||
# boundary_credential_ssh_private_key (Resource)
|
||||
|
||||
The SSH private key credential resource allows you to configure a credential using a username, private key and optional passphrase.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "global scope"
|
||||
scope_id = "global"
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_credential_store_static" "example" {
|
||||
name = "example_static_credential_store"
|
||||
description = "My first static credential store!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_credential_ssh_private_key" "example" {
|
||||
name = "example_ssh_private_key"
|
||||
description = "My first ssh private key credential!"
|
||||
credential_store_id = boundary_credential_store_static.example.id
|
||||
username = "my-username"
|
||||
private_key = file("~/.ssh/id_rsa") # change to valid SSH Private Key
|
||||
private_key_passphrase = "optional-passphrase" # change to the passphrase of the Private Key if required
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `credential_store_id` (String) ID of the credential store this credential belongs to.
|
||||
- `private_key` (String, Sensitive) The private key associated with the credential.
|
||||
- `username` (String) The username associated with the credential.
|
||||
|
||||
### Optional
|
||||
|
||||
- `description` (String) The description of the credential.
|
||||
- `name` (String) The name of the credential. Defaults to the resource name.
|
||||
- `private_key_passphrase` (String, Sensitive) The passphrase of the private key associated with the credential.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `id` (String) The ID of the credential.
|
||||
- `private_key_hmac` (String) The private key hmac.
|
||||
- `private_key_passphrase_hmac` (String) The private key passphrase hmac.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_credential_ssh_private_key.example_ssh_private_key <my-id>
|
||||
```
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_credential_store_static Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The static credential store resource allows you to configure a Boundary static credential store.
|
||||
---
|
||||
|
||||
# boundary_credential_store_static (Resource)
|
||||
|
||||
The static credential store resource allows you to configure a Boundary static credential store.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "global scope"
|
||||
scope_id = "global"
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_credential_store_static" "example" {
|
||||
name = "example_static_credential_store"
|
||||
description = "My first static credential store!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `scope_id` (String) The scope for this credential store.
|
||||
|
||||
### Optional
|
||||
|
||||
- `description` (String) The static credential store description.
|
||||
- `name` (String) The static credential store name. Defaults to the resource name.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `id` (String) The ID of the static credential store.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_credential_store_static.example_static_credential_store <my-id>
|
||||
```
|
||||
@@ -56,6 +56,7 @@ resource "boundary_credential_store_vault" "example" {
|
||||
- `namespace` (String) The namespace within Vault to use.
|
||||
- `tls_server_name` (String) Name to use as the SNI host when connecting to Vault via TLS.
|
||||
- `tls_skip_verify` (Boolean) Whether or not to skip TLS verification.
|
||||
- `worker_filter` (String) HCP Only. A filter used to control which PKI workers can handle Vault requests. This allows the use of private Vault instances with Boundary.
|
||||
|
||||
### Read-Only
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_credential_username_password Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The username/password credential resource allows you to configure a credential using a username and password pair.
|
||||
---
|
||||
|
||||
# boundary_credential_username_password (Resource)
|
||||
|
||||
The username/password credential resource allows you to configure a credential using a username and password pair.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "global scope"
|
||||
scope_id = "global"
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_credential_store_static" "example" {
|
||||
name = "example_static_credential_store"
|
||||
description = "My first static credential store!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_credential_username_password" "example" {
|
||||
name = "example_username_password"
|
||||
description = "My first username password credential!"
|
||||
credential_store_id = boundary_credential_store_static.example.id
|
||||
username = "my-username"
|
||||
password = "my-password"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `credential_store_id` (String) The credential store in which to save this username/password credential.
|
||||
- `password` (String, Sensitive) The password of this username/password credential.
|
||||
- `username` (String) The username of this username/password credential.
|
||||
|
||||
### Optional
|
||||
|
||||
- `description` (String) The description of this username/password credential.
|
||||
- `name` (String) The name of this username/password credential. Defaults to the resource name.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `id` (String) The ID of this username/password credential.
|
||||
- `password_hmac` (String) The password hmac.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_credential_username_password.example_username_password <my-id>
|
||||
```
|
||||
@@ -3,12 +3,12 @@
|
||||
page_title: "boundary_host Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
Deprecated: use resource_host_static instead.
|
||||
Deprecated: use boundary_host_static instead.
|
||||
---
|
||||
|
||||
# boundary_host (Resource)
|
||||
|
||||
Deprecated: use `resource_host_static` instead.
|
||||
Deprecated: use `boundary_host_static` instead.
|
||||
|
||||
## Example Usage
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
page_title: "boundary_host_catalog Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
Deprecated: use resource_host_catalog_static instead.
|
||||
Deprecated: use boundary_host_catalog_static instead.
|
||||
---
|
||||
|
||||
# boundary_host_catalog (Resource)
|
||||
|
||||
Deprecated: use `resource_host_catalog_static` instead.
|
||||
Deprecated: use `boundary_host_catalog_static` instead.
|
||||
|
||||
## Example Usage
|
||||
|
||||
|
||||
@@ -10,7 +10,70 @@ description: |-
|
||||
|
||||
The host catalog resource allows you to configure a Boundary plugin-type host catalog. Host catalogs are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host catalog.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.global.id
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
# For more information about the aws plugin, please visit here:
|
||||
# https://github.com/hashicorp/boundary-plugin-host-aws
|
||||
#
|
||||
# For more information about aws users, please visit here:
|
||||
# https://learn.hashicorp.com/tutorials/boundary/aws-host-catalogs?in=boundary/oss-access-management#configure-terraform-and-iam-user-privileges
|
||||
resource "boundary_host_catalog_plugin" "aws_example" {
|
||||
name = "My aws catalog"
|
||||
description = "My first host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
plugin_name = "aws"
|
||||
attributes_json = jsonencode({ "region" = "us-east-1" })
|
||||
|
||||
# recommended to pass in aws secrets using a file() or using environment variables
|
||||
# the secrets below must be generated in aws by creating a aws iam user with programmatic access
|
||||
secrets_json = jsonencode({
|
||||
"access_key_id" = "aws_access_key_id_value",
|
||||
"secret_access_key" = "aws_secret_access_key_value"
|
||||
})
|
||||
}
|
||||
|
||||
# For more information about the azure plugin, please visit here:
|
||||
# https://github.com/hashicorp/boundary-plugin-host-azure
|
||||
#
|
||||
# For more information about azure ad applications, please visit here:
|
||||
# https://learn.hashicorp.com/tutorials/boundary/azure-host-catalogs#register-a-new-azure-ad-application-1
|
||||
resource "boundary_host_catalog_plugin" "azure_example" {
|
||||
name = "My azure catalog"
|
||||
description = "My second host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
plugin_name = "azure"
|
||||
|
||||
# the attributes below must be generated in azure by creating an ad application
|
||||
attributes_json = jsonencode({
|
||||
"disable_credential_rotation" = "true",
|
||||
"tenant_id" = "ARM_TENANT_ID",
|
||||
"subscription_id" = "ARM_SUBSCRIPTION_ID",
|
||||
"client_id" = "ARM_CLIENT_ID"
|
||||
})
|
||||
|
||||
# recommended to pass in aws secrets using a file() or using environment variables
|
||||
# the secrets below must be generated in azure by creating an ad application
|
||||
secrets_json = jsonencode({
|
||||
"secret_value" = "ARM_CLIENT_SECRET"
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
@@ -36,4 +99,10 @@ The host catalog resource allows you to configure a Boundary plugin-type host ca
|
||||
|
||||
- `id` (String) The ID of the host catalog.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_host_catalog_plugin.foo <my-id>
|
||||
```
|
||||
@@ -10,7 +10,30 @@ description: |-
|
||||
|
||||
The static host catalog resource allows you to configure a Boundary static-type host catalog. Host catalogs are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host catalog.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.global.id
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_host_catalog_static" "example" {
|
||||
name = "My catalog"
|
||||
description = "My first host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
@@ -28,4 +51,10 @@ The static host catalog resource allows you to configure a Boundary static-type
|
||||
|
||||
- `id` (String) The ID of the host catalog.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_host_catalog_static.foo <my-id>
|
||||
```
|
||||
@@ -3,12 +3,12 @@
|
||||
page_title: "boundary_host_set Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
Deprecated: use resource_host_set_static instead.
|
||||
Deprecated: use boundary_host_set_static instead.
|
||||
---
|
||||
|
||||
# boundary_host_set (Resource)
|
||||
|
||||
Deprecated: use `resource_host_set_static` instead.
|
||||
Deprecated: use `boundary_host_set_static` instead.
|
||||
|
||||
## Example Usage
|
||||
|
||||
|
||||
@@ -10,7 +10,113 @@ description: |-
|
||||
|
||||
The host_set_plugin resource allows you to configure a Boundary host set. Host sets are always part of a host catalog, so a host catalog resource should be used inline or you should have the host catalog ID in hand to successfully configure a host set.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "My first scope!"
|
||||
scope_id = "global"
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
# For more information about the aws plugin, please visit here:
|
||||
# https://github.com/hashicorp/boundary-plugin-host-aws
|
||||
#
|
||||
# For more information about aws users, please visit here:
|
||||
# https://learn.hashicorp.com/tutorials/boundary/aws-host-catalogs?in=boundary/oss-access-management#configure-terraform-and-iam-user-privileges
|
||||
resource "boundary_host_catalog_plugin" "aws_example" {
|
||||
name = "My aws catalog"
|
||||
description = "My first host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
plugin_name = "aws"
|
||||
attributes_json = jsonencode({ "region" = "us-east-1" })
|
||||
|
||||
# recommended to pass in aws secrets using a file() or using environment variables
|
||||
# the secrets below must be generated in aws by creating a aws iam user with programmatic access
|
||||
secrets_json = jsonencode({
|
||||
"access_key_id" = "aws_access_key_id_value",
|
||||
"secret_access_key" = "aws_secret_access_key_value"
|
||||
})
|
||||
}
|
||||
|
||||
resource "boundary_host_set_plugin" "web" {
|
||||
name = "My web host set plugin"
|
||||
host_catalog_id = boundary_host_catalog_plugin.aws_example.id
|
||||
attributes_json = jsonencode({ "filters" = "tag:service-type=web" })
|
||||
}
|
||||
|
||||
resource "boundary_host_set_plugin" "foobar" {
|
||||
name = "My foobar host set plugin"
|
||||
host_catalog_id = boundary_host_catalog_plugin.aws_example.id
|
||||
preferred_endpoints = ["cidr:54.0.0.0/8"]
|
||||
attributes_json = jsonencode({
|
||||
"filters" = "tag-key=foo",
|
||||
"filters" = "tag-key=bar"
|
||||
})
|
||||
}
|
||||
|
||||
resource "boundary_host_set_plugin" "launch" {
|
||||
name = "My launch host set plugin"
|
||||
host_catalog_id = boundary_host_catalog_plugin.aws_example.id
|
||||
sync_interval_seconds = 60
|
||||
attributes_json = jsonencode({
|
||||
"filters" = "tag:development=prod,dev",
|
||||
"filters" = "launch-time=2022-01-04T*"
|
||||
})
|
||||
}
|
||||
|
||||
# For more information about the azure plugin, please visit here:
|
||||
# https://github.com/hashicorp/boundary-plugin-host-azure
|
||||
#
|
||||
# For more information about azure ad applications, please visit here:
|
||||
# https://learn.hashicorp.com/tutorials/boundary/azure-host-catalogs#register-a-new-azure-ad-application-1
|
||||
resource "boundary_host_catalog_plugin" "azure_example" {
|
||||
name = "My azure catalog"
|
||||
description = "My second host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
plugin_name = "azure"
|
||||
|
||||
# the attributes below must be generated in azure by creating an ad application
|
||||
attributes_json = jsonencode({
|
||||
"disable_credential_rotation" = "true",
|
||||
"tenant_id" = "ARM_TENANT_ID",
|
||||
"subscription_id" = "ARM_SUBSCRIPTION_ID",
|
||||
"client_id" = "ARM_CLIENT_ID"
|
||||
})
|
||||
|
||||
# recommended to pass in aws secrets using a file() or using environment variables
|
||||
# the secrets below must be generated in azure by creating an ad application
|
||||
secrets_json = jsonencode({
|
||||
"secret_value" = "ARM_CLIENT_SECRET"
|
||||
})
|
||||
}
|
||||
|
||||
resource "boundary_host_set_plugin" "database" {
|
||||
name = "My database host set plugin"
|
||||
host_catalog_id = boundary_host_catalog_plugin.azure_example.id
|
||||
attributes_json = jsonencode({ "filter" = "tagName eq 'service-type' and tagValue eq 'database'" })
|
||||
}
|
||||
|
||||
resource "boundary_host_set_plugin" "foodev" {
|
||||
name = "My foodev host set plugin"
|
||||
host_catalog_id = boundary_host_catalog_plugin.azure_example.id
|
||||
preferred_endpoints = ["cidr:54.0.0.0/8"]
|
||||
sync_interval_seconds = 60
|
||||
attributes_json = jsonencode({
|
||||
"filter" = "tagName eq 'tag-key' and tagValue eq 'foo'",
|
||||
"filter" = "tagName eq 'application' and tagValue eq 'dev'",
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
@@ -32,4 +138,10 @@ The host_set_plugin resource allows you to configure a Boundary host set. Host s
|
||||
|
||||
- `id` (String) The ID of the host set.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_host_set_plugin.foo <my-id>
|
||||
```
|
||||
@@ -10,7 +10,50 @@ description: |-
|
||||
|
||||
The host_set_static resource allows you to configure a Boundary host set. Host sets are always part of a host catalog, so a host catalog resource should be used inline or you should have the host catalog ID in hand to successfully configure a host set.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "My first scope!"
|
||||
scope_id = "global"
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_host_catalog_static" "example" {
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_host_static" "first" {
|
||||
name = "host_1"
|
||||
description = "My first host!"
|
||||
address = "10.0.0.1"
|
||||
host_catalog_id = boundary_host_catalog_static.example.id
|
||||
}
|
||||
|
||||
resource "boundary_host_static" "second" {
|
||||
name = "host_2"
|
||||
description = "My second host!"
|
||||
address = "10.0.0.2"
|
||||
host_catalog_id = boundary_host_catalog_static.example.id
|
||||
}
|
||||
|
||||
resource "boundary_host_set_static" "web" {
|
||||
host_catalog_id = boundary_host_catalog_static.example.id
|
||||
host_ids = [
|
||||
boundary_host_static.first.id,
|
||||
boundary_host_static.second.id,
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
@@ -30,4 +73,10 @@ The host_set_static resource allows you to configure a Boundary host set. Host s
|
||||
|
||||
- `id` (String) The ID of the host set.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_host_set_static.foo <my-id>
|
||||
```
|
||||
@@ -10,7 +10,37 @@ description: |-
|
||||
|
||||
The static host resource allows you to configure a Boundary static host. Hosts are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "My first scope!"
|
||||
scope_id = "global"
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_host_catalog_static" "example" {
|
||||
name = "My catalog"
|
||||
description = "My first host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_host_static" "example" {
|
||||
name = "example_host"
|
||||
description = "My first host!"
|
||||
address = "10.0.0.1"
|
||||
host_catalog_id = boundary_host_catalog_static.example.id
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
@@ -30,4 +60,10 @@ The static host resource allows you to configure a Boundary static host. Hosts a
|
||||
|
||||
- `id` (String) The ID of the host.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_host_static.foo <my-id>
|
||||
```
|
||||
@@ -18,7 +18,7 @@ The managed group resource allows you to configure a Boundary group.
|
||||
### Required
|
||||
|
||||
- `auth_method_id` (String) The resource ID for the auth method.
|
||||
- `filter` (String) Boolean expression to filter the workers for this managed group.
|
||||
- `filter` (String) Boolean expression to filter the users for this managed group.
|
||||
|
||||
### Optional
|
||||
|
||||
@@ -28,5 +28,3 @@ The managed group resource allows you to configure a Boundary group.
|
||||
### Read-Only
|
||||
|
||||
- `id` (String) The ID of the group.
|
||||
|
||||
|
||||
@@ -126,7 +126,6 @@ resource "boundary_role" "readonly" {
|
||||
|
||||
### Optional
|
||||
|
||||
- `default_role` (Boolean) Indicates that the role containing this value is the default role (that is, has the id 'r_default'), which triggers some specialized behavior to allow it to be imported and managed.
|
||||
- `description` (String) The role description.
|
||||
- `grant_scope_id` (String)
|
||||
- `grant_strings` (Set of String) A list of stringified grants for the role.
|
||||
|
||||
@@ -47,6 +47,7 @@ resource "boundary_credential_library_vault" "foo" {
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/foo" # change to Vault backend path
|
||||
http_method = "GET"
|
||||
credential_type = "username_password"
|
||||
}
|
||||
|
||||
resource "boundary_host_catalog" "foo" {
|
||||
@@ -60,7 +61,6 @@ resource "boundary_host" "foo" {
|
||||
type = "static"
|
||||
name = "foo"
|
||||
host_catalog_id = boundary_host_catalog.foo.id
|
||||
scope_id = boundary_scope.project.id
|
||||
address = "10.0.0.1"
|
||||
}
|
||||
|
||||
@@ -68,7 +68,6 @@ resource "boundary_host" "bar" {
|
||||
type = "static"
|
||||
name = "bar"
|
||||
host_catalog_id = boundary_host_catalog.foo.id
|
||||
scope_id = boundary_scope.project.id
|
||||
address = "10.0.0.1"
|
||||
}
|
||||
|
||||
@@ -92,10 +91,33 @@ resource "boundary_target" "foo" {
|
||||
host_source_ids = [
|
||||
boundary_host_set.foo.id
|
||||
]
|
||||
application_credential_source_ids = [
|
||||
brokered_credential_source_ids = [
|
||||
boundary_credential_library_vault.foo.id
|
||||
]
|
||||
}
|
||||
|
||||
resource "boundary_target" "ssh_foo" {
|
||||
name = "ssh_foo"
|
||||
description = "Ssh target"
|
||||
type = "ssh"
|
||||
default_port = "22"
|
||||
scope_id = boundary_scope.project.id
|
||||
host_source_ids = [
|
||||
boundary_host_set.foo.id
|
||||
]
|
||||
injected_application_credential_source_ids = [
|
||||
boundary_credential_library_vault.foo.id
|
||||
]
|
||||
}
|
||||
|
||||
resource "boundary_target" "address_foo" {
|
||||
name = "address_foo"
|
||||
description = "Foo target with an address"
|
||||
type = "tcp"
|
||||
default_port = "22"
|
||||
scope_id = boundary_scope.project.id
|
||||
address = "127.0.0.1"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
@@ -108,14 +130,19 @@ resource "boundary_target" "foo" {
|
||||
|
||||
### Optional
|
||||
|
||||
- `application_credential_source_ids` (Set of String) A list of application credential source ID's.
|
||||
- `address` (String) Optionally, a valid network address to connect to for this target. Cannot be used alongside host_source_ids.
|
||||
- `brokered_credential_source_ids` (Set of String) A list of brokered credential source ID's.
|
||||
- `default_client_port` (Number) The default client port for this target.
|
||||
- `default_port` (Number) The default port for this target.
|
||||
- `description` (String) The target description.
|
||||
- `host_source_ids` (Set of String) A list of host source ID's.
|
||||
- `egress_worker_filter` (String) Boolean expression to filter the workers used to access this target
|
||||
- `host_source_ids` (Set of String) A list of host source ID's. Cannot be used alongside address.
|
||||
- `ingress_worker_filter` (String) HCP Only. Boolean expression to filter the workers a user will connect to when initiating a session against this target
|
||||
- `injected_application_credential_source_ids` (Set of String) A list of injected application credential source ID's.
|
||||
- `name` (String) The target name. Defaults to the resource name.
|
||||
- `session_connection_limit` (Number)
|
||||
- `session_max_seconds` (Number)
|
||||
- `worker_filter` (String) Boolean expression to filter the workers for this target
|
||||
- `worker_filter` (String, Deprecated) Boolean expression to filter the workers for this target
|
||||
|
||||
### Read-Only
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_worker Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The resource allows you to create a self-managed worker object.
|
||||
---
|
||||
|
||||
# boundary_worker (Resource)
|
||||
|
||||
The resource allows you to create a self-managed worker object.
|
||||
|
||||
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Optional
|
||||
|
||||
- `description` (String) The description for the worker.
|
||||
- `name` (String) The name for the worker.
|
||||
- `scope_id` (String) The scope for the worker. Defaults to `global`.
|
||||
- `worker_generated_auth_token` (String) The worker authentication token required to register the worker for the worker-led authentication flow. Leaving this blank will result in a controller generated token.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `address` (String) The accessible address of the self managed worker.
|
||||
- `authorized_actions` (List of String) A list of actions that the worker is entitled to perform.
|
||||
- `controller_generated_activation_token` (String) A single use token generated by the controller to be passed to the self-managed worker.
|
||||
- `id` (String) The ID of the worker.
|
||||
- `release_version` (Number) The version of the Boundary binary running on the self managed worker.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_worker.foo <my-id>
|
||||
```
|
||||
@@ -4,3 +4,16 @@ provider "boundary" {
|
||||
password_auth_method_login_name = "myuser" # changeme
|
||||
password_auth_method_password = "passpass" # changeme
|
||||
}
|
||||
|
||||
provider "boundary" {
|
||||
addr = "http://127.0.0.1:9200"
|
||||
password_auth_method_login_name = "myuser"
|
||||
password_auth_method_password = "passpass"
|
||||
}
|
||||
|
||||
provider "boundary" {
|
||||
addr = "http://127.0.0.1:9200"
|
||||
password_auth_method_login_name = "myuser"
|
||||
password_auth_method_password = "passpass"
|
||||
scope_id = "s_1234567890"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_credential_json.example_json <my-id>
|
||||
@@ -0,0 +1,27 @@
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "global scope"
|
||||
scope_id = "global"
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_credential_store_static" "example" {
|
||||
name = "example_static_credential_store"
|
||||
description = "My first static credential store!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_credential_json" "example" {
|
||||
name = "example_json"
|
||||
description = "My first json credential!"
|
||||
credential_store_id = boundary_credential_store_static.example.id
|
||||
object = file("~/object.json") # change to valid json file
|
||||
}
|
||||
@@ -41,3 +41,30 @@ resource "boundary_credential_library_vault" "bar" {
|
||||
}
|
||||
EOT
|
||||
}
|
||||
|
||||
resource "boundary_credential_library_vault" "baz" {
|
||||
name = "baz"
|
||||
description = "vault username password credential with mapping overrides"
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/baz" # change to Vault backend path
|
||||
http_method = "GET"
|
||||
credential_type = "username_password"
|
||||
credential_mapping_overrides = {
|
||||
password_attribute = "alternative_password_label"
|
||||
username_attribute = "alternative_username_label"
|
||||
}
|
||||
}
|
||||
|
||||
resource "boundary_credential_library_vault" "quz" {
|
||||
name = "quz"
|
||||
description = "vault ssh private key credential with mapping overrides"
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/quz" # change to Vault backend path
|
||||
http_method = "GET"
|
||||
credential_type = "ssh_private_key"
|
||||
credential_mapping_overrides = {
|
||||
private_key_attribute = "alternative_key_label"
|
||||
private_key_passphrase_attribute = "alternative_passphrase_label"
|
||||
username_attribute = "alternative_username_label"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_credential_library_vault_ssh_certificate.foo <my-id>
|
||||
@@ -0,0 +1,63 @@
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "My first scope!"
|
||||
scope_id = "global"
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_credential_store_vault" "foo" {
|
||||
name = "foo"
|
||||
description = "My first Vault credential store!"
|
||||
address = "http://127.0.0.1:8200" # change to Vault address
|
||||
token = "s.0ufRo6XEGU2jOqnIr7OlFYP5" # change to valid Vault token
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_credential_library_vault_ssh_certificate" "foo" {
|
||||
name = "foo"
|
||||
description = "My first Vault SSH certificate credential library!"
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "ssh/sign/foo" # change to correct Vault endpoint and role
|
||||
username = "foo" # change to valid username
|
||||
}
|
||||
|
||||
resource "boundary_credential_library_vault_ssh_certificate" "bar" {
|
||||
name = "bar"
|
||||
description = "My second Vault SSH certificate credential library!"
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "ssh/sign/foo" # change to correct Vault endpoint and role
|
||||
username = "foo"
|
||||
key_type = "ecdsa"
|
||||
key_bits = 384
|
||||
|
||||
extensions = {
|
||||
permit-pty = ""
|
||||
}
|
||||
}
|
||||
|
||||
resource "boundary_credential_library_vault_ssh_certificate" "baz" {
|
||||
name = "baz"
|
||||
description = "vault "
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "ssh/issue/foo" # change to correct Vault endpoint and role
|
||||
username = "foo"
|
||||
key_type = "rsa"
|
||||
key_bits = 4096
|
||||
|
||||
extensions = {
|
||||
permit-pty = ""
|
||||
permit-X11-forwarding = ""
|
||||
}
|
||||
|
||||
critical_options = {
|
||||
force-command = "/bin/some_script"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_credential_ssh_private_key.example_ssh_private_key <my-id>
|
||||
@@ -0,0 +1,29 @@
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "global scope"
|
||||
scope_id = "global"
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_credential_store_static" "example" {
|
||||
name = "example_static_credential_store"
|
||||
description = "My first static credential store!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_credential_ssh_private_key" "example" {
|
||||
name = "example_ssh_private_key"
|
||||
description = "My first ssh private key credential!"
|
||||
credential_store_id = boundary_credential_store_static.example.id
|
||||
username = "my-username"
|
||||
private_key = file("~/.ssh/id_rsa") # change to valid SSH Private Key
|
||||
private_key_passphrase = "optional-passphrase" # change to the passphrase of the Private Key if required
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_credential_store_static.example_static_credential_store <my-id>
|
||||
@@ -0,0 +1,20 @@
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "global scope"
|
||||
scope_id = "global"
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_credential_store_static" "example" {
|
||||
name = "example_static_credential_store"
|
||||
description = "My first static credential store!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_credential_username_password.example_username_password <my-id>
|
||||
@@ -0,0 +1,28 @@
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "global scope"
|
||||
scope_id = "global"
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_credential_store_static" "example" {
|
||||
name = "example_static_credential_store"
|
||||
description = "My first static credential store!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_credential_username_password" "example" {
|
||||
name = "example_username_password"
|
||||
description = "My first username password credential!"
|
||||
credential_store_id = boundary_credential_store_static.example.id
|
||||
username = "my-username"
|
||||
password = "my-password"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_host_catalog_plugin.foo <my-id>
|
||||
@@ -0,0 +1,60 @@
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.global.id
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
# For more information about the aws plugin, please visit here:
|
||||
# https://github.com/hashicorp/boundary-plugin-host-aws
|
||||
#
|
||||
# For more information about aws users, please visit here:
|
||||
# https://learn.hashicorp.com/tutorials/boundary/aws-host-catalogs?in=boundary/oss-access-management#configure-terraform-and-iam-user-privileges
|
||||
resource "boundary_host_catalog_plugin" "aws_example" {
|
||||
name = "My aws catalog"
|
||||
description = "My first host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
plugin_name = "aws"
|
||||
attributes_json = jsonencode({ "region" = "us-east-1" })
|
||||
|
||||
# recommended to pass in aws secrets using a file() or using environment variables
|
||||
# the secrets below must be generated in aws by creating a aws iam user with programmatic access
|
||||
secrets_json = jsonencode({
|
||||
"access_key_id" = "aws_access_key_id_value",
|
||||
"secret_access_key" = "aws_secret_access_key_value"
|
||||
})
|
||||
}
|
||||
|
||||
# For more information about the azure plugin, please visit here:
|
||||
# https://github.com/hashicorp/boundary-plugin-host-azure
|
||||
#
|
||||
# For more information about azure ad applications, please visit here:
|
||||
# https://learn.hashicorp.com/tutorials/boundary/azure-host-catalogs#register-a-new-azure-ad-application-1
|
||||
resource "boundary_host_catalog_plugin" "azure_example" {
|
||||
name = "My azure catalog"
|
||||
description = "My second host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
plugin_name = "azure"
|
||||
|
||||
# the attributes below must be generated in azure by creating an ad application
|
||||
attributes_json = jsonencode({
|
||||
"disable_credential_rotation" = "true",
|
||||
"tenant_id" = "ARM_TENANT_ID",
|
||||
"subscription_id" = "ARM_SUBSCRIPTION_ID",
|
||||
"client_id" = "ARM_CLIENT_ID"
|
||||
})
|
||||
|
||||
# recommended to pass in aws secrets using a file() or using environment variables
|
||||
# the secrets below must be generated in azure by creating an ad application
|
||||
secrets_json = jsonencode({
|
||||
"secret_value" = "ARM_CLIENT_SECRET"
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_host_catalog_static.foo <my-id>
|
||||
@@ -0,0 +1,20 @@
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.global.id
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_host_catalog_static" "example" {
|
||||
name = "My catalog"
|
||||
description = "My first host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_host_set_plugin.foo <my-id>
|
||||
@@ -0,0 +1,103 @@
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "My first scope!"
|
||||
scope_id = "global"
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
# For more information about the aws plugin, please visit here:
|
||||
# https://github.com/hashicorp/boundary-plugin-host-aws
|
||||
#
|
||||
# For more information about aws users, please visit here:
|
||||
# https://learn.hashicorp.com/tutorials/boundary/aws-host-catalogs?in=boundary/oss-access-management#configure-terraform-and-iam-user-privileges
|
||||
resource "boundary_host_catalog_plugin" "aws_example" {
|
||||
name = "My aws catalog"
|
||||
description = "My first host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
plugin_name = "aws"
|
||||
attributes_json = jsonencode({ "region" = "us-east-1" })
|
||||
|
||||
# recommended to pass in aws secrets using a file() or using environment variables
|
||||
# the secrets below must be generated in aws by creating a aws iam user with programmatic access
|
||||
secrets_json = jsonencode({
|
||||
"access_key_id" = "aws_access_key_id_value",
|
||||
"secret_access_key" = "aws_secret_access_key_value"
|
||||
})
|
||||
}
|
||||
|
||||
resource "boundary_host_set_plugin" "web" {
|
||||
name = "My web host set plugin"
|
||||
host_catalog_id = boundary_host_catalog_plugin.aws_example.id
|
||||
attributes_json = jsonencode({ "filters" = "tag:service-type=web" })
|
||||
}
|
||||
|
||||
resource "boundary_host_set_plugin" "foobar" {
|
||||
name = "My foobar host set plugin"
|
||||
host_catalog_id = boundary_host_catalog_plugin.aws_example.id
|
||||
preferred_endpoints = ["cidr:54.0.0.0/8"]
|
||||
attributes_json = jsonencode({
|
||||
"filters" = "tag-key=foo",
|
||||
"filters" = "tag-key=bar"
|
||||
})
|
||||
}
|
||||
|
||||
resource "boundary_host_set_plugin" "launch" {
|
||||
name = "My launch host set plugin"
|
||||
host_catalog_id = boundary_host_catalog_plugin.aws_example.id
|
||||
sync_interval_seconds = 60
|
||||
attributes_json = jsonencode({
|
||||
"filters" = "tag:development=prod,dev",
|
||||
"filters" = "launch-time=2022-01-04T*"
|
||||
})
|
||||
}
|
||||
|
||||
# For more information about the azure plugin, please visit here:
|
||||
# https://github.com/hashicorp/boundary-plugin-host-azure
|
||||
#
|
||||
# For more information about azure ad applications, please visit here:
|
||||
# https://learn.hashicorp.com/tutorials/boundary/azure-host-catalogs#register-a-new-azure-ad-application-1
|
||||
resource "boundary_host_catalog_plugin" "azure_example" {
|
||||
name = "My azure catalog"
|
||||
description = "My second host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
plugin_name = "azure"
|
||||
|
||||
# the attributes below must be generated in azure by creating an ad application
|
||||
attributes_json = jsonencode({
|
||||
"disable_credential_rotation" = "true",
|
||||
"tenant_id" = "ARM_TENANT_ID",
|
||||
"subscription_id" = "ARM_SUBSCRIPTION_ID",
|
||||
"client_id" = "ARM_CLIENT_ID"
|
||||
})
|
||||
|
||||
# recommended to pass in aws secrets using a file() or using environment variables
|
||||
# the secrets below must be generated in azure by creating an ad application
|
||||
secrets_json = jsonencode({
|
||||
"secret_value" = "ARM_CLIENT_SECRET"
|
||||
})
|
||||
}
|
||||
|
||||
resource "boundary_host_set_plugin" "database" {
|
||||
name = "My database host set plugin"
|
||||
host_catalog_id = boundary_host_catalog_plugin.azure_example.id
|
||||
attributes_json = jsonencode({ "filter" = "tagName eq 'service-type' and tagValue eq 'database'" })
|
||||
}
|
||||
|
||||
resource "boundary_host_set_plugin" "foodev" {
|
||||
name = "My foodev host set plugin"
|
||||
host_catalog_id = boundary_host_catalog_plugin.azure_example.id
|
||||
preferred_endpoints = ["cidr:54.0.0.0/8"]
|
||||
sync_interval_seconds = 60
|
||||
attributes_json = jsonencode({
|
||||
"filter" = "tagName eq 'tag-key' and tagValue eq 'foo'",
|
||||
"filter" = "tagName eq 'application' and tagValue eq 'dev'",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_host_set_static.foo <my-id>
|
||||
@@ -0,0 +1,40 @@
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "My first scope!"
|
||||
scope_id = "global"
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_host_catalog_static" "example" {
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_host_static" "first" {
|
||||
name = "host_1"
|
||||
description = "My first host!"
|
||||
address = "10.0.0.1"
|
||||
host_catalog_id = boundary_host_catalog_static.example.id
|
||||
}
|
||||
|
||||
resource "boundary_host_static" "second" {
|
||||
name = "host_2"
|
||||
description = "My second host!"
|
||||
address = "10.0.0.2"
|
||||
host_catalog_id = boundary_host_catalog_static.example.id
|
||||
}
|
||||
|
||||
resource "boundary_host_set_static" "web" {
|
||||
host_catalog_id = boundary_host_catalog_static.example.id
|
||||
host_ids = [
|
||||
boundary_host_static.first.id,
|
||||
boundary_host_static.second.id,
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_host_static.foo <my-id>
|
||||
@@ -0,0 +1,27 @@
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "My first scope!"
|
||||
scope_id = "global"
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_host_catalog_static" "example" {
|
||||
name = "My catalog"
|
||||
description = "My first host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_host_static" "example" {
|
||||
name = "example_host"
|
||||
description = "My first host!"
|
||||
address = "10.0.0.1"
|
||||
host_catalog_id = boundary_host_catalog_static.example.id
|
||||
}
|
||||
@@ -32,6 +32,7 @@ resource "boundary_credential_library_vault" "foo" {
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/foo" # change to Vault backend path
|
||||
http_method = "GET"
|
||||
credential_type = "username_password"
|
||||
}
|
||||
|
||||
resource "boundary_host_catalog" "foo" {
|
||||
@@ -77,7 +78,30 @@ resource "boundary_target" "foo" {
|
||||
host_source_ids = [
|
||||
boundary_host_set.foo.id
|
||||
]
|
||||
application_credential_source_ids = [
|
||||
brokered_credential_source_ids = [
|
||||
boundary_credential_library_vault.foo.id
|
||||
]
|
||||
}
|
||||
|
||||
resource "boundary_target" "ssh_foo" {
|
||||
name = "ssh_foo"
|
||||
description = "Ssh target"
|
||||
type = "ssh"
|
||||
default_port = "22"
|
||||
scope_id = boundary_scope.project.id
|
||||
host_source_ids = [
|
||||
boundary_host_set.foo.id
|
||||
]
|
||||
injected_application_credential_source_ids = [
|
||||
boundary_credential_library_vault.foo.id
|
||||
]
|
||||
}
|
||||
|
||||
resource "boundary_target" "address_foo" {
|
||||
name = "address_foo"
|
||||
description = "Foo target with an address"
|
||||
type = "tcp"
|
||||
default_port = "22"
|
||||
scope_id = boundary_scope.project.id
|
||||
address = "127.0.0.1"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_worker.foo <my-id>
|
||||
@@ -0,0 +1,13 @@
|
||||
resource "boundary_worker" "controller_led" {
|
||||
scope_id = "global"
|
||||
name = "worker 1"
|
||||
description = "self managed worker with controlled led auth"
|
||||
worker_generated_auth_token = var.worker_generated_auth_token
|
||||
}
|
||||
|
||||
resource "boundary_self_managed_worker" "worker_led" {
|
||||
scope_id = "global"
|
||||
name = "worker 2"
|
||||
description = "self managed worker with controlled led auth"
|
||||
worker_generated_auth_token = var.worker_generated_auth_token
|
||||
}
|
||||
@@ -1,32 +1,33 @@
|
||||
module github.com/hashicorp/terraform-provider-boundary
|
||||
|
||||
go 1.18
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/hashicorp/boundary v0.8.1
|
||||
github.com/hashicorp/boundary/api v0.0.24
|
||||
github.com/hashicorp/boundary/sdk v0.0.15
|
||||
github.com/hashicorp/boundary v0.12.2
|
||||
github.com/hashicorp/boundary/api v0.0.34
|
||||
github.com/hashicorp/boundary/sdk v0.0.30
|
||||
github.com/hashicorp/cap v0.2.0
|
||||
github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637
|
||||
github.com/hashicorp/go-kms-wrapping/v2 v2.0.5
|
||||
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.3
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.5
|
||||
github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.1
|
||||
github.com/hashicorp/terraform-plugin-docs v0.9.0
|
||||
github.com/hashicorp/terraform-plugin-sdk/v2 v2.17.0
|
||||
github.com/kr/pretty v0.3.0
|
||||
github.com/hashicorp/go-kms-wrapping/v2 v2.0.9-0.20230315135303-7bedb33a2280
|
||||
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.7
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7
|
||||
github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.3
|
||||
github.com/hashicorp/terraform-plugin-docs v0.14.1
|
||||
github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.1
|
||||
github.com/kr/pretty v0.3.1
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/stretchr/testify v1.7.1
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
|
||||
github.com/stretchr/testify v1.8.2
|
||||
golang.org/x/crypto v0.7.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.2 // indirect
|
||||
github.com/Microsoft/go-winio v0.4.16 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/agext/levenshtein v1.2.2 // indirect
|
||||
github.com/alessio/shellescape v1.4.1 // indirect
|
||||
@@ -36,107 +37,109 @@ require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bgentry/speakeasy v0.1.0 // indirect
|
||||
github.com/cenkalti/backoff/v3 v3.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/containerd/containerd v1.4.3 // indirect
|
||||
github.com/containerd/continuity v0.0.0-20200709052629-daa8e1ccc0bc // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/containerd/continuity v0.3.0 // indirect
|
||||
github.com/coreos/go-oidc/v3 v3.1.0 // indirect
|
||||
github.com/danieljoos/wincred v1.1.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/cli v20.10.7+incompatible // indirect
|
||||
github.com/docker/docker v20.10.7+incompatible // indirect
|
||||
github.com/docker/cli v20.10.14+incompatible // indirect
|
||||
github.com/docker/docker v20.10.9+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/fatih/color v1.14.1 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.0.6 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-migrate/migrate/v4 v4.14.1 // indirect
|
||||
github.com/golang-migrate/migrate/v4 v4.15.1 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.2 // indirect
|
||||
github.com/hashicorp/dbassert v0.0.0-20210708202608-ecf920cf1ed8 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/eventlogger v0.1.1-0.20211106154408-4ff8da3a890c // indirect
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.7 // indirect
|
||||
github.com/hashicorp/eventlogger v0.1.2-0.20230227112545-f26a3bdf6871 // indirect
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20230227112545-f26a3bdf6871 // indirect
|
||||
github.com/hashicorp/go-bexpr v0.1.10 // indirect
|
||||
github.com/hashicorp/go-checkpoint v0.5.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-dbw v0.0.0-20211215222256-2ff0d37184ff // indirect
|
||||
github.com/hashicorp/go-hclog v1.2.0 // indirect
|
||||
github.com/hashicorp/go-dbw v0.0.0-20220910135738-ed4505749995 // indirect
|
||||
github.com/hashicorp/go-hclog v1.3.1 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.2 // indirect
|
||||
github.com/hashicorp/go-kms-wrapping/extras/kms/v2 v2.0.0-20221122211539-47c893099f13 // indirect
|
||||
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.3 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-plugin v1.4.4 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
|
||||
github.com/hashicorp/go-plugin v1.4.9 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/gatedwriter v0.1.1 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/kv-builder v0.1.1 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.4 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.5 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.2 // indirect
|
||||
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||
github.com/hashicorp/go-version v1.5.0 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/hashicorp/hc-install v0.3.2 // indirect
|
||||
github.com/hashicorp/hc-install v0.5.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.12.0 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.16.2 // indirect
|
||||
github.com/hashicorp/logutils v1.0.0 // indirect
|
||||
github.com/hashicorp/terraform-exec v0.16.1 // indirect
|
||||
github.com/hashicorp/terraform-json v0.14.0 // indirect
|
||||
github.com/hashicorp/terraform-plugin-go v0.9.1 // indirect
|
||||
github.com/hashicorp/terraform-plugin-log v0.4.0 // indirect
|
||||
github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896 // indirect
|
||||
github.com/hashicorp/nodeenrollment v0.1.18 // indirect
|
||||
github.com/hashicorp/terraform-exec v0.18.1 // indirect
|
||||
github.com/hashicorp/terraform-json v0.16.0 // indirect
|
||||
github.com/hashicorp/terraform-plugin-go v0.14.3 // indirect
|
||||
github.com/hashicorp/terraform-plugin-log v0.7.0 // indirect
|
||||
github.com/hashicorp/terraform-registry-address v0.1.0 // indirect
|
||||
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect
|
||||
github.com/hashicorp/vault/api v1.3.1 // indirect
|
||||
github.com/hashicorp/vault/sdk v0.3.0 // indirect
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.10.1 // indirect
|
||||
github.com/jackc/pgconn v1.12.1 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.2.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.3.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||
github.com/jackc/pgtype v1.9.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.14.0 // indirect
|
||||
github.com/jackc/pgtype v1.11.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.16.1 // indirect
|
||||
github.com/jefferai/go-libsecret v0.0.0-20210525195240-b53481abef97 // indirect
|
||||
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f // indirect
|
||||
github.com/jefferai/keyring v1.1.7-0.20220316160357-58a74bb55891 // indirect
|
||||
github.com/jinzhu/gorm v1.9.12 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.3 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lib/pq v1.10.2 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mattn/go-sqlite3 v2.0.1+incompatible // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mitchellh/cli v1.1.4 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a // indirect
|
||||
github.com/mitchellh/cli v1.1.5 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/pointerstructure v1.2.0 // indirect
|
||||
github.com/mitchellh/pointerstructure v1.2.1 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||
github.com/mtibben/percent v0.2.1 // indirect
|
||||
github.com/oklog/run v1.0.0 // indirect
|
||||
github.com/oklog/run v1.1.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||
github.com/opencontainers/runc v1.0.0-rc9 // indirect
|
||||
github.com/ory/dockertest/v3 v3.7.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/opencontainers/runc v1.1.2 // indirect
|
||||
github.com/ory/dockertest/v3 v3.9.1 // indirect
|
||||
github.com/pierrec/lz4 v2.5.2+incompatible // indirect
|
||||
github.com/pires/go-proxyproto v0.6.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
@@ -146,11 +149,12 @@ require (
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||
github.com/russross/blackfriday v1.6.0 // indirect
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/sethvargo/go-diceware v0.3.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sirupsen/logrus v1.7.0 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
||||
github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect
|
||||
@@ -158,24 +162,26 @@ require (
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/xo/dburl v0.9.0 // indirect
|
||||
github.com/xo/dburl v0.11.0 // indirect
|
||||
github.com/zalando/go-keyring v0.2.1 // indirect
|
||||
github.com/zclconf/go-cty v1.10.0 // indirect
|
||||
github.com/zclconf/go-cty v1.13.1 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
|
||||
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/oauth2 v0.4.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/term v0.6.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
|
||||
google.golang.org/grpc v1.46.0 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488 // indirect
|
||||
google.golang.org/grpc v1.53.0 // indirect
|
||||
google.golang.org/protobuf v1.29.1 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0 // indirect
|
||||
gorm.io/driver/postgres v1.2.2 // indirect
|
||||
gorm.io/driver/sqlite v1.2.4 // indirect
|
||||
gorm.io/gorm v1.22.3 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gorm.io/driver/postgres v1.3.8 // indirect
|
||||
gorm.io/driver/sqlite v1.3.6 // indirect
|
||||
gorm.io/gorm v1.23.8 // indirect
|
||||
)
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
const (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
// Helpers for dealing with file paths and their contents
|
||||
//
|
||||
// Originally from Terraform; see
|
||||
|
||||
+136
-33
@@ -1,9 +1,13 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
@@ -14,7 +18,12 @@ import (
|
||||
"github.com/hashicorp/go-secure-stdlib/pluginutil/v2"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-provider-boundary/plugins/kms"
|
||||
kms_plugin_assets "github.com/hashicorp/terraform-provider-boundary/plugins/kms"
|
||||
)
|
||||
|
||||
const (
|
||||
PASSWORD_AUTH_METHOD_PREFIX = "ampw"
|
||||
DEFAULT_PROVIDER_SCOPE = "global"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -60,30 +69,46 @@ func New() *schema.Provider {
|
||||
Optional: true,
|
||||
Description: "When set to true, does not validate the Boundary API endpoint certificate",
|
||||
},
|
||||
"plugin_execution_dir": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: `Specifies a directory that the Boundary provider can use to write and execute its built-in plugins.`,
|
||||
},
|
||||
"scope_id": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: `The scope ID for the default auth method.`,
|
||||
},
|
||||
},
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
"boundary_account": resourceAccount(),
|
||||
"boundary_account_password": resourceAccountPassword(),
|
||||
"boundary_account_oidc": resourceAccountOidc(),
|
||||
"boundary_auth_method": resourceAuthMethod(),
|
||||
"boundary_auth_method_password": resourceAuthMethodPassword(),
|
||||
"boundary_auth_method_oidc": resourceAuthMethodOidc(),
|
||||
"boundary_credential_library_vault": resourceCredentialLibraryVault(),
|
||||
"boundary_credential_store_vault": resourceCredentialStoreVault(),
|
||||
"boundary_managed_group": resourceManagedGroup(),
|
||||
"boundary_group": resourceGroup(),
|
||||
"boundary_host": resourceHost(),
|
||||
"boundary_host_static": resourceHostStatic(),
|
||||
"boundary_host_catalog": resourceHostCatalog(),
|
||||
"boundary_host_catalog_static": resourceHostCatalogStatic(),
|
||||
"boundary_host_catalog_plugin": resourceHostCatalogPlugin(),
|
||||
"boundary_host_set": resourceHostSet(),
|
||||
"boundary_host_set_static": resourceHostSetStatic(),
|
||||
"boundary_host_set_plugin": resourceHostSetPlugin(),
|
||||
"boundary_role": resourceRole(),
|
||||
"boundary_scope": resourceScope(),
|
||||
"boundary_target": resourceTarget(),
|
||||
"boundary_user": resourceUser(),
|
||||
"boundary_account": resourceAccount(),
|
||||
"boundary_account_password": resourceAccountPassword(),
|
||||
"boundary_account_oidc": resourceAccountOidc(),
|
||||
"boundary_auth_method": resourceAuthMethod(),
|
||||
"boundary_auth_method_password": resourceAuthMethodPassword(),
|
||||
"boundary_auth_method_oidc": resourceAuthMethodOidc(),
|
||||
"boundary_credential_library_vault": resourceCredentialLibraryVault(),
|
||||
"boundary_credential_library_vault_ssh_certificate": resourceCredentialLibraryVaultSshCertificate(),
|
||||
"boundary_credential_store_vault": resourceCredentialStoreVault(),
|
||||
"boundary_credential_store_static": resourceCredentialStoreStatic(),
|
||||
"boundary_credential_username_password": resourceCredentialUsernamePassword(),
|
||||
"boundary_credential_ssh_private_key": resourceCredentialSshPrivateKey(),
|
||||
"boundary_credential_json": resourceCredentialJson(),
|
||||
"boundary_managed_group": resourceManagedGroup(),
|
||||
"boundary_group": resourceGroup(),
|
||||
"boundary_host": resourceHost(),
|
||||
"boundary_host_static": resourceHostStatic(),
|
||||
"boundary_host_catalog": resourceHostCatalog(),
|
||||
"boundary_host_catalog_static": resourceHostCatalogStatic(),
|
||||
"boundary_host_catalog_plugin": resourceHostCatalogPlugin(),
|
||||
"boundary_host_set": resourceHostSet(),
|
||||
"boundary_host_set_static": resourceHostSetStatic(),
|
||||
"boundary_host_set_plugin": resourceHostSetPlugin(),
|
||||
"boundary_role": resourceRole(),
|
||||
"boundary_scope": resourceScope(),
|
||||
"boundary_target": resourceTarget(),
|
||||
"boundary_user": resourceUser(),
|
||||
"boundary_worker": resourceWorker(),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -99,24 +124,56 @@ type metaData struct {
|
||||
|
||||
func providerAuthenticate(ctx context.Context, d *schema.ResourceData, md *metaData) error {
|
||||
var credentials map[string]interface{}
|
||||
amClient := authmethods.NewClient(md.client)
|
||||
|
||||
var providerScope string
|
||||
scopeId, scopeIdOk := d.GetOk("scope_id")
|
||||
switch {
|
||||
case scopeIdOk:
|
||||
providerScope = scopeId.(string)
|
||||
default:
|
||||
providerScope = DEFAULT_PROVIDER_SCOPE
|
||||
}
|
||||
|
||||
authMethodId, authMethodIdOk := d.GetOk("auth_method_id")
|
||||
recoveryKmsHcl, recoveryKmsHclOk := d.GetOk("recovery_kms_hcl")
|
||||
if token, ok := d.GetOk("token"); ok {
|
||||
md.client.SetToken(token.(string))
|
||||
}
|
||||
|
||||
// If auth_method_id is not set, get the default auth method ID for the given scope ID.
|
||||
// Skip fetching default auth method when recovery_kms_hcl is set or the client token isn't null.
|
||||
authMethodId, authMethodIdOk := d.GetOk("auth_method_id")
|
||||
if !authMethodIdOk && !recoveryKmsHclOk && md.client.Token() == "" {
|
||||
defaultAuthMethodId, err := getDefaultAuthMethodId(ctx, amClient, providerScope, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authMethodIdOk = true
|
||||
authMethodId = defaultAuthMethodId
|
||||
}
|
||||
|
||||
switch {
|
||||
case recoveryKmsHclOk:
|
||||
recoveryHclStr, _, err := ReadPathOrContents(recoveryKmsHcl.(string))
|
||||
if err != nil {
|
||||
return fmt.Errorf(`error reading data from "recovery_kms_hcl": %v`, err)
|
||||
}
|
||||
wrapper, _, err := wrapper.GetWrapperFromHcl(ctx, recoveryHclStr, "recovery",
|
||||
configutil.WithPluginOptions(
|
||||
pluginutil.WithPluginsMap(kms_plugin_assets.BuiltinKmsPlugins()),
|
||||
pluginutil.WithPluginsFilesystem(kms_plugin_assets.KmsPluginPrefix, kms_plugin_assets.FileSystem()),
|
||||
))
|
||||
|
||||
opts := []pluginutil.Option{
|
||||
pluginutil.WithPluginsMap(kms_plugin_assets.BuiltinKmsPlugins()),
|
||||
pluginutil.WithPluginsFilesystem(kms_plugin_assets.KmsPluginPrefix, kms_plugin_assets.FileSystem()),
|
||||
}
|
||||
|
||||
if execDir, ok := d.GetOk("plugin_execution_dir"); ok {
|
||||
opts = append(opts, pluginutil.WithPluginExecutionDirectory(execDir.(string)))
|
||||
}
|
||||
|
||||
// TODO: cleanup plugin when finished
|
||||
wrapper, _, err := wrapper.GetWrapperFromHcl(
|
||||
ctx,
|
||||
recoveryHclStr,
|
||||
"recovery",
|
||||
configutil.WithPluginOptions(opts...))
|
||||
if err != nil {
|
||||
return fmt.Errorf(`error reading wrappers from "recovery_kms_hcl": %v`, err)
|
||||
}
|
||||
@@ -147,15 +204,24 @@ func providerAuthenticate(ctx context.Context, d *schema.ResourceData, md *metaD
|
||||
"login_name": authMethodLoginName,
|
||||
"password": authMethodPassword,
|
||||
}
|
||||
|
||||
case strings.HasPrefix(authMethodId.(string), "amoidc"):
|
||||
// OIDC-style
|
||||
return errors.New("OIDC auth method is currently not supported by Boundary Terraform Provider. only password auth method is supported at this time")
|
||||
default:
|
||||
return errors.New("no suitable typed auth method information found")
|
||||
}
|
||||
|
||||
am := authmethods.NewClient(md.client)
|
||||
|
||||
at, err := am.Authenticate(ctx, authMethodId.(string), "login", credentials)
|
||||
at, err := amClient.Authenticate(ctx, authMethodId.(string), "login", credentials)
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil {
|
||||
statusCode := apiErr.Response().StatusCode()
|
||||
if statusCode == http.StatusNotFound {
|
||||
return fmt.Errorf("unknown auth_method_id: %s", err.Error())
|
||||
}
|
||||
if statusCode == http.StatusUnauthorized {
|
||||
return fmt.Errorf("invalid login name or password: %s", err.Error())
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
md.client.SetToken(at.Attributes["token"].(string))
|
||||
@@ -202,3 +268,40 @@ func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc {
|
||||
return md, nil
|
||||
}
|
||||
}
|
||||
|
||||
// getDefaultAuthMethodId iterates over boundary client.List() to find the default auth method ID for the given scopeId.
|
||||
// If there is only one auth method, it'll return it even if it's not the primary auth method
|
||||
// If scope ID is empty or no primary auth method is found, it returns an error.
|
||||
func getDefaultAuthMethodId(ctx context.Context, client *authmethods.Client, scopeId, amType string) (string, error) {
|
||||
if scopeId == "" {
|
||||
return "", fmt.Errorf("must pass a non empty scope ID string to get default auth method ID")
|
||||
}
|
||||
authMethodListResult, err := client.List(ctx, scopeId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
authMethodItems := authMethodListResult.GetItems()
|
||||
|
||||
// If there is only one auth method that matches the auth method prefix, return it even if it's not the primary auth method
|
||||
if len(authMethodItems) == 1 {
|
||||
authMethod := authMethodItems[0]
|
||||
if !strings.HasPrefix(authMethod.Id, amType) {
|
||||
return "", fmt.Errorf("error looking up default auth method for scope ID: '%s'. got '%s' but the provider requires an auth method prefix of '%s'", scopeId, authMethod.Id, amType)
|
||||
}
|
||||
|
||||
return authMethod.Id, nil
|
||||
}
|
||||
|
||||
// find the primary auth method that matches auth method prefix
|
||||
for _, m := range authMethodItems {
|
||||
if m.IsPrimary {
|
||||
if !strings.HasPrefix(m.Id, amType) {
|
||||
return "", fmt.Errorf("error looking up primary auth method for scope ID: '%s'. got '%s' but the provider requires an auth method prefix of '%s'", scopeId, m.Id, amType)
|
||||
}
|
||||
|
||||
return m.Id, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("default auth method not found for scope ID: '%s'. Please set a primary auth method on this scope or pass one explicitly using the `auth_method_id` field", scopeId)
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
@@ -5,14 +8,17 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/testing/controller"
|
||||
"github.com/hashicorp/cap/oidc"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/go-kms-wrapping/v2/aead"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -95,13 +101,46 @@ provider "boundary" {
|
||||
return strings.Join(c, "\n")
|
||||
}
|
||||
|
||||
func testConfigWithDefaultAuthMethod(url string, res ...string) string {
|
||||
provider := fmt.Sprintf(`
|
||||
provider "boundary" {
|
||||
addr = "%s"
|
||||
password_auth_method_login_name = "%s"
|
||||
password_auth_method_password = "%s"
|
||||
}`, url, tcLoginName, tcPassword)
|
||||
|
||||
c := []string{provider}
|
||||
c = append(c, res...)
|
||||
return strings.Join(c, "\n")
|
||||
}
|
||||
|
||||
func testConfigWithOIDCAuthMethod(url string, res ...string) string {
|
||||
provider := fmt.Sprintf(`
|
||||
provider "boundary" {
|
||||
addr = "%s"
|
||||
auth_method_id = "amoidc_0000000000"
|
||||
}`, url)
|
||||
|
||||
c := []string{provider}
|
||||
c = append(c, res...)
|
||||
return strings.Join(c, "\n")
|
||||
}
|
||||
|
||||
func testConfigWithoutAMPWCredentials(url string, res ...string) string {
|
||||
provider := fmt.Sprintf(`
|
||||
provider "boundary" {
|
||||
addr = "%s"
|
||||
}`, url)
|
||||
|
||||
c := []string{provider}
|
||||
c = append(c, res...)
|
||||
return strings.Join(c, "\n")
|
||||
}
|
||||
|
||||
func testConfigWithRecovery(url string, res ...string) string {
|
||||
provider := fmt.Sprintf(`
|
||||
provider "boundary" {
|
||||
addr = "%s"
|
||||
auth_method_id = "%s"
|
||||
password_auth_method_login_name = "%s"
|
||||
password_auth_method_password = "%s"
|
||||
recovery_kms_hcl = <<DOC
|
||||
kms "aead" {
|
||||
purpose = ["recovery", "config"]
|
||||
@@ -110,7 +149,7 @@ provider "boundary" {
|
||||
key_id = "global_recovery"
|
||||
}
|
||||
DOC
|
||||
}`, url, tcPAUM, tcLoginName, tcPassword)
|
||||
}`, url)
|
||||
|
||||
c := []string{provider}
|
||||
c = append(c, res...)
|
||||
@@ -136,3 +175,120 @@ func TestProvider(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigWithDefaultAuthMethod(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
IsUnitTest: true,
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckScopeResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testConfigWithDefaultAuthMethod(url, fooOrg, firstProjectFoo, secondProject),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckScopeResourceExists(provider, "boundary_scope.org1"),
|
||||
testProviderTokenExists(provider),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfigWithoutAMPWCredentials(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
IsUnitTest: true,
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testConfigWithoutAMPWCredentials(url, fooOrg, firstProjectFoo, secondProject),
|
||||
ExpectError: regexp.MustCompile("password-style auth method login name not set, please set password_auth_method_login_name on the provider"),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfigWithOIDCAuthMethod(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
IsUnitTest: true,
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testConfigWithOIDCAuthMethod(url, fooOrg, firstProjectFoo, secondProject),
|
||||
ExpectError: regexp.MustCompile("OIDC auth method is currently not supported by Boundary Terraform Provider. only password auth method is supported at this time"),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Create OIDC auth method and set it as the primary auth method.
|
||||
// Attempt to authenticate with recovery to test checks for default auth method
|
||||
func TestRecoveryWithOIDCDefaultAuthMethod(t *testing.T) {
|
||||
tp := oidc.StartTestProvider(t)
|
||||
wrapper := testWrapper(context.Background(), t, tcRecoveryKey)
|
||||
tc := controller.NewTestController(t, append(tcConfig, controller.WithRecoveryKms(wrapper))...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
tpCert := strings.TrimSpace(tp.CACert())
|
||||
createConfig := fmt.Sprintf(fooAuthMethodOidc, fooAuthMethodOidcDesc, tp.Addr(), tpCert)
|
||||
updateConfig := fmt.Sprintf(fooAuthMethodOidcUpdate, fooAuthMethodOidcDescUpdate, fooAuthMethodOidcCaCerts)
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckAuthMethodResourceDestroy(t, provider, oidcAuthMethodType),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create auth method
|
||||
Config: testConfig(url, fooOrg, createConfig),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", "description", fooAuthMethodOidcDesc),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", "name", "test"),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcIssuerKey, tp.Addr()),
|
||||
),
|
||||
},
|
||||
importStep("boundary_auth_method_oidc.foo", "client_secret", "is_primary_for_scope"),
|
||||
{
|
||||
// set auth method as primary auth method
|
||||
Config: testConfig(url, fooOrg, updateConfig),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", "name", "test"),
|
||||
testAccIsPrimaryForScope(provider, "boundary_auth_method_oidc.foo", true),
|
||||
),
|
||||
},
|
||||
{
|
||||
// authenticate provider with recovery kms with unsupported OIDC primary auth method
|
||||
Config: testConfigWithRecovery(url, fooOrg, updateConfig),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", "name", "test"),
|
||||
testAccIsPrimaryForScope(provider, "boundary_auth_method_oidc.foo", true),
|
||||
),
|
||||
},
|
||||
importStep("boundary_auth_method_oidc.foo", "client_secret", "is_primary_for_scope", authmethodOidcMaxAgeKey),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testProviderTokenExists(testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
md := testProvider.Meta().(*metaData)
|
||||
if md.client.Token() == "" {
|
||||
return fmt.Errorf("token not set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
@@ -12,8 +15,8 @@ import (
|
||||
|
||||
func resourceAccount() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
DeprecationMessage: "Deprecated: use `resource_account_password` instead.",
|
||||
Description: "Deprecated: use `resource_account_password` instead.",
|
||||
DeprecationMessage: "Deprecated: use `boundary_account_password` instead.",
|
||||
Description: "Deprecated: use `boundary_account_password` instead.",
|
||||
|
||||
CreateContext: resourceAccountCreate,
|
||||
ReadContext: resourceAccountRead,
|
||||
@@ -60,7 +63,7 @@ func resourceAccount() *schema.Resource {
|
||||
Description: "The account password. Only set on create, changes will not be reflected when updating account.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
|
||||
Sensitive: true,
|
||||
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
|
||||
if d.Id() == "" {
|
||||
// This is a new resource do not suppress password diff
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
@@ -65,7 +68,7 @@ func resourceAccountPassword() *schema.Resource {
|
||||
Description: "The account password. Only set on create, changes will not be reflected when updating account.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
|
||||
Sensitive: true,
|
||||
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
|
||||
if d.Id() == "" {
|
||||
// This is a new resource do not suppress password diff
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
@@ -99,6 +102,7 @@ func resourceAuthMethodOidc() *schema.Resource {
|
||||
Description: "The secret key assigned to this auth method from the provider. Once set, only the hash will be kept and the original value can be removed from configuration.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
authmethodOidcIssuerKey: {
|
||||
Description: "The issuer corresponding to the provider, which must match the issuer field in generated tokens.",
|
||||
@@ -111,9 +115,11 @@ func resourceAuthMethodOidc() *schema.Resource {
|
||||
Optional: true,
|
||||
},
|
||||
authmethodOidcMaxAgeKey: {
|
||||
Description: "The max age to provide to the provider, indicating how much time is allowed to have passed since the last authentication before the user is challenged again.",
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Description: `The max age to provide to the provider, indicating how much time is allowed to have passed since the last authentication before the user is challenged again. ` +
|
||||
`A value of 0 sets an immediate requirement for all users to reauthenticate, and an unset maxAge results in a Terraform value of -1 and the default TTL of the chosen OIDC will be used.`,
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Default: -1,
|
||||
},
|
||||
authmethodOidcSigningAlgorithmsKey: {
|
||||
Description: "Allowed signing algorithms for the provider's issued tokens.",
|
||||
@@ -162,7 +168,7 @@ func resourceAuthMethodOidc() *schema.Resource {
|
||||
Computed: true,
|
||||
},
|
||||
authmethodOidcIsPrimaryAuthMethodForScope: {
|
||||
Description: "When true, makes this auth method the primary auth method for the scope in which it resides. The primary auth method for a scope means the the user will be automatically created when they login using an OIDC account.",
|
||||
Description: "When true, makes this auth method the primary auth method for the scope in which it resides. The primary auth method for a scope means the user will be automatically created when they login using an OIDC account.",
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
},
|
||||
@@ -263,8 +269,12 @@ func resourceAuthMethodOidcCreate(ctx context.Context, d *schema.ResourceData, m
|
||||
if clientSecret, ok := d.GetOk(authmethodOidcClientSecretKey); ok {
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodClientSecret(clientSecret.(string)))
|
||||
}
|
||||
if maxAge, ok := d.GetOk(authmethodOidcMaxAgeKey); ok {
|
||||
// null values are not correctly recognized by the Terraform SDK, so we instead check here for maxAge value
|
||||
// if maxAge is unset it will default and set the terraform state to -1 and clear the maxAge param in Boundary
|
||||
if maxAge, _ := d.GetOk(authmethodOidcMaxAgeKey); maxAge.(int) >= 0 {
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodMaxAge(uint32(maxAge.(int))))
|
||||
} else {
|
||||
opts = append(opts, authmethods.DefaultOidcAuthMethodMaxAge())
|
||||
}
|
||||
if prefix, ok := d.GetOk(authmethodOidcApiUrlPrefixKey); ok {
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodApiUrlPrefix(prefix.(string)))
|
||||
@@ -467,8 +477,12 @@ func resourceAuthMethodOidcUpdate(ctx context.Context, d *schema.ResourceData, m
|
||||
}
|
||||
}
|
||||
if d.HasChange(authmethodOidcMaxAgeKey) {
|
||||
if maxAge, ok := d.GetOk(authmethodOidcMaxAgeKey); ok {
|
||||
// null values are not correctly recognized by the Terraform SDK, so we instead check here for maxAge value
|
||||
// if maxAge is unset it will default and set the terraform state to -1 and clear the maxAge param in Boundary
|
||||
if maxAge, _ := d.GetOk(authmethodOidcMaxAgeKey); maxAge.(int) >= 0 {
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodMaxAge(uint32(maxAge.(int))))
|
||||
} else {
|
||||
opts = append(opts, authmethods.DefaultOidcAuthMethodMaxAge())
|
||||
}
|
||||
}
|
||||
if d.HasChange(authmethodOidcSigningAlgorithmsKey) {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/authmethods"
|
||||
"github.com/hashicorp/boundary/api/scopes"
|
||||
"github.com/hashicorp/boundary/testing/controller"
|
||||
@@ -49,13 +50,13 @@ var (
|
||||
resource "boundary_auth_method_oidc" "foo" {
|
||||
name = "test"
|
||||
description = "%s"
|
||||
scope_id = boundary_scope.org1.id
|
||||
scope_id = "global"
|
||||
depends_on = [boundary_role.org1_admin]
|
||||
|
||||
issuer = "%s"
|
||||
client_id = "foo_id"
|
||||
client_secret = "foo_secret"
|
||||
max_age = 10
|
||||
max_age = 0
|
||||
api_url_prefix = "http://localhost:9200"
|
||||
idp_ca_certs = [
|
||||
<<EOT
|
||||
@@ -72,14 +73,13 @@ EOT
|
||||
resource "boundary_auth_method_oidc" "foo" {
|
||||
name = "test"
|
||||
description = "%s"
|
||||
scope_id = boundary_scope.org1.id
|
||||
scope_id = "global"
|
||||
is_primary_for_scope = true
|
||||
depends_on = [boundary_role.org1_admin]
|
||||
|
||||
issuer = "https://test-update.com"
|
||||
client_id = "foo_id_update"
|
||||
client_secret = "foo_secret_update"
|
||||
max_age = 1
|
||||
api_url_prefix = "http://localhost:9200"
|
||||
idp_ca_certs = [
|
||||
<<EOT
|
||||
@@ -91,7 +91,7 @@ EOT
|
||||
account_claim_maps = ["oid=sub"]
|
||||
claims_scopes = ["profile"]
|
||||
|
||||
// we need to disable this validatin, since the updated issuer isn't discoverable
|
||||
// we need to disable this validation, since the updated issuer isn't discoverable
|
||||
disable_discovered_config_validation = true
|
||||
}`
|
||||
)
|
||||
@@ -109,7 +109,7 @@ func TestAccAuthMethodOidc(t *testing.T) {
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckAuthMethodOidcResourceDestroy(t, provider),
|
||||
CheckDestroy: testAccCheckAuthMethodResourceDestroy(t, provider, oidcAuthMethodType),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
@@ -119,13 +119,13 @@ func TestAccAuthMethodOidc(t *testing.T) {
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", "name", "test"),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcIssuerKey, tp.Addr()),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcClientIdKey, "foo_id"),
|
||||
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcIdpCaCertsKey, []string{tpCert}),
|
||||
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcAllowedAudiencesKey, []string{"foo_aud"}),
|
||||
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcSigningAlgorithmsKey, []string{"ES256"}),
|
||||
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcAccountClaimMapsKey, []string{"oid=sub"}),
|
||||
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcClaimsScopesKey, []string{"profile"}),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcMaxAgeKey, "10"),
|
||||
testAccCheckAuthMethodOidcResourceExists(provider, "boundary_auth_method_oidc.foo"),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcMaxAgeKey, "0"),
|
||||
testAccCheckAuthMethodAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcIdpCaCertsKey, []string{tpCert}),
|
||||
testAccCheckAuthMethodAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcAllowedAudiencesKey, []string{"foo_aud"}),
|
||||
testAccCheckAuthMethodAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcSigningAlgorithmsKey, []string{"ES256"}),
|
||||
testAccCheckAuthMethodAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcAccountClaimMapsKey, []string{"oid=sub"}),
|
||||
testAccCheckAuthMethodAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcClaimsScopesKey, []string{"profile"}),
|
||||
testAccCheckAuthMethodResourceExists(provider, "boundary_auth_method_oidc.foo"),
|
||||
testAccIsPrimaryForScope(provider, "boundary_auth_method_oidc.foo", false),
|
||||
),
|
||||
},
|
||||
@@ -138,71 +138,20 @@ func TestAccAuthMethodOidc(t *testing.T) {
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", "name", "test"),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcIssuerKey, "https://test-update.com"),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcClientIdKey, "foo_id_update"),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcMaxAgeKey, "1"),
|
||||
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcIdpCaCertsKey, []string{fooAuthMethodOidcCaCerts}),
|
||||
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcAllowedAudiencesKey, []string{"foo_aud_update"}),
|
||||
testAccCheckAuthMethodOidcResourceExists(provider, "boundary_auth_method_oidc.foo"),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcMaxAgeKey, "-1"),
|
||||
testAccCheckAuthMethodAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcIdpCaCertsKey, []string{fooAuthMethodOidcCaCerts}),
|
||||
testAccCheckAuthMethodAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcAllowedAudiencesKey, []string{"foo_aud_update"}),
|
||||
testAccCheckAuthMethodResourceExists(provider, "boundary_auth_method_oidc.foo"),
|
||||
testAccIsPrimaryForScope(provider, "boundary_auth_method_oidc.foo", true),
|
||||
testAccCheckAuthMethodOidcResourceExists(provider, "boundary_auth_method_oidc.foo"),
|
||||
testAccCheckAuthMethodResourceExists(provider, "boundary_auth_method_oidc.foo"),
|
||||
),
|
||||
},
|
||||
importStep("boundary_auth_method_oidc.foo", "client_secret", "is_primary_for_scope"),
|
||||
importStep("boundary_auth_method_oidc.foo", "client_secret", "is_primary_for_scope", authmethodOidcMaxAgeKey),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckAuthMethodOidcResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", name)
|
||||
}
|
||||
|
||||
id := rs.Primary.ID
|
||||
if id == "" {
|
||||
return fmt.Errorf("No ID is set")
|
||||
}
|
||||
|
||||
md := testProvider.Meta().(*metaData)
|
||||
|
||||
amClient := authmethods.NewClient(md.client)
|
||||
|
||||
if _, err := amClient.Read(context.Background(), id); err != nil {
|
||||
return fmt.Errorf("Got an error when reading auth method %q: %w", id, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckAuthMethodOidcResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if testProvider.Meta() == nil {
|
||||
t.Fatal("got nil provider metadata")
|
||||
}
|
||||
md := testProvider.Meta().(*metaData)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
switch rs.Type {
|
||||
case "boundary_auth_method_oidc":
|
||||
id := rs.Primary.ID
|
||||
|
||||
amClient := authmethods.NewClient(md.client)
|
||||
|
||||
_, err := amClient.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed auth method %q: %v", id, err)
|
||||
}
|
||||
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckAuthMethodOidcAttrAryValueSet(testProvider *schema.Provider, name string, key string, strAry []string) resource.TestCheckFunc {
|
||||
func testAccCheckAuthMethodAttrAryValueSet(testProvider *schema.Provider, name string, key string, strAry []string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
@@ -47,7 +50,7 @@ func TestAccAuthMethodPassword(t *testing.T) {
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckAuthMethodResourceDestroy(t, provider),
|
||||
CheckDestroy: testAccCheckAuthMethodResourceDestroy(t, provider, passwordAuthMethodType),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
@@ -99,7 +102,14 @@ func testAccCheckAuthMethodResourceExists(testProvider *schema.Provider, name st
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckAuthMethodResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
type authMethodType string
|
||||
|
||||
const (
|
||||
passwordAuthMethodType authMethodType = "boundary_auth_method_password"
|
||||
oidcAuthMethodType authMethodType = "boundary_auth_method_oidc"
|
||||
)
|
||||
|
||||
func testAccCheckAuthMethodResourceDestroy(t *testing.T, testProvider *schema.Provider, typ authMethodType) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if testProvider.Meta() == nil {
|
||||
t.Fatal("got nil provider metadata")
|
||||
@@ -108,7 +118,7 @@ func testAccCheckAuthMethodResourceDestroy(t *testing.T, testProvider *schema.Pr
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
switch rs.Type {
|
||||
case "boundary_auth_method_password":
|
||||
case string(typ):
|
||||
id := rs.Primary.ID
|
||||
|
||||
amClient := authmethods.NewClient(md.client)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
@@ -49,7 +52,7 @@ func TestAccBaseAuthMethodPassword(t *testing.T) {
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckAuthMethodResourceDestroy(t, provider),
|
||||
CheckDestroy: testAccCheckAuthMethodResourceDestroy(t, provider, passwordAuthMethodType),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentials"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
credentialJsonCredentialType = "json"
|
||||
credentialJsonObjectKey = "object"
|
||||
credentialJsonObjectHmacKey = "object_hmac"
|
||||
)
|
||||
|
||||
func resourceCredentialJson() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The json credential resource allows you to congiure a credential using a json object.",
|
||||
|
||||
CreateContext: resourceCredentialJsonCreate,
|
||||
ReadContext: resourceCredentialJsonRead,
|
||||
UpdateContext: resourceCredentialJsonUpdate,
|
||||
DeleteContext: resourceCredentialJsonDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
IDKey: {
|
||||
Description: "The ID of this json credential.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
NameKey: {
|
||||
Description: "The name of this json credential. Defaults to the resource name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
DescriptionKey: {
|
||||
Description: "The description of this json credential.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialStoreIdKey: {
|
||||
Description: "The credential store in which to save this json credential.",
|
||||
Type: schema.TypeString,
|
||||
ForceNew: true,
|
||||
Required: true,
|
||||
},
|
||||
credentialJsonObjectKey: {
|
||||
Description: `The object for the this json credential. Either values encoded with the "jsonencode" function, pre-escaped JSON string, or a file`,
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
credentialJsonObjectHmacKey: {
|
||||
Description: "The object hmac.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromCredentialJsonResponseMap(d *schema.ResourceData, raw map[string]interface{}, fromRead bool) error {
|
||||
if err := d.Set(NameKey, raw[NameKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(DescriptionKey, raw[DescriptionKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(credentialStoreIdKey, raw[credentialStoreIdKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if attrsVal, ok := raw["attributes"]; ok {
|
||||
attrs := attrsVal.(map[string]interface{})
|
||||
stateObjectHmac := d.Get(credentialJsonObjectHmacKey)
|
||||
boundaryObjectHmac := attrs[credentialJsonObjectHmacKey].(string)
|
||||
if stateObjectHmac.(string) != boundaryObjectHmac && fromRead {
|
||||
if err := d.Set(credentialJsonObjectKey, "(changed in Boundary)"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := d.Set(credentialJsonObjectHmacKey, boundaryObjectHmac); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialJsonCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
|
||||
var opts []credentials.Option
|
||||
if v, ok := d.GetOk(NameKey); ok {
|
||||
opts = append(opts, credentials.WithName(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(DescriptionKey); ok {
|
||||
opts = append(opts, credentials.WithDescription(v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk(credentialJsonObjectKey); ok {
|
||||
var jsonObject map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(v.(string)), &jsonObject); err != nil {
|
||||
return diag.Errorf("error unmarshaling json: %v", err)
|
||||
}
|
||||
opts = append(opts, credentials.WithJsonCredentialObject(jsonObject))
|
||||
}
|
||||
|
||||
var credentialStoreId string
|
||||
retrievedStoreId, ok := d.GetOk(credentialStoreIdKey)
|
||||
if ok {
|
||||
credentialStoreId = retrievedStoreId.(string)
|
||||
} else {
|
||||
return diag.Errorf("credential store id is unset")
|
||||
}
|
||||
|
||||
client := credentials.NewClient(md.client)
|
||||
cred, err := client.Create(ctx, credentialJsonCredentialType, credentialStoreId, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error creating credential: %v", err)
|
||||
}
|
||||
if cred == nil {
|
||||
return diag.Errorf("nil credential after create")
|
||||
}
|
||||
|
||||
if err := setFromCredentialJsonResponseMap(d, cred.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential from response map: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialJsonRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentials.NewClient(md.client)
|
||||
|
||||
cred, err := client.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
return diag.Errorf("error reading credential: %v", err)
|
||||
}
|
||||
if cred == nil {
|
||||
return diag.Errorf("credential nil after read")
|
||||
}
|
||||
|
||||
if err := setFromCredentialJsonResponseMap(d, cred.GetResponse().Map, true); err != nil {
|
||||
return diag.Errorf("error generating credential from response map: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialJsonUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentials.NewClient(md.client)
|
||||
|
||||
var opts []credentials.Option
|
||||
if d.HasChange(NameKey) {
|
||||
opts = append(opts, credentials.DefaultName())
|
||||
if v, ok := d.GetOk(NameKey); ok {
|
||||
opts = append(opts, credentials.WithName(v.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(DescriptionKey) {
|
||||
opts = append(opts, credentials.DefaultDescription())
|
||||
if v, ok := d.GetOk(DescriptionKey); ok {
|
||||
opts = append(opts, credentials.WithDescription(v.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialJsonObjectKey) {
|
||||
if v, ok := d.GetOk(credentialJsonObjectKey); ok {
|
||||
var jsonObject map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(v.(string)), &jsonObject); err != nil {
|
||||
return diag.Errorf("error unmarshaling json: %v", err)
|
||||
}
|
||||
opts = append(opts, credentials.WithJsonCredentialObject(jsonObject))
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, credentials.WithAutomaticVersioning(true))
|
||||
credUpdate, err := client.Update(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating credential: %v", err)
|
||||
}
|
||||
if credUpdate == nil {
|
||||
return diag.Errorf("credential nil after update")
|
||||
}
|
||||
|
||||
if err = setFromCredentialJsonResponseMap(d, credUpdate.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential from response map: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialJsonDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentials.NewClient(md.client)
|
||||
|
||||
_, err := client.Delete(ctx, d.Id())
|
||||
if err != nil {
|
||||
return diag.Errorf("error deleting credential: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentials"
|
||||
"github.com/hashicorp/boundary/testing/controller"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
||||
)
|
||||
|
||||
const (
|
||||
jsonCredResc = "boundary_credential_json.example"
|
||||
jsonCredName = "bar"
|
||||
jsonCredNameUpdate = "foo"
|
||||
jsonCredDesc = "the bar"
|
||||
jsonCredDescUpdate = "the foo"
|
||||
jsonCredObj = `jsonencode({
|
||||
password = "password",
|
||||
username = "admin"
|
||||
})`
|
||||
jsonCredObjUpdate = `jsonencode({
|
||||
password = "password",
|
||||
username = "db-admin"
|
||||
})`
|
||||
)
|
||||
|
||||
func jsonCredResource(name, description, object string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "boundary_credential_store_static" "example" {
|
||||
name = "static store name"
|
||||
description = "static store description"
|
||||
scope_id = boundary_scope.proj1.id
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
}
|
||||
|
||||
resource "boundary_credential_json" "example" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
credential_store_id = boundary_credential_store_static.example.id
|
||||
object = %s
|
||||
}`, name, description, object)
|
||||
}
|
||||
|
||||
func TestAccCredentialJson(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
res := jsonCredResource(
|
||||
jsonCredName,
|
||||
jsonCredDesc,
|
||||
jsonCredObj,
|
||||
)
|
||||
|
||||
resUpdate := jsonCredResource(
|
||||
jsonCredNameUpdate,
|
||||
jsonCredDescUpdate,
|
||||
jsonCredObjUpdate,
|
||||
)
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckCredentialJsonResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, res),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(jsonCredResc, NameKey, jsonCredName),
|
||||
resource.TestCheckResourceAttr(jsonCredResc, DescriptionKey, jsonCredDesc),
|
||||
resource.TestCheckResourceAttr(jsonCredResc, credentialJsonObjectKey, `{"password":"password","username":"admin"}`),
|
||||
|
||||
testAccCheckCredentialJsonObjectHmac(provider),
|
||||
testAccCheckCredentialJsonResourceExists(provider, jsonCredResc),
|
||||
),
|
||||
},
|
||||
importStep(jsonCredResc, credentialJsonObjectKey),
|
||||
{
|
||||
// update
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(jsonCredResc, NameKey, jsonCredNameUpdate),
|
||||
resource.TestCheckResourceAttr(jsonCredResc, DescriptionKey, jsonCredDescUpdate),
|
||||
resource.TestCheckResourceAttr(jsonCredResc, credentialJsonObjectKey, `{"password":"password","username":"db-admin"}`),
|
||||
|
||||
testAccCheckCredentialJsonObjectHmac(provider),
|
||||
testAccCheckCredentialJsonResourceExists(provider, jsonCredResc),
|
||||
),
|
||||
},
|
||||
importStep(jsonCredResc, credentialJsonObjectKey),
|
||||
{
|
||||
// Run a plan only update and verify no changes
|
||||
PlanOnly: true,
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
},
|
||||
importStep(jsonCredResc, credentialJsonObjectKey),
|
||||
{
|
||||
// update again but apply a preConfig to externally update resource
|
||||
PreConfig: func() { jsonCredExternalUpdate(t, provider) },
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
},
|
||||
importStep(jsonCredResc, credentialJsonObjectKey),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckCredentialJsonResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if testProvider.Meta() == nil {
|
||||
t.Fatal("got nil provider metadata")
|
||||
}
|
||||
md := testProvider.Meta().(*metaData)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
switch rs.Type {
|
||||
case "boundary_credential_json":
|
||||
id := rs.Primary.ID
|
||||
|
||||
c := credentials.NewClient(md.client)
|
||||
_, err := c.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed json credential %q: %v", id, err)
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckCredentialJsonResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("not found: %s", name)
|
||||
}
|
||||
|
||||
id := rs.Primary.ID
|
||||
if id == "" {
|
||||
return fmt.Errorf("no ID is set")
|
||||
}
|
||||
storeId = id
|
||||
|
||||
md := testProvider.Meta().(*metaData)
|
||||
c := credentials.NewClient(md.client)
|
||||
if _, err := c.Read(context.Background(), id); err != nil {
|
||||
return fmt.Errorf("got an error reading %q: %w", id, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckCredentialJsonObjectHmac(testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[jsonCredResc]
|
||||
if !ok {
|
||||
return fmt.Errorf("not found: %s", jsonCredResc)
|
||||
}
|
||||
|
||||
computed := rs.Primary.Attributes["object_hmac"]
|
||||
if len(computed) != 43 {
|
||||
return fmt.Errorf("Computed password hmac not the expected length of 43 characters. hmac: %q", computed)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func jsonCredExternalUpdate(t *testing.T, testProvider *schema.Provider) {
|
||||
if storeId == "" {
|
||||
t.Fatal("storeId must be set before testing an external update")
|
||||
}
|
||||
|
||||
md := testProvider.Meta().(*metaData)
|
||||
c := credentials.NewClient(md.client)
|
||||
cr, err := c.Read(context.Background(), storeId)
|
||||
if err != nil {
|
||||
t.Fatal(fmt.Errorf("got an error reading %q: %w", storeId, err))
|
||||
}
|
||||
|
||||
// update credential options
|
||||
var opts []credentials.Option
|
||||
opts = append(opts, credentials.WithDescription("this is an updated description"))
|
||||
|
||||
_, err = c.Update(context.Background(), cr.Item.Id, cr.Item.Version, opts...)
|
||||
if err != nil {
|
||||
t.Fatal(fmt.Errorf("got an error updating %q: %w", cr.Item.Id, err))
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
@@ -6,16 +9,18 @@ import (
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentiallibraries"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
credentialStoreIdKey = "credential_store_id"
|
||||
credentialLibraryVaultHttpMethodKey = "http_method"
|
||||
credentialLibraryVaultHttpRequestBodyKey = "http_request_body"
|
||||
credentialLibraryVaultPathKey = "path"
|
||||
credentialStoreIdKey = "credential_store_id"
|
||||
credentialLibraryVaultType = "vault-generic"
|
||||
credentialLibraryVaultHttpMethodKey = "http_method"
|
||||
credentialLibraryVaultHttpRequestBodyKey = "http_request_body"
|
||||
credentialLibraryVaultPathKey = "path"
|
||||
credentialLibraryCredentialTypeKey = "credential_type"
|
||||
credentialLibraryCredentialMappingOverridesKey = "credential_mapping_overrides"
|
||||
)
|
||||
|
||||
var libraryVaultAttrs = []string{
|
||||
@@ -73,6 +78,17 @@ func resourceCredentialLibraryVault() *schema.Resource {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
credentialLibraryCredentialTypeKey: {
|
||||
Description: "The type of credential the library generates. Cannot be updated on an existing resource.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
credentialLibraryCredentialMappingOverridesKey: {
|
||||
Description: "The credential mapping override.",
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -88,6 +104,10 @@ func setFromVaultCredentialLibraryResponseMap(d *schema.ResourceData, raw map[st
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.Set(credentialLibraryCredentialMappingOverridesKey, raw[credentialLibraryCredentialMappingOverridesKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if attrsVal, ok := raw["attributes"]; ok {
|
||||
attrs := attrsVal.(map[string]interface{})
|
||||
for _, v := range libraryVaultAttrs {
|
||||
@@ -121,6 +141,13 @@ func resourceCredentialLibraryCreateVault(ctx context.Context, d *schema.Resourc
|
||||
if v, ok := d.GetOk(credentialLibraryVaultPathKey); ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryPath(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialLibraryCredentialTypeKey); ok {
|
||||
opts = append(opts, credentiallibraries.WithCredentialType(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialLibraryCredentialMappingOverridesKey); ok {
|
||||
mappingOverrides := v.(map[string]interface{})
|
||||
opts = append(opts, credentiallibraries.WithCredentialMappingOverrides(mappingOverrides))
|
||||
}
|
||||
|
||||
var credentialStoreId string
|
||||
cid, ok := d.GetOk(credentialStoreIdKey)
|
||||
@@ -131,7 +158,7 @@ func resourceCredentialLibraryCreateVault(ctx context.Context, d *schema.Resourc
|
||||
}
|
||||
|
||||
client := credentiallibraries.NewClient(md.client)
|
||||
cr, err := client.Create(ctx, credentialStoreId, opts...)
|
||||
cr, err := client.Create(ctx, credentialLibraryVaultType, credentialStoreId, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error creating credential library: %v", err)
|
||||
}
|
||||
@@ -208,6 +235,40 @@ func resourceCredentialLibraryUpdateVault(ctx context.Context, d *schema.Resourc
|
||||
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryPath(v.(string)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(credentialLibraryCredentialMappingOverridesKey) {
|
||||
var oldMapping, newMapping map[string]interface{}
|
||||
old, new := d.GetChange(credentialLibraryCredentialMappingOverridesKey)
|
||||
|
||||
if old == nil {
|
||||
old = map[string]interface{}{}
|
||||
}
|
||||
oldMapping = old.(map[string]interface{})
|
||||
|
||||
if new == nil {
|
||||
new = map[string]interface{}{}
|
||||
}
|
||||
newMapping = new.(map[string]interface{})
|
||||
|
||||
newAttrs := []string{}
|
||||
for k := range newMapping {
|
||||
newAttrs = append(newAttrs, k)
|
||||
}
|
||||
|
||||
for oldAttr := range oldMapping {
|
||||
isRemoved := true
|
||||
for _, newAttr := range newAttrs {
|
||||
if oldAttr == newAttr {
|
||||
isRemoved = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if isRemoved {
|
||||
newMapping[oldAttr] = nil
|
||||
}
|
||||
}
|
||||
|
||||
opts = append(opts, credentiallibraries.WithCredentialMappingOverrides(newMapping))
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, credentiallibraries.WithAutomaticVersioning(true))
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentiallibraries"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
credentialLibraryVaultSshCertificateType = "vault-ssh-certificate"
|
||||
credentialLibraryVaultSshCertificatePathKey = "path"
|
||||
credentialLibraryVaultSshCertificateUsernameKey = "username"
|
||||
credentialLibraryVaultSshCertificateKeyTypeKey = "key_type"
|
||||
credentialLibraryVaultSshCertificateKeyBitsKey = "key_bits"
|
||||
credentialLibraryVaultSshCertificateTtlKey = "ttl"
|
||||
credentialLibraryVaultSshCertificateKeyIdKey = "key_id"
|
||||
credentialLibraryVaultSshCertificateCriticalOptionsKey = "critical_options"
|
||||
credentialLibraryVaultSshCertificateExtensionsKey = "extensions"
|
||||
)
|
||||
|
||||
var libraryVaultSshCertificateAttrs = []string{
|
||||
credentialLibraryVaultSshCertificatePathKey,
|
||||
credentialLibraryVaultSshCertificateUsernameKey,
|
||||
credentialLibraryVaultSshCertificateKeyTypeKey,
|
||||
credentialLibraryVaultSshCertificateKeyBitsKey,
|
||||
credentialLibraryVaultSshCertificateTtlKey,
|
||||
credentialLibraryVaultSshCertificateKeyIdKey,
|
||||
credentialLibraryVaultSshCertificateCriticalOptionsKey,
|
||||
credentialLibraryVaultSshCertificateExtensionsKey,
|
||||
}
|
||||
|
||||
func resourceCredentialLibraryVaultSshCertificate() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The credential library for Vault resource allows you to configure a Boundary credential library for Vault.",
|
||||
|
||||
CreateContext: resourceCredentialLibraryCreateVaultSshCertificate,
|
||||
ReadContext: resourceCredentialLibraryReadVaultSshCertificate,
|
||||
UpdateContext: resourceCredentialLibraryUpdateVaultSshCertificate,
|
||||
DeleteContext: resourceCredentialLibraryDeleteVaultSshCertificate,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
IDKey: {
|
||||
Description: "The ID of the Vault credential library.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
NameKey: {
|
||||
Description: "The Vault credential library name. Defaults to the resource name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
DescriptionKey: {
|
||||
Description: "The Vault credential library description.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialStoreIdKey: {
|
||||
Description: "The ID of the credential store that this library belongs to.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
credentialLibraryVaultSshCertificatePathKey: {
|
||||
Description: "The path in Vault to request credentials from.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
credentialLibraryVaultSshCertificateUsernameKey: {
|
||||
Description: "The username to use with the certificate returned by the library.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
credentialLibraryVaultSshCertificateKeyTypeKey: {
|
||||
Description: "Specifies the desired key type; must be ed25519, ecdsa, or rsa.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialLibraryVaultSshCertificateKeyBitsKey: {
|
||||
Description: "Specifies the number of bits to use for the generated keys.",
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
},
|
||||
credentialLibraryVaultSshCertificateTtlKey: {
|
||||
Description: "Specifies the requested time to live for a certificate returned from the library.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialLibraryVaultSshCertificateKeyIdKey: {
|
||||
Description: "Specifies the key id a certificate should have.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialLibraryVaultSshCertificateCriticalOptionsKey: {
|
||||
Description: "Specifies a map of the critical options that the certificate should be signed for.",
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
},
|
||||
credentialLibraryVaultSshCertificateExtensionsKey: {
|
||||
Description: "Specifies a map of the extensions that the certificate should be signed for.",
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromVaultSshCertificateCredentialLibraryResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||
if err := d.Set(NameKey, raw[NameKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(DescriptionKey, raw[DescriptionKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(credentialStoreIdKey, raw[credentialStoreIdKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if attrsVal, ok := raw["attributes"]; ok {
|
||||
attrs := attrsVal.(map[string]interface{})
|
||||
for _, v := range libraryVaultSshCertificateAttrs {
|
||||
if err := d.Set(v, attrs[v]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialLibraryCreateVaultSshCertificate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
|
||||
var opts []credentiallibraries.Option
|
||||
if v, ok := d.GetOk(NameKey); ok {
|
||||
opts = append(opts, credentiallibraries.WithName(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(DescriptionKey); ok {
|
||||
opts = append(opts, credentiallibraries.WithDescription(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialLibraryVaultSshCertificatePathKey); ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryPath(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialLibraryVaultSshCertificateUsernameKey); ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryUsername(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialLibraryVaultSshCertificateKeyTypeKey); ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryKeyType(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialLibraryVaultSshCertificateKeyBitsKey); ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryKeyBits(uint32(v.(int))))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialLibraryVaultSshCertificateTtlKey); ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryTtl(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialLibraryVaultSshCertificateKeyIdKey); ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryKeyId(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialLibraryVaultSshCertificateCriticalOptionsKey); ok {
|
||||
if vv, ok := v.(map[string]any); ok {
|
||||
co := make(map[string]string, len(vv))
|
||||
for k, vvv := range vv {
|
||||
co[k] = vvv.(string)
|
||||
}
|
||||
opts = append(opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryCriticalOptions(co))
|
||||
}
|
||||
}
|
||||
if v, ok := d.GetOk(credentialLibraryVaultSshCertificateExtensionsKey); ok {
|
||||
if vv, ok := v.(map[string]any); ok {
|
||||
e := make(map[string]string, len(vv))
|
||||
for k, vvv := range vv {
|
||||
e[k] = vvv.(string)
|
||||
}
|
||||
opts = append(opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryExtensions(e))
|
||||
}
|
||||
}
|
||||
|
||||
var credentialStoreId string
|
||||
cid, ok := d.GetOk(credentialStoreIdKey)
|
||||
if ok {
|
||||
credentialStoreId = cid.(string)
|
||||
} else {
|
||||
return diag.Errorf("no credential store ID is set")
|
||||
}
|
||||
|
||||
client := credentiallibraries.NewClient(md.client)
|
||||
cr, err := client.Create(ctx, credentialLibraryVaultSshCertificateType, credentialStoreId, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error creating credential library: %v", err)
|
||||
}
|
||||
if cr == nil {
|
||||
return diag.Errorf("nil credential library after create")
|
||||
}
|
||||
|
||||
if err := setFromVaultSshCertificateCredentialLibraryResponseMap(d, cr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialLibraryReadVaultSshCertificate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentiallibraries.NewClient(md.client)
|
||||
|
||||
cr, err := client.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
return diag.Errorf("error reading credential library: %v", err)
|
||||
}
|
||||
if cr == nil {
|
||||
return diag.Errorf("credential library nil after read")
|
||||
}
|
||||
|
||||
if err := setFromVaultSshCertificateCredentialLibraryResponseMap(d, cr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialLibraryUpdateVaultSshCertificate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentiallibraries.NewClient(md.client)
|
||||
|
||||
var opts []credentiallibraries.Option
|
||||
if d.HasChange(NameKey) {
|
||||
opts = append(opts, credentiallibraries.DefaultName())
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
opts = append(opts, credentiallibraries.WithName(nameVal.(string)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(DescriptionKey) {
|
||||
opts = append(opts, credentiallibraries.DefaultDescription())
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
opts = append(opts, credentiallibraries.WithDescription(descVal.(string)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(credentialLibraryVaultSshCertificatePathKey) {
|
||||
v, ok := d.GetOk(credentialLibraryVaultSshCertificatePathKey)
|
||||
if ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryPath(v.(string)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(credentialLibraryVaultSshCertificateUsernameKey) {
|
||||
v, ok := d.GetOk(credentialLibraryVaultSshCertificateUsernameKey)
|
||||
if ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryUsername(v.(string)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(credentialLibraryVaultSshCertificateKeyTypeKey) {
|
||||
v, ok := d.GetOk(credentialLibraryVaultSshCertificateKeyTypeKey)
|
||||
if ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryKeyType(v.(string)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(credentialLibraryVaultSshCertificateKeyBitsKey) {
|
||||
v, ok := d.GetOk(credentialLibraryVaultSshCertificateKeyBitsKey)
|
||||
if ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryKeyBits(uint32(v.(int))))
|
||||
}
|
||||
}
|
||||
if d.HasChange(credentialLibraryVaultSshCertificateTtlKey) {
|
||||
v, ok := d.GetOk(credentialLibraryVaultSshCertificateTtlKey)
|
||||
if ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryTtl(v.(string)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(credentialLibraryVaultSshCertificateKeyIdKey) {
|
||||
v, ok := d.GetOk(credentialLibraryVaultSshCertificateKeyIdKey)
|
||||
if ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryKeyId(v.(string)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(credentialLibraryVaultSshCertificateCriticalOptionsKey) {
|
||||
var oldCriticalOptions, newCriticalOptions map[string]interface{}
|
||||
old, new := d.GetChange(credentialLibraryVaultSshCertificateCriticalOptionsKey)
|
||||
|
||||
if old == nil {
|
||||
old = map[string]interface{}{}
|
||||
}
|
||||
oldCriticalOptions = old.(map[string]interface{})
|
||||
|
||||
if new == nil {
|
||||
new = map[string]interface{}{}
|
||||
}
|
||||
newCriticalOptions = new.(map[string]interface{})
|
||||
|
||||
newAttrs := []string{}
|
||||
for k := range newCriticalOptions {
|
||||
newAttrs = append(newAttrs, k)
|
||||
}
|
||||
|
||||
for oldAttr := range oldCriticalOptions {
|
||||
isRemoved := true
|
||||
for _, newAttr := range newAttrs {
|
||||
if oldAttr == newAttr {
|
||||
isRemoved = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if isRemoved {
|
||||
newCriticalOptions[oldAttr] = nil
|
||||
}
|
||||
}
|
||||
|
||||
co := make(map[string]string, 0)
|
||||
for k, v := range newCriticalOptions {
|
||||
if v != nil {
|
||||
co[k] = v.(string)
|
||||
}
|
||||
}
|
||||
if len(co) == 0 {
|
||||
co = nil
|
||||
}
|
||||
opts = append(opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryCriticalOptions(co))
|
||||
|
||||
}
|
||||
if d.HasChange(credentialLibraryVaultSshCertificateExtensionsKey) {
|
||||
var oldExtensions, newExtensions map[string]interface{}
|
||||
old, new := d.GetChange(credentialLibraryVaultSshCertificateExtensionsKey)
|
||||
|
||||
if old == nil {
|
||||
old = map[string]interface{}{}
|
||||
}
|
||||
oldExtensions = old.(map[string]interface{})
|
||||
|
||||
if new == nil {
|
||||
new = map[string]interface{}{}
|
||||
}
|
||||
newExtensions = new.(map[string]interface{})
|
||||
|
||||
newAttrs := []string{}
|
||||
for k := range newExtensions {
|
||||
newAttrs = append(newAttrs, k)
|
||||
}
|
||||
|
||||
for oldAttr := range oldExtensions {
|
||||
isRemoved := true
|
||||
for _, newAttr := range newAttrs {
|
||||
if oldAttr == newAttr {
|
||||
isRemoved = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if isRemoved {
|
||||
newExtensions[oldAttr] = nil
|
||||
}
|
||||
}
|
||||
|
||||
e := make(map[string]string, 0)
|
||||
for k, v := range newExtensions {
|
||||
if v != nil {
|
||||
e[k] = v.(string)
|
||||
}
|
||||
}
|
||||
if len(e) == 0 {
|
||||
e = nil
|
||||
}
|
||||
opts = append(opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryExtensions(e))
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, credentiallibraries.WithAutomaticVersioning(true))
|
||||
aur, err := client.Update(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating credential library: %v", err)
|
||||
}
|
||||
|
||||
if err := setFromVaultSshCertificateCredentialLibraryResponseMap(d, aur.GetResponse().Map); err != nil {
|
||||
return diag.Errorf("error setting credential library from response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialLibraryDeleteVaultSshCertificate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentiallibraries.NewClient(md.client)
|
||||
|
||||
_, err := client.Delete(ctx, d.Id())
|
||||
if err != nil {
|
||||
return diag.Errorf("error deleting credential library: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentiallibraries"
|
||||
"github.com/hashicorp/boundary/testing/controller"
|
||||
"github.com/hashicorp/boundary/testing/vault"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
||||
)
|
||||
|
||||
const (
|
||||
vaultSshCertCredResc = "boundary_credential_library_vault_ssh_certificate.example"
|
||||
vaultSshCertCredExtCOResc = "boundary_credential_library_vault_ssh_certificate.ext_co_example"
|
||||
vaultSshCertCredLibName = "foo"
|
||||
vaultSshCertCredLibDesc = "the foo"
|
||||
vaultSshCertCredLibPath = "/ssh/sign/foo"
|
||||
vaultSshCertCredUsername = "bar"
|
||||
vaultSshCertCredLibStringUpdate = "_random"
|
||||
vaultSshCertCredKeyType = "ecdsa"
|
||||
vaultSshCertCredKeyBits = 256
|
||||
)
|
||||
|
||||
var vaultSshCertCredLibResource = fmt.Sprintf(`
|
||||
resource "boundary_credential_library_vault_ssh_certificate" "example" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "%s"
|
||||
username = "%s"
|
||||
key_type = "ed25519"
|
||||
|
||||
}`, vaultSshCertCredLibName,
|
||||
vaultSshCertCredLibDesc,
|
||||
vaultSshCertCredLibPath,
|
||||
vaultSshCertCredUsername)
|
||||
|
||||
var vaultSshCertCredLibResourceUpdate = fmt.Sprintf(`
|
||||
resource "boundary_credential_library_vault_ssh_certificate" "example" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "%s"
|
||||
username = "%s"
|
||||
key_type = "%s"
|
||||
key_bits = %d
|
||||
}`, vaultSshCertCredLibName+vaultSshCertCredLibStringUpdate,
|
||||
vaultSshCertCredLibDesc+vaultSshCertCredLibStringUpdate,
|
||||
vaultSshCertCredLibPath+vaultSshCertCredLibStringUpdate,
|
||||
vaultSshCertCredUsername+vaultSshCertCredLibStringUpdate,
|
||||
vaultSshCertCredKeyType,
|
||||
vaultSshCertCredKeyBits)
|
||||
|
||||
var vaultSshCertCredLibResourceExtensionsCriticalOpts = fmt.Sprintf(`
|
||||
resource "boundary_credential_library_vault_ssh_certificate" "ext_co_example" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "%s"
|
||||
username = "%s"
|
||||
key_type = "ed25519"
|
||||
|
||||
extensions = {
|
||||
permit-pty = ""
|
||||
}
|
||||
|
||||
critical_options = {
|
||||
force-command = "/bin/foo"
|
||||
}
|
||||
}`, vaultSshCertCredLibName,
|
||||
vaultSshCertCredLibDesc,
|
||||
vaultSshCertCredLibPath,
|
||||
vaultSshCertCredUsername)
|
||||
|
||||
var vaultSshCertCredLibResourceExtensionsCriticalOptsUpdate = fmt.Sprintf(`
|
||||
resource "boundary_credential_library_vault_ssh_certificate" "ext_co_example" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "%s"
|
||||
username = "%s"
|
||||
key_type = "ed25519"
|
||||
|
||||
extensions = {
|
||||
permit-pty = ""
|
||||
permit-X11-forwarding = ""
|
||||
}
|
||||
}`, vaultSshCertCredLibName,
|
||||
vaultSshCertCredLibDesc,
|
||||
vaultSshCertCredLibPath,
|
||||
vaultSshCertCredUsername)
|
||||
|
||||
var vaultSshCertCredLibResourceExtensionsCriticalOptsUpdate2 = fmt.Sprintf(`
|
||||
resource "boundary_credential_library_vault_ssh_certificate" "ext_co_example" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "%s"
|
||||
username = "%s"
|
||||
key_type = "ed25519"
|
||||
|
||||
extensions = {
|
||||
permit-pty = ""
|
||||
}
|
||||
|
||||
critical_options = {
|
||||
force-command = "/bin/foo"
|
||||
source-address = "10.10.0.0/16"
|
||||
}
|
||||
}`, vaultSshCertCredLibName,
|
||||
vaultSshCertCredLibDesc,
|
||||
vaultSshCertCredLibPath,
|
||||
vaultSshCertCredUsername)
|
||||
|
||||
func TestAccCredentialLibraryVaultSshCertificate(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
vc := vault.NewTestVaultServer(t)
|
||||
_, token := vc.CreateToken(t)
|
||||
credStoreRes := vaultCredStoreResource(vc,
|
||||
vaultCredStoreName,
|
||||
vaultCredStoreDesc,
|
||||
vaultCredStoreNamespace,
|
||||
"www.original.com",
|
||||
token,
|
||||
true)
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
IsUnitTest: true,
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckCredentialLibraryVaultSshCertificateResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, vaultSshCertCredLibResource),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredResc, NameKey, vaultSshCertCredLibName),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredResc, DescriptionKey, vaultSshCertCredLibDesc),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredResc, credentialLibraryVaultPathKey, vaultSshCertCredLibPath),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredResc, credentialLibraryVaultSshCertificateUsernameKey, vaultSshCertCredUsername),
|
||||
|
||||
testAccCheckCredentialLibraryVaultSshCertificateResourceExists(provider, vaultSshCertCredResc),
|
||||
),
|
||||
},
|
||||
importStep(vaultSshCertCredResc),
|
||||
{
|
||||
// update
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, vaultSshCertCredLibResourceUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredResc, NameKey, vaultSshCertCredLibName+vaultSshCertCredLibStringUpdate),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredResc, DescriptionKey, vaultSshCertCredLibDesc+vaultSshCertCredLibStringUpdate),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredResc, credentialLibraryVaultPathKey, vaultSshCertCredLibPath+vaultSshCertCredLibStringUpdate),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredResc, credentialLibraryVaultSshCertificateUsernameKey, vaultSshCertCredUsername+vaultSshCertCredLibStringUpdate),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredResc, credentialLibraryVaultSshCertificateKeyTypeKey, vaultSshCertCredKeyType),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredResc, credentialLibraryVaultSshCertificateKeyBitsKey, strconv.Itoa(vaultSshCertCredKeyBits)),
|
||||
|
||||
testAccCheckCredentialLibraryVaultSshCertificateResourceExists(provider, vaultSshCertCredResc),
|
||||
),
|
||||
},
|
||||
importStep(vaultSshCertCredResc),
|
||||
{
|
||||
// create with extensions and critical options
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, vaultSshCertCredLibResourceExtensionsCriticalOpts),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredExtCOResc, NameKey, vaultSshCertCredLibName),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredExtCOResc, DescriptionKey, vaultSshCertCredLibDesc),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredExtCOResc, credentialLibraryVaultPathKey, vaultSshCertCredLibPath),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredExtCOResc, credentialLibraryVaultSshCertificateUsernameKey, vaultSshCertCredUsername),
|
||||
|
||||
testAccCheckCredentialLibraryVaultSshCertificateResourceExists(provider, vaultSshCertCredExtCOResc),
|
||||
),
|
||||
},
|
||||
importStep(vaultSshCertCredExtCOResc),
|
||||
{
|
||||
// update with extensions and remove critical options
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, vaultSshCertCredLibResourceExtensionsCriticalOptsUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredExtCOResc, NameKey, vaultSshCertCredLibName),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredExtCOResc, DescriptionKey, vaultSshCertCredLibDesc),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredExtCOResc, credentialLibraryVaultPathKey, vaultSshCertCredLibPath),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredExtCOResc, credentialLibraryVaultSshCertificateUsernameKey, vaultSshCertCredUsername),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredExtCOResc, credentialLibraryVaultSshCertificateCriticalOptionsKey+".%", "0"),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredExtCOResc, credentialLibraryVaultSshCertificateExtensionsKey+".%", "2"),
|
||||
|
||||
testAccCheckCredentialLibraryVaultSshCertificateResourceExists(provider, vaultSshCertCredExtCOResc),
|
||||
),
|
||||
},
|
||||
importStep(vaultSshCertCredExtCOResc),
|
||||
{
|
||||
// update with extensions and remove critical options
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, vaultSshCertCredLibResourceExtensionsCriticalOptsUpdate2),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredExtCOResc, NameKey, vaultSshCertCredLibName),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredExtCOResc, DescriptionKey, vaultSshCertCredLibDesc),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredExtCOResc, credentialLibraryVaultPathKey, vaultSshCertCredLibPath),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredExtCOResc, credentialLibraryVaultSshCertificateUsernameKey, vaultSshCertCredUsername),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredExtCOResc, credentialLibraryVaultSshCertificateCriticalOptionsKey+".%", "2"),
|
||||
resource.TestCheckResourceAttr(vaultSshCertCredExtCOResc, credentialLibraryVaultSshCertificateExtensionsKey+".%", "1"),
|
||||
|
||||
testAccCheckCredentialLibraryVaultSshCertificateResourceExists(provider, vaultSshCertCredExtCOResc),
|
||||
),
|
||||
},
|
||||
importStep(vaultSshCertCredExtCOResc),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckCredentialLibraryVaultSshCertificateResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("not found: %s", name)
|
||||
}
|
||||
|
||||
id := rs.Primary.ID
|
||||
if id == "" {
|
||||
return fmt.Errorf("no ID is set")
|
||||
}
|
||||
|
||||
md := testProvider.Meta().(*metaData)
|
||||
c := credentiallibraries.NewClient(md.client)
|
||||
if _, err := c.Read(context.Background(), id); err != nil {
|
||||
return fmt.Errorf("got an error reading %q: %w", id, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckCredentialLibraryVaultSshCertificateResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if testProvider.Meta() == nil {
|
||||
t.Fatal("got nil provider metadata")
|
||||
}
|
||||
md := testProvider.Meta().(*metaData)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
switch rs.Type {
|
||||
case "boundary_credential_library_vault":
|
||||
id := rs.Primary.ID
|
||||
|
||||
c := credentiallibraries.NewClient(md.client)
|
||||
_, err := c.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed vault credential library %q: %v", id, err)
|
||||
}
|
||||
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
@@ -16,23 +19,26 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
vaultCredResc = "boundary_credential_library_vault.example"
|
||||
vaultCredLibName = "foo"
|
||||
vaultCredLibDesc = "the foo"
|
||||
vaultCredLibPath = "/foo/bar"
|
||||
vaultCredLibMethodGet = "GET"
|
||||
vaultCredLibMethodPost = "POST"
|
||||
vaultCredLibRequestBody = "foobar"
|
||||
vaultCredLibStringUpdate = "_random"
|
||||
vaultCredResc = "boundary_credential_library_vault.example"
|
||||
vaultCredTypedResc = "boundary_credential_library_vault.typed_example"
|
||||
vaultCredUsernamePasswordResc = "boundary_credential_library_vault.username_password_mapping_override"
|
||||
vaultCredSshPrivateKeyResc = "boundary_credential_library_vault.ssh_private_key_mapping_override"
|
||||
vaultCredLibName = "foo"
|
||||
vaultCredLibDesc = "the foo"
|
||||
vaultCredLibPath = "/foo/bar"
|
||||
vaultCredLibMethodGet = "GET"
|
||||
vaultCredLibMethodPost = "POST"
|
||||
vaultCredLibRequestBody = "foobar"
|
||||
vaultCredLibStringUpdate = "_random"
|
||||
)
|
||||
|
||||
var vaultCredLibResource = fmt.Sprintf(`
|
||||
resource "boundary_credential_library_vault" "example" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "%s"
|
||||
http_method = "%s"
|
||||
path = "%s"
|
||||
http_method = "%s"
|
||||
}`, vaultCredLibName,
|
||||
vaultCredLibDesc,
|
||||
vaultCredLibPath,
|
||||
@@ -40,18 +46,96 @@ resource "boundary_credential_library_vault" "example" {
|
||||
|
||||
var vaultCredLibResourceUpdate = fmt.Sprintf(`
|
||||
resource "boundary_credential_library_vault" "example" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "%s"
|
||||
http_method = "%s"
|
||||
http_request_body = "%s"
|
||||
path = "%s"
|
||||
http_method = "%s"
|
||||
http_request_body = "%s"
|
||||
}`, vaultCredLibName+vaultCredLibStringUpdate,
|
||||
vaultCredLibDesc+vaultCredLibStringUpdate,
|
||||
vaultCredLibPath+vaultCredLibStringUpdate,
|
||||
vaultCredLibMethodPost,
|
||||
vaultCredLibRequestBody)
|
||||
|
||||
var vaultTypedCredLibResource = fmt.Sprintf(`
|
||||
resource "boundary_credential_library_vault" "typed_example" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "%s"
|
||||
http_method = "%s"
|
||||
credential_type = "ssh_private_key"
|
||||
}`, vaultCredLibName,
|
||||
vaultCredLibDesc,
|
||||
vaultCredLibPath,
|
||||
vaultCredLibMethodGet)
|
||||
|
||||
var vaultUsernamePasswordMappingOverrideCredLibResource = fmt.Sprintf(`
|
||||
resource "boundary_credential_library_vault" "username_password_mapping_override" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "%s"
|
||||
http_method = "%s"
|
||||
credential_type = "username_password"
|
||||
credential_mapping_overrides = {
|
||||
password_attribute = "alternative_password_label"
|
||||
username_attribute = "alternative_username_label"
|
||||
}
|
||||
}`, vaultCredLibName,
|
||||
vaultCredLibDesc,
|
||||
vaultCredLibPath,
|
||||
vaultCredLibMethodGet)
|
||||
|
||||
var vaultUsernamePasswordMappingOverrideCredLibResourceUpdate = fmt.Sprintf(`
|
||||
resource "boundary_credential_library_vault" "username_password_mapping_override" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "%s"
|
||||
http_method = "%s"
|
||||
credential_type = "username_password"
|
||||
credential_mapping_overrides = {
|
||||
password_attribute = "updated_password_label"
|
||||
username_attribute = "updated_username_label"
|
||||
}
|
||||
}`, vaultCredLibName,
|
||||
vaultCredLibDesc,
|
||||
vaultCredLibPath,
|
||||
vaultCredLibMethodGet)
|
||||
|
||||
var vaultUsernamePasswordMappingOverrideCredLibResourceRemove = fmt.Sprintf(`
|
||||
resource "boundary_credential_library_vault" "username_password_mapping_override" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "%s"
|
||||
http_method = "%s"
|
||||
credential_type = "username_password"
|
||||
}`, vaultCredLibName,
|
||||
vaultCredLibDesc,
|
||||
vaultCredLibPath,
|
||||
vaultCredLibMethodGet)
|
||||
|
||||
var vaultSshPrivateKeyMappingOverrideCredLibResource = fmt.Sprintf(`
|
||||
resource "boundary_credential_library_vault" "ssh_private_key_mapping_override" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "%s"
|
||||
http_method = "%s"
|
||||
credential_type = "ssh_private_key"
|
||||
credential_mapping_overrides = {
|
||||
private_key_attribute = "alternative_key_label"
|
||||
private_key_passphrase_attribute = "alternative_passphrase_label"
|
||||
username_attribute = "alternative_username_label"
|
||||
}
|
||||
}`, vaultCredLibName,
|
||||
vaultCredLibDesc,
|
||||
vaultCredLibPath,
|
||||
vaultCredLibMethodGet)
|
||||
|
||||
func TestAccCredentialLibraryVault(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
@@ -69,8 +153,9 @@ func TestAccCredentialLibraryVault(t *testing.T) {
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
IsUnitTest: true,
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckAuthMethodResourceDestroy(t, provider),
|
||||
CheckDestroy: testAccCheckCredentialLibraryVaultResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
@@ -101,6 +186,90 @@ func TestAccCredentialLibraryVault(t *testing.T) {
|
||||
),
|
||||
},
|
||||
importStep(vaultCredResc),
|
||||
|
||||
{
|
||||
// create typed credential library, note credential type is immutable so no need for update test
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, vaultCredLibResourceUpdate, vaultTypedCredLibResource),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(vaultCredTypedResc, NameKey, vaultCredLibName),
|
||||
resource.TestCheckResourceAttr(vaultCredTypedResc, DescriptionKey, vaultCredLibDesc),
|
||||
resource.TestCheckResourceAttr(vaultCredTypedResc, credentialLibraryVaultPathKey, vaultCredLibPath),
|
||||
resource.TestCheckResourceAttr(vaultCredTypedResc, credentialLibraryVaultHttpMethodKey, vaultCredLibMethodGet),
|
||||
resource.TestCheckResourceAttr(vaultCredTypedResc, credentialLibraryVaultHttpRequestBodyKey, ""),
|
||||
resource.TestCheckResourceAttr(vaultCredTypedResc, credentialLibraryCredentialTypeKey, "ssh_private_key"),
|
||||
|
||||
testAccCheckCredentialLibraryVaultResourceExists(provider, vaultCredTypedResc),
|
||||
),
|
||||
},
|
||||
importStep(vaultCredResc),
|
||||
|
||||
{
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, vaultCredLibResourceUpdate, vaultUsernamePasswordMappingOverrideCredLibResource),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, NameKey, vaultCredLibName),
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, DescriptionKey, vaultCredLibDesc),
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, credentialLibraryVaultPathKey, vaultCredLibPath),
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, credentialLibraryVaultHttpMethodKey, vaultCredLibMethodGet),
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, credentialLibraryVaultHttpRequestBodyKey, ""),
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, credentialLibraryCredentialTypeKey, "username_password"),
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, "credential_mapping_overrides.password_attribute", "alternative_password_label"),
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, "credential_mapping_overrides.username_attribute", "alternative_username_label"),
|
||||
|
||||
testAccCheckCredentialLibraryVaultResourceExists(provider, vaultCredUsernamePasswordResc),
|
||||
),
|
||||
},
|
||||
importStep(vaultCredResc),
|
||||
|
||||
{
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, vaultCredLibResourceUpdate, vaultUsernamePasswordMappingOverrideCredLibResourceUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, NameKey, vaultCredLibName),
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, DescriptionKey, vaultCredLibDesc),
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, credentialLibraryVaultPathKey, vaultCredLibPath),
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, credentialLibraryVaultHttpMethodKey, vaultCredLibMethodGet),
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, credentialLibraryVaultHttpRequestBodyKey, ""),
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, credentialLibraryCredentialTypeKey, "username_password"),
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, "credential_mapping_overrides.password_attribute", "updated_password_label"),
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, "credential_mapping_overrides.username_attribute", "updated_username_label"),
|
||||
|
||||
testAccCheckCredentialLibraryVaultResourceExists(provider, vaultCredUsernamePasswordResc),
|
||||
),
|
||||
},
|
||||
importStep(vaultCredResc),
|
||||
|
||||
{
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, vaultCredLibResourceUpdate, vaultUsernamePasswordMappingOverrideCredLibResourceRemove),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, NameKey, vaultCredLibName),
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, DescriptionKey, vaultCredLibDesc),
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, credentialLibraryVaultPathKey, vaultCredLibPath),
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, credentialLibraryVaultHttpMethodKey, vaultCredLibMethodGet),
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, credentialLibraryVaultHttpRequestBodyKey, ""),
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, credentialLibraryCredentialTypeKey, "username_password"),
|
||||
resource.TestCheckResourceAttr(vaultCredUsernamePasswordResc, credentialLibraryCredentialMappingOverridesKey+".%", "0"),
|
||||
|
||||
testAccCheckCredentialLibraryVaultResourceExists(provider, vaultCredUsernamePasswordResc),
|
||||
),
|
||||
},
|
||||
importStep(vaultCredResc),
|
||||
|
||||
{
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, vaultCredLibResourceUpdate, vaultSshPrivateKeyMappingOverrideCredLibResource),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(vaultCredSshPrivateKeyResc, NameKey, vaultCredLibName),
|
||||
resource.TestCheckResourceAttr(vaultCredSshPrivateKeyResc, DescriptionKey, vaultCredLibDesc),
|
||||
resource.TestCheckResourceAttr(vaultCredSshPrivateKeyResc, credentialLibraryVaultPathKey, vaultCredLibPath),
|
||||
resource.TestCheckResourceAttr(vaultCredSshPrivateKeyResc, credentialLibraryVaultHttpMethodKey, vaultCredLibMethodGet),
|
||||
resource.TestCheckResourceAttr(vaultCredSshPrivateKeyResc, credentialLibraryVaultHttpRequestBodyKey, ""),
|
||||
resource.TestCheckResourceAttr(vaultCredSshPrivateKeyResc, credentialLibraryCredentialTypeKey, "ssh_private_key"),
|
||||
resource.TestCheckResourceAttr(vaultCredSshPrivateKeyResc, "credential_mapping_overrides.private_key_attribute", "alternative_key_label"),
|
||||
resource.TestCheckResourceAttr(vaultCredSshPrivateKeyResc, "credential_mapping_overrides.private_key_passphrase_attribute", "alternative_passphrase_label"),
|
||||
resource.TestCheckResourceAttr(vaultCredSshPrivateKeyResc, "credential_mapping_overrides.username_attribute", "alternative_username_label"),
|
||||
|
||||
testAccCheckCredentialLibraryVaultResourceExists(provider, vaultCredSshPrivateKeyResc),
|
||||
),
|
||||
},
|
||||
importStep(vaultCredResc),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentials"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
credentialSshPrivateKeyUsernameKey = "username"
|
||||
credentialSshPrivateKeyPrivateKeyKey = "private_key"
|
||||
credentialSshPrivateKeyPrivateKeyHmacKey = "private_key_hmac"
|
||||
credentialSshPrivateKeyPassphraseKey = "private_key_passphrase"
|
||||
credentialSshPrivateKeyPassphraseHmacKey = "private_key_passphrase_hmac"
|
||||
credentialSshPrivateKeyCredentialType = "ssh_private_key"
|
||||
)
|
||||
|
||||
func resourceCredentialSshPrivateKey() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The SSH private key credential resource allows you to configure a credential using a username, private key and optional passphrase.",
|
||||
|
||||
CreateContext: resourceCredentialSshPrivateKeyCreate,
|
||||
ReadContext: resourceCredentialSshPrivateKeyRead,
|
||||
UpdateContext: resourceCredentialSshPrivateKeyUpdate,
|
||||
DeleteContext: resourceCredentialSshPrivateKeyDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
IDKey: {
|
||||
Description: "The ID of the credential.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
NameKey: {
|
||||
Description: "The name of the credential. Defaults to the resource name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
DescriptionKey: {
|
||||
Description: "The description of the credential.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialStoreIdKey: {
|
||||
Description: "ID of the credential store this credential belongs to.",
|
||||
Type: schema.TypeString,
|
||||
ForceNew: true,
|
||||
Required: true,
|
||||
},
|
||||
credentialSshPrivateKeyUsernameKey: {
|
||||
Description: "The username associated with the credential.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
credentialSshPrivateKeyPrivateKeyKey: {
|
||||
Description: "The private key associated with the credential.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
credentialSshPrivateKeyPrivateKeyHmacKey: {
|
||||
Description: "The private key hmac.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
credentialSshPrivateKeyPassphraseKey: {
|
||||
Description: "The passphrase of the private key associated with the credential.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
credentialSshPrivateKeyPassphraseHmacKey: {
|
||||
Description: "The private key passphrase hmac.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromCredentialSshPrivateKeyResponseMap(d *schema.ResourceData, raw map[string]interface{}, fromRead bool) error {
|
||||
if err := d.Set(NameKey, raw[NameKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(DescriptionKey, raw[DescriptionKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(credentialStoreIdKey, raw[credentialStoreIdKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if attrsVal, ok := raw["attributes"]; ok {
|
||||
attrs := attrsVal.(map[string]interface{})
|
||||
if err := d.Set(credentialSshPrivateKeyUsernameKey, attrs[credentialSshPrivateKeyUsernameKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
statePrivKeyHmac := d.Get(credentialSshPrivateKeyPrivateKeyHmacKey)
|
||||
boundaryPrivKeyHmac := attrs[credentialSshPrivateKeyPrivateKeyHmacKey].(string)
|
||||
if statePrivKeyHmac.(string) != boundaryPrivKeyHmac && fromRead {
|
||||
// PrivateKeyHmac has changed in Boundary, therefore the private key has changed.
|
||||
// Update private key value to force tf to attempt update.
|
||||
if err := d.Set(credentialSshPrivateKeyPrivateKeyKey, "(changed in Boundary)"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := d.Set(credentialSshPrivateKeyPrivateKeyHmacKey, boundaryPrivKeyHmac); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var statePassphraseHmac, boundaryPassphraseHmac string
|
||||
if v := d.Get(credentialSshPrivateKeyPassphraseHmacKey); v != nil {
|
||||
statePassphraseHmac = v.(string)
|
||||
}
|
||||
if v := attrs[credentialSshPrivateKeyPassphraseHmacKey]; v != nil {
|
||||
boundaryPassphraseHmac = v.(string)
|
||||
}
|
||||
if statePassphraseHmac != boundaryPassphraseHmac && fromRead {
|
||||
// PassphraseHmac has changed in Boundary, therefore the private key passphrase has changed.
|
||||
// Update private key passphrase value to force tf to attempt update.
|
||||
if err := d.Set(credentialSshPrivateKeyPassphraseKey, "(changed in Boundary)"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := d.Set(credentialSshPrivateKeyPassphraseHmacKey, boundaryPassphraseHmac); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialSshPrivateKeyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
|
||||
var opts []credentials.Option
|
||||
if v, ok := d.GetOk(NameKey); ok {
|
||||
opts = append(opts, credentials.WithName(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(DescriptionKey); ok {
|
||||
opts = append(opts, credentials.WithDescription(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialSshPrivateKeyUsernameKey); ok {
|
||||
opts = append(opts, credentials.WithSshPrivateKeyCredentialUsername(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialSshPrivateKeyPrivateKeyKey); ok {
|
||||
opts = append(opts, credentials.WithSshPrivateKeyCredentialPrivateKey(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialSshPrivateKeyPassphraseKey); ok {
|
||||
opts = append(opts, credentials.WithSshPrivateKeyCredentialPrivateKeyPassphrase(v.(string)))
|
||||
}
|
||||
|
||||
var credentialStoreId string
|
||||
retrievedStoreId, ok := d.GetOk(credentialStoreIdKey)
|
||||
if ok {
|
||||
credentialStoreId = retrievedStoreId.(string)
|
||||
} else {
|
||||
return diag.Errorf("credential store id is unset")
|
||||
}
|
||||
|
||||
client := credentials.NewClient(md.client)
|
||||
cr, err := client.Create(ctx, credentialSshPrivateKeyCredentialType, credentialStoreId, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error creating credential: %v", err)
|
||||
}
|
||||
if cr == nil {
|
||||
return diag.Errorf("nil credential after create")
|
||||
}
|
||||
|
||||
if err := setFromCredentialSshPrivateKeyResponseMap(d, cr.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential from response map: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialSshPrivateKeyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentials.NewClient(md.client)
|
||||
|
||||
cr, err := client.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
return diag.Errorf("error reading credential: %v", err)
|
||||
}
|
||||
if cr == nil {
|
||||
return diag.Errorf("credential nil after read")
|
||||
}
|
||||
|
||||
if err := setFromCredentialSshPrivateKeyResponseMap(d, cr.GetResponse().Map, true); err != nil {
|
||||
return diag.Errorf("error generating credential from response map: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialSshPrivateKeyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentials.NewClient(md.client)
|
||||
|
||||
var opts []credentials.Option
|
||||
if d.HasChange(NameKey) {
|
||||
opts = append(opts, credentials.DefaultName())
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
opts = append(opts, credentials.WithName(nameVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(DescriptionKey) {
|
||||
opts = append(opts, credentials.DefaultDescription())
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
opts = append(opts, credentials.WithDescription(descVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialSshPrivateKeyUsernameKey) {
|
||||
usernameVal, ok := d.GetOk(credentialSshPrivateKeyUsernameKey)
|
||||
if ok {
|
||||
opts = append(opts, credentials.WithSshPrivateKeyCredentialUsername(usernameVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialSshPrivateKeyPrivateKeyKey) {
|
||||
privKeyVal, ok := d.GetOk(credentialSshPrivateKeyPrivateKeyKey)
|
||||
if ok {
|
||||
opts = append(opts, credentials.WithSshPrivateKeyCredentialPrivateKey(privKeyVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialSshPrivateKeyPassphraseKey) {
|
||||
passVal, ok := d.GetOk(credentialSshPrivateKeyPassphraseKey)
|
||||
if ok {
|
||||
opts = append(opts, credentials.WithSshPrivateKeyCredentialPrivateKeyPassphrase(passVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, credentials.WithAutomaticVersioning(true))
|
||||
crUpdate, err := client.Update(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating credential: %v", err)
|
||||
}
|
||||
if crUpdate == nil {
|
||||
return diag.Errorf("credential nil after update")
|
||||
}
|
||||
|
||||
if err = setFromCredentialSshPrivateKeyResponseMap(d, crUpdate.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential from response map: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialSshPrivateKeyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentials.NewClient(md.client)
|
||||
|
||||
_, err := client.Delete(ctx, d.Id())
|
||||
if err != nil {
|
||||
return diag.Errorf("error deleting credential: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentials"
|
||||
"github.com/hashicorp/boundary/testing/controller"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
||||
"golang.org/x/crypto/ssh/testdata"
|
||||
)
|
||||
|
||||
const (
|
||||
sshPrivateKeyCredResc = "boundary_credential_ssh_private_key.example"
|
||||
sshPrivateKeyCredName = "Dr Jekyll"
|
||||
sshPrivateKeyCredDesc = "my best description"
|
||||
sshPrivateKeyUpdate = "some magic update string"
|
||||
sshPrivateKeyUsername = "my-user"
|
||||
)
|
||||
|
||||
var staticStore = fmt.Sprintf(`
|
||||
resource "boundary_credential_store_static" "ssh_store" {
|
||||
name = "static store name"
|
||||
description = "static store description"
|
||||
scope_id = boundary_scope.proj1.id
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
}`)
|
||||
|
||||
func sshPrivateKeyResource(name, description, username, privateKey, passphrase string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "boundary_credential_ssh_private_key" "example" {
|
||||
name = %q
|
||||
description = %q
|
||||
credential_store_id = boundary_credential_store_static.ssh_store.id
|
||||
username = %q
|
||||
private_key = %q
|
||||
private_key_passphrase = %q
|
||||
}`, name, description, username, privateKey, passphrase)
|
||||
}
|
||||
|
||||
func TestAccCredentialSshPrivateKey(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
privKey := string(testdata.PEMBytes["rsa"])
|
||||
privKeyUpdate := string(testdata.PEMEncryptedKeys[0].PEMBytes)
|
||||
privKeyUpdatePassphrase := testdata.PEMEncryptedKeys[0].EncryptionKey
|
||||
|
||||
res := sshPrivateKeyResource(
|
||||
sshPrivateKeyCredName,
|
||||
sshPrivateKeyCredDesc,
|
||||
sshPrivateKeyUsername,
|
||||
privKey,
|
||||
"",
|
||||
)
|
||||
|
||||
resUpdate := sshPrivateKeyResource(
|
||||
sshPrivateKeyCredName+sshPrivateKeyUpdate,
|
||||
sshPrivateKeyCredDesc+sshPrivateKeyUpdate,
|
||||
sshPrivateKeyUsername+sshPrivateKeyUpdate,
|
||||
privKeyUpdate,
|
||||
privKeyUpdatePassphrase,
|
||||
)
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
IsUnitTest: true,
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckCredentialSshPrivateKeyResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, staticStore, res),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(sshPrivateKeyCredResc, NameKey, sshPrivateKeyCredName),
|
||||
resource.TestCheckResourceAttr(sshPrivateKeyCredResc, DescriptionKey, sshPrivateKeyCredDesc),
|
||||
resource.TestCheckResourceAttr(sshPrivateKeyCredResc, credentialSshPrivateKeyUsernameKey, sshPrivateKeyUsername),
|
||||
resource.TestCheckResourceAttr(sshPrivateKeyCredResc, credentialSshPrivateKeyPrivateKeyKey, privKey),
|
||||
resource.TestCheckResourceAttr(sshPrivateKeyCredResc, credentialSshPrivateKeyPassphraseKey, ""),
|
||||
|
||||
testAccCheckCredentialStoreSshPrivateKeyHmac(provider),
|
||||
testAccCheckCredentialSshPrivateKeyResourceExists(provider, sshPrivateKeyCredResc),
|
||||
),
|
||||
},
|
||||
importStep(sshPrivateKeyCredResc, credentialSshPrivateKeyPrivateKeyKey, credentialSshPrivateKeyPassphraseKey),
|
||||
{
|
||||
// update
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, staticStore, resUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(sshPrivateKeyCredResc, NameKey, sshPrivateKeyCredName+sshPrivateKeyUpdate),
|
||||
resource.TestCheckResourceAttr(sshPrivateKeyCredResc, DescriptionKey, sshPrivateKeyCredDesc+sshPrivateKeyUpdate),
|
||||
resource.TestCheckResourceAttr(sshPrivateKeyCredResc, credentialSshPrivateKeyUsernameKey, sshPrivateKeyUsername+sshPrivateKeyUpdate),
|
||||
resource.TestCheckResourceAttr(sshPrivateKeyCredResc, credentialSshPrivateKeyPrivateKeyKey, privKeyUpdate),
|
||||
resource.TestCheckResourceAttr(sshPrivateKeyCredResc, credentialSshPrivateKeyPassphraseKey, privKeyUpdatePassphrase),
|
||||
|
||||
testAccCheckCredentialStoreSshPrivateKeyHmac(provider),
|
||||
testAccCheckCredentialSshPrivateKeyResourceExists(provider, sshPrivateKeyCredResc),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckCredentialSshPrivateKeyResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("not found: %s", name)
|
||||
}
|
||||
|
||||
id := rs.Primary.ID
|
||||
if id == "" {
|
||||
return fmt.Errorf("no ID is set")
|
||||
}
|
||||
storeId = id
|
||||
|
||||
md := testProvider.Meta().(*metaData)
|
||||
c := credentials.NewClient(md.client)
|
||||
if _, err := c.Read(context.Background(), id); err != nil {
|
||||
return fmt.Errorf("got an error reading %q: %w", id, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckCredentialSshPrivateKeyResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if testProvider.Meta() == nil {
|
||||
t.Fatal("got nil provider metadata")
|
||||
}
|
||||
md := testProvider.Meta().(*metaData)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
switch rs.Type {
|
||||
case "boundary_credential_ssh_private_key":
|
||||
id := rs.Primary.ID
|
||||
|
||||
c := credentials.NewClient(md.client)
|
||||
_, err := c.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed credential %q: %v", id, err)
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckCredentialStoreSshPrivateKeyHmac(testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[sshPrivateKeyCredResc]
|
||||
if !ok {
|
||||
return fmt.Errorf("not found: %s", sshPrivateKeyCredResc)
|
||||
}
|
||||
|
||||
computed := rs.Primary.Attributes["private_key_hmac"]
|
||||
if len(computed) != 43 {
|
||||
return fmt.Errorf("computed private key hmac not the expected length of 43 characters, got: %q", computed)
|
||||
}
|
||||
|
||||
if rs.Primary.Attributes["private_key_passphrase"] != "" {
|
||||
// We set a passphrase, validate the computed hmac is expected length
|
||||
computed := rs.Primary.Attributes["private_key_passphrase_hmac"]
|
||||
if len(computed) != 43 {
|
||||
return fmt.Errorf("computed private key passphrase hmac not the expected length of 43 characters, got: %q", computed)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentialstores"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
|
||||
const staticCredentialStoreType = "static"
|
||||
|
||||
func resourceCredentialStoreStatic() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The static credential store resource allows you to configure a Boundary static credential store.",
|
||||
|
||||
CreateContext: resourceStaticCredentialStoreCreate,
|
||||
ReadContext: resourceStaticCredentialStoreRead,
|
||||
UpdateContext: resourceStaticCredentialStoreUpdate,
|
||||
DeleteContext: resourceStaticCredentialStoreDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
IDKey: {
|
||||
Description: "The ID of the static credential store.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
NameKey: {
|
||||
Description: "The static credential store name. Defaults to the resource name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
DescriptionKey: {
|
||||
Description: "The static credential store description.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
ScopeIdKey: {
|
||||
Description: "The scope for this credential store.",
|
||||
Type: schema.TypeString,
|
||||
ForceNew: true,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromStaticCredentialStoreResponseMap(d *schema.ResourceData, raw map[string]interface{}, fromRead bool) error {
|
||||
if err := d.Set(NameKey, raw[NameKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(DescriptionKey, raw[DescriptionKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(ScopeIdKey, raw[ScopeIdKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceStaticCredentialStoreCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
|
||||
var opts []credentialstores.Option
|
||||
if v, ok := d.GetOk(NameKey); ok {
|
||||
opts = append(opts, credentialstores.WithName(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(DescriptionKey); ok {
|
||||
opts = append(opts, credentialstores.WithDescription(v.(string)))
|
||||
}
|
||||
|
||||
var scope string
|
||||
gotScope, ok := d.GetOk(ScopeIdKey)
|
||||
if ok {
|
||||
scope = gotScope.(string)
|
||||
} else {
|
||||
return diag.Errorf("no scope is set")
|
||||
}
|
||||
|
||||
client := credentialstores.NewClient(md.client)
|
||||
cr, err := client.Create(ctx, staticCredentialStoreType, scope, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error creating credential store: %v", err)
|
||||
}
|
||||
if cr == nil {
|
||||
return diag.Errorf("nil credential store after create")
|
||||
}
|
||||
|
||||
if err := setFromStaticCredentialStoreResponseMap(d, cr.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential store from response map: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceStaticCredentialStoreRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentialstores.NewClient(md.client)
|
||||
|
||||
cr, err := client.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
return diag.Errorf("error reading credential store: %v", err)
|
||||
}
|
||||
if cr == nil {
|
||||
return diag.Errorf("credential store nil after read")
|
||||
}
|
||||
|
||||
if err := setFromStaticCredentialStoreResponseMap(d, cr.GetResponse().Map, true); err != nil {
|
||||
return diag.Errorf("error generating credential store from response map: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceStaticCredentialStoreUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentialstores.NewClient(md.client)
|
||||
|
||||
var opts []credentialstores.Option
|
||||
if d.HasChange(NameKey) {
|
||||
opts = append(opts, credentialstores.DefaultName())
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithName(nameVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(DescriptionKey) {
|
||||
opts = append(opts, credentialstores.DefaultDescription())
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithDescription(descVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, credentialstores.WithAutomaticVersioning(true))
|
||||
crUpdate, err := client.Update(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating credential store: %v", err)
|
||||
}
|
||||
if crUpdate == nil {
|
||||
return diag.Errorf("credential store nil after update")
|
||||
}
|
||||
|
||||
if err = setFromStaticCredentialStoreResponseMap(d, crUpdate.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential store from response map: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceStaticCredentialStoreDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentialstores.NewClient(md.client)
|
||||
|
||||
_, err := client.Delete(ctx, d.Id())
|
||||
if err != nil {
|
||||
return diag.Errorf("error deleting credential store: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentialstores"
|
||||
"github.com/hashicorp/boundary/testing/controller"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
||||
)
|
||||
|
||||
const (
|
||||
staticCredStoreResc = "boundary_credential_store_static.example"
|
||||
staticCredStoreName = "foo"
|
||||
staticCredStoreDesc = "the foo"
|
||||
staticCredStoreNamespace = "static"
|
||||
staticCredStoreUpdate = "_random"
|
||||
)
|
||||
|
||||
func staticCredStoreResource(name string, description string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "boundary_credential_store_static" "example" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
scope_id = boundary_scope.proj1.id
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
}`, name,
|
||||
description)
|
||||
}
|
||||
|
||||
func TestAccCredentialStoreStatic(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
res := staticCredStoreResource(staticCredStoreName,
|
||||
staticCredStoreDesc)
|
||||
|
||||
resUpdate := staticCredStoreResource(staticCredStoreName+staticCredStoreUpdate,
|
||||
staticCredStoreDesc+staticCredStoreUpdate)
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckCredentialStoreStaticResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, res),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(staticCredStoreResc, NameKey, staticCredStoreName),
|
||||
resource.TestCheckResourceAttr(staticCredStoreResc, DescriptionKey, staticCredStoreDesc),
|
||||
|
||||
testAccCheckCredentialStoreVaultResourceExists(provider, staticCredStoreResc),
|
||||
),
|
||||
},
|
||||
importStep(staticCredStoreResc),
|
||||
{
|
||||
// update
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(staticCredStoreResc, NameKey, staticCredStoreName+staticCredStoreUpdate),
|
||||
resource.TestCheckResourceAttr(staticCredStoreResc, DescriptionKey, staticCredStoreDesc+staticCredStoreUpdate),
|
||||
|
||||
testAccCheckCredentialStoreVaultResourceExists(provider, staticCredStoreResc),
|
||||
),
|
||||
},
|
||||
importStep(staticCredStoreResc),
|
||||
{
|
||||
// Run a plan only update and verify no changes
|
||||
PlanOnly: true,
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
},
|
||||
importStep(staticCredStoreResc),
|
||||
{
|
||||
// update again but apply a preConfig to externally update resource
|
||||
// TODO: Boundary currently causes an error on moving back to a previously
|
||||
// used token, for now verify that a plan only step had changes
|
||||
PreConfig: func() { staticCredentialStoreExternalUpdate(t, provider) },
|
||||
PlanOnly: true,
|
||||
ExpectNonEmptyPlan: true,
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
},
|
||||
importStep(staticCredStoreResc),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func staticCredentialStoreExternalUpdate(t *testing.T, testProvider *schema.Provider) {
|
||||
if storeId == "" {
|
||||
t.Fatal("storeId must be set before testing an external update")
|
||||
}
|
||||
|
||||
md := testProvider.Meta().(*metaData)
|
||||
c := credentialstores.NewClient(md.client)
|
||||
cr, err := c.Read(context.Background(), storeId)
|
||||
if err != nil {
|
||||
t.Fatal(fmt.Errorf("got an error reading %q: %w", storeId, err))
|
||||
}
|
||||
|
||||
// update credential store options
|
||||
var opts []credentialstores.Option
|
||||
opts = append(opts, credentialstores.WithDescription("this is an updated description, my guy"))
|
||||
|
||||
_, err = c.Update(context.Background(), cr.Item.Id, cr.Item.Version, opts...)
|
||||
if err != nil {
|
||||
t.Fatal(fmt.Errorf("got an error updating %q: %w", cr.Item.Id, err))
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckCredentialStoreStaticResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if testProvider.Meta() == nil {
|
||||
t.Fatal("got nil provider metadata")
|
||||
}
|
||||
md := testProvider.Meta().(*metaData)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
switch rs.Type {
|
||||
case "boundary_credential_store_static":
|
||||
id := rs.Primary.ID
|
||||
|
||||
c := credentialstores.NewClient(md.client)
|
||||
_, err := c.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed static credential store %q: %v", id, err)
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
@@ -22,6 +26,7 @@ const (
|
||||
credentialStoreVaultClientCertificateKeyKey = "client_certificate_key"
|
||||
credentialStoreVaultClientCertificateKeyHmacKey = "client_certificate_key_hmac"
|
||||
credentialStoreType = "vault"
|
||||
credentialStoreVaultWorkerFilterKey = "worker_filter"
|
||||
)
|
||||
|
||||
var storeVaultAttrs = []string{
|
||||
@@ -119,40 +124,61 @@ func resourceCredentialStoreVault() *schema.Resource {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
credentialStoreVaultWorkerFilterKey: {
|
||||
Description: "HCP Only. A filter used to control which PKI workers can handle Vault requests. This allows the use of private Vault instances with Boundary.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromVaultCredentialStoreResponseMap(d *schema.ResourceData, raw map[string]interface{}, fromRead bool) error {
|
||||
func setFromVaultCredentialStoreResponseMap(d *schema.ResourceData, raw map[string]interface{}, fromRead bool) diag.Diagnostics {
|
||||
if err := d.Set(NameKey, raw[NameKey]); err != nil {
|
||||
return err
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
if err := d.Set(DescriptionKey, raw[DescriptionKey]); err != nil {
|
||||
return err
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
if err := d.Set(ScopeIdKey, raw[ScopeIdKey]); err != nil {
|
||||
return err
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
if err := d.Set(credentialStoreVaultWorkerFilterKey, raw[credentialStoreVaultWorkerFilterKey]); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
var diags diag.Diagnostics
|
||||
csId := raw["id"]
|
||||
if attrsVal, ok := raw["attributes"]; ok {
|
||||
attrs := attrsVal.(map[string]interface{})
|
||||
for _, v := range storeVaultAttrs {
|
||||
if err := d.Set(v, attrs[v]); err != nil {
|
||||
return err
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
stateTokenHmac := d.Get(credentialStoreVaultTokenHmacKey)
|
||||
boundaryTokenHmac := attrs[credentialStoreVaultTokenHmacKey].(string)
|
||||
if stateTokenHmac.(string) != boundaryTokenHmac && fromRead {
|
||||
// TokenHmac has changed in Boundary, therefore the token has changed.
|
||||
// Update token value to force tf to attempt update.
|
||||
if err := d.Set(credentialStoreVaultTokenKey, "(changed in Boundary)"); err != nil {
|
||||
return err
|
||||
boundaryTokenHmac, ok := attrs[credentialStoreVaultTokenHmacKey]
|
||||
tokenStatus := attrs["token_status"]
|
||||
switch {
|
||||
case ok:
|
||||
boundaryTokenHmacStr := boundaryTokenHmac.(string)
|
||||
stateTokenHmac := d.Get(credentialStoreVaultTokenHmacKey)
|
||||
if stateTokenHmac.(string) != boundaryTokenHmacStr && fromRead {
|
||||
// TokenHmac has changed in Boundary, therefore the token has changed.
|
||||
// Update token value to force tf to attempt update.
|
||||
if err := d.Set(credentialStoreVaultTokenKey, "(changed in Boundary)"); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := d.Set(credentialStoreVaultTokenHmacKey, boundaryTokenHmac); err != nil {
|
||||
return err
|
||||
if err := d.Set(credentialStoreVaultTokenHmacKey, boundaryTokenHmacStr); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
case tokenStatus == "expired":
|
||||
diags = append(diags, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: fmt.Sprintf("Vault token has expired for credential store %q, please update.", csId),
|
||||
})
|
||||
}
|
||||
|
||||
stateClientKeyHmac := d.Get(credentialStoreVaultClientCertificateKeyHmacKey)
|
||||
@@ -164,17 +190,17 @@ func setFromVaultCredentialStoreResponseMap(d *schema.ResourceData, raw map[stri
|
||||
// ClientKeyHmac has changed in Boundary, therefore the ClientKey has changed.
|
||||
// Update ClientKey value to force tf to attempt update.
|
||||
if err := d.Set(credentialStoreVaultClientCertificateKeyKey, "(changed in Boundary)"); err != nil {
|
||||
return err
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
if err := d.Set(credentialStoreVaultClientCertificateKeyHmacKey, boundaryClientKeyHmac); err != nil {
|
||||
return err
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
d.SetId(csId.(string))
|
||||
|
||||
return nil
|
||||
return diags
|
||||
}
|
||||
|
||||
func resourceCredentialStoreVaultCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
@@ -211,6 +237,9 @@ func resourceCredentialStoreVaultCreate(ctx context.Context, d *schema.ResourceD
|
||||
if v, ok := d.GetOk(credentialStoreVaultTokenKey); ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreToken(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialStoreVaultWorkerFilterKey); ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreWorkerFilter(v.(string)))
|
||||
}
|
||||
|
||||
var scope string
|
||||
gotScope, ok := d.GetOk(ScopeIdKey)
|
||||
@@ -229,8 +258,8 @@ func resourceCredentialStoreVaultCreate(ctx context.Context, d *schema.ResourceD
|
||||
return diag.Errorf("nil credential store after create")
|
||||
}
|
||||
|
||||
if err := setFromVaultCredentialStoreResponseMap(d, cr.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential store from response map: %v", err)
|
||||
if diagnostics := setFromVaultCredentialStoreResponseMap(d, cr.GetResponse().Map, false); diagnostics != nil {
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -252,8 +281,8 @@ func resourceCredentialStoreVaultRead(ctx context.Context, d *schema.ResourceDat
|
||||
return diag.Errorf("credential store nil after read")
|
||||
}
|
||||
|
||||
if err := setFromVaultCredentialStoreResponseMap(d, cr.GetResponse().Map, true); err != nil {
|
||||
return diag.Errorf("error generating credential store from response map: %v", err)
|
||||
if diagnostics := setFromVaultCredentialStoreResponseMap(d, cr.GetResponse().Map, true); diagnostics != nil {
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -342,6 +371,14 @@ func resourceCredentialStoreVaultUpdate(ctx context.Context, d *schema.ResourceD
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialStoreVaultWorkerFilterKey) {
|
||||
opts = append(opts, credentialstores.DefaultVaultCredentialStoreWorkerFilter())
|
||||
v, ok := d.GetOk(credentialStoreVaultWorkerFilterKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreWorkerFilter(v.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, credentialstores.WithAutomaticVersioning(true))
|
||||
crUpdate, err := client.Update(ctx, d.Id(), 0, opts...)
|
||||
@@ -352,8 +389,8 @@ func resourceCredentialStoreVaultUpdate(ctx context.Context, d *schema.ResourceD
|
||||
return diag.Errorf("credential store nil after update")
|
||||
}
|
||||
|
||||
if err = setFromVaultCredentialStoreResponseMap(d, crUpdate.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential store from response map: %v", err)
|
||||
if diagnostics := setFromVaultCredentialStoreResponseMap(d, crUpdate.GetResponse().Map, false); diagnostics != nil {
|
||||
return diagnostics
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentials"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
credentialUsernamePasswordUsernameKey = "username"
|
||||
credentialUsernamePasswordPasswordKey = "password"
|
||||
credentialUsernamePasswordPasswordHmacKey = "password_hmac"
|
||||
credentialUsernamePasswordCredentialType = "username_password"
|
||||
)
|
||||
|
||||
func resourceCredentialUsernamePassword() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The username/password credential resource allows you to configure a credential using a username and password pair.",
|
||||
|
||||
CreateContext: resourceCredentialUsernamePasswordCreate,
|
||||
ReadContext: resourceCredentialUsernamePasswordRead,
|
||||
UpdateContext: resourceCredentialUsernamePasswordUpdate,
|
||||
DeleteContext: resourceCredentialUsernamePasswordDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
IDKey: {
|
||||
Description: "The ID of this username/password credential.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
NameKey: {
|
||||
Description: "The name of this username/password credential. Defaults to the resource name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
DescriptionKey: {
|
||||
Description: "The description of this username/password credential.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialStoreIdKey: {
|
||||
Description: "The credential store in which to save this username/password credential.",
|
||||
Type: schema.TypeString,
|
||||
ForceNew: true,
|
||||
Required: true,
|
||||
},
|
||||
credentialUsernamePasswordUsernameKey: {
|
||||
Description: "The username of this username/password credential.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
credentialUsernamePasswordPasswordKey: {
|
||||
Description: "The password of this username/password credential.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
credentialUsernamePasswordPasswordHmacKey: {
|
||||
Description: "The password hmac.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromCredentialUsernamePasswordResponseMap(d *schema.ResourceData, raw map[string]interface{}, fromRead bool) error {
|
||||
if err := d.Set(NameKey, raw[NameKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(DescriptionKey, raw[DescriptionKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(credentialStoreIdKey, raw[credentialStoreIdKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if attrsVal, ok := raw["attributes"]; ok {
|
||||
attrs := attrsVal.(map[string]interface{})
|
||||
if err := d.Set(credentialUsernamePasswordUsernameKey, attrs[credentialUsernamePasswordUsernameKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
statePasswordHmac := d.Get(credentialUsernamePasswordPasswordHmacKey)
|
||||
boundaryPasswordHmac := attrs[credentialUsernamePasswordPasswordHmacKey].(string)
|
||||
if statePasswordHmac.(string) != boundaryPasswordHmac && fromRead {
|
||||
// PasswordHmac has changed in Boundary, therefore the password has changed.
|
||||
// Update password value to force tf to attempt update.
|
||||
if err := d.Set(credentialUsernamePasswordPasswordKey, "(changed in Boundary)"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := d.Set(credentialUsernamePasswordPasswordHmacKey, boundaryPasswordHmac); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialUsernamePasswordCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
|
||||
var opts []credentials.Option
|
||||
if v, ok := d.GetOk(NameKey); ok {
|
||||
opts = append(opts, credentials.WithName(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(DescriptionKey); ok {
|
||||
opts = append(opts, credentials.WithDescription(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialUsernamePasswordUsernameKey); ok {
|
||||
opts = append(opts, credentials.WithUsernamePasswordCredentialUsername(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialUsernamePasswordPasswordKey); ok {
|
||||
opts = append(opts, credentials.WithUsernamePasswordCredentialPassword(v.(string)))
|
||||
}
|
||||
|
||||
var credentialStoreId string
|
||||
retrievedStoreId, ok := d.GetOk(credentialStoreIdKey)
|
||||
if ok {
|
||||
credentialStoreId = retrievedStoreId.(string)
|
||||
} else {
|
||||
return diag.Errorf("credential store id is unset")
|
||||
}
|
||||
|
||||
client := credentials.NewClient(md.client)
|
||||
cr, err := client.Create(ctx, credentialUsernamePasswordCredentialType, credentialStoreId, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error creating credential: %v", err)
|
||||
}
|
||||
if cr == nil {
|
||||
return diag.Errorf("nil credential after create")
|
||||
}
|
||||
|
||||
if err := setFromCredentialUsernamePasswordResponseMap(d, cr.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential from response map: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialUsernamePasswordRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentials.NewClient(md.client)
|
||||
|
||||
cr, err := client.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
return diag.Errorf("error reading credential: %v", err)
|
||||
}
|
||||
if cr == nil {
|
||||
return diag.Errorf("credential nil after read")
|
||||
}
|
||||
|
||||
if err := setFromCredentialUsernamePasswordResponseMap(d, cr.GetResponse().Map, true); err != nil {
|
||||
return diag.Errorf("error generating credential from response map: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialUsernamePasswordUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentials.NewClient(md.client)
|
||||
|
||||
var opts []credentials.Option
|
||||
if d.HasChange(NameKey) {
|
||||
opts = append(opts, credentials.DefaultName())
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
opts = append(opts, credentials.WithName(nameVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(DescriptionKey) {
|
||||
opts = append(opts, credentials.DefaultDescription())
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
opts = append(opts, credentials.WithDescription(descVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialUsernamePasswordUsernameKey) {
|
||||
usernameVal, ok := d.GetOk(credentialUsernamePasswordUsernameKey)
|
||||
if ok {
|
||||
opts = append(opts, credentials.WithUsernamePasswordCredentialUsername(usernameVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialUsernamePasswordPasswordKey) {
|
||||
passwordVal, ok := d.GetOk(credentialUsernamePasswordPasswordKey)
|
||||
if ok {
|
||||
opts = append(opts, credentials.WithUsernamePasswordCredentialPassword(passwordVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, credentials.WithAutomaticVersioning(true))
|
||||
crUpdate, err := client.Update(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating credential: %v", err)
|
||||
}
|
||||
if crUpdate == nil {
|
||||
return diag.Errorf("credential nil after update")
|
||||
}
|
||||
|
||||
if err = setFromCredentialUsernamePasswordResponseMap(d, crUpdate.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential from response map: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialUsernamePasswordDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentials.NewClient(md.client)
|
||||
|
||||
_, err := client.Delete(ctx, d.Id())
|
||||
if err != nil {
|
||||
return diag.Errorf("error deleting credential: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentials"
|
||||
"github.com/hashicorp/boundary/testing/controller"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
||||
)
|
||||
|
||||
const (
|
||||
usernamePasswordCredResc = "boundary_credential_username_password.example"
|
||||
usernamePasswordCredName = "foo"
|
||||
usernamePasswordCredDesc = "the foo"
|
||||
usernamePasswordCredUsername = "default_username"
|
||||
usernamePasswordCredPassword = "default_password"
|
||||
usernamePasswordCredUpdate = "_random"
|
||||
)
|
||||
|
||||
func usernamePasswordCredResource(name, description, username, password string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "boundary_credential_store_static" "example" {
|
||||
name = "static store name"
|
||||
description = "static store description"
|
||||
scope_id = boundary_scope.proj1.id
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
}
|
||||
|
||||
resource "boundary_credential_username_password" "example" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
credential_store_id = boundary_credential_store_static.example.id
|
||||
username = "%s"
|
||||
password = "%s"
|
||||
}`, name,
|
||||
description,
|
||||
username,
|
||||
password)
|
||||
}
|
||||
|
||||
func TestAccCredentialUsernamePassword(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
res := usernamePasswordCredResource(
|
||||
usernamePasswordCredName,
|
||||
usernamePasswordCredDesc,
|
||||
usernamePasswordCredUsername,
|
||||
usernamePasswordCredPassword,
|
||||
)
|
||||
|
||||
resUpdate := usernamePasswordCredResource(
|
||||
usernamePasswordCredName,
|
||||
usernamePasswordCredDesc,
|
||||
usernamePasswordCredUsername+usernamePasswordCredUpdate,
|
||||
usernamePasswordCredPassword+usernamePasswordCredUpdate,
|
||||
)
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckCredentialUsernamePasswordResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, res),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(usernamePasswordCredResc, NameKey, usernamePasswordCredName),
|
||||
resource.TestCheckResourceAttr(usernamePasswordCredResc, DescriptionKey, usernamePasswordCredDesc),
|
||||
resource.TestCheckResourceAttr(usernamePasswordCredResc, credentialUsernamePasswordUsernameKey, usernamePasswordCredUsername),
|
||||
resource.TestCheckResourceAttr(usernamePasswordCredResc, credentialUsernamePasswordPasswordKey, usernamePasswordCredPassword),
|
||||
|
||||
testAccCheckCredentialStoreUsernamePasswordHmac(provider),
|
||||
testAccCheckCredentialUsernamePasswordResourceExists(provider, usernamePasswordCredResc),
|
||||
),
|
||||
},
|
||||
importStep(usernamePasswordCredResc, credentialUsernamePasswordPasswordKey),
|
||||
{
|
||||
// update
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(usernamePasswordCredResc, NameKey, usernamePasswordCredName),
|
||||
resource.TestCheckResourceAttr(usernamePasswordCredResc, DescriptionKey, usernamePasswordCredDesc),
|
||||
resource.TestCheckResourceAttr(usernamePasswordCredResc, credentialUsernamePasswordUsernameKey, usernamePasswordCredUsername+usernamePasswordCredUpdate),
|
||||
resource.TestCheckResourceAttr(usernamePasswordCredResc, credentialUsernamePasswordPasswordKey, usernamePasswordCredPassword+usernamePasswordCredUpdate),
|
||||
|
||||
testAccCheckCredentialStoreUsernamePasswordHmac(provider),
|
||||
testAccCheckCredentialUsernamePasswordResourceExists(provider, usernamePasswordCredResc),
|
||||
),
|
||||
},
|
||||
importStep(usernamePasswordCredResc, credentialUsernamePasswordPasswordKey),
|
||||
{
|
||||
// Run a plan only update and verify no changes
|
||||
PlanOnly: true,
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
},
|
||||
importStep(usernamePasswordCredResc, credentialUsernamePasswordPasswordKey),
|
||||
{
|
||||
// update again but apply a preConfig to externally update resource
|
||||
PreConfig: func() { usernamePasswordCredExternalUpdate(t, provider) },
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
},
|
||||
importStep(usernamePasswordCredResc, credentialUsernamePasswordPasswordKey),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func usernamePasswordCredExternalUpdate(t *testing.T, testProvider *schema.Provider) {
|
||||
if storeId == "" {
|
||||
t.Fatal("storeId must be set before testing an external update")
|
||||
}
|
||||
|
||||
md := testProvider.Meta().(*metaData)
|
||||
c := credentials.NewClient(md.client)
|
||||
cr, err := c.Read(context.Background(), storeId)
|
||||
if err != nil {
|
||||
t.Fatal(fmt.Errorf("got an error reading %q: %w", storeId, err))
|
||||
}
|
||||
|
||||
// update credential options
|
||||
var opts []credentials.Option
|
||||
opts = append(opts, credentials.WithDescription("this is an updated description, my guy"))
|
||||
|
||||
_, err = c.Update(context.Background(), cr.Item.Id, cr.Item.Version, opts...)
|
||||
if err != nil {
|
||||
t.Fatal(fmt.Errorf("got an error updating %q: %w", cr.Item.Id, err))
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckCredentialUsernamePasswordResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("not found: %s", name)
|
||||
}
|
||||
|
||||
id := rs.Primary.ID
|
||||
if id == "" {
|
||||
return fmt.Errorf("no ID is set")
|
||||
}
|
||||
storeId = id
|
||||
|
||||
md := testProvider.Meta().(*metaData)
|
||||
c := credentials.NewClient(md.client)
|
||||
if _, err := c.Read(context.Background(), id); err != nil {
|
||||
return fmt.Errorf("got an error reading %q: %w", id, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckCredentialUsernamePasswordResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if testProvider.Meta() == nil {
|
||||
t.Fatal("got nil provider metadata")
|
||||
}
|
||||
md := testProvider.Meta().(*metaData)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
switch rs.Type {
|
||||
case "boundary_credential_username_password":
|
||||
id := rs.Primary.ID
|
||||
|
||||
c := credentials.NewClient(md.client)
|
||||
_, err := c.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed username password credential %q: %v", id, err)
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckCredentialStoreUsernamePasswordHmac(testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[usernamePasswordCredResc]
|
||||
if !ok {
|
||||
return fmt.Errorf("not found: %s", usernamePasswordCredResc)
|
||||
}
|
||||
|
||||
computed := rs.Primary.Attributes["password_hmac"]
|
||||
if len(computed) != 43 {
|
||||
return fmt.Errorf("Computed password hmac not the expected length of 43 characters. hmac: %q", computed)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
@@ -75,7 +78,7 @@ func setFromGroupResponseMap(d *schema.ResourceData, raw map[string]interface{})
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
func resourceGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) (errs diag.Diagnostics) {
|
||||
md := meta.(*metaData)
|
||||
|
||||
var scopeId string
|
||||
@@ -108,7 +111,12 @@ func resourceGroupCreate(ctx context.Context, d *schema.ResourceData, meta inter
|
||||
if gcr == nil {
|
||||
return diag.Errorf("group nil after create")
|
||||
}
|
||||
raw := gcr.GetResponse().Map
|
||||
apiResponse := gcr.GetResponse().Map
|
||||
defer func() {
|
||||
if err := setFromGroupResponseMap(d, apiResponse); err != nil {
|
||||
errs = append(errs, diag.FromErr(err)...)
|
||||
}
|
||||
}()
|
||||
|
||||
if val, ok := d.GetOk(groupMemberIdsKey); ok {
|
||||
list := val.(*schema.Set).List()
|
||||
@@ -118,16 +126,12 @@ func resourceGroupCreate(ctx context.Context, d *schema.ResourceData, meta inter
|
||||
}
|
||||
gcsmr, err := grps.SetMembers(ctx, gcr.Item.Id, gcr.Item.Version, memberIds)
|
||||
if err != nil {
|
||||
return diag.Errorf("error setting principals on role: %v", err)
|
||||
return diag.Errorf("error setting members on group: %v", err)
|
||||
}
|
||||
if gcsmr == nil {
|
||||
return diag.Errorf("group nil after setting members")
|
||||
}
|
||||
raw = gcsmr.GetResponse().Map
|
||||
}
|
||||
|
||||
if err := setFromGroupResponseMap(d, raw); err != nil {
|
||||
return diag.FromErr(err)
|
||||
apiResponse = gcsmr.GetResponse().Map
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
@@ -16,8 +19,8 @@ const (
|
||||
|
||||
func resourceHostCatalog() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
DeprecationMessage: "Deprecated: use `resource_host_catalog_static` instead.",
|
||||
Description: "Deprecated: use `resource_host_catalog_static` instead.",
|
||||
DeprecationMessage: "Deprecated: use `boundary_host_catalog_static` instead.",
|
||||
Description: "Deprecated: use `boundary_host_catalog_static` instead.",
|
||||
|
||||
CreateContext: resourceHostCatalogStaticCreate(true),
|
||||
ReadContext: resourceHostCatalogStaticRead(true),
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
|
||||
Loaded 100 of 166 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user