Skip to content

Commit 44b32e0

Browse files
authored
group access management (#412)
Signed-off-by: Frank Jogeleit <[email protected]>
1 parent 502af1e commit 44b32e0

File tree

12 files changed

+107
-30
lines changed

12 files changed

+107
-30
lines changed

backend/pkg/auth/handler.go

+12-5
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ import (
1010
)
1111

1212
type Handler struct {
13-
basePath string
13+
basePath string
14+
groupClaim string
1415
}
1516

16-
func NewHandler(basePath string) *Handler {
17-
return &Handler{basePath: basePath}
17+
func NewHandler(basePath, groupClaim string) *Handler {
18+
return &Handler{basePath: basePath, groupClaim: groupClaim}
1819
}
1920

2021
func (h *Handler) Callback(ctx *gin.Context) {
@@ -25,8 +26,11 @@ func (h *Handler) Callback(ctx *gin.Context) {
2526
return
2627
}
2728

29+
profile := NewProfile(user)
30+
profile.AssignGroups(mapGroups(user, h.groupClaim))
31+
2832
session := sessions.Default(ctx)
29-
session.Set("profile", NewProfile(user))
33+
session.Set("profile", profile)
3034

3135
if err := session.Save(); err != nil {
3236
zap.L().Error("failed to save session", zap.Error(err))
@@ -44,8 +48,11 @@ func (h *Handler) Login(ctx *gin.Context) {
4448
return
4549
}
4650

51+
profile := NewProfile(user)
52+
profile.AssignGroups(mapGroups(user, h.groupClaim))
53+
4754
session := sessions.Default(ctx)
48-
session.Set("profile", NewProfile(user))
55+
session.Set("profile", profile)
4956

5057
ctx.Redirect(http.StatusTemporaryRedirect, h.basePath)
5158
}

backend/pkg/auth/helper.go

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package auth
2+
3+
import (
4+
"github.com/markbates/goth"
5+
)
6+
7+
func mapGroups(user goth.User, claim string) []string {
8+
groups := make([]string, 0)
9+
10+
if claim == "" {
11+
return groups
12+
}
13+
14+
rawGroups, ok := user.RawData[claim]
15+
if !ok {
16+
return groups
17+
}
18+
19+
mapped, ok := rawGroups.([]any)
20+
if !ok {
21+
return groups
22+
}
23+
24+
for _, group := range mapped {
25+
if g, ok := group.(string); ok {
26+
groups = append(groups, g)
27+
}
28+
}
29+
30+
return groups
31+
}

backend/pkg/auth/middleware.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func Provider(provider string) gin.HandlerFunc {
2626
}
2727
}
2828

29-
func Valid(basePath string) gin.HandlerFunc {
29+
func Valid(basePath, groupClaim string) gin.HandlerFunc {
3030
return func(ctx *gin.Context) {
3131
providerName, err := gothic.GetProviderName(ctx.Request)
3232
if err != nil {
@@ -71,7 +71,10 @@ func Valid(basePath string) gin.HandlerFunc {
7171
return
7272
}
7373

74-
session.Set("profile", NewProfile(user))
74+
newProfile := NewProfile(user)
75+
newProfile.AssignGroups(mapGroups(user, groupClaim))
76+
77+
session.Set("profile", newProfile)
7578
if err := session.Save(); err != nil {
7679
zap.L().Error("failed to save profile session", zap.Error(err))
7780
}

backend/pkg/auth/model.go

+5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type Profile struct {
1616
Firstname string `json:"firstname"`
1717
Name string `json:"name"`
1818
Email string `json:"email"`
19+
Groups []string `json:"groups"`
1920
RefreshToken string `json:"-"`
2021
AccessToken string `json:"-"`
2122
IDToken string `json:"-"`
@@ -34,6 +35,10 @@ func (p *Profile) GetName() string {
3435
return p.Email
3536
}
3637

38+
func (p *Profile) AssignGroups(groups []string) {
39+
p.Groups = groups
40+
}
41+
3742
func NewProfile(user goth.User) Profile {
3843
return Profile{
3944
ID: user.UserID,

backend/pkg/auth/permissions.go

+15-4
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,33 @@ import (
66
"strings"
77

88
"github.com/gin-gonic/gin"
9+
10+
"github.com/kyverno/policy-reporter-ui/pkg/utils"
911
)
1012

1113
type AccessControl struct {
1214
Emails []string
15+
Groups []string
1316
}
1417

1518
type Permissions struct {
1619
AccessControl AccessControl `json:"-"`
1720
}
1821

19-
func (p Permissions) AllowedEmail(email string) bool {
20-
if len(p.AccessControl.Emails) == 0 {
22+
func (p Permissions) Allowed(profile *Profile) bool {
23+
if len(p.AccessControl.Emails) == 0 && len(p.AccessControl.Groups) == 0 {
24+
return true
25+
}
26+
27+
if len(p.AccessControl.Emails) > 0 && slices.Contains(p.AccessControl.Emails, profile.Email) {
28+
return true
29+
}
30+
31+
if len(p.AccessControl.Groups) > 0 && utils.Some(p.AccessControl.Groups, profile.Groups) {
2132
return true
2233
}
2334

24-
return slices.Contains(p.AccessControl.Emails, email)
35+
return false
2536
}
2637

2738
func ClusterPermissions(permissions map[string]Permissions) gin.HandlerFunc {
@@ -38,7 +49,7 @@ func ClusterPermissions(permissions map[string]Permissions) gin.HandlerFunc {
3849
}
3950

4051
if profile := ProfileFrom(ctx); profile != nil {
41-
if !permissions[cluster].AllowedEmail(profile.Email) {
52+
if !permissions[cluster].Allowed(profile) {
4253
ctx.AbortWithStatus(http.StatusUnauthorized)
4354
return
4455
}

backend/pkg/auth/router.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212

1313
const SessionKey = "auth-session"
1414

15-
func Setup(engine *gin.Engine, basePath, provider, tempDir string) {
15+
func Setup(engine *gin.Engine, basePath, groupKey, provider, tempDir string) {
1616
gob.Register(Profile{})
1717
gob.Register(map[string]any{})
1818

@@ -28,7 +28,7 @@ func Setup(engine *gin.Engine, basePath, provider, tempDir string) {
2828

2929
engine.Use(sessions.Sessions(SessionKey, NewStore(authStore)))
3030

31-
handler := NewHandler(basePath)
31+
handler := NewHandler(basePath, groupKey)
3232

3333
engine.GET("/login", Provider(provider), handler.Login)
3434
engine.GET("/logout", Provider(provider), handler.Logout)

backend/pkg/config/config.go

+10
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type OpenIDConnect struct {
2424
CallbackURL string `mapstructure:"callbackUrl"`
2525
ClientID string `mapstructure:"clientId"`
2626
ClientSecret string `mapstructure:"clientSecret"`
27+
GroupClaim string `mapstructure:"groupClaim"`
2728
Scopes []string `mapstructure:"scopes"`
2829
}
2930

@@ -191,6 +192,7 @@ type Source struct {
191192

192193
type AccessControl struct {
193194
Emails []string `mapstructure:"emails"`
195+
Groups []string `mapstructure:"groups"`
194196
}
195197

196198
type Boards struct {
@@ -243,3 +245,11 @@ func (c *Config) AuthBasePath() string {
243245

244246
return c.OpenIDConnect.BasePath()
245247
}
248+
249+
func (c *Config) AuthGroupClaim() string {
250+
if c.OAuth.Enabled {
251+
return ""
252+
}
253+
254+
return c.OpenIDConnect.GroupClaim
255+
}

backend/pkg/config/mapper.go

+2-6
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,7 @@ func MapConfig(c *Config) *api.Config {
4343
Banner: c.UI.Banner,
4444
Boards: api.Boards{
4545
Permissions: auth.Permissions{
46-
AccessControl: auth.AccessControl{
47-
Emails: c.Boards.AccessControl.Emails,
48-
},
46+
AccessControl: auth.AccessControl(c.Boards.AccessControl),
4947
},
5048
},
5149
Sources: utils.Map(c.Sources, func(s Source) api.Source {
@@ -81,9 +79,7 @@ func MapCustomBoards(customBoards []CustomBoard) map[string]api.CustomBoard {
8179
List: c.Sources.List,
8280
},
8381
Permissions: auth.Permissions{
84-
AccessControl: auth.AccessControl{
85-
Emails: c.AccessControl.Emails,
86-
},
82+
AccessControl: auth.AccessControl(c.AccessControl),
8783
},
8884
PolicyReports: api.PolicyReports{
8985
Selector: c.PolicyReports.Selector,

backend/pkg/config/resolver.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ func (r *Resolver) SetupOAuth(ctx context.Context, engine *gin.Engine) ([]gin.Ha
248248
}
249249

250250
goth.UseProviders(provider)
251-
auth.Setup(engine, r.config.OAuth.BasePath(), config.Provider, r.config.TempDir)
251+
auth.Setup(engine, r.config.OAuth.BasePath(), r.config.AuthGroupClaim(), config.Provider, r.config.TempDir)
252252

253253
return []gin.HandlerFunc{auth.Provider(r.config.OAuth.Provider), auth.Auth(r.config.OAuth.BasePath())}, nil
254254
}
@@ -272,7 +272,7 @@ func (r *Resolver) SetupOIDC(ctx context.Context, engine *gin.Engine) ([]gin.Han
272272

273273
goth.UseProviders(provider)
274274

275-
auth.Setup(engine, r.config.OpenIDConnect.BasePath(), "openid-connect", r.config.TempDir)
275+
auth.Setup(engine, r.config.OpenIDConnect.BasePath(), r.config.AuthGroupClaim(), "openid-connect", r.config.TempDir)
276276

277277
return []gin.HandlerFunc{auth.Provider("openid-connect"), auth.Auth(r.config.OpenIDConnect.BasePath())}, nil
278278
}
@@ -369,7 +369,7 @@ func (r *Resolver) Server(ctx context.Context) (*server.Server, error) {
369369
if !r.config.UI.Disabled {
370370
var uiMiddleware []gin.HandlerFunc
371371
if r.config.AuthEnabled() {
372-
uiMiddleware = append(uiMiddleware, auth.Valid(r.config.AuthBasePath()))
372+
uiMiddleware = append(uiMiddleware, auth.Valid(r.config.AuthBasePath(), r.config.AuthGroupClaim()))
373373
}
374374

375375
zap.L().Info("register UI", zap.String("path", r.config.UI.Path))

backend/pkg/server/api/handler.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func (h *Handler) Config(ctx *gin.Context) {
3636

3737
clusters := make([]Cluster, 0, len(h.config.Clusters))
3838
for _, cl := range h.config.Clusters {
39-
access := cl.AllowedEmail(profile.Email)
39+
access := cl.Allowed(profile)
4040
if access {
4141
clusters = append(clusters, cl)
4242
}
@@ -69,7 +69,7 @@ func (h *Handler) ListCustomBoards(ctx *gin.Context) {
6969

7070
func (h *Handler) ListPolicySources(ctx *gin.Context) {
7171
if profile := auth.ProfileFrom(ctx); profile != nil {
72-
if !h.config.Boards.AllowedEmail(profile.Email) {
72+
if !h.config.Boards.Allowed(profile) {
7373
ctx.AbortWithStatus(http.StatusUnauthorized)
7474
return
7575
}
@@ -153,7 +153,7 @@ func (h *Handler) GetCustomBoard(ctx *gin.Context) {
153153
}
154154

155155
if profile := auth.ProfileFrom(ctx); profile != nil {
156-
if !config.AllowedEmail(profile.Email) {
156+
if !config.Allowed(profile) {
157157
ctx.AbortWithStatus(http.StatusUnauthorized)
158158
return
159159
}
@@ -252,14 +252,14 @@ func (h *Handler) Layout(ctx *gin.Context) {
252252
boards = make(map[string]CustomBoard, len(h.customBoards))
253253

254254
for key, board := range h.customBoards {
255-
if !board.AllowedEmail(profile.Email) {
255+
if !board.Allowed(profile) {
256256
continue
257257
}
258258

259259
boards[key] = board
260260
}
261261

262-
if !h.config.Boards.AllowedEmail(profile.Email) {
262+
if !h.config.Boards.Allowed(profile) {
263263
sources = nil
264264
}
265265
} else {
@@ -277,7 +277,7 @@ func (h *Handler) Layout(ctx *gin.Context) {
277277

278278
func (h *Handler) Dashboard(ctx *gin.Context) {
279279
if profile := auth.ProfileFrom(ctx); profile != nil {
280-
if !h.config.Boards.AllowedEmail(profile.Email) {
280+
if !h.config.Boards.Allowed(profile) {
281281
ctx.AbortWithStatus(http.StatusUnauthorized)
282282
return
283283
}
@@ -334,7 +334,7 @@ func (h *Handler) Dashboard(ctx *gin.Context) {
334334

335335
func (h *Handler) Policies(ctx *gin.Context) {
336336
if profile := auth.ProfileFrom(ctx); profile != nil {
337-
if !h.config.Boards.AllowedEmail(profile.Email) {
337+
if !h.config.Boards.Allowed(profile) {
338338
ctx.AbortWithStatus(http.StatusUnauthorized)
339339
return
340340
}

backend/pkg/server/api/model.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package api
22

3-
import "github.com/kyverno/policy-reporter-ui/pkg/auth"
3+
import (
4+
"github.com/kyverno/policy-reporter-ui/pkg/auth"
5+
)
46

57
type Policy struct {
68
Source string `json:"source,omitempty"`

backend/pkg/utils/contains.go

+12
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,15 @@ func Contains[T comparable](list []T, item T) bool {
99

1010
return false
1111
}
12+
13+
func Some[T comparable](list []T, items []T) bool {
14+
for _, i := range list {
15+
for _, j := range items {
16+
if i == j {
17+
return true
18+
}
19+
}
20+
}
21+
22+
return false
23+
}

0 commit comments

Comments
 (0)