Skip to content

Commit 315b2dd

Browse files
ptnapoleonluphaz
andauthored
CHAOSPLT-216: Allow for requiring specific user groups before creating a disruption (#831)
* Rename validateUserInfo * CHAOSPLT-216: Allow for restricting disruption creation based on group * Improve the logging and errors --------- Co-authored-by: Matthieu Bono <luphaz@users.noreply.github.com>
1 parent 5f0dd8c commit 315b2dd

File tree

7 files changed

+108
-23
lines changed

7 files changed

+108
-23
lines changed

api/v1beta1/disruption_webhook.go

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,24 @@ import (
3636
)
3737

3838
var (
39-
logger *zap.SugaredLogger
40-
k8sClient client.Client
41-
metricsSink metrics.Sink
42-
tracerSink tracer.Sink
43-
recorder record.EventRecorder
44-
deleteOnly bool
45-
enableSafemode bool
46-
defaultNamespaceThreshold float64
47-
defaultClusterThreshold float64
48-
handlerEnabled bool
49-
maxDuration time.Duration
50-
defaultDuration time.Duration
51-
cloudServicesProvidersManager cloudservice.CloudServicesProvidersManager
52-
chaosNamespace string
53-
ddmarkClient ddmark.Client
54-
safemodeEnvironment string
39+
logger *zap.SugaredLogger
40+
k8sClient client.Client
41+
metricsSink metrics.Sink
42+
tracerSink tracer.Sink
43+
recorder record.EventRecorder
44+
deleteOnly bool
45+
enableSafemode bool
46+
defaultNamespaceThreshold float64
47+
defaultClusterThreshold float64
48+
handlerEnabled bool
49+
maxDuration time.Duration
50+
defaultDuration time.Duration
51+
cloudServicesProvidersManager cloudservice.CloudServicesProvidersManager
52+
chaosNamespace string
53+
ddmarkClient ddmark.Client
54+
safemodeEnvironment string
55+
permittedUserGroups map[string]struct{}
56+
permittedUserGroupWarningString string
5557
)
5658

5759
const SafemodeEnvironmentAnnotation = GroupName + "/environment"
@@ -80,6 +82,13 @@ func (r *Disruption) SetupWebhookWithManager(setupWebhookConfig utils.SetupWebho
8082
cloudServicesProvidersManager = setupWebhookConfig.CloudServicesProvidersManager
8183
chaosNamespace = setupWebhookConfig.ChaosNamespace
8284
safemodeEnvironment = setupWebhookConfig.Environment
85+
permittedUserGroups = map[string]struct{}{}
86+
87+
for _, group := range setupWebhookConfig.PermittedUserGroups {
88+
permittedUserGroups[group] = struct{}{}
89+
}
90+
91+
permittedUserGroupWarningString = strings.Join(setupWebhookConfig.PermittedUserGroups, ",")
8392

8493
return ctrl.NewWebhookManagedBy(setupWebhookConfig.Manager).
8594
For(r).
@@ -120,6 +129,10 @@ func (r *Disruption) ValidateCreate() error {
120129
return errors.New("the controller is currently in delete-only mode, you can't create new disruptions for now")
121130
}
122131

132+
if err = r.validateUserInfoGroup(); err != nil {
133+
return err
134+
}
135+
123136
// reject disruptions with a name which would not be a valid label value
124137
// according to https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set
125138
if _, err := labels.Parse(fmt.Sprintf("name=%s", r.Name)); err != nil {
@@ -233,7 +246,7 @@ func (r *Disruption) ValidateUpdate(old runtime.Object) error {
233246

234247
oldDisruption := old.(*Disruption)
235248

236-
if err := r.validateUserInfo(oldDisruption); err != nil {
249+
if err := r.validateUserInfoImmutable(oldDisruption); err != nil {
237250
return err
238251
}
239252

@@ -316,7 +329,35 @@ You first need to remove those chaos pods (and potentially their finalizers) to
316329
return nil
317330
}
318331

319-
func (r *Disruption) validateUserInfo(oldDisruption *Disruption) error {
332+
// validateUserInfoGroup checks that if permittedUserGroups is set, which is controlled in controller.safeMode.permittedUserGroups in the configmap,
333+
// then we will return an error if the user in r.UserInfo does not belong to any groups. If permittedUserGroups is unset, or if the user belongs to one of those
334+
// groups, then we will return nil
335+
func (r *Disruption) validateUserInfoGroup() error {
336+
if len(permittedUserGroups) == 0 {
337+
return nil
338+
}
339+
340+
userInfo, err := r.UserInfo()
341+
if err != nil {
342+
return err
343+
}
344+
345+
for _, group := range userInfo.Groups {
346+
_, ok := permittedUserGroups[group]
347+
if ok {
348+
logger.Debugw("permitting user disruption creation, due to group membership", "group", group)
349+
350+
return nil
351+
}
352+
}
353+
354+
logger.Warnw("rejecting user from creating this disruption", "permittedUserGroups", permittedUserGroups, "userGroups", userInfo.Groups)
355+
356+
return fmt.Errorf("lacking sufficient authorization to create disruptions. your user groups are %s, but you must be in one of the following groups: %s", userInfo.Groups, permittedUserGroupWarningString)
357+
}
358+
359+
// validateUserInfoImmutable checks that no changes have been made to the oldDisruption's UserInfo in the latest update
360+
func (r *Disruption) validateUserInfoImmutable(oldDisruption *Disruption) error {
320361
oldUserInfo, err := oldDisruption.UserInfo()
321362
if err != nil {
322363
return nil

api/v1beta1/disruption_webhook_test.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ var _ = Describe("Disruption", func() {
219219
Describe("general errors expectations", func() {
220220
BeforeEach(func() {
221221
k8sClient = makek8sClientWithDisruptionPod()
222+
recorder = record.NewFakeRecorder(1)
222223
tracerSink = tracernoop.New(logger)
223224
deleteOnly = false
224225
})
@@ -231,6 +232,7 @@ var _ = Describe("Disruption", func() {
231232
AfterEach(func() {
232233
k8sClient = nil
233234
newDisruption = nil
235+
permittedUserGroups = map[string]struct{}{}
234236
})
235237

236238
When("disruption has delete-only mode enable", func() {
@@ -384,6 +386,32 @@ var _ = Describe("Disruption", func() {
384386
Expect(newDisruption.ValidateCreate().Error()).Should(ContainSubstring("inject.notBefore must come after createPods.notBefore if both are specified"))
385387
})
386388
})
389+
390+
When("user group membership is invalid", func() {
391+
It("should return an error if they lack membership", func() {
392+
permittedUserGroups = map[string]struct{}{}
393+
permittedUserGroups["system:nobody"] = struct{}{}
394+
permittedUserGroupWarningString = "system:nobody"
395+
396+
err := newDisruption.ValidateCreate()
397+
398+
Expect(err).Should(HaveOccurred())
399+
Expect(err.Error()).Should(ContainSubstring("lacking sufficient authorization to create disruptions. your user groups are [some], but you must be in one of the following groups: system:nobody"))
400+
})
401+
402+
It("should not return an error if they are within a permitted group", func() {
403+
ddmarkMock.EXPECT().ValidateStructMultierror(mock.Anything, mock.Anything).Return(&multierror.Error{})
404+
permittedUserGroups = map[string]struct{}{}
405+
permittedUserGroups["some"] = struct{}{}
406+
permittedUserGroups["any"] = struct{}{}
407+
permittedUserGroupWarningString = "some, any"
408+
409+
err := newDisruption.ValidateCreate()
410+
411+
Expect(err).ShouldNot(HaveOccurred())
412+
Expect(ddmarkMock.AssertNumberOfCalls(GinkgoT(), "ValidateStructMultierror", 1)).To(BeTrue())
413+
})
414+
})
387415
})
388416

389417
Describe("expectations with a disk failure disruption", func() {
@@ -502,7 +530,8 @@ func makeValidNetworkDisruption() *Disruption {
502530
IntVal: 1,
503531
},
504532
Network: &NetworkDisruptionSpec{
505-
Drop: 100,
533+
Drop: 100,
534+
Hosts: []NetworkDisruptionHostSpec{{Port: 80}},
506535
},
507536
Selector: labels.Set{
508537
"name": "random",

chart/templates/configmap.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ data:
5656
port: {{ .Values.controller.webhook.port }}
5757
safeMode:
5858
enable: {{ .Values.controller.safeMode.enable }}
59+
permittedUserGroups:
60+
{{- range $index, $group := .Values.controller.safeMode.permittedUserGroups }}
61+
- {{ $group | quote }}
62+
{{- end }}
5963
environment: {{ tpl .Values.controller.safeMode.environment . }}
6064
namespaceThreshold: {{ .Values.controller.safeMode.namespaceThreshold }}
6165
clusterThreshold: {{ .Values.controller.safeMode.clusterThreshold }}

chart/values.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ controller:
6666
port: 9443 # port to use to serve requests
6767
safeMode:
6868
environment: ""
69+
permittedUserGroups: # (optional) specify a list of strings which represent user info groups. if set, a user must belong to at least one in order to create a Disruption
70+
- system:authenticated
6971
enable: false
7072
namespaceThreshold: 80
7173
clusterThreshold: 66

config/config.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,11 @@ type controllerWebhookConfig struct {
5353
}
5454

5555
type safeModeConfig struct {
56-
Environment string `json:"environment"`
57-
Enable bool `json:"enable"`
58-
NamespaceThreshold int `json:"namespaceThreshold"`
59-
ClusterThreshold int `json:"clusterThreshold"`
56+
Environment string `json:"environment"`
57+
PermittedUserGroups []string `json:"permittedUserGroups"`
58+
Enable bool `json:"enable"`
59+
NamespaceThreshold int `json:"namespaceThreshold"`
60+
ClusterThreshold int `json:"clusterThreshold"`
6061
}
6162

6263
type injectorConfig struct {
@@ -320,6 +321,12 @@ func New(logger *zap.SugaredLogger, osArgs []string) (config, error) {
320321
return cfg, err
321322
}
322323

324+
mainFS.StringSliceVar(&cfg.Controller.SafeMode.PermittedUserGroups, "permitted-user-groups", []string{}, "Set of user groups which, if set, a user must belong to at least one in order to create a disruption")
325+
326+
if err := viper.BindPFlag("controller.safeMode.permittedUserGroups", mainFS.Lookup("permitted-user-groups")); err != nil {
327+
return cfg, err
328+
}
329+
323330
mainFS.BoolVar(&cfg.Controller.SafeMode.Enable, "safemode-enable", true,
324331
"Enable or disable the safemode functionality of the chaos-controller")
325332

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ func main() {
343343
ChaosNamespace: cfg.Injector.ChaosNamespace,
344344
CloudServicesProvidersManager: cloudProviderManager,
345345
Environment: cfg.Controller.SafeMode.Environment,
346+
PermittedUserGroups: cfg.Controller.SafeMode.PermittedUserGroups,
346347
}
347348
if err = (&chaosv1beta1.Disruption{}).SetupWebhookWithManager(setupWebhookConfig); err != nil {
348349
logger.Fatalw("unable to create webhook", "webhook", chaosv1beta1.DisruptionKind, "error", err)

utils/utils.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,5 @@ type SetupWebhookWithManagerConfig struct {
5555
ChaosNamespace string
5656
CloudServicesProvidersManager cloudservice.CloudServicesProvidersManager
5757
Environment string
58+
PermittedUserGroups []string
5859
}

0 commit comments

Comments
 (0)