Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unix Domain Socket support for upstreams and downstreams #9981

Merged
merged 7 commits into from
May 4, 2021
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
3 changes: 3 additions & 0 deletions .changelog/9981.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
connect: add support for unix domain sockets addresses for service upstreams and downstreams
```
4 changes: 2 additions & 2 deletions agent/agent_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ func TestAgent_Service(t *testing.T) {
Service: "web-sidecar-proxy",
Port: 8000,
Proxy: expectProxy.ToAPI(),
ContentHash: "eb557bc310d4f8a0",
ContentHash: "35ad6dd5b1ff8d18",
Weights: api.AgentWeights{
Passing: 1,
Warning: 1,
Expand All @@ -413,7 +413,7 @@ func TestAgent_Service(t *testing.T) {
// Copy and modify
updatedResponse := *expectedResponse
updatedResponse.Port = 9999
updatedResponse.ContentHash = "d61c11f438c7eb02"
updatedResponse.ContentHash = "8e407e299ec9eba"

// Simple response for non-proxy service registered in TestAgent config
expectWebResponse := &api.AgentService{
Expand Down
25 changes: 25 additions & 0 deletions agent/config/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -1640,6 +1641,14 @@ func (b *builder) serviceVal(v *ServiceDefinition) *structs.ServiceDefinition {
if err := structs.ValidateWeights(serviceWeights); err != nil {
b.err = multierror.Append(fmt.Errorf("Invalid weight definition for service %s: %s", stringVal(v.Name), err))
}

if (v.Port != nil || v.Address != nil) && (v.SocketPath != nil) {
b.err = multierror.Append(
fmt.Errorf("service %s cannot have both socket path %s and address/port",
stringVal(v.Name), stringVal(v.SocketPath)), b.err)

}

return &structs.ServiceDefinition{
Kind: kind,
ID: stringVal(v.ID),
Expand All @@ -1649,6 +1658,7 @@ func (b *builder) serviceVal(v *ServiceDefinition) *structs.ServiceDefinition {
TaggedAddresses: b.svcTaggedAddresses(v.TaggedAddresses),
Meta: meta,
Port: intVal(v.Port),
SocketPath: stringVal(v.SocketPath),
Token: stringVal(v.Token),
EnableTagOverride: boolVal(v.EnableTagOverride),
Weights: serviceWeights,
Expand Down Expand Up @@ -1687,6 +1697,7 @@ func (b *builder) serviceProxyVal(v *ServiceProxy) *structs.ConnectProxyConfig {
DestinationServiceID: stringVal(v.DestinationServiceID),
LocalServiceAddress: stringVal(v.LocalServiceAddress),
LocalServicePort: intVal(v.LocalServicePort),
LocalServiceSocketPath: stringVal(&v.LocalServiceSocketPath),
Config: v.Config,
Upstreams: b.upstreamsVal(v.Upstreams),
MeshGateway: b.meshGatewayConfVal(v.MeshGateway),
Expand All @@ -1706,6 +1717,8 @@ func (b *builder) upstreamsVal(v []Upstream) structs.Upstreams {
Datacenter: stringVal(u.Datacenter),
LocalBindAddress: stringVal(u.LocalBindAddress),
LocalBindPort: intVal(u.LocalBindPort),
LocalBindSocketPath: stringVal(u.LocalBindSocketPath),
LocalBindSocketMode: b.unixPermissionsVal("local_bind_socket_mode", u.LocalBindSocketMode),
Config: u.Config,
MeshGateway: b.meshGatewayConfVal(u.MeshGateway),
}
Expand Down Expand Up @@ -1892,6 +1905,18 @@ func uint64Val(v *uint64) uint64 {
return *v
}

// Expect an octal permissions string, e.g. 0644
func (b *builder) unixPermissionsVal(name string, v *string) string {
if v == nil {
return ""
}
if _, err := strconv.ParseUint(*v, 8, 32); err == nil {
return *v
}
b.err = multierror.Append(b.err, fmt.Errorf("%s: invalid mode: %s", name, *v))
return "0"
}

func (b *builder) portVal(name string, v *int) int {
if v == nil || *v <= 0 {
return -1
Expand Down
24 changes: 24 additions & 0 deletions agent/config/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,30 @@ func TestBuilder_BuildAndValidate_NodeName(t *testing.T) {
}
}

func TestBuilder_unixPermissionsVal(t *testing.T) {

b, _ := newBuilder(LoadOpts{
FlagValues: Config{
NodeName: pString("foo"),
DataDir: pString("dir"),
},
})

goodmode := "666"
badmode := "9666"

patchLoadOptsShims(&b.opts)
require.NoError(t, b.err)
_ = b.unixPermissionsVal("local_bind_socket_mode", &goodmode)
require.NoError(t, b.err)
require.Len(t, b.Warnings, 0)

_ = b.unixPermissionsVal("local_bind_socket_mode", &badmode)
require.NotNil(t, b.err)
require.Contains(t, b.err.Error(), "local_bind_socket_mode: invalid mode")
require.Len(t, b.Warnings, 0)
}

func patchLoadOptsShims(opts *LoadOpts) {
if opts.hostname == nil {
opts.hostname = func() (string, error) {
Expand Down
14 changes: 13 additions & 1 deletion agent/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ type ServiceDefinition struct {
TaggedAddresses map[string]ServiceAddress `mapstructure:"tagged_addresses"`
Meta map[string]string `mapstructure:"meta"`
Port *int `mapstructure:"port"`
SocketPath *string `mapstructure:"socket_path"`
Check *CheckDefinition `mapstructure:"check"`
Checks []CheckDefinition `mapstructure:"checks"`
Token *string `mapstructure:"token"`
Expand Down Expand Up @@ -461,6 +462,10 @@ type ServiceProxy struct {
// (DestinationServiceID is set) but otherwise will be ignored.
LocalServicePort *int `mapstructure:"local_service_port"`

// LocalServiceSocketPath is the socket of the local service instance. It is optional
// and should only be specified for "side-car" style proxies.
LocalServiceSocketPath string `mapstructure:"local_service_socket_path"`

// TransparentProxy configuration.
TransparentProxy *TransparentProxyConfig `mapstructure:"transparent_proxy"`

Expand Down Expand Up @@ -503,14 +508,21 @@ type Upstream struct {
// datacenter.
Datacenter *string `mapstructure:"datacenter"`

// It would be worth thinking about a separate structure for these four items,
// unifying under address as something like "unix:/tmp/foo", "tcp:localhost:80" could make sense
// LocalBindAddress is the ip address a side-car proxy should listen on for
// traffic destined for this upstream service. Default if empty is 127.0.0.1.
// traffic destined for this upstream service. Default if empty and local bind socket
// is not present is 127.0.0.1.
LocalBindAddress *string `mapstructure:"local_bind_address"`

// LocalBindPort is the ip address a side-car proxy should listen on for traffic
// destined for this upstream service. Required.
LocalBindPort *int `mapstructure:"local_bind_port"`

// These are exclusive with LocalBindAddress/LocalBindPort. These are created under our control.
LocalBindSocketPath *string `mapstructure:"local_bind_socket_path"`
LocalBindSocketMode *string `mapstructure:"local_bind_socket_mode"`

// Config is an opaque config that is specific to the proxy process being run.
// It can be used to pass arbitrary configuration for this specific upstream
// to the proxy.
Expand Down
16 changes: 16 additions & 0 deletions agent/config/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2590,6 +2590,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) {
{
"destination_name": "db",
"local_bind_port": 7000
},
{
"destination_name": "db2",
"local_bind_socket_path": "/tmp/socketpath",
"local_bind_socket_mode": "0644"
}
]
}
Expand Down Expand Up @@ -2631,6 +2636,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) {
destination_name = "db"
local_bind_port = 7000
},
{
destination_name = "db2",
local_bind_socket_path = "/tmp/socketpath",
local_bind_socket_mode = "0644"
}
]
}
}
Expand Down Expand Up @@ -2675,6 +2685,12 @@ func TestLoad_IntegrationWithFlags(t *testing.T) {
DestinationName: "db",
LocalBindPort: 7000,
},
structs.Upstream{
DestinationType: "service",
DestinationName: "db2",
LocalBindSocketPath: "/tmp/socketpath",
LocalBindSocketMode: "0644",
},
},
},
Weights: &structs.Weights{
Expand Down
5 changes: 5 additions & 0 deletions agent/config/testdata/full-config.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ service = {
address = "cOlSOhbp"
token = "msy7iWER"
port = 24237
socket_path = "/tmp/rc78ap"
weights = {
passing = 100,
warning = 1
Expand Down Expand Up @@ -455,6 +456,7 @@ services = [
address = "9RhqPSPB"
token = "myjKJkWH"
port = 72219
socket_path = "/foo/bar/sock_7IszXMQ1"
enable_tag_override = true
check = {
id = "qmfeO5if"
Expand Down Expand Up @@ -561,6 +563,7 @@ services = [
destination_service_id = "6L6BVfgH-id"
local_service_address = "127.0.0.2"
local_service_port = 23759
local_service_socket_path = "/foo/bar/local"
config {
cedGGtZf = "pWrUNiWw"
}
Expand All @@ -578,6 +581,8 @@ services = [
destination_name = "KSd8HsRl"
local_bind_port = 11884
local_bind_address = "127.24.88.0"
local_bind_socket_path = "/foo/bar/upstream"
local_bind_socket_mode = "0600"
},
]
expose {
Expand Down
7 changes: 6 additions & 1 deletion agent/config/testdata/full-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@
"address": "cOlSOhbp",
"token": "msy7iWER",
"port": 24237,
"socket_path": "/tmp/rc78ap",
"weights": {
"passing": 100,
"warning": 1
Expand Down Expand Up @@ -452,6 +453,7 @@
"address": "9RhqPSPB",
"token": "myjKJkWH",
"port": 72219,
"socket_path":"/foo/bar/sock_7IszXMQ1",
"enable_tag_override": true,
"check": {
"id": "qmfeO5if",
Expand Down Expand Up @@ -561,6 +563,7 @@
"destination_service_name": "6L6BVfgH",
"local_service_address": "127.0.0.2",
"local_service_port": 23759,
"local_service_socket_path": "/foo/bar/local",
"expose": {
"checks": true,
"paths": [
Expand Down Expand Up @@ -589,7 +592,9 @@
"destination_namespace": "9nakw0td",
"destination_type": "prepared_query",
"local_bind_address": "127.24.88.0",
"local_bind_port": 11884
"local_bind_port": 11884,
"local_bind_socket_path": "/foo/bar/upstream",
"local_bind_socket_mode": "0600"
}
]
}
Expand Down
2 changes: 2 additions & 0 deletions agent/proxycfg/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ func TestManager_BasicLifecycle(t *testing.T) {
UpstreamConfig: map[string]*structs.Upstream{
upstreams[0].Identifier(): &upstreams[0],
upstreams[1].Identifier(): &upstreams[1],
upstreams[2].Identifier(): &upstreams[2],
},
},
PreparedQueryEndpoints: map[string]structs.CheckServiceNodes{},
Expand Down Expand Up @@ -290,6 +291,7 @@ func TestManager_BasicLifecycle(t *testing.T) {
UpstreamConfig: map[string]*structs.Upstream{
upstreams[0].Identifier(): &upstreams[0],
upstreams[1].Identifier(): &upstreams[1],
upstreams[2].Identifier(): &upstreams[2],
},
},
PreparedQueryEndpoints: map[string]structs.CheckServiceNodes{},
Expand Down
2 changes: 2 additions & 0 deletions agent/proxycfg/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -1709,6 +1709,8 @@ func (s *state) handleUpdateIngressGateway(u cache.UpdateEvent, snap *ConfigSnap
return nil
}

// Note: Ingress gateways are always bound to ports and never unix sockets.
// This means LocalBindPort is the only possibility
func makeUpstream(g *structs.GatewayService) structs.Upstream {
upstream := structs.Upstream{
DestinationName: g.Service.Name,
Expand Down
1 change: 1 addition & 0 deletions agent/proxycfg/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -1639,6 +1639,7 @@ func testConfigSnapshotIngressGateway(
additionalEntries ...structs.ConfigEntry,
) *ConfigSnapshot {
roots, leaf := TestCerts(t)

snap := &ConfigSnapshot{
Kind: structs.ServiceKindIngressGateway,
Service: "ingress-gateway",
Expand Down
19 changes: 14 additions & 5 deletions agent/sidecar_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,20 @@ func (a *Agent) sidecarServiceFromNodeService(ns *structs.NodeService, token str
if sidecar.Proxy.DestinationServiceID == "" {
sidecar.Proxy.DestinationServiceID = ns.ID
}
if sidecar.Proxy.LocalServiceAddress == "" {
sidecar.Proxy.LocalServiceAddress = "127.0.0.1"
}
if sidecar.Proxy.LocalServicePort < 1 {
sidecar.Proxy.LocalServicePort = ns.Port

// Fill defaults from NodeService if none of the address components are present.
// This really argues for a refactoring to a more generalized 'address' concept.
if sidecar.Proxy.LocalServiceSocketPath == "" && (sidecar.Proxy.LocalServiceAddress == "" || sidecar.Proxy.LocalServicePort < 1) {
if ns.SocketPath != "" {
sidecar.Proxy.LocalServiceSocketPath = ns.SocketPath
} else {
if sidecar.Proxy.LocalServiceAddress == "" {
sidecar.Proxy.LocalServiceAddress = "127.0.0.1"
}
if sidecar.Proxy.LocalServicePort < 1 {
sidecar.Proxy.LocalServicePort = ns.Port
}
}
}

// Allocate port if needed (min and max inclusive).
Expand Down
Loading