@@ -6,48 +6,12 @@ import (
66
77 authorizationv1 "k8s.io/api/authorization/v1"
88 corev1 "k8s.io/api/core/v1"
9- rbacv1 "k8s.io/api/rbac/v1"
109 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11- "k8s.io/apimachinery/pkg/util/sets"
1210 typedauth "k8s.io/client-go/kubernetes/typed/authorization/v1"
1311)
1412
1513//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
1614
17- // DefautltWegoAppRules is the minimun set of permissions a user will need to use the wego-app in a given namespace
18- var DefautltWegoAppRules = []rbacv1.PolicyRule {
19- {
20- APIGroups : []string {"" },
21- Resources : []string {"pods" , "secrets" },
22- Verbs : []string {"get" , "list" },
23- },
24- {
25- APIGroups : []string {"" },
26- Resources : []string {"events" },
27- Verbs : []string {"get" , "list" , "watch" },
28- },
29- {
30- APIGroups : []string {"apps" },
31- Resources : []string {"deployments" , "replicasets" },
32- Verbs : []string {"get" , "list" },
33- },
34- {
35- APIGroups : []string {"kustomize.toolkit.fluxcd.io" },
36- Resources : []string {"kustomizations" },
37- Verbs : []string {"get" , "list" },
38- },
39- {
40- APIGroups : []string {"helm.toolkit.fluxcd.io" },
41- Resources : []string {"helmreleases" },
42- Verbs : []string {"get" , "list" },
43- },
44- {
45- APIGroups : []string {"source.toolkit.fluxcd.io" },
46- Resources : []string {"buckets" , "helmcharts" , "helmrepositories" , "gitrepositories" , "ocirepositories" },
47- Verbs : []string {"get" , "list" },
48- },
49- }
50-
5115// Checker contains methods for validing user access to Kubernetes namespaces, based on a set of PolicyRules
5216//
5317//counterfeiter:generate . Checker
@@ -56,19 +20,25 @@ type Checker interface {
5620 FilterAccessibleNamespaces (ctx context.Context , auth typedauth.AuthorizationV1Interface , namespaces []corev1.Namespace ) ([]corev1.Namespace , error )
5721}
5822
59- type simpleChecker struct {
60- rules []rbacv1.PolicyRule
61- }
23+ type simpleChecker struct {}
6224
63- func NewChecker (rules []rbacv1. PolicyRule ) Checker {
64- return simpleChecker {rules : rules }
25+ func NewChecker () Checker {
26+ return simpleChecker {}
6527}
6628
6729func (sc simpleChecker ) FilterAccessibleNamespaces (ctx context.Context , auth typedauth.AuthorizationV1Interface , namespaces []corev1.Namespace ) ([]corev1.Namespace , error ) {
68- result := []corev1.Namespace {}
30+ accessToAllNamespace , err := hasAccessToAllNamespaces (ctx , auth )
31+ if err != nil {
32+ return nil , err
33+ }
34+
35+ if accessToAllNamespace {
36+ return namespaces , nil
37+ }
6938
39+ var result []corev1.Namespace
7040 for _ , ns := range namespaces {
71- ok , err := userCanUseNamespace (ctx , auth , ns , sc . rules )
41+ ok , err := hasAccessToNamespace (ctx , auth , ns )
7242 if err != nil {
7343 return nil , fmt .Errorf ("user namespace access: %w" , err )
7444 }
@@ -81,163 +51,35 @@ func (sc simpleChecker) FilterAccessibleNamespaces(ctx context.Context, auth typ
8151 return result , nil
8252}
8353
84- func userCanUseNamespace (ctx context.Context , auth typedauth.AuthorizationV1Interface , ns corev1.Namespace , rules []rbacv1.PolicyRule ) (bool , error ) {
85- sar := & authorizationv1.SelfSubjectRulesReview {
86- Spec : authorizationv1.SelfSubjectRulesReviewSpec {
87- Namespace : ns .Name ,
54+ func hasAccessToNamespace (ctx context.Context , auth typedauth.AuthorizationV1Interface , ns corev1.Namespace ) (bool , error ) {
55+ ssar := & authorizationv1.SelfSubjectAccessReview {
56+ Spec : authorizationv1.SelfSubjectAccessReviewSpec {
57+ ResourceAttributes : & authorizationv1.ResourceAttributes {
58+ Verb : "get" ,
59+ Resource : "configmaps" ,
60+ Namespace : ns .Name ,
61+ },
8862 },
8963 }
90-
91- authRes , err := auth .SelfSubjectRulesReviews ().Create (ctx , sar , metav1.CreateOptions {})
64+ res , err := auth .SelfSubjectAccessReviews ().Create (ctx , ssar , metav1.CreateOptions {})
9265 if err != nil {
9366 return false , err
9467 }
95-
96- return hasAllRules (authRes .Status , rules ), nil
68+ return res .Status .Allowed , nil
9769}
9870
99- var allK8sVerbs = []string {"create" , "get" , "list" , "watch" , "patch" , "delete" , "deletecollection" }
100-
101- func allResources (rules []rbacv1.PolicyRule ) []string {
102- resources := sets .NewString ()
103- for _ , r := range rules {
104- resources .Insert (r .Resources ... )
105- }
106-
107- return resources .List ()
108- }
109-
110- func allAPIGroups (rules []rbacv1.PolicyRule ) []string {
111- apiGroups := sets .NewString ()
112- for _ , r := range rules {
113- apiGroups .Insert (r .APIGroups ... )
114- }
115-
116- return apiGroups .List ()
117- }
118-
119- // hasAll rules determines if a set of SubjectRulesReview rules match a minimum set of policy rules
120- //
121- // We need to understand the "sum" of all the rules for a role.
122- // Convert to a hash lookup to make it easier to tell what a user can do.
123- // Looks like { "apps": { "deployments": { get: true, list: true } } }
124- //
125- // We handle wildcards by expanding them out to all the types of resources/APIGroups
126- // that are relevant to the provided rules.
127- // This creates slightly nonsensical sets sometimes, for example given:
128- //
129- // PolicyRules:
130- //
131- // {
132- // APIGroups: []string{""},
133- // Resources: []string{"secrets"},
134- // Verbs: []string{"get"},
135- // },
136- //
137- // {
138- // APIGroups: []string{"apps"},
139- // Resources: []string{"deployment"}",
140- // Verbs: []string{"get"},
141- // },
142- //
143- // SubjectRulesReviewStatus:
144- // ["*", "*", "get"]
145- //
146- // We get:
147- //
148- // {
149- // "": {
150- // "secrets": { get: true }
151- // "deployments": { get: true }
152- // },
153- // "apps": {
154- // "secrets": { get: true }
155- // "deployments", "secrets": { get: true }
156- // }
157- // }
158- //
159- // Secrets don't exist in "apps" according to k8s,
160- // but the "index checker" that checks this struct does not mind.
161- func hasAllRules (status authorizationv1.SubjectRulesReviewStatus , rules []rbacv1.PolicyRule ) bool {
162- derivedAccess := map [string ]map [string ]map [string ]bool {}
163-
164- allResourcesInRules := allResources (rules )
165- allAPIGroupsInRules := allAPIGroups (rules )
166-
167- for _ , statusRule := range status .ResourceRules {
168- apiGroups := statusRule .APIGroups
169- if containsWildcard (apiGroups ) {
170- apiGroups = allAPIGroupsInRules
171- }
172-
173- for _ , apiGroup := range apiGroups {
174- if _ , ok := derivedAccess [apiGroup ]; ! ok {
175- derivedAccess [apiGroup ] = map [string ]map [string ]bool {}
176- }
177-
178- resources := statusRule .Resources
179- if containsWildcard (resources ) {
180- resources = allResourcesInRules
181- }
182-
183- for _ , resource := range resources {
184- if _ , ok := derivedAccess [apiGroup ][resource ]; ! ok {
185- derivedAccess [apiGroup ][resource ] = map [string ]bool {}
186- }
187-
188- verbs := statusRule .Verbs
189- if containsWildcard (verbs ) {
190- verbs = allK8sVerbs
191- }
192-
193- for _ , v := range verbs {
194- derivedAccess [apiGroup ][resource ][v ] = true
195- }
196- }
197- }
198- }
199-
200- hasAccess := true
201-
202- Rules:
203- for _ , rule := range rules {
204- for _ , apiGroup := range rule .APIGroups {
205- g , ok := derivedAccess [apiGroup ]
206-
207- if ! ok {
208- hasAccess = false
209- break Rules
210- }
211- for _ , resource := range rule .Resources {
212- r , ok := g [resource ]
213- if ! ok {
214- // A resource is not present for this apiGroup.
215- hasAccess = false
216- break Rules
217- }
218-
219- for _ , verb := range rule .Verbs {
220- _ , ok := r [verb ]
221- if ! ok {
222- // A verb is not present for this resource,
223- // no need to check the rest of the verbs.
224- hasAccess = false
225- break Rules
226- }
227- }
228- }
229- }
71+ func hasAccessToAllNamespaces (ctx context.Context , auth typedauth.AuthorizationV1Interface ) (bool , error ) {
72+ ssar := & authorizationv1.SelfSubjectAccessReview {
73+ Spec : authorizationv1.SelfSubjectAccessReviewSpec {
74+ ResourceAttributes : & authorizationv1.ResourceAttributes {
75+ Verb : "list" ,
76+ Resource : "namespaces" ,
77+ },
78+ },
23079 }
231-
232- return hasAccess
233- }
234-
235- func containsWildcard (permissions []string ) bool {
236- for _ , p := range permissions {
237- if p == "*" {
238- return true
239- }
80+ res , err := auth .SelfSubjectAccessReviews ().Create (ctx , ssar , metav1.CreateOptions {})
81+ if err != nil {
82+ return false , err
24083 }
241-
242- return false
84+ return res .Status .Allowed , nil
24385}
0 commit comments