diff --git a/connectivity/check/policy.go b/connectivity/check/policy.go index a657abd35c..07df6f7c0f 100644 --- a/connectivity/check/policy.go +++ b/connectivity/check/policy.go @@ -182,6 +182,15 @@ var ( ExitCode: ExitCurlHTTPError, } + // ResultDNSOKCurlHTTPError expects a failed command, generating DNS traffic and without a dropped flow. + ResultDNSOKCurlHTTPError = Result{ + DNSProxy: true, + L7Proxy: true, + Drop: false, + DropReasonFunc: defaultDropReason, + ExitCode: ExitCurlHTTPError, + } + // ResultDrop expects a dropped flow and a failed command. ResultDrop = Result{ Drop: true, diff --git a/connectivity/manifests/client-egress-l7-http-matchheader-secret.yaml b/connectivity/manifests/client-egress-l7-http-matchheader-secret.yaml new file mode 100644 index 0000000000..c63730b170 --- /dev/null +++ b/connectivity/manifests/client-egress-l7-http-matchheader-secret.yaml @@ -0,0 +1,32 @@ +--- +# client2 is allowed to contact the echo Pod +# on port 8080 via POST method. HTTP introspection is enabled for client2. +# The request to /auth-header-required will be injected with an auth header to work +apiVersion: "cilium.io/v2" +kind: CiliumNetworkPolicy +metadata: + name: client-egress-l7-http-matchheader-secret +spec: + description: "Allow POST :8080/auth-header-required and set the header from client2" + endpointSelector: + matchLabels: + other: client + egress: + # Allow POST /auth-header-required requests towards echo pods with added header. + - toEndpoints: + - matchLabels: + kind: echo + toPorts: + - ports: + - port: "8080" + protocol: TCP + rules: + http: + - method: "POST" + path: "/auth-header-required$" + headerMatches: + - name: Authorization + mismatch: REPLACE + secret: + namespace: "{{.TestNamespace}}" + name: header-match diff --git a/connectivity/suite.go b/connectivity/suite.go index 76ed9ebc62..5592bd8029 100644 --- a/connectivity/suite.go +++ b/connectivity/suite.go @@ -9,6 +9,8 @@ import ( "fmt" "github.com/blang/semver/v4" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/cilium/cilium-cli/connectivity/check" "github.com/cilium/cilium-cli/connectivity/tests" @@ -98,6 +100,9 @@ var ( //go:embed manifests/client-egress-l7-tls.yaml clientEgressL7TLSPolicyYAML string + //go:embed manifests/client-egress-l7-http-matchheader-secret.yaml + clientEgressL7HTTPMatchheaderSecretYAML string + //go:embed manifests/echo-ingress-l7-http.yaml echoIngressL7HTTPPolicyYAML string @@ -125,12 +130,13 @@ func Run(ctx context.Context, ct *check.ConnectivityTest) error { // render templates, if any problems fail early for key, temp := range map[string]string{ - "clientEgressToCIDR1111PolicyYAML": clientEgressToCIDR1111PolicyYAML, - "clientEgressToCIDR1111DenyPolicyYAML": clientEgressToCIDR1111DenyPolicyYAML, - "clientEgressL7HTTPPolicyYAML": clientEgressL7HTTPPolicyYAML, - "clientEgressL7HTTPNamedPortPolicyYAML": clientEgressL7HTTPNamedPortPolicyYAML, - "clientEgressToFQDNsCiliumIOPolicyYAML": clientEgressToFQDNsCiliumIOPolicyYAML, - "clientEgressL7TLSPolicyYAML": clientEgressL7TLSPolicyYAML, + "clientEgressToCIDR1111PolicyYAML": clientEgressToCIDR1111PolicyYAML, + "clientEgressToCIDR1111DenyPolicyYAML": clientEgressToCIDR1111DenyPolicyYAML, + "clientEgressL7HTTPPolicyYAML": clientEgressL7HTTPPolicyYAML, + "clientEgressL7HTTPNamedPortPolicyYAML": clientEgressL7HTTPNamedPortPolicyYAML, + "clientEgressToFQDNsCiliumIOPolicyYAML": clientEgressToFQDNsCiliumIOPolicyYAML, + "clientEgressL7TLSPolicyYAML": clientEgressL7TLSPolicyYAML, + "clientEgressL7HTTPMatchheaderSecretYAML": clientEgressL7HTTPMatchheaderSecretYAML, } { val, err := utils.RenderTemplate(temp, ct.Params()) if err != nil { @@ -697,6 +703,31 @@ func Run(ctx context.Context, ct *check.ConnectivityTest) error { return check.ResultOK, check.ResultNone }) + // Test L7 HTTP with a header replace set in the policy + ct.NewTest("client-egress-l7-set-header"). + WithFeatureRequirements(check.RequireFeatureEnabled(check.FeatureL7Proxy)). + WithFeatureRequirements(check.RequireFeatureEnabled(check.FeatureSecretBackendK8s)). + WithSecret(&corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "header-match", + }, + Data: map[string][]byte{ + "value": []byte("Bearer 123456"), + }, + }). + WithPolicy(renderedTemplates["clientEgressL7HTTPMatchheaderSecretYAML"]). // L7 allow policy with HTTP introspection (POST only) + WithScenarios( + tests.PodToPodWithEndpoints(tests.WithMethod("POST"), tests.WithPath("auth-header-required"), tests.WithDestinationLabelsOption(map[string]string{"other": "echo"})), + tests.PodToPodWithEndpoints(tests.WithMethod("POST"), tests.WithPath("auth-header-required"), tests.WithDestinationLabelsOption(map[string]string{"first": "echo"})), + ). + WithExpectations(func(a *check.Action) (egress, ingress check.Result) { + if a.Source().HasLabel("other", "client") && // Only client2 has the header policy. + (a.Destination().Port() == 8080) { // port 8080 is traffic to echo Pod. + return check.ResultOK, check.ResultNone + } + return check.ResultDNSOKCurlHTTPError, check.ResultNone // if the header is not set the request will get a 403 + }) + // Only allow UDP:53 to kube-dns, no DNS proxy enabled. ct.NewTest("dns-only").WithPolicy(clientEgressOnlyDNSPolicyYAML). WithFeatureRequirements(check.RequireFeatureEnabled(check.FeatureL7Proxy)). diff --git a/connectivity/tests/common.go b/connectivity/tests/common.go index 4e4854b46d..b3450fc5a8 100644 --- a/connectivity/tests/common.go +++ b/connectivity/tests/common.go @@ -13,6 +13,7 @@ type labelsOption struct { sourceLabels map[string]string destinationLabels map[string]string method string + path string } func WithMethod(method string) Option { @@ -33,6 +34,12 @@ func WithDestinationLabelsOption(destinationLabels map[string]string) Option { } } +func WithPath(path string) Option { + return func(option *labelsOption) { + option.path = path + } +} + func hasAllLabels(labelsContainer labelsContainer, filters map[string]string) bool { for k, v := range filters { if !labelsContainer.HasLabel(k, v) { diff --git a/connectivity/tests/pod.go b/connectivity/tests/pod.go index 17b39c29df..6ea6b07299 100644 --- a/connectivity/tests/pod.go +++ b/connectivity/tests/pod.go @@ -76,6 +76,7 @@ func PodToPodWithEndpoints(opts ...Option) check.Scenario { sourceLabels: options.sourceLabels, destinationLabels: options.destinationLabels, method: options.method, + path: options.path, } } @@ -84,6 +85,7 @@ type podToPodWithEndpoints struct { sourceLabels map[string]string destinationLabels map[string]string method string + path string } func (s *podToPodWithEndpoints) Name() string { @@ -123,7 +125,12 @@ func (s *podToPodWithEndpoints) curlEndpoints(ctx context.Context, t *check.Test } // Manually construct an HTTP endpoint for each API endpoint. - for _, path := range []string{"public", "private"} { + paths := []string{"public", "private"} + if s.path != "" { // Override default paths if one is set + paths = []string{s.path} + } + + for _, path := range paths { epName := fmt.Sprintf("%s-%s", name, path) url := fmt.Sprintf("%s/%s", baseURL, path) ep := check.HTTPEndpointWithLabels(epName, url, echo.Labels()) diff --git a/defaults/defaults.go b/defaults/defaults.go index f92231b25d..a06a664d8d 100644 --- a/defaults/defaults.go +++ b/defaults/defaults.go @@ -69,7 +69,7 @@ const ( ConnectivityCheckAlpineCurlImage = "quay.io/cilium/alpine-curl:v1.6.0@sha256:408430f548a8390089b9b83020148b0ef80b0be1beb41a98a8bfe036709c196e" ConnectivityPerformanceImage = "quay.io/cilium/network-perf:a816f935930cb2b40ba43230643da4d5751a5711@sha256:679d3a370c696f63884da4557a4466f3b5569b4719bb4f86e8aac02fbe390eea" - ConnectivityCheckJSONMockImage = "quay.io/cilium/json-mock:v1.3.3@sha256:f26044a2b8085fcaa8146b6b8bb73556134d7ec3d5782c6a04a058c945924ca0" + ConnectivityCheckJSONMockImage = "quay.io/cilium/json-mock-ci:latest@sha256:5be90d8c2adc99f13bc42d7198d81fe179a2925b8091fc73437b9ba3b290afec" // MAARTJE FIX THIS ConnectivityDNSTestServerImage = "docker.io/coredns/coredns:1.10.0@sha256:017727efcfeb7d053af68e51436ce8e65edbc6ca573720afb4f79c8594036955" ConfigMapName = "cilium-config"