Skip to content

Commit 0d4aa69

Browse files
committed
feat: add sensitive_request_headers argument
This seeks to address issue #34 via `sensitive_request_headers`, which serves the same function as `request_headers`, but are denoted as sensitive and their values are obscured as `*****` in Terraform state. Signed-off-by: Mike Ball <[email protected]>
1 parent ccd7a81 commit 0d4aa69

File tree

3 files changed

+127
-16
lines changed

3 files changed

+127
-16
lines changed

docs/data-sources/http.md

+1
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ resource "null_resource" "example" {
157157
- `request_headers` (Map of String) A map of request header field names and values.
158158
- `request_timeout_ms` (Number) The request timeout in milliseconds.
159159
- `retry` (Block, Optional) Retry request configuration. By default there are no retries. Configuring this block will result in retries if an error is returned by the client (e.g., connection errors) or if a 5xx-range (except 501) status code is received. For further details see [go-retryablehttp](https://pkg.go.dev/github.com/hashicorp/go-retryablehttp). (see [below for nested schema](#nestedblock--retry))
160+
- `sensitive_request_headers` (Map of String, Sensitive) A map of request header field names and values regarded as sensitive. The header values are masked in CLI output, as well as Terraform state.
160161

161162
### Read-Only
162163

internal/provider/data_source_http.go

+49-16
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,14 @@ a 5xx-range (except 501) status code is received. For further details see
9898
Optional: true,
9999
},
100100

101+
"sensitive_request_headers": schema.MapAttribute{
102+
Description: "A map of request header field names and values regarded as sensitive. " +
103+
"The header values are masked in CLI output, as well as Terraform state.",
104+
ElementType: types.StringType,
105+
Optional: true,
106+
Sensitive: true,
107+
},
108+
101109
"request_body": schema.StringAttribute{
102110
Description: "The request body as a string.",
103111
Optional: true,
@@ -218,6 +226,7 @@ func (d *httpDataSource) Read(ctx context.Context, req datasource.ReadRequest, r
218226
requestURL := model.URL.ValueString()
219227
method := model.Method.ValueString()
220228
requestHeaders := model.RequestHeaders
229+
sensitiveRequestHeaders := model.SensitiveRequestHeaders
221230

222231
if method == "" {
223232
method = "GET"
@@ -350,6 +359,20 @@ func (d *httpDataSource) Read(ctx context.Context, req datasource.ReadRequest, r
350359
}
351360
}
352361

362+
for name, value := range sensitiveRequestHeaders.Elements() {
363+
var header string
364+
diags = tfsdk.ValueAs(ctx, value, &header)
365+
resp.Diagnostics.Append(diags...)
366+
if resp.Diagnostics.HasError() {
367+
return
368+
}
369+
370+
request.Header.Set(name, header)
371+
if strings.ToLower(name) == "host" {
372+
request.Host = header
373+
}
374+
}
375+
353376
response, err := retryClient.Do(request)
354377
if err != nil {
355378
target := &url.Error{}
@@ -416,27 +439,37 @@ func (d *httpDataSource) Read(ctx context.Context, req datasource.ReadRequest, r
416439
model.ResponseBodyBase64 = types.StringValue(responseBodyBase64Std)
417440
model.StatusCode = types.Int64Value(int64(response.StatusCode))
418441

442+
// Override SensitiveRequestHeaders' header values with a mask.
443+
sensitiveHeaders := map[string]string{}
444+
for name, _ := range sensitiveRequestHeaders.Elements() {
445+
sensitiveHeaders[name] = "*****"
446+
}
447+
448+
maskedSensitiveHeaders, _ := types.MapValueFrom(ctx, types.StringType, sensitiveHeaders)
449+
model.SensitiveRequestHeaders = maskedSensitiveHeaders
450+
419451
diags = resp.State.Set(ctx, model)
420452
resp.Diagnostics.Append(diags...)
421453
}
422454

423455
type modelV0 struct {
424-
ID types.String `tfsdk:"id"`
425-
URL types.String `tfsdk:"url"`
426-
Method types.String `tfsdk:"method"`
427-
RequestHeaders types.Map `tfsdk:"request_headers"`
428-
RequestBody types.String `tfsdk:"request_body"`
429-
RequestTimeout types.Int64 `tfsdk:"request_timeout_ms"`
430-
Retry types.Object `tfsdk:"retry"`
431-
ResponseHeaders types.Map `tfsdk:"response_headers"`
432-
CaCertificate types.String `tfsdk:"ca_cert_pem"`
433-
ClientCert types.String `tfsdk:"client_cert_pem"`
434-
ClientKey types.String `tfsdk:"client_key_pem"`
435-
Insecure types.Bool `tfsdk:"insecure"`
436-
ResponseBody types.String `tfsdk:"response_body"`
437-
Body types.String `tfsdk:"body"`
438-
ResponseBodyBase64 types.String `tfsdk:"response_body_base64"`
439-
StatusCode types.Int64 `tfsdk:"status_code"`
456+
ID types.String `tfsdk:"id"`
457+
URL types.String `tfsdk:"url"`
458+
Method types.String `tfsdk:"method"`
459+
RequestHeaders types.Map `tfsdk:"request_headers"`
460+
SensitiveRequestHeaders types.Map `tfsdk:"sensitive_request_headers"`
461+
RequestBody types.String `tfsdk:"request_body"`
462+
RequestTimeout types.Int64 `tfsdk:"request_timeout_ms"`
463+
Retry types.Object `tfsdk:"retry"`
464+
ResponseHeaders types.Map `tfsdk:"response_headers"`
465+
CaCertificate types.String `tfsdk:"ca_cert_pem"`
466+
ClientCert types.String `tfsdk:"client_cert_pem"`
467+
ClientKey types.String `tfsdk:"client_key_pem"`
468+
Insecure types.Bool `tfsdk:"insecure"`
469+
ResponseBody types.String `tfsdk:"response_body"`
470+
Body types.String `tfsdk:"body"`
471+
ResponseBodyBase64 types.String `tfsdk:"response_body_base64"`
472+
StatusCode types.Int64 `tfsdk:"status_code"`
440473
}
441474

442475
type retryModel struct {

internal/provider/data_source_http_test.go

+77
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,83 @@ func TestDataSource_withAuthorizationRequestHeader_200(t *testing.T) {
149149
})
150150
}
151151

152+
func TestDataSource_withSensitiveAuthorizationRequestHeader_200(t *testing.T) {
153+
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
154+
if r.Header.Get("Authorization") == "Zm9vOmJhcg==" {
155+
w.Header().Set("Content-Type", "text/plain")
156+
_, err := w.Write([]byte("1.0.0"))
157+
if err != nil {
158+
t.Errorf("error writing body: %s", err)
159+
}
160+
} else {
161+
w.WriteHeader(http.StatusForbidden)
162+
}
163+
}))
164+
defer testServer.Close()
165+
166+
resource.ParallelTest(t, resource.TestCase{
167+
ProtoV5ProviderFactories: protoV5ProviderFactories(),
168+
Steps: []resource.TestStep{
169+
{
170+
Config: fmt.Sprintf(`
171+
data "http" "http_test" {
172+
url = "%s"
173+
174+
sensitive_request_headers = {
175+
"Authorization" = "Zm9vOmJhcg=="
176+
}
177+
}`, testServer.URL),
178+
Check: resource.ComposeTestCheckFunc(
179+
resource.TestCheckResourceAttr("data.http.http_test", "response_body", "1.0.0"),
180+
resource.TestCheckResourceAttr("data.http.http_test", "status_code", "200"),
181+
resource.TestCheckResourceAttr("data.http.http_test", "sensitive_request_headers.Authorization", "*****"),
182+
),
183+
},
184+
},
185+
})
186+
}
187+
188+
func TestDataSource_withSensitiveAndNotSensitiveRequestHeaders_200(t *testing.T) {
189+
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
190+
if r.Header.Get("Authorization") == "Zm9vOmJhcg==" {
191+
w.Header().Set("Content-Type", "text/plain")
192+
_, err := w.Write([]byte("1.0.0"))
193+
if err != nil {
194+
t.Errorf("error writing body: %s", err)
195+
}
196+
} else {
197+
w.WriteHeader(http.StatusForbidden)
198+
}
199+
}))
200+
defer testServer.Close()
201+
202+
resource.ParallelTest(t, resource.TestCase{
203+
ProtoV5ProviderFactories: protoV5ProviderFactories(),
204+
Steps: []resource.TestStep{
205+
{
206+
Config: fmt.Sprintf(`
207+
data "http" "http_test" {
208+
url = "%s"
209+
210+
request_headers = {
211+
"foo" = "bar"
212+
}
213+
214+
sensitive_request_headers = {
215+
"Authorization" = "Zm9vOmJhcg=="
216+
}
217+
}`, testServer.URL),
218+
Check: resource.ComposeTestCheckFunc(
219+
resource.TestCheckResourceAttr("data.http.http_test", "response_body", "1.0.0"),
220+
resource.TestCheckResourceAttr("data.http.http_test", "status_code", "200"),
221+
resource.TestCheckResourceAttr("data.http.http_test", "request_headers.foo", "bar"),
222+
resource.TestCheckResourceAttr("data.http.http_test", "sensitive_request_headers.Authorization", "*****"),
223+
),
224+
},
225+
},
226+
})
227+
}
228+
152229
func TestDataSource_withAuthorizationRequestHeader_403(t *testing.T) {
153230
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
154231
if r.Header.Get("Authorization") != "Zm9vOmJhcg==" {

0 commit comments

Comments
 (0)