1
2
3
4
5
6
7
8
9
10
11
12
13
14 package webhook
15
16 import (
17 "context"
18 "fmt"
19 "net/http"
20 "strings"
21
22 authv1 "k8s.io/api/authorization/v1"
23 authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1"
24 ctrl "sigs.k8s.io/controller-runtime"
25 "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
26
27 "github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
28 "github.com/chaos-mesh/chaos-mesh/controllers/common"
29 )
30
31 var alwaysAllowedKind = []string{
32 v1alpha1.KindAWSChaos,
33 v1alpha1.KindPodNetworkChaos,
34 v1alpha1.KindPodIOChaos,
35 v1alpha1.KindGCPChaos,
36 v1alpha1.KindPodHttpChaos,
37
38
39
40 v1alpha1.KindSchedule,
41
42 "Workflow",
43 "WorkflowNode",
44 }
45
46 var authLog = ctrl.Log.WithName("validate-auth")
47
48
49
50
51 type AuthValidator struct {
52 enabled bool
53 authCli *authorizationv1.AuthorizationV1Client
54
55 decoder *admission.Decoder
56
57 clusterScoped bool
58 targetNamespace string
59 enableFilterNamespace bool
60 }
61
62
63 func NewAuthValidator(enabled bool, authCli *authorizationv1.AuthorizationV1Client,
64 clusterScoped bool, targetNamespace string, enableFilterNamespace bool) *AuthValidator {
65 return &AuthValidator{
66 enabled: enabled,
67 authCli: authCli,
68 clusterScoped: clusterScoped,
69 targetNamespace: targetNamespace,
70 enableFilterNamespace: enableFilterNamespace,
71 }
72 }
73
74
75 func (v *AuthValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
76 if !v.enabled {
77 return admission.Allowed("")
78 }
79
80 username := req.UserInfo.Username
81 groups := req.UserInfo.Groups
82 requestKind := req.Kind.Kind
83
84 if contains(alwaysAllowedKind, requestKind) {
85 return admission.Allowed(fmt.Sprintf("skip the RBAC check for type %s", requestKind))
86 }
87
88 kind, ok := v1alpha1.AllKinds()[requestKind]
89 if !ok {
90 err := fmt.Errorf("kind %s is not support", requestKind)
91 return admission.Errored(http.StatusBadRequest, err)
92 }
93 chaos := kind.Chaos.DeepCopyObject().(common.InnerObjectWithSelector)
94 if chaos == nil {
95 err := fmt.Errorf("kind %s is not support", requestKind)
96 return admission.Errored(http.StatusBadRequest, err)
97 }
98
99 err := v.decoder.Decode(req, chaos)
100 if err != nil {
101 return admission.Errored(http.StatusBadRequest, err)
102 }
103 specs := chaos.GetSelectorSpecs()
104
105 requireClusterPrivileges := false
106 affectedNamespaces := make(map[string]struct{})
107
108 for _, spec := range specs {
109 var selector *v1alpha1.PodSelector
110 if s, ok := spec.(*v1alpha1.ContainerSelector); ok {
111 selector = &s.PodSelector
112 }
113 if p, ok := spec.(*v1alpha1.PodSelector); ok {
114 selector = p
115 }
116 if selector == nil {
117 return admission.Allowed("")
118 }
119
120 if selector.Selector.ClusterScoped() {
121 requireClusterPrivileges = true
122 }
123
124 for _, namespace := range selector.Selector.AffectedNamespaces() {
125 affectedNamespaces[namespace] = struct{}{}
126 }
127 }
128
129 if requireClusterPrivileges {
130 allow, err := v.auth(username, groups, "", requestKind)
131 if err != nil {
132 return admission.Errored(http.StatusBadRequest, err)
133 }
134
135 if !allow {
136 return admission.Denied(fmt.Sprintf("%s is forbidden on cluster", username))
137 }
138 authLog.Info("user have the privileges on cluster, auth validate passed", "user", username, "groups", groups, "namespace", affectedNamespaces)
139 } else {
140 for namespace := range affectedNamespaces {
141 allow, err := v.auth(username, groups, namespace, requestKind)
142 if err != nil {
143 return admission.Errored(http.StatusBadRequest, err)
144 }
145
146 if !allow {
147 return admission.Denied(fmt.Sprintf("%s is forbidden on namespace %s", username, namespace))
148 }
149 }
150
151 authLog.Info("user have the privileges on namespace, auth validate passed", "user", username, "groups", groups, "namespace", affectedNamespaces)
152 }
153
154 return admission.Allowed("")
155 }
156
157
158
159
160
161 func (v *AuthValidator) InjectDecoder(d *admission.Decoder) error {
162 v.decoder = d
163 return nil
164 }
165
166 func (v *AuthValidator) auth(username string, groups []string, namespace string, chaosKind string) (bool, error) {
167 resourceName, err := v.resourceFor(chaosKind)
168 if err != nil {
169 return false, err
170 }
171 sar := authv1.SubjectAccessReview{
172 Spec: authv1.SubjectAccessReviewSpec{
173 ResourceAttributes: &authv1.ResourceAttributes{
174 Namespace: namespace,
175 Verb: "create",
176 Group: "chaos-mesh.org",
177 Resource: resourceName,
178 },
179 User: username,
180 Groups: groups,
181 },
182 }
183
184 response, err := v.authCli.SubjectAccessReviews().Create(&sar)
185 if err != nil {
186 return false, err
187 }
188
189 return response.Status.Allowed, nil
190 }
191
192 func (v *AuthValidator) resourceFor(name string) (string, error) {
193
194 return strings.ToLower(name), nil
195 }
196
197 func contains(arr []string, target string) bool {
198 for _, item := range arr {
199 if item == target {
200 return true
201 }
202 }
203 return false
204 }
205