Skip to content

Commit fc78b73

Browse files
committed
Fix double-slash URL path bug in 74 GetRequest methods (#650)
When optional path segments (Indices, Index, Repo, etc.) are empty, path construction produced "//" which http.NewRequest misparses per RFC 3986, treating the next segment as the URL authority. After the transport overwrites the host, the path loses its API prefix, causing requests to hit the wrong endpoint. Extract opensearch.BuildPath helper that skips empty segments (1 alloc, ~21ns — benchmarked against path.Join's 3 allocs, ~63ns). Convert all 74 vulnerable GetRequest methods across opensearchapi, plugins/security, and plugins/ism to use it. Fixes: #617, #650 Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
1 parent 2b426d2 commit fc78b73

80 files changed

Lines changed: 110 additions & 254 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
149149
- Extract `newMultiServerPoolFromClientWithLock` as single source of truth for Client-to-pool settings propagation ([#786](https://github.com/opensearch-project/opensearch-go/pull/786))
150150
- Skip shard routing integration tests on OpenSearch < 2.2.0 with security plugin due to server-side `OptionalDataException` from non-thread-safe User serialization (opensearch-project/security#1970)
151151
- Fix alias, mapping, settings, and block API URL path construction when Indices is empty, which caused `http.NewRequest` to misparse the double-slash as an authority separator ([#650](https://github.com/opensearch-project/opensearch-go/issues/650))
152+
- Fix URL path construction across 74 `GetRequest` methods where empty path segments produced a double-slash `//` that `http.NewRequest` misparsed as an RFC 3986 authority separator; extract `opensearch.BuildPath` helper to prevent the class of bug ([#617](https://github.com/opensearch-project/opensearch-go/issues/617), [#650](https://github.com/opensearch-project/opensearch-go/issues/650))
152153
- Fix discovery pool wipe when all cluster nodes time out during `/_nodes/http` fan-out: parse `_nodes` metadata envelope and return `errDiscoveryEmpty` when `successful == 0`, preserving the existing connection pool for retry ([#821](https://github.com/opensearch-project/opensearch-go/pull/821))
153154
- Fix flaky `TestDefaultHealthCheck_RetryAfterMaxRetry`: replace wall-clock `time.Sleep` + `atomic.Int64` synchronization with context cancellation (`ctx.Done()`), and widen `maxRetryClusterHealth` to 5s so the baseline HTTP round-trip cannot race past the retry interval ([#787](https://github.com/opensearch-project/opensearch-go/pull/787))
154155
- Fix connection lifecycle bug in multiServerPool.OnFailure where connections were scheduled for resurrection before being moved from ready to dead list, causing potential race conditions

opensearchapi/api_cat-snapshots.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
package opensearchapi
88

99
import (
10-
"fmt"
1110
"net/http"
1211

1312
"github.com/opensearch-project/opensearch-go/v4"
@@ -24,7 +23,7 @@ type CatSnapshotsReq struct {
2423
func (r CatSnapshotsReq) GetRequest() (*http.Request, error) {
2524
return opensearch.BuildRequest(
2625
"GET",
27-
fmt.Sprintf("%s%s", "/_cat/snapshots/", r.Repository),
26+
opensearch.BuildPath("_cat", "snapshots", r.Repository),
2827
nil,
2928
r.Params.get(),
3029
r.Header,

opensearchapi/api_cluster-decommission.go

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ package opensearchapi
88

99
import (
1010
"net/http"
11-
"strings"
1211

1312
"github.com/opensearch-project/opensearch-go/v4"
1413
)
@@ -24,16 +23,9 @@ type ClusterPutDecommissionReq struct {
2423

2524
// GetRequest returns the *http.Request that gets executed by the client
2625
func (r ClusterPutDecommissionReq) GetRequest() (*http.Request, error) {
27-
var path strings.Builder
28-
path.Grow(34 + len(r.AwarenessAttrName) + len(r.AwarenessAttrValue))
29-
path.WriteString("/_cluster/decommission/awareness/")
30-
path.WriteString(r.AwarenessAttrName)
31-
path.WriteString("/")
32-
path.WriteString(r.AwarenessAttrValue)
33-
3426
return opensearch.BuildRequest(
3527
"PUT",
36-
path.String(),
28+
opensearch.BuildPath("_cluster", "decommission", "awareness", r.AwarenessAttrName, r.AwarenessAttrValue),
3729
nil,
3830
r.Params.get(),
3931
r.Header,
@@ -89,15 +81,9 @@ type ClusterGetDecommissionReq struct {
8981

9082
// GetRequest returns the *http.Request that gets executed by the client
9183
func (r ClusterGetDecommissionReq) GetRequest() (*http.Request, error) {
92-
var path strings.Builder
93-
path.Grow(41 + len(r.AwarenessAttrName))
94-
path.WriteString("/_cluster/decommission/awareness/")
95-
path.WriteString(r.AwarenessAttrName)
96-
path.WriteString("/_status")
97-
9884
return opensearch.BuildRequest(
9985
"GET",
100-
path.String(),
86+
opensearch.BuildPath("_cluster", "decommission", "awareness", r.AwarenessAttrName, "_status"),
10187
nil,
10288
r.Params.get(),
10389
r.Header,

opensearchapi/api_component_template-create.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
package opensearchapi
88

99
import (
10-
"fmt"
1110
"io"
1211
"net/http"
1312

@@ -28,7 +27,7 @@ type ComponentTemplateCreateReq struct {
2827
func (r ComponentTemplateCreateReq) GetRequest() (*http.Request, error) {
2928
return opensearch.BuildRequest(
3029
"PUT",
31-
fmt.Sprintf("/_component_template/%s", r.ComponentTemplate),
30+
opensearch.BuildPath("_component_template", r.ComponentTemplate),
3231
r.Body,
3332
r.Params.get(),
3433
r.Header,

opensearchapi/api_component_template-delete.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
package opensearchapi
88

99
import (
10-
"fmt"
1110
"net/http"
1211

1312
"github.com/opensearch-project/opensearch-go/v4"
@@ -25,7 +24,7 @@ type ComponentTemplateDeleteReq struct {
2524
func (r ComponentTemplateDeleteReq) GetRequest() (*http.Request, error) {
2625
return opensearch.BuildRequest(
2726
"DELETE",
28-
fmt.Sprintf("/_component_template/%s", r.ComponentTemplate),
27+
opensearch.BuildPath("_component_template", r.ComponentTemplate),
2928
nil,
3029
r.Params.get(),
3130
r.Header,

opensearchapi/api_component_template-exists.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
package opensearchapi
88

99
import (
10-
"fmt"
1110
"net/http"
1211

1312
"github.com/opensearch-project/opensearch-go/v4"
@@ -25,7 +24,7 @@ type ComponentTemplateExistsReq struct {
2524
func (r ComponentTemplateExistsReq) GetRequest() (*http.Request, error) {
2625
return opensearch.BuildRequest(
2726
"HEAD",
28-
fmt.Sprintf("/_component_template/%s", r.ComponentTemplate),
27+
opensearch.BuildPath("_component_template", r.ComponentTemplate),
2928
nil,
3029
r.Params.get(),
3130
r.Header,

opensearchapi/api_dangling-delete.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
package opensearchapi
88

99
import (
10-
"fmt"
1110
"net/http"
1211

1312
"github.com/opensearch-project/opensearch-go/v4"
@@ -25,7 +24,7 @@ type DanglingDeleteReq struct {
2524
func (r DanglingDeleteReq) GetRequest() (*http.Request, error) {
2625
return opensearch.BuildRequest(
2726
"DELETE",
28-
fmt.Sprintf("/_dangling/%s", r.IndexUUID),
27+
opensearch.BuildPath("_dangling", r.IndexUUID),
2928
nil,
3029
r.Params.get(),
3130
r.Header,

opensearchapi/api_dangling-import.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
package opensearchapi
88

99
import (
10-
"fmt"
1110
"net/http"
1211

1312
"github.com/opensearch-project/opensearch-go/v4"
@@ -25,7 +24,7 @@ type DanglingImportReq struct {
2524
func (r DanglingImportReq) GetRequest() (*http.Request, error) {
2625
return opensearch.BuildRequest(
2726
"POST",
28-
fmt.Sprintf("/_dangling/%s", r.IndexUUID),
27+
opensearch.BuildPath("_dangling", r.IndexUUID),
2928
nil,
3029
r.Params.get(),
3130
r.Header,

opensearchapi/api_datastream-create.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
package opensearchapi
88

99
import (
10-
"fmt"
1110
"net/http"
1211

1312
"github.com/opensearch-project/opensearch-go/v4"
@@ -25,7 +24,7 @@ type DataStreamCreateReq struct {
2524
func (r DataStreamCreateReq) GetRequest() (*http.Request, error) {
2625
return opensearch.BuildRequest(
2726
"PUT",
28-
fmt.Sprintf("/_data_stream/%s", r.DataStream),
27+
opensearch.BuildPath("_data_stream", r.DataStream),
2928
nil,
3029
r.Params.get(),
3130
r.Header,

opensearchapi/api_datastream-delete.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
package opensearchapi
88

99
import (
10-
"fmt"
1110
"net/http"
1211

1312
"github.com/opensearch-project/opensearch-go/v4"
@@ -25,7 +24,7 @@ type DataStreamDeleteReq struct {
2524
func (r DataStreamDeleteReq) GetRequest() (*http.Request, error) {
2625
return opensearch.BuildRequest(
2726
"DELETE",
28-
fmt.Sprintf("/_data_stream/%s", r.DataStream),
27+
opensearch.BuildPath("_data_stream", r.DataStream),
2928
nil,
3029
r.Params.get(),
3130
r.Header,

0 commit comments

Comments
 (0)