Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 2.5.0

FEATURES:

- Add optional `response_sensitive` and sensitive response attributes to `terracurl_request` resource, data source, and ephemeral resource to prevent secret values appearing in plan output. Based on #144 by @ohaibbq. Closes #142.

## 2.4.2

BUG FIXES:
Expand Down
4 changes: 3 additions & 1 deletion docs/data-sources/request.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ TerraCurl request data source
- `max_retry` (Number) Maximum number of tries until it is marked as failed
- `request_body` (String) A request body to attach to the API call
- `request_parameters` (Map of String) Map of parameters to attach to the API call
- `response_sensitive` (Boolean) Set to `true` to treat the response as sensitive. When enabled, the response body is written to `sensitive_response` (a sensitive attribute) and `response` is left empty so that secret values are not displayed in plan output. Defaults to `false` to preserve existing behavior.
- `retry_interval` (Number) Interval between each attempt
- `skip_tls_verify` (Boolean) Set this to true to disable verification of the server's TLS certificate
- `timeout` (Number) Time in seconds before each request times out. Defaults to 10
Expand All @@ -40,5 +41,6 @@ TerraCurl request data source

- `id` (String) Example identifier
- `request_url_string` (String) Request URL includes parameters if request specified
- `response` (String) JSON response received from request
- `response` (String) JSON response received from request. Empty when `response_sensitive` is `true`; use `sensitive_response` instead.
- `sensitive_response` (String, Sensitive) JSON response received from request, marked as sensitive so it is not displayed in plan output. Populated only when `response_sensitive` is `true`.
- `status_code` (String) Response status code received from request
10 changes: 7 additions & 3 deletions docs/ephemeral-resources/request.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ TerraCurl request ephemeral resource
- `renew_url` (String) Api endpoint to call
- `request_body` (String) A request body to attach to the API call
- `request_parameters` (Map of String) Map of parameters to attach to the API call
- `response_sensitive` (Boolean) Set to `true` to treat response bodies as sensitive. When enabled, response bodies are written to the corresponding `sensitive_*` attributes and the non-sensitive attributes are left empty so secret values are not displayed in plan output. Defaults to `false` to preserve existing behavior.
- `retry_interval` (Number) Interval between each attempt
- `skip_close` (Boolean) Set to true if there are no api calls to make to clean up the ephemeral resource on the target platform. Default value is set to `true`.
- `skip_renew` (Boolean) Set to true to skip renewing ephemeral resources. Default value is `true`
Expand All @@ -70,10 +71,13 @@ TerraCurl request ephemeral resource
### Read-Only

- `close_request_url_string` (String) Request URL includes parameters if request specified
- `close_response` (String) JSON response received from request
- `close_response` (String) JSON response received from request. Empty when `response_sensitive` is `true`; use `sensitive_close_response` instead.
- `id` (String) Example identifier
- `renew_request_url_string` (String) Request URL includes parameters if request specified
- `renew_response` (String) JSON response received from request
- `renew_response` (String) JSON response received from request. Empty when `response_sensitive` is `true`; use `sensitive_renew_response` instead.
- `request_url_string` (String) Request URL includes parameters if request specified
- `response` (String) JSON response received from request
- `response` (String) JSON response received from request. Empty when `response_sensitive` is `true`; use `sensitive_response` instead.
- `sensitive_close_response` (String, Sensitive) JSON response received from close request, marked as sensitive so it is not displayed in plan output. Populated only when `response_sensitive` is `true`.
- `sensitive_renew_response` (String, Sensitive) JSON response received from renew request, marked as sensitive so it is not displayed in plan output. Populated only when `response_sensitive` is `true`.
- `sensitive_response` (String, Sensitive) JSON response received from request, marked as sensitive so it is not displayed in plan output. Populated only when `response_sensitive` is `true`.
- `status_code` (String) Response status code received from request
4 changes: 3 additions & 1 deletion docs/resources/request.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ TerraCurl request resource
- `read_url` (String) API endpoint for reading resource state. Required if `skip_read` is false.
- `request_body` (String) A request body to attach to the API call
- `request_parameters` (Map of String) Map of parameters to attach to the API call
- `response_sensitive` (Boolean) Set to `true` to treat the response as sensitive. When enabled, the response body is written to `sensitive_response` (a sensitive attribute) and `response` is left empty so that secret values are not displayed in plan output. Defaults to `false` to preserve existing behavior.
- `retry_interval` (Number) Interval between each attempt
- `skip_destroy` (Boolean) Set this to true to skip issuing a request when the resource is being destroyed
- `skip_read` (Boolean) Set to true to skip the read operation (no drift detection). Defaults to true.
Expand All @@ -70,5 +71,6 @@ TerraCurl request resource
- `drift_marker` (String) Marker to track state drift and trigger resource replacement
- `id` (String) Example identifier
- `request_url_string` (String) Request URL includes parameters if request specified
- `response` (String) JSON response received from request
- `response` (String) JSON response received from request. Empty when `response_sensitive` is `true`; use `sensitive_response` instead.
- `sensitive_response` (String, Sensitive) JSON response received from request, marked as sensitive so it is not displayed in plan output. Populated only when `response_sensitive` is `true`.
- `status_code` (String) Response status code received from request
16 changes: 14 additions & 2 deletions internal/provider/curl_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ type CurlDataSourceModel struct {
MaxRetry types.Int64 `tfsdk:"max_retry"`
Timeout types.Int64 `tfsdk:"timeout"`
Response types.String `tfsdk:"response"`
SensitiveResponse types.String `tfsdk:"sensitive_response"`
ResponseSensitive types.Bool `tfsdk:"response_sensitive"`
ResponseCodes types.List `tfsdk:"response_codes"`
StatusCode types.String `tfsdk:"status_code"`
}
Expand Down Expand Up @@ -128,7 +130,17 @@ func (d *CurlDataSource) Schema(ctx context.Context, req datasource.SchemaReques
},
"response": schema.StringAttribute{
Computed: true,
MarkdownDescription: "JSON response received from request",
MarkdownDescription: "JSON response received from request. Empty when `response_sensitive` is `true`; use `sensitive_response` instead.",
},
"sensitive_response": schema.StringAttribute{
Computed: true,
Sensitive: true,
MarkdownDescription: "JSON response received from request, marked as sensitive so it is not displayed in plan output. Populated only when `response_sensitive` is `true`.",
},
"response_sensitive": schema.BoolAttribute{
Optional: true,
Computed: true,
MarkdownDescription: "Set to `true` to treat the response as sensitive. When enabled, the response body is written to `sensitive_response` (a sensitive attribute) and `response` is left empty so that secret values are not displayed in plan output. Defaults to `false` to preserve existing behavior.",
},
"response_codes": schema.ListAttribute{
Required: true,
Expand Down Expand Up @@ -286,7 +298,7 @@ func (d *CurlDataSource) Read(ctx context.Context, req datasource.ReadRequest, r
}

data.RequestUrlString = types.StringValue(request.URL.String())
data.Response = types.StringValue(bodyString)
setDataSourceResponseValues(&data, bodyString)
data.StatusCode = types.StringValue(strconv.Itoa(statusCode))

// Save data into Terraform state.
Expand Down
86 changes: 86 additions & 0 deletions internal/provider/curl_data_source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,3 +329,89 @@ data "terracurl_request" "tls_skip_verify_test" {
}
`, url, certFile, keyFile)
}

func TestAccDataSourceCurlResponseSensitive(t *testing.T) {
t.Setenv("TF_ACC", "true")
t.Setenv("USE_DEFAULT_CLIENT_FOR_TESTS", "true")

httpmock.Activate()
defer httpmock.DeactivateAndReset()

secretBody := `{"token": "super-secret-token", "key": "key-1234"}`
httpmock.RegisterResponder(
"GET",
"https://example.com/data-sensitive",
httpmock.NewStringResponder(200, secretBody),
)

rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccDataSourceCurlResponseSensitive(rName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.terracurl_request.sensitive_test", "response_sensitive", "true"),
resource.TestCheckResourceAttr("data.terracurl_request.sensitive_test", "response", ""),
resource.TestCheckResourceAttr("data.terracurl_request.sensitive_test", "sensitive_response", secretBody),
),
},
},
})
}

func testAccDataSourceCurlResponseSensitive(name string) string {
return fmt.Sprintf(`
data "terracurl_request" "sensitive_test" {
name = "%s"
url = "https://example.com/data-sensitive"
method = "GET"
response_codes = ["200"]
response_sensitive = true
}
`, name)
}

func TestAccDataSourceCurlResponseSensitiveDefault(t *testing.T) {
t.Setenv("TF_ACC", "true")
t.Setenv("USE_DEFAULT_CLIENT_FOR_TESTS", "true")

httpmock.Activate()
defer httpmock.DeactivateAndReset()

body := `{"message": "ok"}`
httpmock.RegisterResponder(
"GET",
"https://example.com/data-default",
httpmock.NewStringResponder(200, body),
)

rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccDataSourceCurlResponseSensitiveDefault(rName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.terracurl_request.default_test", "response", body),
resource.TestCheckResourceAttr("data.terracurl_request.default_test", "sensitive_response", ""),
),
},
},
})
}

func testAccDataSourceCurlResponseSensitiveDefault(name string) string {
return fmt.Sprintf(`
data "terracurl_request" "default_test" {
name = "%s"
url = "https://example.com/data-default"
method = "GET"
response_codes = ["200"]
}
`, name)
}
50 changes: 44 additions & 6 deletions internal/provider/curl_ephemeral_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ type CurlEphemeralModel struct {
MaxRetry types.Int64 `tfsdk:"max_retry"`
Timeout types.Int64 `tfsdk:"timeout"`
Response types.String `tfsdk:"response"`
SensitiveResponse types.String `tfsdk:"sensitive_response"`
ResponseSensitive types.Bool `tfsdk:"response_sensitive"`
ResponseCodes types.List `tfsdk:"response_codes"`
StatusCode types.String `tfsdk:"status_code"`
SkipRenew types.Bool `tfsdk:"skip_renew"`
Expand All @@ -80,6 +82,7 @@ type CurlEphemeralModel struct {
RenewMaxRetry types.Int64 `tfsdk:"renew_max_retry"`
RenewTimeout types.Int64 `tfsdk:"renew_timeout"`
RenewResponse types.String `tfsdk:"renew_response"`
SensitiveRenewResponse types.String `tfsdk:"sensitive_renew_response"`
RenewResponseCodes types.List `tfsdk:"renew_response_codes"`

SkipClose types.Bool `tfsdk:"skip_close"`
Expand All @@ -99,6 +102,7 @@ type CurlEphemeralModel struct {
CloseMaxRetry types.Int64 `tfsdk:"close_max_retry"`
CloseTimeout types.Int64 `tfsdk:"close_timeout"`
CloseResponse types.String `tfsdk:"close_response"`
SensitiveCloseResponse types.String `tfsdk:"sensitive_close_response"`
CloseResponseCodes types.List `tfsdk:"close_response_codes"`
}

Expand Down Expand Up @@ -177,7 +181,17 @@ func (e *EphemeralCurlResource) Schema(ctx context.Context, req ephemeral.Schema
},
"response": schema.StringAttribute{
Computed: true,
MarkdownDescription: "JSON response received from request",
MarkdownDescription: "JSON response received from request. Empty when `response_sensitive` is `true`; use `sensitive_response` instead.",
},
"sensitive_response": schema.StringAttribute{
Computed: true,
Sensitive: true,
MarkdownDescription: "JSON response received from request, marked as sensitive so it is not displayed in plan output. Populated only when `response_sensitive` is `true`.",
},
"response_sensitive": schema.BoolAttribute{
Optional: true,
Computed: true,
MarkdownDescription: "Set to `true` to treat response bodies as sensitive. When enabled, response bodies are written to the corresponding `sensitive_*` attributes and the non-sensitive attributes are left empty so secret values are not displayed in plan output. Defaults to `false` to preserve existing behavior.",
},
"response_codes": schema.ListAttribute{
Required: true,
Expand Down Expand Up @@ -260,7 +274,12 @@ func (e *EphemeralCurlResource) Schema(ctx context.Context, req ephemeral.Schema
},
"renew_response": schema.StringAttribute{
Computed: true,
MarkdownDescription: "JSON response received from request",
MarkdownDescription: "JSON response received from request. Empty when `response_sensitive` is `true`; use `sensitive_renew_response` instead.",
},
"sensitive_renew_response": schema.StringAttribute{
Computed: true,
Sensitive: true,
MarkdownDescription: "JSON response received from renew request, marked as sensitive so it is not displayed in plan output. Populated only when `response_sensitive` is `true`.",
},
"renew_response_codes": schema.ListAttribute{
Optional: true,
Expand Down Expand Up @@ -334,7 +353,12 @@ func (e *EphemeralCurlResource) Schema(ctx context.Context, req ephemeral.Schema
},
"close_response": schema.StringAttribute{
Computed: true,
MarkdownDescription: "JSON response received from request",
MarkdownDescription: "JSON response received from request. Empty when `response_sensitive` is `true`; use `sensitive_close_response` instead.",
},
"sensitive_close_response": schema.StringAttribute{
Computed: true,
Sensitive: true,
MarkdownDescription: "JSON response received from close request, marked as sensitive so it is not displayed in plan output. Populated only when `response_sensitive` is `true`.",
},
"close_response_codes": schema.ListAttribute{
Optional: true,
Expand Down Expand Up @@ -514,7 +538,7 @@ func (e *EphemeralCurlResource) Open(ctx context.Context, req ephemeral.OpenRequ
}

data.RequestUrlString = types.StringValue(request.URL.String())
data.Response = types.StringValue(bodyString)
setEphemeralOpenResponse(&data, bodyString)
data.StatusCode = types.StringValue(strconv.Itoa(statusCode))

tflog.Debug(ctx, fmt.Sprintf("renew parameters in Open() is set to %v", convertMap(data.RenewRequestParameters)))
Expand Down Expand Up @@ -544,7 +568,8 @@ func (e *EphemeralCurlResource) Open(ctx context.Context, req ephemeral.OpenRequ
"Timeout": data.Timeout.ValueInt64(),
"StatusCode": data.StatusCode.ValueString(),
"Response_codes": data.ResponseCodes.Elements(),
"Response": data.Response.ValueString(),
"Response": bodyString,
"ResponseSensitive": data.ResponseSensitive.ValueBool(),

"SkipRenew": data.SkipRenew.ValueBool(),
"RenewUrl": data.RenewUrl.ValueString(),
Expand Down Expand Up @@ -984,7 +1009,13 @@ func (e *EphemeralCurlResource) Renew(ctx context.Context, req ephemeral.RenewRe
return
}

responseSensitive, ok := privateMap["ResponseSensitive"].(bool)
if !ok {
responseSensitive = false
}

privateData := CurlEphemeralModel{
ResponseSensitive: types.BoolValue(responseSensitive),
RenewMethod: types.StringValue(renewMethod),
RenewUrl: types.StringValue(renewUrl),
SkipRenew: types.BoolValue(skipRenew),
Expand Down Expand Up @@ -1161,7 +1192,7 @@ func (e *EphemeralCurlResource) Renew(ctx context.Context, req ephemeral.RenewRe
}

privateData.RenewRequestUrlString = types.StringValue(request.URL.String())
privateData.RenewResponse = types.StringValue(bodyString)
setEphemeralRenewResponse(&privateData, bodyString)
privateData.StatusCode = types.StringValue(strconv.Itoa(statusCode))

// Renew again
Expand Down Expand Up @@ -1362,7 +1393,13 @@ func (e *EphemeralCurlResource) Close(ctx context.Context, req ephemeral.CloseRe
return
}

responseSensitive, ok := privateMap["ResponseSensitive"].(bool)
if !ok {
responseSensitive = false
}

privateData := CurlEphemeralModel{
ResponseSensitive: types.BoolValue(responseSensitive),
CloseMethod: types.StringValue(closeMethod),
CloseUrl: types.StringValue(closeUrl),
SkipClose: types.BoolValue(skipClose),
Expand Down Expand Up @@ -1521,6 +1558,7 @@ func (e *EphemeralCurlResource) Close(ctx context.Context, req ephemeral.CloseRe
}
}

setEphemeralCloseResponse(&privateData, string(bodyBytes))
}

func (e EphemeralCurlResource) ConfigValidators(ctx context.Context) []ephemeral.ConfigValidator {
Expand Down
Loading
Loading