this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

support multiple admin passwords (#1044)

- relay support for multiple admin passwords at the same time (to make
secret rotation easier)
- have a parallel PR going with README updates; will mention in that PR
that the env var is comma-separated, so passwords can't contain a comma
- does not change the arg name or env var name, so existing configs work
fine
- also switches to constant-time string comparison using go stdlib (more
secure)

authored by

bnewbold and committed by
GitHub
d6fa2c23 9f5c9a1f

+28 -11
+6 -5
cmd/relay/main.go
··· 42 42 Version: versioninfo.Short(), 43 43 } 44 44 app.Flags = []cli.Flag{ 45 - &cli.StringFlag{ 45 + &cli.StringSliceFlag{ 46 46 Name: "admin-password", 47 - Usage: "secret password/token for accessing admin endpoints (random is used if not set)", 47 + Usage: "secret password/token for accessing admin endpoints (multiple values allowed)", 48 48 EnvVars: []string{"RELAY_ADMIN_PASSWORD", "RELAY_ADMIN_KEY"}, 49 49 }, 50 50 &cli.StringFlag{ ··· 260 260 logger.Info("sibling relay hosts configured for admin state forwarding", "servers", svcConfig.SiblingRelayHosts) 261 261 } 262 262 if cctx.IsSet("admin-password") { 263 - svcConfig.AdminPassword = cctx.String("admin-password") 263 + svcConfig.AdminPasswords = cctx.StringSlice("admin-password") 264 264 } else { 265 265 var rblob [10]byte 266 266 _, _ = rand.Read(rblob[:]) 267 - svcConfig.AdminPassword = base64.URLEncoding.EncodeToString(rblob[:]) 268 - logger.Info("generated random admin password", "username", "admin", "password", svcConfig.AdminPassword) 267 + randPassword := base64.URLEncoding.EncodeToString(rblob[:]) 268 + svcConfig.AdminPasswords = []string{randPassword} 269 + logger.Info("generated random admin password", "username", "admin", "password", randPassword) 269 270 } 270 271 271 272 evtman := eventmgr.NewEventManager(persister)
+22 -6
cmd/relay/service.go
··· 2 2 3 3 import ( 4 4 "context" 5 + "crypto/subtle" 5 6 "encoding/base64" 6 7 "log/slog" 7 8 "net" ··· 29 30 // list of hosts which get forwarded admin state changes (takedowns, etc) 30 31 SiblingRelayHosts []string 31 32 32 - // verified against Basic admin auth 33 - AdminPassword string 33 + // verified against Basic admin auth. multiple passwords are allowed server-side, to make secret rotations operationally easier 34 + AdminPasswords []string 34 35 35 36 // how long to wait for the requested server socket to become available for use 36 37 ListenerBootTimeout time.Duration ··· 197 198 } 198 199 199 200 func (svc *Service) checkAdminAuth(next echo.HandlerFunc) echo.HandlerFunc { 200 - headerVal := "Basic " + base64.StdEncoding.EncodeToString([]byte("admin:"+svc.config.AdminPassword)) 201 + 202 + // pre-compute valid HTTP auth headers based on the set of 203 + validAuthHeaders := []string{} 204 + for _, pw := range svc.config.AdminPasswords { 205 + hdr := "Basic " + base64.StdEncoding.EncodeToString([]byte("admin:"+pw)) 206 + validAuthHeaders = append(validAuthHeaders, hdr) 207 + } 208 + 209 + // for paths that this middleware is applied to, enforce that the auth header must exist and match one of the known passwords 201 210 return func(c echo.Context) error { 202 211 hdr := c.Request().Header.Get("Authorization") 203 - if hdr != headerVal { 204 - return echo.ErrForbidden 212 + if hdr == "" { 213 + c.Response().Header().Set("WWW-Authenticate", "Basic") 214 + return echo.ErrUnauthorized 215 + } 216 + for _, val := range validAuthHeaders { 217 + if subtle.ConstantTimeCompare([]byte(hdr), []byte(val)) == 1 { 218 + return next(c) 219 + } 205 220 } 206 - return next(c) 221 + c.Response().Header().Set("WWW-Authenticate", "Basic") 222 + return echo.ErrUnauthorized 207 223 } 208 224 }