Skip to content

Commit

Permalink
group access management (#412)
Browse files Browse the repository at this point in the history
Signed-off-by: Frank Jogeleit <frank.jogeleit@web.de>
  • Loading branch information
fjogeleit authored Oct 14, 2024
1 parent 502af1e commit 44b32e0
Show file tree
Hide file tree
Showing 12 changed files with 107 additions and 30 deletions.
17 changes: 12 additions & 5 deletions backend/pkg/auth/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import (
)

type Handler struct {
basePath string
basePath string
groupClaim string
}

func NewHandler(basePath string) *Handler {
return &Handler{basePath: basePath}
func NewHandler(basePath, groupClaim string) *Handler {
return &Handler{basePath: basePath, groupClaim: groupClaim}
}

func (h *Handler) Callback(ctx *gin.Context) {
Expand All @@ -25,8 +26,11 @@ func (h *Handler) Callback(ctx *gin.Context) {
return
}

profile := NewProfile(user)
profile.AssignGroups(mapGroups(user, h.groupClaim))

session := sessions.Default(ctx)
session.Set("profile", NewProfile(user))
session.Set("profile", profile)

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

profile := NewProfile(user)
profile.AssignGroups(mapGroups(user, h.groupClaim))

session := sessions.Default(ctx)
session.Set("profile", NewProfile(user))
session.Set("profile", profile)

ctx.Redirect(http.StatusTemporaryRedirect, h.basePath)
}
Expand Down
31 changes: 31 additions & 0 deletions backend/pkg/auth/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package auth

import (
"github.com/markbates/goth"
)

func mapGroups(user goth.User, claim string) []string {
groups := make([]string, 0)

if claim == "" {
return groups
}

rawGroups, ok := user.RawData[claim]
if !ok {
return groups
}

mapped, ok := rawGroups.([]any)
if !ok {
return groups
}

for _, group := range mapped {
if g, ok := group.(string); ok {
groups = append(groups, g)
}
}

return groups
}
7 changes: 5 additions & 2 deletions backend/pkg/auth/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func Provider(provider string) gin.HandlerFunc {
}
}

func Valid(basePath string) gin.HandlerFunc {
func Valid(basePath, groupClaim string) gin.HandlerFunc {
return func(ctx *gin.Context) {
providerName, err := gothic.GetProviderName(ctx.Request)
if err != nil {
Expand Down Expand Up @@ -71,7 +71,10 @@ func Valid(basePath string) gin.HandlerFunc {
return
}

session.Set("profile", NewProfile(user))
newProfile := NewProfile(user)
newProfile.AssignGroups(mapGroups(user, groupClaim))

session.Set("profile", newProfile)
if err := session.Save(); err != nil {
zap.L().Error("failed to save profile session", zap.Error(err))
}
Expand Down
5 changes: 5 additions & 0 deletions backend/pkg/auth/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Profile struct {
Firstname string `json:"firstname"`
Name string `json:"name"`
Email string `json:"email"`
Groups []string `json:"groups"`
RefreshToken string `json:"-"`
AccessToken string `json:"-"`
IDToken string `json:"-"`
Expand All @@ -34,6 +35,10 @@ func (p *Profile) GetName() string {
return p.Email
}

func (p *Profile) AssignGroups(groups []string) {
p.Groups = groups
}

func NewProfile(user goth.User) Profile {
return Profile{
ID: user.UserID,
Expand Down
19 changes: 15 additions & 4 deletions backend/pkg/auth/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,33 @@ import (
"strings"

"github.com/gin-gonic/gin"

"github.com/kyverno/policy-reporter-ui/pkg/utils"
)

type AccessControl struct {
Emails []string
Groups []string
}

type Permissions struct {
AccessControl AccessControl `json:"-"`
}

func (p Permissions) AllowedEmail(email string) bool {
if len(p.AccessControl.Emails) == 0 {
func (p Permissions) Allowed(profile *Profile) bool {
if len(p.AccessControl.Emails) == 0 && len(p.AccessControl.Groups) == 0 {
return true
}

if len(p.AccessControl.Emails) > 0 && slices.Contains(p.AccessControl.Emails, profile.Email) {
return true
}

if len(p.AccessControl.Groups) > 0 && utils.Some(p.AccessControl.Groups, profile.Groups) {
return true
}

return slices.Contains(p.AccessControl.Emails, email)
return false
}

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

if profile := ProfileFrom(ctx); profile != nil {
if !permissions[cluster].AllowedEmail(profile.Email) {
if !permissions[cluster].Allowed(profile) {
ctx.AbortWithStatus(http.StatusUnauthorized)
return
}
Expand Down
4 changes: 2 additions & 2 deletions backend/pkg/auth/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

const SessionKey = "auth-session"

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

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

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

handler := NewHandler(basePath)
handler := NewHandler(basePath, groupKey)

engine.GET("/login", Provider(provider), handler.Login)
engine.GET("/logout", Provider(provider), handler.Logout)
Expand Down
10 changes: 10 additions & 0 deletions backend/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type OpenIDConnect struct {
CallbackURL string `mapstructure:"callbackUrl"`
ClientID string `mapstructure:"clientId"`
ClientSecret string `mapstructure:"clientSecret"`
GroupClaim string `mapstructure:"groupClaim"`
Scopes []string `mapstructure:"scopes"`
}

Expand Down Expand Up @@ -191,6 +192,7 @@ type Source struct {

type AccessControl struct {
Emails []string `mapstructure:"emails"`
Groups []string `mapstructure:"groups"`
}

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

return c.OpenIDConnect.BasePath()
}

func (c *Config) AuthGroupClaim() string {
if c.OAuth.Enabled {
return ""
}

return c.OpenIDConnect.GroupClaim
}
8 changes: 2 additions & 6 deletions backend/pkg/config/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ func MapConfig(c *Config) *api.Config {
Banner: c.UI.Banner,
Boards: api.Boards{
Permissions: auth.Permissions{
AccessControl: auth.AccessControl{
Emails: c.Boards.AccessControl.Emails,
},
AccessControl: auth.AccessControl(c.Boards.AccessControl),
},
},
Sources: utils.Map(c.Sources, func(s Source) api.Source {
Expand Down Expand Up @@ -81,9 +79,7 @@ func MapCustomBoards(customBoards []CustomBoard) map[string]api.CustomBoard {
List: c.Sources.List,
},
Permissions: auth.Permissions{
AccessControl: auth.AccessControl{
Emails: c.AccessControl.Emails,
},
AccessControl: auth.AccessControl(c.AccessControl),
},
PolicyReports: api.PolicyReports{
Selector: c.PolicyReports.Selector,
Expand Down
6 changes: 3 additions & 3 deletions backend/pkg/config/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ func (r *Resolver) SetupOAuth(ctx context.Context, engine *gin.Engine) ([]gin.Ha
}

goth.UseProviders(provider)
auth.Setup(engine, r.config.OAuth.BasePath(), config.Provider, r.config.TempDir)
auth.Setup(engine, r.config.OAuth.BasePath(), r.config.AuthGroupClaim(), config.Provider, r.config.TempDir)

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

goth.UseProviders(provider)

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

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

zap.L().Info("register UI", zap.String("path", r.config.UI.Path))
Expand Down
14 changes: 7 additions & 7 deletions backend/pkg/server/api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func (h *Handler) Config(ctx *gin.Context) {

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

func (h *Handler) ListPolicySources(ctx *gin.Context) {
if profile := auth.ProfileFrom(ctx); profile != nil {
if !h.config.Boards.AllowedEmail(profile.Email) {
if !h.config.Boards.Allowed(profile) {
ctx.AbortWithStatus(http.StatusUnauthorized)
return
}
Expand Down Expand Up @@ -153,7 +153,7 @@ func (h *Handler) GetCustomBoard(ctx *gin.Context) {
}

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

for key, board := range h.customBoards {
if !board.AllowedEmail(profile.Email) {
if !board.Allowed(profile) {
continue
}

boards[key] = board
}

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

func (h *Handler) Dashboard(ctx *gin.Context) {
if profile := auth.ProfileFrom(ctx); profile != nil {
if !h.config.Boards.AllowedEmail(profile.Email) {
if !h.config.Boards.Allowed(profile) {
ctx.AbortWithStatus(http.StatusUnauthorized)
return
}
Expand Down Expand Up @@ -334,7 +334,7 @@ func (h *Handler) Dashboard(ctx *gin.Context) {

func (h *Handler) Policies(ctx *gin.Context) {
if profile := auth.ProfileFrom(ctx); profile != nil {
if !h.config.Boards.AllowedEmail(profile.Email) {
if !h.config.Boards.Allowed(profile) {
ctx.AbortWithStatus(http.StatusUnauthorized)
return
}
Expand Down
4 changes: 3 additions & 1 deletion backend/pkg/server/api/model.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package api

import "github.com/kyverno/policy-reporter-ui/pkg/auth"
import (
"github.com/kyverno/policy-reporter-ui/pkg/auth"
)

type Policy struct {
Source string `json:"source,omitempty"`
Expand Down
12 changes: 12 additions & 0 deletions backend/pkg/utils/contains.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,15 @@ func Contains[T comparable](list []T, item T) bool {

return false
}

func Some[T comparable](list []T, items []T) bool {
for _, i := range list {
for _, j := range items {
if i == j {
return true
}
}
}

return false
}

0 comments on commit 44b32e0

Please sign in to comment.