diff --git a/authenticate/authenticate.go b/authenticate/authenticate.go index 4474de525..ecf444663 100644 --- a/authenticate/authenticate.go +++ b/authenticate/authenticate.go @@ -9,6 +9,7 @@ import ( "fmt" "html/template" "net/url" + "sync" "gopkg.in/square/go-jose.v2" @@ -25,6 +26,7 @@ import ( "github.com/pomerium/pomerium/internal/httputil" "github.com/pomerium/pomerium/internal/identity" "github.com/pomerium/pomerium/internal/identity/oauth" + "github.com/pomerium/pomerium/internal/log" "github.com/pomerium/pomerium/internal/sessions" "github.com/pomerium/pomerium/internal/sessions/cookie" "github.com/pomerium/pomerium/internal/sessions/header" @@ -105,6 +107,8 @@ type Authenticate struct { // userClient is used to update users userClient user.UserServiceClient + // guard administrator below. + administratorMu sync.Mutex // administrators keeps track of administrator users. administrator map[string]struct{} @@ -184,10 +188,6 @@ func New(opts config.Options) (*Authenticate, error) { return nil, err } - administrator := make(map[string]struct{}, len(opts.Administrators)) - for _, admin := range opts.Administrators { - administrator[admin] = struct{}{} - } a := &Authenticate{ RedirectURL: redirectURL, // shared state @@ -208,7 +208,6 @@ func New(opts config.Options) (*Authenticate, error) { dataBrokerClient: dataBrokerClient, sessionClient: sessionClient, userClient: userClient, - administrator: administrator, jwk: &jose.JSONWebKeySet{}, templates: template.Must(frontend.NewTemplates()), } @@ -227,3 +226,26 @@ func New(opts config.Options) (*Authenticate, error) { return a, nil } + +func (a *Authenticate) setAdminUsers(opts *config.Options) { + a.administratorMu.Lock() + defer a.administratorMu.Unlock() + + a.administrator = make(map[string]struct{}, len(opts.Administrators)) + for _, admin := range opts.Administrators { + a.administrator[admin] = struct{}{} + } +} + +// UpdateOptions implements the OptionsUpdater interface and updates internal +// structures based on config.Options +func (a *Authenticate) UpdateOptions(opts config.Options) error { + if a == nil { + return nil + } + + log.Info().Str("checksum", fmt.Sprintf("%x", opts.Checksum())).Msg("authenticate: updating options") + a.setAdminUsers(&opts) + + return nil +} diff --git a/authenticate/authenticate_test.go b/authenticate/authenticate_test.go index cb10e6a78..56c2bb4c3 100644 --- a/authenticate/authenticate_test.go +++ b/authenticate/authenticate_test.go @@ -156,6 +156,7 @@ func TestIsAdmin(t *testing.T) { opts := newTestOptions(t) opts.Administrators = tc.admins a, err := New(*opts) + assert.NoError(t, a.UpdateOptions(*opts)) require.NoError(t, err) assert.True(t, a.isAdmin(tc.user) == tc.isAdmin) }) diff --git a/authenticate/handlers.go b/authenticate/handlers.go index 30c5a8a67..2e70bf424 100644 --- a/authenticate/handlers.go +++ b/authenticate/handlers.go @@ -459,6 +459,9 @@ func (a *Authenticate) deleteSession(ctx context.Context, sessionID string) erro } func (a *Authenticate) isAdmin(user string) bool { + a.administratorMu.Lock() + defer a.administratorMu.Unlock() + _, ok := a.administrator[user] return ok } diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index 98192bc28..1befa59ec 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -4,13 +4,14 @@ ### New -- config: add remove_request_headers @cuonglm [GH-702] +- config: add remove_request_headers @cuonglm [GH-822] - config: change default log level to INFO @cuonglm [GH-902] - config: add pass_identity_headers @cuonglm [GH-903] +- authenticate: allow hot reloaded admin users config @cuonglm [GH-984] ### Changes -- proxy: do not set X-Pomerium-Jwt-Assertion/X-Pomerium-Claim-* headers by default [GH-903] +- proxy: do not set X-Pomerium-Jwt-Assertion/X-Pomerium-Claim-* headers by default @cuonglm [GH-903] ## v0.9.1 diff --git a/internal/cmd/pomerium/pomerium.go b/internal/cmd/pomerium/pomerium.go index 078130e9d..e3251ef19 100644 --- a/internal/cmd/pomerium/pomerium.go +++ b/internal/cmd/pomerium/pomerium.go @@ -72,7 +72,7 @@ func Run(ctx context.Context, configFile string) error { } // add services - if err := setupAuthenticate(opt, controlPlane); err != nil { + if err := setupAuthenticate(opt, controlPlane, &optionsUpdaters); err != nil { return err } var authorizeServer *authorize.Authorize @@ -132,7 +132,7 @@ func Run(ctx context.Context, configFile string) error { return eg.Wait() } -func setupAuthenticate(opt *config.Options, controlPlane *controlplane.Server) error { +func setupAuthenticate(opt *config.Options, controlPlane *controlplane.Server, optionsUpdaters *[]config.OptionsUpdater) error { if !config.IsAuthenticate(opt.Services) { return nil } @@ -141,6 +141,11 @@ func setupAuthenticate(opt *config.Options, controlPlane *controlplane.Server) e if err != nil { return fmt.Errorf("error creating authenticate service: %w", err) } + *optionsUpdaters = append(*optionsUpdaters, svc) + err = svc.UpdateOptions(*opt) + if err != nil { + return fmt.Errorf("error updating authenticate options: %w", err) + } host := urlutil.StripPort(opt.GetAuthenticateURL().Host) sr := controlPlane.HTTPRouter.Host(host).Subrouter() svc.Mount(sr)