diff --git a/.github/dependency-review-config.yml b/.github/dependency-review-config.yml new file mode 100644 index 0000000000..ffa2fec090 --- /dev/null +++ b/.github/dependency-review-config.yml @@ -0,0 +1,13 @@ +allow_licenses: + - Apache-1.1 + - Apache-2.0 + - BSD-2-Clause + - BSD-3-Clause + - BSL-1.0 + - ISC + - MIT + - NCSA + - OpenSSL + - Python-2.0 + - X11 +comment-summary-in-pr: true diff --git a/.github/workflows/build-oss.yml b/.github/workflows/build-oss.yml index c1c83364f7..12cb9c75f8 100644 --- a/.github/workflows/build-oss.yml +++ b/.github/workflows/build-oss.yml @@ -66,7 +66,7 @@ jobs: if: ${{ github.event_name != 'pull_request' && ! startsWith(github.ref, 'refs/heads/release-') }} - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@5727f247b64f324ec403ac56ae05e220fd02b65f # v2.1.0 + uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 # v2.2.0 with: aws-region: us-east-1 role-to-assume: ${{ secrets.AWS_ROLE_PUBLIC_ECR }} diff --git a/.github/workflows/build-plus.yml b/.github/workflows/build-plus.yml index 12edf8660d..75f2b4d699 100644 --- a/.github/workflows/build-plus.yml +++ b/.github/workflows/build-plus.yml @@ -69,7 +69,7 @@ jobs: if: github.event_name != 'pull_request' - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@5727f247b64f324ec403ac56ae05e220fd02b65f # v2.1.0 + uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 # v2.2.0 with: aws-region: us-east-1 role-to-assume: ${{ secrets.AWS_ROLE_MARKETPLACE }} diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000000..f8b7a874ff --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,20 @@ +name: "Dependency Review" +on: [pull_request] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-22.04 + permissions: + contents: read + pull-requests: write + steps: + - name: "Checkout Repository" + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + + - name: "Dependency Review" + uses: actions/dependency-review-action@1360a344ccb0ab6e9475edef90ad2f46bf8003b1 # v3.0.6 + with: + config-file: "./.github/dependency-review-config.yml" diff --git a/.goreleaser.yml b/.goreleaser.yml index 8ecdbf5d60..eaac2c88bd 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -93,36 +93,38 @@ builds: tags: - aws -archives: - - id: kubernetes-ingress - builds: [kubernetes-ingress] - changelog: skip: true -checksum: - name_template: 'checksums.txt' +archives: + - id: kubernetes-ingress + builds: [kubernetes-ingress] sboms: - artifacts: archive ids: [kubernetes-ingress] + documents: + - "${artifact}.spdx.json" release: ids: [kubernetes-ingress] extra_files: - - glob: ./dist/**.sbom + - glob: ./dist/**.spdx.json blobs: - provider: azblob bucket: '{{.Env.AZURE_BUCKET_NAME}}' extra_files: - - glob: ./dist/**.sbom - -milestones: - - close: true + - glob: ./dist/**.spdx.json announce: slack: enabled: true channel: '#announcements' message_template: 'NGINX Ingress Controller {{ .Tag }} is out! Check it out: {{ .ReleaseURL }}' + +milestones: + - close: true + +snapshot: + name_template: 'edge' diff --git a/Makefile b/Makefile index b9b72a2dd7..393798dcdb 100644 --- a/Makefile +++ b/Makefile @@ -95,7 +95,7 @@ endif .PHONY: build-goreleaser build-goreleaser: ## Build Ingress Controller binary using GoReleaser @goreleaser -v || (code=$$?; printf "\033[0;31mError\033[0m: there was a problem with GoReleaser. Follow the docs to install it https://goreleaser.com/install\n"; exit $$code) - GOOS=linux GOPATH=$(shell go env GOPATH) GOARCH=$(ARCH) goreleaser build --rm-dist --debug --snapshot --id kubernetes-ingress --single-target + GOOS=linux GOPATH=$(shell go env GOPATH) GOARCH=$(ARCH) goreleaser build --clean --debug --snapshot --id kubernetes-ingress --single-target .PHONY: debian-image debian-image: build ## Create Docker image for Ingress Controller (Debian) diff --git a/build/Dockerfile b/build/Dockerfile index bc24cfe2f2..cb5e5f8fa9 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -10,7 +10,7 @@ FROM opentracing/nginx-opentracing:nginx-1.25.0-alpine as alpine-opentracing-lib ############################################# Base image for Debian ############################################# -FROM nginx:1.25.0 AS debian +FROM nginx:1.25.1 AS debian RUN --mount=type=bind,from=opentracing-lib,target=/tmp/ot/ \ apt-get update \ @@ -24,7 +24,7 @@ RUN --mount=type=bind,from=opentracing-lib,target=/tmp/ot/ \ ############################################# Base image for Alpine ############################################# -FROM nginx:1.25.0-alpine AS alpine +FROM nginx:1.25.1-alpine AS alpine RUN --mount=type=bind,from=alpine-opentracing-lib,target=/tmp/ot/ \ apk add --no-cache libcap libstdc++ \ @@ -110,7 +110,7 @@ RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode ############################################# Base image for UBI ############################################# -FROM nginxcontrib/nginx:1.25.0-ubi AS ubi +FROM nginxcontrib/nginx:1.25.1-ubi AS ubi ARG IC_VERSION LABEL name="NGINX Ingress Controller" \ diff --git a/deployments/common/crds/k8s.nginx.org_virtualserverroutes.yaml b/deployments/common/crds/k8s.nginx.org_virtualserverroutes.yaml index 4584c3e8dc..679df933c3 100644 --- a/deployments/common/crds/k8s.nginx.org_virtualserverroutes.yaml +++ b/deployments/common/crds/k8s.nginx.org_virtualserverroutes.yaml @@ -588,6 +588,8 @@ spec: type: string path: type: string + samesite: + type: string secure: type: boolean slow-start: diff --git a/deployments/common/crds/k8s.nginx.org_virtualservers.yaml b/deployments/common/crds/k8s.nginx.org_virtualservers.yaml index 8b06969a91..e15e804297 100644 --- a/deployments/common/crds/k8s.nginx.org_virtualservers.yaml +++ b/deployments/common/crds/k8s.nginx.org_virtualservers.yaml @@ -675,6 +675,8 @@ spec: type: string path: type: string + samesite: + type: string secure: type: boolean slow-start: diff --git a/deployments/helm-chart/crds/k8s.nginx.org_virtualserverroutes.yaml b/deployments/helm-chart/crds/k8s.nginx.org_virtualserverroutes.yaml index 4584c3e8dc..679df933c3 100644 --- a/deployments/helm-chart/crds/k8s.nginx.org_virtualserverroutes.yaml +++ b/deployments/helm-chart/crds/k8s.nginx.org_virtualserverroutes.yaml @@ -588,6 +588,8 @@ spec: type: string path: type: string + samesite: + type: string secure: type: boolean slow-start: diff --git a/deployments/helm-chart/crds/k8s.nginx.org_virtualservers.yaml b/deployments/helm-chart/crds/k8s.nginx.org_virtualservers.yaml index 8b06969a91..e15e804297 100644 --- a/deployments/helm-chart/crds/k8s.nginx.org_virtualservers.yaml +++ b/deployments/helm-chart/crds/k8s.nginx.org_virtualservers.yaml @@ -675,6 +675,8 @@ spec: type: string path: type: string + samesite: + type: string secure: type: boolean slow-start: diff --git a/docs/content/configuration/virtualserver-and-virtualserverroute-resources.md b/docs/content/configuration/virtualserver-and-virtualserverroute-resources.md index c5453dcdd0..0134d0b716 100644 --- a/docs/content/configuration/virtualserver-and-virtualserverroute-resources.md +++ b/docs/content/configuration/virtualserver-and-virtualserverroute-resources.md @@ -460,6 +460,7 @@ sessionCookie: domain: .example.com httpOnly: false secure: true + samesite: strict ``` See the [`sticky`](https://nginx.org/en/docs/http/ngx_http_upstream_module.html?#sticky) directive for additional information. The session cookie corresponds to the `sticky cookie` method. @@ -475,6 +476,7 @@ Note: This feature is supported only in NGINX Plus. |``domain`` | The domain for which the cookie is set. | ``string`` | No | |``httpOnly`` | Adds the ``HttpOnly`` attribute to the cookie. | ``boolean`` | No | |``secure`` | Adds the ``Secure`` attribute to the cookie. | ``boolean`` | No | +|``samesite`` | Adds the ``SameSite`` attribute to the cookie. The allowed values are: ``strict``, ``lax``, ``none`` | ``string`` | No | {{% /table %}} ### Header diff --git a/docs/content/f5-ingresslink.md b/docs/content/f5-ingresslink.md index 6ce5fc32ca..45f35b4115 100644 --- a/docs/content/f5-ingresslink.md +++ b/docs/content/f5-ingresslink.md @@ -2,7 +2,7 @@ title: Using with F5 BIG-IP description: | Learn how to use NGINX Ingress Controller with F5 IngressLink to configure your F5 BIG-IP device. -weight: 1800 +weight: 2000 doctypes: ["concept"] toc: true docs: "DOCS-600" diff --git a/docs/content/installation/installation-with-manifests.md b/docs/content/installation/installation-with-manifests.md index ec525b4728..49a1ea15c3 100644 --- a/docs/content/installation/installation-with-manifests.md +++ b/docs/content/installation/installation-with-manifests.md @@ -112,7 +112,7 @@ Create a custom resource definition for `APDosPolicy`, `APDosLogConf` and `DosPr $ kubectl apply -f common/crds/appprotectdos.f5.com_dosprotectedresources.yaml ``` -## 3. Deploy the Ingress Controller +## 4. Deploy the Ingress Controller We include two options for deploying the Ingress Controller: * *Deployment*. Use a Deployment if you plan to dynamically change the number of Ingress Controller replicas. @@ -132,7 +132,7 @@ If you would like to use the App Protect DoS module, you will need to deploy the $ kubectl apply -f service/appprotect-dos-arb-svc.yaml ``` -### 3.1 Run the Ingress Controller +### 4.1 Run the Ingress Controller * *Use a Deployment*. When you run the Ingress Controller by using a Deployment, by default, Kubernetes will create one Ingress Controller pod. @@ -165,20 +165,20 @@ If you would like to use the App Protect DoS module, you will need to deploy the **Note**: Update the `nginx-plus-ingress.yaml` with the chosen image from the F5 Container registry; or the container image that you have built. -### 3.2 Check that the Ingress Controller is Running +### 4.2 Check that the Ingress Controller is Running Run the following command to make sure that the Ingress Controller pods are running: ``` $ kubectl get pods --namespace=nginx-ingress ``` -## 4. Get Access to the Ingress Controller +## 5. Get Access to the Ingress Controller **If you created a daemonset**, ports 80 and 443 of the Ingress Controller container are mapped to the same ports of the node where the container is running. To access the Ingress Controller, use those ports and an IP address of any node of the cluster where the Ingress Controller is running. **If you created a deployment**, below are two options for accessing the Ingress Controller pods. -### 4.1 Create a Service for the Ingress Controller Pods +### 5.1 Create a Service for the Ingress Controller Pods * *Use a NodePort service*. diff --git a/docs/content/releases.md b/docs/content/releases.md index 14cc17eceb..35a2d48838 100644 --- a/docs/content/releases.md +++ b/docs/content/releases.md @@ -1,7 +1,7 @@ --- title: Releases description: "NGINX Ingress Controller Release Notes." -weight: 1900 +weight: 2100 doctypes: ["concept"] toc: true docs: "DOCS-616" diff --git a/docs/content/technical-specifications.md b/docs/content/technical-specifications.md index a7187b1208..186aaa8722 100644 --- a/docs/content/technical-specifications.md +++ b/docs/content/technical-specifications.md @@ -1,7 +1,7 @@ --- title: Technical Specifications description: "NGINX Ingress Controller Technical Specifications." -weight: 2000 +weight: 2200 doctypes: ["concept"] toc: true docs: "DOCS-617" diff --git a/docs/content/troubleshooting/_index.md b/docs/content/troubleshooting/_index.md index 64ce8a39e4..dd82dc52a7 100644 --- a/docs/content/troubleshooting/_index.md +++ b/docs/content/troubleshooting/_index.md @@ -1,7 +1,7 @@ --- title: Troubleshooting description: -weight: 2000 +weight: 1800 menu: docs: parent: NGINX Ingress Controller diff --git a/docs/content/tutorials/_index.md b/docs/content/tutorials/_index.md index 919c1dd02d..109c994dd1 100644 --- a/docs/content/tutorials/_index.md +++ b/docs/content/tutorials/_index.md @@ -1,7 +1,7 @@ --- title: Tutorials description: -weight: 2100 +weight: 1900 menu: docs: parent: NGINX Ingress Controller diff --git a/docs/go.mod b/docs/go.mod index 86007118d1..a21c7f7de5 100644 --- a/docs/go.mod +++ b/docs/go.mod @@ -2,4 +2,4 @@ module github.com/nginxinc/kubernetes-ingress/docs go 1.19 -require github.com/nginxinc/nginx-hugo-theme v0.28.0 +require github.com/nginxinc/nginx-hugo-theme v0.32.0 diff --git a/docs/go.sum b/docs/go.sum index 9946777919..22fe8644fd 100644 --- a/docs/go.sum +++ b/docs/go.sum @@ -1,2 +1,3 @@ github.com/nginxinc/nginx-hugo-theme v0.28.0 h1:RHHvBmFk2Uptk+efLPSIuBd2elc3IOZPElkJbkkpAHo= github.com/nginxinc/nginx-hugo-theme v0.28.0/go.mod h1:DPNgSS5QYxkjH/BfH4uPDiTfODqWJ50NKZdorguom8M= +github.com/nginxinc/nginx-hugo-theme v0.32.0/go.mod h1:DPNgSS5QYxkjH/BfH4uPDiTfODqWJ50NKZdorguom8M= diff --git a/go.mod b/go.mod index bcc130c901..7b6dee688d 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/nginxinc/kubernetes-ingress go 1.20 require ( - github.com/aws/aws-sdk-go-v2/config v1.18.22 - github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.10 + github.com/aws/aws-sdk-go-v2/config v1.18.26 + github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.13 github.com/cert-manager/cert-manager v1.12.1 github.com/go-chi/chi/v5 v5.0.8 github.com/golang-jwt/jwt/v4 v4.5.0 @@ -14,14 +14,14 @@ require ( github.com/nginxinc/nginx-plus-go-client v0.10.0 github.com/nginxinc/nginx-prometheus-exporter v0.11.0 github.com/nginxinc/nginx-service-mesh v1.7.0 - github.com/prometheus/client_golang v1.15.1 + github.com/prometheus/client_golang v1.16.0 github.com/spiffe/go-spiffe/v2 v2.1.6 github.com/stretchr/testify v1.8.4 golang.org/x/exp v0.0.0-20230105000112-eab7a2c85304 - k8s.io/api v0.27.2 - k8s.io/apimachinery v0.27.2 - k8s.io/client-go v0.27.2 - k8s.io/code-generator v0.27.2 + k8s.io/api v0.27.3 + k8s.io/apimachinery v0.27.3 + k8s.io/client-go v0.27.3 + k8s.io/code-generator v0.27.3 k8s.io/utils v0.0.0-20230505201702-9f6742963106 sigs.k8s.io/controller-tools v0.12.0 ) @@ -29,16 +29,16 @@ require ( require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect github.com/Microsoft/go-winio v0.6.0 // indirect - github.com/aws/aws-sdk-go-v2 v1.18.0 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.13.21 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.12.9 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.9 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.18.10 // indirect + github.com/aws/aws-sdk-go-v2 v1.18.1 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.13.25 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.12.11 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.11 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.19.1 // indirect github.com/aws/smithy-go v1.13.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect @@ -85,7 +85,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect + github.com/prometheus/procfs v0.10.1 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect diff --git a/go.sum b/go.sum index 46690ecbf2..de4bb18bc6 100644 --- a/go.sum +++ b/go.sum @@ -42,30 +42,30 @@ github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2y github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/aws/aws-sdk-go-v2 v1.18.0 h1:882kkTpSFhdgYRKVZ/VCgf7sd0ru57p2JCxz4/oN5RY= -github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2/config v1.18.22 h1:7vkUEmjjv+giht4wIROqLs+49VWmiQMMHSduxmoNKLU= -github.com/aws/aws-sdk-go-v2/config v1.18.22/go.mod h1:mN7Li1wxaPxSSy4Xkr6stFuinJGf3VZW3ZSNvO0q6sI= -github.com/aws/aws-sdk-go-v2/credentials v1.13.21 h1:VRiXnPEaaPeGeoFcXvMZOB5K/yfIXOYE3q97Kgb0zbU= -github.com/aws/aws-sdk-go-v2/credentials v1.13.21/go.mod h1:90Dk1lJoMyspa/EDUrldTxsPns0wn6+KpRKpdAWc0uA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 h1:jJPgroehGvjrde3XufFIJUZVK5A2L9a3KwSFgKy9n8w= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3/go.mod h1:4Q0UFP0YJf0NrsEuEYHpM9fTSEVnD16Z3uyEF7J9JGM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 h1:kG5eQilShqmJbv11XL1VpyDbaEJzWxd4zRiCG30GSn4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 h1:vFQlirhuM8lLlpI7imKOMsjdQLuN9CPi+k44F/OFVsk= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34 h1:gGLG7yKaXG02/jBlg210R7VgQIotiQntNhsCFejawx8= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34/go.mod h1:Etz2dj6UHYuw+Xw830KfzCfWGMzqvUTCjUj5b76GVDc= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 h1:0iKliEXAcCa2qVtRs7Ot5hItA2MsufrphbRFlz1Owxo= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw= -github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.10 h1:kOYx/2t72BOrfk54WgZPxPT3O4VnKn16FxEAWz9hvi4= -github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.10/go.mod h1:1vX7Mjdi4PqMgNJct61O10EF5BNJ+UvxTVaiZTX7oZM= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.9 h1:GAiaQWuQhQQui76KjuXeShmyXqECwQ0mGRMc/rwsL+c= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.9/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.9 h1:TraLwncRJkWqtIBVKI/UqBymq4+hL+3MzUOtUATuzkA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.9/go.mod h1:AFvkxc8xfBe8XA+5St5XIHHrQQtkxqrRincx4hmMHOk= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.10 h1:6UbNM/KJhMBfOI5+lpVcJ/8OA7cBSz0O6OX37SRKlSw= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.10/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8= +github.com/aws/aws-sdk-go-v2 v1.18.1 h1:+tefE750oAb7ZQGzla6bLkOwfcQCEtC5y2RqoqCeqKo= +github.com/aws/aws-sdk-go-v2 v1.18.1/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= +github.com/aws/aws-sdk-go-v2/config v1.18.26 h1:ivCHcSmKd1+9rBlqVsxZHB35eCW88KWbMdG2VL3BuBw= +github.com/aws/aws-sdk-go-v2/config v1.18.26/go.mod h1:NVmd//z/PNl7U+ZU2EnuffxOA060JWzgbH3BnqQrUoY= +github.com/aws/aws-sdk-go-v2/credentials v1.13.25 h1:5wROoMcUC7nAE66e0b3IIht6Tos76M4HC+GQw8MeqxU= +github.com/aws/aws-sdk-go-v2/credentials v1.13.25/go.mod h1:W9I2660WXSwZQ23mM1Ks72+UGeyirIxuU7/KzN7daeA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4 h1:LxK/bitrAr4lnh9LnIS6i7zWbCOdMsfzKFBI6LUCS0I= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4/go.mod h1:E1hLXN/BL2e6YizK1zFlYd8vsfi2GTjbjBazinMmeaM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34 h1:A5UqQEmPaCFpedKouS4v+dHCTUo2sKqhoKO9U5kxyWo= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34/go.mod h1:wZpTEecJe0Btj3IYnDx/VlUzor9wm3fJHyvLpQF0VwY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28 h1:srIVS45eQuewqz6fKKu6ZGXaq6FuFg5NzgQBAM6g8Y4= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28/go.mod h1:7VRpKQQedkfIEXb4k52I7swUnZP0wohVajJMRn3vsUw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35 h1:LWA+3kDM8ly001vJ1X1waCuLJdtTl48gwkPKWy9sosI= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35/go.mod h1:0Eg1YjxE0Bhn56lx+SHJwCzhW+2JGtizsrx+lCqrfm0= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28 h1:bkRyG4a929RCnpVSTvLM2j/T4ls015ZhhYApbmYs15s= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28/go.mod h1:jj7znCIg05jXlaGBlFMGP8+7UN3VtCkRBG2spnmRQkU= +github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.13 h1:INNEByjR77yjugBQPVXkDTleLf5AIvxUslT1N3MG2So= +github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.13/go.mod h1:xmV0oIDDFg4i3vNjRfwRAl1ZGpNsEj06gLYpFT+hwpI= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.11 h1:cNrMc266RsZJ8V1u1OQQONKcf9HmfxQFqgcpY7ZJBhY= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.11/go.mod h1:HuCOxYsF21eKrerARYO6HapNeh9GBNq7fius2AcwodY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.11 h1:h2VhtCE5PBiJefmlVCjJRSzBfFcQeAE10SXIGkXw1jQ= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.11/go.mod h1:E4VrHCPzmVB/KFXtqBGKb3c8zpbNBgKe3fisDNLAW5w= +github.com/aws/aws-sdk-go-v2/service/sts v1.19.1 h1:ehPTnLR/es8TL1fpBfq8qw9cAwOpQr47fLmZD9yhHjk= +github.com/aws/aws-sdk-go-v2/service/sts v1.19.1/go.mod h1:dp0yLPsLBOi++WTxzCjA/oZqi6NPIhoR+uF7GeMU9eg= github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= @@ -294,15 +294,15 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= -github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= @@ -707,18 +707,18 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.27.2 h1:+H17AJpUMvl+clT+BPnKf0E3ksMAzoBBg7CntpSuADo= -k8s.io/api v0.27.2/go.mod h1:ENmbocXfBT2ADujUXcBhHV55RIT31IIEvkntP6vZKS4= +k8s.io/api v0.27.3 h1:yR6oQXXnUEBWEWcvPWS0jQL575KoAboQPfJAuKNrw5Y= +k8s.io/api v0.27.3/go.mod h1:C4BNvZnQOF7JA/0Xed2S+aUyJSfTGkGFxLXz9MnpIpg= k8s.io/apiextensions-apiserver v0.27.2 h1:iwhyoeS4xj9Y7v8YExhUwbVuBhMr3Q4bd/laClBV6Bo= k8s.io/apiextensions-apiserver v0.27.2/go.mod h1:Oz9UdvGguL3ULgRdY9QMUzL2RZImotgxvGjdWRq6ZXQ= -k8s.io/apimachinery v0.27.2 h1:vBjGaKKieaIreI+oQwELalVG4d8f3YAMNpWLzDXkxeg= -k8s.io/apimachinery v0.27.2/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= +k8s.io/apimachinery v0.27.3 h1:Ubye8oBufD04l9QnNtW05idcOe9Z3GQN8+7PqmuVcUM= +k8s.io/apimachinery v0.27.3/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= k8s.io/apiserver v0.27.2 h1:p+tjwrcQEZDrEorCZV2/qE8osGTINPuS5ZNqWAvKm5E= k8s.io/apiserver v0.27.2/go.mod h1:EsOf39d75rMivgvvwjJ3OW/u9n1/BmUMK5otEOJrb1Y= -k8s.io/client-go v0.27.2 h1:vDLSeuYvCHKeoQRhCXjxXO45nHVv2Ip4Fe0MfioMrhE= -k8s.io/client-go v0.27.2/go.mod h1:tY0gVmUsHrAmjzHX9zs7eCjxcBsf8IiNe7KQ52biTcQ= -k8s.io/code-generator v0.27.2 h1:RmK0CnU5qRaK6WRtSyWNODmfTZNoJbrizpVcsgbtrvI= -k8s.io/code-generator v0.27.2/go.mod h1:DPung1sI5vBgn4AGKtlPRQAyagj/ir/4jI55ipZHVww= +k8s.io/client-go v0.27.3 h1:7dnEGHZEJld3lYwxvLl7WoehK6lAq7GvgjxpA3nv1E8= +k8s.io/client-go v0.27.3/go.mod h1:2MBEKuTo6V1lbKy3z1euEGnhPfGZLKTS9tiJ2xodM48= +k8s.io/code-generator v0.27.3 h1:JRhRQkzKdQhHmv9s5f7vuqveL8qukAQ2IqaHm6MFspM= +k8s.io/code-generator v0.27.3/go.mod h1:DPung1sI5vBgn4AGKtlPRQAyagj/ir/4jI55ipZHVww= k8s.io/component-base v0.27.2 h1:neju+7s/r5O4x4/txeUONNTS9r1HsPbyoPBAtHsDCpo= k8s.io/component-base v0.27.2/go.mod h1:5UPk7EjfgrfgRIuDBFtsEFAe4DAvP3U+M8RTzoSJkpo= k8s.io/gengo v0.0.0-20220902162205-c0856e24416d h1:U9tB195lKdzwqicbJvyJeOXV7Klv+wNAWENRnXEGi08= diff --git a/internal/configs/oidc/openid_connect.js b/internal/configs/oidc/openid_connect.js index 1ec212643a..68195a3f0a 100644 --- a/internal/configs/oidc/openid_connect.js +++ b/internal/configs/oidc/openid_connect.js @@ -5,7 +5,7 @@ */ var newSession = false; // Used by oidcAuth() and validateIdToken() -export default { auth, codeExchange, validateIdToken, logout }; +export default {auth, codeExchange, validateIdToken, logout}; function retryOriginalRequest(r) { delete r.headersOut["WWW-Authenticate"]; // Remove evidence of original failed auth_jwt @@ -55,7 +55,7 @@ function auth(r, afterSyncCheck) { // Pass the refresh token to the /_refresh location so that it can be // proxied to the IdP in exchange for a new id_token r.subrequest("/_refresh", "token=" + r.variables.refresh_token, - function (reply) { + function(reply) { if (reply.status != 200) { // Refresh request failed, log the reason var error_log = "OIDC refresh failure"; @@ -63,13 +63,13 @@ function auth(r, afterSyncCheck) { error_log += ", timeout waiting for IdP"; } else if (reply.status == 400) { try { - var errorset = JSON.parse(reply.responseBody); + var errorset = JSON.parse(reply.responseText); error_log += ": " + errorset.error + " " + errorset.error_description; } catch (e) { - error_log += ": " + reply.responseBody; + error_log += ": " + reply.responseText; } } else { - error_log += " " + reply.status; + error_log += " " + reply.status; } r.error(error_log); @@ -81,7 +81,7 @@ function auth(r, afterSyncCheck) { // Refresh request returned 200, check response try { - var tokenset = JSON.parse(reply.responseBody); + var tokenset = JSON.parse(reply.responseText); if (!tokenset.id_token) { r.error("OIDC refresh response did not include id_token"); if (tokenset.error) { @@ -94,7 +94,7 @@ function auth(r, afterSyncCheck) { // Send the new ID Token to auth_jwt location for validation r.subrequest("/_id_token_validation", "token=" + tokenset.id_token, - function (reply) { + function(reply) { if (reply.status != 204) { r.variables.refresh_token = "-"; r.return(302, r.variables.request_uri); @@ -103,7 +103,7 @@ function auth(r, afterSyncCheck) { // ID Token is valid, update keyval r.log("OIDC refresh success, updating id_token for " + r.variables.cookie_auth_token); - r.variables.session_jwt = tokenset.id_token; + r.variables.session_jwt = tokenset.id_token; // Update key-value store if (tokenset.access_token) { r.variables.access_token = tokenset.access_token; } else { @@ -142,70 +142,70 @@ function codeExchange(r) { // Pass the authorization code to the /_token location so that it can be // proxied to the IdP in exchange for a JWT - r.subrequest("/_token", idpClientAuth(r), function (reply) { - if (reply.status == 504) { - r.error("OIDC timeout connecting to IdP when sending authorization code"); - r.return(504); - return; - } - - if (reply.status != 200) { - try { - var errorset = JSON.parse(reply.responseBody); - if (errorset.error) { - r.error("OIDC error from IdP when sending authorization code: " + errorset.error + ", " + errorset.error_description); - } else { - r.error("OIDC unexpected response from IdP when sending authorization code (HTTP " + reply.status + "). " + reply.responseBody); - } - } catch (e) { - r.error("OIDC unexpected response from IdP when sending authorization code (HTTP " + reply.status + "). " + reply.responseBody); - } - r.return(502); - return; - } - - // Code exchange returned 200, check for errors - try { - var tokenset = JSON.parse(reply.responseBody); - if (tokenset.error) { - r.error("OIDC " + tokenset.error + " " + tokenset.error_description); - r.return(500); + r.subrequest("/_token",idpClientAuth(r), function(reply) { + if (reply.status == 504) { + r.error("OIDC timeout connecting to IdP when sending authorization code"); + r.return(504); return; } - // Send the ID Token to auth_jwt location for validation - r.subrequest("/_id_token_validation", "token=" + tokenset.id_token, - function (reply) { - if (reply.status != 204) { - r.return(500); // validateIdToken() will log errors - return; - } - - // If the response includes a refresh token then store it - if (tokenset.refresh_token) { - r.variables.new_refresh = tokenset.refresh_token; // Create key-value store entry - r.log("OIDC refresh token stored"); + if (reply.status != 200) { + try { + var errorset = JSON.parse(reply.responseText); + if (errorset.error) { + r.error("OIDC error from IdP when sending authorization code: " + errorset.error + ", " + errorset.error_description); } else { - r.warn("OIDC no refresh token"); + r.error("OIDC unexpected response from IdP when sending authorization code (HTTP " + reply.status + "). " + reply.responseText); } + } catch (e) { + r.error("OIDC unexpected response from IdP when sending authorization code (HTTP " + reply.status + "). " + reply.responseText); + } + r.return(502); + return; + } - // Add opaque token to keyval session store - r.log("OIDC success, creating session " + r.variables.request_id); - r.variables.new_session = tokenset.id_token; // Create key-value store entry - if (tokenset.access_token) { - r.variables.new_access_token = tokenset.access_token; - } else { - r.variables.new_access_token = ""; - } - r.headersOut["Set-Cookie"] = "auth_token=" + r.variables.request_id + "; " + r.variables.oidc_cookie_flags; - r.return(302, r.variables.redirect_base + r.variables.cookie_auth_redir); + // Code exchange returned 200, check for errors + try { + var tokenset = JSON.parse(reply.responseText); + if (tokenset.error) { + r.error("OIDC " + tokenset.error + " " + tokenset.error_description); + r.return(500); + return; } - ); - } catch (e) { - r.error("OIDC authorization code sent but token response is not JSON. " + reply.responseBody); - r.return(502); + + // Send the ID Token to auth_jwt location for validation + r.subrequest("/_id_token_validation", "token=" + tokenset.id_token, + function(reply) { + if (reply.status != 204) { + r.return(500); // validateIdToken() will log errors + return; + } + + // If the response includes a refresh token then store it + if (tokenset.refresh_token) { + r.variables.new_refresh = tokenset.refresh_token; // Create key-value store entry + r.log("OIDC refresh token stored"); + } else { + r.warn("OIDC no refresh token"); + } + + // Add opaque token to keyval session store + r.log("OIDC success, creating session " + r.variables.request_id); + r.variables.new_session = tokenset.id_token; // Create key-value store entry + if (tokenset.access_token) { + r.variables.new_access_token = tokenset.access_token; + } else { + r.variables.new_access_token = ""; + } + r.headersOut["Set-Cookie"] = "auth_token=" + r.variables.request_id + "; " + r.variables.oidc_cookie_flags; + r.return(302, r.variables.redirect_base + r.variables.cookie_auth_redir); + } + ); + } catch (e) { + r.error("OIDC authorization code sent but token response is not JSON. " + reply.responseText); + r.return(502); + } } - } ); } @@ -214,7 +214,7 @@ function validateIdToken(r) { var required_claims = ["iat", "iss", "sub"]; // aud is checked separately var missing_claims = []; for (var i in required_claims) { - if (r.variables["jwt_claim_" + required_claims[i]].length == 0) { + if (r.variables["jwt_claim_" + required_claims[i]].length == 0 ) { missing_claims.push(required_claims[i]); } } @@ -265,8 +265,8 @@ function validateIdToken(r) { function logout(r) { r.log("OIDC logout for " + r.variables.cookie_auth_token); - r.variables.session_jwt = "-"; - r.variables.access_token = "-"; + r.variables.session_jwt = "-"; + r.variables.access_token = "-"; r.variables.refresh_token = "-"; r.return(302, r.variables.oidc_logout_redirect); } @@ -277,7 +277,7 @@ function getAuthZArgs(r) { var c = require('crypto'); var h = c.createHmac('sha256', r.variables.oidc_hmac_key).update(noncePlain); var nonceHash = h.digest('base64url'); - var authZArgs = "?response_type=code&scope=" + r.variables.oidc_scopes + "&client_id=" + r.variables.oidc_client + "&redirect_uri=" + r.variables.redirect_base + r.variables.redir_location + "&nonce=" + nonceHash; + var authZArgs = "?response_type=code&scope=" + r.variables.oidc_scopes + "&client_id=" + r.variables.oidc_client + "&redirect_uri="+ r.variables.redirect_base + r.variables.redir_location + "&nonce=" + nonceHash; if (r.variables.oidc_authz_extra_args) { authZArgs += "&" + r.variables.oidc_authz_extra_args; @@ -288,7 +288,7 @@ function getAuthZArgs(r) { "auth_nonce=" + noncePlain + "; " + r.variables.oidc_cookie_flags ]; - if (r.variables.oidc_pkce_enable == 1) { + if ( r.variables.oidc_pkce_enable == 1 ) { var pkce_code_verifier = c.createHmac('sha256', r.variables.oidc_hmac_key).update(String(Math.random())).digest('hex'); r.variables.pkce_id = c.createHash('sha256').update(String(Math.random())).digest('base64url'); var pkce_code_challenge = c.createHash('sha256').update(pkce_code_verifier).digest('base64url'); @@ -303,7 +303,7 @@ function getAuthZArgs(r) { function idpClientAuth(r) { // If PKCE is enabled we have to use the code_verifier - if (r.variables.oidc_pkce_enable == 1) { + if ( r.variables.oidc_pkce_enable == 1 ) { r.variables.pkce_id = r.variables.arg_state; return "code=" + r.variables.arg_code + "&code_verifier=" + r.variables.pkce_code_verifier; } else { diff --git a/internal/configs/version2/http.go b/internal/configs/version2/http.go index 2a8e5c934d..daa83f9dd9 100644 --- a/internal/configs/version2/http.go +++ b/internal/configs/version2/http.go @@ -283,6 +283,7 @@ type SessionCookie struct { Domain string HTTPOnly bool Secure bool + SameSite string } // Distribution maps weight to a value in a SplitClient. diff --git a/internal/configs/version2/nginx-plus.virtualserver.tmpl b/internal/configs/version2/nginx-plus.virtualserver.tmpl index dc2ebf1b81..c4c4b0e1f3 100644 --- a/internal/configs/version2/nginx-plus.virtualserver.tmpl +++ b/internal/configs/version2/nginx-plus.virtualserver.tmpl @@ -18,7 +18,7 @@ upstream {{ $u.Name }} { {{ with $u.SessionCookie }} {{ if .Enable }} - sticky cookie {{ .Name }}{{ if .Expires }} expires={{ .Expires }}{{ end }}{{ if .Domain }} domain={{ .Domain }}{{ end }}{{ if .HTTPOnly }} httponly{{ end }}{{ if .Secure }} secure{{ end }}{{ if .Path }} path={{ .Path }}{{ end }}; + sticky cookie {{ .Name }}{{ if .Expires }} expires={{ .Expires }}{{ end }}{{ if .Domain }} domain={{ .Domain }}{{ end }}{{ if .HTTPOnly }} httponly{{ end }}{{ if .SameSite}} samesite={{.SameSite | toLower }}{{ end }}{{ if .Secure }} secure{{ end }}{{ if .Path }} path={{ .Path }}{{ end }}; {{ end }} {{ end }} diff --git a/internal/configs/version2/template_helper.go b/internal/configs/version2/template_helper.go index 35c31a4f71..86767fa6ae 100644 --- a/internal/configs/version2/template_helper.go +++ b/internal/configs/version2/template_helper.go @@ -20,7 +20,17 @@ func hasCIKey(key string, d map[string]string) bool { return ok } +// toLower takes a string and make it lowercase. +// +// Example: +// +// {{ if .SameSite}} samesite={{.SameSite | toLower }}{{ end }} +func toLower(s string) string { + return strings.ToLower(s) +} + var helperFunctions = template.FuncMap{ "headerListToCIMap": headerListToCIMap, "hasCIKey": hasCIKey, + "toLower": toLower, } diff --git a/internal/configs/version2/templates_test.go b/internal/configs/version2/templates_test.go index f56315880d..4e5fca873a 100644 --- a/internal/configs/version2/templates_test.go +++ b/internal/configs/version2/templates_test.go @@ -77,6 +77,22 @@ func TestExecuteVirtualServerTemplate_RendersTemplateWithServerGunzipNotSet(t *t t.Log(string(got)) } +func TestExecuteVirtualServerTemplate_RendersTemplateWithSessionCookieSameSite(t *testing.T) { + t.Parallel() + executor, err := NewTemplateExecutor(nginxPlusVirtualServerTmpl, nginxPlusTransportServerTmpl) + if err != nil { + t.Fatal(err) + } + got, err := executor.ExecuteVirtualServerTemplate(&virtualServerCfgWithSessionCookieSameSite) + if err != nil { + t.Error(err) + } + if !bytes.Contains(got, []byte("samesite=strict")) { + t.Error("want `samesite=strict` in generated template") + } + t.Log(string(got)) +} + func TestVirtualServerForNginxPlusWithWAFApBundle(t *testing.T) { t.Parallel() executor, err := NewTemplateExecutor(nginxPlusVirtualServerTmpl, nginxPlusTransportServerTmpl) @@ -1890,6 +1906,358 @@ var ( }, } + virtualServerCfgWithSessionCookieSameSite = VirtualServerConfig{ + LimitReqZones: []LimitReqZone{ + { + ZoneName: "pol_rl_test_test_test", Rate: "10r/s", ZoneSize: "10m", Key: "$url", + }, + }, + Upstreams: []Upstream{ + { + Name: "test-upstream", + Servers: []UpstreamServer{ + { + Address: "10.0.0.20:8001", + }, + }, + LBMethod: "random", + Keepalive: 32, + MaxFails: 4, + FailTimeout: "10s", + MaxConns: 31, + SlowStart: "10s", + UpstreamZoneSize: "256k", + Queue: &Queue{Size: 10, Timeout: "60s"}, + // SessionCookie set for test: + SessionCookie: &SessionCookie{ + Enable: true, + Name: "test", + Path: "/tea", + Expires: "25s", + SameSite: "STRICT", + }, + NTLM: true, + }, + { + Name: "coffee-v1", + Servers: []UpstreamServer{ + { + Address: "10.0.0.31:8001", + }, + }, + MaxFails: 8, + FailTimeout: "15s", + MaxConns: 2, + UpstreamZoneSize: "256k", + }, + { + Name: "coffee-v2", + Servers: []UpstreamServer{ + { + Address: "10.0.0.32:8001", + }, + }, + MaxFails: 12, + FailTimeout: "20s", + MaxConns: 4, + UpstreamZoneSize: "256k", + }, + }, + SplitClients: []SplitClient{ + { + Source: "$request_id", + Variable: "$split_0", + Distributions: []Distribution{ + { + Weight: "50%", + Value: "@loc0", + }, + { + Weight: "50%", + Value: "@loc1", + }, + }, + }, + }, + Maps: []Map{ + { + Source: "$match_0_0", + Variable: "$match", + Parameters: []Parameter{ + { + Value: "~^1", + Result: "@match_loc_0", + }, + { + Value: "default", + Result: "@match_loc_default", + }, + }, + }, + { + Source: "$http_x_version", + Variable: "$match_0_0", + Parameters: []Parameter{ + { + Value: "v2", + Result: "1", + }, + { + Value: "default", + Result: "0", + }, + }, + }, + }, + HTTPSnippets: []string{"# HTTP snippet"}, + Server: Server{ + ServerName: "example.com", + StatusZone: "example.com", + ProxyProtocol: true, + SSL: &SSL{ + HTTP2: true, + Certificate: "cafe-secret.pem", + CertificateKey: "cafe-secret.pem", + }, + TLSRedirect: &TLSRedirect{ + BasedOn: "$scheme", + Code: 301, + }, + ServerTokens: "off", + SetRealIPFrom: []string{"0.0.0.0/0"}, + RealIPHeader: "X-Real-IP", + RealIPRecursive: true, + Allow: []string{"127.0.0.1"}, + Deny: []string{"127.0.0.1"}, + LimitReqs: []LimitReq{ + { + ZoneName: "pol_rl_test_test_test", + Delay: 10, + Burst: 5, + }, + }, + LimitReqOptions: LimitReqOptions{ + LogLevel: "error", + RejectCode: 503, + }, + JWTAuth: &JWTAuth{ + Realm: "My Api", + Secret: "jwk-secret", + }, + IngressMTLS: &IngressMTLS{ + ClientCert: "ingress-mtls-secret", + VerifyClient: "on", + VerifyDepth: 2, + }, + WAF: &WAF{ + ApPolicy: "/etc/nginx/waf/nac-policies/default-dataguard-alarm", + ApSecurityLogEnable: true, + ApLogConf: []string{"/etc/nginx/waf/nac-logconfs/default-logconf"}, + }, + Snippets: []string{"# server snippet"}, + InternalRedirectLocations: []InternalRedirectLocation{ + { + Path: "/split", + Destination: "@split_0", + }, + { + Path: "/coffee", + Destination: "@match", + }, + }, + HealthChecks: []HealthCheck{ + { + Name: "coffee", + URI: "/", + Interval: "5s", + Jitter: "0s", + Fails: 1, + Passes: 1, + Port: 50, + ProxyPass: "http://coffee-v2", + Mandatory: true, + Persistent: true, + }, + { + Name: "tea", + Interval: "5s", + Jitter: "0s", + Fails: 1, + Passes: 1, + Port: 50, + ProxyPass: "http://tea-v2", + GRPCPass: "grpc://tea-v3", + GRPCStatus: createPointerFromInt(12), + GRPCService: "tea-servicev2", + }, + }, + Locations: []Location{ + { + Path: "/", + Snippets: []string{"# location snippet"}, + Allow: []string{"127.0.0.1"}, + Deny: []string{"127.0.0.1"}, + LimitReqs: []LimitReq{ + { + ZoneName: "loc_pol_rl_test_test_test", + }, + }, + ProxyConnectTimeout: "30s", + ProxyReadTimeout: "31s", + ProxySendTimeout: "32s", + ClientMaxBodySize: "1m", + ProxyBuffering: true, + ProxyBuffers: "8 4k", + ProxyBufferSize: "4k", + ProxyMaxTempFileSize: "1024m", + ProxyPass: "http://test-upstream", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "5s", + Internal: true, + ProxyPassRequestHeaders: false, + ProxyPassHeaders: []string{"Host"}, + ProxyPassRewrite: "$request_uri", + ProxyHideHeaders: []string{"Header"}, + ProxyIgnoreHeaders: "Cache", + Rewrites: []string{"$request_uri $request_uri", "$request_uri $request_uri"}, + AddHeaders: []AddHeader{ + { + Header: Header{ + Name: "Header-Name", + Value: "Header Value", + }, + Always: true, + }, + }, + EgressMTLS: &EgressMTLS{ + Certificate: "egress-mtls-secret.pem", + CertificateKey: "egress-mtls-secret.pem", + VerifyServer: true, + VerifyDepth: 1, + Ciphers: "DEFAULT", + Protocols: "TLSv1.3", + TrustedCert: "trusted-cert.pem", + SessionReuse: true, + ServerName: true, + }, + }, + { + Path: "@loc0", + ProxyConnectTimeout: "30s", + ProxyReadTimeout: "31s", + ProxySendTimeout: "32s", + ClientMaxBodySize: "1m", + ProxyPass: "http://coffee-v1", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "5s", + ProxyInterceptErrors: true, + ErrorPages: []ErrorPage{ + { + Name: "@error_page_1", + Codes: "400 500", + ResponseCode: 200, + }, + { + Name: "@error_page_2", + Codes: "500", + ResponseCode: 0, + }, + }, + }, + { + Path: "@loc1", + ProxyConnectTimeout: "30s", + ProxyReadTimeout: "31s", + ProxySendTimeout: "32s", + ClientMaxBodySize: "1m", + ProxyPass: "http://coffee-v2", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "5s", + }, + { + Path: "@loc2", + ProxyConnectTimeout: "30s", + ProxyReadTimeout: "31s", + ProxySendTimeout: "32s", + ClientMaxBodySize: "1m", + ProxyPass: "http://coffee-v2", + GRPCPass: "grpc://coffee-v3", + }, + { + Path: "@match_loc_0", + ProxyConnectTimeout: "30s", + ProxyReadTimeout: "31s", + ProxySendTimeout: "32s", + ClientMaxBodySize: "1m", + ProxyPass: "http://coffee-v2", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "5s", + }, + { + Path: "@match_loc_default", + ProxyConnectTimeout: "30s", + ProxyReadTimeout: "31s", + ProxySendTimeout: "32s", + ClientMaxBodySize: "1m", + ProxyPass: "http://coffee-v1", + ProxyNextUpstream: "error timeout", + ProxyNextUpstreamTimeout: "5s", + }, + { + Path: "/return", + ProxyInterceptErrors: true, + ErrorPages: []ErrorPage{ + { + Name: "@return_0", + Codes: "418", + ResponseCode: 200, + }, + }, + InternalProxyPass: "http://unix:/var/lib/nginx/nginx-418-server.sock", + }, + }, + ErrorPageLocations: []ErrorPageLocation{ + { + Name: "@vs_cafe_cafe_vsr_tea_tea_tea__tea_error_page_0", + DefaultType: "application/json", + Return: &Return{ + Code: 200, + Text: "Hello World", + }, + Headers: nil, + }, + { + Name: "@vs_cafe_cafe_vsr_tea_tea_tea__tea_error_page_1", + DefaultType: "", + Return: &Return{ + Code: 200, + Text: "Hello World", + }, + Headers: []Header{ + { + Name: "Set-Cookie", + Value: "cookie1=test", + }, + { + Name: "Set-Cookie", + Value: "cookie2=test; Secure", + }, + }, + }, + }, + ReturnLocations: []ReturnLocation{ + { + Name: "@return_0", + DefaultType: "text/html", + Return: Return{ + Code: 200, + Text: "Hello!", + }, + }, + }, + }, + } + transportServerCfg = TransportServerConfig{ Upstreams: []StreamUpstream{ { diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index c399b910d1..a01fe24eaa 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -1537,6 +1537,7 @@ func generateSessionCookie(sc *conf_v1.SessionCookie) *version2.SessionCookie { Domain: sc.Domain, HTTPOnly: sc.HTTPOnly, Secure: sc.Secure, + SameSite: sc.SameSite, } } diff --git a/internal/configs/virtualserver_test.go b/internal/configs/virtualserver_test.go index 2e40ed2385..8bafb39849 100644 --- a/internal/configs/virtualserver_test.go +++ b/internal/configs/virtualserver_test.go @@ -9308,12 +9308,19 @@ func TestGenerateSessionCookie(t *testing.T) { expected: nil, msg: "session cookie not enabled", }, + { + sc: &conf_v1.SessionCookie{Enable: true, Name: "testcookie", SameSite: "lax"}, + expected: &version2.SessionCookie{Enable: true, Name: "testcookie", SameSite: "lax"}, + msg: "session cookie with samesite param", + }, } for _, test := range tests { - result := generateSessionCookie(test.sc) - if !reflect.DeepEqual(result, test.expected) { - t.Errorf("generateSessionCookie() returned %v, but expected %v for the case of: %v", result, test.expected, test.msg) - } + t.Run(test.msg, func(t *testing.T) { + result := generateSessionCookie(test.sc) + if !cmp.Equal(test.expected, result) { + t.Error(cmp.Diff(test.expected, result)) + } + }) } } diff --git a/internal/metrics/collectors/latency_test.go b/internal/metrics/collectors/latency_test.go index 5780a50353..201c3caf68 100644 --- a/internal/metrics/collectors/latency_test.go +++ b/internal/metrics/collectors/latency_test.go @@ -71,7 +71,7 @@ func TestParseMessageWithValidInputs(t *testing.T) { } } else { if err != nil { - t.Errorf("parseMessage returned an unexpected error: %v", err) + t.Fatalf("parseMessage returned an unexpected error: %v", err) } if actual != test.expected { t.Errorf("parseMessage returned: %+v, expected: %+v", actual, test.expected) @@ -112,7 +112,7 @@ func TestCreateLatencyLabelValuesWithCorrectNumberOfLabels(t *testing.T) { expected := []string{"upstream-1", "10.0.0.1", "200", "service-1", "ingress", "ingress-1", "default", "pod-1"} actual, err := collector.createLatencyLabelValues(lm) if err != nil { - t.Errorf("createLatencyLabelValues returned unexpected error: %v", err) + t.Fatalf("createLatencyLabelValues returned unexpected error: %v", err) } if !reflect.DeepEqual(expected, actual) { t.Errorf("createLatencyLabelValues returned: %v, expected: %v", actual, expected) @@ -184,7 +184,7 @@ func TestMetricsPublished(t *testing.T) { // verify metrics for upstream-1 are correct upstream1Metrics, ok := collector.metricsPublishedMap["upstream-1/10.0.0.0:80"] if !ok { - t.Errorf("updateMetricsPublished did not add upstream-1 as key to map") + t.Fatal("updateMetricsPublished did not add upstream-1 as key to map") } if l := len(upstream1Metrics); l != 2 { t.Errorf("updateMetricsPublished did not update upstream-1 map correctly, length is %d expected 2", l) @@ -208,7 +208,7 @@ func TestMetricsPublished(t *testing.T) { // verify metrics for upstream-2 are correct upstream2Metrics, ok := collector.metricsPublishedMap["upstream-2/10.0.0.0:80"] if !ok { - t.Errorf("updateMetricsPublished did not add upstream-2 as key to map") + t.Fatal("updateMetricsPublished did not add upstream-2 as key to map") } if l := len(upstream2Metrics); l != 1 { t.Errorf("updateMetricsPublished did not update upstream-2 map correctly, length is %d expected 1", l) diff --git a/pkg/apis/configuration/v1/types.go b/pkg/apis/configuration/v1/types.go index adc519df13..36b857e2d7 100644 --- a/pkg/apis/configuration/v1/types.go +++ b/pkg/apis/configuration/v1/types.go @@ -162,6 +162,7 @@ type SessionCookie struct { Domain string `json:"domain"` HTTPOnly bool `json:"httpOnly"` Secure bool `json:"secure"` + SameSite string `json:"samesite"` } // Route defines a route. diff --git a/pkg/apis/configuration/validation/virtualserver.go b/pkg/apis/configuration/validation/virtualserver.go index 434c49223c..a5f6bb5291 100644 --- a/pkg/apis/configuration/validation/virtualserver.go +++ b/pkg/apis/configuration/validation/virtualserver.go @@ -368,6 +368,9 @@ func validateGrpcStatus(i *int, fieldPath *field.Path) field.ErrorList { return allErrs } +// validateSessionCookie implements validation rules for session cookies. +// +// [Ref.]: https://nginx.org/en/docs/http/ngx_http_upstream_module.html#sticky func validateSessionCookie(sc *v1.SessionCookie, fieldPath *field.Path) field.ErrorList { if sc == nil { return nil @@ -398,6 +401,13 @@ func validateSessionCookie(sc *v1.SessionCookie, fieldPath *field.Path) field.Er } } + if sc.SameSite != "" { + switch strings.ToLower(sc.SameSite) { + case "strict", "lax", "none": + default: + allErrs = append(allErrs, field.Invalid(fieldPath.Child("samesite"), sc.SameSite, "must be one of: `strict`, `lax`, `none`")) + } + } return allErrs } diff --git a/pkg/apis/configuration/validation/virtualserver_test.go b/pkg/apis/configuration/validation/virtualserver_test.go index a694fa76d2..60e7470706 100644 --- a/pkg/apis/configuration/validation/virtualserver_test.go +++ b/pkg/apis/configuration/validation/virtualserver_test.go @@ -1823,19 +1823,8 @@ func TestValidateConditionFails(t *testing.T) { } } -func TestIsCookieName(t *testing.T) { +func TestIsCookieName_ErrorsOnInvalidInput(t *testing.T) { t.Parallel() - validCookieNames := []string{ - "123", - "my_cookie", - } - - for _, name := range validCookieNames { - errs := isCookieName(name) - if len(errs) > 0 { - t.Errorf("isCookieName(%q) returned errors %v for valid input", name, errs) - } - } invalidCookieNames := []string{ "", @@ -1851,6 +1840,22 @@ func TestIsCookieName(t *testing.T) { } } +func TestIsCookieName_IsValidOnValidInput(t *testing.T) { + t.Parallel() + + validCookieNames := []string{ + "123", + "my_cookie", + } + + for _, name := range validCookieNames { + errs := isCookieName(name) + if len(errs) > 0 { + t.Errorf("isCookieName(%q) returned errors %v for valid input", name, errs) + } + } +} + func TestIsArgumentName(t *testing.T) { t.Parallel() validArgumentNames := []string{ @@ -2959,7 +2964,6 @@ func TestValidateSessionCookie(t *testing.T) { sc: &v1.SessionCookie{ Enable: true, Name: "test", Path: "/tea", Expires: "1", Domain: ".example.com", HTTPOnly: false, Secure: true, }, - msg: "max valid config", }, } @@ -2971,7 +2975,30 @@ func TestValidateSessionCookie(t *testing.T) { } } -func TestValidateSessionCookieFails(t *testing.T) { +func TestValidateSessionCookie_IsValidOnValidSameSiteInput(t *testing.T) { + t.Parallel() + + samesites := []string{ + "strict", + "Strict", + "STRICT", + "lax", + "Lax", + "LAX", + "none", + "None", + "NONE", + } + for _, samesite := range samesites { + sc := &v1.SessionCookie{Enable: true, Name: "ValidCookie", SameSite: samesite} + allErr := validateSessionCookie(sc, field.NewPath("sessionCookie")) + if len(allErr) != 0 { + t.Errorf("validateSessionCookie() returned errors for valid input: %s", samesite) + } + } +} + +func TestValidateSessionCookie_FailsOnInvalidInput(t *testing.T) { t.Parallel() tests := []struct { sc *v1.SessionCookie @@ -2997,12 +3024,18 @@ func TestValidateSessionCookieFails(t *testing.T) { sc: &v1.SessionCookie{Enable: true, Name: "test", Path: "/ coffee"}, msg: "invalid path format", }, + { + sc: &v1.SessionCookie{Enable: true, Name: "ValidCookie", SameSite: "bogus_value"}, + msg: "invalid samesite value", + }, } for _, test := range tests { - allErrs := validateSessionCookie(test.sc, field.NewPath("sessionCookie")) - if len(allErrs) == 0 { - t.Errorf("validateSessionCookie() returned no errors for invalid input for the case of: %v", test.msg) - } + t.Run(test.msg, func(t *testing.T) { + allErrs := validateSessionCookie(test.sc, field.NewPath("sessionCookie")) + if len(allErrs) == 0 { + t.Errorf("validateSessionCookie() did not return errors for invalid input for the case of: %s", test.msg) + } + }) } } diff --git a/tests/docker/Dockerfile b/tests/docker/Dockerfile index 9c9d5bf40a..c6aa54afa5 100644 --- a/tests/docker/Dockerfile +++ b/tests/docker/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1.5 # this is here so we can grab the latest version of kind and have dependabot keep it up to date -FROM kindest/node:v1.27.2 +FROM kindest/node:v1.27.3 FROM python:3.11 diff --git a/tests/suite/test_virtual_server_upstream_options.py b/tests/suite/test_virtual_server_upstream_options.py index 491942fd3e..29049153a2 100644 --- a/tests/suite/test_virtual_server_upstream_options.py +++ b/tests/suite/test_virtual_server_upstream_options.py @@ -501,6 +501,7 @@ class TestOptionsSpecificForPlus: "domain": "virtual-server-route.example.com", "httpOnly": True, "secure": True, + "samesite": "strict", }, }, [ @@ -511,7 +512,65 @@ class TestOptionsSpecificForPlus: "slow_start=3h", "queue 100 timeout=60s;", "ntlm;", - "sticky cookie TestCookie expires=max domain=virtual-server-route.example.com httponly secure path=/some-valid/path;", + "sticky cookie TestCookie expires=max domain=virtual-server-route.example.com httponly samesite=strict secure path=/some-valid/path;", + ], + ), + ( + { + "lb-method": "least_conn", + "healthCheck": {"enable": True, "mandatory": True, "persistent": True}, + "slow-start": "3h", + "queue": {"size": 100}, + "ntlm": True, + "sessionCookie": { + "enable": True, + "name": "TestCookie", + "path": "/some-valid/path", + "expires": "max", + "domain": "virtual-server-route.example.com", + "httpOnly": True, + "secure": True, + "samesite": "lax", + }, + }, + [ + "health_check uri=/ interval=5s jitter=0s", + "fails=1 passes=1", + "mandatory persistent", + "keepalive_time=60s;", + "slow_start=3h", + "queue 100 timeout=60s;", + "ntlm;", + "sticky cookie TestCookie expires=max domain=virtual-server-route.example.com httponly samesite=lax secure path=/some-valid/path;", + ], + ), + ( + { + "lb-method": "least_conn", + "healthCheck": {"enable": True, "mandatory": True, "persistent": True}, + "slow-start": "3h", + "queue": {"size": 100}, + "ntlm": True, + "sessionCookie": { + "enable": True, + "name": "TestCookie", + "path": "/some-valid/path", + "expires": "max", + "domain": "virtual-server-route.example.com", + "httpOnly": True, + "secure": True, + "samesite": "none", + }, + }, + [ + "health_check uri=/ interval=5s jitter=0s", + "fails=1 passes=1", + "mandatory persistent", + "keepalive_time=60s;", + "slow_start=3h", + "queue 100 timeout=60s;", + "ntlm;", + "sticky cookie TestCookie expires=max domain=virtual-server-route.example.com httponly samesite=none secure path=/some-valid/path;", ], ), (