diff --git a/CHANGELOG.md b/CHANGELOG.md index a04bbf78e9..f6a52b3bcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,12 @@ IMPROVEMENTS: * `consul.hashicorp.com/transparent-proxy-exclude-outbound-cidrs` - Comma-separated list of IPs or CIDRs to exclude. * `consul.hashicorp.com/transparent-proxy-exclude-uids` - Comma-separated list of Linux user IDs to exclude. +* Connect: Add the ability to set default tproxy mode at namespace level via label. [[GH-501](https://github.com/hashicorp/consul-k8s/pull/510)] + + * Setting the annotation `consul.hashicorp.com/transparent-proxy` to `true/false` will define whether tproxy is enabled/disabled for the pod. + * Setting the label `consul.hashicorp.com/transparent-proxy` to `true/false` on a namespace will define the default behavior for pods in that namespace, which do not also have the annotation set. + * The default tproxy behavior will be defined by the value of `-enable-transparent-proxy` flag to the `consul-k8s inject-connect` command. It can be overridden in a namespace by the the label on the namespace or for a pod using the annotation on the pod. + BUG FIXES: * Connect: Use `runAsNonRoot: false` for connect-init's container when tproxy is enabled. [[GH-493](https://github.com/hashicorp/consul-k8s/pull/493)] * CRDs: Fix a bug where the `config` field in `ProxyDefaults` CR was not synced to Consul because diff --git a/connect-inject/annotations.go b/connect-inject/annotations.go index 9be8bbd0c1..32bbc7b75a 100644 --- a/connect-inject/annotations.go +++ b/connect-inject/annotations.go @@ -84,9 +84,11 @@ const ( // annotationConsulNamespace is the Consul namespace the service is registered into. annotationConsulNamespace = "consul.hashicorp.com/consul-namespace" - // annotationTransparentProxy enables or disables transparent proxy mode for a given pod. + // keyTransparentProxy enables or disables transparent proxy for a given pod. It can also be set as a label + // on a namespace to define the default behaviour for connect-injected pods which do not otherwise override this setting + // with their own annotation. // This annotation takes a boolean value (true/false). - annotationTransparentProxy = "consul.hashicorp.com/transparent-proxy" + keyTransparentProxy = "consul.hashicorp.com/transparent-proxy" // annotationTProxyExcludeInboundPorts is a comma-separated list of inbound ports to exclude from traffic redirection. annotationTProxyExcludeInboundPorts = "consul.hashicorp.com/transparent-proxy-exclude-inbound-ports" diff --git a/connect-inject/container_init.go b/connect-inject/container_init.go index 21bcd72089..8a3718618e 100644 --- a/connect-inject/container_init.go +++ b/connect-inject/container_init.go @@ -92,16 +92,16 @@ func (h *Handler) containerInitCopyContainer() corev1.Container { // containerInit returns the init container spec for registering the Consul // service, setting up the Envoy bootstrap, etc. -func (h *Handler) containerInit(pod corev1.Pod, k8sNamespace string) (corev1.Container, error) { +func (h *Handler) containerInit(namespace corev1.Namespace, pod corev1.Pod) (corev1.Container, error) { // Check if tproxy is enabled on this pod. - tproxyEnabled, err := transparentProxyEnabled(pod, h.EnableTransparentProxy) + tproxyEnabled, err := transparentProxyEnabled(namespace, pod, h.EnableTransparentProxy) if err != nil { return corev1.Container{}, err } data := initContainerCommandData{ AuthMethod: h.AuthMethod, - ConsulNamespace: h.consulNamespace(k8sNamespace), + ConsulNamespace: h.consulNamespace(namespace.Name), NamespaceMirroringEnabled: h.EnableK8SNSMirroring, ConsulCACert: h.ConsulCACert, EnableTransparentProxy: tproxyEnabled, @@ -214,12 +214,18 @@ func (h *Handler) containerInit(pod corev1.Pod, k8sNamespace string) (corev1.Con } // transparentProxyEnabled returns true if transparent proxy should be enabled for this pod. -// It returns an error when the annotation value cannot be parsed by strconv.ParseBool. -func transparentProxyEnabled(pod corev1.Pod, globalEnabled bool) (bool, error) { - if raw, ok := pod.Annotations[annotationTransparentProxy]; ok { +// It returns an error when the annotation value cannot be parsed by strconv.ParseBool or if we are unable +// to read the pod's namespace label when it exists. +func transparentProxyEnabled(namespace corev1.Namespace, pod corev1.Pod, globalEnabled bool) (bool, error) { + // First check to see if the pod annotation exists to override the namespace or global settings. + if raw, ok := pod.Annotations[keyTransparentProxy]; ok { return strconv.ParseBool(raw) } - + // Next see if the namespace has been defaulted. + if raw, ok := namespace.Labels[keyTransparentProxy]; ok { + return strconv.ParseBool(raw) + } + // Else fall back to the global default. return globalEnabled, nil } diff --git a/connect-inject/container_init_test.go b/connect-inject/container_init_test.go index da3b0dd908..957a102796 100644 --- a/connect-inject/container_init_test.go +++ b/connect-inject/container_init_test.go @@ -129,7 +129,7 @@ consul-k8s connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ h := tt.Handler pod := *tt.Pod(minimal()) - container, err := h.containerInit(pod, k8sNamespace) + container, err := h.containerInit(testNS, pod) require.NoError(err) actual := strings.Join(container.Command, " ") require.Contains(actual, tt.Cmd) @@ -146,67 +146,66 @@ func TestHandlerContainerInit_transparentProxy(t *testing.T) { annotations map[string]string expectedContainsCmd string expectedNotContainsCmd string + namespaceLabel map[string]string }{ - "enabled globally, annotation not provided": { + "enabled globally, ns not set, annotation not provided": { true, nil, `/consul/connect-inject/consul connect redirect-traffic \ -proxy-id="$(cat /consul/connect-inject/proxyid)" \ -proxy-uid=5995`, "", + nil, }, - "enabled globally, annotation is false": { + "enabled globally, ns not set, annotation is false": { true, - map[string]string{ - annotationTransparentProxy: "false", - }, + map[string]string{keyTransparentProxy: "false"}, "", `/consul/connect-inject/consul connect redirect-traffic \ -proxy-id="$(cat /consul/connect-inject/proxyid)" \ -proxy-uid=5995`, + nil, }, - "enabled globally, annotation is true": { + "enabled globally, ns not set, annotation is true": { true, - map[string]string{ - annotationTransparentProxy: "true", - }, + map[string]string{keyTransparentProxy: "true"}, `/consul/connect-inject/consul connect redirect-traffic \ -proxy-id="$(cat /consul/connect-inject/proxyid)" \ -proxy-uid=5995`, "", + nil, }, - "disabled globally, annotation not provided": { + "disabled globally, ns not set, annotation not provided": { false, nil, "", `/consul/connect-inject/consul connect redirect-traffic \ -proxy-id="$(cat /consul/connect-inject/proxyid)" \ -proxy-uid=5995`, + nil, }, - "disabled globally, annotation is false": { + "disabled globally, ns not set, annotation is false": { false, - map[string]string{ - annotationTransparentProxy: "false", - }, + map[string]string{keyTransparentProxy: "false"}, "", `/consul/connect-inject/consul connect redirect-traffic \ -proxy-id="$(cat /consul/connect-inject/proxyid)" \ -proxy-uid=5995`, + nil, }, - "disabled globally, annotation is true": { + "disabled globally, ns not set, annotation is true": { false, - map[string]string{ - annotationTransparentProxy: "true", - }, + map[string]string{keyTransparentProxy: "true"}, `/consul/connect-inject/consul connect redirect-traffic \ -proxy-id="$(cat /consul/connect-inject/proxyid)" \ -proxy-uid=5995`, "", + nil, }, - "exclude-inbound-ports annotation is provided": { + "exclude-inbound-ports, ns is not set, annotation is provided": { true, map[string]string{ - annotationTransparentProxy: "true", + keyTransparentProxy: "true", annotationTProxyExcludeInboundPorts: "9090,9091", }, `/consul/connect-inject/consul connect redirect-traffic \ @@ -215,11 +214,12 @@ func TestHandlerContainerInit_transparentProxy(t *testing.T) { -proxy-id="$(cat /consul/connect-inject/proxyid)" \ -proxy-uid=5995`, "", + nil, }, - "exclude-outbound-ports annotation is provided": { + "exclude-outbound-ports, ns is not set, annotation is provided": { true, map[string]string{ - annotationTransparentProxy: "true", + keyTransparentProxy: "true", annotationTProxyExcludeOutboundPorts: "9090,9091", }, `/consul/connect-inject/consul connect redirect-traffic \ @@ -228,11 +228,12 @@ func TestHandlerContainerInit_transparentProxy(t *testing.T) { -proxy-id="$(cat /consul/connect-inject/proxyid)" \ -proxy-uid=5995`, "", + nil, }, "exclude-outbound-cidrs annotation is provided": { true, map[string]string{ - annotationTransparentProxy: "true", + keyTransparentProxy: "true", annotationTProxyExcludeOutboundCIDRs: "1.1.1.1,2.2.2.2/24", }, `/consul/connect-inject/consul connect redirect-traffic \ @@ -241,11 +242,12 @@ func TestHandlerContainerInit_transparentProxy(t *testing.T) { -proxy-id="$(cat /consul/connect-inject/proxyid)" \ -proxy-uid=5995`, "", + nil, }, - "exclude-uids annotation is provided": { + "exclude-uids annotation is provided, ns is not set": { true, map[string]string{ - annotationTransparentProxy: "true", + keyTransparentProxy: "true", annotationTProxyExcludeUIDs: "6000,7000", }, `/consul/connect-inject/consul connect redirect-traffic \ @@ -254,6 +256,25 @@ func TestHandlerContainerInit_transparentProxy(t *testing.T) { -proxy-id="$(cat /consul/connect-inject/proxyid)" \ -proxy-uid=5995`, "", + nil, + }, + "disabled globally, ns enabled, annotation not set": { + false, + nil, + `/consul/connect-inject/consul connect redirect-traffic \ + -proxy-id="$(cat /consul/connect-inject/proxyid)" \ + -proxy-uid=5995`, + "", + map[string]string{keyTransparentProxy: "true"}, + }, + "enabled globally, ns disabled, annotation not set": { + true, + nil, + "", + `/consul/connect-inject/consul connect redirect-traffic \ + -proxy-id="$(cat /consul/connect-inject/proxyid)" \ + -proxy-uid=5995`, + map[string]string{keyTransparentProxy: "false"}, }, } for name, c := range cases { @@ -270,7 +291,9 @@ func TestHandlerContainerInit_transparentProxy(t *testing.T) { }, RunAsNonRoot: pointerToBool(false), } - container, err := h.containerInit(*pod, k8sNamespace) + ns := testNS + ns.Labels = c.namespaceLabel + container, err := h.containerInit(ns, *pod) require.NoError(t, err) actualCmd := strings.Join(container.Command, " ") @@ -529,7 +552,7 @@ consul-k8s connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ require := require.New(t) h := tt.Handler - container, err := h.containerInit(*tt.Pod(minimal()), k8sNamespace) + container, err := h.containerInit(testNS, *tt.Pod(minimal())) require.NoError(err) actual := strings.Join(container.Command, " ") require.Equal(tt.Cmd, actual) @@ -565,7 +588,7 @@ func TestHandlerContainerInit_authMethod(t *testing.T) { ServiceAccountName: "foo", }, } - container, err := h.containerInit(*pod, k8sNamespace) + container, err := h.containerInit(testNS, *pod) require.NoError(err) actual := strings.Join(container.Command, " ") require.Contains(actual, ` @@ -602,7 +625,7 @@ func TestHandlerContainerInit_WithTLS(t *testing.T) { }, }, } - container, err := h.containerInit(*pod, k8sNamespace) + container, err := h.containerInit(testNS, *pod) require.NoError(err) actual := strings.Join(container.Command, " ") require.Contains(actual, ` @@ -646,7 +669,7 @@ func TestHandlerContainerInit_Resources(t *testing.T) { }, }, } - container, err := h.containerInit(*pod, k8sNamespace) + container, err := h.containerInit(testNS, *pod) require.NoError(err) require.Equal(corev1.ResourceRequirements{ Limits: corev1.ResourceList{ @@ -675,3 +698,9 @@ func TestHandlerContainerInitCopyContainer(t *testing.T) { actual := strings.Join(container.Command, " ") require.Contains(actual, `cp /bin/consul /consul/connect-inject/consul`) } + +var testNS = corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: k8sNamespace, + }, +} diff --git a/connect-inject/endpoints_controller.go b/connect-inject/endpoints_controller.go index 13c87e825d..33598925b2 100644 --- a/connect-inject/endpoints_controller.go +++ b/connect-inject/endpoints_controller.go @@ -345,7 +345,14 @@ func (r *EndpointsController) createServiceRegistrations(pod corev1.Pod, service proxyService.Tags = tags } - tproxyEnabled, err := transparentProxyEnabled(pod, r.EnableTransparentProxy) + // A user can enable/disable tproxy for an entire namespace. + var ns corev1.Namespace + err = r.Client.Get(r.Context, types.NamespacedName{Name: pod.Namespace, Namespace: ""}, &ns) + if err != nil { + return nil, nil, err + } + + tproxyEnabled, err := transparentProxyEnabled(ns, pod, r.EnableTransparentProxy) if err != nil { return nil, nil, err } diff --git a/connect-inject/endpoints_controller_ent_test.go b/connect-inject/endpoints_controller_ent_test.go index 956cd75a76..ee98c3f1a9 100644 --- a/connect-inject/endpoints_controller_ent_test.go +++ b/connect-inject/endpoints_controller_ent_test.go @@ -203,8 +203,10 @@ func TestReconcileCreateEndpointWithNamespaces(t *testing.T) { fakeClientPod := createPod("fake-consul-client", "127.0.0.1", false) fakeClientPod.Labels = map[string]string{"component": "client", "app": "consul", "release": "consul"} + // Add the pods namespace. + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: test.SourceKubeNS}} // Create fake k8s client. - k8sObjects := append(setup.k8sObjects(), fakeClientPod) + k8sObjects := append(setup.k8sObjects(), fakeClientPod, &ns) fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() // Create test Consul server. @@ -926,8 +928,10 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { fakeClientPod := createPod("fake-consul-client", "127.0.0.1", false) fakeClientPod.Labels = map[string]string{"component": "client", "app": "consul", "release": "consul"} + // Add the pods namespace. + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ts.SourceKubeNS}} // Create fake k8s client. - k8sObjects := append(tt.k8sObjects(), fakeClientPod) + k8sObjects := append(tt.k8sObjects(), fakeClientPod, &ns) fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() masterToken := "b78d37c7-0ca7-5f4d-99ee-6d9975ce4586" diff --git a/connect-inject/endpoints_controller_test.go b/connect-inject/endpoints_controller_test.go index d739701cee..c23bb369b3 100644 --- a/connect-inject/endpoints_controller_test.go +++ b/connect-inject/endpoints_controller_test.go @@ -799,8 +799,11 @@ func TestReconcileCreateEndpoint(t *testing.T) { fakeClientPod := createPod("fake-consul-client", "127.0.0.1", false) fakeClientPod.Labels = map[string]string{"component": "client", "app": "consul", "release": "consul"} + // Add the default namespace. + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "default"}} // Create fake k8s client - k8sObjects := append(tt.k8sObjects(), fakeClientPod) + k8sObjects := append(tt.k8sObjects(), fakeClientPod, &ns) + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() // Create test consul server @@ -1614,8 +1617,10 @@ func TestReconcileUpdateEndpoint(t *testing.T) { fakeClientPod := createPod("fake-consul-client", "127.0.0.1", false) fakeClientPod.Labels = map[string]string{"component": "client", "app": "consul", "release": "consul"} + // Add the default namespace. + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "default"}} // Create fake k8s client - k8sObjects := append(tt.k8sObjects(), fakeClientPod) + k8sObjects := append(tt.k8sObjects(), fakeClientPod, &ns) fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() masterToken := "b78d37c7-0ca7-5f4d-99ee-6d9975ce4586" @@ -1788,8 +1793,10 @@ func TestReconcileDeleteEndpoint(t *testing.T) { fakeClientPod := createPod("fake-consul-client", "127.0.0.1", false) fakeClientPod.Labels = map[string]string{"component": "client", "app": "consul", "release": "consul"} + // Add the default namespace. + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "default"}} // Create fake k8s client - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(fakeClientPod).Build() + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(fakeClientPod, &ns).Build() // Create test consul server consul, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { @@ -2577,6 +2584,7 @@ func TestEndpointsController_createServiceRegistrations_withTransparentProxy(t * service *corev1.Service expTaggedAddresses map[string]api.ServiceAddress proxyMode api.ProxyMode + namespaceLabels map[string]string expErr string }{ "enabled globally, annotation not provided": { @@ -2720,6 +2728,53 @@ func TestEndpointsController_createServiceRegistrations_withTransparentProxy(t * }, expErr: "", }, + "disabled globally, namespace enabled, no annotation": { + globalEnabled: false, + service: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: "default", + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "10.0.0.1", + Ports: []corev1.ServicePort{ + { + Port: 8081, + }, + }, + }, + }, + proxyMode: api.ProxyModeTransparent, + expTaggedAddresses: map[string]api.ServiceAddress{ + "virtual": { + Address: "10.0.0.1", + Port: 8081, + }, + }, + namespaceLabels: map[string]string{keyTransparentProxy: "true"}, + expErr: "", + }, + "enabled globally, namespace disabled, no annotation": { + globalEnabled: true, + service: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: "default", + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "10.0.0.1", + Ports: []corev1.ServicePort{ + { + Port: 80, + }, + }, + }, + }, + proxyMode: api.ProxyModeDefault, + expTaggedAddresses: nil, + namespaceLabels: map[string]string{keyTransparentProxy: "false"}, + expErr: "", + }, // This case is impossible since we're always passing an endpoints object to this function, // and Kubernetes will ensure that there is only an endpoints object if there is a service object. // However, we're testing this case to check that we return an error in case we cannot get the service from k8s. @@ -2959,7 +3014,7 @@ func TestEndpointsController_createServiceRegistrations_withTransparentProxy(t * t.Run(name, func(t *testing.T) { pod := createPod("test-pod-1", "1.2.3.4", true) if c.annotationEnabled != nil { - pod.Annotations[annotationTransparentProxy] = strconv.FormatBool(*c.annotationEnabled) + pod.Annotations[keyTransparentProxy] = strconv.FormatBool(*c.annotationEnabled) } pod.Spec.Containers = []corev1.Container{ { @@ -2997,11 +3052,15 @@ func TestEndpointsController_createServiceRegistrations_withTransparentProxy(t * }, }, } + // Add the pods namespace. + ns := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: pod.Namespace, Labels: c.namespaceLabels}, + } var fakeClient client.Client if c.service != nil { - fakeClient = fake.NewClientBuilder().WithRuntimeObjects(pod, endpoints, c.service).Build() + fakeClient = fake.NewClientBuilder().WithRuntimeObjects(pod, endpoints, c.service, &ns).Build() } else { - fakeClient = fake.NewClientBuilder().WithRuntimeObjects(pod, endpoints).Build() + fakeClient = fake.NewClientBuilder().WithRuntimeObjects(pod, endpoints, &ns).Build() } epCtrl := EndpointsController{ diff --git a/connect-inject/handler.go b/connect-inject/handler.go index b98da8d4b2..7492449a84 100644 --- a/connect-inject/handler.go +++ b/connect-inject/handler.go @@ -18,6 +18,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) @@ -34,6 +35,7 @@ var ( // Handler is the HTTP handler for admission webhooks. type Handler struct { ConsulClient *api.Client + Clientset kubernetes.Interface // ImageConsul is the container image for Consul to use. // ImageEnvoy is the container image for Envoy to use. @@ -136,7 +138,7 @@ type Handler struct { // Handle is the admission.Handler implementation that actually handles the // webhook request for admission control. This should be registered or // served via the controller runtime manager. -func (h *Handler) Handle(_ context.Context, req admission.Request) admission.Response { +func (h *Handler) Handle(ctx context.Context, req admission.Request) admission.Response { var pod corev1.Pod // Decode the pod from the request @@ -195,9 +197,15 @@ func (h *Handler) Handle(_ context.Context, req admission.Request) admission.Res initCopyContainer := h.containerInitCopyContainer() pod.Spec.InitContainers = append(pod.Spec.InitContainers, initCopyContainer) - // Add the init container that registers the service and sets up - // the Envoy configuration. - initContainer, err := h.containerInit(pod, req.Namespace) + // A user can enable/disable tproxy for an entire namespace via a label. + ns, err := h.Clientset.CoreV1().Namespaces().Get(ctx, req.Namespace, metav1.GetOptions{}) + if err != nil { + h.Log.Error(err, "error fetching namespace metadata for container", "request name", req.Name) + return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error getting namespace metadata for container: %s", err)) + } + + // Add the init container that registers the service and sets up the Envoy configuration. + initContainer, err := h.containerInit(*ns, pod) if err != nil { h.Log.Error(err, "error configuring injection init container", "request name", req.Name) return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring injection init container: %s", err)) diff --git a/connect-inject/handler_ent_test.go b/connect-inject/handler_ent_test.go index 7faf9786b8..f99cde6f0e 100644 --- a/connect-inject/handler_ent_test.go +++ b/connect-inject/handler_ent_test.go @@ -55,6 +55,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { EnableNamespaces: true, ConsulDestinationNamespace: "default", decoder: decoder, + Clientset: defaultTestClientWithNamespace(), }, Req: admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ @@ -76,6 +77,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { EnableNamespaces: true, ConsulDestinationNamespace: "default", decoder: decoder, + Clientset: clientWithNamespace("non-default"), }, Req: admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ @@ -97,6 +99,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { EnableNamespaces: true, ConsulDestinationNamespace: "dest", decoder: decoder, + Clientset: defaultTestClientWithNamespace(), }, Req: admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ @@ -118,6 +121,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { EnableNamespaces: true, ConsulDestinationNamespace: "dest", decoder: decoder, + Clientset: clientWithNamespace("non-default"), }, Req: admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ @@ -140,6 +144,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { ConsulDestinationNamespace: "default", // will be overridden EnableK8SNSMirroring: true, decoder: decoder, + Clientset: defaultTestClientWithNamespace(), }, Req: admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ @@ -162,6 +167,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { ConsulDestinationNamespace: "default", // will be overridden EnableK8SNSMirroring: true, decoder: decoder, + Clientset: clientWithNamespace("dest"), }, Req: admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ @@ -185,6 +191,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { EnableK8SNSMirroring: true, K8SNSMirroringPrefix: "k8s-", decoder: decoder, + Clientset: defaultTestClientWithNamespace(), }, Req: admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ @@ -208,6 +215,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { EnableK8SNSMirroring: true, K8SNSMirroringPrefix: "k8s-", decoder: decoder, + Clientset: clientWithNamespace("dest"), }, Req: admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ @@ -304,6 +312,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { ConsulDestinationNamespace: "default", CrossNamespaceACLPolicy: "cross-namespace-policy", decoder: decoder, + Clientset: defaultTestClientWithNamespace(), }, Req: admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ @@ -326,6 +335,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { ConsulDestinationNamespace: "default", CrossNamespaceACLPolicy: "cross-namespace-policy", decoder: decoder, + Clientset: clientWithNamespace("non-default"), }, Req: admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ @@ -348,6 +358,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { ConsulDestinationNamespace: "dest", CrossNamespaceACLPolicy: "cross-namespace-policy", decoder: decoder, + Clientset: defaultTestClientWithNamespace(), }, Req: admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ @@ -370,6 +381,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { ConsulDestinationNamespace: "dest", CrossNamespaceACLPolicy: "cross-namespace-policy", decoder: decoder, + Clientset: clientWithNamespace("non-default"), }, Req: admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ @@ -393,6 +405,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { EnableK8SNSMirroring: true, CrossNamespaceACLPolicy: "cross-namespace-policy", decoder: decoder, + Clientset: defaultTestClientWithNamespace(), }, Req: admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ @@ -416,6 +429,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { EnableK8SNSMirroring: true, CrossNamespaceACLPolicy: "cross-namespace-policy", decoder: decoder, + Clientset: clientWithNamespace("dest"), }, Req: admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ @@ -440,6 +454,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { K8SNSMirroringPrefix: "k8s-", CrossNamespaceACLPolicy: "cross-namespace-policy", decoder: decoder, + Clientset: defaultTestClientWithNamespace(), }, Req: admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ @@ -464,6 +479,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { K8SNSMirroringPrefix: "k8s-", CrossNamespaceACLPolicy: "cross-namespace-policy", decoder: decoder, + Clientset: clientWithNamespace("dest"), }, Req: admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ @@ -634,6 +650,7 @@ func TestHandler_MutateWithNamespaces_Annotation(t *testing.T) { K8SNSMirroringPrefix: c.MirroringPrefix, ConsulClient: client, decoder: decoder, + Clientset: clientWithNamespace(sourceKubeNS), } pod := corev1.Pod{ diff --git a/connect-inject/handler_test.go b/connect-inject/handler_test.go index 1d82b2d876..3aeb80f9bf 100644 --- a/connect-inject/handler_test.go +++ b/connect-inject/handler_test.go @@ -6,8 +6,9 @@ import ( "strings" "testing" - mapset "github.com/deckarep/golang-set" + "github.com/deckarep/golang-set" logrtest "github.com/go-logr/logr/testing" + "github.com/hashicorp/consul-k8s/namespaces" "github.com/stretchr/testify/require" "gomodules.xyz/jsonpatch/v2" admissionv1 "k8s.io/api/admission/v1" @@ -15,6 +16,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) @@ -93,9 +96,11 @@ func TestHandlerHandle(t *testing.T) { AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, + Clientset: defaultTestClientWithNamespace(), }, admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, Object: encodeRaw(t, &corev1.Pod{ Spec: basicSpec, }), @@ -133,9 +138,11 @@ func TestHandlerHandle(t *testing.T) { AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, + Clientset: defaultTestClientWithNamespace(), }, admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, Object: encodeRaw(t, &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ @@ -178,9 +185,11 @@ func TestHandlerHandle(t *testing.T) { AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, + Clientset: defaultTestClientWithNamespace(), }, admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, Object: encodeRaw(t, &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ @@ -202,9 +211,11 @@ func TestHandlerHandle(t *testing.T) { AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, + Clientset: defaultTestClientWithNamespace(), }, admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, Object: encodeRaw(t, &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ @@ -247,9 +258,11 @@ func TestHandlerHandle(t *testing.T) { AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, + Clientset: defaultTestClientWithNamespace(), }, admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, Object: encodeRaw(t, &corev1.Pod{ Spec: basicSpec, ObjectMeta: metav1.ObjectMeta{ @@ -292,9 +305,11 @@ func TestHandlerHandle(t *testing.T) { AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, + Clientset: defaultTestClientWithNamespace(), }, admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, Object: encodeRaw(t, &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{ @@ -340,10 +355,12 @@ func TestHandlerHandle(t *testing.T) { DefaultEnableMetrics: true, DefaultEnableMetricsMerging: true, }, - decoder: decoder, + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), }, admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, Object: encodeRaw(t, &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{ @@ -1177,3 +1194,16 @@ func escapeJSONPointer(s string) string { s = strings.Replace(s, "/", "~1", -1) return s } + +func defaultTestClientWithNamespace() kubernetes.Interface { + return clientWithNamespace("default") +} + +func clientWithNamespace(name string) kubernetes.Interface { + ns := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + return fake.NewSimpleClientset(&ns) +} diff --git a/connect-inject/metrics_configuration_test.go b/connect-inject/metrics_configuration_test.go index c07eaaf1b1..a117456760 100644 --- a/connect-inject/metrics_configuration_test.go +++ b/connect-inject/metrics_configuration_test.go @@ -3,6 +3,7 @@ package connectinject import ( "testing" + "github.com/hashicorp/consul-k8s/namespaces" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -507,7 +508,8 @@ func TestMetricsConfigMergedMetricsServerConfiguration(t *testing.T) { func minimal() *corev1.Pod { return &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ - Name: "minimal", + Namespace: namespaces.DefaultNamespace, + Name: "minimal", Annotations: map[string]string{ annotationService: "foo", }, diff --git a/subcommand/inject-connect/command.go b/subcommand/inject-connect/command.go index 0ae2cb9625..2e4c6f39b7 100644 --- a/subcommand/inject-connect/command.go +++ b/subcommand/inject-connect/command.go @@ -418,6 +418,7 @@ func (c *Command) Run(args []string) int { mgr.GetWebhookServer().Register("/mutate", &webhook.Admission{Handler: &connectinject.Handler{ + Clientset: c.clientset, ConsulClient: c.consulClient, ImageConsul: c.flagConsulImage, ImageEnvoy: c.flagEnvoyImage,