Skip to content

Commit 502af1e

Browse files
authored
Cluster permissions (#410)
* Support access control for cluster access Signed-off-by: Frank Jogeleit <[email protected]>
1 parent ff9872a commit 502af1e

File tree

7 files changed

+125
-45
lines changed

7 files changed

+125
-45
lines changed

backend/pkg/auth/permissions.go

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package auth
2+
3+
import (
4+
"net/http"
5+
"slices"
6+
"strings"
7+
8+
"github.com/gin-gonic/gin"
9+
)
10+
11+
type AccessControl struct {
12+
Emails []string
13+
}
14+
15+
type Permissions struct {
16+
AccessControl AccessControl `json:"-"`
17+
}
18+
19+
func (p Permissions) AllowedEmail(email string) bool {
20+
if len(p.AccessControl.Emails) == 0 {
21+
return true
22+
}
23+
24+
return slices.Contains(p.AccessControl.Emails, email)
25+
}
26+
27+
func ClusterPermissions(permissions map[string]Permissions) gin.HandlerFunc {
28+
return func(ctx *gin.Context) {
29+
cluster := ctx.Param("cluster")
30+
if cluster == "" && strings.HasPrefix(ctx.Request.URL.Path, "/proxy/") {
31+
parts := strings.Split(ctx.Request.URL.Path, "/")
32+
cluster = strings.TrimSpace(parts[2])
33+
}
34+
35+
if cluster == "" {
36+
ctx.Next()
37+
return
38+
}
39+
40+
if profile := ProfileFrom(ctx); profile != nil {
41+
if !permissions[cluster].AllowedEmail(profile.Email) {
42+
ctx.AbortWithStatus(http.StatusUnauthorized)
43+
return
44+
}
45+
}
46+
47+
ctx.Next()
48+
}
49+
}

backend/pkg/config/config.go

+9-8
Original file line numberDiff line numberDiff line change
@@ -118,15 +118,16 @@ func (a Plugin) FromValues(values secrets.Values) Plugin {
118118
return a
119119
}
120120

121-
// APISetup configuration
121+
// Cluster configuration
122122
type Cluster struct {
123-
Name string `mapstructure:"name"`
124-
Host string `mapstructure:"host"`
125-
Plugins []Plugin `mapstructure:"plugins"`
126-
SkipTLS bool `mapstructure:"skipTLS"`
127-
Certificate string `mapstructure:"certificate"`
128-
SecretRef string `mapstructure:"secretRef"`
129-
BasicAuth BasicAuth `mapstructure:"basicAuth"`
123+
Name string `mapstructure:"name"`
124+
Host string `mapstructure:"host"`
125+
Plugins []Plugin `mapstructure:"plugins"`
126+
SkipTLS bool `mapstructure:"skipTLS"`
127+
Certificate string `mapstructure:"certificate"`
128+
SecretRef string `mapstructure:"secretRef"`
129+
BasicAuth BasicAuth `mapstructure:"basicAuth"`
130+
AccessControl AccessControl `mapstructure:"accessControl"`
130131
}
131132

132133
func (a Cluster) FromValues(values secrets.Values) Cluster {

backend/pkg/config/mapper.go

+19-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package config
33
import (
44
"github.com/gosimple/slug"
55

6+
"github.com/kyverno/policy-reporter-ui/pkg/auth"
67
"github.com/kyverno/policy-reporter-ui/pkg/server/api"
78
"github.com/kyverno/policy-reporter-ui/pkg/utils"
89
)
@@ -19,6 +20,9 @@ func MapConfig(c *Config) *api.Config {
1920
Name: cl.Name,
2021
Slug: slug.Make(cl.Name),
2122
Plugins: plugins,
23+
Permissions: auth.Permissions{
24+
AccessControl: auth.AccessControl(cl.AccessControl),
25+
},
2226
})
2327
}
2428

@@ -38,8 +42,8 @@ func MapConfig(c *Config) *api.Config {
3842
OAuth: oauth,
3943
Banner: c.UI.Banner,
4044
Boards: api.Boards{
41-
Permissions: api.Permissions{
42-
AccessControl: api.AccessControl{
45+
Permissions: auth.Permissions{
46+
AccessControl: auth.AccessControl{
4347
Emails: c.Boards.AccessControl.Emails,
4448
},
4549
},
@@ -76,8 +80,8 @@ func MapCustomBoards(customBoards []CustomBoard) map[string]api.CustomBoard {
7680
Sources: api.Sources{
7781
List: c.Sources.List,
7882
},
79-
Permissions: api.Permissions{
80-
AccessControl: api.AccessControl{
83+
Permissions: auth.Permissions{
84+
AccessControl: auth.AccessControl{
8185
Emails: c.AccessControl.Emails,
8286
},
8387
},
@@ -90,3 +94,14 @@ func MapCustomBoards(customBoards []CustomBoard) map[string]api.CustomBoard {
9094

9195
return configs
9296
}
97+
98+
func MapClusterPermissions(c *Config) map[string]auth.Permissions {
99+
permissions := make(map[string]auth.Permissions, len(c.Clusters))
100+
for _, cluster := range c.Clusters {
101+
permissions[slug.Make(cluster.Name)] = auth.Permissions{
102+
AccessControl: auth.AccessControl(cluster.AccessControl),
103+
}
104+
}
105+
106+
return permissions
107+
}

backend/pkg/config/resolver.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,10 @@ func (r *Resolver) Server(ctx context.Context) (*server.Server, error) {
315315
middleware = append(middleware, gin.Recovery())
316316
}
317317

318+
if r.config.AuthEnabled() {
319+
middleware = append(middleware, auth.ClusterPermissions(MapClusterPermissions(r.config)))
320+
}
321+
318322
serv := server.NewServer(engine, r.config.Server.Port, middleware)
319323

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

375-
serv.RegisterAPI(
376-
MapConfig(r.config),
377-
MapCustomBoards(r.config.CustomBoards),
378-
)
379+
serv.RegisterAPI(MapConfig(r.config), MapCustomBoards(r.config.CustomBoards))
379380

380381
return serv, nil
381382
}

backend/pkg/server/api/handler.go

+29
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,35 @@ func (h *Handler) Healthz(ctx *gin.Context) {
3131
}
3232

3333
func (h *Handler) Config(ctx *gin.Context) {
34+
if profile := auth.ProfileFrom(ctx); profile != nil {
35+
cluster := h.config.Default
36+
37+
clusters := make([]Cluster, 0, len(h.config.Clusters))
38+
for _, cl := range h.config.Clusters {
39+
access := cl.AllowedEmail(profile.Email)
40+
if access {
41+
clusters = append(clusters, cl)
42+
}
43+
if cluster == cl.Slug && !access {
44+
cluster = ""
45+
}
46+
}
47+
48+
if cluster == "" && len(clusters) > 0 {
49+
cluster = clusters[0].Slug
50+
}
51+
52+
ctx.JSON(http.StatusOK, Config{
53+
Clusters: clusters,
54+
Default: cluster,
55+
User: h.config.User,
56+
Sources: h.config.Sources,
57+
Banner: h.config.Banner,
58+
OAuth: h.config.OAuth,
59+
})
60+
return
61+
}
62+
3463
ctx.JSON(http.StatusOK, h.config)
3564
}
3665

backend/pkg/server/api/model.go

+13-28
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package api
22

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

55
type Policy struct {
66
Source string `json:"source,omitempty"`
@@ -33,9 +33,10 @@ type Source struct {
3333
}
3434

3535
type Cluster struct {
36-
Name string `json:"name"`
37-
Slug string `json:"slug"`
38-
Plugins []string `json:"plugins"`
36+
auth.Permissions `json:"-"`
37+
Name string `json:"name"`
38+
Slug string `json:"slug"`
39+
Plugins []string `json:"plugins"`
3940
}
4041

4142
type PolicyReports struct {
@@ -51,34 +52,18 @@ type Sources struct {
5152
List []string
5253
}
5354

54-
type AccessControl struct {
55-
Emails []string
56-
}
57-
58-
type Permissions struct {
59-
AccessControl AccessControl `json:"-"`
60-
}
61-
62-
func (p Permissions) AllowedEmail(email string) bool {
63-
if len(p.AccessControl.Emails) == 0 {
64-
return true
65-
}
66-
67-
return slices.Contains(p.AccessControl.Emails, email)
68-
}
69-
7055
type CustomBoard struct {
71-
Permissions
72-
Name string `json:"name"`
73-
ID string `json:"id"`
74-
ClusterScope bool `json:"-"`
75-
Namespaces Namespaces `json:"-"`
76-
Sources Sources `json:"-"`
77-
PolicyReports PolicyReports `json:"-"`
56+
auth.Permissions `json:"-"`
57+
Name string `json:"name"`
58+
ID string `json:"id"`
59+
ClusterScope bool `json:"-"`
60+
Namespaces Namespaces `json:"-"`
61+
Sources Sources `json:"-"`
62+
PolicyReports PolicyReports `json:"-"`
7863
}
7964

8065
type Boards struct {
81-
Permissions
66+
auth.Permissions
8267
}
8368

8469
type Config struct {

frontend/modules/core/components/form/ClusterSelect.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
hide-details
1010
density="compact"
1111
prepend-inner-icon="mdi-kubernetes"
12-
style="max-width: 140px;"
12+
style="min-width: 140px;"
1313
@update:model-value="input"
1414
v-if="clusters.length > 1"
1515
/>

0 commit comments

Comments
 (0)