tangled
alpha
login
or
join now
sr.aux1.dev
/
tsproxy
2
fork
atom
HTTP reverse proxy for Tailscale
2
fork
atom
overview
issues
pulls
1
pipelines
add basic auth funnel handler
sr.aux1.dev
9 months ago
78765853
f2c85165
+96
2 changed files
expand all
collapse all
unified
split
tsproxy.go
tsproxy_test.go
+25
tsproxy.go
···
2
2
3
3
import (
4
4
"context"
5
5
+
"crypto/subtle"
5
6
"crypto/tls"
6
7
"encoding/json"
7
8
"errors"
···
76
77
Issuer string
77
78
ClientID string
78
79
ClientSecret string
80
80
+
User string
81
81
+
Password string
79
82
}
80
83
81
84
type target struct {
···
332
335
wrapper.OAuth2Config.Scopes = append(wrapper.OAuth2Config.Scopes, oidc.ScopeProfile)
333
336
334
337
handler = wrapper.Wrap(oidcFunnel(log, lc, proxy))
338
338
+
case funnel.User != "":
339
339
+
handler = insecureFunnel(log, lc, basicAuth(log, funnel.User, funnel.Password, proxy))
335
340
default:
336
341
return fmt.Errorf("upstream %s must set funnel.insecure or funnel.issuer", upstream.Name)
337
342
}
···
373
378
return
374
379
}
375
380
next.ServeHTTP(w, r)
381
381
+
})
382
382
+
}
383
383
+
384
384
+
func basicAuth(logger *slog.Logger, user, password string, next http.Handler) http.Handler {
385
385
+
if user == "" || password == "" {
386
386
+
panic("user and password are required")
387
387
+
}
388
388
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
389
389
+
u, p, ok := r.BasicAuth()
390
390
+
if ok {
391
391
+
userCheck := subtle.ConstantTimeCompare([]byte(user), []byte(u))
392
392
+
passwordCheck := subtle.ConstantTimeCompare([]byte(password), []byte(p))
393
393
+
if userCheck == 1 && passwordCheck == 1 {
394
394
+
next.ServeHTTP(w, r)
395
395
+
return
396
396
+
}
397
397
+
}
398
398
+
logger.ErrorContext(r.Context(), "authentication failed", slog.String("user", u))
399
399
+
w.Header().Set("WWW-Authenticate", "Basic realm=\"protected\", charset=\"UTF-8\"")
400
400
+
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
376
401
})
377
402
}
378
403
+71
tsproxy_test.go
···
310
310
}
311
311
}
312
312
313
313
+
func TestBasicAuthHandler(t *testing.T) {
314
314
+
t.Parallel()
315
315
+
316
316
+
logger := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{}))
317
317
+
318
318
+
for _, tc := range []struct {
319
319
+
name string
320
320
+
user string
321
321
+
password string
322
322
+
request func(*http.Request)
323
323
+
wantNext bool
324
324
+
wantStatus int
325
325
+
}{
326
326
+
{
327
327
+
name: "no basic auth provided",
328
328
+
user: "admin",
329
329
+
password: "secret",
330
330
+
request: func(_ *http.Request) {},
331
331
+
wantStatus: http.StatusUnauthorized,
332
332
+
},
333
333
+
{
334
334
+
name: "wrong user",
335
335
+
user: "admin",
336
336
+
password: "secret",
337
337
+
request: func(r *http.Request) { r.SetBasicAuth("bad", "secret") },
338
338
+
wantStatus: http.StatusUnauthorized,
339
339
+
},
340
340
+
{
341
341
+
name: "wrong password",
342
342
+
user: "admin",
343
343
+
password: "secret",
344
344
+
request: func(r *http.Request) { r.SetBasicAuth("admin", "bad") },
345
345
+
wantStatus: http.StatusUnauthorized,
346
346
+
},
347
347
+
{
348
348
+
name: "ok",
349
349
+
user: "admin",
350
350
+
password: "secret",
351
351
+
request: func(r *http.Request) { r.SetBasicAuth("admin", "secret") },
352
352
+
wantNext: true,
353
353
+
wantStatus: http.StatusOK,
354
354
+
},
355
355
+
} {
356
356
+
t.Run(tc.name, func(t *testing.T) {
357
357
+
t.Parallel()
358
358
+
359
359
+
var nextReq *http.Request
360
360
+
h := basicAuth(logger, tc.user, tc.password, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
361
361
+
nextReq = r
362
362
+
fmt.Fprintf(w, "OK")
363
363
+
}))
364
364
+
w := httptest.NewRecorder()
365
365
+
req := httptest.NewRequest("", "/", nil)
366
366
+
tc.request(req)
367
367
+
h.ServeHTTP(w, req)
368
368
+
resp := w.Result()
369
369
+
370
370
+
if want, got := tc.wantStatus, resp.StatusCode; want != got {
371
371
+
t.Errorf("want status %d, got: %d", want, got)
372
372
+
}
373
373
+
374
374
+
if tc.wantNext && nextReq == nil {
375
375
+
t.Fatalf("next handler not called")
376
376
+
}
377
377
+
if !tc.wantNext && nextReq != nil {
378
378
+
t.Fatalf("next handler should not have been called")
379
379
+
}
380
380
+
})
381
381
+
}
382
382
+
}
383
383
+
313
384
func TestServeDiscovery(t *testing.T) {
314
385
t.Parallel()
315
386