Skip to content

Commit 230e9e9

Browse files
ashmckenzieJames Fargher
and
James Fargher
committed
Merge branch 'proxy_ip_allowed' into 'main'
Restrict IP access for PROXY protocol Closes #577 See merge request https://gitlab.com/gitlab-org/gitlab-shell/-/merge_requests/693 Merged-by: Ash McKenzie <[email protected]> Approved-by: Alejandro Rodríguez <[email protected]> Co-authored-by: James Fargher <[email protected]>
2 parents 8303261 + 344ada0 commit 230e9e9

File tree

5 files changed

+90
-13
lines changed

5 files changed

+90
-13
lines changed

cmd/gitlab-sshd/acceptance_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,7 @@ func TestGitUploadArchiveSuccess(t *testing.T) {
477477
require.NoError(t, err)
478478

479479
_, err = fmt.Fprintln(stdin, "0012argument HEAD\n0000")
480+
require.NoError(t, err)
480481

481482
line, err := reader.ReadString('\n')
482483
require.Equal(t, "0008ACK\n", line)
@@ -489,5 +490,6 @@ func TestGitUploadArchiveSuccess(t *testing.T) {
489490
output, err := io.ReadAll(stdout)
490491
require.NoError(t, err)
491492

493+
t.Logf("output: %q", output)
492494
require.Equal(t, []byte("0000"), output[len(output)-4:])
493495
}

config.yml.example

+4
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ sshd:
7272
# Proxy protocol policy ("use", "require", "reject", "ignore"), "use" is the default value
7373
# Values: https://github.com/pires/go-proxyproto/blob/195fedcfbfc1be163f3a0d507fac1709e9d81fed/policy.go#L20
7474
proxy_policy: "use"
75+
# Proxy allowed IP addresses. Takes precedent over proxy_policy. Disabled by default.
76+
# proxy_allowed:
77+
# - "192.168.0.1"
78+
# - "192.168.1.0/24"
7579
# Address which the server listens on HTTP for monitoring/health checks. Defaults to localhost:9122.
7680
web_listen: "localhost:9122"
7781
# Maximum number of concurrent sessions allowed on a single SSH connection. Defaults to 10.

internal/config/config.go

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type ServerConfig struct {
2727
Listen string `yaml:"listen,omitempty"`
2828
ProxyProtocol bool `yaml:"proxy_protocol,omitempty"`
2929
ProxyPolicy string `yaml:"proxy_policy,omitempty"`
30+
ProxyAllowed []string `yaml:"proxy_allowed,omitempty"`
3031
WebListen string `yaml:"web_listen,omitempty"`
3132
ConcurrentSessionsLimit int64 `yaml:"concurrent_sessions_limit,omitempty"`
3233
ClientAliveInterval YamlDuration `yaml:"client_alive_interval,omitempty"`

internal/sshd/sshd.go

+22-7
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"sync"
1010
"time"
1111

12-
"github.com/pires/go-proxyproto"
12+
proxyproto "github.com/pires/go-proxyproto"
1313
"golang.org/x/crypto/ssh"
1414

1515
"gitlab.com/gitlab-org/gitlab-shell/v14/client"
@@ -95,9 +95,14 @@ func (s *Server) listen(ctx context.Context) error {
9595
}
9696

9797
if s.Config.Server.ProxyProtocol {
98+
policy, err := s.proxyPolicy()
99+
if err != nil {
100+
return fmt.Errorf("invalid policy configuration: %w", err)
101+
}
102+
98103
sshListener = &proxyproto.Listener{
99104
Listener: sshListener,
100-
Policy: s.requirePolicy,
105+
Policy: policy,
101106
ReadHeaderTimeout: time.Duration(s.Config.Server.ProxyHeaderTimeout),
102107
}
103108

@@ -200,17 +205,27 @@ func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
200205
})
201206
}
202207

203-
func (s *Server) requirePolicy(_ net.Addr) (proxyproto.Policy, error) {
208+
func (s *Server) proxyPolicy() (proxyproto.PolicyFunc, error) {
209+
if len(s.Config.Server.ProxyAllowed) > 0 {
210+
return proxyproto.StrictWhiteListPolicy(s.Config.Server.ProxyAllowed)
211+
}
212+
204213
// Set the Policy value based on config
205214
// Values are taken from https://github.com/pires/go-proxyproto/blob/195fedcfbfc1be163f3a0d507fac1709e9d81fed/policy.go#L20
206215
switch strings.ToLower(s.Config.Server.ProxyPolicy) {
207216
case "require":
208-
return proxyproto.REQUIRE, nil
217+
return staticProxyPolicy(proxyproto.REQUIRE), nil
209218
case "ignore":
210-
return proxyproto.IGNORE, nil
219+
return staticProxyPolicy(proxyproto.IGNORE), nil
211220
case "reject":
212-
return proxyproto.REJECT, nil
221+
return staticProxyPolicy(proxyproto.REJECT), nil
213222
default:
214-
return proxyproto.USE, nil
223+
return staticProxyPolicy(proxyproto.USE), nil
224+
}
225+
}
226+
227+
func staticProxyPolicy(policy proxyproto.Policy) proxyproto.PolicyFunc {
228+
return func(_ net.Addr) (proxyproto.Policy, error) {
229+
return policy, nil
215230
}
216231
}

internal/sshd/sshd_test.go

+61-6
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func TestListenAndServe(t *testing.T) {
5050
verifyStatus(t, s, StatusClosed)
5151
}
5252

53-
func TestListenAndServeRejectsPlainConnectionsWhenProxyProtocolEnabled(t *testing.T) {
53+
func TestListenAndServe_proxyProtocolEnabled(t *testing.T) {
5454
target, err := net.ResolveTCPAddr("tcp", serverUrl)
5555
require.NoError(t, err)
5656

@@ -70,10 +70,11 @@ func TestListenAndServeRejectsPlainConnectionsWhenProxyProtocolEnabled(t *testin
7070
}()
7171

7272
testCases := []struct {
73-
desc string
74-
proxyPolicy string
75-
header *proxyproto.Header
76-
isRejected bool
73+
desc string
74+
proxyPolicy string
75+
proxyAllowed []string
76+
header *proxyproto.Header
77+
isRejected bool
7778
}{
7879
{
7980
desc: "USE (default) without a header",
@@ -123,11 +124,65 @@ func TestListenAndServeRejectsPlainConnectionsWhenProxyProtocolEnabled(t *testin
123124
header: header,
124125
isRejected: false,
125126
},
127+
{
128+
desc: "Allow-listed IP with a header",
129+
proxyAllowed: []string{"127.0.0.1"},
130+
header: header,
131+
isRejected: false,
132+
},
133+
{
134+
desc: "Allow-listed IP without a header",
135+
proxyAllowed: []string{"127.0.0.1"},
136+
header: nil,
137+
isRejected: false,
138+
},
139+
{
140+
desc: "Allow-listed range with a header",
141+
proxyAllowed: []string{"127.0.0.0/24"},
142+
header: header,
143+
isRejected: false,
144+
},
145+
{
146+
desc: "Allow-listed range without a header",
147+
proxyAllowed: []string{"127.0.0.0/24"},
148+
header: nil,
149+
isRejected: false,
150+
},
151+
{
152+
desc: "Not allow-listed IP with a header",
153+
proxyAllowed: []string{"192.168.1.1"},
154+
header: header,
155+
isRejected: true,
156+
},
157+
{
158+
desc: "Not allow-listed IP without a header",
159+
proxyAllowed: []string{"192.168.1.1"},
160+
header: nil,
161+
isRejected: false,
162+
},
163+
{
164+
desc: "Not allow-listed range with a header",
165+
proxyAllowed: []string{"192.168.1.0/24"},
166+
header: header,
167+
isRejected: true,
168+
},
169+
{
170+
desc: "Not allow-listed range without a header",
171+
proxyAllowed: []string{"192.168.1.0/24"},
172+
header: nil,
173+
isRejected: false,
174+
},
126175
}
127176

128177
for _, tc := range testCases {
129178
t.Run(tc.desc, func(t *testing.T) {
130-
setupServerWithConfig(t, &config.Config{Server: config.ServerConfig{ProxyProtocol: true, ProxyPolicy: tc.proxyPolicy}})
179+
setupServerWithConfig(t, &config.Config{
180+
Server: config.ServerConfig{
181+
ProxyProtocol: true,
182+
ProxyPolicy: tc.proxyPolicy,
183+
ProxyAllowed: tc.proxyAllowed,
184+
},
185+
})
131186

132187
conn, err := net.DialTCP("tcp", nil, target)
133188
require.NoError(t, err)

0 commit comments

Comments
 (0)