Skip to content

Commit be72fa9

Browse files
committed
api: verify OIDC and OpenFGA connectivity on security config update
Signed-off-by: Stéphane Graber <stephane.graber@futurfusion.io>
1 parent 8e67fc9 commit be72fa9

1 file changed

Lines changed: 46 additions & 2 deletions

File tree

internal/api/daemon.go

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ type Daemon struct {
120120
oidcVerifier *authnoidc.Verifier
121121
authorizer *authz.Authorizer
122122

123+
// securityRetryCancel stops the background connection retries of the
124+
// security infrastructure from the previous configuration.
125+
securityRetryCancel context.CancelFunc
126+
123127
server *http.Server
124128
listener *listener.FancyTLSListener
125129

@@ -188,6 +192,28 @@ func (d *Daemon) Start(ctx context.Context) error {
188192
slog.ErrorContext(ctx, "Failed to load security config", logger.Err(err))
189193
}
190194

195+
// On update of the OIDC or OpenFGA configuration, verify the configured
196+
// services are reachable, so a broken configuration update is rejected
197+
// right away. Connection failures after a successful update are handled
198+
// by background retries.
199+
lifecycle.SecurityValidateSignal.AddListenerWithErr(func(ctx context.Context, cfg apisystem.Security) error {
200+
if cfg.OIDC.Issuer != "" && cfg.OIDC.ClientID != "" {
201+
err := authnoidc.CheckConnectivity(ctx, cfg.OIDC.Issuer)
202+
if err != nil {
203+
return fmt.Errorf("Failed to reach OIDC issuer %q: %w", cfg.OIDC.Issuer, err)
204+
}
205+
}
206+
207+
if cfg.OpenFGA.APIURL != "" && cfg.OpenFGA.APIToken != "" && cfg.OpenFGA.StoreID != "" {
208+
err := authzopenfga.CheckConnectivity(ctx, cfg.OpenFGA.APIURL, cfg.OpenFGA.APIToken, cfg.OpenFGA.StoreID)
209+
if err != nil {
210+
return fmt.Errorf("Failed to reach OpenFGA at %q: %w", cfg.OpenFGA.APIURL, err)
211+
}
212+
}
213+
214+
return nil
215+
})
216+
191217
// On update of the security configuration, perform reload of the security
192218
// related infrastructure.
193219
lifecycle.SecurityUpdateSignal.AddListener(func(ctx context.Context, cfg apisystem.Security) {
@@ -467,6 +493,16 @@ func (d *Daemon) securityConfigReload(ctx context.Context, cfg apisystem.Securit
467493
d.configReloadMu.Lock()
468494
defer d.configReloadMu.Unlock()
469495

496+
// Cancel background connection retries from the previous configuration.
497+
if d.securityRetryCancel != nil {
498+
d.securityRetryCancel()
499+
}
500+
501+
// Detached context for background connection retries, which needs to
502+
// outlive ctx, e.g. if the reload is triggered by an API request.
503+
retryCtx, retryCancel := context.WithCancel(logger.DetachedContext(ctx))
504+
d.securityRetryCancel = retryCancel
505+
470506
var errs []error
471507

472508
// UnixSocket authenticator is always available.
@@ -477,7 +513,7 @@ func (d *Daemon) securityConfigReload(ctx context.Context, cfg apisystem.Securit
477513
// Setup OIDC authentication.
478514
if cfg.OIDC.Issuer != "" && cfg.OIDC.ClientID != "" {
479515
var err error
480-
newOIDCVerifier, err := authnoidc.NewVerifier(context.TODO(), cfg.OIDC.Issuer, cfg.OIDC.ClientID, cfg.OIDC.Scope, cfg.OIDC.Audience, cfg.OIDC.Claim)
516+
newOIDCVerifier, err := authnoidc.NewVerifier(retryCtx, cfg.OIDC.Issuer, cfg.OIDC.ClientID, cfg.OIDC.Scope, cfg.OIDC.Audience, cfg.OIDC.Claim)
481517
if err != nil {
482518
errs = append(errs, err)
483519
} else {
@@ -509,7 +545,7 @@ func (d *Daemon) securityConfigReload(ctx context.Context, cfg apisystem.Securit
509545
}
510546

511547
if cfg.OpenFGA.APIURL != "" && cfg.OpenFGA.APIToken != "" && cfg.OpenFGA.StoreID != "" {
512-
openfgaAuthorizer, err := authzopenfga.New(ctx, cfg.OpenFGA.APIURL, cfg.OpenFGA.APIToken, cfg.OpenFGA.StoreID)
548+
openfgaAuthorizer, err := authzopenfga.New(retryCtx, cfg.OpenFGA.APIURL, cfg.OpenFGA.APIToken, cfg.OpenFGA.StoreID)
513549
if err != nil {
514550
errs = append(errs, err)
515551
} else {
@@ -1717,6 +1753,13 @@ func (d *Daemon) incusOSSelfPoll(ctx context.Context, serverSvc provisioning.Ser
17171753
}
17181754

17191755
func (d *Daemon) Stop(ctx context.Context) error {
1756+
d.configReloadMu.Lock()
1757+
if d.securityRetryCancel != nil {
1758+
d.securityRetryCancel()
1759+
}
1760+
1761+
d.configReloadMu.Unlock()
1762+
17201763
errs := make([]error, 0, len(d.shutdownFuncs)+1)
17211764

17221765
for _, shutdown := range d.shutdownFuncs {
@@ -1732,6 +1775,7 @@ func (d *Daemon) Stop(ctx context.Context) error {
17321775
// Remove all signal listeners.
17331776
lifecycle.ServerCertificateUpdateSignal.Reset()
17341777
lifecycle.NetworkUpdateSignal.Reset()
1778+
lifecycle.SecurityValidateSignal.Reset()
17351779
lifecycle.SecurityUpdateSignal.Reset()
17361780
lifecycle.SecurityTrustedHTTPSProxiesUpdateSignal.Reset()
17371781
lifecycle.SecurityACMEUpdateSignal.Reset()

0 commit comments

Comments
 (0)