Skip to content

Commit 0a3e7bf

Browse files
authored
fix: use policy engine in oauth whitelist check (#904)
1 parent c346113 commit 0a3e7bf

7 files changed

Lines changed: 48 additions & 20 deletions

File tree

internal/bootstrap/service_bootstrap.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func (app *BootstrapApp) setupServices() error {
4242
oauthBrokerService := service.NewOAuthBrokerService(app.log, app.runtime.OAuthProviders, app.ctx)
4343
app.services.oauthBrokerService = oauthBrokerService
4444

45-
authService := service.NewAuthService(app.log, app.config, app.runtime, app.ctx, &app.wg, app.services.ldapService, app.queries, app.services.oauthBrokerService, app.services.tailscaleService)
45+
authService := service.NewAuthService(app.log, app.config, app.runtime, app.ctx, &app.wg, app.services.ldapService, app.queries, app.services.oauthBrokerService, app.services.tailscaleService, app.services.policyEngine)
4646
app.services.authService = authService
4747

4848
oidcService, err := service.NewOIDCService(app.log, app.config, app.runtime, app.queries, app.ctx, &app.wg)

internal/controller/proxy_controller_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,6 @@ func TestProxyController(t *testing.T) {
357357
ctx := context.TODO()
358358

359359
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
360-
authService := service.NewAuthService(log, cfg, runtime, ctx, wg, nil, store, broker, nil)
361360
aclsService := service.NewAccessControlsService(log, cfg, nil)
362361

363362
policyEngine, err := service.NewPolicyEngine(cfg, log)
@@ -383,6 +382,8 @@ func TestProxyController(t *testing.T) {
383382
Log: log,
384383
})
385384

385+
authService := service.NewAuthService(log, cfg, runtime, ctx, wg, nil, store, broker, nil, policyEngine)
386+
386387
for _, test := range tests {
387388
t.Run(test.description, func(t *testing.T) {
388389
router := gin.Default()

internal/controller/user_controller_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,8 +414,11 @@ func TestUserController(t *testing.T) {
414414
ctx := context.TODO()
415415
wg := &sync.WaitGroup{}
416416

417+
policyEngine, err := service.NewPolicyEngine(cfg, log)
418+
require.NoError(t, err)
419+
417420
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
418-
authService := service.NewAuthService(log, cfg, runtime, ctx, wg, nil, store, broker, nil)
421+
authService := service.NewAuthService(log, cfg, runtime, ctx, wg, nil, store, broker, nil, policyEngine)
419422

420423
beforeEach := func() {
421424
// Clear failed login attempts before each test

internal/middleware/context_middleware_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,11 @@ func TestContextMiddleware(t *testing.T) {
254254

255255
store := memory.New()
256256

257+
policyEngine, err := service.NewPolicyEngine(cfg, log)
258+
require.NoError(t, err)
259+
257260
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
258-
authService := service.NewAuthService(log, cfg, runtime, ctx, wg, nil, store, broker, nil)
261+
authService := service.NewAuthService(log, cfg, runtime, ctx, wg, nil, store, broker, nil, policyEngine)
259262

260263
contextMiddleware := middleware.NewContextMiddleware(log, runtime, authService, broker, nil)
261264

internal/service/auth_service.go

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,11 @@ type AuthService struct {
7575
runtime model.RuntimeConfig
7676
context context.Context
7777

78-
ldap *LdapService
79-
queries repository.Store
80-
oauthBroker *OAuthBrokerService
81-
tailscale *TailscaleService
78+
ldap *LdapService
79+
queries repository.Store
80+
oauthBroker *OAuthBrokerService
81+
tailscale *TailscaleService
82+
policyEngine *PolicyEngine
8283

8384
loginAttempts map[string]*LoginAttempt
8485
ldapGroupsCache map[string]*LdapGroupsCache
@@ -101,6 +102,7 @@ func NewAuthService(
101102
queries repository.Store,
102103
oauthBroker *OAuthBrokerService,
103104
tailscale *TailscaleService,
105+
policy *PolicyEngine,
104106
) *AuthService {
105107
service := &AuthService{
106108
log: log,
@@ -114,6 +116,7 @@ func NewAuthService(
114116
queries: queries,
115117
oauthBroker: oauthBroker,
116118
tailscale: tailscale,
119+
policyEngine: policy,
117120
}
118121

119122
wg.Go(service.CleanupOAuthSessionsRoutine)
@@ -285,18 +288,27 @@ func (auth *AuthService) RecordLoginAttempt(identifier string, success bool) {
285288
}
286289
}
287290

291+
// We could also directly access the policyEngine.effectToAccess but
292+
// I believe it's better to use the exported functions instead
288293
func (auth *AuthService) IsEmailWhitelisted(provider string, email string) bool {
289-
whitelist := auth.runtime.OAuthWhitelist
290-
if providerConfig, ok := auth.runtime.OAuthProviders[provider]; ok && len(providerConfig.Whitelist) > 0 {
291-
whitelist = providerConfig.Whitelist
292-
}
293-
294-
match, err := utils.CheckFilter(strings.Join(whitelist, ","), email)
295-
if err != nil {
296-
auth.log.App.Warn().Err(err).Str("provider", provider).Str("email", email).Msg("Invalid email filter pattern")
297-
return false
298-
}
299-
return match
294+
return auth.policyEngine.EvaluateFunc(func() Effect {
295+
whitelist := auth.runtime.OAuthWhitelist
296+
if providerConfig, ok := auth.runtime.OAuthProviders[provider]; ok && len(providerConfig.Whitelist) > 0 {
297+
whitelist = providerConfig.Whitelist
298+
}
299+
match, err := utils.CheckFilter(strings.Join(whitelist, ","), email)
300+
if err != nil {
301+
if err == utils.ErrFilterEmpty {
302+
return EffectAbstain
303+
}
304+
auth.log.App.Error().Err(err).Str("email", email).Msg("Failed to evaluate email whitelist filter, defaulting to deny")
305+
return EffectDeny
306+
}
307+
if match {
308+
return EffectAllow
309+
}
310+
return EffectDeny
311+
})
300312
}
301313

302314
func (auth *AuthService) CreateSession(ctx context.Context, data repository.Session) (*http.Cookie, error) {

internal/service/policy_engine.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,7 @@ func (engine *PolicyEngine) Policy() Policy {
108108
func (engine *PolicyEngine) Rules() map[RuleName]Rule {
109109
return engine.rules
110110
}
111+
112+
func (engine *PolicyEngine) EvaluateFunc(f func() Effect) bool {
113+
return engine.effectToAccess(f())
114+
}

internal/utils/security_utils.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package utils
33
import (
44
"crypto/rand"
55
"encoding/base64"
6+
"errors"
67
"fmt"
78
"net"
89
"regexp"
@@ -11,6 +12,10 @@ import (
1112
"github.com/google/uuid"
1213
)
1314

15+
var (
16+
ErrFilterEmpty = errors.New("filter is empty")
17+
)
18+
1419
func GetSecret(conf string, file string) string {
1520
if conf == "" && file == "" {
1621
return ""
@@ -78,7 +83,7 @@ func CheckIPFilter(filter string, ip string) (bool, error) {
7883

7984
func CheckFilter(filter string, input string) (bool, error) {
8085
if len(strings.TrimSpace(filter)) == 0 {
81-
return false, fmt.Errorf("filter is empty")
86+
return false, ErrFilterEmpty
8287
}
8388

8489
if strings.HasPrefix(filter, "/") && strings.HasSuffix(filter, "/") {

0 commit comments

Comments
 (0)