From c30a70aa48e557832b16f4c280388c21185d2058 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Wed, 17 Jul 2024 16:35:06 -0400 Subject: [PATCH 1/9] Add `allow_host_override` attribute that allows the `Host` request header to override the request host. --- internal/provider/data_source_http.go | 13 +++ internal/provider/data_source_http_test.go | 107 +++++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/internal/provider/data_source_http.go b/internal/provider/data_source_http.go index f6cb5508..21600e31 100644 --- a/internal/provider/data_source_http.go +++ b/internal/provider/data_source_http.go @@ -111,6 +111,11 @@ a 5xx-range (except 501) status code is received. For further details see }, }, + "allow_host_override": schema.BoolAttribute{ + Description: "Allows the `Host` header defined in `request_headers` to override the host of the request. Defaults to `false`", + Optional: true, + }, + "response_body": schema.StringAttribute{ Description: "The response body returned as a string.", Computed: true, @@ -317,6 +322,13 @@ func (d *httpDataSource) Read(ctx context.Context, req datasource.ReadRequest, r request.Header.Set(name, header) } + hostHeader := request.Header.Get("Host") + if hostHeader != "" && model.AllowHostOverride.ValueBool() { + request.Host = hostHeader + request.Request.Host = hostHeader + request.URL.Host = hostHeader + } + response, err := retryClient.Do(request) if err != nil { target := &url.Error{} @@ -395,6 +407,7 @@ type modelV0 struct { RequestBody types.String `tfsdk:"request_body"` RequestTimeout types.Int64 `tfsdk:"request_timeout_ms"` Retry types.Object `tfsdk:"retry"` + AllowHostOverride types.Bool `tfsdk:"allow_host_override"` ResponseHeaders types.Map `tfsdk:"response_headers"` CaCertificate types.String `tfsdk:"ca_cert_pem"` Insecure types.Bool `tfsdk:"insecure"` diff --git a/internal/provider/data_source_http_test.go b/internal/provider/data_source_http_test.go index d10dde53..c07f7e44 100644 --- a/internal/provider/data_source_http_test.go +++ b/internal/provider/data_source_http_test.go @@ -602,6 +602,113 @@ func TestDataSource_UnsupportedInsecureCaCert(t *testing.T) { }) } +func TestDataSource_AllowHostOverrideTrue_200(t *testing.T) { + testServerA := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.Header().Set("X-Single", "foo") + w.Header().Add("X-Double", "1") + w.Header().Add("X-Double", "2") + _, err := w.Write([]byte("1.0.0")) + if err != nil { + t.Errorf("error writing body: %s", err) + } + })) + defer testServerA.Close() + + testServerB := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.Header().Set("X-Single", "bar") + w.Header().Add("X-Double", "3") + w.Header().Add("X-Double", "4") + _, err := w.Write([]byte("2.0.0")) + if err != nil { + t.Errorf("error writing body: %s", err) + } + })) + defer testServerB.Close() + + testServerBUrl, err := url.Parse(testServerB.URL) + if err != nil { + t.Errorf("error parsing testServerB URL: %s", err) + } + + resource.ParallelTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` + data "http" "http_test" { + url = "%s" + request_headers = { + "Host" = "%s" + } + allow_host_override = true + }`, testServerA.URL, testServerBUrl.Host), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.http.http_test", "response_body", "2.0.0"), + resource.TestCheckResourceAttr("data.http.http_test", "response_headers.Content-Type", "text/plain"), + resource.TestCheckResourceAttr("data.http.http_test", "response_headers.X-Single", "bar"), + resource.TestCheckResourceAttr("data.http.http_test", "response_headers.X-Double", "3, 4"), + resource.TestCheckResourceAttr("data.http.http_test", "status_code", "200"), + ), + }, + }, + }) +} + +func TestDataSource_AllowHostOverrideFalse_200(t *testing.T) { + testServerA := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.Header().Set("X-Single", "foo") + w.Header().Add("X-Double", "1") + w.Header().Add("X-Double", "2") + _, err := w.Write([]byte("1.0.0")) + if err != nil { + t.Errorf("error writing body: %s", err) + } + })) + defer testServerA.Close() + + testServerB := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.Header().Set("X-Single", "bar") + w.Header().Add("X-Double", "3") + w.Header().Add("X-Double", "4") + _, err := w.Write([]byte("2.0.0")) + if err != nil { + t.Errorf("error writing body: %s", err) + } + })) + defer testServerB.Close() + + testServerBUrl, err := url.Parse(testServerB.URL) + if err != nil { + t.Errorf("error parsing testServerB URL: %s", err) + } + + resource.ParallelTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` + data "http" "http_test" { + url = "%s" + request_headers = { + "Host" = "%s" + } + }`, testServerA.URL, testServerBUrl.Host), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.http.http_test", "response_body", "1.0.0"), + resource.TestCheckResourceAttr("data.http.http_test", "response_headers.Content-Type", "text/plain"), + resource.TestCheckResourceAttr("data.http.http_test", "response_headers.X-Single", "foo"), + resource.TestCheckResourceAttr("data.http.http_test", "response_headers.X-Double", "1, 2"), + resource.TestCheckResourceAttr("data.http.http_test", "status_code", "200"), + ), + }, + }, + }) +} + // testProxiedURL is a hardcoded URL used in acceptance testing where it is // expected that a locally started HTTP proxy will handle the request. // From 13663bee5084bb450fee7134097ee911a3e68bff Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Wed, 17 Jul 2024 16:53:15 -0400 Subject: [PATCH 2/9] Remove duplicate host override --- internal/provider/data_source_http.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/provider/data_source_http.go b/internal/provider/data_source_http.go index 21600e31..6312a973 100644 --- a/internal/provider/data_source_http.go +++ b/internal/provider/data_source_http.go @@ -325,7 +325,6 @@ func (d *httpDataSource) Read(ctx context.Context, req datasource.ReadRequest, r hostHeader := request.Header.Get("Host") if hostHeader != "" && model.AllowHostOverride.ValueBool() { request.Host = hostHeader - request.Request.Host = hostHeader request.URL.Host = hostHeader } From 7e4c7119c99a8dfb1b5a8b88cd2904d159e9b30c Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Wed, 17 Jul 2024 16:58:08 -0400 Subject: [PATCH 3/9] Add changelog entry --- .changes/unreleased/FEATURES-20240717-165642.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changes/unreleased/FEATURES-20240717-165642.yaml diff --git a/.changes/unreleased/FEATURES-20240717-165642.yaml b/.changes/unreleased/FEATURES-20240717-165642.yaml new file mode 100644 index 00000000..5c4155d5 --- /dev/null +++ b/.changes/unreleased/FEATURES-20240717-165642.yaml @@ -0,0 +1,6 @@ +kind: FEATURES +body: 'data-source/http: Add new `allow_host_override` attribute that allows the `Host` + request header to override the http request host.' +time: 2024-07-17T16:56:42.91218-04:00 +custom: + Issue: "440" From 604f3b23712972e3deb02d9c6672236ea8354be6 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Wed, 17 Jul 2024 16:58:36 -0400 Subject: [PATCH 4/9] Update provider documentation --- docs/data-sources/http.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/data-sources/http.md b/docs/data-sources/http.md index 0c89b135..d053f363 100644 --- a/docs/data-sources/http.md +++ b/docs/data-sources/http.md @@ -148,6 +148,7 @@ resource "null_resource" "example" { ### Optional +- `allow_host_override` (Boolean) Allows the `Host` header defined in `request_headers` to override the host of the request. Defaults to `false` - `ca_cert_pem` (String) Certificate data of the Certificate Authority (CA) in [PEM (RFC 1421)](https://datatracker.ietf.org/doc/html/rfc1421) format. - `insecure` (Boolean) Disables verification of the server's certificate chain and hostname. Defaults to `false` - `method` (String) The HTTP Method for the request. Allowed methods are a subset of methods defined in [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-4.3) namely, `GET`, `HEAD`, and `POST`. `POST` support is only intended for read-only URLs, such as submitting a search. From d4b50c488c3ff5f65ac5e7a7868100e1c94b35ea Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Fri, 19 Jul 2024 14:40:34 -0400 Subject: [PATCH 5/9] Remove feature flag for host overrides --- .../unreleased/BUG FIXES-20240719-143606.yaml | 6 ++ .../unreleased/FEATURES-20240717-165642.yaml | 6 -- internal/provider/data_source_http.go | 9 +- internal/provider/data_source_http_test.go | 86 +++---------------- 4 files changed, 18 insertions(+), 89 deletions(-) create mode 100644 .changes/unreleased/BUG FIXES-20240719-143606.yaml delete mode 100644 .changes/unreleased/FEATURES-20240717-165642.yaml diff --git a/.changes/unreleased/BUG FIXES-20240719-143606.yaml b/.changes/unreleased/BUG FIXES-20240719-143606.yaml new file mode 100644 index 00000000..e11a2d87 --- /dev/null +++ b/.changes/unreleased/BUG FIXES-20240719-143606.yaml @@ -0,0 +1,6 @@ +kind: BUG FIXES +body: 'data-source/http: allow the `Host` request header to be sent as the http request + host.' +time: 2024-07-19T14:36:06.795798-04:00 +custom: + Issue: "440" diff --git a/.changes/unreleased/FEATURES-20240717-165642.yaml b/.changes/unreleased/FEATURES-20240717-165642.yaml deleted file mode 100644 index 5c4155d5..00000000 --- a/.changes/unreleased/FEATURES-20240717-165642.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: FEATURES -body: 'data-source/http: Add new `allow_host_override` attribute that allows the `Host` - request header to override the http request host.' -time: 2024-07-17T16:56:42.91218-04:00 -custom: - Issue: "440" diff --git a/internal/provider/data_source_http.go b/internal/provider/data_source_http.go index 6312a973..98da4938 100644 --- a/internal/provider/data_source_http.go +++ b/internal/provider/data_source_http.go @@ -111,11 +111,6 @@ a 5xx-range (except 501) status code is received. For further details see }, }, - "allow_host_override": schema.BoolAttribute{ - Description: "Allows the `Host` header defined in `request_headers` to override the host of the request. Defaults to `false`", - Optional: true, - }, - "response_body": schema.StringAttribute{ Description: "The response body returned as a string.", Computed: true, @@ -323,9 +318,8 @@ func (d *httpDataSource) Read(ctx context.Context, req datasource.ReadRequest, r } hostHeader := request.Header.Get("Host") - if hostHeader != "" && model.AllowHostOverride.ValueBool() { + if hostHeader != "" { request.Host = hostHeader - request.URL.Host = hostHeader } response, err := retryClient.Do(request) @@ -406,7 +400,6 @@ type modelV0 struct { RequestBody types.String `tfsdk:"request_body"` RequestTimeout types.Int64 `tfsdk:"request_timeout_ms"` Retry types.Object `tfsdk:"retry"` - AllowHostOverride types.Bool `tfsdk:"allow_host_override"` ResponseHeaders types.Map `tfsdk:"response_headers"` CaCertificate types.String `tfsdk:"ca_cert_pem"` Insecure types.Bool `tfsdk:"insecure"` diff --git a/internal/provider/data_source_http_test.go b/internal/provider/data_source_http_test.go index c07f7e44..e4dfbab4 100644 --- a/internal/provider/data_source_http_test.go +++ b/internal/provider/data_source_http_test.go @@ -602,64 +602,17 @@ func TestDataSource_UnsupportedInsecureCaCert(t *testing.T) { }) } -func TestDataSource_AllowHostOverrideTrue_200(t *testing.T) { - testServerA := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/plain") - w.Header().Set("X-Single", "foo") - w.Header().Add("X-Double", "1") - w.Header().Add("X-Double", "2") - _, err := w.Write([]byte("1.0.0")) - if err != nil { - t.Errorf("error writing body: %s", err) - } - })) - defer testServerA.Close() +func TestDataSource_HostRequestHeaderOverride_200(t *testing.T) { + altHost := "alt-test-host" - testServerB := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/plain") - w.Header().Set("X-Single", "bar") - w.Header().Add("X-Double", "3") - w.Header().Add("X-Double", "4") - _, err := w.Write([]byte("2.0.0")) - if err != nil { - t.Errorf("error writing body: %s", err) + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Host != altHost { + w.WriteHeader(404) + return } - })) - defer testServerB.Close() - - testServerBUrl, err := url.Parse(testServerB.URL) - if err != nil { - t.Errorf("error parsing testServerB URL: %s", err) - } - - resource.ParallelTest(t, resource.TestCase{ - ProtoV5ProviderFactories: protoV5ProviderFactories(), - Steps: []resource.TestStep{ - { - Config: fmt.Sprintf(` - data "http" "http_test" { - url = "%s" - request_headers = { - "Host" = "%s" - } - allow_host_override = true - }`, testServerA.URL, testServerBUrl.Host), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("data.http.http_test", "response_body", "2.0.0"), - resource.TestCheckResourceAttr("data.http.http_test", "response_headers.Content-Type", "text/plain"), - resource.TestCheckResourceAttr("data.http.http_test", "response_headers.X-Single", "bar"), - resource.TestCheckResourceAttr("data.http.http_test", "response_headers.X-Double", "3, 4"), - resource.TestCheckResourceAttr("data.http.http_test", "status_code", "200"), - ), - }, - }, - }) -} -func TestDataSource_AllowHostOverrideFalse_200(t *testing.T) { - testServerA := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") - w.Header().Set("X-Single", "foo") + w.Header().Set("X-Single", "foobar") w.Header().Add("X-Double", "1") w.Header().Add("X-Double", "2") _, err := w.Write([]byte("1.0.0")) @@ -667,24 +620,7 @@ func TestDataSource_AllowHostOverrideFalse_200(t *testing.T) { t.Errorf("error writing body: %s", err) } })) - defer testServerA.Close() - - testServerB := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/plain") - w.Header().Set("X-Single", "bar") - w.Header().Add("X-Double", "3") - w.Header().Add("X-Double", "4") - _, err := w.Write([]byte("2.0.0")) - if err != nil { - t.Errorf("error writing body: %s", err) - } - })) - defer testServerB.Close() - - testServerBUrl, err := url.Parse(testServerB.URL) - if err != nil { - t.Errorf("error parsing testServerB URL: %s", err) - } + defer testServer.Close() resource.ParallelTest(t, resource.TestCase{ ProtoV5ProviderFactories: protoV5ProviderFactories(), @@ -696,13 +632,13 @@ func TestDataSource_AllowHostOverrideFalse_200(t *testing.T) { request_headers = { "Host" = "%s" } - }`, testServerA.URL, testServerBUrl.Host), + }`, testServer.URL, altHost), Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.http.http_test", "status_code", "200"), resource.TestCheckResourceAttr("data.http.http_test", "response_body", "1.0.0"), resource.TestCheckResourceAttr("data.http.http_test", "response_headers.Content-Type", "text/plain"), - resource.TestCheckResourceAttr("data.http.http_test", "response_headers.X-Single", "foo"), + resource.TestCheckResourceAttr("data.http.http_test", "response_headers.X-Single", "foobar"), resource.TestCheckResourceAttr("data.http.http_test", "response_headers.X-Double", "1, 2"), - resource.TestCheckResourceAttr("data.http.http_test", "status_code", "200"), ), }, }, From 79c0d43a25232994beb57190c2e7fd3cde6bd43f Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Fri, 19 Jul 2024 14:43:27 -0400 Subject: [PATCH 6/9] Update documentation --- docs/data-sources/http.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/data-sources/http.md b/docs/data-sources/http.md index d053f363..0c89b135 100644 --- a/docs/data-sources/http.md +++ b/docs/data-sources/http.md @@ -148,7 +148,6 @@ resource "null_resource" "example" { ### Optional -- `allow_host_override` (Boolean) Allows the `Host` header defined in `request_headers` to override the host of the request. Defaults to `false` - `ca_cert_pem` (String) Certificate data of the Certificate Authority (CA) in [PEM (RFC 1421)](https://datatracker.ietf.org/doc/html/rfc1421) format. - `insecure` (Boolean) Disables verification of the server's certificate chain and hostname. Defaults to `false` - `method` (String) The HTTP Method for the request. Allowed methods are a subset of methods defined in [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-4.3) namely, `GET`, `HEAD`, and `POST`. `POST` support is only intended for read-only URLs, such as submitting a search. From 43ee91eebf11dcd58f72cd83b758c2a3d2abee29 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 23 Jul 2024 15:44:18 -0400 Subject: [PATCH 7/9] Apply suggestions from code review Co-authored-by: Austin Valle --- .changes/unreleased/BUG FIXES-20240719-143606.yaml | 3 +-- internal/provider/data_source_http_test.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.changes/unreleased/BUG FIXES-20240719-143606.yaml b/.changes/unreleased/BUG FIXES-20240719-143606.yaml index e11a2d87..e3ddbc9b 100644 --- a/.changes/unreleased/BUG FIXES-20240719-143606.yaml +++ b/.changes/unreleased/BUG FIXES-20240719-143606.yaml @@ -1,6 +1,5 @@ kind: BUG FIXES -body: 'data-source/http: allow the `Host` request header to be sent as the http request - host.' +body: 'data-source/http: Allow `Host` header in `request_headers` to be set on HTTP request' time: 2024-07-19T14:36:06.795798-04:00 custom: Issue: "440" diff --git a/internal/provider/data_source_http_test.go b/internal/provider/data_source_http_test.go index e4dfbab4..8ef7fc70 100644 --- a/internal/provider/data_source_http_test.go +++ b/internal/provider/data_source_http_test.go @@ -607,7 +607,7 @@ func TestDataSource_HostRequestHeaderOverride_200(t *testing.T) { testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Host != altHost { - w.WriteHeader(404) + w.WriteHeader(400) return } From 10edeacff705571f87a0790b89ff93096d9fc475 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 23 Jul 2024 16:02:24 -0400 Subject: [PATCH 8/9] Set `request.Host` in the same loop setting the request headers. --- internal/provider/data_source_http.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/internal/provider/data_source_http.go b/internal/provider/data_source_http.go index 98da4938..ee81b9d6 100644 --- a/internal/provider/data_source_http.go +++ b/internal/provider/data_source_http.go @@ -315,11 +315,9 @@ func (d *httpDataSource) Read(ctx context.Context, req datasource.ReadRequest, r } request.Header.Set(name, header) - } - - hostHeader := request.Header.Get("Host") - if hostHeader != "" { - request.Host = hostHeader + if name == "Host" { + request.Host = header + } } response, err := retryClient.Do(request) From 902ea063706db4e5ce108f3442d4e1fc7bc98186 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 23 Jul 2024 17:15:35 -0400 Subject: [PATCH 9/9] Add note changelog describing functionality change. --- .changes/unreleased/NOTES-20240723-162543.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .changes/unreleased/NOTES-20240723-162543.yaml diff --git a/.changes/unreleased/NOTES-20240723-162543.yaml b/.changes/unreleased/NOTES-20240723-162543.yaml new file mode 100644 index 00000000..b9029ce0 --- /dev/null +++ b/.changes/unreleased/NOTES-20240723-162543.yaml @@ -0,0 +1,17 @@ +kind: NOTES +body: |+ + data-source/http: Previous versions of this provider ignored any `Host` headers specified in the `request_headers` attribute when setting the HTTP request. Any specified `Host` request headers will now override the `url` attribute host. + + For example, in the following configuration: + ```hcl + data "http" "example" { + url = "https://www.example.com" + request_headers = { + Host = "www.differentexample.com" + } + } + ``` + The HTTP request host will now be `www.differentexample.com` instead of `www.example.com`. +time: 2024-07-23T16:25:43.160519-04:00 +custom: + Issue: "440"