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/client"
26 "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
27
28 "github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
29 )
30
31 var authLog = ctrl.Log.WithName("validate-auth")
32
33
34
35
36 type AuthValidator struct {
37 enabled bool
38 client client.Client
39 reader client.Reader
40 authCli *authorizationv1.AuthorizationV1Client
41
42 decoder *admission.Decoder
43
44 clusterScoped bool
45 targetNamespace string
46 enableFilterNamespace bool
47 }
48
49
50 func NewAuthValidator(enabled bool, client client.Client, reader client.Reader, authCli *authorizationv1.AuthorizationV1Client,
51 clusterScoped bool, targetNamespace string, enableFilterNamespace bool) *AuthValidator {
52 return &AuthValidator{
53 enabled: enabled,
54 client: client,
55 reader: reader,
56 authCli: authCli,
57 clusterScoped: clusterScoped,
58 targetNamespace: targetNamespace,
59 enableFilterNamespace: enableFilterNamespace,
60 }
61 }
62
63
64 func (v *AuthValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
65 if !v.enabled {
66 return admission.Allowed("")
67 }
68
69 username := req.UserInfo.Username
70 groups := req.UserInfo.Groups
71 chaosKind := req.Kind.Kind
72
73
74 if chaosKind == v1alpha1.KindAwsChaos || chaosKind == v1alpha1.KindPodNetworkChaos || chaosKind == v1alpha1.KindPodIoChaos {
75 return admission.Allowed("")
76 }
77
78 chaos := v1alpha1.GetChaosValidator(chaosKind)
79 if chaos == nil {
80 err := fmt.Errorf("kind %s is not support", chaosKind)
81 return admission.Errored(http.StatusBadRequest, err)
82 }
83
84 err := v.decoder.Decode(req, chaos)
85 if err != nil {
86 return admission.Errored(http.StatusBadRequest, err)
87 }
88 specs := chaos.GetSelectSpec()
89
90 requireClusterPrivileges := false
91 affectedNamespaces := make(map[string]struct{})
92
93 for _, spec := range specs {
94 if spec.GetSelector().ClusterScoped() {
95 requireClusterPrivileges = true
96 }
97
98 for _, namespace := range spec.GetSelector().AffectedNamespaces() {
99 affectedNamespaces[namespace] = struct{}{}
100 }
101 }
102
103 if requireClusterPrivileges {
104 allow, err := v.auth(username, groups, "", chaosKind)
105 if err != nil {
106 return admission.Errored(http.StatusBadRequest, err)
107 }
108
109 if !allow {
110 return admission.Denied(fmt.Sprintf("%s is forbidden on cluster", username))
111 }
112 authLog.Info("user have the privileges on cluster, auth validate passed", "user", username, "groups", groups, "namespace", affectedNamespaces)
113 } else {
114 for namespace := range affectedNamespaces {
115 allow, err := v.auth(username, groups, namespace, chaosKind)
116 if err != nil {
117 return admission.Errored(http.StatusBadRequest, err)
118 }
119
120 if !allow {
121 return admission.Denied(fmt.Sprintf("%s is forbidden on namespace %s", username, namespace))
122 }
123 }
124
125 authLog.Info("user have the privileges on namespace, auth validate passed", "user", username, "groups", groups, "namespace", affectedNamespaces)
126 }
127
128 return admission.Allowed("")
129 }
130
131
132
133
134
135 func (v *AuthValidator) InjectDecoder(d *admission.Decoder) error {
136 v.decoder = d
137 return nil
138 }
139
140 func (v *AuthValidator) auth(username string, groups []string, namespace string, chaosKind string) (bool, error) {
141 resourceName, err := v.resourceFor(chaosKind)
142 if err != nil {
143 return false, err
144 }
145 sar := authv1.SubjectAccessReview{
146 Spec: authv1.SubjectAccessReviewSpec{
147 ResourceAttributes: &authv1.ResourceAttributes{
148 Namespace: namespace,
149 Verb: "create",
150 Group: "chaos-mesh.org",
151 Resource: resourceName,
152 },
153 User: username,
154 Groups: groups,
155 },
156 }
157
158 response, err := v.authCli.SubjectAccessReviews().Create(&sar)
159 if err != nil {
160 return false, err
161 }
162
163 return response.Status.Allowed, nil
164 }
165
166 func (v *AuthValidator) resourceFor(name string) (string, error) {
167
168 return strings.ToLower(name), nil
169 }
170