From 60bf6ba6429feb4a5d8b485d1bcee87187fbc368 Mon Sep 17 00:00:00 2001 From: Brendan Kamp Date: Mon, 12 Jun 2023 12:25:49 +0200 Subject: [PATCH 1/8] chore: move httpbun to be part of framework (#9955) Signed-off-by: Spazzy --- test/e2e/HTTPBUN_IMAGE | 1 + test/e2e/annotations/auth.go | 53 +----- test/e2e/annotations/grpc.go | 17 +- test/e2e/annotations/satisfy.go | 17 +- test/e2e/framework/deployment.go | 111 +++++++++---- test/e2e/framework/framework.go | 29 +++- test/e2e/run-e2e-suite.sh | 2 + .../servicebackend/service_externalname.go | 153 +++++++++++------- test/e2e/settings/brotli.go | 9 +- .../settings/disable_service_external_name.go | 16 +- test/e2e/settings/global_external_auth.go | 10 +- test/e2e/settings/listen_nondefault_ports.go | 19 +-- test/e2e/settings/ssl_passthrough.go | 32 +++- 13 files changed, 265 insertions(+), 204 deletions(-) create mode 100644 test/e2e/HTTPBUN_IMAGE diff --git a/test/e2e/HTTPBUN_IMAGE b/test/e2e/HTTPBUN_IMAGE new file mode 100644 index 000000000..2d95865c7 --- /dev/null +++ b/test/e2e/HTTPBUN_IMAGE @@ -0,0 +1 @@ +registry.k8s.io/ingress-nginx/e2e-test-httpbun:v20230505-v0.0.1 diff --git a/test/e2e/annotations/auth.go b/test/e2e/annotations/auth.go index 4ca034825..8011186a1 100644 --- a/test/e2e/annotations/auth.go +++ b/test/e2e/annotations/auth.go @@ -23,7 +23,6 @@ import ( "net/url" "regexp" "strings" - "time" "golang.org/x/crypto/bcrypt" @@ -38,7 +37,7 @@ import ( ) var _ = framework.DescribeAnnotation("auth-*", func() { - f := framework.NewDefaultFramework("auth") + f := framework.NewDefaultFramework("auth", framework.WithHTTPBunEnabled()) ginkgo.BeforeEach(func() { f.NewEchoDeployment() @@ -390,10 +389,10 @@ http { assert.GreaterOrEqual(ginkgo.GinkgoT(), len(e.Subsets), 1, "expected at least one endpoint") assert.GreaterOrEqual(ginkgo.GinkgoT(), len(e.Subsets[0].Addresses), 1, "expected at least one address ready in the endpoint") - httpbunIP := e.Subsets[0].Addresses[0].IP + nginxIP := e.Subsets[0].Addresses[0].IP annotations = map[string]string{ - "nginx.ingress.kubernetes.io/auth-url": fmt.Sprintf("http://%s/cookies/set/alma/armud", httpbunIP), + "nginx.ingress.kubernetes.io/auth-url": fmt.Sprintf("http://%s/cookies/set/alma/armud", nginxIP), "nginx.ingress.kubernetes.io/auth-signin": "http://$host/auth/start", } @@ -457,21 +456,8 @@ http { var ing *networking.Ingress ginkgo.BeforeEach(func() { - f.NewHttpbunDeployment() - - err := framework.WaitForEndpoints(f.KubeClientSet, framework.DefaultTimeout, framework.HTTPBunService, f.Namespace, 1) - assert.Nil(ginkgo.GinkgoT(), err) - - e, err := f.KubeClientSet.CoreV1().Endpoints(f.Namespace).Get(context.TODO(), framework.HTTPBunService, metav1.GetOptions{}) - assert.Nil(ginkgo.GinkgoT(), err) - - assert.GreaterOrEqual(ginkgo.GinkgoT(), len(e.Subsets), 1, "expected at least one endpoint") - assert.GreaterOrEqual(ginkgo.GinkgoT(), len(e.Subsets[0].Addresses), 1, "expected at least one address ready in the endpoint") - - httpbunIP := e.Subsets[0].Addresses[0].IP - annotations = map[string]string{ - "nginx.ingress.kubernetes.io/auth-url": fmt.Sprintf("http://%s/basic-auth/user/password", httpbunIP), + "nginx.ingress.kubernetes.io/auth-url": fmt.Sprintf("http://%s/basic-auth/user/password", f.HTTPBunIP), "nginx.ingress.kubernetes.io/auth-signin": "http://$host/auth/start", } @@ -650,20 +636,8 @@ http { var ing *networking.Ingress ginkgo.BeforeEach(func() { - f.NewHttpbunDeployment() - - var httpbunIP string - - err := framework.WaitForEndpoints(f.KubeClientSet, framework.DefaultTimeout, framework.HTTPBunService, f.Namespace, 1) - assert.Nil(ginkgo.GinkgoT(), err) - - e, err := f.KubeClientSet.CoreV1().Endpoints(f.Namespace).Get(context.TODO(), framework.HTTPBunService, metav1.GetOptions{}) - assert.Nil(ginkgo.GinkgoT(), err) - - httpbunIP = e.Subsets[0].Addresses[0].IP - annotations = map[string]string{ - "nginx.ingress.kubernetes.io/auth-url": fmt.Sprintf("http://%s/basic-auth/user/password", httpbunIP), + "nginx.ingress.kubernetes.io/auth-url": fmt.Sprintf("http://%s/basic-auth/user/password", f.HTTPBunIP), "nginx.ingress.kubernetes.io/auth-signin": "http://$host/auth/start", "nginx.ingress.kubernetes.io/auth-signin-redirect-param": "orig", } @@ -729,23 +703,8 @@ http { barPath := "/bar" ginkgo.BeforeEach(func() { - f.NewHttpbunDeployment() - - err := framework.WaitForEndpoints(f.KubeClientSet, framework.DefaultTimeout, framework.HTTPBunService, f.Namespace, 1) - assert.Nil(ginkgo.GinkgoT(), err) - - framework.Sleep(1 * time.Second) - - e, err := f.KubeClientSet.CoreV1().Endpoints(f.Namespace).Get(context.TODO(), framework.HTTPBunService, metav1.GetOptions{}) - assert.Nil(ginkgo.GinkgoT(), err) - - assert.GreaterOrEqual(ginkgo.GinkgoT(), len(e.Subsets), 1, "expected at least one endpoint") - assert.GreaterOrEqual(ginkgo.GinkgoT(), len(e.Subsets[0].Addresses), 1, "expected at least one address ready in the endpoint") - - httpbunIP := e.Subsets[0].Addresses[0].IP - annotations := map[string]string{ - "nginx.ingress.kubernetes.io/auth-url": fmt.Sprintf("http://%s/basic-auth/user/password", httpbunIP), + "nginx.ingress.kubernetes.io/auth-url": fmt.Sprintf("http://%s/basic-auth/user/password", f.HTTPBunIP), "nginx.ingress.kubernetes.io/auth-signin": "http://$host/auth/start", "nginx.ingress.kubernetes.io/auth-cache-key": "fixed", "nginx.ingress.kubernetes.io/auth-cache-duration": "200 201 401 30m", diff --git a/test/e2e/annotations/grpc.go b/test/e2e/annotations/grpc.go index 2bdac553b..c8e530ead 100644 --- a/test/e2e/annotations/grpc.go +++ b/test/e2e/annotations/grpc.go @@ -37,7 +37,7 @@ import ( ) var _ = framework.DescribeAnnotation("backend-protocol - GRPC", func() { - f := framework.NewDefaultFramework("grpc") + f := framework.NewDefaultFramework("grpc", framework.WithHTTPBunEnabled()) ginkgo.It("should use grpc_pass in the configuration file", func() { f.NewGRPCFortuneTellerDeployment() @@ -124,8 +124,6 @@ var _ = framework.DescribeAnnotation("backend-protocol - GRPC", func() { ginkgo.It("authorization metadata should be overwritten by external auth response headers", func() { f.NewGRPCBinDeployment() - f.NewHttpbunDeployment() - host := "echo" svc := &corev1.Service{ @@ -148,19 +146,8 @@ var _ = framework.DescribeAnnotation("backend-protocol - GRPC", func() { } f.EnsureService(svc) - err := framework.WaitForEndpoints(f.KubeClientSet, framework.DefaultTimeout, framework.HTTPBunService, f.Namespace, 1) - assert.Nil(ginkgo.GinkgoT(), err) - - e, err := f.KubeClientSet.CoreV1().Endpoints(f.Namespace).Get(context.TODO(), framework.HTTPBunService, metav1.GetOptions{}) - assert.Nil(ginkgo.GinkgoT(), err) - - assert.GreaterOrEqual(ginkgo.GinkgoT(), len(e.Subsets), 1, "expected at least one endpoint") - assert.GreaterOrEqual(ginkgo.GinkgoT(), len(e.Subsets[0].Addresses), 1, "expected at least one address ready in the endpoint") - - httpbunIP := e.Subsets[0].Addresses[0].IP - annotations := map[string]string{ - "nginx.ingress.kubernetes.io/auth-url": fmt.Sprintf("http://%s/response-headers?authorization=foo", httpbunIP), + "nginx.ingress.kubernetes.io/auth-url": fmt.Sprintf("http://%s/response-headers?authorization=foo", f.HTTPBunIP), "nginx.ingress.kubernetes.io/auth-response-headers": "Authorization", "nginx.ingress.kubernetes.io/backend-protocol": "GRPC", } diff --git a/test/e2e/annotations/satisfy.go b/test/e2e/annotations/satisfy.go index 758ad21a4..6ba6db33e 100644 --- a/test/e2e/annotations/satisfy.go +++ b/test/e2e/annotations/satisfy.go @@ -17,7 +17,6 @@ limitations under the License. package annotations import ( - "context" "fmt" "net/http" "net/url" @@ -27,13 +26,12 @@ import ( "github.com/stretchr/testify/assert" networking "k8s.io/api/networking/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/ingress-nginx/test/e2e/framework" ) var _ = framework.DescribeAnnotation("satisfy", func() { - f := framework.NewDefaultFramework("satisfy") + f := framework.NewDefaultFramework("satisfy", framework.WithHTTPBunEnabled()) ginkgo.BeforeEach(func() { f.NewEchoDeployment() @@ -84,17 +82,6 @@ var _ = framework.DescribeAnnotation("satisfy", func() { ginkgo.It("should allow multiple auth with satisfy any", func() { host := "auth" - // setup external auth - f.NewHttpbunDeployment() - - err := framework.WaitForEndpoints(f.KubeClientSet, framework.DefaultTimeout, framework.HTTPBunService, f.Namespace, 1) - assert.Nil(ginkgo.GinkgoT(), err) - - e, err := f.KubeClientSet.CoreV1().Endpoints(f.Namespace).Get(context.TODO(), framework.HTTPBunService, metav1.GetOptions{}) - assert.Nil(ginkgo.GinkgoT(), err) - - httpbunIP := e.Subsets[0].Addresses[0].IP - // create basic auth secret at ingress s := f.EnsureSecret(buildSecret("uname", "pwd", "basic-secret", f.Namespace)) @@ -105,7 +92,7 @@ var _ = framework.DescribeAnnotation("satisfy", func() { "nginx.ingress.kubernetes.io/auth-realm": "test basic auth", // annotations for external auth - "nginx.ingress.kubernetes.io/auth-url": fmt.Sprintf("http://%s/basic-auth/user/password", httpbunIP), + "nginx.ingress.kubernetes.io/auth-url": fmt.Sprintf("http://%s/basic-auth/user/password", f.HTTPBunIP), "nginx.ingress.kubernetes.io/auth-signin": "http://$host/auth/start", // set satisfy any diff --git a/test/e2e/framework/deployment.go b/test/e2e/framework/deployment.go index 565b8f4ac..d4e20fbf5 100644 --- a/test/e2e/framework/deployment.go +++ b/test/e2e/framework/deployment.go @@ -43,12 +43,28 @@ const HTTPBunService = "httpbun" // NipService name of external service using nip.io const NIPService = "external-nip" +// HTTPBunImage is the default image that is used to deploy HTTPBun with the framwork +var HTTPBunImage = os.Getenv("HTTPBUN_IMAGE") + +// EchoImage is the default image to be used by the echo service +const EchoImage = "registry.k8s.io/ingress-nginx/e2e-test-echo@sha256:4938d1d91a2b7d19454460a8c1b010b89f6ff92d2987fd889ac3e8fc3b70d91a" + +// TODO: change all Deployment functions to use these options +// in order to reduce complexity and have a unified API accross the +// framework type deploymentOptions struct { - namespace string name string - replicas int - svcAnnotations map[string]string + namespace string image string + port int32 + replicas int + command []string + args []string + env []corev1.EnvVar + volumeMounts []corev1.VolumeMount + volumes []corev1.Volume + svcAnnotations map[string]string + setProbe bool } // WithDeploymentNamespace allows configuring the deployment's namespace @@ -100,22 +116,25 @@ func (f *Framework) NewEchoDeployment(opts ...func(*deploymentOptions)) { namespace: f.Namespace, name: EchoService, replicas: 1, - image: "registry.k8s.io/ingress-nginx/e2e-test-echo@sha256:6fc5aa2994c86575975bb20a5203651207029a0d28e3f491d8a127d08baadab4", + image: EchoImage, } for _, o := range opts { o(options) } - deployment := newDeployment(options.name, options.namespace, options.image, 80, int32(options.replicas), + f.EnsureDeployment(newDeployment( + options.name, + options.namespace, + options.image, + 80, + int32(options.replicas), nil, nil, nil, []corev1.VolumeMount{}, []corev1.Volume{}, true, - ) + )) - f.EnsureDeployment(deployment) - - service := &corev1.Service{ + f.EnsureService(&corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: options.name, Namespace: options.namespace, @@ -134,11 +153,15 @@ func (f *Framework) NewEchoDeployment(opts ...func(*deploymentOptions)) { "app": options.name, }, }, - } + }) - f.EnsureService(service) - - err := WaitForEndpoints(f.KubeClientSet, DefaultTimeout, options.name, options.namespace, options.replicas) + err := WaitForEndpoints( + f.KubeClientSet, + DefaultTimeout, + options.name, + options.namespace, + options.replicas, + ) assert.Nil(ginkgo.GinkgoT(), err, "waiting for endpoints to become ready") } @@ -147,6 +170,12 @@ func BuildNIPHost(ip string) string { return fmt.Sprintf("%s.nip.io", ip) } +// GetNipHost used to generate a nip host for external DNS resolving +// for the instance deployed by the framework +func (f *Framework) GetNIPHost() string { + return BuildNIPHost(f.HTTPBunIP) +} + // BuildNIPExternalNameService used to generate a service pointing to nip.io to // help resolve to an IP address func BuildNIPExternalNameService(f *Framework, ip, portName string) *corev1.Service { @@ -177,22 +206,27 @@ func (f *Framework) NewHttpbunDeployment(opts ...func(*deploymentOptions)) strin namespace: f.Namespace, name: HTTPBunService, replicas: 1, - image: "registry.k8s.io/ingress-nginx/e2e-test-httpbun:v20230505-v0.0.1", + image: HTTPBunImage, } for _, o := range opts { o(options) } - deployment := newDeployment(options.name, options.namespace, options.image, 80, int32(options.replicas), + // Create the HTTPBun Deployment + f.EnsureDeployment(newDeployment( + options.name, + options.namespace, + options.image, + 80, + int32(options.replicas), nil, nil, nil, []corev1.VolumeMount{}, []corev1.Volume{}, true, - ) + )) - f.EnsureDeployment(deployment) - - service := &corev1.Service{ + // Create a service pointing to deployment + f.EnsureService(&corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: options.name, Namespace: options.namespace, @@ -211,14 +245,26 @@ func (f *Framework) NewHttpbunDeployment(opts ...func(*deploymentOptions)) strin "app": options.name, }, }, - } + }) - s := f.EnsureService(service) - - err := WaitForEndpoints(f.KubeClientSet, DefaultTimeout, options.name, options.namespace, options.replicas) + // Wait for deployment to become available + err := WaitForEndpoints( + f.KubeClientSet, + DefaultTimeout, + options.name, + options.namespace, + options.replicas, + ) assert.Nil(ginkgo.GinkgoT(), err, "waiting for endpoints to become ready") - return s.Spec.ClusterIPs[0] + // Get cluster ip for HTTPBun to be used in tests + e, err := f.KubeClientSet. + CoreV1(). + Endpoints(f.Namespace). + Get(context.TODO(), HTTPBunService, metav1.GetOptions{}) + assert.Nil(ginkgo.GinkgoT(), err, "failed to get httpbun endpoint") + + return e.Subsets[0].Addresses[0].IP } // NewSlowEchoDeployment creates a new deployment of the slow echo server image in a particular namespace. @@ -276,13 +322,16 @@ func (f *Framework) NGINXDeployment(name string, cfg string, waitendpoint bool) "nginx.conf": cfg, } - _, err := f.KubeClientSet.CoreV1().ConfigMaps(f.Namespace).Create(context.TODO(), &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: f.Namespace, - }, - Data: cfgMap, - }, metav1.CreateOptions{}) + _, err := f.KubeClientSet. + CoreV1(). + ConfigMaps(f.Namespace). + Create(context.TODO(), &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: f.Namespace, + }, + Data: cfgMap, + }, metav1.CreateOptions{}) assert.Nil(ginkgo.GinkgoT(), err, "creating configmap") deployment := newDeployment(name, f.Namespace, f.GetNginxBaseImage(), 80, 1, diff --git a/test/e2e/framework/framework.go b/test/e2e/framework/framework.go index a3d5ea760..69f6dae78 100644 --- a/test/e2e/framework/framework.go +++ b/test/e2e/framework/framework.go @@ -67,16 +67,32 @@ type Framework struct { IngressClass string pod *v1.Pod + // We use httpbun as a service that we route to in our tests through + // the ingress controller. We add it as part of the framework as it + // is used extensively + HTTPBunIP string + HTTPBunEnabled bool +} + +// WithHTTPBunEnabled deploys an instance of HTTPBun for the specific test +func WithHTTPBunEnabled() func(*Framework) { + return func(f *Framework) { + f.HTTPBunEnabled = true + } } // NewDefaultFramework makes a new framework and sets up a BeforeEach/AfterEach for // you (you can write additional before/after each functions). -func NewDefaultFramework(baseName string) *Framework { +func NewDefaultFramework(baseName string, opts ...func(*Framework)) *Framework { defer ginkgo.GinkgoRecover() f := &Framework{ BaseName: baseName, } + // set framework options + for _, o := range opts { + o(f) + } ginkgo.BeforeEach(f.BeforeEach) ginkgo.AfterEach(f.AfterEach) @@ -86,12 +102,16 @@ func NewDefaultFramework(baseName string) *Framework { // NewSimpleFramework makes a new framework that allows the usage of a namespace // for arbitraty tests. -func NewSimpleFramework(baseName string) *Framework { +func NewSimpleFramework(baseName string, opts ...func(*Framework)) *Framework { defer ginkgo.GinkgoRecover() f := &Framework{ BaseName: baseName, } + // set framework options + for _, o := range opts { + o(f) + } ginkgo.BeforeEach(f.CreateEnvironment) ginkgo.AfterEach(f.DestroyEnvironment) @@ -140,6 +160,11 @@ func (f *Framework) BeforeEach() { assert.Nil(ginkgo.GinkgoT(), err, "updating ingress controller pod information") f.WaitForNginxListening(80) + + // If HTTPBun is enabled deploy an instance to the namespace + if f.HTTPBunEnabled { + f.HTTPBunIP = f.NewHttpbunDeployment() + } } // AfterEach deletes the namespace, after reading its events. diff --git a/test/e2e/run-e2e-suite.sh b/test/e2e/run-e2e-suite.sh index a3bf589cd..b56312afd 100755 --- a/test/e2e/run-e2e-suite.sh +++ b/test/e2e/run-e2e-suite.sh @@ -51,6 +51,7 @@ fi BASEDIR=$(dirname "$0") NGINX_BASE_IMAGE=$(cat $BASEDIR/../../NGINX_BASE) +HTTPBUN_IMAGE=$(cat $BASEDIR/HTTPBUN_IMAGE) echo -e "${BGREEN}Granting permissions to ingress-nginx e2e service account...${NC}" kubectl create serviceaccount ingress-nginx-e2e || true @@ -79,6 +80,7 @@ kubectl run --rm \ --env="IS_CHROOT=${IS_CHROOT:-false}"\ --env="E2E_CHECK_LEAKS=${E2E_CHECK_LEAKS}" \ --env="NGINX_BASE_IMAGE=${NGINX_BASE_IMAGE}" \ + --env="HTTPBUN_IMAGE=${HTTPBUN_IMAGE}" \ --overrides='{ "apiVersion": "v1", "spec":{"serviceAccountName": "ingress-nginx-e2e"}}' \ e2e --image=nginx-ingress-controller:e2e diff --git a/test/e2e/servicebackend/service_externalname.go b/test/e2e/servicebackend/service_externalname.go index 2fd6cd080..89ae77b10 100644 --- a/test/e2e/servicebackend/service_externalname.go +++ b/test/e2e/servicebackend/service_externalname.go @@ -35,7 +35,7 @@ import ( ) var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() { - f := framework.NewDefaultFramework("type-externalname") + f := framework.NewDefaultFramework("type-externalname", framework.WithHTTPBunEnabled()) ginkgo.It("works with external name set to incomplete fqdn", func() { f.NewEchoDeployment() @@ -43,7 +43,7 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() { svc := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ - Name: framework.HTTPBunService, + Name: framework.NIPService, Namespace: f.Namespace, }, Spec: corev1.ServiceSpec{ @@ -51,10 +51,15 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() { Type: corev1.ServiceTypeExternalName, }, } - f.EnsureService(svc) - ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.HTTPBunService, 80, nil) + ing := framework.NewSingleIngress(host, + "/", + host, + f.Namespace, + framework.NIPService, + 80, + nil) f.EnsureIngress(ing) f.WaitForNginxServer(host, @@ -70,10 +75,6 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() { }) ginkgo.It("should return 200 for service type=ExternalName without a port defined", func() { - // This is a workaround so we only depend on a self hosted instance of - // httpbun - ip := f.NewHttpbunDeployment() - host := "echo" svc := &corev1.Service{ @@ -82,17 +83,23 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() { Namespace: f.Namespace, }, Spec: corev1.ServiceSpec{ - ExternalName: framework.BuildNIPHost(ip), + ExternalName: f.GetNIPHost(), Type: corev1.ServiceTypeExternalName, }, } - f.EnsureService(svc) annotations := map[string]string{ - "nginx.ingress.kubernetes.io/upstream-vhost": framework.BuildNIPHost(ip), + "nginx.ingress.kubernetes.io/upstream-vhost": f.GetNIPHost(), } - ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.HTTPBunService, 80, annotations) + + ing := framework.NewSingleIngress(host, + "/", + host, + f.Namespace, + framework.HTTPBunService, + 80, + annotations) f.EnsureIngress(ing) f.WaitForNginxServer(host, @@ -108,19 +115,21 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() { }) ginkgo.It("should return 200 for service type=ExternalName with a port defined", func() { - // This is a workaround so we only depend on a self hosted instance of - // httpbun - ip := f.NewHttpbunDeployment() - host := "echo" - svc := framework.BuildNIPExternalNameService(f, ip, host) + svc := framework.BuildNIPExternalNameService(f, f.HTTPBunIP, host) f.EnsureService(svc) annotations := map[string]string{ - "nginx.ingress.kubernetes.io/upstream-vhost": framework.BuildNIPHost(ip), + "nginx.ingress.kubernetes.io/upstream-vhost": f.GetNIPHost(), } - ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.HTTPBunService, 80, annotations) + ing := framework.NewSingleIngress(host, + "/", + host, + f.Namespace, + framework.HTTPBunService, + 80, + annotations) f.EnsureIngress(ing) f.WaitForNginxServer(host, @@ -140,7 +149,7 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() { svc := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ - Name: framework.HTTPBunService, + Name: framework.NIPService, Namespace: f.Namespace, }, Spec: corev1.ServiceSpec{ @@ -148,10 +157,15 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() { Type: corev1.ServiceTypeExternalName, }, } - f.EnsureService(svc) - ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.HTTPBunService, 80, nil) + ing := framework.NewSingleIngress(host, + "/", + host, + f.Namespace, + framework.NIPService, + 80, + nil) f.EnsureIngress(ing) f.WaitForNginxServer(host, @@ -167,19 +181,22 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() { }) ginkgo.It("should return 200 for service type=ExternalName using a port name", func() { - // This is a workaround so we only depend on a self hosted instance of - // httpbun - ip := f.NewHttpbunDeployment() - host := "echo" - svc := framework.BuildNIPExternalNameService(f, ip, host) + svc := framework.BuildNIPExternalNameService(f, f.HTTPBunIP, host) f.EnsureService(svc) annotations := map[string]string{ - "nginx.ingress.kubernetes.io/upstream-vhost": framework.BuildNIPHost(ip), + "nginx.ingress.kubernetes.io/upstream-vhost": f.GetNIPHost(), } - ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.HTTPBunService, 80, annotations) + ing := framework.NewSingleIngress(host, + "/", + host, + f.Namespace, + framework.HTTPBunService, + 80, + annotations) + namedBackend := networking.IngressBackend{ Service: &networking.IngressServiceBackend{ Name: framework.NIPService, @@ -188,6 +205,7 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() { }, }, } + ing.Spec.Rules[0].HTTP.Paths[0].Backend = namedBackend f.EnsureIngress(ing) @@ -204,10 +222,6 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() { }) ginkgo.It("should return 200 for service type=ExternalName using FQDN with trailing dot", func() { - // This is a workaround so we only depend on a self hosted instance of - // httpbun - ip := f.NewHttpbunDeployment() - host := "echo" svc := &corev1.Service{ @@ -216,14 +230,19 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() { Namespace: f.Namespace, }, Spec: corev1.ServiceSpec{ - ExternalName: framework.BuildNIPHost(ip), + ExternalName: f.GetNIPHost(), Type: corev1.ServiceTypeExternalName, }, } - f.EnsureService(svc) - ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.HTTPBunService, 80, nil) + ing := framework.NewSingleIngress(host, + "/", + host, + f.Namespace, + framework.HTTPBunService, + 80, + nil) f.EnsureIngress(ing) f.WaitForNginxServer(host, @@ -239,20 +258,23 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() { }) ginkgo.It("should update the external name after a service update", func() { - // This is a workaround so we only depend on a self hosted instance of - // httpbun - ip := f.NewHttpbunDeployment() - host := "echo" - svc := framework.BuildNIPExternalNameService(f, ip, host) + svc := framework.BuildNIPExternalNameService(f, f.HTTPBunIP, host) f.EnsureService(svc) annotations := map[string]string{ - "nginx.ingress.kubernetes.io/upstream-vhost": framework.BuildNIPHost(ip), + "nginx.ingress.kubernetes.io/upstream-vhost": f.GetNIPHost(), } - ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.HTTPBunService, 80, annotations) + ing := framework.NewSingleIngress(host, + "/", + host, + f.Namespace, + framework.HTTPBunService, + 80, + annotations) + namedBackend := networking.IngressBackend{ Service: &networking.IngressServiceBackend{ Name: framework.NIPService, @@ -279,14 +301,20 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() { assert.Contains(ginkgo.GinkgoT(), body, `"X-Forwarded-Host": "echo"`) - svc, err := f.KubeClientSet.CoreV1().Services(f.Namespace).Get(context.TODO(), framework.NIPService, metav1.GetOptions{}) + svc, err := f.KubeClientSet. + CoreV1(). + Services(f.Namespace). + Get(context.TODO(), framework.NIPService, metav1.GetOptions{}) assert.Nil(ginkgo.GinkgoT(), err, "unexpected error obtaining external service") - ip = f.NewHttpbunDeployment(framework.WithDeploymentName("eu-server")) - + //Deploy a new instance to switch routing to + ip := f.NewHttpbunDeployment(framework.WithDeploymentName("eu-server")) svc.Spec.ExternalName = framework.BuildNIPHost(ip) - _, err = f.KubeClientSet.CoreV1().Services(f.Namespace).Update(context.Background(), svc, metav1.UpdateOptions{}) + _, err = f.KubeClientSet. + CoreV1(). + Services(f.Namespace). + Update(context.Background(), svc, metav1.UpdateOptions{}) assert.Nil(ginkgo.GinkgoT(), err, "unexpected error updating external service") framework.Sleep() @@ -302,21 +330,31 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() { assert.Contains(ginkgo.GinkgoT(), body, `"X-Forwarded-Host": "echo"`) ginkgo.By("checking the service is updated to use new host") - curlCmd := fmt.Sprintf("curl --fail --silent http://localhost:%v/configuration/backends", nginx.StatusPort) + curlCmd := fmt.Sprintf( + "curl --fail --silent http://localhost:%v/configuration/backends", + nginx.StatusPort, + ) + output, err := f.ExecIngressPod(curlCmd) assert.Nil(ginkgo.GinkgoT(), err) - assert.Contains(ginkgo.GinkgoT(), output, fmt.Sprintf("{\"address\":\"%s\"", framework.BuildNIPHost(ip))) + assert.Contains( + ginkgo.GinkgoT(), + output, + fmt.Sprintf("{\"address\":\"%s\"", framework.BuildNIPHost(ip)), + ) }) ginkgo.It("should sync ingress on external name service addition/deletion", func() { - // This is a workaround so we only depend on a self hosted instance of - // httpbun - ip := f.NewHttpbunDeployment() - host := "echo" // Create the Ingress first - ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.NIPService, 80, nil) + ing := framework.NewSingleIngress(host, + "/", + host, + f.Namespace, + framework.NIPService, + 80, + nil) f.EnsureIngress(ing) f.WaitForNginxServer(host, @@ -332,7 +370,7 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() { Status(http.StatusServiceUnavailable) // Now create the service - svc := framework.BuildNIPExternalNameService(f, ip, host) + svc := framework.BuildNIPExternalNameService(f, f.HTTPBunIP, host) f.EnsureService(svc) framework.Sleep() @@ -345,7 +383,10 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() { Status(http.StatusOK) // And back to 503 after deleting the service - err := f.KubeClientSet.CoreV1().Services(f.Namespace).Delete(context.TODO(), framework.NIPService, metav1.DeleteOptions{}) + err := f.KubeClientSet. + CoreV1(). + Services(f.Namespace). + Delete(context.TODO(), framework.NIPService, metav1.DeleteOptions{}) assert.Nil(ginkgo.GinkgoT(), err, "unexpected error deleting external service") framework.Sleep() diff --git a/test/e2e/settings/brotli.go b/test/e2e/settings/brotli.go index a13678f66..aacaddec5 100644 --- a/test/e2e/settings/brotli.go +++ b/test/e2e/settings/brotli.go @@ -28,14 +28,13 @@ import ( ) var _ = framework.IngressNginxDescribe("brotli", func() { - f := framework.NewDefaultFramework("brotli") + f := framework.NewDefaultFramework( + "brotli", + framework.WithHTTPBunEnabled(), + ) host := "brotli" - ginkgo.BeforeEach(func() { - f.NewHttpbunDeployment() - }) - ginkgo.It("should only compress responses that meet the `brotli-min-length` condition", func() { brotliMinLength := 24 contentEncoding := "application/octet-stream" diff --git a/test/e2e/settings/disable_service_external_name.go b/test/e2e/settings/disable_service_external_name.go index 7f03e5355..4ecf69e81 100644 --- a/test/e2e/settings/disable_service_external_name.go +++ b/test/e2e/settings/disable_service_external_name.go @@ -33,7 +33,10 @@ import ( ) var _ = framework.IngressNginxDescribe("[Flag] disable-service-external-name", func() { - f := framework.NewDefaultFramework("disabled-service-external-name") + f := framework.NewDefaultFramework( + "disabled-service-external-name", + framework.WithHTTPBunEnabled(), + ) ginkgo.BeforeEach(func() { f.NewEchoDeployment(framework.WithDeploymentReplicas(2)) @@ -54,21 +57,18 @@ var _ = framework.IngressNginxDescribe("[Flag] disable-service-external-name", f externalhost := "echo-external-svc.com" - ip := f.NewHttpbunDeployment() - svc := framework.BuildNIPExternalNameService(f, ip, "echo") - f.EnsureService(svc) + f.EnsureService(framework.BuildNIPExternalNameService(f, f.HTTPBunIP, "echo")) - svcexternal := &corev1.Service{ + f.EnsureService(&corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "external", Namespace: f.Namespace, }, Spec: corev1.ServiceSpec{ - ExternalName: framework.BuildNIPHost(ip), + ExternalName: f.GetNIPHost(), Type: corev1.ServiceTypeExternalName, }, - } - f.EnsureService(svcexternal) + }) ingexternal := framework.NewSingleIngress(externalhost, "/", externalhost, f.Namespace, "external", 80, nil) f.EnsureIngress(ingexternal) diff --git a/test/e2e/settings/global_external_auth.go b/test/e2e/settings/global_external_auth.go index c5964299f..cc98099ae 100644 --- a/test/e2e/settings/global_external_auth.go +++ b/test/e2e/settings/global_external_auth.go @@ -32,7 +32,10 @@ import ( ) var _ = framework.DescribeSetting("[Security] global-auth-url", func() { - f := framework.NewDefaultFramework("global-external-auth") + f := framework.NewDefaultFramework( + "global-external-auth", + framework.WithHTTPBunEnabled(), + ) host := "global-external-auth" @@ -50,7 +53,6 @@ var _ = framework.DescribeSetting("[Security] global-auth-url", func() { ginkgo.BeforeEach(func() { f.NewEchoDeployment() - f.NewHttpbunDeployment() }) ginkgo.Context("when global external authentication is configured", func() { @@ -307,9 +309,9 @@ http { assert.GreaterOrEqual(ginkgo.GinkgoT(), len(e.Subsets), 1, "expected at least one endpoint") assert.GreaterOrEqual(ginkgo.GinkgoT(), len(e.Subsets[0].Addresses), 1, "expected at least one address ready in the endpoint") - httpbunIP := e.Subsets[0].Addresses[0].IP + nginxIP := e.Subsets[0].Addresses[0].IP - f.UpdateNginxConfigMapData(globalExternalAuthURLSetting, fmt.Sprintf("http://%s/cookies/set/alma/armud", httpbunIP)) + f.UpdateNginxConfigMapData(globalExternalAuthURLSetting, fmt.Sprintf("http://%s/cookies/set/alma/armud", nginxIP)) ing1 = framework.NewSingleIngress(host, "/", host, f.Namespace, "http-cookie-with-error", 80, nil) f.EnsureIngress(ing1) diff --git a/test/e2e/settings/listen_nondefault_ports.go b/test/e2e/settings/listen_nondefault_ports.go index e682cef06..7e3b11b21 100644 --- a/test/e2e/settings/listen_nondefault_ports.go +++ b/test/e2e/settings/listen_nondefault_ports.go @@ -17,14 +17,12 @@ limitations under the License. package settings import ( - "context" "fmt" "net/http" "strings" "github.com/onsi/ginkgo/v2" "github.com/stretchr/testify/assert" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/ingress-nginx/test/e2e/framework" ) @@ -33,7 +31,7 @@ var _ = framework.IngressNginxDescribe("[Flag] custom HTTP and HTTPS ports", fun host := "forwarded-headers" - f := framework.NewDefaultFramework("forwarded-port-headers") + f := framework.NewDefaultFramework("forwarded-port-headers", framework.WithHTTPBunEnabled()) ginkgo.BeforeEach(func() { f.NewEchoDeployment() @@ -98,21 +96,8 @@ var _ = framework.IngressNginxDescribe("[Flag] custom HTTP and HTTPS ports", fun ginkgo.Context("when external authentication is configured", func() { ginkgo.It("should set the X-Forwarded-Port header to 443", func() { - f.NewHttpbunDeployment() - - err := framework.WaitForEndpoints(f.KubeClientSet, framework.DefaultTimeout, framework.HTTPBunService, f.Namespace, 1) - assert.Nil(ginkgo.GinkgoT(), err) - - e, err := f.KubeClientSet.CoreV1().Endpoints(f.Namespace).Get(context.TODO(), framework.HTTPBunService, metav1.GetOptions{}) - assert.Nil(ginkgo.GinkgoT(), err) - - assert.GreaterOrEqual(ginkgo.GinkgoT(), len(e.Subsets), 1, "expected at least one endpoint") - assert.GreaterOrEqual(ginkgo.GinkgoT(), len(e.Subsets[0].Addresses), 1, "expected at least one address ready in the endpoint") - - httpbunIP := e.Subsets[0].Addresses[0].IP - annotations := map[string]string{ - "nginx.ingress.kubernetes.io/auth-url": fmt.Sprintf("http://%s/basic-auth/user/password", httpbunIP), + "nginx.ingress.kubernetes.io/auth-url": fmt.Sprintf("http://%s/basic-auth/user/password", f.HTTPBunIP), "nginx.ingress.kubernetes.io/auth-signin": "http://$host/auth/start", } diff --git a/test/e2e/settings/ssl_passthrough.go b/test/e2e/settings/ssl_passthrough.go index a906a2d11..f0859f878 100644 --- a/test/e2e/settings/ssl_passthrough.go +++ b/test/e2e/settings/ssl_passthrough.go @@ -34,7 +34,7 @@ import ( ) var _ = framework.IngressNginxDescribe("[Flag] enable-ssl-passthrough", func() { - f := framework.NewDefaultFramework("ssl-passthrough") + f := framework.NewDefaultFramework("ssl-passthrough", framework.WithHTTPBunEnabled()) ginkgo.BeforeEach(func() { err := f.UpdateIngressControllerDeployment(func(deployment *appsv1.Deployment) error { @@ -86,7 +86,14 @@ var _ = framework.IngressNginxDescribe("[Flag] enable-ssl-passthrough", func() { "nginx.ingress.kubernetes.io/ssl-passthrough": "true", } - ingressDef := framework.NewSingleIngressWithTLS(host, "/", host, []string{host}, f.Namespace, echoName, 80, annotations) + ingressDef := framework.NewSingleIngressWithTLS(host, + "/", + host, + []string{host}, + f.Namespace, + echoName, + 80, + annotations) tlsConfig, err := framework.CreateIngressTLSSecret(f.KubeClientSet, ingressDef.Spec.TLS[0].Hosts, ingressDef.Spec.TLS[0].SecretName, @@ -119,7 +126,17 @@ var _ = framework.IngressNginxDescribe("[Flag] enable-ssl-passthrough", func() { Value: "/certs/tls.key", }, } - f.NewDeploymentWithOpts("echopass", "ghcr.io/sharat87/httpbun:latest", 80, 1, nil, nil, envs, volumeMount, volume, false) + + f.NewDeploymentWithOpts("echopass", + framework.HTTPBunImage, + 80, + 1, + nil, + nil, + envs, + volumeMount, + volume, + false) f.EnsureIngress(ingressDef) @@ -133,7 +150,14 @@ var _ = framework.IngressNginxDescribe("[Flag] enable-ssl-passthrough", func() { /* This one should not receive traffic as it does not contain passthrough annotation */ hostBad := "noannotationnopassthrough.com" - ingBad := f.EnsureIngress(framework.NewSingleIngressWithTLS(hostBad, "/", hostBad, []string{hostBad}, f.Namespace, echoName, 80, nil)) + ingBad := f.EnsureIngress(framework.NewSingleIngressWithTLS(hostBad, + "/", + hostBad, + []string{hostBad}, + f.Namespace, + echoName, + 80, + nil)) tlsConfigBad, err := framework.CreateIngressTLSSecret(f.KubeClientSet, ingBad.Spec.TLS[0].Hosts, ingBad.Spec.TLS[0].SecretName, From 436df32c2c50dee5b984e06b86b2660f32a5a384 Mon Sep 17 00:00:00 2001 From: Ehsan Saei <71217171+esigo@users.noreply.github.com> Date: Mon, 12 Jun 2023 12:47:48 +0200 Subject: [PATCH 2/8] add distroless otel init (#10035) add distroless otel init --- charts/ingress-nginx/templates/_helpers.tpl | 6 +- .../templates/controller-deployment.yaml | 2 +- images/opentelemetry/rootfs/Dockerfile | 18 ++- images/opentelemetry/rootfs/build.sh | 3 + images/opentelemetry/rootfs/go.mod | 3 + images/opentelemetry/rootfs/init_module.go | 104 ++++++++++++++++++ images/opentelemetry/rootfs/init_module.sh | 22 ---- 7 files changed, 131 insertions(+), 27 deletions(-) create mode 100644 images/opentelemetry/rootfs/go.mod create mode 100644 images/opentelemetry/rootfs/init_module.go delete mode 100755 images/opentelemetry/rootfs/init_module.sh diff --git a/charts/ingress-nginx/templates/_helpers.tpl b/charts/ingress-nginx/templates/_helpers.tpl index 7db5b2ca8..548e8cf12 100644 --- a/charts/ingress-nginx/templates/_helpers.tpl +++ b/charts/ingress-nginx/templates/_helpers.tpl @@ -201,8 +201,12 @@ Extra modules. - name: {{ .name }} image: {{ .image }} + {{- if .distroless | default false }} + command: ['/init_module'] + {{- else }} command: ['sh', '-c', '/usr/local/bin/init_module.sh'] - {{- if (.containerSecurityContext) }} + {{- end }} + {{- if .containerSecurityContext }} securityContext: {{ .containerSecurityContext | toYaml | nindent 4 }} {{- end }} volumeMounts: diff --git a/charts/ingress-nginx/templates/controller-deployment.yaml b/charts/ingress-nginx/templates/controller-deployment.yaml index 323d87623..7fe8804ea 100644 --- a/charts/ingress-nginx/templates/controller-deployment.yaml +++ b/charts/ingress-nginx/templates/controller-deployment.yaml @@ -190,7 +190,7 @@ spec: {{- end }} {{- if .Values.controller.opentelemetry.enabled}} {{ $otelContainerSecurityContext := $.Values.controller.opentelemetry.containerSecurityContext | default $.Values.controller.containerSecurityContext }} - {{- include "extraModules" (dict "name" "opentelemetry" "image" .Values.controller.opentelemetry.image "containerSecurityContext" $otelContainerSecurityContext) | nindent 8}} + {{- include "extraModules" (dict "name" "opentelemetry" "image" .Values.controller.opentelemetry.image "containerSecurityContext" $otelContainerSecurityContext "distroless" false) | nindent 8}} {{- end}} {{- end }} {{- if .Values.controller.hostNetwork }} diff --git a/images/opentelemetry/rootfs/Dockerfile b/images/opentelemetry/rootfs/Dockerfile index 69d82cda2..d15c28bbf 100644 --- a/images/opentelemetry/rootfs/Dockerfile +++ b/images/opentelemetry/rootfs/Dockerfile @@ -21,9 +21,11 @@ COPY . /opt/third_party/ # install build tools RUN apk update \ && apk upgrade \ - && apk add -U bash cmake \ + && apk add -U bash cmake ninja \ && bash /opt/third_party/build.sh -p +ENV NINJA_STATUS "[%p/%f/%t]" + # install gRPC FROM base as grpc RUN bash /opt/third_party/build.sh -g v1.49.2 @@ -39,7 +41,17 @@ COPY --from=grpc /opt/third_party/install/ /usr COPY --from=otel-cpp /opt/third_party/install/ /usr RUN bash /opt/third_party/build.sh -n -FROM alpine:3.18.0 as final -COPY --from=base /opt/third_party/init_module.sh /usr/local/bin/init_module.sh +FROM cgr.dev/chainguard/go:latest as build-init + +WORKDIR /go/src/app +COPY . . + +RUN go mod download +RUN CGO_ENABLED=0 go build -o /go/bin/init_module + +FROM cgr.dev/chainguard/static as final +COPY --from=build-init /go/bin/init_module / COPY --from=nginx /etc/nginx/modules /etc/nginx/modules COPY --from=nginx /opt/third_party/install/lib /etc/nginx/modules + +CMD ["/init_module"] diff --git a/images/opentelemetry/rootfs/build.sh b/images/opentelemetry/rootfs/build.sh index 6ad4601c6..30faad304 100755 --- a/images/opentelemetry/rootfs/build.sh +++ b/images/opentelemetry/rootfs/build.sh @@ -70,6 +70,7 @@ install_grpc() mkdir -p $BUILD_PATH/grpc cd ${BUILD_PATH}/grpc cmake -DCMAKE_INSTALL_PREFIX=${INSTAL_DIR} \ + -G Ninja \ -DGRPC_GIT_TAG=${GRPC_GIT_TAG} /opt/third_party \ -DgRPC_BUILD_GRPC_NODE_PLUGIN=OFF \ -DgRPC_BUILD_GRPC_OBJECTIVE_C_PLUGIN=OFF \ @@ -92,6 +93,7 @@ install_otel() cd .build cmake -DCMAKE_BUILD_TYPE=Release \ + -G Ninja \ -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE \ -DWITH_ZIPKIN=OFF \ -DWITH_JAEGER=OFF \ @@ -143,6 +145,7 @@ install_nginx() mkdir -p build cd build cmake -DCMAKE_BUILD_TYPE=Release \ + -G Ninja \ -DCMAKE_INSTALL_PREFIX=${INSTAL_DIR} \ -DBUILD_SHARED_LIBS=ON \ -DNGINX_VERSION=${NGINX_VERSION} \ diff --git a/images/opentelemetry/rootfs/go.mod b/images/opentelemetry/rootfs/go.mod new file mode 100644 index 000000000..f636c81b7 --- /dev/null +++ b/images/opentelemetry/rootfs/go.mod @@ -0,0 +1,3 @@ +module init-otel + +go 1.20 diff --git a/images/opentelemetry/rootfs/init_module.go b/images/opentelemetry/rootfs/init_module.go new file mode 100644 index 000000000..bebec728f --- /dev/null +++ b/images/opentelemetry/rootfs/init_module.go @@ -0,0 +1,104 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "io" + "os" + "path/filepath" +) + +func main() { + // Enable error handling for all operations + err := run() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} + +func run() error { + // Create the target directory if it doesn't exist + targetDir := "/modules_mount/etc/nginx/modules/otel" + err := os.MkdirAll(targetDir, os.ModePerm) + if err != nil { + return fmt.Errorf("failed to create target directory: %w", err) + } + + // Copy files from source directory to target directory + sourceDir := "/etc/nginx/modules/" + err = filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + // Skip directories + if info.IsDir() { + return nil + } + + // Calculate the destination path + relPath, err := filepath.Rel(sourceDir, path) + if err != nil { + return err + } + destPath := filepath.Join(targetDir, relPath) + + // Create the destination directory if it doesn't exist + destDir := filepath.Dir(destPath) + err = os.MkdirAll(destDir, os.ModePerm) + if err != nil { + return err + } + + // Copy the file + err = copyFile(path, destPath) + if err != nil { + return err + } + + return nil + }) + + if err != nil { + return fmt.Errorf("failed to copy files: %w", err) + } + + return nil +} + +func copyFile(sourcePath, destPath string) error { + sourceFile, err := os.Open(sourcePath) + if err != nil { + return err + } + defer sourceFile.Close() + + destFile, err := os.Create(destPath) + if err != nil { + return err + } + defer destFile.Close() + + _, err = io.Copy(destFile, sourceFile) + if err != nil { + return err + } + + return nil +} diff --git a/images/opentelemetry/rootfs/init_module.sh b/images/opentelemetry/rootfs/init_module.sh deleted file mode 100755 index 5a675aa2b..000000000 --- a/images/opentelemetry/rootfs/init_module.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -# Copyright 2021 The Kubernetes Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -o errexit -set -o nounset -set -o pipefail - -mkdir -p /modules_mount/etc/nginx/modules/otel -cp -R /etc/nginx/modules/* /modules_mount/etc/nginx/modules/otel From dd6d8e0d9f3def3bf9a85a1bac972418e67e5212 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jun 2023 05:03:48 -0700 Subject: [PATCH 3/8] Bump docker/setup-buildx-action from 2.5.0 to 2.6.0 (#10077) Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.5.0 to 2.6.0. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/4b4e9c3e2d4531116a6f8ba8e71fc6e2cb6e6c8c...6a58db7e0d21ca03e6c44877909e80e45217eed2) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3406b250f..756d65c3e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -158,7 +158,7 @@ jobs: - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@4b4e9c3e2d4531116a6f8ba8e71fc6e2cb6e6c8c # v2.5.0 + uses: docker/setup-buildx-action@6a58db7e0d21ca03e6c44877909e80e45217eed2 # v2.6.0 with: version: latest From 18e6bfdebfcc2ba1bf2f369e7fd10af72bd2f428 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jun 2023 05:05:47 -0700 Subject: [PATCH 4/8] Bump aquasecurity/trivy-action from 0.10.0 to 0.11.2 (#10078) Bumps [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) from 0.10.0 to 0.11.2. - [Release notes](https://github.com/aquasecurity/trivy-action/releases) - [Commits](https://github.com/aquasecurity/trivy-action/compare/e5f43133f6e8736992c9f3c1b3296e24b37e17f2...41f05d9ecffa2ed3f1580af306000f734b733e54) --- updated-dependencies: - dependency-name: aquasecurity/trivy-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/vulnerability-scans.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/vulnerability-scans.yaml b/.github/workflows/vulnerability-scans.yaml index 069c9f974..9814c2c95 100644 --- a/.github/workflows/vulnerability-scans.yaml +++ b/.github/workflows/vulnerability-scans.yaml @@ -60,7 +60,7 @@ jobs: - name: Scan image with AquaSec/Trivy id: scan - uses: aquasecurity/trivy-action@e5f43133f6e8736992c9f3c1b3296e24b37e17f2 # v0.10.0 + uses: aquasecurity/trivy-action@41f05d9ecffa2ed3f1580af306000f734b733e54 # v0.11.2 with: image-ref: registry.k8s.io/ingress-nginx/controller:${{ matrix.versions }} format: 'sarif' From 5544c1f1f639e4f13905b927854d4e48e83d5658 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jun 2023 05:33:49 -0700 Subject: [PATCH 5/8] Bump docker/setup-qemu-action from 2.1.0 to 2.2.0 (#10075) Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2.1.0 to 2.2.0. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/e81a89b1732b9c48d79cd809d8d81d79c4647a18...2b82ce82d56a2a04d2637cd93a637ae1b359c0a7) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 756d65c3e..9e3e97edf 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -154,7 +154,7 @@ jobs: check-latest: true - name: Set up QEMU - uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0 + uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0 - name: Set up Docker Buildx id: buildx From 4eeee031c76ac3403f845c8bf85e95caef87fee0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jun 2023 05:35:49 -0700 Subject: [PATCH 6/8] Bump actions/checkout from 3.5.2 to 3.5.3 (#10076) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.2 to 3.5.3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/8e5e7e5ab8b370d6c329ec480221332ada57f0ab...c85c95e3d7251135ab7dc9ce3241c5835cc595a9) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 22 +++++++++++----------- .github/workflows/depreview.yaml | 2 +- .github/workflows/docs.yaml | 4 ++-- .github/workflows/helm.yaml | 4 ++-- .github/workflows/perftest.yaml | 2 +- .github/workflows/plugin.yaml | 2 +- .github/workflows/scorecards.yml | 2 +- .github/workflows/vulnerability-scans.yaml | 4 ++-- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9e3e97edf..abb2af532 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -42,7 +42,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # v2.11.1 id: filter @@ -68,7 +68,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Run Gosec Security Scanner uses: securego/gosec@c5ea1b7bdd9efc3792e513258853552b0ae31e06 # v2.16.0 @@ -85,7 +85,7 @@ jobs: (needs.changes.outputs.go == 'true') steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Set up Go id: go @@ -104,7 +104,7 @@ jobs: (needs.changes.outputs.go == 'true') steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Set up Go id: go @@ -123,7 +123,7 @@ jobs: (needs.changes.outputs.go == 'true') steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Set up Go id: go @@ -144,7 +144,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Set up Go id: go @@ -211,7 +211,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Setup Go uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 @@ -286,7 +286,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: cache uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 @@ -336,7 +336,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: cache uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 @@ -380,7 +380,7 @@ jobs: PLATFORMS: linux/amd64,linux/arm64 steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # v2.11.1 id: filter-images @@ -447,7 +447,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # v2.11.1 id: filter-images diff --git a/.github/workflows/depreview.yaml b/.github/workflows/depreview.yaml index 4f04bdaed..625c2f461 100644 --- a/.github/workflows/depreview.yaml +++ b/.github/workflows/depreview.yaml @@ -9,6 +9,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: 'Dependency Review' uses: actions/dependency-review-action@1360a344ccb0ab6e9475edef90ad2f46bf8003b1 # v3.0.6 diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 1a2ceaa83..f7aee6610 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -23,7 +23,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # v2.11.1 id: filter @@ -47,7 +47,7 @@ jobs: steps: - name: Checkout master - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Deploy uses: ./.github/actions/mkdocs diff --git a/.github/workflows/helm.yaml b/.github/workflows/helm.yaml index 2a82fa124..6303b6a27 100644 --- a/.github/workflows/helm.yaml +++ b/.github/workflows/helm.yaml @@ -23,7 +23,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Run Artifact Hub lint run: | @@ -61,7 +61,7 @@ jobs: steps: - name: Checkout master - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: # Fetch entire history. Required for chart-releaser; see https://github.com/helm/chart-releaser-action/issues/13#issuecomment-602063896 fetch-depth: 0 diff --git a/.github/workflows/perftest.yaml b/.github/workflows/perftest.yaml index 9e87bf1b2..36f1f1ede 100644 --- a/.github/workflows/perftest.yaml +++ b/.github/workflows/perftest.yaml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Install K6 run: | diff --git a/.github/workflows/plugin.yaml b/.github/workflows/plugin.yaml index c5c6fc2b1..6088f77f8 100644 --- a/.github/workflows/plugin.yaml +++ b/.github/workflows/plugin.yaml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 72acf608a..72a112ac4 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -27,7 +27,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: persist-credentials: false diff --git a/.github/workflows/vulnerability-scans.yaml b/.github/workflows/vulnerability-scans.yaml index 9814c2c95..af7d8bda1 100644 --- a/.github/workflows/vulnerability-scans.yaml +++ b/.github/workflows/vulnerability-scans.yaml @@ -22,7 +22,7 @@ jobs: versions: ${{ steps.version.outputs.TAGS }} steps: - name: Checkout code - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 @@ -52,7 +52,7 @@ jobs: versions: ${{ fromJSON(needs.version.outputs.versions) }} steps: - name: Checkout code - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - shell: bash id: test From f1e3f2fa3c38a8a5473bd253175c3f6525bc3f53 Mon Sep 17 00:00:00 2001 From: Jintao Zhang Date: Tue, 13 Jun 2023 10:45:58 +0800 Subject: [PATCH 7/8] docs: add netlify configuration (#10073) Signed-off-by: Jintao Zhang --- Makefile | 5 +++++ netlify.toml | 10 ++++++++++ 2 files changed, 15 insertions(+) create mode 100644 netlify.toml diff --git a/Makefile b/Makefile index fc40a39bb..7b413141a 100644 --- a/Makefile +++ b/Makefile @@ -262,3 +262,8 @@ release: ensure-buildx clean --build-arg COMMIT_SHA="$(COMMIT_SHA)" \ --build-arg BUILD_ID="$(BUILD_ID)" \ -t $(REGISTRY)/controller-chroot:$(TAG) rootfs -f rootfs/Dockerfile-chroot + +.PHONY: build-docs +build-docs: + pip install -U mkdocs-material==6.2.4 mkdocs-awesome-pages-plugin mkdocs-minify-plugin mkdocs-redirects + mkdocs build --config-file mkdocs.yml diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 000000000..add8d4fc3 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,10 @@ +# netlify configuration +[build] +publish = "site" +command = "make build-docs" +# available here https://github.com/netlify/build-image/blob/focal/included_software.md#languages +environment = { PYTHON_VERSION = "3.8" } + +[context.deploy-preview] + publish = "site/" + command = "make build-docs" From cccba35005962f9eee96d2b592cc5b7b1485d965 Mon Sep 17 00:00:00 2001 From: Jintao Zhang Date: Wed, 14 Jun 2023 03:55:59 +0800 Subject: [PATCH 8/8] Revert "Remove fastcgi feature" (#10081) * Revert "Remove fastcgi feature (#9864)" This reverts commit 90ed0ccdbe4edd55ad4ab16ffec40be64f3308d9. * revert fastcgi* annotations warning Signed-off-by: Jintao Zhang --------- Signed-off-by: Jintao Zhang --- .github/workflows/ci.yaml | 6 + MANUAL_RELEASE.md | 2 + docs/e2e-tests.md | 131 +------- docs/kubectl-plugin.md | 1 + docs/user-guide/fcgi-services.md | 118 +++++++- .../nginx-configuration/annotations.md | 2 +- images/fastcgi-helloserver/Makefile | 59 ++++ images/fastcgi-helloserver/cloudbuild.yaml | 22 ++ images/fastcgi-helloserver/rootfs/Dockerfile | 32 ++ images/fastcgi-helloserver/rootfs/main.go | 30 ++ internal/ingress/annotations/annotations.go | 62 ++-- .../annotations/backendprotocol/main.go | 2 +- internal/ingress/annotations/fastcgi/main.go | 106 +++++++ .../ingress/annotations/fastcgi/main_test.go | 285 ++++++++++++++++++ internal/ingress/annotations/parser/main.go | 1 + internal/ingress/controller/controller.go | 5 +- .../ingress/controller/controller_test.go | 2 +- .../ingress/controller/template/template.go | 3 + .../controller/template/template_test.go | 2 + pkg/apis/ingress/types.go | 4 + pkg/apis/ingress/types_equals.go | 4 + rootfs/etc/nginx/template/nginx.tmpl | 11 + test/e2e/annotations/backendprotocol.go | 15 + test/e2e/annotations/fastcgi.go | 125 ++++++++ test/e2e/framework/fastcgi_helloserver.go | 104 +++++++ 25 files changed, 975 insertions(+), 159 deletions(-) create mode 100644 images/fastcgi-helloserver/Makefile create mode 100644 images/fastcgi-helloserver/cloudbuild.yaml create mode 100755 images/fastcgi-helloserver/rootfs/Dockerfile create mode 100644 images/fastcgi-helloserver/rootfs/main.go create mode 100644 internal/ingress/annotations/fastcgi/main.go create mode 100644 internal/ingress/annotations/fastcgi/main_test.go create mode 100644 test/e2e/annotations/fastcgi.go create mode 100644 test/e2e/framework/fastcgi_helloserver.go diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index abb2af532..9764a5e74 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -391,6 +391,8 @@ jobs: - 'images/custom-error-pages/**' cfssl: - 'images/cfssl/**' + fastcgi-helloserver: + - 'images/fastcgi-helloserver/**' echo: - 'images/echo/**' go-grpc-greeter-server: @@ -410,6 +412,10 @@ jobs: if: ${{ steps.filter-images.outputs.cfssl == 'true' }} run: | cd images/cfssl && make build + - name: fastcgi-helloserver + if: ${{ steps.filter-images.outputs.fastcgi-helloserver == 'true' }} + run: | + cd images/fastcgi-helloserver && make build - name: echo image build if: ${{ steps.filter-images.outputs.echo == 'true' }} run: | diff --git a/MANUAL_RELEASE.md b/MANUAL_RELEASE.md index 4b4df0ec6..d7144b85d 100644 --- a/MANUAL_RELEASE.md +++ b/MANUAL_RELEASE.md @@ -56,6 +56,8 @@ - [cfssl](https://github.com/kubernetes/ingress-nginx/tree/main/images/cfssl) + - [fastcgi-helloserver](https://github.com/kubernetes/ingress-nginx/tree/main/images/fastcgi-helloserver) + - [httpbin](https://github.com/kubernetes/ingress-nginx/tree/main/images/httpbin) - [kube-webhook-certgen](https://github.com/kubernetes/ingress-nginx/tree/main/images/kube-webhook-certgen) diff --git a/docs/e2e-tests.md b/docs/e2e-tests.md index 64d35c79a..025ff686d 100644 --- a/docs/e2e-tests.md +++ b/docs/e2e-tests.md @@ -108,6 +108,7 @@ Do not try to edit it manually. - [should set backend protocol to $scheme:// and use proxy_pass](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/backendprotocol.go#L49) - [should set backend protocol to grpc:// and use grpc_pass](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/backendprotocol.go#L64) - [should set backend protocol to grpcs:// and use grpc_pass](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/backendprotocol.go#L79) +- [should set backend protocol to '' and use fastcgi_pass](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/backendprotocol.go#L94) - [should set backend protocol to '' and use ajp_pass](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/backendprotocol.go#L109) ### [canary-*](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L36) @@ -191,6 +192,13 @@ Do not try to edit it manually. - [disable-http-access-log set access_log off](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/disableaccesslog.go#L53) - [disable-stream-access-log set access_log off](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/disableaccesslog.go#L71) +### [backend-protocol - FastCGI](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/fastcgi.go#L30) + +- [should use fastcgi_pass in the configuration file](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/fastcgi.go#L37) +- [should add fastcgi_index in the configuration file](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/fastcgi.go#L54) +- [should add fastcgi_param in the configuration file](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/fastcgi.go#L71) +- [should return OK for service with backend protocol FastCGI](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/fastcgi.go#L102) + ### [force-ssl-redirect](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/forcesslredirect.go#L27) - [should redirect to https](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/forcesslredirect.go#L34) @@ -342,101 +350,6 @@ Do not try to edit it manually. - [should set the X-Forwarded-Prefix to the annotation value](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/xforwardedprefix.go#L35) - [should not add X-Forwarded-Prefix if the annotation value is empty](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/xforwardedprefix.go#L57) -### [denylist-source-range](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/ipdenylist.go#L28) - -- [only deny explicitly denied IPs, allow all others](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/ipdenylist.go#L35) -- [only allow explicitly allowed IPs, deny all others](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/ipdenylist.go#L86) - -### [affinity session-cookie-name](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L35) - -- [should set sticky cookie SERVERID](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L42) -- [should change cookie name on ingress definition change](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L64) -- [should set the path to /something on the generated cookie](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L99) -- [does not set the path to / on the generated cookie if there's more than one rule referring to the same backend](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L121) -- [should set cookie with expires](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L194) -- [should set cookie with domain](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L225) -- [should not set cookie without domain annotation](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L248) -- [should work with use-regex annotation and session-cookie-path](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L270) -- [should warn user when use-regex is true and session-cookie-path is not set](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L294) -- [should not set affinity across all server locations when using separate ingresses](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L320) -- [should set sticky cookie without host](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L352) -- [should work with server-alias annotation](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L372) -- [should set secure in cookie with provided true annotation on http](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L412) -- [should not set secure in cookie with provided false annotation on http](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L435) -- [should set secure in cookie with provided false annotation on https](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L458) - -### [rewrite-target use-regex enable-rewrite-log](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/rewrite.go#L30) - -- [should write rewrite logs](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/rewrite.go#L37) -- [should use correct longest path match](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/rewrite.go#L66) -- [should use ~* location modifier if regex annotation is present](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/rewrite.go#L111) -- [should fail to use longest match for documented warning](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/rewrite.go#L158) -- [should allow for custom rewrite parameters](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/rewrite.go#L190) - -### [auth-*](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L39) - -- [should return status code 200 when no authentication is configured](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L46) -- [should return status code 503 when authentication is configured with an invalid secret](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L65) -- [should return status code 401 when authentication is configured but Authorization header is not configured](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L89) -- [should return status code 401 when authentication is configured and Authorization header is sent with invalid credentials](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L116) -- [should return status code 401 and cors headers when authentication and cors is configured but Authorization header is not configured](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L144) -- [should return status code 200 when authentication is configured and Authorization header is sent](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L172) -- [should return status code 200 when authentication is configured with a map and Authorization header is sent](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L199) -- [should return status code 401 when authentication is configured with invalid content and Authorization header is sent](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L227) -- [ when external auth is configured](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L266) -- [ when external auth is not configured](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L284) -- [ when auth-headers are set](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L301) -- [should set cache_key when external auth cache is configured](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L322) -- [user retains cookie by default](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L411) -- [user does not retain cookie if upstream returns error status code](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L422) -- [user with annotated ingress retains cookie if upstream returns error status code](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L433) -- [should return status code 200 when signed in](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L485) -- [should redirect to signin url when not signed in](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L494) -- [keeps processing new ingresses even if one of the existing ingresses is misconfigured](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L505) -- [should overwrite Foo header with auth response](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L529) -- [should not create additional upstream block when auth-keepalive is not set](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L552) -- [should not create additional upstream block when host part of auth-url contains a variable](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L570) -- [should not create additional upstream block when auth-keepalive is negative](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L590) -- [should not create additional upstream block when auth-keepalive is set with HTTP/2](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L609) -- [should create additional upstream block when auth-keepalive is set with HTTP/1.x](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L623) -- [should return status code 200 when signed in](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L678) -- [should redirect to signin url when not signed in](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L687) -- [keeps processing new ingresses even if one of the existing ingresses is misconfigured](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L698) -- [should return status code 200 when signed in after auth backend is deleted ](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L772) -- [should deny login for different location on same server](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L792) -- [should deny login for different servers](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L820) -- [should redirect to signin url when not signed in](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L849) -- [should return 503 (location was denied)](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L879) -- [should add error to the config](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L887) - -### [canary-*](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L36) - -- [should response with a 200 status from the mainline upstream when requests are made to the mainline ingress](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L48) -- [should return 404 status for requests to the canary if no matching ingress is found](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L80) -- [should return the correct status codes when endpoints are unavailable](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L107) -- [should route requests to the correct upstream if mainline ingress is created before the canary ingress](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L161) -- [should route requests to the correct upstream if mainline ingress is created after the canary ingress](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L206) -- [should route requests to the correct upstream if the mainline ingress is modified](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L250) -- [should route requests to the correct upstream if the canary ingress is modified](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L307) -- [should route requests to the correct upstream](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L372) -- [should route requests to the correct upstream](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L426) -- [should route requests to the correct upstream](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L490) -- [should route requests to the correct upstream](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L532) -- [should routes to mainline upstream when the given Regex causes error](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L566) -- [should route requests to the correct upstream](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L604) -- [respects always and never values](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L643) -- [should route requests only to mainline if canary weight is 0](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L705) -- [should route requests only to canary if canary weight is 100](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L743) -- [should route requests only to canary if canary weight is equal to canary weight total](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L775) -- [should route requests split between mainline and canary if canary weight is 50](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L808) -- [should route requests split between mainline and canary if canary weight is 100 and weight total is 200](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L834) -- [should not use canary as a catch-all server](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L863) -- [should not use canary with domain as a server](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L891) -- [does not crash when canary ingress has multiple paths to the same non-matching backend](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L915) -- [always routes traffic to canary if first request was affinitized to canary (default behavior)](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L943) -- [always routes traffic to canary if first request was affinitized to canary (explicit sticky behavior)](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L1000) -- [routes traffic to either mainline or canary backend (legacy behavior)](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L1058) - ### [Debug CLI](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/dbg/main.go#L29) - [should list the backend servers](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/dbg/main.go#L37) @@ -485,6 +398,10 @@ Do not try to edit it manually. - [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/exec.go#L) +### [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/fastcgi_helloserver.go#L) + +- [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/fastcgi_helloserver.go#L) + ### [[Setting] ](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/framework.go#L194) - [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/framework.go#L) @@ -561,30 +478,6 @@ Do not try to edit it manually. - [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/util.go#L) -### [[Setting] ](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/framework.go#L194) - -- [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/framework.go#L) - -### [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/healthz.go#L) - -- [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/healthz.go#L) - -### [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/k8s.go#L) - -- [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/k8s.go#L) - -### [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/metrics.go#L) - -- [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/metrics.go#L) - -### [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/deployment.go#L) - -- [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/deployment.go#L) - -### [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/exec.go#L) - -- [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/exec.go#L) - ### [[Shutdown] Grace period shutdown](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/gracefulshutdown/grace_period.go#L32) - [/healthz should return status code 500 during shutdown grace period](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/gracefulshutdown/grace_period.go#L35) diff --git a/docs/kubectl-plugin.md b/docs/kubectl-plugin.md index 9dc808642..9e5a5dcc6 100644 --- a/docs/kubectl-plugin.md +++ b/docs/kubectl-plugin.md @@ -200,6 +200,7 @@ kubectl ingress-nginx conf -n ingress-nginx --host testaddr.local ```console $ kubectl ingress-nginx exec -i -n ingress-nginx -- ls /etc/nginx +fastcgi_params geoip lua mime.types diff --git a/docs/user-guide/fcgi-services.md b/docs/user-guide/fcgi-services.md index d4f1d1f1e..db4d9428b 100644 --- a/docs/user-guide/fcgi-services.md +++ b/docs/user-guide/fcgi-services.md @@ -2,10 +2,118 @@ # Exposing FastCGI Servers -**This feature has been removed from Ingress NGINX** +> **FastCGI** is a [binary protocol](https://en.wikipedia.org/wiki/Binary_protocol "Binary protocol") for interfacing interactive programs with a [web server](https://en.wikipedia.org/wiki/Web_server "Web server"). [...] (It's) aim is to reduce the overhead related to interfacing between web server and CGI programs, allowing a server to handle more web page requests per unit of time. +> +> — Wikipedia -People willing to use fastcgi servers, should create an NGINX + FastCGI service and expose -this service via Ingress NGINX. +The _ingress-nginx_ ingress controller can be used to directly expose [FastCGI](https://en.wikipedia.org/wiki/FastCGI) servers. Enabling FastCGI in your Ingress only requires setting the _backend-protocol_ annotation to `FCGI`, and with a couple more annotations you can customize the way _ingress-nginx_ handles the communication with your FastCGI _server_. -We recommend using images like `cgr.dev/chainguard/nginx:latest` and expose your fast_cgi application -as another container on this Pod. + +## Example Objects to Expose a FastCGI Pod + +The _Pod_ example object below exposes port `9000`, which is the conventional FastCGI port. + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: example-app +labels: + app: example-app +spec: + containers: + - name: example-app + image: example-app:1.0 + ports: + - containerPort: 9000 + name: fastcgi +``` + +The _Service_ object example below matches port `9000` from the _Pod_ object above. + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: example-service +spec: + selector: + app: example-app + ports: + - port: 9000 + targetPort: 9000 + name: fastcgi +``` + +And the _Ingress_ and _ConfigMap_ objects below demonstrates the supported _FastCGI_ specific annotations (NGINX actually has 50 FastCGI directives, all of which have not been exposed in the ingress yet), and matches the service `example-service`, and the port named `fastcgi` from above. The _ConfigMap_ **must** be created first for the _Ingress Controller_ to be able to find it when the _Ingress_ object is created, otherwise you will need to restart the _Ingress Controller_ pods. + +```yaml +# The ConfigMap MUST be created first for the ingress controller to be able to +# find it when the Ingress object is created. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: example-cm +data: + SCRIPT_FILENAME: "/example/index.php" + +--- + +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + nginx.ingress.kubernetes.io/backend-protocol: "FCGI" + nginx.ingress.kubernetes.io/fastcgi-index: "index.php" + nginx.ingress.kubernetes.io/fastcgi-params-configmap: "example-cm" + name: example-app +spec: + ingressClassName: nginx + rules: + - host: app.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: example-service + port: + name: fastcgi +``` + +## FastCGI Ingress Annotations + +To enable FastCGI, the `nginx.ingress.kubernetes.io/backend-protocol` annotation needs to be set to `FCGI`, which overrides the default `HTTP` value. + +> `nginx.ingress.kubernetes.io/backend-protocol: "FCGI"` + +**This enables the _FastCGI_ mode for all paths defined in the _Ingress_ object** + +### The `nginx.ingress.kubernetes.io/fastcgi-index` Annotation + +To specify an index file, the `fastcgi-index` annotation value can optionally be set. In the example below, the value is set to `index.php`. This annotation corresponds to [the _NGINX_ `fastcgi_index` directive](https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_index). + +> `nginx.ingress.kubernetes.io/fastcgi-index: "index.php"` + +### The `nginx.ingress.kubernetes.io/fastcgi-params-configmap` Annotation + +To specify [_NGINX_ `fastcgi_param` directives](https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_param), the `fastcgi-params-configmap` annotation is used, which in turn must lead to a _ConfigMap_ object containing the _NGINX_ `fastcgi_param` directives as key/values. + +> `nginx.ingress.kubernetes.io/fastcgi-params-configmap: "example-configmap"` + +And the _ConfigMap_ object to specify the `SCRIPT_FILENAME` and `HTTP_PROXY` _NGINX's_ `fastcgi_param` directives will look like the following: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: example-configmap +data: + SCRIPT_FILENAME: "/example/index.php" + HTTP_PROXY: "" +``` +Using the _namespace/_ prefix is also supported, for example: + +> `nginx.ingress.kubernetes.io/fastcgi-params-configmap: "example-namespace/example-configmap"` diff --git a/docs/user-guide/nginx-configuration/annotations.md b/docs/user-guide/nginx-configuration/annotations.md index fc43a47b2..b515a9f3b 100755 --- a/docs/user-guide/nginx-configuration/annotations.md +++ b/docs/user-guide/nginx-configuration/annotations.md @@ -894,7 +894,7 @@ Include /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf ### Backend Protocol Using `backend-protocol` annotations is possible to indicate how NGINX should communicate with the backend service. (Replaces `secure-backends` in older versions) -Valid Values: HTTP, HTTPS, GRPC, GRPCS, AJP +Valid Values: HTTP, HTTPS, GRPC, GRPCS, AJP and FCGI By default NGINX uses `HTTP`. diff --git a/images/fastcgi-helloserver/Makefile b/images/fastcgi-helloserver/Makefile new file mode 100644 index 000000000..4d277a19d --- /dev/null +++ b/images/fastcgi-helloserver/Makefile @@ -0,0 +1,59 @@ +# Copyright 2020 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Docker image for e2e testing. + +# set default shell +SHELL=/bin/bash -o pipefail -o errexit + +DIR:=$(strip $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))) +INIT_BUILDX=$(DIR)/../../hack/init-buildx.sh + +SHORT_SHA ?=$(shell git rev-parse --short HEAD) +TAG ?=v$(shell date +%Y%m%d)-$(SHORT_SHA) + +REGISTRY ?= local + +IMAGE = $(REGISTRY)/e2e-test-fastcgi-helloserver + +# required to enable buildx +export DOCKER_CLI_EXPERIMENTAL=enabled + +# build with buildx +PLATFORMS?=linux/amd64,linux/arm,linux/arm64 +OUTPUT= +PROGRESS=plain + +build: ensure-buildx + docker buildx build \ + --platform=${PLATFORMS} $(OUTPUT) \ + --progress=$(PROGRESS) \ + --pull \ + -t $(IMAGE):$(TAG) rootfs + +# push the cross built image +push: OUTPUT=--push +push: build + +# enable buildx +ensure-buildx: +# this is required for cloudbuild +ifeq ("$(wildcard $(INIT_BUILDX))","") + @curl -sSL https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/hack/init-buildx.sh | bash +else + @exec $(INIT_BUILDX) +endif + @echo "done" + +.PHONY: build push ensure-buildx diff --git a/images/fastcgi-helloserver/cloudbuild.yaml b/images/fastcgi-helloserver/cloudbuild.yaml new file mode 100644 index 000000000..a4c9d5eff --- /dev/null +++ b/images/fastcgi-helloserver/cloudbuild.yaml @@ -0,0 +1,22 @@ +timeout: 1800s +options: + substitution_option: ALLOW_LOOSE +steps: + - name: gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20211118-2f2d816b90 + entrypoint: bash + env: + - DOCKER_CLI_EXPERIMENTAL=enabled + - SHORT_SHA=$SHORT_SHA + - BASE_REF=$_PULL_BASE_REF + - REGISTRY=gcr.io/k8s-staging-ingress-nginx + # default cloudbuild has HOME=/builder/home and docker buildx is in /root/.docker/cli-plugins/docker-buildx + # set the home to /root explicitly to if using docker buildx + - HOME=/root + args: + - -c + - | + gcloud auth configure-docker \ + && cd images/fastcgi-helloserver && make push +substitutions: + _GIT_TAG: "12345" + _PULL_BASE_REF: "master" diff --git a/images/fastcgi-helloserver/rootfs/Dockerfile b/images/fastcgi-helloserver/rootfs/Dockerfile new file mode 100755 index 000000000..a11834373 --- /dev/null +++ b/images/fastcgi-helloserver/rootfs/Dockerfile @@ -0,0 +1,32 @@ +# Copyright 2017 The Kubernetes Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM golang:1.20.4-alpine3.18 as builder + +WORKDIR /go/src/k8s.io/ingress-nginx/images/fastcgi + +COPY . . + +RUN CGO_ENABLED=0 go build -a -installsuffix cgo \ + -ldflags "-s -w" \ + -o fastcgi-helloserver main.go + +# Use distroless as minimal base image to package the binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +FROM gcr.io/distroless/static:nonroot + +COPY --from=builder /go/src/k8s.io/ingress-nginx/images/fastcgi/fastcgi-helloserver / +USER nonroot:nonroot + +CMD ["/fastcgi-helloserver"] diff --git a/images/fastcgi-helloserver/rootfs/main.go b/images/fastcgi-helloserver/rootfs/main.go new file mode 100644 index 000000000..91db60c26 --- /dev/null +++ b/images/fastcgi-helloserver/rootfs/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "net" + "net/http" + "net/http/fcgi" +) + +func hello(w http.ResponseWriter, r *http.Request) { + keys, ok := r.URL.Query()["name"] + + if !ok || len(keys[0]) < 1 { + fmt.Fprintf(w, "Hello world!") + return + } + + key := keys[0] + fmt.Fprintf(w, "Hello "+string(key)+"!") +} + +func main() { + http.HandleFunc("/hello", hello) + + l, err := net.Listen("tcp", "0.0.0.0:9000") + if err != nil { + panic(err) + } + fcgi.Serve(l, nil) +} diff --git a/internal/ingress/annotations/annotations.go b/internal/ingress/annotations/annotations.go index ad7dad37d..5bb2bf5e6 100644 --- a/internal/ingress/annotations/annotations.go +++ b/internal/ingress/annotations/annotations.go @@ -41,6 +41,7 @@ import ( "k8s.io/ingress-nginx/internal/ingress/annotations/cors" "k8s.io/ingress-nginx/internal/ingress/annotations/customhttperrors" "k8s.io/ingress-nginx/internal/ingress/annotations/defaultbackend" + "k8s.io/ingress-nginx/internal/ingress/annotations/fastcgi" "k8s.io/ingress-nginx/internal/ingress/annotations/globalratelimit" "k8s.io/ingress-nginx/internal/ingress/annotations/http2pushpreload" "k8s.io/ingress-nginx/internal/ingress/annotations/ipdenylist" @@ -85,35 +86,37 @@ type Ingress struct { CorsConfig cors.Config CustomHTTPErrors []int DefaultBackend *apiv1.Service - Denied *string - ExternalAuth authreq.Config - EnableGlobalAuth bool - HTTP2PushPreload bool - Opentracing opentracing.Config - Opentelemetry opentelemetry.Config - Proxy proxy.Config - ProxySSL proxyssl.Config - RateLimit ratelimit.Config - GlobalRateLimit globalratelimit.Config - Redirect redirect.Config - Rewrite rewrite.Config - Satisfy string - ServerSnippet string - ServiceUpstream bool - SessionAffinity sessionaffinity.Config - SSLPassthrough bool - UsePortInRedirects bool - UpstreamHashBy upstreamhashby.Config - LoadBalancing string - UpstreamVhost string - Whitelist ipwhitelist.SourceRange - Denylist ipdenylist.SourceRange - XForwardedPrefix string - SSLCipher sslcipher.Config - Logs log.Config - ModSecurity modsecurity.Config - Mirror mirror.Config - StreamSnippet string + //TODO: Change this back into an error when https://github.com/imdario/mergo/issues/100 is resolved + FastCGI fastcgi.Config + Denied *string + ExternalAuth authreq.Config + EnableGlobalAuth bool + HTTP2PushPreload bool + Opentracing opentracing.Config + Opentelemetry opentelemetry.Config + Proxy proxy.Config + ProxySSL proxyssl.Config + RateLimit ratelimit.Config + GlobalRateLimit globalratelimit.Config + Redirect redirect.Config + Rewrite rewrite.Config + Satisfy string + ServerSnippet string + ServiceUpstream bool + SessionAffinity sessionaffinity.Config + SSLPassthrough bool + UsePortInRedirects bool + UpstreamHashBy upstreamhashby.Config + LoadBalancing string + UpstreamVhost string + Whitelist ipwhitelist.SourceRange + Denylist ipdenylist.SourceRange + XForwardedPrefix string + SSLCipher sslcipher.Config + Logs log.Config + ModSecurity modsecurity.Config + Mirror mirror.Config + StreamSnippet string } // Extractor defines the annotation parsers to be used in the extraction of annotations @@ -135,6 +138,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor { "CorsConfig": cors.NewParser(cfg), "CustomHTTPErrors": customhttperrors.NewParser(cfg), "DefaultBackend": defaultbackend.NewParser(cfg), + "FastCGI": fastcgi.NewParser(cfg), "ExternalAuth": authreq.NewParser(cfg), "EnableGlobalAuth": authreqglobal.NewParser(cfg), "HTTP2PushPreload": http2pushpreload.NewParser(cfg), diff --git a/internal/ingress/annotations/backendprotocol/main.go b/internal/ingress/annotations/backendprotocol/main.go index 0140c30a3..d8ea72386 100644 --- a/internal/ingress/annotations/backendprotocol/main.go +++ b/internal/ingress/annotations/backendprotocol/main.go @@ -31,7 +31,7 @@ import ( const HTTP = "HTTP" var ( - validProtocols = regexp.MustCompile(`^(AUTO_HTTP|HTTP|HTTPS|AJP|GRPC|GRPCS)$`) + validProtocols = regexp.MustCompile(`^(AUTO_HTTP|HTTP|HTTPS|AJP|GRPC|GRPCS|FCGI)$`) ) type backendProtocol struct { diff --git a/internal/ingress/annotations/fastcgi/main.go b/internal/ingress/annotations/fastcgi/main.go new file mode 100644 index 000000000..84bac4109 --- /dev/null +++ b/internal/ingress/annotations/fastcgi/main.go @@ -0,0 +1,106 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fastcgi + +import ( + "fmt" + "reflect" + + networking "k8s.io/api/networking/v1" + "k8s.io/client-go/tools/cache" + + "k8s.io/ingress-nginx/internal/ingress/annotations/parser" + ing_errors "k8s.io/ingress-nginx/internal/ingress/errors" + "k8s.io/ingress-nginx/internal/ingress/resolver" +) + +type fastcgi struct { + r resolver.Resolver +} + +// Config describes the per location fastcgi config +type Config struct { + Index string `json:"index"` + Params map[string]string `json:"params"` +} + +// Equal tests for equality between two Configuration types +func (l1 *Config) Equal(l2 *Config) bool { + if l1 == l2 { + return true + } + + if l1 == nil || l2 == nil { + return false + } + + if l1.Index != l2.Index { + return false + } + + return reflect.DeepEqual(l1.Params, l2.Params) +} + +// NewParser creates a new fastcgiConfig protocol annotation parser +func NewParser(r resolver.Resolver) parser.IngressAnnotation { + return fastcgi{r} +} + +// ParseAnnotations parses the annotations contained in the ingress +// rule used to indicate the fastcgiConfig. +func (a fastcgi) Parse(ing *networking.Ingress) (interface{}, error) { + + fcgiConfig := Config{} + + if ing.GetAnnotations() == nil { + return fcgiConfig, nil + } + + index, err := parser.GetStringAnnotation("fastcgi-index", ing) + if err != nil { + index = "" + } + fcgiConfig.Index = index + + cm, err := parser.GetStringAnnotation("fastcgi-params-configmap", ing) + if err != nil { + return fcgiConfig, nil + } + + cmns, cmn, err := cache.SplitMetaNamespaceKey(cm) + if err != nil { + return fcgiConfig, ing_errors.LocationDenied{ + Reason: fmt.Errorf("error reading configmap name from annotation: %w", err), + } + } + + if cmns != "" && cmns != ing.Namespace { + return fcgiConfig, fmt.Errorf("different namespace is not supported on fast_cgi param configmap") + } + + cm = fmt.Sprintf("%v/%v", ing.Namespace, cmn) + cmap, err := a.r.GetConfigMap(cm) + if err != nil { + return fcgiConfig, ing_errors.LocationDenied{ + Reason: fmt.Errorf("unexpected error reading configmap %s: %w", cm, err), + } + } + + fcgiConfig.Params = cmap.Data + + return fcgiConfig, nil +} diff --git a/internal/ingress/annotations/fastcgi/main_test.go b/internal/ingress/annotations/fastcgi/main_test.go new file mode 100644 index 000000000..35c5bbc12 --- /dev/null +++ b/internal/ingress/annotations/fastcgi/main_test.go @@ -0,0 +1,285 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fastcgi + +import ( + "fmt" + "testing" + + api "k8s.io/api/core/v1" + networking "k8s.io/api/networking/v1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/cache" + "k8s.io/ingress-nginx/internal/ingress/annotations/parser" + "k8s.io/ingress-nginx/internal/ingress/resolver" +) + +func buildIngress() *networking.Ingress { + return &networking.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: networking.IngressSpec{ + DefaultBackend: &networking.IngressBackend{ + Service: &networking.IngressServiceBackend{ + Name: "default-backend", + Port: networking.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }, + } +} + +type mockConfigMap struct { + resolver.Mock +} + +func (m mockConfigMap) GetConfigMap(name string) (*api.ConfigMap, error) { + if name != "default/demo-configmap" && name != "otherns/demo-configmap" { + return nil, fmt.Errorf("there is no configmap with name %v", name) + } + + cmns, cmn, err := cache.SplitMetaNamespaceKey(name) + if err != nil { + return nil, fmt.Errorf("invalid configmap name") + } + + return &api.ConfigMap{ + ObjectMeta: meta_v1.ObjectMeta{ + Namespace: cmns, + Name: cmn, + }, + Data: map[string]string{"REDIRECT_STATUS": "200", "SERVER_NAME": "$server_name"}, + }, nil +} + +func TestParseEmptyFastCGIAnnotations(t *testing.T) { + ing := buildIngress() + + i, err := NewParser(&mockConfigMap{}).Parse(ing) + if err != nil { + t.Errorf("unexpected error parsing ingress without fastcgi") + } + + config, ok := i.(Config) + if !ok { + t.Errorf("Parse do not return a Config object") + } + + if config.Index != "" { + t.Errorf("Index should be an empty string") + } + + if len(config.Params) != 0 { + t.Errorf("Params should be an empty slice") + } +} + +func TestParseFastCGIIndexAnnotation(t *testing.T) { + ing := buildIngress() + + const expectedAnnotation = "index.php" + + data := map[string]string{} + data[parser.GetAnnotationWithPrefix("fastcgi-index")] = expectedAnnotation + ing.SetAnnotations(data) + + i, err := NewParser(&mockConfigMap{}).Parse(ing) + if err != nil { + t.Errorf("unexpected error parsing ingress without fastcgi") + } + + config, ok := i.(Config) + if !ok { + t.Errorf("Parse do not return a Config object") + } + + if config.Index != "index.php" { + t.Errorf("expected %s but %v returned", expectedAnnotation, config.Index) + } +} + +func TestParseEmptyFastCGIParamsConfigMapAnnotation(t *testing.T) { + ing := buildIngress() + + data := map[string]string{} + data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = "" + ing.SetAnnotations(data) + + i, err := NewParser(&mockConfigMap{}).Parse(ing) + if err != nil { + t.Errorf("unexpected error parsing ingress without fastcgi") + } + + config, ok := i.(Config) + if !ok { + t.Errorf("Parse do not return a Config object") + } + + if len(config.Params) != 0 { + t.Errorf("Params should be an empty slice") + } +} + +func TestParseFastCGIInvalidParamsConfigMapAnnotation(t *testing.T) { + ing := buildIngress() + + invalidConfigMapList := []string{"unknown/configMap", "unknown/config/map"} + for _, configmap := range invalidConfigMapList { + + data := map[string]string{} + data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = configmap + ing.SetAnnotations(data) + + i, err := NewParser(&mockConfigMap{}).Parse(ing) + if err == nil { + t.Errorf("Reading an unexisting configmap should return an error") + } + + config, ok := i.(Config) + if !ok { + t.Errorf("Parse do not return a Config object") + } + + if len(config.Params) != 0 { + t.Errorf("Params should be an empty slice") + } + } +} + +func TestParseFastCGIParamsConfigMapAnnotationWithoutNS(t *testing.T) { + ing := buildIngress() + + data := map[string]string{} + data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = "demo-configmap" + ing.SetAnnotations(data) + + i, err := NewParser(&mockConfigMap{}).Parse(ing) + if err != nil { + t.Errorf("unexpected error parsing ingress without fastcgi") + } + + config, ok := i.(Config) + if !ok { + t.Errorf("Parse do not return a Config object") + } + + if len(config.Params) != 2 { + t.Errorf("Params should have a length of 2") + } + + if config.Params["REDIRECT_STATUS"] != "200" || config.Params["SERVER_NAME"] != "$server_name" { + t.Errorf("Params value is not the one expected") + } +} + +func TestParseFastCGIParamsConfigMapAnnotationWithNS(t *testing.T) { + ing := buildIngress() + + data := map[string]string{} + data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = "default/demo-configmap" + ing.SetAnnotations(data) + + i, err := NewParser(&mockConfigMap{}).Parse(ing) + if err != nil { + t.Errorf("unexpected error parsing ingress without fastcgi") + } + + config, ok := i.(Config) + if !ok { + t.Errorf("Parse do not return a Config object") + } + + if len(config.Params) != 2 { + t.Errorf("Params should have a length of 2") + } + + if config.Params["REDIRECT_STATUS"] != "200" || config.Params["SERVER_NAME"] != "$server_name" { + t.Errorf("Params value is not the one expected") + } +} + +func TestParseFastCGIParamsConfigMapAnnotationWithDifferentNS(t *testing.T) { + ing := buildIngress() + + data := map[string]string{} + data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = "otherns/demo-configmap" + ing.SetAnnotations(data) + + _, err := NewParser(&mockConfigMap{}).Parse(ing) + if err == nil { + t.Errorf("Different namespace configmap should return an error") + } + +} + +func TestConfigEquality(t *testing.T) { + + var nilConfig *Config + + config := Config{ + Index: "index.php", + Params: map[string]string{"REDIRECT_STATUS": "200", "SERVER_NAME": "$server_name"}, + } + + configCopy := Config{ + Index: "index.php", + Params: map[string]string{"REDIRECT_STATUS": "200", "SERVER_NAME": "$server_name"}, + } + + config2 := Config{ + Index: "index.php", + Params: map[string]string{"REDIRECT_STATUS": "200"}, + } + + config3 := Config{ + Index: "index.py", + Params: map[string]string{"SERVER_NAME": "$server_name", "REDIRECT_STATUS": "200"}, + } + + config4 := Config{ + Index: "index.php", + Params: map[string]string{"SERVER_NAME": "$server_name", "REDIRECT_STATUS": "200"}, + } + + if !config.Equal(&config) { + t.Errorf("config should be equal to itself") + } + + if nilConfig.Equal(&config) { + t.Errorf("Foo") + } + + if !config.Equal(&configCopy) { + t.Errorf("config should be equal to configCopy") + } + + if config.Equal(&config2) { + t.Errorf("config2 should not be equal to config") + } + + if config.Equal(&config3) { + t.Errorf("config3 should not be equal to config") + } + + if !config.Equal(&config4) { + t.Errorf("config4 should be equal to config") + } +} diff --git a/internal/ingress/annotations/parser/main.go b/internal/ingress/annotations/parser/main.go index 8a0ea4b87..107a278b0 100644 --- a/internal/ingress/annotations/parser/main.go +++ b/internal/ingress/annotations/parser/main.go @@ -160,6 +160,7 @@ func normalizeString(input string) string { var configmapAnnotations = sets.NewString( "auth-proxy-set-header", + "fastcgi-params-configmap", ) // AnnotationsReferencesConfigmap checks if at least one annotation in the Ingress rule diff --git a/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go index e93d3b5de..4a4417130 100644 --- a/internal/ingress/controller/controller.go +++ b/internal/ingress/controller/controller.go @@ -271,8 +271,6 @@ func (n *NGINXController) CheckWarning(ing *networking.Ingress) ([]string, error "influxdb-host", "influxdb-server-name", "secure-verify-ca-secret", - "fastcgi-params-configmap", - "fastcgi-index", ) // Skip checks if the ingress is marked as deleted @@ -608,7 +606,7 @@ func (n *NGINXController) getConfiguration(ingresses []*ingress.Ingress) (sets.S for _, server := range servers { // If a location is defined by a prefix string that ends with the slash character, and requests are processed by one of - // proxy_pass, uwsgi_pass, scgi_pass, memcached_pass, or grpc_pass, then the special processing is performed. + // proxy_pass, fastcgi_pass, uwsgi_pass, scgi_pass, memcached_pass, or grpc_pass, then the special processing is performed. // In response to a request with URI equal to // this string, but without the trailing slash, a permanent redirect with the // code 301 will be returned to the requested URI with the slash appended. If this is not desired, an exact match of the // URIand location could be defined like this: @@ -1519,6 +1517,7 @@ func locationApplyAnnotations(loc *ingress.Location, anns *annotations.Ingress) loc.Logs = anns.Logs loc.DefaultBackend = anns.DefaultBackend loc.BackendProtocol = anns.BackendProtocol + loc.FastCGI = anns.FastCGI loc.CustomHTTPErrors = anns.CustomHTTPErrors loc.ModSecurity = anns.ModSecurity loc.Satisfy = anns.Satisfy diff --git a/internal/ingress/controller/controller_test.go b/internal/ingress/controller/controller_test.go index 355f5da95..c631461c0 100644 --- a/internal/ingress/controller/controller_test.go +++ b/internal/ingress/controller/controller_test.go @@ -431,7 +431,7 @@ func TestCheckWarning(t *testing.T) { t.Run("adding invalid annotations increases the warning count", func(t *testing.T) { ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("enable-influxdb")] = "true" ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("secure-verify-ca-secret")] = "true" - ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("fastcgi-index")] = "blabla" + ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("influxdb-host")] = "blabla" defer func() { ing.ObjectMeta.Annotations = map[string]string{} }() diff --git a/internal/ingress/controller/template/template.go b/internal/ingress/controller/template/template.go index 15ae438ab..2d941f95d 100644 --- a/internal/ingress/controller/template/template.go +++ b/internal/ingress/controller/template/template.go @@ -739,6 +739,9 @@ func buildProxyPass(host string, b interface{}, loc interface{}) string { case "AJP": proto = "" proxyPass = "ajp_pass" + case "FCGI": + proto = "" + proxyPass = "fastcgi_pass" } upstreamName := "upstream_balancer" diff --git a/internal/ingress/controller/template/template_test.go b/internal/ingress/controller/template/template_test.go index 1b88c3fe7..1980d7e52 100644 --- a/internal/ingress/controller/template/template_test.go +++ b/internal/ingress/controller/template/template_test.go @@ -1138,6 +1138,7 @@ func TestOpentracingPropagateContext(t *testing.T) { {BackendProtocol: "GRPC"}: "opentracing_grpc_propagate_context;", {BackendProtocol: "GRPCS"}: "opentracing_grpc_propagate_context;", {BackendProtocol: "AJP"}: "opentracing_propagate_context;", + {BackendProtocol: "FCGI"}: "opentracing_propagate_context;", nil: "", } @@ -1157,6 +1158,7 @@ func TestOpentelemetryPropagateContext(t *testing.T) { {BackendProtocol: "GRPC"}: "opentelemetry_propagate;", {BackendProtocol: "GRPCS"}: "opentelemetry_propagate;", {BackendProtocol: "AJP"}: "opentelemetry_propagate;", + {BackendProtocol: "FCGI"}: "opentelemetry_propagate;", nil: "", } diff --git a/pkg/apis/ingress/types.go b/pkg/apis/ingress/types.go index 68cacc25b..e50666c18 100644 --- a/pkg/apis/ingress/types.go +++ b/pkg/apis/ingress/types.go @@ -27,6 +27,7 @@ import ( "k8s.io/ingress-nginx/internal/ingress/annotations/authtls" "k8s.io/ingress-nginx/internal/ingress/annotations/connection" "k8s.io/ingress-nginx/internal/ingress/annotations/cors" + "k8s.io/ingress-nginx/internal/ingress/annotations/fastcgi" "k8s.io/ingress-nginx/internal/ingress/annotations/globalratelimit" "k8s.io/ingress-nginx/internal/ingress/annotations/ipdenylist" "k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist" @@ -339,6 +340,9 @@ type Location struct { // BackendProtocol indicates which protocol should be used to communicate with the service // By default this is HTTP BackendProtocol string `json:"backend-protocol"` + // FastCGI allows the ingress to act as a FastCGI client for a given location. + // +optional + FastCGI fastcgi.Config `json:"fastcgi,omitempty"` // CustomHTTPErrors specifies the error codes that should be intercepted. // +optional CustomHTTPErrors []int `json:"custom-http-errors"` diff --git a/pkg/apis/ingress/types_equals.go b/pkg/apis/ingress/types_equals.go index fee8c31d5..84b1a186a 100644 --- a/pkg/apis/ingress/types_equals.go +++ b/pkg/apis/ingress/types_equals.go @@ -435,6 +435,10 @@ func (l1 *Location) Equal(l2 *Location) bool { return false } + if !(&l1.FastCGI).Equal(&l2.FastCGI) { + return false + } + match := compareInts(l1.CustomHTTPErrors, l2.CustomHTTPErrors) if !match { return false diff --git a/rootfs/etc/nginx/template/nginx.tmpl b/rootfs/etc/nginx/template/nginx.tmpl index 638333407..9b3a47de3 100644 --- a/rootfs/etc/nginx/template/nginx.tmpl +++ b/rootfs/etc/nginx/template/nginx.tmpl @@ -305,6 +305,7 @@ http { keepalive_requests {{ $cfg.KeepAliveRequests }}; client_body_temp_path /tmp/nginx/client-body; + fastcgi_temp_path /tmp/nginx/fastcgi-temp; proxy_temp_path /tmp/nginx/proxy-temp; ajp_temp_path /tmp/nginx/ajp-temp; @@ -1488,6 +1489,16 @@ stream { {{ range $errCode := $location.CustomHTTPErrors }} error_page {{ $errCode }} = @custom_{{ $location.DefaultBackendUpstreamName }}_{{ $errCode }};{{ end }} + {{ if (eq $location.BackendProtocol "FCGI") }} + include /etc/nginx/fastcgi_params; + {{ end }} + {{- if $location.FastCGI.Index -}} + fastcgi_index {{ $location.FastCGI.Index | quote }}; + {{- end -}} + {{ range $k, $v := $location.FastCGI.Params }} + fastcgi_param {{ $k }} {{ $v | quote }}; + {{ end }} + {{ if not (empty $location.Redirect.URL) }} return {{ $location.Redirect.Code }} {{ $location.Redirect.URL }}; {{ end }} diff --git a/test/e2e/annotations/backendprotocol.go b/test/e2e/annotations/backendprotocol.go index 8256befd2..bccb03afb 100644 --- a/test/e2e/annotations/backendprotocol.go +++ b/test/e2e/annotations/backendprotocol.go @@ -91,6 +91,21 @@ var _ = framework.DescribeAnnotation("backend-protocol", func() { }) }) + ginkgo.It("should set backend protocol to '' and use fastcgi_pass", func() { + host := "backendprotocol.foo.com" + annotations := map[string]string{ + "nginx.ingress.kubernetes.io/backend-protocol": "FCGI", + } + + ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations) + f.EnsureIngress(ing) + + f.WaitForNginxServer(host, + func(server string) bool { + return strings.Contains(server, "fastcgi_pass upstream_balancer;") + }) + }) + ginkgo.It("should set backend protocol to '' and use ajp_pass", func() { host := "backendprotocol.foo.com" annotations := map[string]string{ diff --git a/test/e2e/annotations/fastcgi.go b/test/e2e/annotations/fastcgi.go new file mode 100644 index 000000000..572eca548 --- /dev/null +++ b/test/e2e/annotations/fastcgi.go @@ -0,0 +1,125 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package annotations + +import ( + "net/http" + "strings" + + "github.com/onsi/ginkgo/v2" + corev1 "k8s.io/api/core/v1" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/ingress-nginx/test/e2e/framework" +) + +var _ = framework.DescribeAnnotation("backend-protocol - FastCGI", func() { + f := framework.NewDefaultFramework("fastcgi") + + ginkgo.BeforeEach(func() { + f.NewFastCGIHelloServerDeployment() + }) + + ginkgo.It("should use fastcgi_pass in the configuration file", func() { + host := "fastcgi" + + annotations := map[string]string{ + "nginx.ingress.kubernetes.io/backend-protocol": "FCGI", + } + + ing := framework.NewSingleIngress(host, "/hello", host, f.Namespace, "fastcgi-helloserver", 9000, annotations) + f.EnsureIngress(ing) + + f.WaitForNginxServer(host, + func(server string) bool { + return strings.Contains(server, "include /etc/nginx/fastcgi_params;") && + strings.Contains(server, "fastcgi_pass") + }) + }) + + ginkgo.It("should add fastcgi_index in the configuration file", func() { + host := "fastcgi-index" + + annotations := map[string]string{ + "nginx.ingress.kubernetes.io/backend-protocol": "FCGI", + "nginx.ingress.kubernetes.io/fastcgi-index": "index.php", + } + + ing := framework.NewSingleIngress(host, "/hello", host, f.Namespace, "fastcgi-helloserver", 9000, annotations) + f.EnsureIngress(ing) + + f.WaitForNginxServer(host, + func(server string) bool { + return strings.Contains(server, "fastcgi_index \"index.php\";") + }) + }) + + ginkgo.It("should add fastcgi_param in the configuration file", func() { + configuration := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fastcgi-configmap", + Namespace: f.Namespace, + }, + Data: map[string]string{ + "SCRIPT_FILENAME": "/home/www/scripts/php$fastcgi_script_name", + "REDIRECT_STATUS": "200", + }, + } + + f.EnsureConfigMap(configuration) + + host := "fastcgi-params-configmap" + + annotations := map[string]string{ + "nginx.ingress.kubernetes.io/backend-protocol": "FCGI", + "nginx.ingress.kubernetes.io/fastcgi-params-configmap": "fastcgi-configmap", + } + + ing := framework.NewSingleIngress(host, "/hello", host, f.Namespace, "fastcgi-helloserver", 9000, annotations) + f.EnsureIngress(ing) + + f.WaitForNginxServer(host, + func(server string) bool { + return strings.Contains(server, "fastcgi_param SCRIPT_FILENAME \"/home/www/scripts/php$fastcgi_script_name\";") && + strings.Contains(server, "fastcgi_param REDIRECT_STATUS \"200\";") + }) + }) + + ginkgo.It("should return OK for service with backend protocol FastCGI", func() { + host := "fastcgi-helloserver" + path := "/hello" + + annotations := map[string]string{ + "nginx.ingress.kubernetes.io/backend-protocol": "FCGI", + } + + ing := framework.NewSingleIngress(host, path, host, f.Namespace, "fastcgi-helloserver", 9000, annotations) + f.EnsureIngress(ing) + + f.WaitForNginxServer(host, + func(server string) bool { + return strings.Contains(server, "fastcgi_pass") + }) + + f.HTTPTestClient(). + GET(path). + WithHeader("Host", host). + Expect(). + Status(http.StatusOK). + Body().Contains("Hello world!") + }) +}) diff --git a/test/e2e/framework/fastcgi_helloserver.go b/test/e2e/framework/fastcgi_helloserver.go new file mode 100644 index 000000000..719048c06 --- /dev/null +++ b/test/e2e/framework/fastcgi_helloserver.go @@ -0,0 +1,104 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package framework + +import ( + "github.com/onsi/ginkgo/v2" + "github.com/stretchr/testify/assert" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/util/intstr" +) + +// NewFastCGIHelloServerDeployment creates a new single replica +// deployment of the fortune teller image in a particular namespace +func (f *Framework) NewFastCGIHelloServerDeployment() { + f.NewNewFastCGIHelloServerDeploymentWithReplicas(1) +} + +// NewNewFastCGIHelloServerDeploymentWithReplicas creates a new deployment of the +// fortune teller image in a particular namespace. Number of replicas is configurable +func (f *Framework) NewNewFastCGIHelloServerDeploymentWithReplicas(replicas int32) { + deployment := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fastcgi-helloserver", + Namespace: f.Namespace, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: NewInt32(replicas), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "fastcgi-helloserver", + }, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app": "fastcgi-helloserver", + }, + }, + Spec: corev1.PodSpec{ + TerminationGracePeriodSeconds: NewInt64(0), + Containers: []corev1.Container{ + { + Name: "fastcgi-helloserver", + Image: "registry.k8s.io/ingress-nginx/e2e-test-fastcgi-helloserver@sha256:0e08c836cc58f1ea862578de99b13bc4264fe071e816f96dc1d79857bfba7473", + Env: []corev1.EnvVar{}, + Ports: []corev1.ContainerPort{ + { + Name: "fastcgi", + ContainerPort: 9000, + }, + }, + }, + }, + }, + }, + }, + } + + d := f.EnsureDeployment(deployment) + + err := waitForPodsReady(f.KubeClientSet, DefaultTimeout, int(replicas), f.Namespace, metav1.ListOptions{ + LabelSelector: fields.SelectorFromSet(fields.Set(d.Spec.Template.ObjectMeta.Labels)).String(), + }) + assert.Nil(ginkgo.GinkgoT(), err, "failed to wait for to become ready") + + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fastcgi-helloserver", + Namespace: f.Namespace, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "fastcgi", + Port: 9000, + TargetPort: intstr.FromInt(9000), + Protocol: "TCP", + }, + }, + Selector: map[string]string{ + "app": "fastcgi-helloserver", + }, + }, + } + + f.EnsureService(service) +}