1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package webhook
17
18 import (
19 "context"
20 "fmt"
21 "net/http"
22 "strings"
23
24 "github.com/go-logr/logr"
25 "github.com/pkg/errors"
26 authv1 "k8s.io/api/authorization/v1"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/runtime"
29 authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1"
30 "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
31
32 "github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
33 )
34
35 var alwaysAllowedKind = []string{
36 v1alpha1.KindAWSChaos,
37 v1alpha1.KindPodNetworkChaos,
38 v1alpha1.KindPodIOChaos,
39 v1alpha1.KindGCPChaos,
40 v1alpha1.KindPodHttpChaos,
41 v1alpha1.KindPhysicalMachine,
42 v1alpha1.KindStatusCheck,
43 v1alpha1.KindRemoteCluster,
44
45 "WorkflowNode",
46 }
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 logger logr.Logger
61 }
62
63
64 func NewAuthValidator(enabled bool, authCli *authorizationv1.AuthorizationV1Client, decoderScheme *runtime.Scheme,
65 clusterScoped bool, targetNamespace string, enableFilterNamespace bool, logger logr.Logger) *AuthValidator {
66 return &AuthValidator{
67 enabled: enabled,
68 authCli: authCli,
69 decoder: admission.NewDecoder(decoderScheme),
70 clusterScoped: clusterScoped,
71 targetNamespace: targetNamespace,
72 enableFilterNamespace: enableFilterNamespace,
73 logger: logger,
74 }
75 }
76
77
78 func (v *AuthValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
79 if !v.enabled {
80 return admission.Allowed("")
81 }
82
83 username := req.UserInfo.Username
84 groups := req.UserInfo.Groups
85 requestKind := req.Kind.Kind
86
87 if contains(alwaysAllowedKind, requestKind) {
88 return admission.Allowed(fmt.Sprintf("skip the RBAC check for type %s", requestKind))
89 }
90
91 kind, ok := v1alpha1.AllKindsIncludeScheduleAndWorkflow()[requestKind]
92 if !ok {
93 err := errors.Wrapf(errInvalidValue, "kind %s is not support", requestKind)
94 return admission.Errored(http.StatusBadRequest, err)
95 }
96 chaos := kind.SpawnObject()
97
98 err := v.decoder.Decode(req, chaos)
99 if err != nil {
100 return admission.Errored(http.StatusBadRequest, err)
101 }
102
103 requireClusterPrivileges, affectedNamespaces := affectedNamespaces(chaos)
104
105 if requireClusterPrivileges {
106 allow, err := v.auth(username, groups, "", requestKind)
107 if err != nil {
108 return admission.Errored(http.StatusBadRequest, err)
109 }
110
111 if !allow {
112 return admission.Denied(fmt.Sprintf("%s is forbidden on cluster", username))
113 }
114 v.logger.Info("user have the privileges on cluster, auth validate passed", "user", username, "groups", groups, "namespace", affectedNamespaces)
115 } else {
116 v.logger.Info("start validating user", "user", username, "groups", groups, "namespace", affectedNamespaces)
117
118 for namespace := range affectedNamespaces {
119 allow, err := v.auth(username, groups, namespace, requestKind)
120 if err != nil {
121 return admission.Errored(http.StatusBadRequest, err)
122 }
123
124 if !allow {
125 return admission.Denied(fmt.Sprintf("%s is forbidden on namespace %s", username, namespace))
126 }
127 }
128
129 v.logger.Info("user have the privileges on namespace, auth validate passed", "user", username, "groups", groups, "namespace", affectedNamespaces)
130 }
131
132 return admission.Allowed("")
133 }
134
135 func (v *AuthValidator) auth(username string, groups []string, namespace string, chaosKind string) (bool, error) {
136 resourceName, err := v.resourceFor(chaosKind)
137 if err != nil {
138 return false, err
139 }
140 sar := authv1.SubjectAccessReview{
141 Spec: authv1.SubjectAccessReviewSpec{
142 ResourceAttributes: &authv1.ResourceAttributes{
143 Namespace: namespace,
144 Verb: "create",
145 Group: "chaos-mesh.org",
146 Resource: resourceName,
147 },
148 User: username,
149 Groups: groups,
150 },
151 }
152
153
154 response, err := v.authCli.SubjectAccessReviews().Create(context.TODO(), &sar, metav1.CreateOptions{})
155 if err != nil {
156 return false, err
157 }
158
159 return response.Status.Allowed, nil
160 }
161
162 func (v *AuthValidator) resourceFor(name string) (string, error) {
163
164 return strings.ToLower(name), nil
165 }
166
167 func contains(arr []string, target string) bool {
168 for _, item := range arr {
169 if item == target {
170 return true
171 }
172 }
173 return false
174 }
175