Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a611a2bcbd | ||
|
|
d3cfbee0be | ||
|
|
cf1538c56a | ||
|
|
6cefb8e376 | ||
|
|
77eda49baa | ||
|
|
3f8368e7a6 | ||
|
|
a24f9e15e4 | ||
|
|
99914e6317 | ||
|
|
336f68ba68 | ||
|
|
e1f8352fc4 | ||
|
|
4f0509c55d | ||
|
|
7c73927d17 | ||
|
|
3c5bbd53c6 | ||
|
|
2d2f825035 | ||
|
|
30b38ea89d | ||
|
|
1c1e593eab | ||
|
|
8bfac9f6fa | ||
|
|
3d8121234f | ||
|
|
549a843382 |
No files matched your search
+41
-6
@@ -1,9 +1,44 @@
|
||||
## 1.0.4 (August 19, 2021)
|
||||
|
||||
### New and Improved
|
||||
|
||||
* Adds managed groups resource
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/118)).
|
||||
|
||||
## 1.0.3 (June 30, 2021)
|
||||
|
||||
### New and Improved
|
||||
|
||||
* Adds credential library resource for Vault
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/114)).
|
||||
* Adds credential store resource for Vault
|
||||
([PR 1](https://github.com/hashicorp/terraform-provider-boundary/pull/114)),
|
||||
([PR 2](https://github.com/hashicorp/terraform-provider-boundary/pull/125)).
|
||||
* Adds claim scopes attribute to OIDC auth method
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/112)).
|
||||
* Adds account claim maps attribute to OIDC auth method
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/111)).
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Make OIDC account attribute for subject ForceNew
|
||||
([Issue](https://github.com/hashicorp/terraform-provider-boundary/issues/119)),
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/122)).
|
||||
* Update static type attribute for host catalog resource
|
||||
([Issue](https://github.com/hashicorp/terraform-provider-boundary/issues/115)),
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/121)).
|
||||
|
||||
## 1.0.2 (May 06, 2021)
|
||||
|
||||
### New and Improved
|
||||
|
||||
* Adds OIDC account resource
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/105))
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/105)).
|
||||
* Adds OIDC auth method resource
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/105))
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/105)).
|
||||
|
||||
### Deprecations/Changes
|
||||
|
||||
* Deprecates fields on `resource_auth_method` that will be replaced in the future with generic `attributes` attribute.
|
||||
|
||||
## 1.0.1 (February 02, 2021)
|
||||
@@ -11,7 +46,7 @@
|
||||
### New and Improved
|
||||
|
||||
* Adds worker filter to target resource
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/76))
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/76)).
|
||||
|
||||
## 1.0.0 (January 20, 2021)
|
||||
|
||||
@@ -20,14 +55,14 @@ We are bumping the version of the Boundary Terraform provider to v1.0.0 and will
|
||||
### Bug Fixes
|
||||
|
||||
* During `terraform apply`, do not update existing user account passwords when the password field is updated in the tf file.
|
||||
([Issue](https://github.com/hashicorp/terraform-provider-boundary/issues/71))
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/70))
|
||||
([Issue](https://github.com/hashicorp/terraform-provider-boundary/issues/71)),
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/70)).
|
||||
|
||||
## 0.1.4 (January 14, 2021)
|
||||
|
||||
### New and Improved
|
||||
|
||||
Update provider to handle new domain errors ([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/63))
|
||||
Update provider to handle new domain errors ([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/63)).
|
||||
|
||||
## 0.1.0 (October 14, 2020)
|
||||
|
||||
|
||||
@@ -41,7 +41,10 @@ all:
|
||||
GOOS=windows go build -o $(BUILD_ALL_PATH)/terraform-provider-boundary_windows-amd64 main.go
|
||||
GOOS=linux go build -o $(BUILD_ALL_PATH)/terraform-provider-boundary_linux-amd64 main.go
|
||||
|
||||
docs:
|
||||
go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs
|
||||
|
||||
rm-id-flag-from-docs:
|
||||
find docs/ -name "*.md" -type f | xargs sed -i -e '/- \*\*id\*\*/d'
|
||||
|
||||
.PHONY: testacc tools
|
||||
.PHONY: testacc tools docs
|
||||
@@ -129,7 +129,7 @@ resource "boundary_user" "users" {
|
||||
scope_id = boundary_scope.corp.id
|
||||
}
|
||||
|
||||
// organiation level group for readonly users
|
||||
// organization level group for readonly users
|
||||
resource "boundary_group" "readonly" {
|
||||
name = "readonly"
|
||||
description = "Organization group for readonly users"
|
||||
|
||||
@@ -21,9 +21,11 @@ The OIDC auth method resource allows you to configure a Boundary auth_method_oid
|
||||
|
||||
### Optional
|
||||
|
||||
- **account_claim_maps** (List of String) Account claim maps for the to_claim of sub.
|
||||
- **allowed_audiences** (List of String) Audiences for which the provider responses will be allowed
|
||||
- **api_url_prefix** (String) The API prefix to use when generating callback URLs for the provider. Should be set to an address at which the provider can reach back to the controller.
|
||||
- **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_hmac** (String) The HMAC of the client secret returned by the Boundary controller, which is used for comparison after initial setting of the value.
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_credential_library_vault 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 (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" "foo" {
|
||||
name = "foo"
|
||||
description = "My first Vault credential library!"
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/foo" # change to Vault backend path
|
||||
http_method = "GET"
|
||||
}
|
||||
|
||||
resource "boundary_credential_library_vault" "bar" {
|
||||
name = "bar"
|
||||
description = "My second Vault credential library!"
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/bar" # change to Vault backend path
|
||||
http_method = "POST"
|
||||
request_body = <<EOT
|
||||
{
|
||||
"key": "Value",
|
||||
}
|
||||
EOT
|
||||
}
|
||||
```
|
||||
|
||||
<!-- 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.
|
||||
|
||||
### Optional
|
||||
|
||||
- **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`.
|
||||
- **name** (String) The Vault credential library name. Defaults to the resource name.
|
||||
|
||||
### 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.foo <my-id>
|
||||
```
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_credential_store_vault Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The credential store for Vault resource allows you to configure a Boundary credential store for Vault.
|
||||
---
|
||||
|
||||
# boundary_credential_store_vault (Resource)
|
||||
|
||||
The credential store for Vault resource allows you to configure a Boundary credential store 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" "example" {
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- **address** (String) The address to Vault server. This should be a complete URL such as 'https://127.0.0.1:8200'
|
||||
- **scope_id** (String) The scope for this credential store.
|
||||
- **token** (String, Sensitive) A token used for accessing Vault.
|
||||
|
||||
### Optional
|
||||
|
||||
- **ca_cert** (String) A PEM-encoded CA certificate to verify the Vault server's TLS certificate.
|
||||
- **client_certificate** (String) A PEM-encoded client certificate to use for TLS authentication to the Vault server.
|
||||
- **client_certificate_key** (String, Sensitive) A PEM-encoded private key matching the client certificate from 'client_certificate'.
|
||||
- **description** (String) The Vault credential store description.
|
||||
- **name** (String) The Vault credential store name. Defaults to the resource name.
|
||||
- **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.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **client_certificate_key_hmac** (String) The Vault client certificate key hmac.
|
||||
- **id** (String) The ID of the Vault credential store.
|
||||
- **token_hmac** (String) The Vault token hmac.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_credential_store_vault.foo <my-id>
|
||||
```
|
||||
@@ -42,7 +42,7 @@ resource "boundary_host_catalog" "example" {
|
||||
### Required
|
||||
|
||||
- **scope_id** (String) The scope ID in which the resource is created.
|
||||
- **type** (String) The host catalog type. Only `Static` (yes, title case) is supported.
|
||||
- **type** (String) The host catalog type. Only `static` is supported.
|
||||
|
||||
### Optional
|
||||
|
||||
|
||||
@@ -33,6 +33,22 @@ resource "boundary_scope" "project" {
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_credential_store_vault" "foo" {
|
||||
name = "vault_store"
|
||||
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" "foo" {
|
||||
name = "foo"
|
||||
description = "My first Vault credential library!"
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/foo" # change to Vault backend path
|
||||
http_method = "GET"
|
||||
}
|
||||
|
||||
resource "boundary_host_catalog" "foo" {
|
||||
name = "test"
|
||||
description = "test catalog"
|
||||
@@ -72,6 +88,9 @@ resource "boundary_target" "foo" {
|
||||
host_set_ids = [
|
||||
boundary_host_set.foo.id
|
||||
]
|
||||
application_credential_library_ids = [
|
||||
boundary_credential_library_vault.foo.id
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -85,6 +104,7 @@ resource "boundary_target" "foo" {
|
||||
|
||||
### Optional
|
||||
|
||||
- **application_credential_library_ids** (Set of String) A list of application credential library ID's.
|
||||
- **default_port** (Number) The default port for this target.
|
||||
- **description** (String) The target description.
|
||||
- **host_set_ids** (Set of String) A list of host set ID's.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_credential_library_vault.foo <my-id>
|
||||
@@ -0,0 +1,43 @@
|
||||
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" "foo" {
|
||||
name = "foo"
|
||||
description = "My first Vault credential library!"
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/foo" # change to Vault backend path
|
||||
http_method = "GET"
|
||||
}
|
||||
|
||||
resource "boundary_credential_library_vault" "bar" {
|
||||
name = "bar"
|
||||
description = "My second Vault credential library!"
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/bar" # change to Vault backend path
|
||||
http_method = "POST"
|
||||
request_body = <<EOT
|
||||
{
|
||||
"key": "Value",
|
||||
}
|
||||
EOT
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_credential_store_vault.foo <my-id>
|
||||
@@ -0,0 +1,22 @@
|
||||
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" "example" {
|
||||
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
|
||||
}
|
||||
@@ -18,6 +18,22 @@ resource "boundary_scope" "project" {
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_credential_store_vault" "foo" {
|
||||
name = "vault_store"
|
||||
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" "foo" {
|
||||
name = "foo"
|
||||
description = "My first Vault credential library!"
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/foo" # change to Vault backend path
|
||||
http_method = "GET"
|
||||
}
|
||||
|
||||
resource "boundary_host_catalog" "foo" {
|
||||
name = "test"
|
||||
description = "test catalog"
|
||||
@@ -57,4 +73,7 @@ resource "boundary_target" "foo" {
|
||||
host_set_ids = [
|
||||
boundary_host_set.foo.id
|
||||
]
|
||||
application_credential_library_ids = [
|
||||
boundary_credential_library_vault.foo.id
|
||||
]
|
||||
}
|
||||
@@ -9,33 +9,27 @@ require (
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.3 // indirect
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/validation v0.3.0 // indirect
|
||||
github.com/DataDog/datadog-go v4.0.0+incompatible // indirect
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.545 // indirect
|
||||
github.com/aws/aws-sdk-go v1.35.4 // indirect
|
||||
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
|
||||
github.com/circonus-labs/circonusllhist v0.1.4 // indirect
|
||||
github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe // indirect
|
||||
github.com/golang/snappy v0.0.2 // indirect
|
||||
github.com/hashicorp/boundary v0.2.0
|
||||
github.com/hashicorp/boundary/api v0.0.10
|
||||
github.com/hashicorp/boundary/sdk v0.0.4
|
||||
github.com/hashicorp/cap v0.0.0-20210408110729-0dd65efe5473
|
||||
github.com/hashicorp/boundary v0.5.1
|
||||
github.com/hashicorp/boundary/api v0.0.15
|
||||
github.com/hashicorp/boundary/sdk v0.0.7
|
||||
github.com/hashicorp/cap v0.1.1
|
||||
github.com/hashicorp/go-immutable-radix v1.3.0 // indirect
|
||||
github.com/hashicorp/go-kms-wrapping v0.6.1
|
||||
github.com/hashicorp/go-kms-wrapping v0.6.5
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.6.0 // indirect
|
||||
github.com/hashicorp/terraform-plugin-docs v0.4.0
|
||||
github.com/hashicorp/terraform-plugin-sdk/v2 v2.6.1
|
||||
github.com/hashicorp/terraform-plugin-sdk/v2 v2.7.0
|
||||
github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/oklog/run v1.1.0 // indirect
|
||||
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
|
||||
github.com/prometheus/common v0.14.0 // indirect
|
||||
github.com/prometheus/procfs v0.2.0 // indirect
|
||||
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c // indirect
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
@@ -1,18 +1,18 @@
|
||||
package provider
|
||||
|
||||
const (
|
||||
// Key used for common SDK ID resource attribute
|
||||
// IDKey is used for common SDK ID resource attribute
|
||||
IDKey = "id"
|
||||
// Key used for common "name" resource attribute
|
||||
// NameKey is used for common "name" resource attribute
|
||||
NameKey = "name"
|
||||
// Key used for common "description" resource attribute
|
||||
// DescriptionKey is used for common "description" resource attribute
|
||||
DescriptionKey = "description"
|
||||
// Key used for common "scope_id" resource attribute
|
||||
// ScopeIdKey is used for common "scope_id" resource attribute
|
||||
ScopeIdKey = "scope_id"
|
||||
// Key used for common "type" resource attribute
|
||||
// TypeKey is used for common "type" resource attribute
|
||||
TypeKey = "type"
|
||||
// Key used for common "host_catalog_id" resource attribute
|
||||
// HostCatalogIdKey is used for common "host_catalog_id" resource attribute
|
||||
HostCatalogIdKey = "host_catalog_id"
|
||||
// Key used for common "auth_method_id" resource attribute
|
||||
// AuthMethodIdKey is used for common "auth_method_id" resource attribute
|
||||
AuthMethodIdKey = "auth_method_id"
|
||||
)
|
||||
@@ -54,20 +54,23 @@ func New() *schema.Provider {
|
||||
},
|
||||
},
|
||||
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_group": resourceGroup(),
|
||||
"boundary_host": resourceHost(),
|
||||
"boundary_host_catalog": resourceHostCatalog(),
|
||||
"boundary_host_set": resourceHostset(),
|
||||
"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_store_vault": resourceCredentialStoreVault(),
|
||||
"boundary_managed_group": resourceManagedGroup(),
|
||||
"boundary_group": resourceGroup(),
|
||||
"boundary_host": resourceHost(),
|
||||
"boundary_host_catalog": resourceHostCatalog(),
|
||||
"boundary_host_set": resourceHostset(),
|
||||
"boundary_role": resourceRole(),
|
||||
"boundary_scope": resourceScope(),
|
||||
"boundary_target": resourceTarget(),
|
||||
"boundary_user": resourceUser(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ var (
|
||||
tcPassword = "passpass"
|
||||
tcPAUM = "ampw_0000000000"
|
||||
tcConfig = []controller.Option{
|
||||
controller.WithDefaultAuthMethodId(tcPAUM),
|
||||
controller.WithDefaultPasswordAuthMethodId(tcPAUM),
|
||||
controller.WithDefaultLoginName(tcLoginName),
|
||||
controller.WithDefaultPassword(tcPassword),
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ func resourceAccountOidc() *schema.Resource {
|
||||
Description: "The OIDC subject.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ const (
|
||||
authmethodOidcDisableDiscoveredConfigValidationKey = "disable_discovered_config_validation"
|
||||
authmethodOidcSigningAlgorithmsKey = "signing_algorithms"
|
||||
authmethodOidcIsPrimaryAuthMethodForScope = "is_primary_for_scope"
|
||||
authmethodOidcAccountClaimMapsKey = "account_claim_maps"
|
||||
authmethodOidcClaimsScopesKey = "claims_scopes"
|
||||
|
||||
// computed-only parameters
|
||||
authmethodOidcCallbackUrlKey = "callback_url"
|
||||
@@ -121,6 +123,24 @@ func resourceAuthMethodOidc() *schema.Resource {
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
authmethodOidcAccountClaimMapsKey: {
|
||||
Description: "Account claim maps for the to_claim of sub.",
|
||||
Type: schema.TypeList,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
Optional: true,
|
||||
// per comment in https://github.com/hashicorp/boundary/pull/1186
|
||||
ForceNew: true,
|
||||
},
|
||||
authmethodOidcClaimsScopesKey: {
|
||||
Description: "Claims scopes.",
|
||||
Type: schema.TypeList,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
|
||||
// OIDC specific immutable and computed parameters
|
||||
authmethodOidcClientSecretHmacKey: {
|
||||
@@ -205,6 +225,14 @@ func setFromOidcAuthMethodResponseMap(d *schema.ResourceData, raw map[string]int
|
||||
if p, ok := attrs[authmethodOidcDisableDiscoveredConfigValidationKey]; ok {
|
||||
d.Set(authmethodOidcDisableDiscoveredConfigValidationKey, p.(bool))
|
||||
}
|
||||
|
||||
if p, ok := attrs[authmethodOidcAccountClaimMapsKey]; ok {
|
||||
d.Set(authmethodOidcAccountClaimMapsKey, p.([]interface{}))
|
||||
}
|
||||
|
||||
if p, ok := attrs[authmethodOidcClaimsScopesKey]; ok {
|
||||
d.Set(authmethodOidcClaimsScopesKey, p.([]interface{}))
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
@@ -267,6 +295,22 @@ func resourceAuthMethodOidcCreate(ctx context.Context, d *schema.ResourceData, m
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodSigningAlgorithms(algoList))
|
||||
}
|
||||
|
||||
if claims, ok := d.GetOk(authmethodOidcAccountClaimMapsKey); ok {
|
||||
cList := []string{}
|
||||
for _, c := range claims.([]interface{}) {
|
||||
cList = append(cList, c.(string))
|
||||
}
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodAccountClaimMaps(cList))
|
||||
}
|
||||
|
||||
if claimsScopes, ok := d.GetOk(authmethodOidcClaimsScopesKey); ok {
|
||||
cList := []string{}
|
||||
for _, c := range claimsScopes.([]interface{}) {
|
||||
cList = append(cList, c.(string))
|
||||
}
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodClaimsScopes(cList))
|
||||
}
|
||||
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
nameStr := nameVal.(string)
|
||||
@@ -469,6 +513,15 @@ func resourceAuthMethodOidcUpdate(ctx context.Context, d *schema.ResourceData, m
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodDisableDiscoveredConfigValidation(val.(bool)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(authmethodOidcClaimsScopesKey) {
|
||||
if val, ok := d.GetOk(authmethodOidcClaimsScopesKey); ok {
|
||||
claimsScopes := []string{}
|
||||
for _, c := range val.([]interface{}) {
|
||||
claimsScopes = append(claimsScopes, c.(string))
|
||||
}
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodClaimsScopes(claimsScopes))
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, authmethods.WithAutomaticVersioning(true))
|
||||
|
||||
@@ -64,6 +64,8 @@ EOT
|
||||
]
|
||||
allowed_audiences = ["foo_aud"]
|
||||
signing_algorithms = ["ES256"]
|
||||
account_claim_maps = ["oid=sub"]
|
||||
claims_scopes = ["profile"]
|
||||
}`
|
||||
|
||||
fooAuthMethodOidcUpdate = `
|
||||
@@ -85,7 +87,9 @@ resource "boundary_auth_method_oidc" "foo" {
|
||||
EOT
|
||||
]
|
||||
allowed_audiences = ["foo_aud_update"]
|
||||
signing_algorithms = ["RS256"]
|
||||
signing_algorithms = ["ES256"]
|
||||
account_claim_maps = ["oid=sub"]
|
||||
claims_scopes = ["profile"]
|
||||
|
||||
// we need to disable this validatin, since the updated issuer isn't discoverable
|
||||
disable_discovered_config_validation = true
|
||||
@@ -118,6 +122,8 @@ func TestAccAuthMethodOidc(t *testing.T) {
|
||||
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"),
|
||||
testAccIsPrimaryForScope(provider, "boundary_auth_method_oidc.foo", false),
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
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 (
|
||||
credentialStoreIdKey = "credential_store_id"
|
||||
credentialLibraryVaultHttpMethodKey = "http_method"
|
||||
credentialLibraryVaultHttpRequestBodyKey = "http_request_body"
|
||||
credentialLibraryVaultPathKey = "path"
|
||||
)
|
||||
|
||||
var libraryVaultAttrs = []string{
|
||||
credentialLibraryVaultHttpMethodKey,
|
||||
credentialLibraryVaultHttpRequestBodyKey,
|
||||
credentialLibraryVaultPathKey,
|
||||
}
|
||||
|
||||
func resourceCredentialLibraryVault() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The credential library for Vault resource allows you to configure a Boundary credential library for Vault.",
|
||||
|
||||
CreateContext: resourceCredentialLibraryCreateVault,
|
||||
ReadContext: resourceCredentialLibraryReadVault,
|
||||
UpdateContext: resourceCredentialLibraryUpdateVault,
|
||||
DeleteContext: resourceCredentialLibraryDeleteVault,
|
||||
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,
|
||||
},
|
||||
credentialLibraryVaultHttpMethodKey: {
|
||||
Description: "The HTTP method the library uses when requesting credentials from Vault. Defaults to 'GET'",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialLibraryVaultHttpRequestBodyKey: {
|
||||
Description: "The body of the HTTP request the library sends to Vault when requesting credentials. Only valid if `http_method` is set to `POST`.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialLibraryVaultPathKey: {
|
||||
Description: "The path in Vault to request credentials from.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromVaultCredentialLibraryResponseMap(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 libraryVaultAttrs {
|
||||
if err := d.Set(v, attrs[v]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialLibraryCreateVault(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(credentialLibraryVaultHttpMethodKey); ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryHttpMethod(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialLibraryVaultHttpRequestBodyKey); ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryHttpRequestBody(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialLibraryVaultPathKey); ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryPath(v.(string)))
|
||||
}
|
||||
|
||||
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, 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 := setFromVaultCredentialLibraryResponseMap(d, cr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialLibraryReadVault(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 := setFromVaultCredentialLibraryResponseMap(d, cr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialLibraryUpdateVault(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(credentialLibraryVaultHttpMethodKey) {
|
||||
opts = append(opts, credentiallibraries.DefaultVaultCredentialLibraryHttpMethod())
|
||||
v, ok := d.GetOk(credentialLibraryVaultHttpMethodKey)
|
||||
if ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryHttpMethod(v.(string)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(credentialLibraryVaultHttpRequestBodyKey) {
|
||||
opts = append(opts, credentiallibraries.DefaultVaultCredentialLibraryHttpRequestBody())
|
||||
v, ok := d.GetOk(credentialLibraryVaultHttpRequestBodyKey)
|
||||
if ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryHttpRequestBody(v.(string)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(credentialLibraryVaultPathKey) {
|
||||
v, ok := d.GetOk(credentialLibraryVaultPathKey)
|
||||
if ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryPath(v.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
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 := setFromVaultCredentialLibraryResponseMap(d, aur.GetResponse().Map); err != nil {
|
||||
return diag.Errorf("error setting credential library from response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialLibraryDeleteVault(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,154 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"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 (
|
||||
vaultCredResc = "boundary_credential_library_vault.example"
|
||||
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"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "%s"
|
||||
http_method = "%s"
|
||||
}`, vaultCredLibName,
|
||||
vaultCredLibDesc,
|
||||
vaultCredLibPath,
|
||||
vaultCredLibMethodGet)
|
||||
|
||||
var vaultCredLibResourceUpdate = fmt.Sprintf(`
|
||||
resource "boundary_credential_library_vault" "example" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "%s"
|
||||
http_method = "%s"
|
||||
http_request_body = "%s"
|
||||
}`, vaultCredLibName+vaultCredLibStringUpdate,
|
||||
vaultCredLibDesc+vaultCredLibStringUpdate,
|
||||
vaultCredLibPath+vaultCredLibStringUpdate,
|
||||
vaultCredLibMethodPost,
|
||||
vaultCredLibRequestBody)
|
||||
|
||||
func TestAccCredentialLibraryVault(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{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckAuthMethodResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, vaultCredLibResource),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(vaultCredResc, NameKey, vaultCredLibName),
|
||||
resource.TestCheckResourceAttr(vaultCredResc, DescriptionKey, vaultCredLibDesc),
|
||||
resource.TestCheckResourceAttr(vaultCredResc, credentialLibraryVaultPathKey, vaultCredLibPath),
|
||||
resource.TestCheckResourceAttr(vaultCredResc, credentialLibraryVaultHttpMethodKey, vaultCredLibMethodGet),
|
||||
resource.TestCheckResourceAttr(vaultCredResc, credentialLibraryVaultHttpRequestBodyKey, ""),
|
||||
|
||||
testAccCheckCredentialLibraryVaultResourceExists(provider, vaultCredResc),
|
||||
),
|
||||
},
|
||||
importStep(vaultCredResc),
|
||||
|
||||
{
|
||||
// update
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, vaultCredLibResourceUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(vaultCredResc, NameKey, vaultCredLibName+vaultCredLibStringUpdate),
|
||||
resource.TestCheckResourceAttr(vaultCredResc, DescriptionKey, vaultCredLibDesc+vaultCredLibStringUpdate),
|
||||
resource.TestCheckResourceAttr(vaultCredResc, credentialLibraryVaultPathKey, vaultCredLibPath+vaultCredLibStringUpdate),
|
||||
resource.TestCheckResourceAttr(vaultCredResc, credentialLibraryVaultHttpMethodKey, vaultCredLibMethodPost),
|
||||
resource.TestCheckResourceAttr(vaultCredResc, credentialLibraryVaultHttpRequestBodyKey, vaultCredLibRequestBody),
|
||||
|
||||
testAccCheckCredentialLibraryVaultResourceExists(provider, vaultCredResc),
|
||||
),
|
||||
},
|
||||
importStep(vaultCredResc),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckCredentialLibraryVaultResourceExists(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 testAccCheckCredentialLibraryVaultResourceDestroy(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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
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 (
|
||||
credentialStoreVaultAddressKey = "address"
|
||||
credentialStoreVaultNamespaceKey = "namespace"
|
||||
credentialStoreVaultCaCertKey = "ca_cert"
|
||||
credentialStoreVaultTlsServerNameKey = "tls_server_name"
|
||||
credentialStoreVaultTlsSkipVerifyKey = "tls_skip_verify"
|
||||
credentialStoreVaultTokenKey = "token"
|
||||
credentialStoreVaultTokenHmacKey = "token_hmac"
|
||||
credentialStoreVaultClientCertificateKey = "client_certificate"
|
||||
credentialStoreVaultClientCertificateKeyKey = "client_certificate_key"
|
||||
credentialStoreVaultClientCertificateKeyHmacKey = "client_certificate_key_hmac"
|
||||
credentialStoreType = "vault"
|
||||
)
|
||||
|
||||
var storeVaultAttrs = []string{
|
||||
credentialStoreVaultAddressKey,
|
||||
credentialStoreVaultNamespaceKey,
|
||||
credentialStoreVaultCaCertKey,
|
||||
credentialStoreVaultTlsServerNameKey,
|
||||
credentialStoreVaultTlsSkipVerifyKey,
|
||||
credentialStoreVaultClientCertificateKey,
|
||||
}
|
||||
|
||||
func resourceCredentialStoreVault() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The credential store for Vault resource allows you to configure a Boundary credential store for Vault.",
|
||||
|
||||
CreateContext: resourceCredentialStoreVaultCreate,
|
||||
ReadContext: resourceCredentialStoreVaultRead,
|
||||
UpdateContext: resourceCredentialStoreVaultUpdate,
|
||||
DeleteContext: resourceCredentialStoreVaultDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
IDKey: {
|
||||
Description: "The ID of the Vault credential store.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
NameKey: {
|
||||
Description: "The Vault credential store name. Defaults to the resource name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
DescriptionKey: {
|
||||
Description: "The Vault credential store description.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
ScopeIdKey: {
|
||||
Description: "The scope for this credential store.",
|
||||
Type: schema.TypeString,
|
||||
ForceNew: true,
|
||||
Required: true,
|
||||
},
|
||||
credentialStoreVaultAddressKey: {
|
||||
Description: "The address to Vault server. This should be a complete URL such as 'https://127.0.0.1:8200'",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
credentialStoreVaultNamespaceKey: {
|
||||
Description: "The namespace within Vault to use.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialStoreVaultCaCertKey: {
|
||||
Description: "A PEM-encoded CA certificate to verify the Vault server's TLS certificate.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialStoreVaultTlsServerNameKey: {
|
||||
Description: "Name to use as the SNI host when connecting to Vault via TLS.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialStoreVaultTlsSkipVerifyKey: {
|
||||
Description: "Whether or not to skip TLS verification.",
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
},
|
||||
credentialStoreVaultTokenKey: {
|
||||
Description: "A token used for accessing Vault.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
credentialStoreVaultTokenHmacKey: {
|
||||
Description: "The Vault token hmac.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
credentialStoreVaultClientCertificateKey: {
|
||||
Description: "A PEM-encoded client certificate to use for TLS authentication to the Vault server.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialStoreVaultClientCertificateKeyKey: {
|
||||
Description: "A PEM-encoded private key matching the client certificate from 'client_certificate'.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
credentialStoreVaultClientCertificateKeyHmacKey: {
|
||||
Description: "The Vault client certificate key hmac.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromVaultCredentialStoreResponseMap(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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
if err := d.Set(credentialStoreVaultTokenHmacKey, boundaryTokenHmac); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stateClientKeyHmac := d.Get(credentialStoreVaultClientCertificateKeyHmacKey)
|
||||
var boundaryClientKeyHmac string
|
||||
if v, ok := attrs[credentialStoreVaultClientCertificateKeyHmacKey]; ok {
|
||||
boundaryClientKeyHmac = v.(string)
|
||||
}
|
||||
if stateClientKeyHmac.(string) != boundaryClientKeyHmac && fromRead {
|
||||
// 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
|
||||
}
|
||||
}
|
||||
if err := d.Set(credentialStoreVaultClientCertificateKeyHmacKey, boundaryClientKeyHmac); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialStoreVaultCreate(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)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialStoreVaultAddressKey); ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreAddress(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialStoreVaultNamespaceKey); ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreNamespace(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialStoreVaultCaCertKey); ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreCaCert(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialStoreVaultTlsServerNameKey); ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreTlsServerName(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialStoreVaultTlsSkipVerifyKey); ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreTlsSkipVerify(v.(bool)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialStoreVaultClientCertificateKey); ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreClientCertificate(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialStoreVaultClientCertificateKeyKey); ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreClientCertificateKey(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialStoreVaultTokenKey); ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreToken(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, credentialStoreType, 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 := setFromVaultCredentialStoreResponseMap(d, cr.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential store from response map: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialStoreVaultRead(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 := setFromVaultCredentialStoreResponseMap(d, cr.GetResponse().Map, true); err != nil {
|
||||
return diag.Errorf("error generating credential store from response map: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialStoreVaultUpdate(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 d.HasChange(credentialStoreVaultAddressKey) {
|
||||
v, ok := d.GetOk(credentialStoreVaultAddressKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreAddress(v.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialStoreVaultNamespaceKey) {
|
||||
opts = append(opts, credentialstores.DefaultVaultCredentialStoreNamespace())
|
||||
v, ok := d.GetOk(credentialStoreVaultNamespaceKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreNamespace(v.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialStoreVaultCaCertKey) {
|
||||
opts = append(opts, credentialstores.DefaultVaultCredentialStoreCaCert())
|
||||
v, ok := d.GetOk(credentialStoreVaultCaCertKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreCaCert(v.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialStoreVaultTlsServerNameKey) {
|
||||
opts = append(opts, credentialstores.DefaultVaultCredentialStoreTlsServerName())
|
||||
v, ok := d.GetOk(credentialStoreVaultTlsServerNameKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreTlsServerName(v.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialStoreVaultTlsSkipVerifyKey) {
|
||||
opts = append(opts, credentialstores.DefaultVaultCredentialStoreTlsSkipVerify())
|
||||
v, ok := d.GetOk(credentialStoreVaultTlsSkipVerifyKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreTlsSkipVerify(v.(bool)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialStoreVaultTokenKey) {
|
||||
v, ok := d.GetOk(credentialStoreVaultTokenKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreToken(v.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialStoreVaultClientCertificateKey) {
|
||||
opts = append(opts, credentialstores.DefaultVaultCredentialStoreClientCertificate())
|
||||
v, ok := d.GetOk(credentialStoreVaultClientCertificateKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreClientCertificate(v.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialStoreVaultClientCertificateKeyKey) {
|
||||
opts = append(opts, credentialstores.DefaultVaultCredentialStoreClientCertificateKey())
|
||||
v, ok := d.GetOk(credentialStoreVaultClientCertificateKeyKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreClientCertificateKey(v.(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 = setFromVaultCredentialStoreResponseMap(d, crUpdate.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential store from response map: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialStoreVaultDelete(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,240 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentialstores"
|
||||
"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"
|
||||
"golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
const (
|
||||
vaultCredStoreResc = "boundary_credential_store_vault.example"
|
||||
vaultCredStoreName = "foo"
|
||||
vaultCredStoreDesc = "the foo"
|
||||
vaultCredStoreNamespace = "default"
|
||||
vaultCredStoreUpdate = "_random"
|
||||
)
|
||||
|
||||
func vaultCredStoreResource(vc *vault.TestVaultServer, name, description, namespace, tlsServer, token string, skipVerify bool) string {
|
||||
caCert := fmt.Sprintf("\"%s\"", strings.Replace(string(vc.CaCert), "\n", `\n`, -1))
|
||||
clientCert := fmt.Sprintf("\"%s\"", strings.Replace(string(vc.ClientCert), "\n", `\n`, -1))
|
||||
clientKey := fmt.Sprintf("\"%s\"", strings.Replace(string(vc.ClientKey), "\n", `\n`, -1))
|
||||
|
||||
return fmt.Sprintf(`
|
||||
resource "boundary_credential_store_vault" "example" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
scope_id = boundary_scope.proj1.id
|
||||
address = "%s"
|
||||
namespace = "%s"
|
||||
ca_cert = %s
|
||||
tls_server_name = "%s"
|
||||
tls_skip_verify = "%v"
|
||||
token = "%s"
|
||||
client_certificate = %s
|
||||
client_certificate_key = %s
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
}`, name,
|
||||
description,
|
||||
vc.Addr,
|
||||
namespace,
|
||||
caCert,
|
||||
tlsServer,
|
||||
skipVerify,
|
||||
token,
|
||||
clientCert,
|
||||
clientKey)
|
||||
}
|
||||
|
||||
func tokenHmac(token, accessor string) string {
|
||||
key := blake2b.Sum256([]byte(accessor))
|
||||
mac := hmac.New(sha256.New, key[:])
|
||||
_, _ = mac.Write([]byte(token))
|
||||
hmac := mac.Sum(nil)
|
||||
return base64.RawURLEncoding.EncodeToString(hmac)
|
||||
}
|
||||
|
||||
func TestAccCredentialStoreVault(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
vc := vault.NewTestVaultServer(t)
|
||||
secret, token := vc.CreateToken(t)
|
||||
tHmac := tokenHmac(token, secret.Auth.Accessor)
|
||||
res := vaultCredStoreResource(vc,
|
||||
vaultCredStoreName,
|
||||
vaultCredStoreDesc,
|
||||
vaultCredStoreNamespace,
|
||||
"www.original.com",
|
||||
token,
|
||||
true)
|
||||
|
||||
vcUpdate := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestClientTLS))
|
||||
secret, tokenUpdate := vcUpdate.CreateToken(t)
|
||||
tHmacUpdate := tokenHmac(tokenUpdate, secret.Auth.Accessor)
|
||||
resUpdate := vaultCredStoreResource(vcUpdate,
|
||||
vaultCredStoreName+vaultCredStoreUpdate,
|
||||
vaultCredStoreDesc+vaultCredStoreUpdate,
|
||||
vaultCredStoreNamespace+vaultCredStoreUpdate,
|
||||
"www.updated.com",
|
||||
tokenUpdate,
|
||||
false)
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckCredentialStoreVaultResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, res),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, NameKey, vaultCredStoreName),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, DescriptionKey, vaultCredStoreDesc),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultAddressKey, vc.Addr),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultNamespaceKey, vaultCredStoreNamespace),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultCaCertKey, ""),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTlsServerNameKey, "www.original.com"),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTlsSkipVerifyKey, "true"),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTokenKey, token),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTokenHmacKey, tHmac),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultClientCertificateKey, ""),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultClientCertificateKeyKey, ""),
|
||||
|
||||
testAccCheckCredentialStoreVaultResourceExists(provider, vaultCredStoreResc),
|
||||
),
|
||||
},
|
||||
importStep(vaultCredStoreResc, credentialStoreVaultTokenKey, credentialStoreVaultClientCertificateKeyKey),
|
||||
{
|
||||
// update
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, NameKey, vaultCredStoreName+vaultCredStoreUpdate),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, DescriptionKey, vaultCredStoreDesc+vaultCredStoreUpdate),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultAddressKey, vcUpdate.Addr),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultNamespaceKey, vaultCredStoreNamespace+vaultCredStoreUpdate),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultCaCertKey, string(vcUpdate.CaCert)),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTlsServerNameKey, "www.updated.com"),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTlsSkipVerifyKey, "false"),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTokenKey, tokenUpdate),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTokenHmacKey, tHmacUpdate),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultClientCertificateKey, string(vcUpdate.ClientCert)),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultClientCertificateKeyKey, string(vcUpdate.ClientKey)),
|
||||
|
||||
testAccCheckCredentialStoreVaultResourceExists(provider, vaultCredStoreResc),
|
||||
),
|
||||
},
|
||||
importStep(vaultCredStoreResc, credentialStoreVaultTokenKey, credentialStoreVaultClientCertificateKeyKey),
|
||||
{
|
||||
// Run a plan only update and verify no changes
|
||||
PlanOnly: true,
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
},
|
||||
importStep(vaultCredStoreResc, credentialStoreVaultTokenKey, credentialStoreVaultClientCertificateKeyKey),
|
||||
{
|
||||
// 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() { externalUpdate(t, provider) },
|
||||
PlanOnly: true,
|
||||
ExpectNonEmptyPlan: true,
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
},
|
||||
importStep(vaultCredStoreResc, credentialStoreVaultTokenKey, credentialStoreVaultClientCertificateKeyKey),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
var storeId string
|
||||
|
||||
// externalUpdate uses the global storeId, therefore this function cannot be called until
|
||||
// a previous test step that calls testAccCheckCredentialStoreVaultResourceExists has completed.
|
||||
func externalUpdate(t *testing.T, testProvider *schema.Provider) {
|
||||
if storeId == "" {
|
||||
t.Fatal("storeId must be set before testing an external update")
|
||||
}
|
||||
vs := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestClientTLS))
|
||||
_, token := vs.CreateToken(t)
|
||||
|
||||
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 Vault server to existing store
|
||||
var opts []credentialstores.Option
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreToken(token))
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreAddress(vs.Addr))
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreCaCert(string(vs.CaCert)))
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreClientCertificate(string(vs.ClientCert)))
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreClientCertificateKey(string(vs.ClientKey)))
|
||||
|
||||
_, 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 testAccCheckCredentialStoreVaultResourceExists(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 := credentialstores.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 testAccCheckCredentialStoreVaultResourceDestroy(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_vault":
|
||||
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 Vault credential store %q: %v", id, err)
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -196,13 +196,11 @@ func resourceGroupUpdate(ctx context.Context, d *schema.ResourceData, meta inter
|
||||
if err := d.Set(NameKey, name); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
}
|
||||
if d.HasChange(DescriptionKey) {
|
||||
if err := d.Set(DescriptionKey, desc); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// The above call may not actually happen, so we use d.Id() and automatic
|
||||
|
||||
@@ -51,7 +51,7 @@ func resourceHostCatalog() *schema.Resource {
|
||||
ForceNew: true,
|
||||
},
|
||||
TypeKey: {
|
||||
Description: "The host catalog type. Only `Static` (yes, title case) is supported.",
|
||||
Description: "The host catalog type. Only `static` is supported.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/managedgroups"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
managedGroupFilterKey = "filter"
|
||||
)
|
||||
|
||||
func resourceManagedGroup() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The managed group resource allows you to configure a Boundary group.",
|
||||
|
||||
CreateContext: resourceManagedGroupCreate,
|
||||
ReadContext: resourceManagedGroupRead,
|
||||
UpdateContext: resourceManagedGroupUpdate,
|
||||
DeleteContext: resourceManagedGroupDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
IDKey: {
|
||||
Description: "The ID of the group.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
NameKey: {
|
||||
Description: "The managed group name. Defaults to the resource name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
DescriptionKey: {
|
||||
Description: "The managed group description.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
AuthMethodIdKey: {
|
||||
Description: "The resource ID for the auth method.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
managedGroupFilterKey: {
|
||||
Description: "Boolean expression to filter the workers for this managed group.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromManagedGroupResponseMap(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(AuthMethodIdKey, raw[AuthMethodIdKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if attrsVal, ok := raw["attributes"]; ok {
|
||||
attrs := attrsVal.(map[string]interface{})
|
||||
if v, ok := attrs[managedGroupFilterKey]; ok {
|
||||
if err := d.Set(managedGroupFilterKey, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw[IDKey].(string))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceManagedGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
grpClient := managedgroups.NewClient(md.client)
|
||||
|
||||
var authMethodId string
|
||||
authMethodVal, ok := d.GetOk(AuthMethodIdKey)
|
||||
if !ok {
|
||||
return diag.Errorf("no auth method ID provided")
|
||||
}
|
||||
authMethodId = authMethodVal.(string)
|
||||
|
||||
var opts []managedgroups.Option
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
nameStr := nameVal.(string)
|
||||
opts = append(opts, managedgroups.WithName(nameStr))
|
||||
}
|
||||
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
descStr := descVal.(string)
|
||||
opts = append(opts, managedgroups.WithDescription(descStr))
|
||||
}
|
||||
|
||||
v, ok := d.GetOk(managedGroupFilterKey)
|
||||
if ok {
|
||||
str := v.(string)
|
||||
opts = append(opts, managedgroups.WithOidcManagedGroupFilter(str))
|
||||
}
|
||||
|
||||
grp, err := grpClient.Create(ctx, authMethodId, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error creating managed group: %v", err)
|
||||
}
|
||||
if grp == nil {
|
||||
return diag.Errorf("managed group nil after create")
|
||||
}
|
||||
raw := grp.GetResponse().Map
|
||||
|
||||
if err := setFromManagedGroupResponseMap(d, raw); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceManagedGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
grpClient := managedgroups.NewClient(md.client)
|
||||
|
||||
grp, err := grpClient.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
return diag.Errorf("error reading managed group: %v", err)
|
||||
}
|
||||
if grp == nil {
|
||||
return diag.Errorf("managed group nil after read")
|
||||
}
|
||||
|
||||
if err := setFromManagedGroupResponseMap(d, grp.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceManagedGroupUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
grpClient := managedgroups.NewClient(md.client)
|
||||
|
||||
var opts []managedgroups.Option
|
||||
var name *string
|
||||
if d.HasChange(NameKey) {
|
||||
opts = append(opts, managedgroups.DefaultName())
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
nameStr := nameVal.(string)
|
||||
name = &nameStr
|
||||
opts = append(opts, managedgroups.WithName(nameStr))
|
||||
}
|
||||
}
|
||||
|
||||
var desc *string
|
||||
if d.HasChange(DescriptionKey) {
|
||||
opts = append(opts, managedgroups.DefaultDescription())
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
descStr := descVal.(string)
|
||||
desc = &descStr
|
||||
opts = append(opts, managedgroups.WithDescription(descStr))
|
||||
}
|
||||
}
|
||||
|
||||
var filter *string
|
||||
if d.HasChange(managedGroupFilterKey) {
|
||||
if f, ok := d.GetOk(managedGroupFilterKey); ok {
|
||||
filterStr := f.(string)
|
||||
filter = &filterStr
|
||||
opts = append(opts, managedgroups.WithOidcManagedGroupFilter(filterStr))
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, managedgroups.WithAutomaticVersioning(true))
|
||||
_, err := grpClient.Update(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating managed group: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(NameKey) {
|
||||
if err := d.Set(NameKey, name); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
if d.HasChange(DescriptionKey) {
|
||||
if err := d.Set(DescriptionKey, desc); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
if d.HasChange(managedGroupFilterKey) {
|
||||
if err := d.Set(managedGroupFilterKey, filter); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceManagedGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
grpClient := managedgroups.NewClient(md.client)
|
||||
|
||||
_, err := grpClient.Delete(ctx, d.Id())
|
||||
if err != nil {
|
||||
return diag.Errorf("error calling delete managed group: %s", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/managedgroups"
|
||||
"github.com/hashicorp/boundary/testing/controller"
|
||||
"github.com/hashicorp/cap/oidc"
|
||||
"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 (
|
||||
managedGroupName = "test"
|
||||
managedGroupDescription = "test managed group"
|
||||
managedGroupUpdate = "_update"
|
||||
)
|
||||
|
||||
var (
|
||||
fooManagedGroup = fmt.Sprintf(`
|
||||
resource "boundary_managed_group" "foo" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
auth_method_id = boundary_auth_method_oidc.foo.id
|
||||
filter = "name == \"foo\""
|
||||
}`, managedGroupName, managedGroupDescription)
|
||||
|
||||
fooManagedGroupUpdate = fmt.Sprintf(`
|
||||
resource "boundary_managed_group" "foo" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
auth_method_id = boundary_auth_method_oidc.foo.id
|
||||
filter = "name == \"bar\""
|
||||
}`, managedGroupName+managedGroupUpdate, managedGroupDescription+managedGroupUpdate)
|
||||
)
|
||||
|
||||
func TestAccManagedGroup(t *testing.T) {
|
||||
wrapper := testWrapper(t, tcRecoveryKey)
|
||||
tp := oidc.StartTestProvider(t)
|
||||
tc := controller.NewTestController(t, append(tcConfig, controller.WithRecoveryKms(wrapper))...)
|
||||
|
||||
tpCert := strings.TrimSpace(tp.CACert())
|
||||
createConfig := fmt.Sprintf(fooAuthMethodOidc, fooAuthMethodOidcDesc, tp.Addr(), tpCert)
|
||||
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckManagedGroupResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// test create
|
||||
Config: testConfig(url, fooOrg, createConfig, fooManagedGroup),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckManagedGroupResourceExists(provider, "boundary_managed_group.foo"),
|
||||
resource.TestCheckResourceAttr("boundary_managed_group.foo", DescriptionKey, managedGroupDescription),
|
||||
resource.TestCheckResourceAttr("boundary_managed_group.foo", NameKey, managedGroupName),
|
||||
resource.TestCheckResourceAttr("boundary_managed_group.foo", managedGroupFilterKey, `name == "foo"`),
|
||||
),
|
||||
},
|
||||
importStep("boundary_managed_group.foo"),
|
||||
{
|
||||
// test update
|
||||
Config: testConfig(url, fooOrg, createConfig, fooManagedGroupUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckManagedGroupResourceExists(provider, "boundary_managed_group.foo"),
|
||||
resource.TestCheckResourceAttr("boundary_managed_group.foo", DescriptionKey, managedGroupDescription+managedGroupUpdate),
|
||||
resource.TestCheckResourceAttr("boundary_managed_group.foo", NameKey, managedGroupName+managedGroupUpdate),
|
||||
resource.TestCheckResourceAttr("boundary_managed_group.foo", managedGroupFilterKey, `name == "bar"`),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckManagedGroupResourceExists(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)
|
||||
grpClient := managedgroups.NewClient(md.client)
|
||||
|
||||
_, err := grpClient.Read(context.Background(), id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Got an error when reading group %q: %v", id, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckManagedGroupResourceDestroy(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_managed_group":
|
||||
grpClient := managedgroups.NewClient(md.client)
|
||||
id := rs.Primary.ID
|
||||
|
||||
_, err := grpClient.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 resource %q: %v", id, apiErr)
|
||||
}
|
||||
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -12,11 +12,12 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
targetHostSetIdsKey = "host_set_ids"
|
||||
targetDefaultPortKey = "default_port"
|
||||
targetSessionMaxSecondsKey = "session_max_seconds"
|
||||
targetSessionConnectionLimitKey = "session_connection_limit"
|
||||
targetWorkerFilterKey = "worker_filter"
|
||||
targetHostSetIdsKey = "host_set_ids"
|
||||
targetApplicationCredentialLibraryIdsKey = "application_credential_library_ids"
|
||||
targetDefaultPortKey = "default_port"
|
||||
targetSessionMaxSecondsKey = "session_max_seconds"
|
||||
targetSessionConnectionLimitKey = "session_connection_limit"
|
||||
targetWorkerFilterKey = "worker_filter"
|
||||
|
||||
targetTypeTcp = "tcp"
|
||||
)
|
||||
@@ -72,6 +73,12 @@ func resourceTarget() *schema.Resource {
|
||||
Optional: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
targetApplicationCredentialLibraryIdsKey: {
|
||||
Description: "A list of application credential library ID's.",
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
targetSessionMaxSecondsKey: {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
@@ -107,6 +114,9 @@ func setFromTargetResponseMap(d *schema.ResourceData, raw map[string]interface{}
|
||||
if err := d.Set(targetHostSetIdsKey, raw["host_set_ids"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(targetApplicationCredentialLibraryIdsKey, raw["application_credential_library_ids"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(targetSessionMaxSecondsKey, raw["session_max_seconds"]); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -156,8 +166,7 @@ func resourceTargetCreate(ctx context.Context, d *schema.ResourceData, meta inte
|
||||
return diag.Errorf("invalid type provided")
|
||||
}
|
||||
|
||||
opts := []targets.Option{}
|
||||
|
||||
var opts []targets.Option
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
nameStr := nameVal.(string)
|
||||
@@ -206,6 +215,15 @@ func resourceTargetCreate(ctx context.Context, d *schema.ResourceData, meta inte
|
||||
}
|
||||
}
|
||||
|
||||
var credentialLibraryIds []string
|
||||
if credentialLibraryIdsVal, ok := d.GetOk(targetApplicationCredentialLibraryIdsKey); ok {
|
||||
list := credentialLibraryIdsVal.(*schema.Set).List()
|
||||
credentialLibraryIds = make([]string, 0, len(list))
|
||||
for _, i := range list {
|
||||
credentialLibraryIds = append(credentialLibraryIds, i.(string))
|
||||
}
|
||||
}
|
||||
|
||||
workerFilterVal, ok := d.GetOk(targetWorkerFilterKey)
|
||||
if ok {
|
||||
workerFilterStr := workerFilterVal.(string)
|
||||
@@ -223,12 +241,22 @@ func resourceTargetCreate(ctx context.Context, d *schema.ResourceData, meta inte
|
||||
}
|
||||
raw := tcr.GetResponse().Map
|
||||
|
||||
version := tcr.Item.Version
|
||||
if hostSetIds != nil {
|
||||
tur, err := tc.SetHostSets(ctx, tcr.Item.Id, tcr.Item.Version, hostSetIds)
|
||||
tur, err := tc.SetHostSets(ctx, tcr.Item.Id, version, hostSetIds)
|
||||
if err != nil {
|
||||
return diag.Errorf("error setting host sets on target: %v", err)
|
||||
}
|
||||
raw = tur.GetResponse().Map
|
||||
version = tur.Item.Version
|
||||
}
|
||||
|
||||
if credentialLibraryIds != nil {
|
||||
tur, err := tc.SetCredentialLibraries(ctx, tcr.Item.Id, version, targets.WithApplicationCredentialLibraryIds(credentialLibraryIds))
|
||||
if err != nil {
|
||||
return diag.Errorf("error setting credential libraries on target: %v", err)
|
||||
}
|
||||
raw = tur.GetResponse().Map
|
||||
}
|
||||
|
||||
if err := setFromTargetResponseMap(d, raw); err != nil {
|
||||
@@ -265,7 +293,7 @@ func resourceTargetUpdate(ctx context.Context, d *schema.ResourceData, meta inte
|
||||
md := meta.(*metaData)
|
||||
tc := targets.NewClient(md.client)
|
||||
|
||||
opts := []targets.Option{}
|
||||
var opts []targets.Option
|
||||
|
||||
var name *string
|
||||
if d.HasChange(NameKey) {
|
||||
@@ -400,6 +428,34 @@ func resourceTargetUpdate(ctx context.Context, d *schema.ResourceData, meta inte
|
||||
}
|
||||
}
|
||||
|
||||
// The above calls may not actually happen, so we use d.Id() and automatic
|
||||
// versioning here
|
||||
if d.HasChange(targetApplicationCredentialLibraryIdsKey) {
|
||||
var credentialLibraryIds []string
|
||||
if credentialLibraryIdsVal, ok := d.GetOk(targetApplicationCredentialLibraryIdsKey); ok {
|
||||
credLibsIds := credentialLibraryIdsVal.(*schema.Set).List()
|
||||
for _, credLibId := range credLibsIds {
|
||||
credentialLibraryIds = append(credentialLibraryIds, credLibId.(string))
|
||||
}
|
||||
}
|
||||
|
||||
opts := []targets.Option{
|
||||
targets.WithAutomaticVersioning(true),
|
||||
targets.DefaultApplicationCredentialLibraryIds(),
|
||||
}
|
||||
if len(credentialLibraryIds) > 0 {
|
||||
opts = append(opts, targets.WithApplicationCredentialLibraryIds(credentialLibraryIds))
|
||||
}
|
||||
|
||||
_, err := tc.SetCredentialLibraries(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating credential libraries in target: %v", err)
|
||||
}
|
||||
if err := d.Set(targetApplicationCredentialLibraryIdsKey, credentialLibraryIds); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/testing/vault"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/targets"
|
||||
"github.com/hashicorp/boundary/testing/controller"
|
||||
@@ -20,7 +22,7 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
fooHostSet = `
|
||||
fooBarHostSet = `
|
||||
resource "boundary_host_catalog" "foo" {
|
||||
type = "static"
|
||||
name = "test"
|
||||
@@ -49,10 +51,36 @@ resource "boundary_host_set" "foo" {
|
||||
host_catalog_id = boundary_host_catalog.foo.id
|
||||
host_ids = [
|
||||
boundary_host.foo.id,
|
||||
]
|
||||
}
|
||||
|
||||
resource "boundary_host_set" "bar" {
|
||||
name = "bar"
|
||||
type = "static"
|
||||
host_catalog_id = boundary_host_catalog.foo.id
|
||||
host_ids = [
|
||||
boundary_host.bar.id,
|
||||
]
|
||||
}`
|
||||
|
||||
fooBarCredLibs = `
|
||||
resource "boundary_credential_library_vault" "foo" {
|
||||
name = "foo"
|
||||
description = "foo library"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "foo/bar"
|
||||
http_method = "GET"
|
||||
}
|
||||
|
||||
resource "boundary_credential_library_vault" "bar" {
|
||||
name = "bar"
|
||||
description = "bar library"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "bar/foo"
|
||||
http_method = "GET"
|
||||
}
|
||||
`
|
||||
|
||||
fooTarget = fmt.Sprintf(`
|
||||
resource "boundary_target" "foo" {
|
||||
name = "test"
|
||||
@@ -62,6 +90,9 @@ resource "boundary_target" "foo" {
|
||||
host_set_ids = [
|
||||
boundary_host_set.foo.id
|
||||
]
|
||||
application_credential_library_ids = [
|
||||
boundary_credential_library_vault.foo.id
|
||||
]
|
||||
default_port = 22
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
session_max_seconds = 6000
|
||||
@@ -76,8 +107,24 @@ resource "boundary_target" "foo" {
|
||||
type = "tcp"
|
||||
scope_id = boundary_scope.proj1.id
|
||||
host_set_ids = [
|
||||
boundary_host_set.foo.id
|
||||
boundary_host_set.bar.id
|
||||
]
|
||||
application_credential_library_ids = [
|
||||
boundary_credential_library_vault.bar.id
|
||||
]
|
||||
default_port = 80
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
session_max_seconds = 7000
|
||||
session_connection_limit = 7
|
||||
worker_filter = "type == \"bar\""
|
||||
}`, fooTargetDescriptionUpdate)
|
||||
|
||||
fooTargetUpdateUnsetHostAndCredLibs = fmt.Sprintf(`
|
||||
resource "boundary_target" "foo" {
|
||||
name = "test"
|
||||
description = "%s"
|
||||
type = "tcp"
|
||||
scope_id = boundary_scope.proj1.id
|
||||
default_port = 80
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
session_max_seconds = 7000
|
||||
@@ -92,6 +139,16 @@ func TestAccTarget(t *testing.T) {
|
||||
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{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
@@ -99,7 +156,7 @@ func TestAccTarget(t *testing.T) {
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// test create
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, fooHostSet, fooTarget),
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, fooBarCredLibs, fooBarHostSet, fooTarget),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckTargetResourceExists(provider, "boundary_target.foo"),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", DescriptionKey, fooTargetDescription),
|
||||
@@ -108,12 +165,14 @@ func TestAccTarget(t *testing.T) {
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionMaxSecondsKey, "6000"),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionConnectionLimitKey, "6"),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", targetWorkerFilterKey, `type == "foo"`),
|
||||
testAccCheckTargetResourceHostSet(provider, "boundary_target.foo", []string{"boundary_host_set.foo"}),
|
||||
testAccCheckTargetResourceAppCredLibs(provider, "boundary_target.foo", []string{"boundary_credential_library_vault.foo"}),
|
||||
),
|
||||
},
|
||||
importStep("boundary_target.foo"),
|
||||
{
|
||||
// test update
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, fooHostSet, fooTargetUpdate),
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, fooBarCredLibs, fooBarHostSet, fooTargetUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckTargetResourceExists(provider, "boundary_target.foo"),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", DescriptionKey, fooTargetDescriptionUpdate),
|
||||
@@ -121,6 +180,23 @@ func TestAccTarget(t *testing.T) {
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionMaxSecondsKey, "7000"),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionConnectionLimitKey, "7"),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", targetWorkerFilterKey, `type == "bar"`),
|
||||
testAccCheckTargetResourceHostSet(provider, "boundary_target.foo", []string{"boundary_host_set.bar"}),
|
||||
testAccCheckTargetResourceAppCredLibs(provider, "boundary_target.foo", []string{"boundary_credential_library_vault.bar"}),
|
||||
),
|
||||
},
|
||||
importStep("boundary_target.foo"),
|
||||
{
|
||||
// test unset hosts and cred libs
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, fooBarCredLibs, fooBarHostSet, fooTargetUpdateUnsetHostAndCredLibs),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckTargetResourceExists(provider, "boundary_target.foo"),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", DescriptionKey, fooTargetDescriptionUpdate),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", targetDefaultPortKey, "80"),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionMaxSecondsKey, "7000"),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionConnectionLimitKey, "7"),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", targetWorkerFilterKey, `type == "bar"`),
|
||||
testAccCheckTargetResourceHostSet(provider, "boundary_target.foo", nil),
|
||||
testAccCheckTargetResourceAppCredLibs(provider, "boundary_target.foo", nil),
|
||||
),
|
||||
},
|
||||
importStep("boundary_target.foo"),
|
||||
@@ -128,7 +204,7 @@ func TestAccTarget(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckTargetResourceMembersSet(testProvider *schema.Provider, name string, hostSets []string) resource.TestCheckFunc {
|
||||
func testAccCheckTargetResourceHostSet(testProvider *schema.Provider, name string, hostSets []string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
@@ -141,7 +217,7 @@ func testAccCheckTargetResourceMembersSet(testProvider *schema.Provider, name st
|
||||
}
|
||||
|
||||
// ensure host sets are declared in state
|
||||
hostSetIDs := []string{}
|
||||
var hostSetIDs []string
|
||||
for _, hostSetResourceID := range hostSets {
|
||||
hs, ok := s.RootModule().Resources[hostSetResourceID]
|
||||
if !ok {
|
||||
@@ -165,19 +241,76 @@ func testAccCheckTargetResourceMembersSet(testProvider *schema.Provider, name st
|
||||
return fmt.Errorf("Got an error when reading target %q: %v", id, err)
|
||||
}
|
||||
|
||||
if len(t.Item.HostSetIds) == 0 {
|
||||
return fmt.Errorf("no hostSets found on target")
|
||||
if len(t.Item.HostSetIds) != len(hostSetIDs) {
|
||||
return fmt.Errorf("tf state and boundary have different number of host sets")
|
||||
}
|
||||
|
||||
for _, stateHostSet := range t.Item.HostSetIds {
|
||||
for _, stateHostSetId := range t.Item.HostSetIds {
|
||||
ok := false
|
||||
for _, gotHostSetID := range hostSetIDs {
|
||||
if gotHostSetID == stateHostSet {
|
||||
if gotHostSetID == stateHostSetId {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("host set in state not set in boundary: %s", stateHostSet)
|
||||
return fmt.Errorf("host set id in state not set in boundary: %s", stateHostSetId)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckTargetResourceAppCredLibs(testProvider *schema.Provider, name string, credLibs []string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("target resource not found: %s", name)
|
||||
}
|
||||
|
||||
id := rs.Primary.ID
|
||||
if id == "" {
|
||||
return fmt.Errorf("target resource ID is not set")
|
||||
}
|
||||
|
||||
// ensure host sets are declared in state
|
||||
var credLibIDs []string
|
||||
for _, credLibResourceID := range credLibs {
|
||||
cl, ok := s.RootModule().Resources[credLibResourceID]
|
||||
if !ok {
|
||||
return fmt.Errorf("credential library resource not found: %s", credLibResourceID)
|
||||
}
|
||||
|
||||
credLibID := cl.Primary.ID
|
||||
if id == "" {
|
||||
return fmt.Errorf("credential library resource ID not set")
|
||||
}
|
||||
|
||||
credLibIDs = append(credLibIDs, credLibID)
|
||||
}
|
||||
|
||||
// check boundary to ensure it matches
|
||||
md := testProvider.Meta().(*metaData)
|
||||
tgtsClient := targets.NewClient(md.client)
|
||||
|
||||
t, err := tgtsClient.Read(context.Background(), id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Got an error when reading target %q: %v", id, err)
|
||||
}
|
||||
|
||||
if len(t.Item.ApplicationCredentialLibraryIds) != len(credLibIDs) {
|
||||
return fmt.Errorf("tf state and boundary have different number of application credential libraries")
|
||||
}
|
||||
|
||||
for _, stateCredLibId := range t.Item.ApplicationCredentialLibraryIds {
|
||||
ok := false
|
||||
for _, gotCredLibID := range credLibIDs {
|
||||
if gotCredLibID == stateCredLibId {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("application credential library id in state not set in boundary: %s", stateCredLibId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user