Skip to content

Commit

Permalink
Cluster permissions (#410)
Browse files Browse the repository at this point in the history
* Support access control for cluster access

Signed-off-by: Frank Jogeleit <frank.jogeleit@web.de>
  • Loading branch information
fjogeleit authored Oct 14, 2024
1 parent ff9872a commit 502af1e
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 45 deletions.
49 changes: 49 additions & 0 deletions backend/pkg/auth/permissions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package auth

import (
"net/http"
"slices"
"strings"

"github.com/gin-gonic/gin"
)

type AccessControl struct {
Emails []string
}

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

func (p Permissions) AllowedEmail(email string) bool {
if len(p.AccessControl.Emails) == 0 {
return true
}

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

func ClusterPermissions(permissions map[string]Permissions) gin.HandlerFunc {
return func(ctx *gin.Context) {
cluster := ctx.Param("cluster")
if cluster == "" && strings.HasPrefix(ctx.Request.URL.Path, "/proxy/") {
parts := strings.Split(ctx.Request.URL.Path, "/")
cluster = strings.TrimSpace(parts[2])
}

if cluster == "" {
ctx.Next()
return
}

if profile := ProfileFrom(ctx); profile != nil {
if !permissions[cluster].AllowedEmail(profile.Email) {
ctx.AbortWithStatus(http.StatusUnauthorized)
return
}
}

ctx.Next()
}
}
17 changes: 9 additions & 8 deletions backend/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,16 @@ func (a Plugin) FromValues(values secrets.Values) Plugin {
return a
}

// APISetup configuration
// Cluster configuration
type Cluster struct {
Name string `mapstructure:"name"`
Host string `mapstructure:"host"`
Plugins []Plugin `mapstructure:"plugins"`
SkipTLS bool `mapstructure:"skipTLS"`
Certificate string `mapstructure:"certificate"`
SecretRef string `mapstructure:"secretRef"`
BasicAuth BasicAuth `mapstructure:"basicAuth"`
Name string `mapstructure:"name"`
Host string `mapstructure:"host"`
Plugins []Plugin `mapstructure:"plugins"`
SkipTLS bool `mapstructure:"skipTLS"`
Certificate string `mapstructure:"certificate"`
SecretRef string `mapstructure:"secretRef"`
BasicAuth BasicAuth `mapstructure:"basicAuth"`
AccessControl AccessControl `mapstructure:"accessControl"`
}

func (a Cluster) FromValues(values secrets.Values) Cluster {
Expand Down
23 changes: 19 additions & 4 deletions backend/pkg/config/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"github.com/gosimple/slug"

"github.com/kyverno/policy-reporter-ui/pkg/auth"
"github.com/kyverno/policy-reporter-ui/pkg/server/api"
"github.com/kyverno/policy-reporter-ui/pkg/utils"
)
Expand All @@ -19,6 +20,9 @@ func MapConfig(c *Config) *api.Config {
Name: cl.Name,
Slug: slug.Make(cl.Name),
Plugins: plugins,
Permissions: auth.Permissions{
AccessControl: auth.AccessControl(cl.AccessControl),
},
})
}

Expand All @@ -38,8 +42,8 @@ func MapConfig(c *Config) *api.Config {
OAuth: oauth,
Banner: c.UI.Banner,
Boards: api.Boards{
Permissions: api.Permissions{
AccessControl: api.AccessControl{
Permissions: auth.Permissions{
AccessControl: auth.AccessControl{
Emails: c.Boards.AccessControl.Emails,
},
},
Expand Down Expand Up @@ -76,8 +80,8 @@ func MapCustomBoards(customBoards []CustomBoard) map[string]api.CustomBoard {
Sources: api.Sources{
List: c.Sources.List,
},
Permissions: api.Permissions{
AccessControl: api.AccessControl{
Permissions: auth.Permissions{
AccessControl: auth.AccessControl{
Emails: c.AccessControl.Emails,
},
},
Expand All @@ -90,3 +94,14 @@ func MapCustomBoards(customBoards []CustomBoard) map[string]api.CustomBoard {

return configs
}

func MapClusterPermissions(c *Config) map[string]auth.Permissions {
permissions := make(map[string]auth.Permissions, len(c.Clusters))
for _, cluster := range c.Clusters {
permissions[slug.Make(cluster.Name)] = auth.Permissions{
AccessControl: auth.AccessControl(cluster.AccessControl),
}
}

return permissions
}
9 changes: 5 additions & 4 deletions backend/pkg/config/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,10 @@ func (r *Resolver) Server(ctx context.Context) (*server.Server, error) {
middleware = append(middleware, gin.Recovery())
}

if r.config.AuthEnabled() {
middleware = append(middleware, auth.ClusterPermissions(MapClusterPermissions(r.config)))
}

serv := server.NewServer(engine, r.config.Server.Port, middleware)

for _, cluster := range r.config.Clusters {
Expand Down Expand Up @@ -372,10 +376,7 @@ func (r *Resolver) Server(ctx context.Context) (*server.Server, error) {
serv.RegisterUI(r.config.UI.Path, uiMiddleware)
}

serv.RegisterAPI(
MapConfig(r.config),
MapCustomBoards(r.config.CustomBoards),
)
serv.RegisterAPI(MapConfig(r.config), MapCustomBoards(r.config.CustomBoards))

return serv, nil
}
Expand Down
29 changes: 29 additions & 0 deletions backend/pkg/server/api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,35 @@ func (h *Handler) Healthz(ctx *gin.Context) {
}

func (h *Handler) Config(ctx *gin.Context) {
if profile := auth.ProfileFrom(ctx); profile != nil {
cluster := h.config.Default

clusters := make([]Cluster, 0, len(h.config.Clusters))
for _, cl := range h.config.Clusters {
access := cl.AllowedEmail(profile.Email)
if access {
clusters = append(clusters, cl)
}
if cluster == cl.Slug && !access {
cluster = ""
}
}

if cluster == "" && len(clusters) > 0 {
cluster = clusters[0].Slug
}

ctx.JSON(http.StatusOK, Config{
Clusters: clusters,
Default: cluster,
User: h.config.User,
Sources: h.config.Sources,
Banner: h.config.Banner,
OAuth: h.config.OAuth,
})
return
}

ctx.JSON(http.StatusOK, h.config)
}

Expand Down
41 changes: 13 additions & 28 deletions backend/pkg/server/api/model.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package api

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

type Policy struct {
Source string `json:"source,omitempty"`
Expand Down Expand Up @@ -33,9 +33,10 @@ type Source struct {
}

type Cluster struct {
Name string `json:"name"`
Slug string `json:"slug"`
Plugins []string `json:"plugins"`
auth.Permissions `json:"-"`
Name string `json:"name"`
Slug string `json:"slug"`
Plugins []string `json:"plugins"`
}

type PolicyReports struct {
Expand All @@ -51,34 +52,18 @@ type Sources struct {
List []string
}

type AccessControl struct {
Emails []string
}

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

func (p Permissions) AllowedEmail(email string) bool {
if len(p.AccessControl.Emails) == 0 {
return true
}

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

type CustomBoard struct {
Permissions
Name string `json:"name"`
ID string `json:"id"`
ClusterScope bool `json:"-"`
Namespaces Namespaces `json:"-"`
Sources Sources `json:"-"`
PolicyReports PolicyReports `json:"-"`
auth.Permissions `json:"-"`
Name string `json:"name"`
ID string `json:"id"`
ClusterScope bool `json:"-"`
Namespaces Namespaces `json:"-"`
Sources Sources `json:"-"`
PolicyReports PolicyReports `json:"-"`
}

type Boards struct {
Permissions
auth.Permissions
}

type Config struct {
Expand Down
2 changes: 1 addition & 1 deletion frontend/modules/core/components/form/ClusterSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
hide-details
density="compact"
prepend-inner-icon="mdi-kubernetes"
style="max-width: 140px;"
style="min-width: 140px;"
@update:model-value="input"
v-if="clusters.length > 1"
/>
Expand Down

0 comments on commit 502af1e

Please sign in to comment.