Skip to content

Commit

Permalink
feat(configuration): unix socket listener subpath (#6463)
Browse files Browse the repository at this point in the history
Adds a subpath configuration query parameter to unix sockets and other listeners.

Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
  • Loading branch information
james-d-elliott committed Dec 27, 2023
1 parent 4ca584a commit 7a97373
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 16 deletions.
15 changes: 12 additions & 3 deletions docs/content/en/configuration/prologue/common.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,10 @@ are no optional portions.
The Unix Domain Socket format also accepts a query string. The following query parameters control certain behaviour of
this address type.

| Parameter | Listeners | Connectors | Purpose |
|:---------:|:---------:|:----------:|:------------------------------------------------------------------------------------------------------------------------------------:|
| `umask` | Yes | No | Sets the umask prior to creating the socket and restores it after creating it. The value must be an octal number with 3 or 4 digits. |
| Parameter | Listeners | Connectors | Purpose |
|:---------:|:---------:|:----------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
| `umask` | Yes | No | Sets the umask prior to creating the socket and restores it after creating it. The value must be an octal number with 3 or 4 digits. |
| `path` | Yes | No | Sets the path variable to configure the subpath, specifically for a unix socket but technically works for TCP as well. Note that this should just be the alphanumeric portion it should not be prefixed with a forward slash |


```text
Expand All @@ -124,6 +125,14 @@ unix://<path>
unix://<path>?umask=0022
```

```text
unix://<path>?path=auth
```

```text
unix://<path>?umask=0022&path=auth
```

##### Examples

Various examples for these formats.
Expand Down
16 changes: 15 additions & 1 deletion internal/configuration/schema/types_address.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func (AddressTCP) JSONSchema() *jsonschema.Schema {
return &jsonschema.Schema{
Type: jsonschema.TypeString,
Format: "uri",
Pattern: `^((tcp(4|6)?:\/\/)?([^:\/]*(:\d+)|[^:\/]+(:\d+)?)(\/.*)?|unix:\/\/\/[^?\n]+(\?umask=[0-7]{3,4})?)$`,
Pattern: `^((tcp(4|6)?:\/\/)?([^:\/]*(:\d+)|[^:\/]+(:\d+)?)(\/.*)?|unix:\/\/\/[^?\n]+(\?(umask=[0-7]{3,4}|path=[a-z]+)(&(umask=[0-7]{3,4}|path=[a-zA-Z0-9.~_-]+))?)?)$`,
}
}

Expand Down Expand Up @@ -382,6 +382,20 @@ func (a *Address) Path() string {
return a.url.Path
}

// RouterPath returns the path the server router uses for serving up requests. Should be the same as Path unless the
// path query parameter has been set.
func (a *Address) RouterPath() string {
if !a.valid || a.url == nil {
return ""
}

if a.url.Query().Has("path") {
return fmt.Sprintf("/%s", a.url.Query().Get("path"))
}

return a.url.Path
}

// SetPath sets the path.
func (a *Address) SetPath(path string) {
if !a.valid || a.url == nil {
Expand Down
52 changes: 52 additions & 0 deletions internal/configuration/schema/types_address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,7 @@ func TestAddress_UnixDomainSocket(t *testing.T) {
have string
socket bool
path string
rpath string
strUmask string
umask int
err string
Expand All @@ -624,6 +625,7 @@ func TestAddress_UnixDomainSocket(t *testing.T) {
false,
"",
"",
"",
-1,
"",
},
Expand All @@ -632,6 +634,7 @@ func TestAddress_UnixDomainSocket(t *testing.T) {
fmt.Sprintf("unix://%s", filepath.Join(dir, "example.sock")),
true,
filepath.Join(dir, "example.sock"),
filepath.Join(dir, "example.sock"),
"",
-1,
"",
Expand All @@ -641,6 +644,17 @@ func TestAddress_UnixDomainSocket(t *testing.T) {
fmt.Sprintf("unix://%s?umask=0022", filepath.Join(dir, "example.sock")),
true,
filepath.Join(dir, "example.sock"),
filepath.Join(dir, "example.sock"),
"0022",
18,
"",
},
{
"ShouldParseSocketWithUmaskAndPath",
fmt.Sprintf("unix://%s?umask=0022&path=abc", filepath.Join(dir, "example.sock")),
true,
filepath.Join(dir, "example.sock"),
"/abc",
"0022",
18,
"",
Expand All @@ -651,6 +665,7 @@ func TestAddress_UnixDomainSocket(t *testing.T) {
true,
"",
"",
"",
-1,
fmt.Sprintf("error validating the unix socket address: could not parse address 'unix://%s?umask=abc': the address has a umask value of 'abc' which does not appear to be a valid octal string", filepath.Join(dir, "example.sock")),
},
Expand All @@ -661,8 +676,10 @@ func TestAddress_UnixDomainSocket(t *testing.T) {
actual, err := NewAddress(tc.have)

if tc.err == "" {
require.NoError(t, err)
assert.Equal(t, tc.socket, actual.IsUnixDomainSocket())
assert.Equal(t, tc.path, actual.Path())
assert.Equal(t, tc.rpath, actual.RouterPath())
assert.Equal(t, tc.strUmask, actual.Umask())
assert.Equal(t, tc.umask, actual.umask)

Expand Down Expand Up @@ -749,6 +766,41 @@ func TestAddress_Path(t *testing.T) {
}
}

func TestAddress_RouterPath(t *testing.T) {
testCases := []struct {
name string
have Address
expected string
}{
{
"ShouldReturnEmptyPath",
Address{true, false, -1, 80, &url.URL{Scheme: AddressSchemeTCP, Host: "tcphosta"}},
"",
},
{
"ShouldReturnPath",
Address{true, false, -1, 80, &url.URL{Scheme: AddressSchemeTCP, Host: "tcphosta", Path: "/apath"}},
"/apath",
},
{
"ShouldNotReturnPathInvalid",
Address{false, false, -1, 80, &url.URL{Scheme: AddressSchemeTCP, Host: "tcphosta", Path: "/apath"}},
"",
},
{
"ShouldNotReturnPathNil",
Address{true, false, -1, 80, nil},
"",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, tc.have.RouterPath())
})
}
}

func TestAddress_IsTCP_IsUDP(t *testing.T) {
testCases := []struct {
name string
Expand Down
8 changes: 4 additions & 4 deletions internal/configuration/validator/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,11 @@ func ValidateServerAddress(config *schema.Configuration, validator *schema.Struc
}
}

switch path := config.Server.Address.Path(); {
case path == "":
switch subpath := config.Server.Address.RouterPath(); {
case subpath == "":
config.Server.Address.SetPath("/")
case path != "/" && strings.HasSuffix(path, "/"):
validator.Push(fmt.Errorf(errFmtServerPathNotEndForwardSlash, path))
case subpath != "/" && strings.HasSuffix(subpath, "/"):
validator.Push(fmt.Errorf(errFmtServerPathNotEndForwardSlash, subpath))
}
}

Expand Down
2 changes: 1 addition & 1 deletion internal/configuration/validator/telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func ValidateTelemetry(config *schema.Configuration, validator *schema.StructVal
config.Telemetry.Metrics.Address.SetPort(schema.DefaultTelemetryConfig.Metrics.Address.Port())
}

if config.Telemetry.Metrics.Address.Path() == "" {
if config.Telemetry.Metrics.Address.RouterPath() == "" {
config.Telemetry.Metrics.Address.SetPath(schema.DefaultTelemetryConfig.Metrics.Address.Path())
}

Expand Down
4 changes: 2 additions & 2 deletions internal/server/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,8 +400,8 @@ func handleRouter(config *schema.Configuration, providers middlewares.Providers)
r.NotFound = handleNotFound(bridge(serveIndexHandler))

handler := middlewares.LogRequest(r.Handler)
if config.Server.Address.Path() != "/" {
handler = middlewares.StripPath(config.Server.Address.Path())(handler)
if config.Server.Address.RouterPath() != "/" {
handler = middlewares.StripPath(config.Server.Address.RouterPath())(handler)
}

handler = middlewares.Wrap(middlewares.NewMetricsRequest(providers.Metrics), handler)
Expand Down
10 changes: 5 additions & 5 deletions internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,17 @@ func CreateDefaultServer(config *schema.Configuration, providers middlewares.Pro
}

if err = writeHealthCheckEnv(config.Server.DisableHealthcheck, connectionScheme, config.Server.Address.Hostname(),
config.Server.Address.Path(), config.Server.Address.Port()); err != nil {
config.Server.Address.RouterPath(), config.Server.Address.Port()); err != nil {
return nil, nil, nil, false, fmt.Errorf("unable to configure healthcheck: %w", err)
}

paths = []string{"/"}

switch config.Server.Address.Path() {
switch config.Server.Address.RouterPath() {
case "/", "":
break
default:
paths = append(paths, config.Server.Address.Path())
paths = append(paths, config.Server.Address.RouterPath())
}

return server, listener, paths, isTLS, nil
Expand All @@ -96,7 +96,7 @@ func CreateMetricsServer(config *schema.Configuration, providers middlewares.Pro
server = &fasthttp.Server{
ErrorHandler: handleError("telemetry.metrics"),
NoDefaultServerHeader: true,
Handler: handleMetrics(config.Telemetry.Metrics.Address.Path()),
Handler: handleMetrics(config.Telemetry.Metrics.Address.RouterPath()),
ReadBufferSize: config.Telemetry.Metrics.Buffers.Read,
WriteBufferSize: config.Telemetry.Metrics.Buffers.Write,
ReadTimeout: config.Telemetry.Metrics.Timeouts.Read,
Expand All @@ -109,5 +109,5 @@ func CreateMetricsServer(config *schema.Configuration, providers middlewares.Pro
return nil, nil, nil, false, fmt.Errorf("error occurred while attempting to initialize metrics telemetry server listener for address '%s': %w", config.Telemetry.Metrics.Address.String(), err)
}

return server, listener, []string{config.Telemetry.Metrics.Address.Path()}, false, nil
return server, listener, []string{config.Telemetry.Metrics.Address.RouterPath()}, false, nil
}

0 comments on commit 7a97373

Please sign in to comment.