diff --git a/CHANGELOG.md b/CHANGELOG.md index 6142ac4c..0852670b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ Canonical reference for changes, improvements, and bugfixes for the Boundary Ter * Add support for a storage bucket as a resource ([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/417)) +* Add option to enable session recording on a target resource + ([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/421)) ## 1.1.8 (June 13, 2023) diff --git a/docs/resources/target.md b/docs/resources/target.md index 37a4f1bb..ef6f1d54 100644 --- a/docs/resources/target.md +++ b/docs/resources/target.md @@ -84,6 +84,20 @@ resource "boundary_host_set" "foo" { ] } +resource "boundary_storage_bucket" "aws_example" { + name = "My aws storage bucket" + description = "My first storage bucket!" + scope_id = boundary_scope.org.id + plugin_name = "aws" + bucket_name = "mybucket" + attributes_json = jsonencode({ "region" = "us-east-1" }) + secrets_json = jsonencode({ + "access_key_id" = "aws_access_key_id_value", + "secret_access_key" = "aws_secret_access_key_value" + }) + worker_filter = "\"pki\" in \"/tags/type\"" +} + resource "boundary_target" "foo" { name = "foo" description = "Foo target" @@ -112,6 +126,22 @@ resource "boundary_target" "ssh_foo" { ] } +resource "boundary_target" "ssh_session_recording_foo" { + name = "ssh_foo" + description = "Ssh target" + type = "ssh" + default_port = "22" + scope_id = boundary_scope.project.id + host_source_ids = [ + boundary_host_set.foo.id + ] + injected_application_credential_source_ids = [ + boundary_credential_library_vault.foo.id + ] + enable_session_recording = true + storage_bucket_id = boundary_storage_bucket.aws_example +} + resource "boundary_target" "address_foo" { name = "address_foo" description = "Foo target with an address" @@ -138,12 +168,14 @@ resource "boundary_target" "address_foo" { - `default_port` (Number) The default port for this target. - `description` (String) The target description. - `egress_worker_filter` (String) Boolean expression to filter the workers used to access this target +- `enable_session_recording` (Boolean) HCP/Ent Only. Enable sessions recording for this target. Only applicable for SSH targets. - `host_source_ids` (Set of String) A list of host source ID's. Cannot be used alongside address. - `ingress_worker_filter` (String) HCP Only. Boolean expression to filter the workers a user will connect to when initiating a session against this target - `injected_application_credential_source_ids` (Set of String) A list of injected application credential source ID's. - `name` (String) The target name. Defaults to the resource name. - `session_connection_limit` (Number) - `session_max_seconds` (Number) +- `storage_bucket_id` (String) HCP/Ent Only. Storage bucket for this target. Only applicable for SSH targets. - `worker_filter` (String, Deprecated) Boolean expression to filter the workers for this target ### Read-Only diff --git a/examples/resources/boundary_target/resource.tf b/examples/resources/boundary_target/resource.tf index f570679e..e617e7e0 100644 --- a/examples/resources/boundary_target/resource.tf +++ b/examples/resources/boundary_target/resource.tf @@ -69,6 +69,20 @@ resource "boundary_host_set" "foo" { ] } +resource "boundary_storage_bucket" "aws_example" { + name = "My aws storage bucket" + description = "My first storage bucket!" + scope_id = boundary_scope.org.id + plugin_name = "aws" + bucket_name = "mybucket" + attributes_json = jsonencode({ "region" = "us-east-1" }) + secrets_json = jsonencode({ + "access_key_id" = "aws_access_key_id_value", + "secret_access_key" = "aws_secret_access_key_value" + }) + worker_filter = "\"pki\" in \"/tags/type\"" +} + resource "boundary_target" "foo" { name = "foo" description = "Foo target" @@ -97,6 +111,22 @@ resource "boundary_target" "ssh_foo" { ] } +resource "boundary_target" "ssh_session_recording_foo" { + name = "ssh_foo" + description = "Ssh target" + type = "ssh" + default_port = "22" + scope_id = boundary_scope.project.id + host_source_ids = [ + boundary_host_set.foo.id + ] + injected_application_credential_source_ids = [ + boundary_credential_library_vault.foo.id + ] + enable_session_recording = true + storage_bucket_id = boundary_storage_bucket.aws_example +} + resource "boundary_target" "address_foo" { name = "address_foo" description = "Foo target with an address" diff --git a/internal/provider/resource_target.go b/internal/provider/resource_target.go index 1dcc7ef3..3e6de2b1 100644 --- a/internal/provider/resource_target.go +++ b/internal/provider/resource_target.go @@ -21,8 +21,10 @@ const ( targetInjectedAppCredentialSourceIdsKey = "injected_application_credential_source_ids" targetDefaultPortKey = "default_port" targetDefaultClientPortKey = "default_client_port" + targetEnableSessionRecordingKey = "enable_session_recording" targetSessionMaxSecondsKey = "session_max_seconds" targetSessionConnectionLimitKey = "session_connection_limit" + targetStorageBucketIdKey = "storage_bucket_id" targetWorkerFilterKey = "worker_filter" targetWorkerEgressFilterKey = "egress_worker_filter" targetWorkerIngressFilterKey = "ingress_worker_filter" @@ -133,6 +135,16 @@ func resourceTarget() *schema.Resource { Optional: true, ConflictsWith: []string{targetHostSourceIdsKey}, }, + targetEnableSessionRecordingKey: { + Description: "HCP/Ent Only. Enable sessions recording for this target. Only applicable for SSH targets.", + Type: schema.TypeBool, + Optional: true, + }, + targetStorageBucketIdKey: { + Description: "HCP/Ent Only. Storage bucket for this target. Only applicable for SSH targets.", + Type: schema.TypeString, + Optional: true, + }, }, } } @@ -178,7 +190,9 @@ func setFromTargetResponseMap(d *schema.ResourceData, raw map[string]interface{} return err } - switch raw["type"].(string) { + typeStr := raw["type"].(string) + + switch typeStr { case targetTypeTcp, targetTypeSsh: if attrsVal, ok := raw["attributes"]; ok { attrs := attrsVal.(map[string]interface{}) @@ -194,6 +208,20 @@ func setFromTargetResponseMap(d *schema.ResourceData, raw map[string]interface{} return err } } + + // SSH only features + if typeStr == targetTypeSsh { + if sessionRecordingVal, ok := attrs[targetEnableSessionRecordingKey].(bool); ok { + if err := d.Set(targetEnableSessionRecordingKey, sessionRecordingVal); err != nil { + return err + } + } + if targetStorageBucketIdVal, ok := attrs[targetStorageBucketIdKey]; ok { + if err := d.Set(targetStorageBucketIdKey, targetStorageBucketIdVal); err != nil { + return err + } + } + } } } @@ -264,6 +292,18 @@ func resourceTargetCreate(ctx context.Context, d *schema.ResourceData, meta inte } } + enableSessionRecordingVal, ok := d.GetOk(targetEnableSessionRecordingKey) + if ok { + enableSessionRecordingBool := enableSessionRecordingVal.(bool) + opts = append(opts, targets.WithSshTargetEnableSessionRecording(enableSessionRecordingBool)) + } + + storageBucketIdVal, ok := d.GetOk(targetStorageBucketIdKey) + if ok { + storageBucketIdStr := storageBucketIdVal.(string) + opts = append(opts, targets.WithSshTargetStorageBucketId(storageBucketIdStr)) + } + sessionMaxSecondsVal, ok := d.GetOk(targetSessionMaxSecondsKey) if ok { sessionMaxSecondsInt := sessionMaxSecondsVal.(int) @@ -443,6 +483,34 @@ func resourceTargetUpdate(ctx context.Context, d *schema.ResourceData, meta inte } } + var enableSessionRecording *bool + if d.HasChange(targetEnableSessionRecordingKey) { + switch typeStr { + case targetTypeSsh: + opts = append(opts, targets.WithSshTargetEnableSessionRecording(false)) + enableSessionRecordingVal, ok := d.GetOk(targetEnableSessionRecordingKey) + if ok { + enableSessionRecordingBool := enableSessionRecordingVal.(bool) + enableSessionRecording = &enableSessionRecordingBool + opts = append(opts, targets.WithSshTargetEnableSessionRecording(enableSessionRecordingBool)) + } + } + } + + var storageBucket *string + if d.HasChange(targetStorageBucketIdKey) { + switch typeStr { + case targetTypeSsh: + opts = append(opts, targets.DefaultSshTargetStorageBucketId()) + storageBucketVal, ok := d.GetOk(targetStorageBucketIdKey) + if ok { + storageBucketStr := storageBucketVal.(string) + storageBucket = &storageBucketStr + opts = append(opts, targets.WithSshTargetStorageBucketId(storageBucketStr)) + } + } + } + var defaultPort *int if d.HasChange(targetDefaultPortKey) { switch typeStr { @@ -631,6 +699,16 @@ func resourceTargetUpdate(ctx context.Context, d *schema.ResourceData, meta inte return diag.FromErr(err) } } + if d.HasChange(targetEnableSessionRecordingKey) { + if err := d.Set(targetEnableSessionRecordingKey, enableSessionRecording); err != nil { + return diag.FromErr(err) + } + } + if d.HasChange(targetStorageBucketIdKey) { + if err := d.Set(targetStorageBucketIdKey, storageBucket); err != nil { + return diag.FromErr(err) + } + } // The above call may not actually happen, so we use d.Id() and automatic // versioning here