From baa1d88f1fed42b79065ced5776b71cfd76bb61a Mon Sep 17 00:00:00 2001 From: Jim Date: Thu, 9 Dec 2021 20:41:08 -0500 Subject: [PATCH] feature: Add client ip to inbound request information (#1678) * feature: Add client ip to inbound request information * feature (workers): Add the user's client IP to a session's connection information (#1749) --- CHANGELOG.md | 2 + go.mod | 13 +- go.sum | 14 +- .../oss/postgres/21/02_session.up.sql | 9 + internal/gen/controller/auth/auth.pb.go | 25 ++- .../servers/services/session_service.pb.go | 203 ++++++++++-------- internal/observability/event/event.go | 1 + .../proto/local/controller/auth/v1/auth.proto | 3 + .../servers/services/v1/session_service.proto | 4 + internal/servers/common/client_ip.go | 55 +++++ internal/servers/common/client_ip_test.go | 191 ++++++++++++++++ internal/servers/common/handler.go | 9 +- internal/servers/common/handler_test.go | 10 +- internal/servers/controller/handler.go | 3 +- .../handlers/sessions/session_service_test.go | 4 +- .../handlers/workers/worker_service.go | 1 + internal/servers/controller/interceptor.go | 1 + internal/servers/controller/listeners.go | 16 -- .../controller/session_cleanup_job_test.go | 1 + internal/servers/worker/handler.go | 27 ++- internal/servers/worker/listeners.go | 5 +- internal/servers/worker/proxy/proxy.go | 5 + internal/servers/worker/proxy/tcp/tcp.go | 1 + internal/servers/worker/proxy/tcp/tcp_test.go | 1 + internal/session/connection.go | 9 +- .../session/connection_closed_with_test.go | 2 +- internal/session/connection_state_test.go | 10 +- internal/session/connection_test.go | 29 ++- internal/session/immutable_fields_test.go | 4 +- .../session/repository_connection_test.go | 7 +- internal/session/repository_session.go | 2 + internal/session/repository_session_test.go | 39 +++- internal/session/session_connect_with.go | 4 + internal/session/session_connect_with_test.go | 14 ++ internal/session/testing.go | 4 +- internal/session/testing_test.go | 4 +- 36 files changed, 563 insertions(+), 169 deletions(-) create mode 100644 internal/db/schema/migrations/oss/postgres/21/02_session.up.sql create mode 100644 internal/servers/common/client_ip.go create mode 100644 internal/servers/common/client_ip_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cd93d4de..8bc53169d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ Canonical reference for changes, improvements, and bugfixes for Boundary. * host: Plugin-based host catalogs will now schedule updates for all of its host sets when its attributes are updated. ([PR](https://github.com/hashicorp/boundary/pull/1736)) +* controllers/workers: Add client IP to inbound request information which is included in + Boundary events ([PR](https://github.com/hashicorp/boundary/pull/1678)) ## 0.7.1 (2021/11/18) diff --git a/go.mod b/go.mod index 055637682..e07037ca3 100644 --- a/go.mod +++ b/go.mod @@ -38,12 +38,12 @@ require ( github.com/hashicorp/go-secure-stdlib/configutil v0.1.2 github.com/hashicorp/go-secure-stdlib/gatedwriter v0.1.1 github.com/hashicorp/go-secure-stdlib/kv-builder v0.1.1 - github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.2 + github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.3-0.20211129134915-8f7e6fb52622 github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 github.com/hashicorp/go-secure-stdlib/parseutil v0.1.2 github.com/hashicorp/go-secure-stdlib/password v0.1.1 github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 - github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 github.com/hashicorp/go-uuid v1.0.2 github.com/hashicorp/hcl v1.0.0 github.com/hashicorp/vault/api v1.1.1 @@ -54,7 +54,7 @@ require ( github.com/jefferai/keyring v1.1.7-0.20210105022822-8749b3d9ce79 github.com/kr/pretty v0.3.0 github.com/kr/text v0.2.0 - github.com/mattn/go-colorable v0.1.11 + github.com/mattn/go-colorable v0.1.12 github.com/mitchellh/cli v1.1.2 github.com/mitchellh/copystructure v1.2.0 github.com/mitchellh/go-wordwrap v1.0.1 @@ -72,7 +72,6 @@ require ( github.com/zalando/go-keyring v0.1.1 go.uber.org/atomic v1.9.0 golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 - golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 golang.org/x/term v0.0.0-20210916214954-140adaaadfaf golang.org/x/tools v0.1.6 google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83 @@ -85,6 +84,11 @@ require ( nhooyr.io/websocket v1.8.7 ) +require ( + github.com/hashicorp/go-sockaddr v1.0.2 + golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 +) + require ( cloud.google.com/go v0.65.0 // indirect github.com/AlecAivazis/survey/v2 v2.2.9 // indirect @@ -141,7 +145,6 @@ require ( github.com/hashicorp/go-immutable-radix v1.1.0 // indirect github.com/hashicorp/go-plugin v1.0.1 // indirect github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1 // indirect - github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/go-version v1.3.0 // indirect github.com/hashicorp/golang-lru v0.5.3 // indirect github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect diff --git a/go.sum b/go.sum index 1f4d6d224..0841662b2 100644 --- a/go.sum +++ b/go.sum @@ -498,8 +498,8 @@ github.com/hashicorp/go-secure-stdlib/gatedwriter v0.1.1/go.mod h1:6RoRTSMDK2H/r github.com/hashicorp/go-secure-stdlib/kv-builder v0.1.1 h1:IJgULbAXuvWxzKFfu+Au1FUmHIJulS6N4F7Hkn+Kck0= github.com/hashicorp/go-secure-stdlib/kv-builder v0.1.1/go.mod h1:rf5JPE13wi+NwjgsmGkbg4b2CgHq8v7Htn/F0nDe/hg= github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.1/go.mod h1:irE9ILwhE8VooWdhHhBy0qXzOMRlHKoWoMlr31mAFCE= -github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.2 h1:vx6ak5Tp3L6oJ66iPYFpp8sINOPQBIWVF0Kjn8NdLVQ= -github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.2/go.mod h1:irE9ILwhE8VooWdhHhBy0qXzOMRlHKoWoMlr31mAFCE= +github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.3-0.20211129134915-8f7e6fb52622 h1:fXINbWBhJdR3PJI9gNDsDyJDQhgqBzI5gywgShO4Kbw= +github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.3-0.20211129134915-8f7e6fb52622/go.mod h1:myX7XYMJRIP4PLHtYJiKMTJcKOX0M5ZJNwP0iw+l3uw= github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 h1:cCRo8gK7oq6A2L6LICkUZ+/a5rLiRXFMf1Qd4xSwxTc= github.com/hashicorp/go-secure-stdlib/mlock v0.1.1/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= @@ -509,8 +509,9 @@ github.com/hashicorp/go-secure-stdlib/password v0.1.1 h1:6JzmBqXprakgFEHwBgdchsj github.com/hashicorp/go-secure-stdlib/password v0.1.1/go.mod h1:9hH302QllNwu1o2TGYtSk8I8kTAN0ca1EHpwhm5Mmzo= github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 h1:SMGUnbpAcat8rIKHkBPjfv81yC46a8eCNZ2hsR2l1EI= github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1/go.mod h1:Ch/bf00Qnx77MZd49JRgHYqHQjtEmTgGU2faufpVZb0= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 h1:nd0HIW15E6FG1MsnArYaHfuw9C2zgzM8LxkG5Ty/788= github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1 h1:Yc026VyMyIpq1UWRnakHRG01U8fJm+nEfEmjoAb00n8= github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1/go.mod h1:l8slYwnJA26yBz+ErHpp2IRCLr0vuOMGBORIz4rRiAs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= @@ -709,8 +710,8 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -1197,9 +1198,8 @@ golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI= -golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210916214954-140adaaadfaf h1:Ihq/mm/suC88gF8WFcVwk+OV6Tq+wyA1O0E5UEvDglI= diff --git a/internal/db/schema/migrations/oss/postgres/21/02_session.up.sql b/internal/db/schema/migrations/oss/postgres/21/02_session.up.sql new file mode 100644 index 000000000..ca7e399ca --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/21/02_session.up.sql @@ -0,0 +1,9 @@ +begin; + +alter table session_connection + -- intentionally null because we don't know the user_client_ip when the + -- session is created by the controller. The worker will update the session + -- with the user_client_ip after the user establishes a connection. + add column user_client_ip inet; + +commit; \ No newline at end of file diff --git a/internal/gen/controller/auth/auth.pb.go b/internal/gen/controller/auth/auth.pb.go index 90649f069..99e95abd8 100644 --- a/internal/gen/controller/auth/auth.pb.go +++ b/internal/gen/controller/auth/auth.pb.go @@ -53,6 +53,8 @@ type RequestInfo struct { TraceId string `protobuf:"bytes,120,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty"` // event_id is the request's event id EventId string `protobuf:"bytes,130,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"` + // the client ip for the request + ClientIp string `protobuf:"bytes,140,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"` } func (x *RequestInfo) Reset() { @@ -178,13 +180,20 @@ func (x *RequestInfo) GetEventId() string { return "" } +func (x *RequestInfo) GetClientIp() string { + if x != nil { + return x.ClientIp + } + return "" +} + var File_controller_auth_v1_auth_proto protoreflect.FileDescriptor var file_controller_auth_v1_auth_proto_rawDesc = []byte{ 0x0a, 0x1d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x75, 0x74, 0x68, - 0x2e, 0x76, 0x31, 0x22, 0xc7, 0x03, 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, + 0x2e, 0x76, 0x31, 0x22, 0xe5, 0x03, 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, @@ -212,12 +221,14 @@ var file_controller_auth_v1_auth_proto_rawDesc = []byte{ 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x78, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x82, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x42, 0x41, 0x5a, - 0x3f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, - 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x3b, 0x61, 0x75, 0x74, 0x68, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1c, 0x0a, + 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x8c, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x42, 0x41, 0x5a, 0x3f, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x3b, 0x61, 0x75, 0x74, 0x68, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/gen/controller/servers/services/session_service.pb.go b/internal/gen/controller/servers/services/session_service.pb.go index 0159d05e0..af8715dd7 100644 --- a/internal/gen/controller/servers/services/session_service.pb.go +++ b/internal/gen/controller/servers/services/session_service.pb.go @@ -566,6 +566,9 @@ type ConnectConnectionRequest struct { EndpointTcpAddress string `protobuf:"bytes,40,opt,name=endpoint_tcp_address,json=endpointTcpAddress,proto3" json:"endpoint_tcp_address,omitempty" class:"public"` // @gotags: `class:"public"` EndpointTcpPort uint32 `protobuf:"varint,50,opt,name=endpoint_tcp_port,json=endpointTcpPort,proto3" json:"endpoint_tcp_port,omitempty" class:"public"` // @gotags: `class:"public"` Type string `protobuf:"bytes,60,opt,name=type,proto3" json:"type,omitempty" class:"public"` // @gotags: `class:"public"` + // user_client_ip is the user's client ip for the connection as determined + // the inbound http request + UserClientIp string `protobuf:"bytes,70,opt,name=user_client_ip,json=userClientIp,proto3" json:"user_client_ip,omitempty" class:"public"` // @gotags: `class:"public" } func (x *ConnectConnectionRequest) Reset() { @@ -642,6 +645,13 @@ func (x *ConnectConnectionRequest) GetType() string { return "" } +func (x *ConnectConnectionRequest) GetUserClientIp() string { + if x != nil { + return x.UserClientIp + } + return "" +} + type ConnectConnectionResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1012,7 +1022,7 @@ var file_controller_servers_services_v1_session_service_proto_rawDesc = []byte{ 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x6c, 0x65, 0x66, 0x74, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4c, 0x65, 0x66, 0x74, 0x22, - 0x87, 0x02, 0x0a, 0x18, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0xad, 0x02, 0x0a, 0x18, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, @@ -1028,106 +1038,109 @@ var file_controller_servers_services_v1_session_service_proto_rawDesc = []byte{ 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x74, 0x63, 0x70, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x54, 0x63, 0x70, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x3c, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x65, 0x0a, 0x19, 0x43, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x46, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x22, + 0x65, 0x0a, 0x19, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x4f, + 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x93, 0x01, 0x0a, 0x1a, 0x43, 0x6c, 0x6f, 0x73, 0x65, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x79, + 0x74, 0x65, 0x73, 0x5f, 0x75, 0x70, 0x18, 0x14, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x62, 0x79, + 0x74, 0x65, 0x73, 0x55, 0x70, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x64, + 0x6f, 0x77, 0x6e, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x62, 0x79, 0x74, 0x65, 0x73, + 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x28, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x82, 0x01, 0x0a, + 0x16, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x68, 0x0a, 0x12, 0x63, 0x6c, 0x6f, 0x73, 0x65, + 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0a, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, + 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x52, + 0x10, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, + 0x61, 0x22, 0x8c, 0x01, 0x0a, 0x1b, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, + 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x48, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x14, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x22, 0x93, 0x01, 0x0a, 0x1a, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, - 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, - 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x75, 0x70, - 0x18, 0x14, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x55, 0x70, 0x12, - 0x1d, 0x0a, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x1e, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x09, 0x62, 0x79, 0x74, 0x65, 0x73, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x16, - 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x82, 0x01, 0x0a, 0x16, 0x43, 0x6c, 0x6f, 0x73, 0x65, - 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x68, 0x0a, 0x12, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3a, 0x2e, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x52, 0x10, 0x63, 0x6c, 0x6f, 0x73, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x22, 0x8c, 0x01, 0x0a, 0x1b, - 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x63, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, - 0x12, 0x48, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, - 0x31, 0x2e, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x53, 0x54, 0x41, 0x54, - 0x55, 0x53, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x86, 0x01, 0x0a, 0x17, 0x43, - 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x13, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, - 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0a, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, - 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, - 0x52, 0x11, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, - 0x61, 0x74, 0x61, 0x32, 0xbe, 0x06, 0x0a, 0x0e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7e, 0x0a, 0x0d, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, - 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, - 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x84, 0x01, 0x0a, 0x0f, 0x41, 0x63, 0x74, 0x69, 0x76, - 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x36, 0x2e, 0x63, 0x6f, 0x6e, - 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, - 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x74, 0x69, - 0x76, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7e, 0x0a, - 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x34, - 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, - 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x53, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x90, 0x01, - 0x0a, 0x13, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, - 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, - 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x3b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x43, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x8a, 0x01, 0x0a, 0x11, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x39, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, - 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x84, 0x01, - 0x0a, 0x0f, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x36, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x22, 0x86, 0x01, 0x0a, 0x17, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x13, + 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x64, + 0x61, 0x74, 0x61, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x42, 0x51, 0x5a, 0x4f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, - 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, - 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x73, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x3b, 0x73, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x11, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x32, 0xbe, 0x06, 0x0a, 0x0e, 0x53, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7e, 0x0a, 0x0d, + 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, + 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x84, 0x01, 0x0a, + 0x0f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x36, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, + 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x7e, 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, + 0x65, 0x6c, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x90, 0x01, 0x0a, 0x13, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3a, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, + 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x8a, 0x01, 0x0a, 0x11, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x84, 0x01, 0x0a, 0x0f, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x36, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x37, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x51, 0x5a, 0x4f, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, + 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2f, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/observability/event/event.go b/internal/observability/event/event.go index 2de944ac8..76d9363ea 100644 --- a/internal/observability/event/event.go +++ b/internal/observability/event/event.go @@ -16,6 +16,7 @@ type RequestInfo struct { Method string `json:"method,omitempty" class:"public"` Path string `json:"path,omitempty" class:"public"` PublicId string `json:"public_id,omitempty" class:"public"` + ClientIp string `json:"client_ip,omitempty" class:"public"` } // UserInfo defines the fields captured about a user for a Boundary request. diff --git a/internal/proto/local/controller/auth/v1/auth.proto b/internal/proto/local/controller/auth/v1/auth.proto index d4395722c..87771d316 100644 --- a/internal/proto/local/controller/auth/v1/auth.proto +++ b/internal/proto/local/controller/auth/v1/auth.proto @@ -45,4 +45,7 @@ message RequestInfo { // event_id is the request's event id string event_id = 130; + + // the client ip for the request + string client_ip = 140; } diff --git a/internal/proto/local/controller/servers/services/v1/session_service.proto b/internal/proto/local/controller/servers/services/v1/session_service.proto index 8a297697f..ab1cafbdc 100644 --- a/internal/proto/local/controller/servers/services/v1/session_service.proto +++ b/internal/proto/local/controller/servers/services/v1/session_service.proto @@ -92,6 +92,10 @@ message ConnectConnectionRequest { string endpoint_tcp_address = 40; // @gotags: `class:"public"` uint32 endpoint_tcp_port = 50; // @gotags: `class:"public"` string type = 60; // @gotags: `class:"public"` + + // user_client_ip is the user's client ip for the connection as determined by + // the inbound http request handler + string user_client_ip = 70; // @gotags: `class:"public" } message ConnectConnectionResponse { diff --git a/internal/servers/common/client_ip.go b/internal/servers/common/client_ip.go new file mode 100644 index 000000000..f48180e8a --- /dev/null +++ b/internal/servers/common/client_ip.go @@ -0,0 +1,55 @@ +package common + +import ( + "context" + "net" + "net/http" + "strings" + + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/go-secure-stdlib/listenerutil" +) + +// ClientIpFromRequest will determine the client IP of the http request +func ClientIpFromRequest(ctx context.Context, listenerCfg *listenerutil.ListenerConfig, r *http.Request) (string, error) { + const op = "common.ClientIpFromRequest" + if r == nil { + return "", errors.New(ctx, errors.InvalidParameter, op, "missing http request") + } + if listenerCfg == nil { + return "", errors.New(ctx, errors.InvalidParameter, op, "missing listener config") + } + + // using the XForwardedFor* listener config settings to determine how/if + // X-Forwarded-For are trusted/allowed for an inbound request. + trustedForwardedFor, remoteAddr, err := listenerutil.TrustedFromXForwardedFor(r, listenerCfg) + switch { + case err != nil: + return "", errors.Wrap(ctx, err, op, errors.WithMsg("failed to determine trusted X-Forwarded-For")) + case trustedForwardedFor == nil && remoteAddr == nil: + ip, err := ipFromRequestRemoteAddr(ctx, r) + if err != nil { + return "", errors.Wrap(ctx, err, op) + } + return ip, nil + case trustedForwardedFor == nil && remoteAddr != nil: + // not reachable given listenerutil.TrustedFromXForwardedFor + // implementation in 11/2021 but that could change in the future, so + // this case is explicitly handled + return remoteAddr.Host, nil + default: + return trustedForwardedFor.Host, nil + } +} + +func ipFromRequestRemoteAddr(ctx context.Context, r *http.Request) (string, error) { + const op = "common.ipFromRequestRemoteAddr" + if strings.ContainsRune(r.RemoteAddr, ':') { + if ip, _, err := net.SplitHostPort(r.RemoteAddr); err != nil { + return "", errors.Wrap(ctx, err, op) + } else { + return ip, nil + } + } + return r.RemoteAddr, nil +} diff --git a/internal/servers/common/client_ip_test.go b/internal/servers/common/client_ip_test.go new file mode 100644 index 000000000..ef93d2582 --- /dev/null +++ b/internal/servers/common/client_ip_test.go @@ -0,0 +1,191 @@ +package common + +import ( + "context" + "net/http" + "testing" + + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/go-secure-stdlib/listenerutil" + "github.com/hashicorp/go-sockaddr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_ClientIpFromRequest(t *testing.T) { + // Cannot run in parallels since it relies on the pkg var common.privateNets + testCtx := context.Background() + testReq := func(remote, realIp string, forwardedFor ...string) *http.Request { + h := http.Header{} + h.Set("X-Real-IP", realIp) + for _, address := range forwardedFor { + h.Set("X-Forwarded-For", address) + } + return &http.Request{ + RemoteAddr: remote, + Header: h, + } + } + + pub1 := "147.12.56.100" + pub2 := "119.14.55.11" + localAddr := "127.0.0.1:101" + goodAddr, err := sockaddr.NewIPAddr(localAddr) + require.NoError(t, err) + + tests := []struct { + name string + request *http.Request + listenerCfg *listenerutil.ListenerConfig + wantAddr string + wantErrMatch *errors.Template + wantErrContains string + }{ + { + name: "missing-request", + listenerCfg: func() *listenerutil.ListenerConfig { + listenerConfig := cfgListener(goodAddr) + listenerConfig.XForwardedForRejectNotPresent = false + return listenerConfig + }(), + wantErrMatch: errors.T(errors.InvalidParameter), + wantErrContains: "missing http request", + }, + { + name: "missing-listener-config", + request: testReq(pub1, ""), + wantErrMatch: errors.T(errors.InvalidParameter), + wantErrContains: "missing listener config", + }, + { + name: "no-header-remote-addr", + request: testReq(pub1, ""), + listenerCfg: func() *listenerutil.ListenerConfig { + listenerConfig := cfgListener(goodAddr) + listenerConfig.XForwardedForRejectNotPresent = false + return listenerConfig + }(), + wantAddr: pub1, + }, + { + name: "no-header-remote-addr-with-port", + request: testReq(pub1+":49152", ""), + listenerCfg: func() *listenerutil.ListenerConfig { + listenerConfig := cfgListener(goodAddr) + listenerConfig.XForwardedForRejectNotPresent = false + return listenerConfig + }(), + wantAddr: pub1, + }, + { + name: "one-x-forwarded", + request: testReq(localAddr, "", pub1), + listenerCfg: func() *listenerutil.ListenerConfig { + listenerConfig := cfgListener(goodAddr) + listenerConfig.XForwardedForRejectNotPresent = false + return listenerConfig + }(), + wantAddr: pub1, + }, + { + name: "no-trusted-x-forwarded-with-remote-addr", + request: testReq(localAddr, "", ""), + listenerCfg: func() *listenerutil.ListenerConfig { + listenerConfig := cfgListener(goodAddr) + listenerConfig.XForwardedForRejectNotPresent = false + return listenerConfig + }(), + wantAddr: "127.0.0.1", + }, + { + name: "no-trusted-x-forwarded-with-bad-remote-addr", + request: testReq(localAddr+":22", "", ""), + listenerCfg: func() *listenerutil.ListenerConfig { + listenerConfig := cfgListener(goodAddr) + listenerConfig.XForwardedForRejectNotPresent = false + return listenerConfig + }(), + wantErrMatch: errors.T(errors.Unknown), + wantErrContains: "too many colons in address", + }, + { + name: "many-x-forwarded", + request: testReq(localAddr, "", localAddr, pub1, pub2), + listenerCfg: func() *listenerutil.ListenerConfig { + listenerConfig := cfgListener(goodAddr) + listenerConfig.XForwardedForRejectNotPresent = false + return listenerConfig + }(), + wantAddr: pub2, + }, + { + name: "real-ip", // will fallback to remote addr, since real-ip isn't enabled for boundary + request: testReq(localAddr, pub1), + listenerCfg: func() *listenerutil.ListenerConfig { + listenerConfig := cfgListener(goodAddr) + listenerConfig.XForwardedForRejectNotPresent = false + return listenerConfig + }(), + wantAddr: "127.0.0.1", + }, + { + name: "local-forwaredfor-no-real-ip-use-remote-addr", + request: testReq(pub1, "", localAddr), + listenerCfg: func() *listenerutil.ListenerConfig { + listenerConfig := cfgListener(goodAddr) + listenerConfig.XForwardedForRejectNotPresent = false + return listenerConfig + }(), + wantAddr: pub1, + }, + { + name: "trusted-x-forwarded-for-success", + request: testReq(localAddr, "", pub1), + listenerCfg: func() *listenerutil.ListenerConfig { + listenerConfig := cfgListener(goodAddr) + listenerConfig.XForwardedForRejectNotPresent = true + listenerConfig.XForwardedForRejectNotAuthorized = true + return listenerConfig + }(), + wantAddr: pub1, + }, + { + name: "trusted-x-forwarded-for-failed", + request: testReq("", "", localAddr), + listenerCfg: func() *listenerutil.ListenerConfig { + listenerConfig := cfgListener(goodAddr) + listenerConfig.XForwardedForRejectNotPresent = true + listenerConfig.XForwardedForRejectNotAuthorized = true + return listenerConfig + }(), + wantErrMatch: errors.T(errors.Unknown), + wantErrContains: "failed to determine trusted X-Forwarded-For", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + gotAddr, err := ClientIpFromRequest(testCtx, tt.listenerCfg, tt.request) + if tt.wantErrMatch != nil { + require.Error(err) + assert.Truef(errors.Match(tt.wantErrMatch, err), "wanted %q and got %q", tt.wantErrMatch.Code, err.Error()) + if tt.wantErrContains != "" { + assert.Contains(err.Error(), tt.wantErrContains) + } + return + } + require.NoError(err) + assert.Equal(tt.wantAddr, gotAddr) + }) + } +} + +func cfgListener(addr sockaddr.IPAddr) *listenerutil.ListenerConfig { + return &listenerutil.ListenerConfig{ + XForwardedForAuthorizedAddrs: []*sockaddr.SockAddrMarshaler{ + { + SockAddr: addr, + }, + }, + } +} diff --git a/internal/servers/common/handler.go b/internal/servers/common/handler.go index ffc42d614..93b40e99a 100644 --- a/internal/servers/common/handler.go +++ b/internal/servers/common/handler.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/boundary/internal/observability/event" "github.com/hashicorp/boundary/internal/servers/controller/auth" "github.com/hashicorp/go-secure-stdlib/base62" + "github.com/hashicorp/go-secure-stdlib/listenerutil" ) // GeneratedTraceId returns a boundary generated TraceId or "" if an error occurs when generating @@ -102,7 +103,7 @@ func WrapWithOptionals(ctx context.Context, with *writerWrapper, wrap http.Respo // WrapWithEventsHandler will wrap the provided http.Handler with a // handler that adds an Eventer to the request context and starts/flushes gated // events of type: observation and audit -func WrapWithEventsHandler(h http.Handler, e *event.Eventer, kms *kms.Kms) (http.Handler, error) { +func WrapWithEventsHandler(h http.Handler, e *event.Eventer, kms *kms.Kms, listenerCfg *listenerutil.ListenerConfig) (http.Handler, error) { const op = "common.WrapWithEventsHandler" if h == nil { return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing handler") @@ -124,12 +125,18 @@ func WrapWithEventsHandler(h http.Handler, e *event.Eventer, kms *kms.Kms) (http event.WriteError(ctx, op, err, event.WithInfoMsg("unable to create id for event", "method", r.Method, "url", r.URL.RequestURI())) return } + clientIp, err := ClientIpFromRequest(ctx, listenerCfg, r) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + event.WriteError(ctx, op, err, event.WithInfoMsg("unable to determine client ip")) + } info := &event.RequestInfo{ EventId: id, Id: GeneratedTraceId(ctx), PublicId: publicId, Method: r.Method, Path: r.URL.RequestURI(), + ClientIp: clientIp, } ctx, err = event.NewRequestInfoContext(ctx, info) if err != nil { diff --git a/internal/servers/common/handler_test.go b/internal/servers/common/handler_test.go index ecd288035..2159aa4f1 100644 --- a/internal/servers/common/handler_test.go +++ b/internal/servers/common/handler_test.go @@ -22,6 +22,7 @@ import ( "github.com/hashicorp/eventlogger/filters/gated" "github.com/hashicorp/eventlogger/formatter_filters/cloudevents" "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-sockaddr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -164,6 +165,8 @@ func Test_WrapWithOptionals(t *testing.T) { } func Test_WrapWithEventsHandler(t *testing.T) { + // This cannot run in parallel because it relies on a pkg var common.privateNets + // this cannot run in parallel because it relies on envvar // globals.BOUNDARY_DEVELOPER_ENABLE_EVENTS event.TestEnableEventing(t, true) @@ -176,6 +179,11 @@ func Test_WrapWithEventsHandler(t *testing.T) { fmt.Fprintln(w, "I'm a little teapot short and stout") }) + goodAddr, err := sockaddr.NewIPAddr("127.0.0.1") + require.NoError(t, err) + testListenerCfg := cfgListener(goodAddr) + testListenerCfg.XForwardedForRejectNotPresent = false + c := event.TestEventerConfig(t, "Test_WrapWithEventsHandler", event.TestWithAuditSink(t), event.TestWithObservationSink(t)) testLock := &sync.Mutex{} testLogger := hclog.New(&hclog.LoggerOptions{ @@ -257,7 +265,7 @@ func Test_WrapWithEventsHandler(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - got, err := WrapWithEventsHandler(tt.h, tt.e, tt.kms) + got, err := WrapWithEventsHandler(tt.h, tt.e, tt.kms, testListenerCfg) if tt.wantErrMatch != nil { require.Error(err) assert.Nil(got) diff --git a/internal/servers/controller/handler.go b/internal/servers/controller/handler.go index 8ab4c3b19..dae331251 100644 --- a/internal/servers/controller/handler.go +++ b/internal/servers/controller/handler.go @@ -67,7 +67,7 @@ func (c *Controller) handler(props HandlerProperties) (http.Handler, error) { commonWrappedHandler := wrapHandlerWithCommonFuncs(corsWrappedHandler, c, props) callbackInterceptingHandler := wrapHandlerWithCallbackInterceptor(commonWrappedHandler, c) printablePathCheckHandler := cleanhttp.PrintablePathCheckHandler(callbackInterceptingHandler, nil) - eventsHandler, err := common.WrapWithEventsHandler(printablePathCheckHandler, c.conf.Eventer, c.kms) + eventsHandler, err := common.WrapWithEventsHandler(printablePathCheckHandler, c.conf.Eventer, c.kms, props.ListenerConfig) if err != nil { return nil, err } @@ -292,6 +292,7 @@ func wrapHandlerWithCommonFuncs(h http.Handler, c *Controller, props HandlerProp // piggyback some eventing fields with the auth info proto message requestInfo.EventId = info.EventId requestInfo.TraceId = info.Id + requestInfo.ClientIp = info.ClientIp } else { w.WriteHeader(http.StatusInternalServerError) event.WriteError(ctx, op, errors.New("unable to read event request info from context")) diff --git a/internal/servers/controller/handlers/sessions/session_service_test.go b/internal/servers/controller/handlers/sessions/session_service_test.go index 6e58c00a0..d26ba4691 100644 --- a/internal/servers/controller/handlers/sessions/session_service_test.go +++ b/internal/servers/controller/handlers/sessions/session_service_test.go @@ -281,7 +281,7 @@ func TestList(t *testing.T) { Endpoint: "tcp://127.0.0.1:22", }) - c := session.TestConnection(t, conn, sess.PublicId, "127.0.0.1", 22, "127.0.0.2", 23) + c := session.TestConnection(t, conn, sess.PublicId, "127.0.0.1", 22, "127.0.0.2", 23, "127.0.0.1") status, states := convertStates(sess.States) @@ -326,7 +326,7 @@ func TestList(t *testing.T) { Endpoint: "tcp://127.0.0.1:22", }) - c = session.TestConnection(t, conn, sess.PublicId, "127.0.0.1", 22, "127.0.0.2", 23) + c = session.TestConnection(t, conn, sess.PublicId, "127.0.0.1", 22, "127.0.0.2", 23, "127.0.0.1") status, states = convertStates(sess.States) diff --git a/internal/servers/controller/handlers/workers/worker_service.go b/internal/servers/controller/handlers/workers/worker_service.go index 3728128c9..834d8a78a 100644 --- a/internal/servers/controller/handlers/workers/worker_service.go +++ b/internal/servers/controller/handlers/workers/worker_service.go @@ -409,6 +409,7 @@ func (ws *workerServiceServer) ConnectConnection(ctx context.Context, req *pbs.C ClientTcpPort: req.GetClientTcpPort(), EndpointTcpAddress: req.GetEndpointTcpAddress(), EndpointTcpPort: req.GetEndpointTcpPort(), + UserClientIp: req.GetUserClientIp(), }) if err != nil { return nil, err diff --git a/internal/servers/controller/interceptor.go b/internal/servers/controller/interceptor.go index 5eec995fb..68482dac9 100644 --- a/internal/servers/controller/interceptor.go +++ b/internal/servers/controller/interceptor.go @@ -125,6 +125,7 @@ func requestCtxInterceptor( PublicId: requestInfo.PublicId, Method: requestInfo.Method, Path: requestInfo.Path, + ClientIp: requestInfo.ClientIp, } interceptorCtx, err = event.NewRequestInfoContext(interceptorCtx, info) if err != nil { diff --git a/internal/servers/controller/listeners.go b/internal/servers/controller/listeners.go index 7c3452890..e72e37029 100644 --- a/internal/servers/controller/listeners.go +++ b/internal/servers/controller/listeners.go @@ -40,22 +40,6 @@ func (c *Controller) startListeners(ctx context.Context) error { return err } - /* - // TODO: As I write this Vault's having this code audited, make sure to - // port over any recommendations - // - // We perform validation on the config earlier, we can just cast here - if _, ok := ln.config["x_forwarded_for_authorized_addrs"]; ok { - hopSkips := ln.config["x_forwarded_for_hop_skips"].(int) - authzdAddrs := ln.config["x_forwarded_for_authorized_addrs"].([]*sockaddr.SockAddrMarshaler) - rejectNotPresent := ln.config["x_forwarded_for_reject_not_present"].(bool) - rejectNonAuthz := ln.config["x_forwarded_for_reject_not_authorized"].(bool) - if len(authzdAddrs) > 0 { - handler = vaulthttp.WrapForwardedForHandler(handler, authzdAddrs, rejectNotPresent, rejectNonAuthz, hopSkips) - } - } - */ - // Resolve it here to avoid race conditions if the base context is // replaced cancelCtx := c.baseContext diff --git a/internal/servers/controller/session_cleanup_job_test.go b/internal/servers/controller/session_cleanup_job_test.go index 803fc9e15..cc679678a 100644 --- a/internal/servers/controller/session_cleanup_job_test.go +++ b/internal/servers/controller/session_cleanup_job_test.go @@ -76,6 +76,7 @@ func TestSessionCleanupJob(t *testing.T) { ClientTcpPort: 22, EndpointTcpAddress: "127.0.0.1", EndpointTcpPort: 22, + UserClientIp: "127.0.0.1", }) require.NoError(err) require.Len(cs, 2) diff --git a/internal/servers/worker/handler.go b/internal/servers/worker/handler.go index 1d74f3ff9..980925238 100644 --- a/internal/servers/worker/handler.go +++ b/internal/servers/worker/handler.go @@ -3,6 +3,7 @@ package worker import ( "context" "errors" + "fmt" "io" "net" "net/http" @@ -13,6 +14,7 @@ import ( pbs "github.com/hashicorp/boundary/internal/gen/controller/servers/services" "github.com/hashicorp/boundary/internal/observability/event" "github.com/hashicorp/boundary/internal/proxy" + "github.com/hashicorp/boundary/internal/servers/common" proxyHandlers "github.com/hashicorp/boundary/internal/servers/worker/proxy" "github.com/hashicorp/boundary/internal/servers/worker/session" "github.com/hashicorp/go-secure-stdlib/listenerutil" @@ -26,19 +28,27 @@ type HandlerProperties struct { // Handler returns a http.Handler for the API. This can be used on // its own to mount the Worker API within another web server. -func (w *Worker) handler(props HandlerProperties) http.Handler { +func (w *Worker) handler(props HandlerProperties) (http.Handler, error) { + const op = "worker.(Worker).handler" // Create the muxer to handle the actual endpoints mux := http.NewServeMux() - mux.Handle("/v1/proxy", w.handleProxy()) + h, err := w.handleProxy(props.ListenerConfig) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + mux.Handle("/v1/proxy", h) genericWrappedHandler := w.wrapGenericHandler(mux, props) - return genericWrappedHandler + return genericWrappedHandler, nil } -func (w *Worker) handleProxy() http.HandlerFunc { +func (w *Worker) handleProxy(listenerCfg *listenerutil.ListenerConfig) (http.HandlerFunc, error) { const op = "worker.(Worker).handleProxy" + if listenerCfg == nil { + return nil, fmt.Errorf("%s: missing listener config", op) + } return func(wr http.ResponseWriter, r *http.Request) { ctx := r.Context() if r.TLS == nil { @@ -65,6 +75,12 @@ func (w *Worker) handleProxy() http.HandlerFunc { Port: numPort, } + userClientIp, err := common.ClientIpFromRequest(ctx, listenerCfg, r) + if err != nil { + event.WriteError(ctx, op, err, event.WithInfoMsg("unable to determine user IP")) + wr.WriteHeader(http.StatusInternalServerError) + } + siRaw, valid := w.sessionInfoMap.Load(sessionId) if !valid { event.WriteError(ctx, op, errors.New("session not found in info map"), event.WithInfo("session_id", sessionId)) @@ -216,6 +232,7 @@ func (w *Worker) handleProxy() http.HandlerFunc { } conf := proxyHandlers.Config{ + UserClientIp: net.ParseIP(userClientIp), ClientAddress: clientAddr, ClientConn: conn, RemoteEndpoint: endpoint, @@ -238,7 +255,7 @@ func (w *Worker) handleProxy() http.HandlerFunc { event.WriteError(ctx, op, err, event.WithInfoMsg("error closing client connection")) } } - } + }, nil } func (w *Worker) wrapGenericHandler(h http.Handler, _ HandlerProperties) http.Handler { diff --git a/internal/servers/worker/listeners.go b/internal/servers/worker/listeners.go index adac049b0..615672743 100644 --- a/internal/servers/worker/listeners.go +++ b/internal/servers/worker/listeners.go @@ -41,9 +41,12 @@ func (w *Worker) startListeners() error { return fmt.Errorf("unknown listener purpose %q", purpose) } - handler := w.handler(HandlerProperties{ + handler, err := w.handler(HandlerProperties{ ListenerConfig: ln.Config, }) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } cancelCtx := w.baseContext diff --git a/internal/servers/worker/proxy/proxy.go b/internal/servers/worker/proxy/proxy.go index 4c2e074f9..43c38cc52 100644 --- a/internal/servers/worker/proxy/proxy.go +++ b/internal/servers/worker/proxy/proxy.go @@ -15,6 +15,11 @@ import ( // a provided ClientConn and the RemoteEndpoint, as well as the parameters to update // the connection in the connection repository. type Config struct { + // UserClientIp is the user's client IP + UserClientIp net.IP + // ClientAddress is the remote address (IP and port) of the client. If + // there are any load balancers or proxies between the user and the worker, + // then it will be the address of the last one before the worker. ClientAddress *net.TCPAddr ClientConn *websocket.Conn RemoteEndpoint string diff --git a/internal/servers/worker/proxy/tcp/tcp.go b/internal/servers/worker/proxy/tcp/tcp.go index 57f3c71ed..f91b7a7f1 100644 --- a/internal/servers/worker/proxy/tcp/tcp.go +++ b/internal/servers/worker/proxy/tcp/tcp.go @@ -53,6 +53,7 @@ func handleProxy(ctx context.Context, conf proxy.Config, _ ...proxy.Option) erro EndpointTcpAddress: endpointAddr.IP.String(), EndpointTcpPort: uint32(endpointAddr.Port), Type: "tcp", + UserClientIp: conf.UserClientIp.String(), } connStatus, err := session.ConnectConnection(ctx, conf.SessionClient, connectionInfo) diff --git a/internal/servers/worker/proxy/tcp/tcp_test.go b/internal/servers/worker/proxy/tcp/tcp_test.go index 0a365ba6b..6c8c1b24f 100644 --- a/internal/servers/worker/proxy/tcp/tcp_test.go +++ b/internal/servers/worker/proxy/tcp/tcp_test.go @@ -68,6 +68,7 @@ func TestHandleTcpProxyV1(t *testing.T) { SessionClient: sessClient, SessionInfo: si, ConnectionId: "mock-connection", + UserClientIp: net.ParseIP("127.0.0.1"), } go func() { diff --git a/internal/session/connection.go b/internal/session/connection.go index 73776d479..42712b1dc 100644 --- a/internal/session/connection.go +++ b/internal/session/connection.go @@ -23,6 +23,8 @@ type Connection struct { ClientTcpAddress string `json:"client_tcp_address,omitempty" gorm:"default:null"` // ClientTcpPort of the connection ClientTcpPort uint32 `json:"client_tcp_port,omitempty" gorm:"default:null"` + // UserClientIp is the user's client IP + UserClientIp string `json:"user_client_ip,omitempty" gorm:"default:null"` // EndpointTcpAddress of the connection EndpointTcpAddress string `json:"endpoint_tcp_address,omitempty" gorm:"default:null"` // EndpointTcpPort of the connection @@ -54,7 +56,7 @@ var ( // NewConnection creates a new in memory connection. No options // are currently supported. -func NewConnection(sessionID, clientTcpAddress string, clientTcpPort uint32, endpointTcpAddr string, endpointTcpPort uint32, _ ...Option) (*Connection, error) { +func NewConnection(sessionID, clientTcpAddress string, clientTcpPort uint32, endpointTcpAddr string, endpointTcpPort uint32, userClientIp string, _ ...Option) (*Connection, error) { const op = "session.NewConnection" c := Connection{ SessionId: sessionID, @@ -62,6 +64,7 @@ func NewConnection(sessionID, clientTcpAddress string, clientTcpPort uint32, end ClientTcpPort: clientTcpPort, EndpointTcpAddress: endpointTcpAddr, EndpointTcpPort: endpointTcpPort, + UserClientIp: userClientIp, } if err := c.validateNewConnection(); err != nil { return nil, errors.WrapDeprecated(err, op) @@ -81,6 +84,7 @@ func (c *Connection) Clone() interface{} { SessionId: c.SessionId, ClientTcpAddress: c.ClientTcpAddress, ClientTcpPort: c.ClientTcpPort, + UserClientIp: c.UserClientIp, EndpointTcpAddress: c.EndpointTcpAddress, EndpointTcpPort: c.EndpointTcpPort, BytesUp: c.BytesUp, @@ -172,5 +176,8 @@ func (c *Connection) validateNewConnection() error { if c.EndpointTcpPort == 0 { return errors.NewDeprecated(errors.InvalidParameter, op, "missing endpoint port") } + if c.UserClientIp == "" { + return errors.NewDeprecated(errors.InvalidParameter, op, "missing user client ip") + } return nil } diff --git a/internal/session/connection_closed_with_test.go b/internal/session/connection_closed_with_test.go index 93a881295..8c2b1b09d 100644 --- a/internal/session/connection_closed_with_test.go +++ b/internal/session/connection_closed_with_test.go @@ -12,7 +12,7 @@ func TestClosedWith_validate(t *testing.T) { wrapper := db.TestWrapper(t) iamRepo := iam.TestRepo(t, conn, wrapper) session := TestDefaultSession(t, conn, wrapper, iamRepo) - sessionConnection := TestConnection(t, conn, session.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222) + sessionConnection := TestConnection(t, conn, session.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222, "127.0.0.1") type fields struct { ConnectionId string BytesUp uint64 diff --git a/internal/session/connection_state_test.go b/internal/session/connection_state_test.go index f92e10a61..32758d3d8 100644 --- a/internal/session/connection_state_test.go +++ b/internal/session/connection_state_test.go @@ -17,7 +17,7 @@ func TestConnectionState_Create(t *testing.T) { wrapper := db.TestWrapper(t) iamRepo := iam.TestRepo(t, conn, wrapper) session := TestDefaultSession(t, conn, wrapper, iamRepo) - connection := TestConnection(t, conn, session.PublicId, "127.0.0.1", 443, "127.0.0.1", 4443) + connection := TestConnection(t, conn, session.PublicId, "127.0.0.1", 443, "127.0.0.1", 4443, "127.0.0.1") type args struct { connectionId string @@ -92,8 +92,8 @@ func TestConnectionState_Delete(t *testing.T) { wrapper := db.TestWrapper(t) iamRepo := iam.TestRepo(t, conn, wrapper) s := TestDefaultSession(t, conn, wrapper, iamRepo) - c := TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222) - c2 := TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222) + c := TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222, "127.0.0.1") + c2 := TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222, "127.0.0.1") tests := []struct { name string @@ -163,7 +163,7 @@ func TestConnectionState_Clone(t *testing.T) { t.Run("valid", func(t *testing.T) { assert := assert.New(t) s := TestDefaultSession(t, conn, wrapper, iamRepo) - c := TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222) + c := TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222, "127.0.0.1") state := TestConnectionState(t, conn, c.PublicId, StatusConnected) cp := state.Clone() assert.Equal(cp.(*ConnectionState), state) @@ -171,7 +171,7 @@ func TestConnectionState_Clone(t *testing.T) { t.Run("not-equal", func(t *testing.T) { assert := assert.New(t) s := TestDefaultSession(t, conn, wrapper, iamRepo) - c := TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222) + c := TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222, "127.0.0.1") state := TestConnectionState(t, conn, c.PublicId, StatusConnected) state2 := TestConnectionState(t, conn, c.PublicId, StatusConnected) diff --git a/internal/session/connection_test.go b/internal/session/connection_test.go index fa4def6f5..f4acf44ce 100644 --- a/internal/session/connection_test.go +++ b/internal/session/connection_test.go @@ -24,6 +24,7 @@ func TestConnection_Create(t *testing.T) { clientTcpPort uint32 endpointTcpAddress string endpointTcpPort uint32 + userClientIp string } tests := []struct { name string @@ -42,6 +43,7 @@ func TestConnection_Create(t *testing.T) { clientTcpPort: 22, endpointTcpAddress: "127.0.0.1", endpointTcpPort: 2222, + userClientIp: "::1", }, want: &Connection{ SessionId: s.PublicId, @@ -49,6 +51,7 @@ func TestConnection_Create(t *testing.T) { ClientTcpPort: 22, EndpointTcpAddress: "127.0.0.1", EndpointTcpPort: 2222, + UserClientIp: "::1", }, create: true, }, @@ -59,6 +62,7 @@ func TestConnection_Create(t *testing.T) { clientTcpPort: 22, endpointTcpAddress: "127.0.0.1", endpointTcpPort: 2222, + userClientIp: "127.0.0.1", }, wantErr: true, wantIsErr: errors.InvalidParameter, @@ -70,6 +74,7 @@ func TestConnection_Create(t *testing.T) { clientTcpPort: 22, endpointTcpAddress: "127.0.0.1", endpointTcpPort: 2222, + userClientIp: "127.0.0.1", }, wantErr: true, wantIsErr: errors.InvalidParameter, @@ -81,6 +86,7 @@ func TestConnection_Create(t *testing.T) { clientTcpAddress: "localhost", endpointTcpAddress: "127.0.0.1", endpointTcpPort: 2222, + userClientIp: "127.0.0.1", }, wantErr: true, wantIsErr: errors.InvalidParameter, @@ -92,6 +98,7 @@ func TestConnection_Create(t *testing.T) { clientTcpAddress: "localhost", clientTcpPort: 22, endpointTcpPort: 2222, + userClientIp: "127.0.0.1", }, wantErr: true, wantIsErr: errors.InvalidParameter, @@ -103,6 +110,19 @@ func TestConnection_Create(t *testing.T) { clientTcpAddress: "localhost", clientTcpPort: 22, endpointTcpAddress: "127.0.0.1", + userClientIp: "127.0.0.1", + }, + wantErr: true, + wantIsErr: errors.InvalidParameter, + }, + { + name: "empty-user-client-ip", + args: args{ + sessionId: s.PublicId, + clientTcpAddress: "localhost", + clientTcpPort: 22, + endpointTcpAddress: "127.0.0.1", + endpointTcpPort: 2222, }, wantErr: true, wantIsErr: errors.InvalidParameter, @@ -117,6 +137,7 @@ func TestConnection_Create(t *testing.T) { tt.args.clientTcpPort, tt.args.endpointTcpAddress, tt.args.endpointTcpPort, + tt.args.userClientIp, ) if tt.wantErr { require.Error(err) @@ -158,7 +179,7 @@ func TestConnection_Delete(t *testing.T) { }{ { name: "valid", - connection: TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222), + connection: TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222, "127.0.0.1"), wantErr: false, wantRowsDeleted: 1, }, @@ -208,15 +229,15 @@ func TestConnection_Clone(t *testing.T) { t.Run("valid", func(t *testing.T) { assert := assert.New(t) s := TestDefaultSession(t, conn, wrapper, iamRepo) - c := TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222) + c := TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222, "127.0.0.1") cp := c.Clone() assert.Equal(cp.(*Connection), c) }) t.Run("not-equal", func(t *testing.T) { assert := assert.New(t) s := TestDefaultSession(t, conn, wrapper, iamRepo) - c := TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222) - c2 := TestConnection(t, conn, s.PublicId, "127.0.0.1", 80, "127.0.0.1", 8080) + c := TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222, "127.0.0.1") + c2 := TestConnection(t, conn, s.PublicId, "127.0.0.1", 80, "127.0.0.1", 8080, "127.0.0.1") cp := c.Clone() assert.NotEqual(cp.(*Connection), c2) diff --git a/internal/session/immutable_fields_test.go b/internal/session/immutable_fields_test.go index 96c7315e4..13ec1d1b4 100644 --- a/internal/session/immutable_fields_test.go +++ b/internal/session/immutable_fields_test.go @@ -179,7 +179,7 @@ func TestConnection_ImmutableFields(t *testing.T) { _, _ = iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) session := TestDefaultSession(t, conn, wrapper, iamRepo) - new := TestConnection(t, conn, session.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222) + new := TestConnection(t, conn, session.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222, "127.0.0.1") tests := []struct { name string @@ -246,7 +246,7 @@ func TestConnectionState_ImmutableFields(t *testing.T) { _, _ = iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) session := TestDefaultSession(t, conn, wrapper, iamRepo) - connection := TestConnection(t, conn, session.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222) + connection := TestConnection(t, conn, session.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222, "127.0.0.1") state := TestConnectionState(t, conn, connection.PublicId, StatusConnected) var new ConnectionState diff --git a/internal/session/repository_connection_test.go b/internal/session/repository_connection_test.go index 7e51ba65f..1d51f7df5 100644 --- a/internal/session/repository_connection_test.go +++ b/internal/session/repository_connection_test.go @@ -91,6 +91,7 @@ func TestRepository_ListConnection(t *testing.T) { 22, "127.0.0.1", 2222, + "127.0.0.1", ) testConnections = append(testConnections, c) } @@ -115,6 +116,7 @@ func TestRepository_ListConnection(t *testing.T) { 22, "127.0.0.1", 2222, + "127.0.0.1", ) } got, err := repo.ListConnectionsBySessionId(context.Background(), session.PublicId, WithOrderByCreateTime(db.AscendingOrderBy)) @@ -154,7 +156,7 @@ func TestRepository_DeleteConnection(t *testing.T) { { name: "valid", args: args{ - connection: TestConnection(t, conn, session.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222), + connection: TestConnection(t, conn, session.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222, "127.0.0.1"), }, wantRowsDeleted: 1, wantErr: false, @@ -261,6 +263,7 @@ func TestRepository_CloseDeadConnectionsOnWorker(t *testing.T) { ClientTcpPort: 22, EndpointTcpAddress: "127.0.0.1", EndpointTcpPort: 22, + UserClientIp: "127.0.0.1", }) require.NoError(err) require.Len(cs, 2) @@ -404,6 +407,7 @@ func TestRepository_CloseConnectionsForDeadWorkers(t *testing.T) { ClientTcpPort: 22, EndpointTcpAddress: "127.0.0.1", EndpointTcpPort: 22, + UserClientIp: "127.0.0.1", }) require.NoError(err) require.Len(cs, 2) @@ -658,6 +662,7 @@ func TestRepository_ShouldCloseConnectionsOnWorker(t *testing.T) { ClientTcpPort: 22, EndpointTcpAddress: "127.0.0.1", EndpointTcpPort: 22, + UserClientIp: "127.0.0.1", }) require.NoError(err) require.Len(cs, 2) diff --git a/internal/session/repository_session.go b/internal/session/repository_session.go index 7e14cae7f..6c4e9735e 100644 --- a/internal/session/repository_session.go +++ b/internal/session/repository_session.go @@ -531,11 +531,13 @@ func (r *Repository) ConnectConnection(ctx context.Context, c ConnectWith) (*Con connection.ClientTcpPort = c.ClientTcpPort connection.EndpointTcpAddress = c.EndpointTcpAddress connection.EndpointTcpPort = c.EndpointTcpPort + connection.UserClientIp = c.UserClientIp fieldMask := []string{ "ClientTcpAddress", "ClientTcpPort", "EndpointTcpAddress", "EndpointTcpPort", + "UserClientIp", } rowsUpdated, err := w.Update(ctx, &connection, fieldMask, nil) if err != nil { diff --git a/internal/session/repository_session_test.go b/internal/session/repository_session_test.go index d6bea02a6..cf2545455 100644 --- a/internal/session/repository_session_test.go +++ b/internal/session/repository_session_test.go @@ -117,7 +117,7 @@ func TestRepository_ListSession(t *testing.T) { _ = TestState(t, conn, s.PublicId, StatusActive) testSessions = append(testSessions, s) for i := 0; i < tt.withConnections; i++ { - _ = TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.2", 23) + _ = TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.2", 23, "127.0.0.1") } } assert.Equal(tt.createCnt, len(testSessions)) @@ -598,7 +598,7 @@ func TestRepository_AuthorizeConnect(t *testing.T) { name: "exceeded-connection-limit", session: func() *Session { session := setupFn(nil) - _ = TestConnection(t, conn, session.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222) + _ = TestConnection(t, conn, session.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222, "127.0.0.1") return session }(), wantErr: true, @@ -654,13 +654,14 @@ func TestRepository_ConnectConnection(t *testing.T) { tofu := TestTofu(t) _, _, err := repo.ActivateSession(context.Background(), s.PublicId, s.Version, srv.PrivateId, srv.Type, tofu) require.NoError(t, err) - c := TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222) + c := TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222, "127.0.0.1") return ConnectWith{ ConnectionId: c.PublicId, ClientTcpAddress: "127.0.0.1", ClientTcpPort: 22, EndpointTcpAddress: "127.0.0.1", EndpointTcpPort: 2222, + UserClientIp: "127.0.0.1", } } tests := []struct { @@ -723,6 +724,16 @@ func TestRepository_ConnectConnection(t *testing.T) { wantErr: true, wantIsError: errors.InvalidParameter, }, + { + name: "empty-UserClientIp", + connectWith: func() ConnectWith { + cw := setupFn() + cw.UserClientIp = "" + return cw + }(), + wantErr: true, + wantIsError: errors.InvalidParameter, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -738,6 +749,14 @@ func TestRepository_ConnectConnection(t *testing.T) { require.NotNil(c) require.NotNil(cs) assert.Equal(StatusConnected, cs[0].Status) + gotConn, _, err := repo.LookupConnection(context.Background(), c.PublicId) + require.NoError(err) + assert.Equal(tt.connectWith.ClientTcpAddress, gotConn.ClientTcpAddress) + assert.Equal(tt.connectWith.ClientTcpPort, gotConn.ClientTcpPort) + assert.Equal(tt.connectWith.ClientTcpAddress, gotConn.ClientTcpAddress) + assert.Equal(tt.connectWith.EndpointTcpAddress, gotConn.EndpointTcpAddress) + assert.Equal(tt.connectWith.EndpointTcpPort, gotConn.EndpointTcpPort) + assert.Equal(tt.connectWith.UserClientIp, gotConn.UserClientIp) }) } } @@ -803,7 +822,7 @@ func TestRepository_TerminateSession(t *testing.T) { name: "open-connection", session: func() *Session { s := setupFn() - _ = TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 222) + _ = TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 222, "127.0.0.1") return s }(), reason: ClosedByUser, @@ -848,7 +867,7 @@ func TestRepository_TerminateCompletedSessions(t *testing.T) { tofu := TestTofu(t) s, _, err = repo.ActivateSession(context.Background(), s.PublicId, s.Version, srv.PrivateId, srv.Type, tofu) require.NoError(t, err) - c := TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 222) + c := TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 222, "127.0.0.1") if !leaveOpen { cw := CloseWith{ ConnectionId: c.PublicId, @@ -902,7 +921,7 @@ func TestRepository_TerminateCompletedSessions(t *testing.T) { for i := 0; i < cnt; i++ { // make one with closed connections s := setupFn(2, time.Hour+1, false) - _ = TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 222) + _ = TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 222, "127.0.0.1") sessions = append(sessions, s) wantTermed[s.PublicId] = ConnectionLimit } @@ -1082,7 +1101,7 @@ func TestRepository_CloseConnections(t *testing.T) { require.NoError(t, err) cw := make([]CloseWith, 0, cnt) for i := 0; i < cnt; i++ { - c := TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222) + c := TestConnection(t, conn, s.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222, "127.0.0.1") require.NoError(t, err) cw = append(cw, CloseWith{ ConnectionId: c.PublicId, @@ -1155,7 +1174,7 @@ func TestRepository_CancelSession(t *testing.T) { require.NoError(t, err) setupFn := func() *Session { session := TestDefaultSession(t, conn, wrapper, iamRepo) - _ = TestConnection(t, conn, session.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222) + _ = TestConnection(t, conn, session.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222, "127.0.0.1") return session } tests := []struct { @@ -1176,7 +1195,7 @@ func TestRepository_CancelSession(t *testing.T) { name: "already-terminated", session: func() *Session { session := TestDefaultSession(t, conn, wrapper, iamRepo) - c := TestConnection(t, conn, session.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222) + c := TestConnection(t, conn, session.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222, "127.0.0.1") cw := CloseWith{ ConnectionId: c.PublicId, BytesUp: 1, @@ -1289,7 +1308,7 @@ func TestRepository_CancelSessionViaFKNull(t *testing.T) { require.NoError(t, err) setupFn := func() *Session { session := TestDefaultSession(t, conn, wrapper, iamRepo) - _ = TestConnection(t, conn, session.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222) + _ = TestConnection(t, conn, session.PublicId, "127.0.0.1", 22, "127.0.0.1", 2222, "127.0.0.1") return session } type cancelFk struct { diff --git a/internal/session/session_connect_with.go b/internal/session/session_connect_with.go index 4cb00e076..4ee655330 100644 --- a/internal/session/session_connect_with.go +++ b/internal/session/session_connect_with.go @@ -12,6 +12,7 @@ type ConnectWith struct { ClientTcpPort uint32 EndpointTcpAddress string EndpointTcpPort uint32 + UserClientIp string } func (c ConnectWith) validate() error { @@ -31,5 +32,8 @@ func (c ConnectWith) validate() error { if c.EndpointTcpPort == 0 { return errors.NewDeprecated(errors.InvalidParameter, op, "missing endpoint ctp port") } + if c.UserClientIp == "" { + return errors.NewDeprecated(errors.InvalidParameter, op, "missing user client ip") + } return nil } diff --git a/internal/session/session_connect_with_test.go b/internal/session/session_connect_with_test.go index 8b2d8022c..da7f4f907 100644 --- a/internal/session/session_connect_with_test.go +++ b/internal/session/session_connect_with_test.go @@ -16,6 +16,7 @@ func TestConnectWith_validate(t *testing.T) { ClientTcpPort uint32 EndpointTcpAddress string EndpointTcpPort uint32 + UserClientIp string } tests := []struct { name string @@ -30,6 +31,7 @@ func TestConnectWith_validate(t *testing.T) { ClientTcpPort: 22, EndpointTcpAddress: "0.0.0.1", EndpointTcpPort: 2222, + UserClientIp: "127.0.0.1", }, }, { @@ -82,6 +84,17 @@ func TestConnectWith_validate(t *testing.T) { }, wantErr: true, }, + { + name: "missing-UserClientIp", + fields: fields{ + SessionId: id, + ClientTcpAddress: "0.0.0.1", + ClientTcpPort: 22, + EndpointTcpAddress: "0.0.0.1", + EndpointTcpPort: 2222, + }, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -91,6 +104,7 @@ func TestConnectWith_validate(t *testing.T) { ClientTcpPort: tt.fields.ClientTcpPort, EndpointTcpAddress: tt.fields.EndpointTcpAddress, EndpointTcpPort: tt.fields.EndpointTcpPort, + UserClientIp: tt.fields.UserClientIp, } if err := c.validate(); (err != nil) != tt.wantErr { t.Errorf("ConnectWith.validate() error = %v, wantErr %v", err, tt.wantErr) diff --git a/internal/session/testing.go b/internal/session/testing.go index ac23b25f2..5038e8345 100644 --- a/internal/session/testing.go +++ b/internal/session/testing.go @@ -24,11 +24,11 @@ import ( ) // TestConnection creates a test connection for the sessionId in the repository. -func TestConnection(t *testing.T, conn *db.DB, sessionId, clientTcpAddr string, clientTcpPort uint32, endpointTcpAddr string, endpointTcpPort uint32) *Connection { +func TestConnection(t *testing.T, conn *db.DB, sessionId, clientTcpAddr string, clientTcpPort uint32, endpointTcpAddr string, endpointTcpPort uint32, userClientIp string) *Connection { t.Helper() require := require.New(t) rw := db.New(conn) - c, err := NewConnection(sessionId, clientTcpAddr, clientTcpPort, endpointTcpAddr, endpointTcpPort) + c, err := NewConnection(sessionId, clientTcpAddr, clientTcpPort, endpointTcpAddr, endpointTcpPort, userClientIp) require.NoError(err) id, err := newConnectionId() require.NoError(err) diff --git a/internal/session/testing_test.go b/internal/session/testing_test.go index 7c4657f64..8f6ee6061 100644 --- a/internal/session/testing_test.go +++ b/internal/session/testing_test.go @@ -42,7 +42,7 @@ func Test_TestConnection(t *testing.T) { require.NotNil(s) assert.NotEmpty(s.PublicId) - c := TestConnection(t, conn, s.PublicId, "127.0.0.1", 6500, "127.0.0.1", 22) + c := TestConnection(t, conn, s.PublicId, "127.0.0.1", 6500, "127.0.0.1", 22, "127.0.0.1") require.NotNil(c) } @@ -55,7 +55,7 @@ func Test_TestConnectionState(t *testing.T) { require.NotNil(s) assert.NotEmpty(s.PublicId) - c := TestConnection(t, conn, s.PublicId, "0.0.0.0", 22, "0.0.0.0", 2222) + c := TestConnection(t, conn, s.PublicId, "0.0.0.0", 22, "0.0.0.0", 2222, "127.0.0.1") require.NotNil(c) assert.NotEmpty(c.PublicId)