Skip to content

Commit 0d9bf01

Browse files
author
Cornelius Weig
committed
Check access authorization before querying
Note: this is quite slow. Maybe it is faster to retrieve resources individually without checking access beforehand.
1 parent 1fafb8a commit 0d9bf01

3 files changed

Lines changed: 181 additions & 8 deletions

File tree

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ require (
2121
github.com/spf13/viper v1.3.1
2222
github.com/stretchr/testify v1.2.2
2323
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd // indirect
24-
golang.org/x/oauth2 v0.0.0-20190212230446-3e8b2be13635 // indirect
24+
golang.org/x/oauth2 v0.0.0-20190212230446-3e8b2be13635
2525
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect
2626
gopkg.in/inf.v0 v0.9.1 // indirect
27-
k8s.io/api v0.0.0-20181121191454-a61488babbd6 // indirect
27+
k8s.io/api v0.0.0-20181121191454-a61488babbd6
2828
k8s.io/apimachinery v0.0.0-20190211022232-e355a776c090
2929
k8s.io/cli-runtime v0.0.0-20190202014047-491c94071cfa
3030
k8s.io/client-go v10.0.0+incompatible

pkg/client/access.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
Copyright 2019 Cornelius Weig
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package client
18+
19+
import (
20+
"github.com/pkg/errors"
21+
"github.com/sirupsen/logrus"
22+
"github.com/spf13/viper"
23+
"k8s.io/api/authorization/v1"
24+
authv1 "k8s.io/client-go/kubernetes/typed/authorization/v1"
25+
"sync"
26+
)
27+
28+
/*
29+
restConfig, _ := flags.ToRESTConfig()
30+
authClient, _ := authv1.NewForConfig(restConfig)
31+
allowed, err := CheckResourceAccessPar(authClient, resources)
32+
if err != nil {
33+
return nil, errors.Wrap(err, "check resource access")
34+
}
35+
36+
allowedResourceTypes := ToResourceTypes(allowed)*/
37+
38+
func CheckResourceAccessPar(authClient *authv1.AuthorizationV1Client, grs []groupResource) (allowed []groupResource, err error) {
39+
reviews := authClient.SelfSubjectAccessReviews()
40+
41+
allowedChan := make(chan groupResource)
42+
forbiddenChan := make(chan groupResource)
43+
44+
group := sync.WaitGroup{}
45+
46+
namespace := viper.GetString("namespace")
47+
for _, gr := range grs {
48+
namespace := namespace
49+
gr := gr
50+
group.Add(1)
51+
go func(allowed, forbidden chan<- groupResource) {
52+
defer group.Done()
53+
54+
// This seems to be a bug in kubernetes. If namespace is set for non-namespaced
55+
// resources, the access is reported as "allowed", but in fact it is forbidden.
56+
if !gr.APIResource.Namespaced {
57+
namespace = ""
58+
}
59+
60+
review := v1.SelfSubjectAccessReview{
61+
Spec: v1.SelfSubjectAccessReviewSpec{
62+
ResourceAttributes: &v1.ResourceAttributes{
63+
Verb: "list",
64+
Resource: gr.APIResource.Name,
65+
Group: gr.APIGroup,
66+
Namespace: namespace,
67+
},
68+
},
69+
}
70+
accessReview, e := reviews.Create(&review)
71+
if e != nil {
72+
err = errors.Wrap(e, "retrieve authority")
73+
return
74+
}
75+
logrus.Info(accessReview)
76+
if accessReview.Status.Allowed {
77+
allowed <- gr
78+
} else {
79+
forbidden <- gr
80+
}
81+
}(allowedChan, forbiddenChan)
82+
}
83+
84+
forbidden := []groupResource{}
85+
go func(c <-chan groupResource) {
86+
for gr := range c {
87+
forbidden = append(forbidden, gr)
88+
}
89+
}(forbiddenChan)
90+
go func(c <-chan groupResource) {
91+
for gr := range c {
92+
allowed = append(allowed, gr)
93+
}
94+
}(allowedChan)
95+
96+
group.Wait()
97+
98+
close(allowedChan)
99+
close(forbiddenChan)
100+
101+
if len(forbidden) > 0 {
102+
logrus.Warnf("The following resources may not be read: %s", ToResourceTypes(forbidden))
103+
}
104+
105+
logrus.Debugf("Readable: %s", ToResourceTypes(allowed))
106+
107+
return
108+
}
109+
110+
func CheckResourceAccess(authClient *authv1.AuthorizationV1Client, grs []groupResource) (allowed []groupResource, err error) {
111+
forbidden := []groupResource{}
112+
reviews := authClient.SelfSubjectAccessReviews()
113+
114+
namespace := viper.GetString("namespace")
115+
for _, gr := range grs {
116+
namespace := namespace
117+
gr := gr
118+
// This seems to be a bug in kubernetes. If namespace is set for non-namespaced
119+
// resources, the access is reported as "allowed", but in fact it is forbidden.
120+
if !gr.APIResource.Namespaced {
121+
namespace = ""
122+
}
123+
124+
review := v1.SelfSubjectAccessReview{
125+
Spec: v1.SelfSubjectAccessReviewSpec{
126+
ResourceAttributes: &v1.ResourceAttributes{
127+
Verb: "list",
128+
Resource: gr.APIResource.Name,
129+
Group: gr.APIGroup,
130+
Namespace: namespace,
131+
},
132+
},
133+
}
134+
accessReview, e := reviews.Create(&review)
135+
if e != nil {
136+
err = errors.Wrap(e, "retrieve authority")
137+
return
138+
}
139+
logrus.Info(accessReview)
140+
if accessReview.Status.Allowed {
141+
allowed = append(allowed, gr)
142+
} else {
143+
forbidden = append(forbidden, gr)
144+
}
145+
}
146+
147+
if len(forbidden) > 0 {
148+
logrus.Warnf("The following resources may not be read: %s", ToResourceTypes(forbidden))
149+
}
150+
151+
logrus.Debugf("Readable: %s", ToResourceTypes(allowed))
152+
153+
return
154+
}

pkg/client/client.go

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"k8s.io/apimachinery/pkg/util/sets"
2929
"k8s.io/cli-runtime/pkg/genericclioptions"
3030
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
31+
authv1 "k8s.io/client-go/kubernetes/typed/authorization/v1"
3132
"sort"
3233
"strings"
3334
)
@@ -48,15 +49,26 @@ func GetAllServerResources(flags *genericclioptions.ConfigFlags) (runtime.Object
4849
return nil, errors.Wrap(err, "fetch available group resources")
4950
}
5051

51-
resNames := extractRelevantResourceNames(grs, viper.GetStringSlice(constants.FlagExclude))
52+
resources := extractRelevantResourceNames(grs, viper.GetStringSlice(constants.FlagExclude))
53+
54+
restConfig, _ := flags.ToRESTConfig()
55+
authClient, _ := authv1.NewForConfig(restConfig)
56+
allowed, err := CheckResourceAccessPar(authClient, resources)
57+
if err != nil {
58+
return nil, errors.Wrap(err, "check resource access")
59+
}
60+
61+
allowedResourceTypes := ToResourceTypes(allowed)
62+
logrus.Debugf("Resources to fetch: %s", allowedResourceTypes)
5263

5364
request := resource.NewBuilder(flags).
5465
Unstructured().
5566
SelectAllParam(true).
56-
ResourceTypes(resNames...).
67+
ResourceTypes(allowedResourceTypes...).
5768
Latest()
5869

5970
if ns := viper.GetString(constants.FlagNamespace); ns != "" {
71+
logrus.Debugf("Restricting to namespace %s", ns)
6072
request.NamespaceParam(ns)
6173
} else {
6274
request.AllNamespaces(true)
@@ -126,11 +138,11 @@ func FetchAvailableGroupResources(cache bool, scope string, flags *genericcliopt
126138
return grs, nil
127139
}
128140

129-
func extractRelevantResourceNames(grs []groupResource, exclusions []string) []string {
141+
func extractRelevantResourceNames(grs []groupResource, exclusions []string) []groupResource {
130142
sort.Stable(sortableGroupResource(grs))
131143
forbidden := sets.NewString(exclusions...)
132144

133-
result := []string{}
145+
result := []groupResource{}
134146
for _, r := range grs {
135147
name := r.fullName()
136148
resourceIds := r.APIResource.ShortNames
@@ -139,10 +151,9 @@ func extractRelevantResourceNames(grs []groupResource, exclusions []string) []st
139151
logrus.Debugf("Excluding %s", name)
140152
continue
141153
}
142-
result = append(result, name)
154+
result = append(result, r)
143155
}
144156

145-
logrus.Debugf("Resources to fetch: %s", result)
146157
return result
147158
}
148159

@@ -172,6 +183,14 @@ func (g groupResource) fullName() string {
172183

173184
type sortableGroupResource []groupResource
174185

186+
func ToResourceTypes(in []groupResource) []string {
187+
result := []string{}
188+
for _, r := range in {
189+
result = append(result, r.fullName())
190+
}
191+
return result
192+
}
193+
175194
func (s sortableGroupResource) Len() int { return len(s) }
176195
func (s sortableGroupResource) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
177196
func (s sortableGroupResource) Less(i, j int) bool {

0 commit comments

Comments
 (0)