Skip to content

Commit adf964a

Browse files
committed
OSD-16469 - Add ingress controller webhook
OSD-16469 - Update syncsets and docs OSD-16469 - Use correct apiGroups for ingress controller webhook OSD-16469 - Add exception for SREP OSD-16469 - Add exception for CDO OSD-16469 - Add exception for CIO OSD-16469 - Add exception for IO OSD-16469 - Change ingress controller webhook to use a blacklist approach OSD-16469 - Revert back to whitelist based access for ingresscontroller webhoo OSD-16469 - Add all Service Accounts on the IC webhook whitelist OSD-16469 - Update IC webhook with prefix check for system: and kube: users, add additional logs OSD-16469 - Update fake request arguments to match new definition
1 parent 2bdfc08 commit adf964a

File tree

6 files changed

+621
-2
lines changed

6 files changed

+621
-2
lines changed

build/selectorsyncset.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,38 @@ objects:
237237
scope: Cluster
238238
sideEffects: None
239239
timeoutSeconds: 2
240+
- apiVersion: admissionregistration.k8s.io/v1
241+
kind: ValidatingWebhookConfiguration
242+
metadata:
243+
annotations:
244+
service.beta.openshift.io/inject-cabundle: "true"
245+
creationTimestamp: null
246+
name: sre-ingresscontroller-validation
247+
webhooks:
248+
- admissionReviewVersions:
249+
- v1
250+
clientConfig:
251+
service:
252+
name: validation-webhook
253+
namespace: openshift-validation-webhook
254+
path: /ingresscontroller-validation
255+
failurePolicy: Ignore
256+
matchPolicy: Equivalent
257+
name: ingresscontroller-validation.managed.openshift.io
258+
rules:
259+
- apiGroups:
260+
- operator.openshift.io
261+
apiVersions:
262+
- '*'
263+
operations:
264+
- CREATE
265+
- UPDATE
266+
resources:
267+
- ingresscontroller
268+
- ingresscontrollers
269+
scope: Namespaced
270+
sideEffects: None
271+
timeoutSeconds: 1
240272
- apiVersion: admissionregistration.k8s.io/v1
241273
kind: ValidatingWebhookConfiguration
242274
metadata:

docs/webhooks-short.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
"webhookName": "imagecontentpolicies-validation",
1616
"documentString": "Managed OpenShift customers may not create ImageContentSourcePolicy, ImageDigestMirrorSet, or ImageTagMirrorSet resources that configure mirrors for the entirety of quay.io, registry.redhat.io, nor registry.access.redhat.com. If needed, specific repositories can have mirrors configured, such as quay.io/example."
1717
},
18+
{
19+
"webhookName": "ingresscontroller-validation",
20+
"documentString": "Managed OpenShift Customer may create IngressControllers without necessary taints. This can cause those workloads to be provisioned on infra or master nodes."
21+
},
1822
{
1923
"webhookName": "namespace-validation",
2024
"documentString": "Managed OpenShift Customers may not modify namespaces specified in the [openshift-monitoring/addons-namespaces openshift-monitoring/managed-namespaces openshift-monitoring/ocp-namespaces] ConfigMaps because customer workloads should be placed in customer-created namespaces. Customers may not create namespaces identified by this regular expression (^com$|^io$|^in$) because it could interfere with critical DNS resolution. Additionally, customers may not set or change the values of these Namespace labels [managed.openshift.io/storage-pv-quota-exempt managed.openshift.io/service-lb-quota-exempt]."
@@ -29,7 +33,7 @@
2933
},
3034
{
3135
"webhookName": "regular-user-validation",
32-
"documentString": "Managed OpenShift customers may not manage any objects in the following APIgroups [cloudcredential.openshift.io machine.openshift.io admissionregistration.k8s.io operator.openshift.io splunkforwarder.managed.openshift.io upgrade.managed.openshift.io machineconfiguration.openshift.io managed.openshift.io ocmagent.managed.openshift.io network.openshift.io config.openshift.io addons.managed.openshift.io cloudingress.managed.openshift.io autoscaling.openshift.io], nor may Managed OpenShift customers alter the APIServer, KubeAPIServer, OpenShiftAPIServer, ClusterVersion, Proxy or SubjectPermission objects."
36+
"documentString": "Managed OpenShift customers may not manage any objects in the following APIgroups [autoscaling.openshift.io cloudcredential.openshift.io admissionregistration.k8s.io ocmagent.managed.openshift.io upgrade.managed.openshift.io machine.openshift.io managed.openshift.io operator.openshift.io splunkforwarder.managed.openshift.io network.openshift.io addons.managed.openshift.io cloudingress.managed.openshift.io config.openshift.io machineconfiguration.openshift.io], nor may Managed OpenShift customers alter the APIServer, KubeAPIServer, OpenShiftAPIServer, ClusterVersion, Proxy or SubjectPermission objects."
3337
},
3438
{
3539
"webhookName": "regular-user-validation-osd",

docs/webhooks.json

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,29 @@
108108
],
109109
"documentString": "Managed OpenShift customers may not create ImageContentSourcePolicy, ImageDigestMirrorSet, or ImageTagMirrorSet resources that configure mirrors for the entirety of quay.io, registry.redhat.io, nor registry.access.redhat.com. If needed, specific repositories can have mirrors configured, such as quay.io/example."
110110
},
111+
{
112+
"webhookName": "ingresscontroller-validation",
113+
"rules": [
114+
{
115+
"operations": [
116+
"CREATE",
117+
"UPDATE"
118+
],
119+
"apiGroups": [
120+
"operator.openshift.io"
121+
],
122+
"apiVersions": [
123+
"*"
124+
],
125+
"resources": [
126+
"ingresscontroller",
127+
"ingresscontrollers"
128+
],
129+
"scope": "Namespaced"
130+
}
131+
],
132+
"documentString": "Managed OpenShift Customer may create IngressControllers without necessary taints. This can cause those workloads to be provisioned on infra or master nodes."
133+
},
111134
{
112135
"webhookName": "namespace-validation",
113136
"rules": [
@@ -318,7 +341,7 @@
318341
"scope": "*"
319342
}
320343
],
321-
"documentString": "Managed OpenShift customers may not manage any objects in the following APIgroups [addons.managed.openshift.io ocmagent.managed.openshift.io operator.openshift.io network.openshift.io admissionregistration.k8s.io cloudingress.managed.openshift.io splunkforwarder.managed.openshift.io upgrade.managed.openshift.io config.openshift.io cloudcredential.openshift.io machine.openshift.io managed.openshift.io autoscaling.openshift.io machineconfiguration.openshift.io], nor may Managed OpenShift customers alter the APIServer, KubeAPIServer, OpenShiftAPIServer, ClusterVersion, Proxy or SubjectPermission objects."
344+
"documentString": "Managed OpenShift customers may not manage any objects in the following APIgroups [operator.openshift.io splunkforwarder.managed.openshift.io config.openshift.io upgrade.managed.openshift.io autoscaling.openshift.io machineconfiguration.openshift.io network.openshift.io cloudcredential.openshift.io managed.openshift.io addons.managed.openshift.io cloudingress.managed.openshift.io ocmagent.managed.openshift.io machine.openshift.io admissionregistration.k8s.io], nor may Managed OpenShift customers alter the APIServer, KubeAPIServer, OpenShiftAPIServer, ClusterVersion, Proxy or SubjectPermission objects."
322345
},
323346
{
324347
"webhookName": "regular-user-validation-osd",
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package webhooks
2+
3+
import (
4+
"github.com/openshift/managed-cluster-validating-webhooks/pkg/webhooks/ingresscontroller"
5+
)
6+
7+
func init() {
8+
Register(ingresscontroller.WebhookName, func() Webhook { return ingresscontroller.NewWebhook() })
9+
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
package ingresscontroller
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"strings"
7+
8+
operatorv1 "github.com/openshift/api/operator/v1"
9+
"github.com/openshift/managed-cluster-validating-webhooks/pkg/webhooks/utils"
10+
admissionregv1 "k8s.io/api/admissionregistration/v1"
11+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12+
"k8s.io/apimachinery/pkg/runtime"
13+
admissionctl "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
14+
15+
logf "sigs.k8s.io/controller-runtime/pkg/log"
16+
)
17+
18+
const (
19+
WebhookName string = "ingresscontroller-validation"
20+
docString string = `Managed OpenShift Customer may create IngressControllers without necessary taints. This can cause those workloads to be provisioned on infra or master nodes.`
21+
)
22+
23+
var (
24+
log = logf.Log.WithName(WebhookName)
25+
scope = admissionregv1.NamespacedScope
26+
rules = []admissionregv1.RuleWithOperations{
27+
{
28+
Operations: []admissionregv1.OperationType{admissionregv1.Create, admissionregv1.Update},
29+
Rule: admissionregv1.Rule{
30+
APIGroups: []string{"operator.openshift.io"},
31+
APIVersions: []string{"*"},
32+
Resources: []string{"ingresscontroller", "ingresscontrollers"},
33+
Scope: &scope,
34+
},
35+
},
36+
}
37+
allowedUsers = []string{
38+
"backplane-cluster-admin",
39+
}
40+
)
41+
42+
type IngressControllerWebhook struct {
43+
s runtime.Scheme
44+
}
45+
46+
// ObjectSelector implements Webhook interface
47+
func (wh *IngressControllerWebhook) ObjectSelector() *metav1.LabelSelector { return nil }
48+
49+
func (wh *IngressControllerWebhook) Doc() string {
50+
return fmt.Sprintf(docString)
51+
}
52+
53+
// TimeoutSeconds implements Webhook interface
54+
func (wh *IngressControllerWebhook) TimeoutSeconds() int32 { return 1 }
55+
56+
// MatchPolicy implements Webhook interface
57+
func (wh *IngressControllerWebhook) MatchPolicy() admissionregv1.MatchPolicyType {
58+
return admissionregv1.Equivalent
59+
}
60+
61+
// Name implements Webhook interface
62+
func (wh *IngressControllerWebhook) Name() string { return WebhookName }
63+
64+
// FailurePolicy implements Webhook interface and defines how unrecognized errors and timeout errors from the admission webhook are handled. Allowed values are Ignore or Fail.
65+
// Ignore means that an error calling the webhook is ignored and the API request is allowed to continue.
66+
// It's important to leave the FailurePolicy set to Ignore because otherwise the pod will fail to be created as the API request will be rejected.
67+
func (wh *IngressControllerWebhook) FailurePolicy() admissionregv1.FailurePolicyType {
68+
return admissionregv1.Ignore
69+
}
70+
71+
// Rules implements Webhook interface
72+
func (wh *IngressControllerWebhook) Rules() []admissionregv1.RuleWithOperations { return rules }
73+
74+
// GetURI implements Webhook interface
75+
func (wh *IngressControllerWebhook) GetURI() string { return "/" + WebhookName }
76+
77+
// SideEffects implements Webhook interface
78+
func (wh *IngressControllerWebhook) SideEffects() admissionregv1.SideEffectClass {
79+
return admissionregv1.SideEffectClassNone
80+
}
81+
82+
// Validate implements Webhook interface
83+
func (wh *IngressControllerWebhook) Validate(req admissionctl.Request) bool {
84+
valid := true
85+
valid = valid && (req.UserInfo.Username != "")
86+
valid = valid && (req.Kind.Kind == "IngressController")
87+
88+
return valid
89+
}
90+
91+
func (wh *IngressControllerWebhook) renderIngressController(req admissionctl.Request) (*operatorv1.IngressController, error) {
92+
decoder, err := admissionctl.NewDecoder(&wh.s)
93+
if err != nil {
94+
return nil, err
95+
}
96+
ic := &operatorv1.IngressController{}
97+
err = decoder.DecodeRaw(req.Object, ic)
98+
99+
if err != nil {
100+
return nil, err
101+
}
102+
103+
return ic, nil
104+
}
105+
106+
func (wh *IngressControllerWebhook) authorized(request admissionctl.Request) admissionctl.Response {
107+
var ret admissionctl.Response
108+
ic, err := wh.renderIngressController(request)
109+
if err != nil {
110+
log.Error(err, "Couldn't render an IngressController from the incoming request")
111+
return admissionctl.Errored(http.StatusBadRequest, err)
112+
}
113+
114+
log.Info("Checking if user is unauthenticated")
115+
if request.AdmissionRequest.UserInfo.Username == "system:unauthenticated" {
116+
// This could highlight a significant problem with RBAC since an
117+
// unauthenticated user should have no permissions.
118+
log.Info("system:unauthenticated made a webhook request. Check RBAC rules", "request", request.AdmissionRequest)
119+
ret = admissionctl.Denied("Unauthenticated")
120+
ret.UID = request.AdmissionRequest.UID
121+
return ret
122+
}
123+
124+
log.Info("Checking if user is authenticated system: user")
125+
if strings.HasPrefix(request.AdmissionRequest.UserInfo.Username, "system:") {
126+
ret = admissionctl.Allowed("authenticated system: users are allowed")
127+
ret.UID = request.AdmissionRequest.UID
128+
return ret
129+
}
130+
131+
log.Info("Checking if user is kube: user")
132+
if strings.HasPrefix(request.AdmissionRequest.UserInfo.Username, "kube:") {
133+
ret = admissionctl.Allowed("kube: users are allowed")
134+
ret.UID = request.AdmissionRequest.UID
135+
return ret
136+
}
137+
138+
// Check if the group does not have exceptions
139+
if !isAllowedUser(request) {
140+
for _, toleration := range ic.Spec.NodePlacement.Tolerations {
141+
if strings.Contains(toleration.Key, "node-role.kubernetes.io/master") || strings.Contains(toleration.Key, "node-role.kubernetes.io/infra") {
142+
ret = admissionctl.Denied("Not allowed to provision ingress controller pods with toleration for master and infra nodes.")
143+
ret.UID = request.AdmissionRequest.UID
144+
145+
return ret
146+
}
147+
}
148+
}
149+
150+
ret = admissionctl.Allowed("IngressController operation is allowed")
151+
ret.UID = request.AdmissionRequest.UID
152+
153+
return ret
154+
}
155+
156+
// isAllowedUser checks if the user is allowed to perform the action
157+
func isAllowedUser(request admissionctl.Request) bool {
158+
log.Info(fmt.Sprintf("Checking username %s on whitelist", request.UserInfo.Username))
159+
if utils.SliceContains(request.UserInfo.Username, allowedUsers) {
160+
log.Info(fmt.Sprintf("%s is listed in whitelist", request.UserInfo.Username))
161+
return true
162+
}
163+
164+
log.Info("No allowed user found")
165+
166+
return false
167+
}
168+
169+
// Authorized implements Webhook interface
170+
func (wh *IngressControllerWebhook) Authorized(request admissionctl.Request) admissionctl.Response {
171+
return wh.authorized(request)
172+
}
173+
174+
// SyncSetLabelSelector returns the label selector to use in the SyncSet.
175+
func (s *IngressControllerWebhook) SyncSetLabelSelector() metav1.LabelSelector {
176+
return utils.DefaultLabelSelector()
177+
}
178+
179+
func (s *IngressControllerWebhook) HypershiftEnabled() bool { return false }
180+
181+
// NewWebhook creates a new webhook
182+
func NewWebhook() *IngressControllerWebhook {
183+
scheme := runtime.NewScheme()
184+
return &IngressControllerWebhook{
185+
s: *scheme,
186+
}
187+
}

0 commit comments

Comments
 (0)