Skip to content

Commit d1c9711

Browse files
committed
feat: Support x-forwarded-for headers for IPs
Fixes #4430.
1 parent 72288c3 commit d1c9711

File tree

14 files changed

+1061
-22
lines changed

14 files changed

+1061
-22
lines changed

cli/deployment/flags.go

+12
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,18 @@ func Flags() *codersdk.DeploymentFlags {
236236
Description: "Scopes to grant when authenticating with OIDC.",
237237
Default: []string{oidc.ScopeOpenID, "profile", "email"},
238238
},
239+
ProxyTrustedHeaders: &codersdk.StringArrayFlag{
240+
Name: "Trusted HTTP Proxy Headers",
241+
Flag: "proxy-trusted-headers",
242+
EnvVar: "CODER_PROXY_TRUSTED_HEADERS",
243+
Description: "Headers to trust for forwarding IP addresses. e.g. \"X-Forwarded-for\"",
244+
},
245+
ProxyTrustedOrigins: &codersdk.StringArrayFlag{
246+
Name: "Trusted HTTP Proxy Origins",
247+
Flag: "proxy-trusted-origins",
248+
EnvVar: "CODER_PROXY_TRUSTED_ORIGINS",
249+
Description: "Origin addresses to respect \"proxy-trusted-headers\".",
250+
},
239251
TelemetryEnable: &codersdk.BoolFlag{
240252
Name: "Telemetry Enabled",
241253
Flag: "telemetry",

cli/server.go

+7
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import (
5656
"github.com/coder/coder/coderd/devtunnel"
5757
"github.com/coder/coder/coderd/gitsshkey"
5858
"github.com/coder/coder/coderd/httpapi"
59+
"github.com/coder/coder/coderd/httpmw"
5960
"github.com/coder/coder/coderd/prometheusmetrics"
6061
"github.com/coder/coder/coderd/telemetry"
6162
"github.com/coder/coder/coderd/tracing"
@@ -321,6 +322,11 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code
321322
}
322323
}
323324

325+
realIPConfig, err := httpmw.ParseRealIPConfig(dflags.ProxyTrustedHeaders.Value, dflags.ProxyTrustedOrigins.Value)
326+
if err != nil {
327+
return xerrors.Errorf("parse real ip config: %w", err)
328+
}
329+
324330
options := &coderd.Options{
325331
AccessURL: accessURLParsed,
326332
AppHostname: appHostname,
@@ -332,6 +338,7 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code
332338
CacheDir: dflags.CacheDir.Value,
333339
GoogleTokenValidator: googleTokenValidator,
334340
SecureAuthCookie: dflags.SecureAuthCookie.Value,
341+
RealIPConfig: realIPConfig,
335342
SSHKeygenAlgorithm: sshKeygenAlgorithm,
336343
TracerProvider: tracerProvider,
337344
Telemetry: telemetry.NewNoop(),

coderd/apikey.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,7 @@ func (api *API) createAPIKey(ctx context.Context, params createAPIKeyParams) (*h
230230
}
231231
}
232232

233-
host, _, _ := net.SplitHostPort(params.RemoteAddr)
234-
ip := net.ParseIP(host)
233+
ip := net.ParseIP(params.RemoteAddr)
235234
if ip == nil {
236235
ip = net.IPv4(0, 0, 0, 0)
237236
}

coderd/audit.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,7 @@ func (api *API) generateFakeAuditLog(rw http.ResponseWriter, r *http.Request) {
117117
return
118118
}
119119

120-
ipRaw, _, _ := net.SplitHostPort(r.RemoteAddr)
121-
ip := net.ParseIP(ipRaw)
120+
ip := net.ParseIP(r.RemoteAddr)
122121
ipNet := pqtype.Inet{}
123122
if ip != nil {
124123
ipNet = pqtype.Inet{

coderd/audit/request.go

+4-16
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,8 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request
129129
}
130130
}
131131

132-
ip, err := parseIP(p.Request.RemoteAddr)
133-
if err != nil {
134-
p.Log.Warn(logCtx, "parse ip", slog.Error(err))
135-
}
136-
137-
err = p.Audit.Export(ctx, database.AuditLog{
132+
ip := parseIP(p.Request.RemoteAddr)
133+
err := p.Audit.Export(ctx, database.AuditLog{
138134
ID: uuid.New(),
139135
Time: database.Now(),
140136
UserID: httpmw.APIKey(p.Request).UserID,
@@ -166,16 +162,8 @@ func either[T Auditable, R any](old, new T, fn func(T) R) R {
166162
}
167163
}
168164

169-
func parseIP(ipStr string) (pqtype.Inet, error) {
170-
var err error
171-
172-
ipStr, _, err = net.SplitHostPort(ipStr)
173-
if err != nil {
174-
return pqtype.Inet{}, err
175-
}
176-
165+
func parseIP(ipStr string) pqtype.Inet {
177166
ip := net.ParseIP(ipStr)
178-
179167
ipNet := net.IPNet{}
180168
if ip != nil {
181169
ipNet = net.IPNet{
@@ -187,5 +175,5 @@ func parseIP(ipStr string) (pqtype.Inet, error) {
187175
return pqtype.Inet{
188176
IPNet: ipNet,
189177
Valid: ip != nil,
190-
}, nil
178+
}
191179
}

coderd/coderd.go

+2
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ type Options struct {
8282
Telemetry telemetry.Reporter
8383
TracerProvider trace.TracerProvider
8484
AutoImportTemplates []AutoImportTemplate
85+
RealIPConfig *httpmw.RealIPConfig
8586

8687
// TLSCertificates is used to mesh DERP servers securely.
8788
TLSCertificates []tls.Certificate
@@ -198,6 +199,7 @@ func New(options *Options) *API {
198199
r.Use(
199200
httpmw.AttachRequestID,
200201
httpmw.Recover(api.Logger),
202+
httpmw.ExtractRealIP(api.RealIPConfig),
201203
httpmw.Logger(api.Logger),
202204
httpmw.Prometheus(options.PrometheusRegistry),
203205
// handleSubdomainApplications checks if the first subdomain is a valid

coderd/coderdtest/coderdtest.go

+3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import (
5757
"github.com/coder/coder/coderd/database/dbtestutil"
5858
"github.com/coder/coder/coderd/gitsshkey"
5959
"github.com/coder/coder/coderd/httpapi"
60+
"github.com/coder/coder/coderd/httpmw"
6061
"github.com/coder/coder/coderd/rbac"
6162
"github.com/coder/coder/coderd/telemetry"
6263
"github.com/coder/coder/coderd/util/ptr"
@@ -77,6 +78,7 @@ type Options struct {
7778
Experimental bool
7879
AzureCertificates x509.VerifyOptions
7980
GithubOAuth2Config *coderd.GithubOAuth2Config
81+
RealIPConfig *httpmw.RealIPConfig
8082
OIDCConfig *coderd.OIDCConfig
8183
GoogleTokenValidator *idtoken.Validator
8284
SSHKeygenAlgorithm gitsshkey.Algorithm
@@ -238,6 +240,7 @@ func NewOptions(t *testing.T, options *Options) (func(http.Handler), context.Can
238240
AWSCertificates: options.AWSCertificates,
239241
AzureCertificates: options.AzureCertificates,
240242
GithubOAuth2Config: options.GithubOAuth2Config,
243+
RealIPConfig: options.RealIPConfig,
241244
OIDCConfig: options.OIDCConfig,
242245
GoogleTokenValidator: options.GoogleTokenValidator,
243246
SSHKeygenAlgorithm: options.SSHKeygenAlgorithm,

coderd/httpmw/apikey.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,7 @@ func ExtractAPIKey(cfg ExtractAPIKeyConfig) func(http.Handler) http.Handler {
250250
// Only update LastUsed once an hour to prevent database spam.
251251
if now.Sub(key.LastUsed) > time.Hour {
252252
key.LastUsed = now
253-
host, _, _ := net.SplitHostPort(r.RemoteAddr)
254-
remoteIP := net.ParseIP(host)
253+
remoteIP := net.ParseIP(r.RemoteAddr)
255254
if remoteIP == nil {
256255
remoteIP = net.IPv4(0, 0, 0, 0)
257256
}

0 commit comments

Comments
 (0)