Skip to content

Commit c4265b6

Browse files
authored
Automatically allow webhook traffic using the port defined in the webhook configuration & use new confoguration (#598)
1 parent cfa36a3 commit c4265b6

File tree

9 files changed

+496
-229
lines changed

9 files changed

+496
-229
lines changed

src/operator/controllers/webhook_traffic/network_policy_handler.go

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/otterize/intents-operator/src/shared/serviceidresolver"
1111
"github.com/otterize/intents-operator/src/shared/serviceidresolver/serviceidentity"
1212
"github.com/samber/lo"
13+
"github.com/sirupsen/logrus"
1314
"golang.org/x/exp/slices"
1415
admissionv1 "k8s.io/api/admissionregistration/v1"
1516
corev1 "k8s.io/api/core/v1"
@@ -33,6 +34,7 @@ const (
3334
ReasonPatchingWebhookTrafficNetpol = "PatchingNetworkPolicyForWebhook"
3435
ReasonPatchingWebhookTrafficNetpolFailed = "PatchingNetworkPolicyForWebhookFailed"
3536
ReasonPatchingWebhookTrafficNetpolSuccess = "PatchingNetworkPolicyForWebhookSucceeded"
37+
ReasonWebhookPortNotFoundOnServiceError = "WebhookPortNotFoundOnServiceError"
3638
)
3739

3840
type WebhookClientConfigurationWithMeta struct {
@@ -168,7 +170,7 @@ func (n *NetworkPolicyHandler) reduceWebhooksNetpols(ctx context.Context, webhoo
168170
// At this point we want to create the network policy, because the configuration is either set to "always" or
169171
// that it is set to "if blocked by otterize" and the service is blocked by otterize
170172
// TODO: do we also need to create netpol for Otterize?
171-
netpol, err := n.buildNetworkPolicy(ctx, webhookClientConfig.webhookName, service)
173+
netpol, err := n.buildNetworkPolicy(ctx, webhookClientConfig.webhookName, webhookClientConfig.clientConfiguration.Service, service)
172174
if err != nil {
173175
return make(NetworkPolicyWithMetaByName), errors.Wrap(err)
174176
}
@@ -192,6 +194,9 @@ func (n *NetworkPolicyHandler) reduceWebhooksNetpols(ctx context.Context, webhoo
192194
func (n *NetworkPolicyHandler) isServiceBlockedByOtterize(ctx context.Context, service *corev1.Service) (bool, error) {
193195
endpoints := &corev1.Endpoints{}
194196
err := n.client.Get(ctx, types.NamespacedName{Namespace: service.Namespace, Name: service.Name}, endpoints)
197+
if err != nil && k8serrors.IsNotFound(err) {
198+
return false, nil
199+
}
195200
if err != nil {
196201
return false, errors.Wrap(err)
197202
}
@@ -200,10 +205,8 @@ func (n *NetworkPolicyHandler) isServiceBlockedByOtterize(ctx context.Context, s
200205

201206
for _, address := range endpointsAddresses {
202207
pod, err := n.getAffectedPod(ctx, address)
203-
if k8sErr := &(k8serrors.StatusError{}); errors.As(err, &k8sErr) {
204-
if k8serrors.IsNotFound(k8sErr) {
205-
continue
206-
}
208+
if k8serrors.IsNotFound(err) {
209+
continue
207210
}
208211

209212
if err != nil {
@@ -307,7 +310,7 @@ func (n *NetworkPolicyHandler) getWebhookService(ctx context.Context, webhookSer
307310
return service, true, nil
308311
}
309312

310-
func (n *NetworkPolicyHandler) buildNetworkPolicy(ctx context.Context, webhookName string, service *corev1.Service) (v1.NetworkPolicy, error) {
313+
func (n *NetworkPolicyHandler) buildNetworkPolicy(ctx context.Context, webhookName string, webhookService *admissionv1.ServiceReference, service *corev1.Service) (v1.NetworkPolicy, error) {
311314
policyName := fmt.Sprintf("webhook-%s-access-to-%s", strings.ToLower(webhookName), strings.ToLower(service.Name))
312315
rule := v1.NetworkPolicyIngressRule{}
313316

@@ -352,23 +355,43 @@ func (n *NetworkPolicyHandler) buildNetworkPolicy(ctx context.Context, webhookNa
352355
},
353356
}
354357

355-
netpolPorts := make([]v1.NetworkPolicyPort, 0, 2*len(service.Spec.Ports))
356-
357-
for _, servicePort := range service.Spec.Ports {
358-
// Add the port
359-
netpolPorts = append(netpolPorts,
360-
v1.NetworkPolicyPort{
361-
Port: lo.ToPtr(intstr.IntOrString{IntVal: servicePort.Port, Type: intstr.Int}),
362-
Protocol: lo.ToPtr(servicePort.Protocol),
363-
})
364-
// Add the target port
365-
if servicePort.TargetPort.IntVal != 0 || servicePort.TargetPort.StrVal != "" {
366-
netpolPorts = append(netpolPorts,
367-
v1.NetworkPolicyPort{
368-
Port: lo.ToPtr(servicePort.TargetPort),
369-
Protocol: lo.ToPtr(servicePort.Protocol),
370-
})
371-
}
358+
netpolPorts := make([]v1.NetworkPolicyPort, 0, 2)
359+
if webhookService.Port != nil {
360+
// If the webhook defines a port - lets use it
361+
netpolPorts = append(netpolPorts, v1.NetworkPolicyPort{
362+
Port: lo.ToPtr(intstr.IntOrString{IntVal: *webhookService.Port, Type: intstr.Int}),
363+
Protocol: lo.ToPtr(corev1.ProtocolTCP),
364+
})
365+
} else {
366+
// Otherwise, use the defaukt webhook port - 443
367+
netpolPorts = append(netpolPorts, v1.NetworkPolicyPort{
368+
Port: lo.ToPtr(intstr.IntOrString{IntVal: 443, Type: intstr.Int}),
369+
Protocol: lo.ToPtr(corev1.ProtocolTCP),
370+
})
371+
}
372+
373+
// Find the webhook's-service's-port in the service
374+
servicePort, servicePortFound := lo.Find(service.Spec.Ports, func(item corev1.ServicePort) bool {
375+
return item.Port == netpolPorts[0].Port.IntVal
376+
})
377+
378+
if !servicePortFound {
379+
logrus.WithFields(logrus.Fields{
380+
"WebhookName": webhookName,
381+
"ServiceName": service.Name,
382+
"ServiceNamespace": service.Namespace,
383+
"ServicePort": netpolPorts[0].Port.IntVal,
384+
}).Warning("Webhook service port not found on service resource")
385+
n.RecordWarningEventf(service, ReasonWebhookPortNotFoundOnServiceError, "Webhook %s is defined to work on port %d", webhookName, netpolPorts[0].Port.IntVal)
386+
return v1.NetworkPolicy{}, errors.New("Webhook port was not found on service")
387+
}
388+
389+
// Add the service's target port if exists
390+
if servicePort.TargetPort.IntVal != 0 || servicePort.TargetPort.StrVal != "" {
391+
netpolPorts = append(netpolPorts, v1.NetworkPolicyPort{
392+
Port: lo.ToPtr(servicePort.TargetPort),
393+
Protocol: lo.ToPtr(servicePort.Protocol),
394+
})
372395
}
373396

374397
newPolicy.Spec.Ingress[0].Ports = netpolPorts

src/operator/controllers/webhook_traffic/network_policy_handler_test.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,101 @@ func (s *NetworkPolicyHandlerTestSuite) TestNetworkPolicyHandler_HandleAlways_Se
407407
s.ExpectEvent(ReasonCreatingWebhookTrafficNetpolSuccess)
408408
}
409409

410+
func (s *NetworkPolicyHandlerTestSuite) TestNetworkPolicyHandler_HandleAlways_ServiceHasNoTargetPortThanPort_CreatePolicy() {
411+
s.handler = NewNetworkPolicyHandler(s.Client, &runtime.Scheme{}, automate_third_party_network_policy.Always, 32, false)
412+
s.handler.InjectRecorder(s.Recorder)
413+
414+
s.webhookService = &corev1.Service{
415+
ObjectMeta: metav1.ObjectMeta{
416+
Name: TestServiceName,
417+
Namespace: TestNamespace,
418+
},
419+
Spec: corev1.ServiceSpec{
420+
Selector: map[string]string{
421+
"Taylor": "Swift",
422+
},
423+
Ports: []corev1.ServicePort{
424+
{
425+
Port: TestServicePort,
426+
Protocol: corev1.ProtocolTCP,
427+
},
428+
},
429+
},
430+
}
431+
432+
s.mockForReturningValidatingWebhook()
433+
s.mockReturningWebhookService()
434+
//s.mockServiceIsBlockedByOtterize(make([]v1.NetworkPolicy, 0))
435+
s.mockGetControlPlaneIPs()
436+
s.mockGetExistingOtterizeWebhooksNetpols([]v1.NetworkPolicy{})
437+
438+
netpolMatcher := NewNetworkPolicyMatcher([]int32{TestServicePort}, s.handler.allowAllIncomingTraffic, nil)
439+
s.Client.EXPECT().Create(gomock.Any(), gomock.All(netpolMatcher)).Return(nil)
440+
err := s.handler.HandleAll(context.Background())
441+
s.Require().NoError(err)
442+
s.ExpectEvent(ReasonCreatingWebhookTrafficNetpol)
443+
s.ExpectEvent(ReasonCreatingWebhookTrafficNetpolSuccess)
444+
}
445+
446+
func (s *NetworkPolicyHandlerTestSuite) TestNetworkPolicyHandler_HandleAlways_WebhookDoesNotDefinePort_CreatePolicyWithDefaultPort() {
447+
s.handler = NewNetworkPolicyHandler(s.Client, &runtime.Scheme{}, automate_third_party_network_policy.Always, 32, false)
448+
s.handler.InjectRecorder(s.Recorder)
449+
450+
s.validatingWebhook = ValidatingWebhookConfiguration.DeepCopy()
451+
s.validatingWebhook.Webhooks[0].ClientConfig.Service.Port = nil
452+
453+
s.webhookService = &corev1.Service{
454+
ObjectMeta: metav1.ObjectMeta{
455+
Name: TestServiceName,
456+
Namespace: TestNamespace,
457+
},
458+
Spec: corev1.ServiceSpec{
459+
Selector: map[string]string{
460+
"Taylor": "Swift",
461+
},
462+
Ports: []corev1.ServicePort{
463+
{
464+
Port: 443,
465+
Protocol: corev1.ProtocolTCP,
466+
},
467+
},
468+
},
469+
}
470+
471+
s.mockForReturningValidatingWebhook()
472+
s.mockReturningWebhookService()
473+
//s.mockServiceIsBlockedByOtterize(make([]v1.NetworkPolicy, 0))
474+
s.mockGetControlPlaneIPs()
475+
s.mockGetExistingOtterizeWebhooksNetpols([]v1.NetworkPolicy{})
476+
477+
netpolMatcher := NewNetworkPolicyMatcher([]int32{443}, s.handler.allowAllIncomingTraffic, nil)
478+
s.Client.EXPECT().Create(gomock.Any(), gomock.All(netpolMatcher)).Return(nil)
479+
err := s.handler.HandleAll(context.Background())
480+
s.Require().NoError(err)
481+
s.ExpectEvent(ReasonCreatingWebhookTrafficNetpol)
482+
s.ExpectEvent(ReasonCreatingWebhookTrafficNetpolSuccess)
483+
}
484+
485+
func (s *NetworkPolicyHandlerTestSuite) TestNetworkPolicyHandler_HandleAlways_WebhookPortNotFoundOnService_ExpectErrors() {
486+
s.handler = NewNetworkPolicyHandler(s.Client, &runtime.Scheme{}, automate_third_party_network_policy.Always, 32, false)
487+
s.handler.InjectRecorder(s.Recorder)
488+
489+
s.validatingWebhook = ValidatingWebhookConfiguration.DeepCopy()
490+
s.validatingWebhook.Webhooks[0].ClientConfig.Service.Port = nil
491+
492+
s.mockForReturningValidatingWebhook()
493+
s.mockReturningWebhookService()
494+
//s.mockServiceIsBlockedByOtterize(make([]v1.NetworkPolicy, 0))
495+
s.mockGetControlPlaneIPs()
496+
//s.mockGetExistingOtterizeWebhooksNetpols([]v1.NetworkPolicy{})
497+
498+
//netpolMatcher := NewNetworkPolicyMatcher([]int32{443}, s.handler.allowAllIncomingTraffic, nil)
499+
//s.Client.EXPECT().Create(gomock.Any(), gomock.All(netpolMatcher)).Return(nil)
500+
err := s.handler.HandleAll(context.Background())
501+
s.Require().Error(err, "Webhook port was not found on service")
502+
s.ExpectEvent(ReasonWebhookPortNotFoundOnServiceError)
503+
}
504+
410505
func (s *NetworkPolicyHandlerTestSuite) TestNetworkPolicyHandler_HandleAlways_WebhookNameTooLong_CreatePolicy() {
411506
s.handler = NewNetworkPolicyHandler(s.Client, &runtime.Scheme{}, automate_third_party_network_policy.Always, 32, false)
412507
s.handler.InjectRecorder(s.Recorder)
@@ -427,6 +522,46 @@ func (s *NetworkPolicyHandlerTestSuite) TestNetworkPolicyHandler_HandleAlways_We
427522
s.ExpectEvent(ReasonCreatingWebhookTrafficNetpolSuccess)
428523
}
429524

525+
func (s *NetworkPolicyHandlerTestSuite) TestNetworkPolicyHandler_HandleIfBlockedByOtterize_ServiceIsBlockedByOtterize_TwoWebhooksToSameServiceDifferentPorts_CreatingOneWebhookPolicy() {
526+
secondPort := int32(1432)
527+
s.validatingWebhook.Webhooks = append(s.validatingWebhook.Webhooks,
528+
admissionv1.ValidatingWebhook{
529+
Name: "Second",
530+
ClientConfig: admissionv1.WebhookClientConfig{
531+
Service: &admissionv1.ServiceReference{
532+
Name: TestServiceName,
533+
Namespace: TestNamespace,
534+
Port: lo.ToPtr(secondPort),
535+
},
536+
},
537+
})
538+
s.webhookService.Spec.Ports = append(s.webhookService.Spec.Ports, corev1.ServicePort{
539+
Port: secondPort,
540+
Protocol: corev1.ProtocolTCP,
541+
})
542+
543+
s.mockForReturningValidatingWebhook()
544+
545+
// Called once for "First" webhook
546+
s.mockReturningWebhookService()
547+
s.mockServiceIsBlockedByOtterize(OtterizeIngressNetpols)
548+
s.mockGetControlPlaneIPs()
549+
550+
// Called second time for "Second"" webhook
551+
s.mockReturningWebhookService()
552+
s.mockServiceIsBlockedByOtterize(OtterizeIngressNetpols)
553+
s.mockGetControlPlaneIPs()
554+
555+
s.mockGetExistingOtterizeWebhooksNetpols([]v1.NetworkPolicy{})
556+
557+
netpolMatcher := NewNetworkPolicyMatcher([]int32{secondPort, TestServicePort}, s.handler.allowAllIncomingTraffic, nil)
558+
s.Client.EXPECT().Create(gomock.Any(), gomock.All(netpolMatcher)).Return(nil)
559+
err := s.handler.HandleAll(context.Background())
560+
s.Require().NoError(err)
561+
s.ExpectEvent(ReasonCreatingWebhookTrafficNetpol)
562+
s.ExpectEvent(ReasonCreatingWebhookTrafficNetpolSuccess)
563+
}
564+
430565
func (s *NetworkPolicyHandlerTestSuite) mockForReturningValidatingWebhook() {
431566
s.Client.EXPECT().List(
432567
gomock.Any(), gomock.Eq(&admissionv1.ValidatingWebhookConfigurationList{}),

src/operator/main.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ import (
5252
"github.com/otterize/intents-operator/src/shared/k8sconf"
5353
"github.com/otterize/intents-operator/src/shared/operator_cloud_client"
5454
"github.com/otterize/intents-operator/src/shared/operatorconfig"
55-
"github.com/otterize/intents-operator/src/shared/operatorconfig/automate_third_party_network_policy"
5655
"github.com/otterize/intents-operator/src/shared/operatorconfig/enforcement"
5756
"github.com/otterize/intents-operator/src/shared/reconcilergroup"
5857
"github.com/otterize/intents-operator/src/shared/serviceidresolver"
@@ -240,7 +239,7 @@ func main() {
240239

241240
webhooksTrafficNetworkHandler := webhook_traffic.NewNetworkPolicyHandler(mgr.GetClient(),
242241
scheme,
243-
automate_third_party_network_policy.Off,
242+
enforcementConfig.GetAutomateAllowWebhookTraffic(),
244243
viper.GetInt(operatorconfig.ControlPlaneIPv4CidrPrefixLength),
245244
viper.GetBool(operatorconfig.WebhookTrafficAllowAllKey))
246245
webhookTrafficReconcilerManager := webhook_traffic.NewWebhookTrafficReconcilerManager(mgr.GetClient(), webhooksTrafficNetworkHandler)

src/shared/operator_cloud_client/status_report.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,19 @@ func getAllowExternalTrafficConfig() graphqlclient.AllowExternalTrafficPolicy {
9494
}
9595
}
9696

97+
func getAutomateAllowWebhookTrafficConfig() graphqlclient.AutomateThirdPartyNetworkPolicy {
98+
switch enforcement.GetConfig().AutomateAllowWebhookTraffic {
99+
case automate_third_party_network_policy.Always:
100+
return graphqlclient.AutomateThirdPartyNetworkPolicyAlways
101+
case automate_third_party_network_policy.Off:
102+
return graphqlclient.AutomateThirdPartyNetworkPolicyOff
103+
case automate_third_party_network_policy.IfBlockedByOtterize:
104+
return graphqlclient.AutomateThirdPartyNetworkPolicyIfBlockedByOtterize
105+
default:
106+
return ""
107+
}
108+
}
109+
97110
func uploadConfiguration(ctx context.Context, client CloudClient, mgr manager.Manager) {
98111
ingressConfigIdentities := operatorconfig.GetIngressControllerServiceIdentities()
99112
externallyManagedPolicyWorkloadIdentities := operatorconfig.GetExternallyManagedPoliciesServiceIdentities()
@@ -143,6 +156,7 @@ func uploadConfiguration(ctx context.Context, client CloudClient, mgr manager.Ma
143156
AllowExternalTrafficPolicy: getAllowExternalTrafficConfig(), // The server expect for AllowExternalTrafficPolicy because of backwards compatibility
144157
AutomateThirdPartyNetworkPolicies: getAutomateThirdPartyNetworkPoliciesConfig(),
145158
PrometheusServerConfigs: getPrometheusServiceIdentities(),
159+
AutomateAllowWebhookTraffic: getAutomateAllowWebhookTrafficConfig(),
146160
}
147161

148162
configInput.IngressControllerConfig = lo.Map(ingressConfigIdentities, func(identity serviceidentity.ServiceIdentity, _ int) graphqlclient.IngressControllerConfigInput {
@@ -165,7 +179,6 @@ func uploadConfiguration(ctx context.Context, client CloudClient, mgr manager.Ma
165179
configInput.AutomatedThirdPartyPolicyTypes = []graphqlclient.AutomatedThirdPartyPolicyTypes{
166180
graphqlclient.AutomatedThirdPartyPolicyTypesExternalTraffic,
167181
graphqlclient.AutomatedThirdPartyPolicyTypesMetricsTraffic,
168-
graphqlclient.AutomatedThirdPartyPolicyTypesWebhookTraffic,
169182
}
170183

171184
client.ReportIntentsOperatorConfiguration(timeoutCtx, configInput)

src/shared/operatorconfig/enforcement/config.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type Config struct {
2525
EnforcedNamespaces *goset.Set[string]
2626
ExcludedStrictModeNamespaces *goset.Set[string]
2727
AutomateThirdPartyNetworkPolicies automate_third_party_network_policy.Enum
28+
AutomateAllowWebhookTraffic automate_third_party_network_policy.Enum
2829
PrometheusServiceIdentities []serviceidentity.ServiceIdentity
2930
}
3031

@@ -46,10 +47,29 @@ func (c Config) GetAutomateThirdPartyNetworkPolicy() automate_third_party_networ
4647
}
4748
}
4849

50+
func (c Config) GetAutomateAllowWebhookTraffic() automate_third_party_network_policy.Enum {
51+
switch c.AutomateAllowWebhookTraffic {
52+
case automate_third_party_network_policy.Off:
53+
return automate_third_party_network_policy.Off
54+
case automate_third_party_network_policy.Always:
55+
if !c.EnforcementDefaultState {
56+
// We don't want to create network policies for third parties when enforcement is disabled.
57+
// However, if one uses shadow mode we can still block third party traffic to his protected services
58+
// therefore we should return automate_third_party_network_policy.IfBlockedByOtterize
59+
return automate_third_party_network_policy.IfBlockedByOtterize
60+
}
61+
return automate_third_party_network_policy.Always
62+
default:
63+
return automate_third_party_network_policy.IfBlockedByOtterize
64+
}
65+
}
66+
4967
const (
5068
ActiveEnforcementNamespacesKey = "active-enforcement-namespaces" // When using the "shadow enforcement" mode, namespaces in this list will be treated as if the enforcement were active
5169
AutomateThirdPartyNetworkPoliciesKey = "automate-third-party-network-policies" // Whether to automatically create network policies for external traffic & metrics collection traffic
5270
AutomateThirdPartyNetworkPoliciesDefault = string(automate_third_party_network_policy.IfBlockedByOtterize)
71+
AutomateAllowWebhookTrafficKey = "automate-allow-webhook-traffic" // Whether to automatically create network policies for webhook services
72+
AutomateAllowWebhookTrafficDefault = string(automate_third_party_network_policy.IfBlockedByOtterize)
5373
EnforcementDefaultStateKey = "enforcement-default-state" // Sets the default state of the If true, always enforces. If false, can be overridden using ProtectedService.
5474
EnforcementDefaultStateDefault = true
5575
EnableNetworkPolicyKey = "enable-network-policy-creation" // Whether to enable Intents network policy creation
@@ -88,6 +108,7 @@ func init() {
88108
viper.SetDefault(EnableGCPPolicyKey, EnableGCPPolicyDefault)
89109
viper.SetDefault(EnableAzurePolicyKey, EnableAzurePolicyDefault)
90110
viper.SetDefault(AutomateThirdPartyNetworkPoliciesKey, AutomateThirdPartyNetworkPoliciesDefault)
111+
viper.SetDefault(AutomateAllowWebhookTrafficKey, AutomateAllowWebhookTrafficDefault)
91112
viper.SetDefault(EnableStrictModeIntentsKey, EnableStrictModeIntentsDefault)
92113
}
93114

@@ -121,6 +142,7 @@ func GetConfig() Config {
121142
EnforcedNamespaces: goset.FromSlice(viper.GetStringSlice(ActiveEnforcementNamespacesKey)),
122143
ExcludedStrictModeNamespaces: goset.FromSlice(viper.GetStringSlice(ActiveEnforcementNamespacesKey)),
123144
AutomateThirdPartyNetworkPolicies: automate_third_party_network_policy.Enum(viper.GetString(AutomateThirdPartyNetworkPoliciesKey)),
145+
AutomateAllowWebhookTraffic: automate_third_party_network_policy.Enum(viper.GetString(AutomateAllowWebhookTrafficKey)),
124146
PrometheusServiceIdentities: GetPrometheusServiceIdentities(),
125147
}
126148
}

0 commit comments

Comments
 (0)