├── .gitattributes ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── build-and-sign-image.yml │ ├── run-scorecard.yml │ └── run-tests.yml ├── .gitignore ├── .tool-versions ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── ca-secret.yaml ├── charts └── nlk │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ ├── _helpers.tpl │ ├── clusterrole.yaml │ ├── clusterrolebinding.yaml │ ├── nlk-configmap.yaml │ ├── nlk-deployment.yaml │ ├── nlk-secret.yaml │ └── nlk-serviceaccount.yaml │ └── values.yaml ├── client-secret.yaml ├── cmd ├── certificates-test-harness │ ├── doc.go │ └── main.go ├── configuration-test-harness │ ├── doc.go │ └── main.go ├── nginx-loadbalancer-kubernetes │ ├── doc.go │ ├── main.go │ └── main_test.go └── tls-config-factory-test-harness │ ├── doc.go │ └── main.go ├── deployments ├── checks │ ├── sample-ingress-mod.yaml │ └── sample-ingress.yaml ├── deployment │ ├── configmap.yaml │ ├── deployment.yaml │ └── namespace.yaml └── rbac │ ├── apply.sh │ ├── clusterrole.yaml │ ├── clusterrolebinding.yaml │ ├── secret.yaml │ ├── serviceaccount.yaml │ └── unapply.sh ├── doc.go ├── docs ├── DEMO │ ├── COMMANDS.md │ ├── SLIDE-1.md │ ├── SLIDE-2.md │ ├── SLIDE-3.md │ ├── SLIDE-4.md │ ├── SLIDE-5.md │ ├── SLIDE-6.md │ ├── SLIDE-7.md │ └── SLIDE-8.md ├── DESIGN.md ├── README.md ├── cafe-demo │ ├── cafe-secret.yaml │ ├── cafe-virtualserver.yaml │ └── cafe.yaml ├── http │ ├── clusters.conf │ ├── dashboard.conf │ ├── grafana-dashboard.json │ ├── http-installation-guide.md │ ├── http-multicluster-overview.md │ ├── loadbalancer-cluster1.yaml │ ├── loadbalancer-cluster2.yaml │ ├── nginx.conf │ ├── nodeport-cluster1.yaml │ ├── nodeport-cluster2.yaml │ ├── prometheus.conf │ ├── prometheus.yml │ ├── single-cluster.conf │ └── zonesync.conf ├── media │ ├── cafe-dashboard.png │ ├── grafana-icon.png │ ├── kubernetes-icon.png │ ├── linux-icon.png │ ├── nginx-2020.png │ ├── nginx-icon.png │ ├── nginx-ingress-icon.png │ ├── nginx-logo.png │ ├── nginx-plus-icon.png │ ├── nginx-red-plus.png │ ├── nkl-blog-diagram-v2.png │ ├── nkl-desktop-background.png │ ├── nlk-banner.png │ ├── nlk-blog-diagram-v1.png │ ├── nlk-cluster1-add-loadbalancer.png │ ├── nlk-cluster1-add-nodeport.png │ ├── nlk-cluster1-delete-nodeport.png │ ├── nlk-cluster1-nodeport.png │ ├── nlk-cluster1-upstreams.png │ ├── nlk-clusters-10.png │ ├── nlk-clusters-50.png │ ├── nlk-configmap.png │ ├── nlk-desktop-background.png │ ├── nlk-grafana-reqs-10.png │ ├── nlk-grafana-reqs-50.png │ ├── nlk-grafana-reqs-90.png │ ├── nlk-grafana-resp.png │ ├── nlk-http-dashboard.png │ ├── nlk-keyval-split.png │ ├── nlk-logo-favicon.png │ ├── nlk-logo.png │ ├── nlk-multicluster-config.png │ ├── nlk-multicluster-diagram.png │ ├── nlk-multicluster-upstreams.png │ ├── nlk-stream-add-loadbalancer.png │ ├── nlk-stream-create-nodeport.png │ ├── nlk-stream-dashboard.png │ ├── nlk-stream-diagram.png │ ├── nlk-stream-logs-created.png │ ├── nlk-stream-logs-deleted.png │ ├── nlk-stream-no-nodeport.png │ ├── nlk-stream-nodeport.png │ ├── nlk-stream-upstreams.png │ ├── nlk-zone-sync.png │ ├── prometheus-icon.png │ ├── prometheus-upstreams.png │ └── robot.svg ├── tcp │ ├── dashboard.conf │ ├── default-tcp.conf │ ├── loadbalancer-nlk.yaml │ ├── nginx.conf │ ├── nginxk8slb.conf │ ├── nodeport-nlk.yaml │ ├── nodeport.yaml │ └── tcp-installation-guide.md └── tls │ ├── CA-MTLS.md │ ├── CA-TLS.md │ ├── CERTIFICATE-AUTHORITY.md │ ├── CLIENT-CERTIFICATE.md │ ├── DOCUMENT-HIERARCHY.md │ ├── FIFTY-THOUSAND-FOOT-TLDR.md │ ├── KUBERNETES-SECRETS.md │ ├── NGINX-PLUS-CONFIGURATION.md │ ├── NO-TLS.md │ ├── README.md │ ├── SERVER-CERTIFICATE.md │ ├── SS-MTLS.md │ └── SS-TLS.md ├── go.mod ├── go.sum ├── internal ├── application │ ├── application_common_test.go │ ├── application_constants.go │ ├── border_client.go │ ├── border_client_test.go │ ├── doc.go │ ├── nginx_client_interface.go │ ├── nginx_http_border_client.go │ ├── nginx_http_border_client_test.go │ ├── nginx_stream_border_client.go │ ├── nginx_stream_border_client_test.go │ ├── null_border_client.go │ └── null_border_client_test.go ├── authentication │ ├── doc.go │ ├── factory.go │ └── factory_test.go ├── certification │ ├── certificates.go │ ├── certificates_test.go │ └── doc.go ├── communication │ ├── doc.go │ ├── factory.go │ ├── factory_test.go │ ├── roundtripper.go │ └── roundtripper_test.go ├── configuration │ ├── doc.go │ ├── settings.go │ ├── tlsmodes.go │ └── tlsmodes_test.go ├── core │ ├── doc.go │ ├── event.go │ ├── event_test.go │ ├── secret_bytes.go │ ├── secret_bytes_test.go │ ├── server_update_event.go │ ├── server_update_event_test.go │ ├── upstream_server.go │ └── upstream_server_test.go ├── observation │ ├── doc.go │ ├── handler.go │ ├── handler_test.go │ ├── watcher.go │ └── watcher_test.go ├── probation │ ├── check.go │ ├── check_test.go │ ├── doc.go │ ├── server.go │ └── server_test.go ├── synchronization │ ├── doc.go │ ├── rand.go │ ├── synchronizer.go │ └── synchronizer_test.go └── translation │ ├── doc.go │ ├── translator.go │ └── translator_test.go ├── nlk-logo.svg ├── server-secret.yaml └── test └── mocks ├── mock_check.go ├── mock_handler.go ├── mock_nginx_plus_client.go ├── mock_ratelimitinginterface.go ├── mock_responsewriter.go └── mock_synchronizer.go /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | 3 | *.png binary -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Main global owner # 2 | ##################### 3 | * @ciroque @chrisakker 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | ### Describe the bug 9 | 10 | A clear and concise description of what the bug is. 11 | 12 | ### To reproduce 13 | 14 | Steps to reproduce the behavior: 15 | 16 | 1. Deploy nginx_loadbalancer_kubernetes using 17 | 2. View output/logs/configuration on '...' 18 | 3. See error 19 | 20 | ### Expected behavior 21 | 22 | A clear and concise description of what you expected to happen. 23 | 24 | ### Your environment 25 | 26 | - Version of the nginx_loadbalancer_kubernetes or specific commit 27 | 28 | - Target deployment platform 29 | 30 | ### Additional context 31 | 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | ### Is your feature request related to a problem? Please describe 9 | 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when ... 11 | 12 | ### Describe the solution you'd like 13 | 14 | A clear and concise description of what you want to happen. 15 | 16 | ### Describe alternatives you've considered 17 | 18 | A clear and concise description of any alternative solutions or features you've considered. 19 | 20 | ### Additional context 21 | 22 | Add any other context or screenshots about the feature request here. 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: github-actions 5 | directory: / 6 | schedule: 7 | interval: weekly 8 | day: monday 9 | time: "00:00" 10 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Proposed changes 2 | 3 | Describe the use case and detail of the change. If this PR addresses an issue on GitHub, make sure to include a link to that issue using one of the [supported keywords](https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue) here in this description (not in the title of the PR). 4 | 5 | ### Checklist 6 | 7 | Before creating a PR, run through this checklist and mark each as complete. 8 | 9 | - [ ] I have read the [`CONTRIBUTING`](https://github.com/nginxinc/nginx-loadbalancer-kubernetes/blob/main/CONTRIBUTING.md) document 10 | - [ ] If applicable, I have added tests that prove my fix is effective or that my feature works 11 | - [ ] If applicable, I have checked that any relevant tests pass after adding my changes 12 | - [ ] I have updated any relevant documentation ([`README.md`](https://github.com/nginxinc/nginx-loadbalancer-kubernetes/blob/main/README.md) and [`CHANGELOG.md`](https://github.com/nginxinc/nginx-loadbalancer-kubernetes/blob/main/CHANGELOG.md)) 13 | -------------------------------------------------------------------------------- /.github/workflows/build-and-sign-image.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build and push a signed Docker image 2 | 3 | name: Build and sign image 4 | 5 | on: 6 | push: 7 | tags: 8 | - "v[0-9]+.[0-9]+.[0-9]+" 9 | env: 10 | REGISTRY: ghcr.io 11 | IMAGE_NAME: ${{ github.repository }} 12 | 13 | jobs: 14 | build_and_sign_image: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: write 18 | packages: write 19 | id-token: write 20 | security-events: write 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | 26 | - name: Clear Sigstore cache 27 | run: rm -rf ~/.sigstore 28 | 29 | - uses: anchore/sbom-action@v0 30 | with: 31 | image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 32 | output-file: ./nginx-loadbalancer-kubernetes-${{env.GITHUB_REF_NAME}}.spdx.json 33 | registry-username: ${{ github.actor }} 34 | registry-password: ${{ secrets.GITHUB_TOKEN }} 35 | 36 | - name: Install cosign 37 | uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da #v3.7.0 38 | with: 39 | cosign-release: 'v2.4.1' 40 | 41 | - name: Log into registry ${{ env.REGISTRY }} for ${{ github.actor }} 42 | uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d 43 | with: 44 | registry: ${{ env.REGISTRY }} 45 | username: ${{ github.actor }} 46 | password: ${{ secrets.GITHUB_TOKEN }} 47 | 48 | - name: Extract metadata (tags, labels) for Docker 49 | id: meta 50 | uses: docker/metadata-action@9dc751fe249ad99385a2583ee0d084c400eee04e 51 | with: 52 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 53 | 54 | - name: Build Docker Image 55 | id: docker-build-and-push 56 | uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 57 | with: 58 | context: . 59 | file: ./Dockerfile 60 | push: true 61 | tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest,${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{github.run_number}} 62 | 63 | - name: Sign the published Docker images 64 | env: 65 | COSIGN_EXPERIMENTAL: "true" 66 | # This step uses the identity token to provision an ephemeral certificate 67 | # against the sigstore community Fulcio instance. 68 | run: cosign sign --yes "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.docker-build-and-push.outputs.digest }}" 69 | 70 | # NOTE: This runs statically against the latest tag in Docker Hub which was not produced by this workflow 71 | # This should be updated once this workflow is fully implemented 72 | - name: Run Trivy vulnerability scanner 73 | uses: aquasecurity/trivy-action@91713af97dc80187565512baba96e4364e983601 # 0.16.0 74 | continue-on-error: true 75 | with: 76 | image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest 77 | format: 'sarif' 78 | output: 'trivy-results-${{ inputs.image }}.sarif' 79 | ignore-unfixed: 'true' 80 | 81 | - name: Upload Trivy scan results to GitHub Security tab 82 | uses: github/codeql-action/upload-sarif@012739e5082ff0c22ca6d6ab32e07c36df03c4a4 # v2.2.11 83 | continue-on-error: true 84 | with: 85 | sarif_file: 'trivy-results-${{ inputs.image }}.sarif' 86 | sha: ${{ github.sha }} 87 | ref: ${{ github.ref }} 88 | 89 | - name: Generate Release 90 | uses: ncipollo/release-action@v1 91 | with: 92 | artifacts: | 93 | trivy-results-${{ inputs.image }}.sarif 94 | ./nginx-loadbalancer-kubernetes-${{env.GITHUB_REF_NAME}}.spdx.json 95 | body: | 96 | # Release ${{env.GITHUB_REF_NAME}} 97 | ## Changelog 98 | ${{ steps.meta.outputs.changelog }} 99 | generateReleaseNotes: true 100 | makeLatest: false 101 | name: "${{env.GITHUB_REF_NAME}}" 102 | -------------------------------------------------------------------------------- /.github/workflows/run-scorecard.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecard supply-chain security 6 | on: 7 | # For Branch-Protection check. Only the default branch is supported. See 8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 9 | branch_protection_rule: 10 | # To guarantee Maintained check is occasionally updated. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 12 | schedule: 13 | - cron: '15 14 * * 3' 14 | push: 15 | branches: [ "main" ] 16 | 17 | # Declare default permissions as read only. 18 | permissions: read-all 19 | 20 | jobs: 21 | analysis: 22 | name: Scorecard analysis 23 | runs-on: ubuntu-latest 24 | permissions: 25 | # Needed to upload the results to code-scanning dashboard. 26 | security-events: write 27 | # Needed to publish results and get a badge (see publish_results below). 28 | id-token: write 29 | # Uncomment the permissions below if installing in a private repository. 30 | # contents: read 31 | # actions: read 32 | 33 | steps: 34 | - name: "Checkout code" 35 | uses: actions/checkout@v4 # v3.1.0 36 | with: 37 | persist-credentials: false 38 | 39 | - name: "Run analysis" 40 | uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 41 | with: 42 | results_file: results.sarif 43 | results_format: sarif 44 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 45 | # - you want to enable the Branch-Protection check on a *public* repository, or 46 | # - you are installing Scorecard on a *private* repository 47 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 48 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 49 | 50 | # Public repositories: 51 | # - Publish results to OpenSSF REST API for easy access by consumers 52 | # - Allows the repository to include the Scorecard badge. 53 | # - See https://github.com/ossf/scorecard-action#publishing-results. 54 | # For private repositories: 55 | # - `publish_results` will always be set to `false`, regardless 56 | # of the value entered here. 57 | publish_results: true 58 | 59 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 60 | # format to the repository Actions tab. 61 | - name: "Upload artifact" 62 | uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 63 | with: 64 | name: SARIF file 65 | path: results.sarif 66 | retention-days: 5 67 | 68 | # Upload the results to GitHub's code scanning dashboard. 69 | - name: "Upload to code-scanning" 70 | uses: github/codeql-action/upload-sarif@012739e5082ff0c22ca6d6ab32e07c36df03c4a4 # v3.22.12 71 | with: 72 | sarif_file: results.sarif 73 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Run tests 5 | 6 | on: 7 | branch_protection_rule: 8 | types: 9 | - created 10 | 11 | jobs: 12 | 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Set up Go 19 | uses: actions/setup-go@v5 20 | with: 21 | go-version: 1.19 22 | 23 | - name: Build 24 | run: go build -v ./... 25 | 26 | - name: Test 27 | run: go test -v ./... 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | bin/ 3 | 4 | # Any private crt and keys # 5 | ############################ 6 | *.crt 7 | *.key 8 | *~ 9 | \#* 10 | 11 | # OS Specific # 12 | ############### 13 | Thumbs.db 14 | .DS_Store 15 | .vscode 16 | 17 | # Logs # 18 | ######## 19 | *.log 20 | /misc-dev/ 21 | 22 | cover* 23 | tmp/ 24 | docs/tls/DESIGN.md 25 | :q 26 | qqq -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | golang 1.23.3 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.0.0 (Month Date, Year) 4 | 5 | Initial release of the NGINX template repository. 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the moderation team at nginx-oss-community@f5.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, 71 | available at 72 | 73 | For answers to common questions about this code of conduct, see 74 | 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | The following is a set of guidelines for contributing to the nginx_loadbalancer_kubernetes. We really appreciate that you are considering contributing! 4 | 5 | #### Table Of Contents 6 | 7 | [Getting Started](#getting-started) 8 | 9 | [Contributing](#contributing) 10 | 11 | [Code Guidelines](#code-guidelines) 12 | 13 | [Code of Conduct](https://github.com/nginxinc/nginx-loadbalancer-kubernetes/blob/main/CODE_OF_CONDUCT.md) 14 | 15 | ## Getting Started 16 | 17 | Follow our [Installation Guide](https://github.com/nginxinc/nginx-loadbalancer-kubernetes/blob/main/README.md#Installation) to get the nginx_loadbalancer_kubernetes up and running. 18 | 19 | 20 | 21 | ## Contributing 22 | 23 | We are not currently accepting contributions to this project. Please create an issue on GitHub with the label `feature` or `enhancement` using the available feature request issue template. Please ensure the feature or enhancement has not already been suggested. 24 | 25 | ### Report a Bug 26 | 27 | To report a bug, open an issue on GitHub with the label `bug` using the available bug report issue template. Please ensure the bug has not already been reported. **If the bug is a potential security vulnerability, please report using our security policy.** 28 | 29 | ### Suggest a Feature or Enhancement 30 | 31 | To suggest a feature or enhancement, please create an issue on GitHub with the label `feature` or `enhancement` using the available feature request issue template. Please ensure the feature or enhancement has not already been suggested. 32 | 33 | ## Code Guidelines 34 | 35 | 36 | 37 | ### Git Guidelines 38 | 39 | * Keep a clean, concise and meaningful git commit history on your branch (within reason), rebasing locally and squashing before submitting a PR. 40 | * If possible and/or relevant, use the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) format when writing a commit message, so that changelogs can be automatically generated 41 | * Follow the guidelines of writing a good commit message as described here and summarised in the next few points: 42 | * In the subject line, use the present tense ("Add feature" not "Added feature"). 43 | * In the subject line, use the imperative mood ("Move cursor to..." not "Moves cursor to..."). 44 | * Limit the subject line to 67 characters or less. 45 | * Limit the rest of the commit message to 72 characters or less. 46 | * Reference issues and pull requests liberally after the subject line. 47 | * Add more detailed description in the body of the git message (`git commit -a` to give you more space and time in your text editor to write a good message instead of `git commit -am`). 48 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2023 f5 Inc. All rights reserved. 2 | # Use of this source code is governed by the Apache 3 | # license that can be found in the LICENSE file. 4 | 5 | FROM golang:1.23.3-alpine3.20 AS builder 6 | 7 | WORKDIR /app 8 | 9 | COPY go.mod go.sum ./ 10 | 11 | RUN go mod download 12 | 13 | COPY . . 14 | 15 | RUN go build -o nginx-loadbalancer-kubernetes ./cmd/nginx-loadbalancer-kubernetes/main.go 16 | 17 | FROM alpine:3.20 18 | 19 | WORKDIR /opt/nginx-loadbalancer-kubernetes 20 | 21 | RUN adduser -u 11115 -D -H nlk 22 | 23 | USER nlk 24 | 25 | COPY --from=builder /app/nginx-loadbalancer-kubernetes . 26 | 27 | ENTRYPOINT ["/opt/nginx-loadbalancer-kubernetes/nginx-loadbalancer-kubernetes"] 28 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Latest Versions 4 | 5 | We advise users to run or update to the most recent release of the nginx_loadbalancer_kubernetes. Older versions of the nginx_loadbalancer_kubernetes may not have all enhancements and/or bug fixes applied to them. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | The F5 Security Incident Response Team (F5 SIRT) has an email alias that makes it easy to report potential security vulnerabilities. 10 | 11 | * If you’re an F5 customer with an active support contract, please contact [F5 Technical Support](https://www.f5.com/services/support). 12 | * If you aren’t an F5 customer, please report any potential or current instances of security vulnerabilities with any F5 product to the F5 Security Incident Response Team at F5SIRT@f5.com 13 | 14 | For more information visit [https://www.f5.com/services/support/report-a-vulnerability](https://www.f5.com/services/support/report-a-vulnerability) 15 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## Ask a Question 4 | 5 | We use GitHub for tracking bugs and feature requests related to this project. 6 | 7 | Don't know how something in the nginx_loadbalancer_kubernetes works? Curious if the nginx_loadbalancer_kubernetes can achieve your desired functionality? Please open an Issue on GitHub with the label `question`. 8 | 9 | ## NGINX Specific Questions and/or Issues 10 | 11 | This isn't the right place to get support for NGINX specific questions, but the following resources are available below. Thanks for your understanding! 12 | 13 | ### Community Slack 14 | 15 | We have a community [Slack](https://nginxcommunity.slack.com/)! 16 | 17 | If you are not a member click [here](https://join.slack.com/t/nginxcommunity/shared_invite/zt-1aaa22w80-~_~wSMNyPxLPLp5xunOC7w) to sign up (and let us know if the link does not seem to be working!) 18 | 19 | Once you join, check out the `#beginner-questions` and `nginx-users` channels :) 20 | 21 | ### Documentation 22 | 23 | For a comprehensive list of all NGINX directives, check out . 24 | 25 | For a comprehensive list of admin and deployment guides for all NGINX products, check out . 26 | 27 | ### Mailing List 28 | 29 | Want to get in touch with the NGINX dev team directly? Try using the relevant mailing list found at ! 30 | -------------------------------------------------------------------------------- /ca-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURUekNDQWpjQ0ZGNGlKWWxnWFYrNksxclE0L1JacXEyTWxRTWZNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1HUXgKQ3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSURBcFhZWE5vYVc1bmRHOXVNUkF3RGdZRFZRUUhEQWRUWldGMApkR3hsTVE0d0RBWURWUVFLREFWT1IwbE9XREVlTUJ3R0ExVUVDd3dWUTI5dGJYVnVhWFI1SUNZZ1FXeHNhV0Z1ClkyVnpNQjRYRFRJek1UQXdNakl6TURReU9Wb1hEVEl6TVRFd01USXpNRFF5T1Zvd1pERUxNQWtHQTFVRUJoTUMKVlZNeEV6QVJCZ05WQkFnTUNsZGhjMmhwYm1kMGIyNHhFREFPQmdOVkJBY01CMU5sWVhSMGJHVXhEakFNQmdOVgpCQW9NQlU1SFNVNVlNUjR3SEFZRFZRUUxEQlZEYjIxdGRXNXBkSGtnSmlCQmJHeHBZVzVqWlhNd2dnRWlNQTBHCkNTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFEc0xteW9vc2NKNXZQbmFER3FCZkozYmQxT2F0NXYKYjQwSjcrc0ROamxhUFNGRjdNZGJyN2JFOFhkRUNGOHZCYkE2MkUwTVJRYXJHMFl5aWJBOG05OFV5TVJ1R0VRTApSZkltZ2pHVWtXdkRmU0ViSkJLZ1RXay83ckJPeDVRY1lFVnlCZ2Z2SDZMWk5hampic0VFaUFjalVObkp3WkNpCjdIWjJDSVZ5NVhpUmlKWVZLbGZwN3QvYlIwTXRtWVNEMlh6L0RoZXBRMnpkQ25zNnpFK3Q5aGxINnorcThydUUKam45ZTJaUGNBeTlHc1lNVi9WSTdIK0ZyMVBaREJJNUhtb1pZeUo4MTVqcWhiQUxGeU00Z0x3V1JlSVJuek9aZApNeVV0QW8rM1Vic1ZwQ0NOTE9XSk1WeXVUUVV1U3QwOFlXNTFDeGtESlBUMDY0bFAyZXBLQ2RQcEFnTUJBQUV3CkRRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFMUStGRzVxNDVSV0JFK1JwNkpwUlZWakphMFF6ZE44V1ljcWUwTDkKVlJPbG5aSkxva2JGNzJrOEdrZ0t3YzNVZ21IRzFMbmMvdzlTbFJ1Ti9uRkMySEtZbWxqWHkwaHpWaFZLbDRjRgozc1NKV0c2dzBkcXFNVmk0bDBybFVDQlk3cDBTQXA4eFdLd2ZoanE0NU1YSGlwNERRUzVTUXUrWjJiSCtTN3pzCjNxcGhDaEpkNkpGZUNnL0pGQ1VIWjZGRXk0ZitJcGl1bU01SENKakJUcHdEVUdLcHB3a2pPeWp6Ym1Vc1hEcUYKRkIvYzZ4MnluYjlROGQ4MG1oREV5V0dFWDhtbE1ycXNrblFZd2JTenkyUitudnFLOFp3NTFGbDRSUU96K1lPZAphMUlTemZKWkYwdEtqcittS0ZSODZXWm05VWNOT1JnWjQ1WjVRNWI1V1AvMVF6dz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= 4 | tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktZd2dnU2lBZ0VBQW9JQkFRRHNMbXlvb3NjSjV2UG4KYURHcUJmSjNiZDFPYXQ1dmI0MEo3K3NETmpsYVBTRkY3TWRicjdiRThYZEVDRjh2QmJBNjJFME1SUWFyRzBZeQppYkE4bTk4VXlNUnVHRVFMUmZJbWdqR1VrV3ZEZlNFYkpCS2dUV2svN3JCT3g1UWNZRVZ5Qmdmdkg2TFpOYWpqCmJzRUVpQWNqVU5uSndaQ2k3SFoyQ0lWeTVYaVJpSllWS2xmcDd0L2JSME10bVlTRDJYei9EaGVwUTJ6ZENuczYKekUrdDlobEg2eitxOHJ1RWpuOWUyWlBjQXk5R3NZTVYvVkk3SCtGcjFQWkRCSTVIbW9aWXlKODE1anFoYkFMRgp5TTRnTHdXUmVJUm56T1pkTXlVdEFvKzNVYnNWcENDTkxPV0pNVnl1VFFVdVN0MDhZVzUxQ3hrREpQVDA2NGxQCjJlcEtDZFBwQWdNQkFBRUNnZ0VBWWV3Tm1RMkVRSkxFNVZqSjNwaUFvd3VtQ2ZFOU1DNnI1MGJWeFlzaDFFd3MKRTNYTVlqTkVML3Q5VzNPdEl5M1VsMUUvQUt0TnpIdU9hejJ6R0MzNEhBSHhqMFA0VWtRNTFjVjlFUUFLRWc4NwpQcW1DSDN4NCtzelh4Skh5MHFFSHFmTGVMMEtLbmt3bExjYXB1RnM5dW1LM0tYTmJxSEVwM0Y1RUZoTVdIaUFiClV4cWpYZXJKWXBZMXU3RVhwUHFWaSt3dFhkWDNuT092bStZMzNLemRGNTErTXBpOHVkMGpTY0p2VEtIYXpKV1oKZE40aFF4SEpEOGpMZm8zNDRNWEFYQndkWFVuN1V4NW9qU0dVemtnU1pKTnBkQVNaUU5lN2E1Qk4xalREZ29LaQp0My9kS0NFTmFsMGdUQVI1MFArYWJxeWhSOFUxbVVuT1dsWDN1ekJsa1FLQmdRRDNFS09tWm16Z293Nk5mOWp2CmpkdzJOUUQ1Q2NlWEk4cWlxRjlMNWZoVWV0b29PajA2WlZpYjBEejNWcWlqVnRITFBQV2pRQ2ZwWC9DVHdkUTkKajNHTVBDOEhIZWJkeEZqTHpRSWlDUXAvaGFlc2RGTUhHRzFBak92VDRNdkxOaklWM29JOCszZjhIWk81Q0pYQgpJdW1xcG82YWJhV094VEhDS2pYY2YxNTI0d0tCZ1FEMHVRWGNJWlJrNjdrYU5PZWhjLzFuU2QyTVJSVG1zV3JYClFjTEtQczdWVFkwWlA1T2hsU1pRVkM2MW9PUWtuZnFScGhVbnJVVDF1NXFRbzJUSGhGcmN4a3Qrd1hCenhDWDgKUXZXY1RTTmkvZ0Y2Rkx1OEsyYXlOVlBQTDFNQjJ4ZGxTL0Z0SDRLSG5RWHM1NFM5MVZHRTR5OUd6aDIwWEcwOQp4K0FiQzlPM3d3S0JnSGNuWURXbFdrY3dmSmxEbW1WV0htbEtRTkRhcFphLzNUOTdRcEtCTTdYU2xob21sRmJ3CmY3Nk52SWx4RXQzTHhseGxadlkzdjhmdXpFRUdqd3l0Zkk2c2krVzd4eGNYVmRmY1pIWHp0RXR5TXo2WnoxMHgKcTZjaEQ2OWMwQXlPYzdOV1g2dDNnQk5vVkZFOTBiT1cyZWpDY1M0TFNYaEVwRTNITzdpKytOa1BBb0dBZW9CYgo1Sk9TbXVvOG9GZTNVMlNpaHArOUhVZy9iRE9IamZWSE1zSTUreUIwN3h5YUpCcHJNVzdTYXV6OUJ5OWxqSjhjCm05M3FWUy94OFZFNVUzNTNsV2hWeGovQ3NOQ1JTek9oaXZvNktvV0g2N3FSTjJKcVorNjE0MUtITkxpZGY0R0MKZXVONURiV1dqNzVjL2tIWUtyTW1xVVRvTGE3T3FFeHpiRmFCUnMwQ2dZQUN6UlBiQTBqK0JyOWpxenFKYU56VQpPZzJ4aUFhZlNuTzBxTkREZWZNdXhZSnFMZ0llMG9HRUExb1JlcDhvTDNDeWZYd2h3U2xqMlRjeGZZTFR5MENKCmZBejF4QjY3SUF3cGlvZnI0K1ZXekF4SC9BbnlhVGNZVGdtakdKdlhtZ3l1RGU3ZGJsa3lJU1M5ZTBLc1JzeTYKQ2hPdVZoQy96bjErbm9BMkNsdzFJdz09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K 5 | kind: Secret 6 | metadata: 7 | creationTimestamp: null 8 | name: nlk-tls-ca-secret 9 | namespace: nlk 10 | type: kubernetes.io/tls 11 | -------------------------------------------------------------------------------- /charts/nlk/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | .png 3 | -------------------------------------------------------------------------------- /charts/nlk/Chart.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v2 3 | appVersion: 0.1.0 4 | description: NGINX LoadBalancer for Kubernetes 5 | name: nginx-loadbalancer-kubernetes 6 | home: https://github.com/nginxinc/nginx-loadbalancer-kubernetes 7 | icon: https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/main/nlk-logo.svg 8 | keywords: 9 | - nginx 10 | - loadbalancer 11 | - ingress 12 | kubeVersion: '>= 1.22.0-0' 13 | maintainers: 14 | - name: "@ciroque" 15 | - name: "@chrisakker" 16 | - name: "@abdennour" 17 | 18 | type: application 19 | version: 0.0.1 20 | -------------------------------------------------------------------------------- /charts/nlk/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | 3 | {{/* 4 | Expand the name of the chart. 5 | */}} 6 | {{- define "nlk.name" -}} 7 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 8 | {{- end }} 9 | 10 | {{/* 11 | Create a default fully qualified app name. 12 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 13 | If release name contains chart name it will be used as a full name. 14 | */}} 15 | {{- define "nlk.fullname" -}} 16 | {{- if .Values.fullnameOverride }} 17 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 18 | {{- else }} 19 | {{- $name := default .Chart.Name .Values.nameOverride }} 20 | {{- if contains $name .Release.Name }} 21 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 22 | {{- else }} 23 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 24 | {{- end }} 25 | {{- end }} 26 | {{- end }} 27 | 28 | {{/* 29 | Create a default fully qualified nlk name. 30 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 31 | */}} 32 | {{- define "nlk.nlk.fullname" -}} 33 | {{- printf "%s-%s" (include "nlk.fullname" .) .Values.nlk.name | trunc 63 | trimSuffix "-" -}} 34 | {{- end -}} 35 | 36 | {{/* 37 | Create a default fully qualified nlk service name. 38 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 39 | */}} 40 | {{- define "nlk.nlk.service.name" -}} 41 | {{- default (include "nlk.nlk.fullname" .) .Values.serviceNameOverride | trunc 63 | trimSuffix "-" -}} 42 | {{- end -}} 43 | 44 | {{/* 45 | Create chart name and version as used by the chart label. 46 | */}} 47 | {{- define "nlk.chart" -}} 48 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 49 | {{- end }} 50 | 51 | {{/* 52 | Common labels 53 | */}} 54 | {{- define "nlk.labels" -}} 55 | helm.sh/chart: {{ include "nlk.chart" . }} 56 | {{ include "nlk.selectorLabels" . }} 57 | {{- if .Chart.AppVersion }} 58 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 59 | {{- end }} 60 | app.kubernetes.io/managed-by: {{ .Release.Service }} 61 | {{- end }} 62 | 63 | {{/* 64 | Selector labels 65 | */}} 66 | {{- define "nlk.selectorLabels" -}} 67 | {{- if .Values.nlk.selectorLabels -}} 68 | {{ toYaml .Values.nlk.selectorLabels }} 69 | {{- else -}} 70 | app.kubernetes.io/name: {{ include "nlk.name" . }} 71 | app.kubernetes.io/instance: {{ .Release.Name }} 72 | {{- end -}} 73 | {{- end -}} 74 | 75 | {{/* 76 | Expand the name of the configmap. 77 | */}} 78 | {{- define "nlk.configName" -}} 79 | {{- if .Values.nlk.customConfigMap -}} 80 | {{ .Values.nlk.customConfigMap }} 81 | {{- else -}} 82 | {{- default (include "nlk.fullname" .) .Values.nlk.config.name -}} 83 | {{- end -}} 84 | {{- end -}} 85 | 86 | {{/* 87 | Expand service account name. 88 | */}} 89 | {{- define "nlk.serviceAccountName" -}} 90 | {{- default (include "nlk.fullname" .) .Values.nlk.serviceAccount.name -}} 91 | {{- end -}} 92 | 93 | {{- define "nlk.tag" -}} 94 | {{- default .Chart.AppVersion .Values.nlk.image.tag -}} 95 | {{- end -}} 96 | 97 | {{/* 98 | Expand image name. 99 | */}} 100 | {{- define "nlk.image" -}} 101 | {{- if .Values.nlk.image.digest -}} 102 | {{- printf "%s/%s@%s" .Values.nlk.image.registry .Values.nlk.image.repository .Values.nlk.image.digest -}} 103 | {{- else -}} 104 | {{- printf "%s/%s:%s" .Values.nlk.image.registry .Values.nlk.image.repository (include "nlk.tag" .) -}} 105 | {{- end -}} 106 | {{- end -}} 107 | 108 | {{- define "nlk.prometheus.serviceName" -}} 109 | {{- printf "%s-%s" (include "nlk.fullname" .) "prometheus-service" -}} 110 | {{- end -}} 111 | -------------------------------------------------------------------------------- /charts/nlk/templates/clusterrole.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create }} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: {{ .Release.Namespace }}-{{ include "nlk.fullname" . }} 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | - nodes 12 | - secrets 13 | - services 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | {{- end }} 19 | -------------------------------------------------------------------------------- /charts/nlk/templates/clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create }} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRoleBinding 4 | metadata: 5 | name: {{ .Release.Namespace }}-{{ include "nlk.fullname" . }} 6 | subjects: 7 | - kind: ServiceAccount 8 | name: {{ include "nlk.fullname" . }} 9 | namespace: nlk 10 | roleRef: 11 | kind: ClusterRole 12 | name: {{ .Release.Namespace }}-{{ include "nlk.fullname" . }} 13 | apiGroup: rbac.authorization.k8s.io 14 | {{- end }} 15 | -------------------------------------------------------------------------------- /charts/nlk/templates/nlk-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: nlk-config 5 | namespace: nlk 6 | data: 7 | {{- if .Values.nlk.config.entries.hosts }} 8 | nginx-hosts: "{{ .Values.nlk.config.entries.hosts }}" 9 | {{- end }} 10 | tls-mode: "{{ index .Values.nlk.defaultTLS "tls-mode" }}" 11 | ca-certificate: "{{ index .Values.nlk.defaultTLS "ca-certificate" }}" 12 | client-certificate: "{{ index .Values.nlk.defaultTLS "client-certificate" }}" 13 | log-level: "{{ .Values.nlk.logLevel }}" 14 | 15 | -------------------------------------------------------------------------------- /charts/nlk/templates/nlk-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "nlk.fullname" . }} 5 | namespace: nlk 6 | labels: 7 | app: nlk 8 | spec: 9 | replicas: {{ .Values.nlk.replicaCount }} 10 | selector: 11 | matchLabels: 12 | app: nlk 13 | template: 14 | metadata: 15 | labels: 16 | app: nlk 17 | spec: 18 | containers: 19 | - name: {{ .Chart.Name }} 20 | image: {{ include "nlk.image" .}} 21 | imagePullPolicy: {{ .Values.nlk.image.pullPolicy }} 22 | ports: 23 | {{- range $key, $value := .Values.nlk.containerPort }} 24 | - name: {{ $key }} 25 | containerPort: {{ $value }} 26 | protocol: TCP 27 | {{- end }} 28 | {{- if .Values.nlk.liveStatus.enable }} 29 | livenessProbe: 30 | httpGet: 31 | path: /livez 32 | port: {{ .Values.nlk.liveStatus.port }} 33 | initialDelaySeconds: {{ .Values.nlk.liveStatus.initialDelaySeconds }} 34 | periodSeconds: {{ .Values.nlk.readyStatus.periodSeconds }} 35 | {{- end }} 36 | {{- if .Values.nlk.readyStatus.enable }} 37 | readinessProbe: 38 | httpGet: 39 | path: /readyz 40 | port: {{ .Values.nlk.readyStatus.port }} 41 | initialDelaySeconds: {{ .Values.nlk.readyStatus.initialDelaySeconds }} 42 | periodSeconds: {{ .Values.nlk.readyStatus.periodSeconds }} 43 | {{- end }} 44 | serviceAccountName: {{ include "nlk.fullname" . }} 45 | -------------------------------------------------------------------------------- /charts/nlk/templates/nlk-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: {{ include "nlk.fullname" . }} 5 | namespace: nlk 6 | annotations: 7 | kubernetes.io/service-account.name: {{ include "nlk.fullname" . }} 8 | type: kubernetes.io/service-account-token 9 | -------------------------------------------------------------------------------- /charts/nlk/templates/nlk-serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create }} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "nlk.fullname" . }} 6 | namespace: nlk 7 | {{- end }} 8 | -------------------------------------------------------------------------------- /charts/nlk/values.yaml: -------------------------------------------------------------------------------- 1 | nlk: 2 | name: nginx-loadbalancer-kubernetes 3 | 4 | kind: deployment 5 | 6 | replicaCount: 1 7 | 8 | image: 9 | registry: ghcr.io 10 | repository: nginxinc/nginx-loadbalancer-kubernetes 11 | pullPolicy: Always 12 | # Overrides the image tag whose default is the chart appVersion. 13 | tag: latest 14 | 15 | imagePullSecrets: [] 16 | nameOverride: "" 17 | fullnameOverride: "" 18 | 19 | serviceAccount: 20 | # Specifies whether a service account should be created 21 | create: true 22 | # Automatically mount a ServiceAccount's API credentials? 23 | automount: true 24 | # Annotations to add to the service account 25 | annotations: {} 26 | 27 | podAnnotations: {} 28 | podLabels: {} 29 | 30 | podSecurityContext: {} 31 | # fsGroup: 2000 32 | 33 | securityContext: {} 34 | # capabilities: 35 | # drop: 36 | # - ALL 37 | # readOnlyRootFilesystem: true 38 | # runAsNonRoot: true 39 | # runAsUser: 1000 40 | 41 | service: 42 | type: ClusterIP 43 | port: 80 44 | 45 | ingress: 46 | enabled: false 47 | className: "" 48 | annotations: {} 49 | # kubernetes.io/ingress.class: nginx 50 | # kubernetes.io/tls-acme: "true" 51 | hosts: 52 | - host: chart-example.local 53 | paths: 54 | - path: / 55 | pathType: ImplementationSpecific 56 | tls: [] 57 | # - secretName: chart-example-tls 58 | # hosts: 59 | # - chart-example.local 60 | 61 | resources: 62 | requests: 63 | cpu: 100m 64 | memory: 128Mi 65 | # limits: 66 | # cpu: 100m 67 | # memory: 128Mi 68 | 69 | autoscaling: 70 | enabled: false 71 | minReplicas: 1 72 | maxReplicas: 3 73 | targetCPUUtilizationPercentage: 80 74 | # targetMemoryUtilizationPercentage: 80 75 | 76 | # Additional volumes on the output Deployment definition. 77 | volumes: [] 78 | # - name: foo 79 | # secret: 80 | # secretName: mysecret 81 | # optional: false 82 | 83 | # Additional volumeMounts on the output Deployment definition. 84 | volumeMounts: [] 85 | # - name: foo 86 | # mountPath: "/etc/foo" 87 | # readOnly: true 88 | 89 | nodeSelector: {} 90 | 91 | tolerations: [] 92 | 93 | affinity: {} 94 | 95 | config: 96 | entries: 97 | hosts: 98 | "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" 99 | 100 | defaultTLS: 101 | tls-mode: "no-tls" 102 | ca-certificate: "" 103 | client-certificate: "" 104 | 105 | logLevel: "warn" 106 | 107 | containerPort: 108 | http: 51031 109 | 110 | liveStatus: 111 | enable: true 112 | port: 51031 113 | initialDelaySeconds: 5 114 | periodSeconds: 2 115 | 116 | readyStatus: 117 | enable: true 118 | port: 51031 119 | initialDelaySeconds: 5 120 | periodSeconds: 2 121 | 122 | rbac: 123 | ## Configures RBAC. 124 | create: true 125 | -------------------------------------------------------------------------------- /client-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVIRENDQXdTZ0F3SUJBZ0lVUFRrVExlS3FNQlRlNFZhQXpiL1hXcSt6THM4d0RRWUpLb1pJaHZjTkFRRUwKQlFBd1pERUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2xkaGMyaHBibWQwYjI0eEVEQU9CZ05WQkFjTQpCMU5sWVhSMGJHVXhEakFNQmdOVkJBb01CVTVIU1U1WU1SNHdIQVlEVlFRTERCVkRiMjF0ZFc1cGRIa2dKaUJCCmJHeHBZVzVqWlhNd0hoY05Nak14TURBeU1qTXdOak0zV2hjTk1qUXhNREF4TWpNd05qTTNXakJrTVFzd0NRWUQKVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLVjJGemFHbHVaM1J2YmpFUU1BNEdBMVVFQnd3SFUyVmhkSFJzWlRFTwpNQXdHQTFVRUNnd0ZUa2RKVGxneEhqQWNCZ05WQkFzTUZVTnZiVzExYm1sMGVTQW1JRUZzYkdsaGJtTmxjekNDCkFTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBSzRPY1h4YWVrNkVQbTBDZUo5Z21iRlEKbFg1THlHNlUybEZIOFBFY3UvUlVTenJRQW1Rckh5di9mMlFBb3Q5VzhGNVlpaVVlQi85TkVGY1M2ZUVEK00xbwpVMy9ubEdHS21qTlQ5amNpbnBDZ0lYVUZ5UVQvWkd3QjFVWEdZYUViMHFWOFVVUlpTS1AxWXI5SzJBTzRxWE95CkFGTjZyUkdrdzBZTEErd3FEcDQxdFBNZEZZWWcrRit4OFZ5S1hLckZScUZRTFNPa2hzQXNwT3RZcjNOMkIzUlMKVGozeXFyNUZkc2w0VklSYWZCMWlhbGhSRWdnbDZjekZZaHZmci9kNjdNcHJvVlhudUZ5cS9OUDAvbXRnWXhvRwpKRVFoQmhnOFR4eUhiZ1lsTTFXZkxQTDh0SG5WUTZZVTVzamwrcG8vRU1yR24wNiszNnRwdksyT0RmNkJ6SDhDCkF3RUFBYU9CeFRDQndqQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFkQmdOVkhRNEVGZ1FVMkdrWmcxakUKYlcyN0lJSjV5cTB3ekJXa1FzY3dnWXNHQTFVZEl3U0JnekNCZ0tGb3BHWXdaREVMTUFrR0ExVUVCaE1DVlZNeApFekFSQmdOVkJBZ01DbGRoYzJocGJtZDBiMjR4RURBT0JnTlZCQWNNQjFObFlYUjBiR1V4RGpBTUJnTlZCQW9NCkJVNUhTVTVZTVI0d0hBWURWUVFMREJWRGIyMXRkVzVwZEhrZ0ppQkJiR3hwWVc1alpYT0NGRjRpSllsZ1hWKzYKSzFyUTQvUlpxcTJNbFFNZk1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQWkwais2eDMrZjJ4ZXJlQlg2OFNmWAozM0tmc0tOaGJIOXJKeU9ROXViV1RrdHdaQnVOUmFKNEptWUtFa0IxUnVnOTFVVmdMdmZzWm5VY1FTOHRQUU8rCnNEcHFEamdwdnlZbU1LSm1HVHZ0M0tmK3JpV0dtU3g3ZDZUb0R5bGpNZ2dUdmJ4dFZhQTNtak9UcGFzb0ZWTzMKcXlVU29sUCtoZzI5M2Z2Q1JaeWhNTTd0ejdEdUIzRmFjR1JHNmNoaE55N0UzaGRpd2JjVUdIQ0VGN0ZwbWNLTgpOTlhyUVMwOW9GeUVmZEd3TkFHOUtPVHZkVDNZUzFqcmo3QnROOGlMSGxQUFFNaFk3aWhTUnlVUmN1S05vSC9BCnRTQm83eFJWV3BZMTIrMEJhaERjQ1lndzJ0RjMvd2Q1ekhwb1RuZi9YZFJiMm4vUzBKbTZueE54NUdlbDhMK2EKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= 4 | tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ3VEbkY4V25wT2hENXQKQW5pZllKbXhVSlYrUzhodWxOcFJSL0R4SEx2MFZFczYwQUprS3g4ci8zOWtBS0xmVnZCZVdJb2xIZ2YvVFJCWApFdW5oQS9qTmFGTi81NVJoaXBvelUvWTNJcDZRb0NGMUJja0UvMlJzQWRWRnhtR2hHOUtsZkZGRVdVaWo5V0svClN0Z0R1S2x6c2dCVGVxMFJwTU5HQ3dQc0tnNmVOYlR6SFJXR0lQaGZzZkZjaWx5cXhVYWhVQzBqcEliQUxLVHIKV0s5emRnZDBVazQ5OHFxK1JYYkplRlNFV253ZFltcFlVUklJSmVuTXhXSWIzNi8zZXV6S2E2RlY1N2hjcXZ6VAo5UDVyWUdNYUJpUkVJUVlZUEU4Y2gyNEdKVE5Wbnl6eS9MUjUxVU9tRk9iSTVmcWFQeERLeHA5T3Z0K3JhYnl0CmpnMytnY3gvQWdNQkFBRUNnZ0VBSXo2VWQwaEE1TjQ5WDhoMDBWejNzaUp0cXYzQWI3ZmZmejd3aUhvM2l2RjQKckVlTGZHb0k3VmxXbTlMUEtDZE1FK2Fjem9oR3VVa0xDbjZ2Y2h0aVNZR2JDdGJEUW44VTIxamdqZWlLTUNIawp0SFAvOE8yZ0VZakxmVTMrM2VjcTM4eU5EaWlBSDRja1FEVHhDY3ZlTUNtMmpERFdrN0NIeEFxZCtEZko3dm45Cnp2enY2TkkzQWhWZktwYmszV0RONmlRajdhelNxN2dPV0JnaS81d3hmcnJqYnpRbGU0YWRvRHZXUFcxbXg4TEcKbFNQekUyWUVIbHRGN2lFbTFTTW9SMFRtQ28xOVYrTUFjQTRLek4yUk05WXNGRER0b1hSMjRodm1wS0ZiazdIQwpITm01ZTd6dUdYK1NFOW1sbDBST1RuMXhTQUw1bjMvaFI5MzFINzdGeVFLQmdRRHhoWWhiNkN2Wm5Nb3krejVWCnR4VFh6dFR4UG0yOVpYaHdVTEZVYUphVUR3Rk13MlVhMld5ZGlscDMzUkZwWlFPNnZwZXl4NE55RjVBTmh6TzMKZE11VktmMTNWekR4NkxROXpRNng3R0NmekRkdDllWTc2aUwxRzBJV3NUZUZaTWduSDE2WUNVaVFlMVRhNVNPOQphWXN5eGErTkFIaU4yVDZydGNuZ0FKMjNPUUtCZ1FDNGZaR0JaZmtQRFlQaEpKM0RmSUcyM1hua1UrM29rUElSCitWcHBmSkhjVnZ4TDFYREd4Zk5tVGZpcmFJZUYvK0ZUdGJsb0Q4eEZTeU9zZGwwNTJsVlhtV0svV3hRSGl2c2IKL1I2WHJLNjRjZXdtMVFQY3dtek1Lc24vOW02UlVsdlF3VmUyVGYzYWxHYzRVWFBCVGtJZDlESTFaWlV5a2ZmUQppQXhPL0NXcGR3S0JnQzEySlNTbm54bG5HZWhld216LytUeG1Bazhtb1NGMWFDWThDaVVKU3M2enhGcmVyTGxSCkU5RFRxaFBGMlBFdHduWDBTam1zdEdGVmJoZ2R5dTVOWGNUR0VwL1VHYkp2U3Y0WEN4MFNrVjJDNHl3ZmpTYloKKzVxSGR2a3VnblRwYzROcHREU0tDczZuYUdHTG9CNlhMMHh2U1l3UStxQTR0RU0rQkxIVmE5cUJBb0dCQUk1NApXYzlsb2lvZnM4SS85cDBxSHpuS1d3RWFWMVVMNmdRN1hiaXNmQzk5OVNQUzFsNktLMmJMdThjUzErV0JMczdvClBSL0JZMnYzbExyd1JSb1NJMm1jaUFkaUhGdWUxa0JNL2p6L0c0WlFZNSt4VEdSRXVLUUtQeWd0ZEVGQktxcFIKUkowQ0taR01uUkYreFRkNGFkS2I2OUlVZWwwdElBU25xMm1yaXFJTkFvR0FKcmtDS2Rxejg5blRYd3FnbmZrWgpSbVB5Tk1sMjlidDNIT1ZsR3ZoTjJib3lBeHFaYk4xLzdaWE5OSlFKR3J0TUx0QzFXY3o1bXE0czY1QW5KSFhaCkhQWVRyWGdmdjc2SHROMG95TDcxbFBUOTZpU3RHc0tWSmx1R1MvT3I2TXh2Nm5XTEY0aG1mUURqaGpSNVgvdEsKTW1XV0J5Q2JRTU9mRm5hRjB2azJuTXM9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K 5 | kind: Secret 6 | metadata: 7 | creationTimestamp: null 8 | name: nlk-tls-client-secret 9 | namespace: nlk 10 | type: kubernetes.io/tls 11 | -------------------------------------------------------------------------------- /cmd/certificates-test-harness/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | /* 7 | Package certificates_test_harness includes functionality boostrap and test the certification.Certificates implplementation. 8 | */ 9 | 10 | package main 11 | -------------------------------------------------------------------------------- /cmd/certificates-test-harness/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" 8 | "github.com/sirupsen/logrus" 9 | "k8s.io/client-go/kubernetes" 10 | "k8s.io/client-go/rest" 11 | "k8s.io/client-go/tools/clientcmd" 12 | "k8s.io/client-go/util/homedir" 13 | "path/filepath" 14 | ) 15 | 16 | func main() { 17 | logrus.SetLevel(logrus.DebugLevel) 18 | err := run() 19 | if err != nil { 20 | logrus.Fatal(err) 21 | } 22 | } 23 | 24 | func run() error { 25 | logrus.Info("certificates-test-harness::run") 26 | 27 | ctx := context.Background() 28 | var err error 29 | 30 | k8sClient, err := buildKubernetesClient() 31 | if err != nil { 32 | return fmt.Errorf(`error building a Kubernetes client: %w`, err) 33 | } 34 | 35 | certificates := certification.NewCertificates(ctx, k8sClient) 36 | 37 | err = certificates.Initialize() 38 | if err != nil { 39 | return fmt.Errorf(`error occurred initializing certificates: %w`, err) 40 | } 41 | 42 | go certificates.Run() 43 | 44 | <-ctx.Done() 45 | return nil 46 | } 47 | 48 | func buildKubernetesClient() (*kubernetes.Clientset, error) { 49 | logrus.Debug("Watcher::buildKubernetesClient") 50 | 51 | var kubeconfig *string 52 | var k8sConfig *rest.Config 53 | 54 | k8sConfig, err := rest.InClusterConfig() 55 | if errors.Is(err, rest.ErrNotInCluster) { 56 | if home := homedir.HomeDir(); home != "" { 57 | path := filepath.Join(home, ".kube", "config") 58 | kubeconfig = &path 59 | 60 | k8sConfig, err = clientcmd.BuildConfigFromFlags("", *kubeconfig) 61 | if err != nil { 62 | return nil, fmt.Errorf(`error occurred building the kubeconfig: %w`, err) 63 | } 64 | } else { 65 | return nil, fmt.Errorf(`not running in a Cluster: %w`, err) 66 | } 67 | } else if err != nil { 68 | return nil, fmt.Errorf(`error occurred getting the Cluster config: %w`, err) 69 | } 70 | 71 | client, err := kubernetes.NewForConfig(k8sConfig) 72 | if err != nil { 73 | return nil, fmt.Errorf(`error occurred creating a client: %w`, err) 74 | } 75 | return client, nil 76 | } 77 | -------------------------------------------------------------------------------- /cmd/configuration-test-harness/doc.go: -------------------------------------------------------------------------------- 1 | package main 2 | -------------------------------------------------------------------------------- /cmd/configuration-test-harness/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | configuration2 "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" 8 | "github.com/sirupsen/logrus" 9 | "k8s.io/client-go/kubernetes" 10 | "k8s.io/client-go/rest" 11 | "k8s.io/client-go/tools/clientcmd" 12 | "k8s.io/client-go/util/homedir" 13 | "path/filepath" 14 | ) 15 | 16 | func main() { 17 | logrus.SetLevel(logrus.DebugLevel) 18 | err := run() 19 | if err != nil { 20 | logrus.Fatal(err) 21 | } 22 | } 23 | 24 | func run() error { 25 | logrus.Info("configuration-test-harness::run") 26 | 27 | ctx := context.Background() 28 | var err error 29 | 30 | k8sClient, err := buildKubernetesClient() 31 | if err != nil { 32 | return fmt.Errorf(`error building a Kubernetes client: %w`, err) 33 | } 34 | 35 | configuration, err := configuration2.NewSettings(ctx, k8sClient) 36 | if err != nil { 37 | return fmt.Errorf(`error occurred creating configuration: %w`, err) 38 | } 39 | 40 | err = configuration.Initialize() 41 | if err != nil { 42 | return fmt.Errorf(`error occurred initializing configuration: %w`, err) 43 | } 44 | 45 | go configuration.Run() 46 | 47 | <-ctx.Done() 48 | 49 | return err 50 | } 51 | 52 | func buildKubernetesClient() (*kubernetes.Clientset, error) { 53 | logrus.Debug("Watcher::buildKubernetesClient") 54 | 55 | var kubeconfig *string 56 | var k8sConfig *rest.Config 57 | 58 | k8sConfig, err := rest.InClusterConfig() 59 | if errors.Is(err, rest.ErrNotInCluster) { 60 | if home := homedir.HomeDir(); home != "" { 61 | path := filepath.Join(home, ".kube", "config") 62 | kubeconfig = &path 63 | 64 | k8sConfig, err = clientcmd.BuildConfigFromFlags("", *kubeconfig) 65 | if err != nil { 66 | return nil, fmt.Errorf(`error occurred building the kubeconfig: %w`, err) 67 | } 68 | } else { 69 | return nil, fmt.Errorf(`not running in a Cluster: %w`, err) 70 | } 71 | } else if err != nil { 72 | return nil, fmt.Errorf(`error occurred getting the Cluster config: %w`, err) 73 | } 74 | 75 | client, err := kubernetes.NewForConfig(k8sConfig) 76 | if err != nil { 77 | return nil, fmt.Errorf(`error occurred creating a client: %w`, err) 78 | } 79 | return client, nil 80 | } 81 | -------------------------------------------------------------------------------- /cmd/nginx-loadbalancer-kubernetes/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | /* 7 | Package main includes the entrypoint for the nginx-loadbalancer-kubernetes. 8 | */ 9 | 10 | package main 11 | -------------------------------------------------------------------------------- /cmd/nginx-loadbalancer-kubernetes/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package main 7 | 8 | import ( 9 | "context" 10 | "fmt" 11 | "os" 12 | 13 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" 14 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/observation" 15 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/probation" 16 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/synchronization" 17 | "github.com/sirupsen/logrus" 18 | "k8s.io/client-go/kubernetes" 19 | "k8s.io/client-go/rest" 20 | "k8s.io/client-go/tools/clientcmd" 21 | "k8s.io/client-go/util/workqueue" 22 | ) 23 | 24 | func main() { 25 | err := run() 26 | if err != nil { 27 | logrus.Fatal(err) 28 | } 29 | } 30 | 31 | func run() error { 32 | ctx := context.Background() 33 | var err error 34 | 35 | k8sClient, err := buildKubernetesClient() 36 | if err != nil { 37 | return fmt.Errorf(`error building a Kubernetes client: %w`, err) 38 | } 39 | 40 | settings, err := configuration.NewSettings(ctx, k8sClient) 41 | if err != nil { 42 | return fmt.Errorf(`error occurred creating settings: %w`, err) 43 | } 44 | 45 | err = settings.Initialize() 46 | if err != nil { 47 | return fmt.Errorf(`error occurred initializing settings: %w`, err) 48 | } 49 | 50 | go settings.Run() 51 | 52 | synchronizerWorkqueue, err := buildWorkQueue(settings.Synchronizer.WorkQueueSettings) 53 | if err != nil { 54 | return fmt.Errorf(`error occurred building a workqueue: %w`, err) 55 | } 56 | 57 | synchronizer, err := synchronization.NewSynchronizer(settings, synchronizerWorkqueue) 58 | if err != nil { 59 | return fmt.Errorf(`error initializing synchronizer: %w`, err) 60 | } 61 | 62 | handlerWorkqueue, err := buildWorkQueue(settings.Synchronizer.WorkQueueSettings) 63 | if err != nil { 64 | return fmt.Errorf(`error occurred building a workqueue: %w`, err) 65 | } 66 | 67 | handler := observation.NewHandler(settings, synchronizer, handlerWorkqueue) 68 | 69 | watcher, err := observation.NewWatcher(settings, handler) 70 | if err != nil { 71 | return fmt.Errorf(`error occurred creating a watcher: %w`, err) 72 | } 73 | 74 | err = watcher.Initialize() 75 | if err != nil { 76 | return fmt.Errorf(`error occurred initializing the watcher: %w`, err) 77 | } 78 | 79 | go handler.Run(ctx.Done()) 80 | go synchronizer.Run(ctx.Done()) 81 | 82 | probeServer := probation.NewHealthServer() 83 | probeServer.Start() 84 | 85 | err = watcher.Watch() 86 | if err != nil { 87 | return fmt.Errorf(`error occurred watching for events: %w`, err) 88 | } 89 | 90 | <-ctx.Done() 91 | return nil 92 | } 93 | 94 | // buildKubernetesClient builds a Kubernetes clientset, supporting both in-cluster and out-of-cluster (kubeconfig) configurations. 95 | func buildKubernetesClient() (*kubernetes.Clientset, error) { 96 | var config *rest.Config 97 | var err error 98 | 99 | // Try in-cluster config first 100 | config, err = rest.InClusterConfig() 101 | if err != nil { 102 | if err == rest.ErrNotInCluster { 103 | // Not running in a cluster, fall back to kubeconfig 104 | kubeconfigPath := os.Getenv("KUBECONFIG") 105 | if kubeconfigPath == "" { 106 | kubeconfigPath = clientcmd.RecommendedHomeFile // ~/.kube/config 107 | } 108 | 109 | config, err = clientcmd.BuildConfigFromFlags("", kubeconfigPath) 110 | if err != nil { 111 | return nil, fmt.Errorf("could not get Kubernetes config: %w", err) 112 | } 113 | } else { 114 | return nil, fmt.Errorf("error occurred getting the in-cluster config: %w", err) 115 | } 116 | } 117 | 118 | // Create the clientset 119 | client, err := kubernetes.NewForConfig(config) 120 | if err != nil { 121 | return nil, fmt.Errorf("error occurred creating a Kubernetes client: %w", err) 122 | } 123 | 124 | return client, nil 125 | } 126 | 127 | func buildWorkQueue(settings configuration.WorkQueueSettings) (workqueue.RateLimitingInterface, error) { 128 | logrus.Debug("Watcher::buildSynchronizerWorkQueue") 129 | 130 | rateLimiter := workqueue.NewItemExponentialFailureRateLimiter(settings.RateLimiterBase, settings.RateLimiterMax) 131 | return workqueue.NewNamedRateLimitingQueue(rateLimiter, settings.Name), nil 132 | } 133 | -------------------------------------------------------------------------------- /cmd/nginx-loadbalancer-kubernetes/main_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package main 7 | -------------------------------------------------------------------------------- /cmd/tls-config-factory-test-harness/doc.go: -------------------------------------------------------------------------------- 1 | package main 2 | -------------------------------------------------------------------------------- /deployments/checks/sample-ingress-mod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: example-ingress 5 | annotations: 6 | nginx.ingress.kubernetes.io/rewrite-target: /$1 7 | spec: 8 | rules: 9 | - host: hello-world.net 10 | http: 11 | paths: 12 | - path: / 13 | pathType: Prefix 14 | backend: 15 | service: 16 | name: web 17 | port: 18 | number: 8080 -------------------------------------------------------------------------------- /deployments/checks/sample-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: example-ingress 5 | annotations: 6 | nginx.ingress.kubernetes.io/rewrite-target: /$1 7 | spec: 8 | rules: 9 | - host: hello-world.com 10 | http: 11 | paths: 12 | - path: / 13 | pathType: Prefix 14 | backend: 15 | service: 16 | name: web 17 | port: 18 | number: 8080 -------------------------------------------------------------------------------- /deployments/deployment/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | data: 4 | nginx-hosts: "https://10.0.0.1:9000/api" 5 | tls-mode: "no-tls" 6 | ca-certificate: "" 7 | client-certificate: "" 8 | log-level: "warn" 9 | metadata: 10 | name: nlk-config 11 | namespace: nlk 12 | -------------------------------------------------------------------------------- /deployments/deployment/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: nlk-deployment 5 | namespace: nlk 6 | labels: 7 | app: nlk 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: nlk 13 | template: 14 | metadata: 15 | labels: 16 | app: nlk 17 | spec: 18 | containers: 19 | - name: nginx-loadbalancer-kubernetes 20 | image: ghcr.io/nginxinc/nginx-loadbalancer-kubernetes:latest 21 | imagePullPolicy: Always 22 | ports: 23 | - name: http 24 | containerPort: 51031 25 | protocol: TCP 26 | livenessProbe: 27 | httpGet: 28 | path: /livez 29 | port: 51031 30 | initialDelaySeconds: 5 31 | periodSeconds: 2 32 | readinessProbe: 33 | httpGet: 34 | path: /readyz 35 | port: 51031 36 | initialDelaySeconds: 5 37 | periodSeconds: 2 38 | serviceAccountName: nginx-loadbalancer-kubernetes 39 | -------------------------------------------------------------------------------- /deployments/deployment/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: nlk 5 | labels: 6 | name: nlk 7 | -------------------------------------------------------------------------------- /deployments/rbac/apply.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pushd "$(dirname "$0")" 4 | 5 | echo "Applying all RBAC resources..." 6 | 7 | kubectl apply -f serviceaccount.yaml 8 | kubectl apply -f clusterrole.yaml 9 | kubectl apply -f clusterrolebinding.yaml 10 | kubectl apply -f secret.yaml 11 | 12 | popd 13 | -------------------------------------------------------------------------------- /deployments/rbac/clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: resource-get-watch-list 5 | namespace: nlk 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: ["services", "nodes", "configmaps", "secrets"] 10 | verbs: ["get", "watch", "list"] 11 | -------------------------------------------------------------------------------- /deployments/rbac/clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: "nginx-loadbalancer-kubernetes:resource-get-watch-list" 5 | namespace: nlk 6 | subjects: 7 | - kind: ServiceAccount 8 | name: nginx-loadbalancer-kubernetes 9 | namespace: nlk 10 | roleRef: 11 | kind: ClusterRole 12 | name: resource-get-watch-list 13 | apiGroup: rbac.authorization.k8s.io 14 | -------------------------------------------------------------------------------- /deployments/rbac/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: nginx-loadbalancer-kubernetes-secret 5 | namespace: nlk 6 | annotations: 7 | kubernetes.io/service-account.name: nginx-loadbalancer-kubernetes 8 | type: kubernetes.io/service-account-token 9 | -------------------------------------------------------------------------------- /deployments/rbac/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: nginx-loadbalancer-kubernetes 5 | namespace: nlk 6 | -------------------------------------------------------------------------------- /deployments/rbac/unapply.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Unapplying all RBAC resources..." 4 | 5 | kubectl delete -f serviceaccount.yaml 6 | kubectl delete -f clusterrole.yaml 7 | kubectl delete -f clusterrolebinding.yaml 8 | kubectl delete -f secret.yaml 9 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package kubernetes_nginx_ingress 7 | -------------------------------------------------------------------------------- /docs/DEMO/SLIDE-1.md: -------------------------------------------------------------------------------- 1 | # TLS in NLK 2 | 3 | NGINX Loadbalancer for Kubernetes, or NLK, is a Kubernetes Controller that uses NGINX Plus to load balance traffic to Kubernetes Services. 4 | 5 | [Next](SLIDE-2.md) -------------------------------------------------------------------------------- /docs/DEMO/SLIDE-2.md: -------------------------------------------------------------------------------- 1 | ## What is TLS? 2 | 3 | Transport Layer Security, or TLS, is a cryptographic protocol that provides end-to-end security of data sent between applications over the Internet; TLS is the successor to Secure Sockets Layer, or SSL. 4 | 5 | TLS serves two primary purposes: 6 | * Authentications of actors in a communication channel; 7 | * Encryption of data sent between actors in a communication channel; 8 | 9 | [Next](SLIDE-3.md) -------------------------------------------------------------------------------- /docs/DEMO/SLIDE-3.md: -------------------------------------------------------------------------------- 1 | ## TLS uses Certificates 2 | 3 | A Transport Layer Security (TLS) certificate, also known as an SSL certificate (Secure Sockets Layer), is a digital document that plays a crucial role in securing internet communications. Imagine it as a special, electronic passport for websites. 4 | 5 | A TLS certificate includes a "Chain of Trust" where there are multiple certificates that are used to verify the authenticity of the certificate. The certificate at the top of the chain is called the Root Certificate Authority (CA) Certificate. The Root CA Certificate is used to sign the certificates below it in the chain. The Root CA Certificate is not signed by any other certificate in the chain. 6 | 7 | The Root CA Certificate is used to sign the Intermediate CA Certificate. The Intermediate CA Certificate is used to sign the TLS Certificate. The TLS Certificate is used to sign the TLS Certificate Signing Request (CSR). The TLS Certificate Signing Request is used to sign the TLS Certificate. 8 | 9 | Root CA certificates can be expensive and are not required for most use cases. An alternative to purchasing an Intermediate CA certificate is to use a self-signed certificate. Self-signed certificates are free and can be used to sign TLS certificates. 10 | 11 | [Next](SLIDE-4.md) -------------------------------------------------------------------------------- /docs/DEMO/SLIDE-4.md: -------------------------------------------------------------------------------- 1 | ## One-way TLS and Mutual TLS 2 | 3 | There are two types of TLS: one-way TLS and mutual TLS. 4 | 5 | ### One-way TLS 6 | 7 | One-way TLS is the most common type of TLS. In one-way TLS, the client verifies the server's identity, but the server does not verify the client's identity. One-way TLS is used to secure the connection between the client and the server. 8 | 9 | ### Mutual TLS 10 | 11 | Mutual TLS is less common than one-way TLS. In mutual TLS, the client verifies the server's identity, and the server verifies the client's identity. Mutual TLS is used to secure the connection between the client and the server. 12 | 13 | The following diagram shows the difference between one-way TLS and mutual TLS. 14 | 15 | ```mermaid 16 | graph LR 17 | CACertificate[CA Certificate] 18 | 19 | subgraph "One-way TLS" 20 | NGINXPlusCert[NGINX Plus Certificate] 21 | NLK[nginx-loadbalancer-kubernetes] 22 | NGINXPlus[NGINX Plus] 23 | NGINXPlusCert -->|Used by| NLK 24 | NLK -->|Verifies| NGINXPlus 25 | end 26 | 27 | subgraph "Mutual TLS" 28 | NLKCert[NLK Certificate] 29 | MNGINXPlusCert[NGINX Plus Certificate] 30 | MNLK[nginx-loadbalancer-kubernetes] 31 | MNGINXPlus[NGINX Plus] 32 | NLKCert -->|Used by| MNGINXPlus 33 | MNGINXPlus -->|Verifies| MNLK 34 | MNGINXPlusCert -->|Used by| MNLK 35 | MNLK -->|Verifies| MNGINXPlus 36 | end 37 | 38 | CACertificate -->|Used for Signing| NLKCert 39 | CACertificate -->|Used for Signing| NGINXPlusCert 40 | CACertificate -->|Used for Signing| MNGINXPlusCert 41 | ``` 42 | 43 | [Next](SLIDE-5.md) -------------------------------------------------------------------------------- /docs/DEMO/SLIDE-5.md: -------------------------------------------------------------------------------- 1 | ## TLS in NLK 2 | 3 | NLK supports three options for securing communications between the NLK and NGINX Plus: 4 | 5 | 1. No TLS 6 | 2. TLS with self-signed certificates 7 | 3. TLS with certificates signed by a Certificate Authority (CA) 8 | 9 | Within the TLS options there are two sub-options: 10 | 11 | 1. One-way TLS 12 | 2. Mutual TLS 13 | 14 | This gives five options for securing communications between the NLK and NGINX Plus. 15 | 16 | * No TLS: No authentication nor encryption is used. 17 | * One-way TLS with self-signed certificates: The NLK verifies the NGINX Plus's identity, but the NGINX Plus does not verify the NLK's identity. 18 | * One-way TLS with certificates signed by a CA: The NLK verifies the NGINX Plus's identity, but the NGINX Plus does not verify the NLK's identity. 19 | * Mutual TLS with self-signed certificates: The NLK verifies the NGINX Plus's identity, and the NGINX Plus verifies the NLK's identity. 20 | * Mutual TLS with certificates signed by a CA: The NLK verifies the NGINX Plus's identity, and the NGINX Plus verifies the NLK's identity. 21 | 22 | [Next](SLIDE-6.md) -------------------------------------------------------------------------------- /docs/DEMO/SLIDE-6.md: -------------------------------------------------------------------------------- 1 | ### Configuring TLS in NLK 2 | 3 | NLK uses a Kubernetes ConfigMap to configure TLS. The ConfigMap is named `nlk-config` and is located in the `nlk` namespace. 4 | 5 | There are three fields in the ConfigMap that are used to configure TLS: 6 | * tls-mode: The TLS mode to use. Valid values are `none`, `ss-tls`, `ss-mtls`, `ca-tls`, and `ca-mtls`. 7 | * ca-certificate: The CA certificate to use. This field is only used when `tls-mode` is set to `ss-tls` or `ss-mtls`. This certificate contains the "Chain of Trust" that is used to verify the authenticity of the TLS certificate. 8 | * client-certificate: The client certificate to use. This field is only used when `tls-mode` is set to `ss-mtls` or `ca-mtls`. This certificate is provided to the NGINX Plus hosts for client authentication. 9 | 10 | The fields required depend on the `tls-mode` value. 11 | 12 | [Next](SLIDE-7.md) -------------------------------------------------------------------------------- /docs/DEMO/SLIDE-7.md: -------------------------------------------------------------------------------- 1 | ### How NLK uses TLS 2 | 3 | NLK uses the `github.com/nginxinc/nginx-plus-go-client` to communicate with the NGINX Plus API. This library accepts a low-level HTTP client that is used for the actual communication with the NGINX Plus hosts. NLK generates a TLS Configuration based on the configured `tls-mode` and provides it to the low-level HTTP client. 4 | 5 | [Next](SLIDE-8.md) -------------------------------------------------------------------------------- /docs/DEMO/SLIDE-8.md: -------------------------------------------------------------------------------- 1 | # Demo 2 | 3 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # nginx-loadbalancer-kubernetes 2 | 3 |
4 | 5 | # Welcome to the Nginx LoadBalancer for Kubernetes Solution! 6 | 7 |
8 | 9 | ![Nginx K8s LB](media/nlk-logo.png) | ![Nginx K8s LB](media/nginx-2020.png) 10 | --- | --- 11 | 12 |
13 | 14 | This repo contains source code and documents for a new `Kubernetes Controller from Nginx`, that provides TCP and HTTP load balancing external to a Kubernetes Cluster running On Premises. 15 | 16 |
17 | 18 | >>**This is a replacement for a Cloud Providers `Service Type Loadbalancer`, that is not available for On Premises Kubernetes Clusters.** 19 | 20 |
21 |
22 | 23 | 24 | # Overview 25 | 26 | - `NLK - NGINX Loadbalancer for Kubernetes` is a new K8s Controller from Nginx, that monitors specified K8s Services, and then sends API calls to an external Nginx Plus server to manage Nginx Upstream servers dynamically. 27 | - This will `synchronize` the K8s Service Endpoint list, with the Nginx LB Server's upstream list. 28 | - The primary use case and Solution provided is for tracking the K8s` NodePort` IP:Port definitions for the Nginx Ingress Controller's `nginx-ingress Service`. 29 | - NLK is a native Kubernetes Controller, running, configured and managed with standard K8s commands. 30 | - NLK paired with the Nginx Plus Server located external to the K8s cluster, this new controller LB function will provide a `TCP Load Balancer Service` for On Premises K8s clusters, which do not have access to a Cloud providers "Service Type LoadBalancer". 31 | - NLK paired with the Nginx Plus Server located external to the Cluster, using Nginx's advanced HTTP features, provide an `HTTP Load Balancer Service` for Enterprise traffic management solutions, such as: 32 | - MultiCluster Active/Active Load Balancing 33 | - Horizontal Cluster Scaling 34 | - HTTP Split Clients - for A/B, Blue/Green, and Canary test and production traffic steering. Allows Cluster operations/maintainence like upgrades, patching, expansion and troubleshooting with no downtime or reloads 35 | - Advanced TLS Processing - MutualTLS, OCSP, FIPS, dynamic cert loading 36 | - Advanced Security features - Oauth, JWT, App Protect WAF Firewall, Rate and Bandwidth limits 37 | - Nginx Java Script (NJS) for custom solutions 38 | - Nginx Zone Sync of KeyVal data 39 | 40 |
41 | 42 | ## NLK Controller Software Design Overview - How it works 43 | 44 | [NLK Controller DESIGN and Architecture](DESIGN.md) 45 | 46 |
47 | 48 | ## Reference Diagram for NLK TCP Load Balancer Service 49 | 50 |
51 | 52 | ![NLK Stream Diagram](media/nlk-blog-diagram-v1.png) 53 | 54 |
55 | 56 | ## Sample Screenshots of Solution at Runtime 57 | 58 |
59 | 60 | ![NGINX LB ConfigMap](media/nlk-configmap.png) 61 | ### ConfigMap with 2 Nginx LB Servers defined for HA 62 | 63 |
64 | 65 | ![NGINX LB Create Nodeport](media/nlk-stream-create-nodeport.png) 66 | ### Nginx LB Server Dashboard, NodePort, and NLK Controller Logging 67 | 68 | ### Legend: 69 | - Red - kubectl nodeport commands 70 | - Blue - nodeport and upstreams for http traffic 71 | - Indigo - nodeport and upstreams for https traffic 72 | - Green - NLK log for api calls to LB Server #1 73 | - Orange - Nginx LB Server upstream dashboard details 74 | - Kubernetes Worker Nodes are 10.1.1.8 and 10.1.1.10 75 | 76 |
77 | 78 | The `Installation Guide` for TCP Loadbalancer Solution is located in the tcp folder: 79 | 80 | [TCP Installation Guide](tcp/tcp-installation-guide.md) 81 | 82 |
83 | 84 | The `Installation Guide` for HTTP Loadbalancer Solution is located in the http folder: 85 | 86 | [HTTP Installation Guide](http/http-installation-guide.md) 87 | 88 |
89 | 90 | ## Requirements 91 | 92 | Please see the /docs folder and Installation Guides for detailed documentation. 93 | 94 |
95 | 96 | ## Development 97 | 98 | Read the [`CONTRIBUTING.md`](https://github.com/nginxinc/nginx-loadbalancer-kubernetes/blob/main/CONTRIBUTING.md) file. 99 | 100 |
101 | 102 | ## Authors 103 | - Chris Akker - Solutions Architect - Community and Alliances @ F5, Inc. 104 | - Steve Wagner - Solutions Architect - Community and Alliances @ F5, Inc. 105 | 106 |
107 | 108 | ## License 109 | 110 | [Apache License, Version 2.0](https://github.com/nginxinc/nginx-loadbalancer-kubernetes/blob/main/LICENSE) 111 | 112 | © [F5 Networks, Inc.](https://www.f5.com/) 2023 113 | -------------------------------------------------------------------------------- /docs/cafe-demo/cafe-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: cafe-secret 5 | type: kubernetes.io/tls 6 | data: 7 | tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURMakNDQWhZQ0NRREFPRjl0THNhWFdqQU5CZ2txaGtpRzl3MEJBUXNGQURCYU1Rc3dDUVlEVlFRR0V3SlYKVXpFTE1Ba0dBMVVFQ0F3Q1EwRXhJVEFmQmdOVkJBb01HRWx1ZEdWeWJtVjBJRmRwWkdkcGRITWdVSFI1SUV4MApaREViTUJrR0ExVUVBd3dTWTJGbVpTNWxlR0Z0Y0d4bExtTnZiU0FnTUI0WERURTRNRGt4TWpFMk1UVXpOVm9YCkRUSXpNRGt4TVRFMk1UVXpOVm93V0RFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVNFd0h3WUQKVlFRS0RCaEpiblJsY201bGRDQlhhV1JuYVhSeklGQjBlU0JNZEdReEdUQVhCZ05WQkFNTUVHTmhabVV1WlhoaApiWEJzWlM1amIyMHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDcDZLbjdzeTgxCnAwanVKL2N5ayt2Q0FtbHNmanRGTTJtdVpOSzBLdGVjcUcyZmpXUWI1NXhRMVlGQTJYT1N3SEFZdlNkd0kyaloKcnVXOHFYWENMMnJiNENaQ0Z4d3BWRUNyY3hkam0zdGVWaVJYVnNZSW1tSkhQUFN5UWdwaW9iczl4N0RsTGM2SQpCQTBaalVPeWwwUHFHOVNKZXhNVjczV0lJYTVyRFZTRjJyNGtTa2JBajREY2o3TFhlRmxWWEgySTVYd1hDcHRDCm42N0pDZzQyZitrOHdnemNSVnA4WFprWldaVmp3cTlSVUtEWG1GQjJZeU4xWEVXZFowZXdSdUtZVUpsc202OTIKc2tPcktRajB2a29QbjQxRUUvK1RhVkVwcUxUUm9VWTNyemc3RGtkemZkQml6Rk8yZHNQTkZ4MkNXMGpYa05MdgpLbzI1Q1pyT2hYQUhBZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFLSEZDY3lPalp2b0hzd1VCTWRMClJkSEliMzgzcFdGeW5acS9MdVVvdnNWQTU4QjBDZzdCRWZ5NXZXVlZycTVSSWt2NGxaODFOMjl4MjFkMUpINnIKalNuUXgrRFhDTy9USkVWNWxTQ1VwSUd6RVVZYVVQZ1J5anNNL05VZENKOHVIVmhaSitTNkZBK0NuT0Q5cm4yaQpaQmVQQ0k1ckh3RVh3bm5sOHl3aWozdnZRNXpISXV5QmdsV3IvUXl1aTlmalBwd1dVdlVtNG52NVNNRzl6Q1Y3ClBwdXd2dWF0cWpPMTIwOEJqZkUvY1pISWc4SHc5bXZXOXg5QytJUU1JTURFN2IvZzZPY0s3TEdUTHdsRnh2QTgKN1dqRWVxdW5heUlwaE1oS1JYVmYxTjM0OWVOOThFejM4Zk9USFRQYmRKakZBL1BjQytHeW1lK2lHdDVPUWRGaAp5UkU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K 8 | tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBcWVpcCs3TXZOYWRJN2lmM01wUHJ3Z0pwYkg0N1JUTnBybVRTdENyWG5LaHRuNDFrCkcrZWNVTldCUU5semtzQndHTDBuY0NObzJhN2x2S2wxd2k5cTIrQW1RaGNjS1ZSQXEzTVhZNXQ3WGxZa1YxYkcKQ0pwaVJ6ejBza0lLWXFHN1BjZXc1UzNPaUFRTkdZMURzcGRENmh2VWlYc1RGZTkxaUNHdWF3MVVoZHErSkVwRwp3SStBM0kreTEzaFpWVng5aU9WOEZ3cWJRcCt1eVFvT05uL3BQTUlNM0VWYWZGMlpHVm1WWThLdlVWQ2cxNWhRCmRtTWpkVnhGbldkSHNFYmltRkNaYkp1dmRySkRxeWtJOUw1S0Q1K05SQlAvazJsUkthaTAwYUZHTjY4NE93NUgKYzMzUVlzeFR0bmJEelJjZGdsdEkxNURTN3lxTnVRbWF6b1Z3QndJREFRQUJBb0lCQVFDUFNkU1luUXRTUHlxbApGZlZGcFRPc29PWVJoZjhzSStpYkZ4SU91UmF1V2VoaEp4ZG01Uk9ScEF6bUNMeUw1VmhqdEptZTIyM2dMcncyCk45OUVqVUtiL1ZPbVp1RHNCYzZvQ0Y2UU5SNThkejhjbk9SVGV3Y290c0pSMXBuMWhobG5SNUhxSkpCSmFzazEKWkVuVVFmY1hackw5NGxvOUpIM0UrVXFqbzFGRnM4eHhFOHdvUEJxalpzVjdwUlVaZ0MzTGh4bndMU0V4eUZvNApjeGI5U09HNU9tQUpvelN0Rm9RMkdKT2VzOHJKNXFmZHZ5dGdnOXhiTGFRTC94MGtwUTYyQm9GTUJEZHFPZVBXCktmUDV6WjYvMDcvdnBqNDh5QTFRMzJQem9idWJzQkxkM0tjbjMyamZtMUU3cHJ0V2wrSmVPRmlPem5CUUZKYk4KNHFQVlJ6NWhBb0dCQU50V3l4aE5DU0x1NFArWGdLeWNrbGpKNkY1NjY4Zk5qNUN6Z0ZScUowOXpuMFRsc05ybwpGVExaY3hEcW5SM0hQWU00MkpFUmgySi9xREZaeW5SUW8zY2czb2VpdlVkQlZHWTgrRkkxVzBxZHViL0w5K3l1CmVkT1pUUTVYbUdHcDZyNmpleHltY0ppbS9Pc0IzWm5ZT3BPcmxEN1NQbUJ2ek5MazRNRjZneGJYQW9HQkFNWk8KMHA2SGJCbWNQMHRqRlhmY0tFNzdJbUxtMHNBRzR1SG9VeDBlUGovMnFyblRuT0JCTkU0TXZnRHVUSnp5K2NhVQprOFJxbWRIQ2JIelRlNmZ6WXEvOWl0OHNaNzdLVk4xcWtiSWN1YytSVHhBOW5OaDFUanNSbmU3NFowajFGQ0xrCmhIY3FIMHJpN1BZU0tIVEU4RnZGQ3haWWRidUI4NENtWmlodnhicFJBb0dBSWJqcWFNWVBUWXVrbENkYTVTNzkKWVNGSjFKelplMUtqYS8vdER3MXpGY2dWQ0thMzFqQXdjaXowZi9sU1JxM0hTMUdHR21lemhQVlRpcUxmZVpxYwpSMGlLYmhnYk9jVlZrSkozSzB5QXlLd1BUdW14S0haNnpJbVpTMGMwYW0rUlk5WUdxNVQ3WXJ6cHpjZnZwaU9VCmZmZTNSeUZUN2NmQ21mb09oREN0enVrQ2dZQjMwb0xDMVJMRk9ycW40M3ZDUzUxemM1em9ZNDR1QnpzcHd3WU4KVHd2UC9FeFdNZjNWSnJEakJDSCtULzZzeXNlUGJKRUltbHpNK0l3eXRGcEFOZmlJWEV0LzQ4WGY2ME54OGdXTQp1SHl4Wlp4L05LdER3MFY4dlgxUE9ucTJBNWVpS2ErOGpSQVJZS0pMWU5kZkR1d29seHZHNmJaaGtQaS80RXRUCjNZMThzUUtCZ0h0S2JrKzdsTkpWZXN3WEU1Y1VHNkVEVXNEZS8yVWE3ZlhwN0ZjanFCRW9hcDFMU3crNlRYcDAKWmdybUtFOEFSek00NytFSkhVdmlpcS9udXBFMTVnMGtKVzNzeWhwVTl6WkxPN2x0QjBLSWtPOVpSY21Vam84UQpjcExsSE1BcWJMSjhXWUdKQ2toaVd4eWFsNmhZVHlXWTRjVmtDMHh0VGwvaFVFOUllTktvCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== 9 | -------------------------------------------------------------------------------- /docs/cafe-demo/cafe-virtualserver.yaml: -------------------------------------------------------------------------------- 1 | #Example virtual server with routes for Cafe Demo 2 | #For NLK Solution, redirects required for LB Server health checks 3 | #Chris Akker, Apr 2023 4 | # 5 | apiVersion: k8s.nginx.org/v1 6 | kind: VirtualServer 7 | metadata: 8 | name: cafe-vs 9 | spec: 10 | host: cafe.example.com 11 | tls: 12 | secret: cafe-secret 13 | redirect: 14 | enable: true #Redirect from http > https 15 | code: 301 16 | upstreams: 17 | - name: tea 18 | service: tea-svc 19 | port: 80 20 | lb-method: round_robin 21 | slow-start: 20s 22 | healthCheck: 23 | enable: true 24 | path: /tea 25 | interval: 20s 26 | jitter: 3s 27 | fails: 5 28 | passes: 2 29 | connect-timeout: 30s 30 | read-timeout: 20s 31 | - name: coffee 32 | service: coffee-svc 33 | port: 80 34 | lb-method: round_robin 35 | healthCheck: 36 | enable: true 37 | path: /coffee 38 | interval: 10s 39 | jitter: 3s 40 | fails: 3 41 | passes: 2 42 | connect-timeout: 30s 43 | read-timeout: 20s 44 | routes: 45 | - path: / 46 | action: 47 | redirect: 48 | url: https://cafe.example.com/coffee 49 | code: 302 #Redirect from / > /coffee 50 | - path: /tea 51 | action: 52 | pass: tea 53 | - path: /coffee 54 | action: 55 | pass: coffee 56 | -------------------------------------------------------------------------------- /docs/cafe-demo/cafe.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: coffee 5 | spec: 6 | replicas: 3 7 | selector: 8 | matchLabels: 9 | app: coffee 10 | template: 11 | metadata: 12 | labels: 13 | app: coffee 14 | spec: 15 | containers: 16 | - name: coffee 17 | image: nginxinc/ingress-demo # upgraded Cafe Docker image 18 | ports: 19 | - containerPort: 80 20 | --- 21 | apiVersion: v1 22 | kind: Service 23 | metadata: 24 | name: coffee-svc 25 | spec: 26 | type: ClusterIP 27 | clusterIP: None 28 | ports: 29 | - port: 80 30 | targetPort: 80 31 | protocol: TCP 32 | name: http 33 | selector: 34 | app: coffee 35 | --- 36 | apiVersion: apps/v1 37 | kind: Deployment 38 | metadata: 39 | name: tea 40 | spec: 41 | replicas: 3 42 | selector: 43 | matchLabels: 44 | app: tea 45 | template: 46 | metadata: 47 | labels: 48 | app: tea 49 | spec: 50 | containers: 51 | - name: tea 52 | image: nginxinc/ingress-demo 53 | ports: 54 | - containerPort: 80 55 | --- 56 | apiVersion: v1 57 | kind: Service 58 | metadata: 59 | name: tea-svc 60 | spec: 61 | type: ClusterIP 62 | clusterIP: None 63 | ports: 64 | - port: 80 65 | targetPort: 80 66 | protocol: TCP 67 | name: http 68 | selector: 69 | app: tea 70 | -------------------------------------------------------------------------------- /docs/http/clusters.conf: -------------------------------------------------------------------------------- 1 | # NGINX Loadbalancer for K8s HTTP configuration, for L7 load balancing 2 | # Chris Akker, Apr 2023 3 | # HTTP Proxy and load balancing 4 | # MultiCluster Load Balancing with http split clients 0-100% 5 | # Upstream servers managed by NLK Controller 6 | # Nginx Key Value store for Split ratios 7 | # 8 | #### clusters.conf 9 | 10 | # Define Key Value store, backup state file, timeout, and enable sync 11 | 12 | keyval_zone zone=split:1m state=/var/lib/nginx/state/split.keyval timeout=365d sync; 13 | keyval $host $split_level zone=split; 14 | 15 | # Main Nginx Server Block for cafe.example.com, with TLS 16 | 17 | server { 18 | listen 443 ssl; 19 | status_zone https://cafe.example.com; 20 | server_name cafe.example.com; 21 | 22 | ssl_certificate /etc/ssl/nginx/default.crt; # self-signed for example only 23 | ssl_certificate_key /etc/ssl/nginx/default.key; 24 | 25 | location / { 26 | status_zone /; 27 | 28 | proxy_set_header Host $host; 29 | proxy_http_version 1.1; 30 | proxy_set_header "Connection" ""; 31 | proxy_pass https://$upstream; 32 | 33 | } 34 | 35 | location @health_check_cluster1_cafe { 36 | 37 | health_check interval=10 match=cafe; 38 | proxy_connect_timeout 2s; 39 | proxy_read_timeout 3s; 40 | proxy_set_header Host cafe.example.com; 41 | proxy_pass https://cluster1-https; 42 | } 43 | 44 | location @health_check_cluster2_cafe { 45 | 46 | health_check interval=10 match=cafe; 47 | proxy_connect_timeout 2s; 48 | proxy_read_timeout 3s; 49 | proxy_set_header Host cafe.example.com; 50 | proxy_pass https://cluster2-https; 51 | } 52 | } 53 | 54 | match cafe { 55 | status 200-399; 56 | } 57 | 58 | # Cluster1 upstreams 59 | 60 | upstream cluster1-https { 61 | zone cluster1-https 256k; 62 | least_time last_byte; 63 | keepalive 16; 64 | #servers managed by NLK Controller 65 | state /var/lib/nginx/state/cluster1-https.state; 66 | } 67 | 68 | # Cluster2 upstreams 69 | 70 | upstream cluster2-https { 71 | zone cluster2-https 256k; 72 | least_time last_byte; 73 | #servers managed by NLK Controller 74 | state /var/lib/nginx/state/cluster2-https.state; 75 | } 76 | 77 | # HTTP Split Clients Configuration for Cluster1/Cluster2 ratios 78 | 79 | split_clients $request_id $split0 { 80 | * cluster2-https; 81 | } 82 | 83 | split_clients $request_id $split1 { 84 | 1.0% cluster1-https; 85 | * cluster2-https; 86 | } 87 | 88 | split_clients $request_id $split5 { 89 | 5.0% cluster1-https; 90 | * cluster2-https; 91 | } 92 | 93 | split_clients $request_id $split10 { 94 | 10% cluster1-https; 95 | * cluster2-https; 96 | } 97 | 98 | split_clients $request_id $split25 { 99 | 25% cluster1-https; 100 | * cluster2-https; 101 | } 102 | 103 | split_clients $request_id $split50 { 104 | 50% cluster1-https; 105 | * cluster2-https; 106 | } 107 | 108 | split_clients $request_id $split75 { 109 | 75% cluster1-https; 110 | * cluster2-https; 111 | } 112 | 113 | split_clients $request_id $split90 { 114 | 90% cluster1-https; 115 | * cluster2-https; 116 | } 117 | 118 | split_clients $request_id $split95 { 119 | 95% cluster1-https; 120 | * cluster2-https; 121 | } 122 | 123 | split_clients $request_id $split99 { 124 | 99% cluster1-https; 125 | * cluster2-https; 126 | } 127 | 128 | split_clients $request_id $split100 { 129 | * cluster1-https; 130 | } 131 | 132 | map $split_level $upstream { 133 | 0 $split0; 134 | 1.0 $split1; 135 | 5.0 $split5; 136 | 10 $split10; 137 | 25 $split25; 138 | 50 $split50; 139 | 75 $split75; 140 | 90 $split90; 141 | 95 $split95; 142 | 99 $split99; 143 | 100 $split100; 144 | default $split50; 145 | } 146 | -------------------------------------------------------------------------------- /docs/http/dashboard.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 9000; 3 | 4 | location /api { 5 | api write=on; 6 | } 7 | 8 | location = /dashboard.html { 9 | root /usr/share/nginx/html; 10 | } 11 | 12 | # Redirect requests for "/" to "/dashboard.html" 13 | location / { 14 | return 301 /dashboard.html; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /docs/http/http-multicluster-overview.md: -------------------------------------------------------------------------------- 1 | 2 | ## NLK and Kuberentes MultiCluster Load Balancing with HTTP/S 3 | 4 |
5 | 6 | ![Kubernetes](../media/kubernetes-icon.png) | ![NLK](../media/nlk-logo.png)| ![NGINX Plus](../media/nginx-plus-icon.png) 7 | --- | --- | --- 8 | 9 |
10 | 11 | ## Overview 12 | 13 |
14 | 15 | With the NGINX Plus Servers located external to the Cluster, using NGINX's advanced HTTP/S features provide Enterprise class traffic management Solutions. Using NGINX in HTTP mode allows for many solutions, some of are: 16 | 17 | - MultiCluster Active/Active Load Balancing 18 | - Horizontal Cluster Scaling 19 | - HTTP Split Clients - for `A/B, Blue/Green, and Canary` test and production traffic steering. Allows Cluster operations/maintainence like: 20 | - Node upgrades / additions 21 | - Software upgrades/security patches 22 | - Cluster resource expansions - memory, compute, storage, network, nodes 23 | - Troubleshooting, `using Live Traffic if needed` 24 | - ^^ With NO downtime or reloads 25 | - NGINX Zone Sync of KeyVal data 26 | - API Gateway functions 27 | - Advanced TLS Processing - MutualTLS, OCSP, FIPS, dynamic cert loading 28 | - Advanced Security features - App Protect WAF Firewall, Oauth, OIDC/JWT, Dynamic Rate and Bandwidth limits, GeoIP, IP block/allow lists 29 | - NGINX Java Script (NJS) for custom solutions 30 | 31 |
32 | 33 | ## Reference Diagram for NGINX NLK HTTP MultiCluster Load Balancing Solution 34 | 35 |
36 | 37 | Multiple K8s Clusters, HA NGINX Plus LB Servers, NLK Controllers 38 | 39 | ![NLK MultiCluster Diagram](../media/nlk-multicluster-config.png) 40 | 41 | 42 |
43 | 44 | NLK Controller watching nginx-ingress Service and Updating HTTP Upstreams; for Service Type Loadbalancer or NodePort: 45 | 46 | ![NLK MultiCluster LoadBalancer](../media/nlk-cluster1-add-loadbalancer.png) 47 | or 48 | ![NLK MultiCluster NodePort](../media/nlk-cluster1-add-nodeport.png) 49 | 50 |
51 | 52 | MultiCluster Load Balancing 53 | 54 | ![NLK MultiCluster Dashboard](../media/nlk-multicluster-upstreams.png) 55 | 56 |
57 | 58 | NGINX HTTP Split Clients with Dynamic Ratio -- 10% Cluster1 : 90% Cluster2 59 | 60 | ![NGINX HTTP Split 10](../media/nlk-clusters-10.png) 61 | 62 | - NGINX Zone Sync of KeyVal data 63 | - API Gateway functions 64 | - Advanced TLS Processing - MutualTLS, OCSP, FIPS, dynamic cert loading 65 | - Advanced Security features - App Protect WAF Firewall, Oauth, JWT, Dynamic Rate and Bandwidth limits, GeoIP, IP block/allow lists 66 | - NGINX Java Script (NJS) for custom solutions 67 | 68 |
69 | 70 | ## Reference Diagram for NLK HTTP MultiCluster Load Balancing Solution 71 | 72 |
73 | 74 | Multiple K8s Clusters, HA NGINX Plus LB Servers, NLK Controllers 75 | 76 | ![NLK MultiCluster Diagram](../media/nlk-multicluster-config.png) 77 | 78 | 79 |
80 | 81 | NLK Watching nginx-ingress Service and Updating HTTP Upstreams; Service Type Loadbalancer or NodePort: 82 | 83 | ![NLK MultiCluster LoadBalancer](../media/nlk-cluster1-add-loadbalancer.png) 84 | or 85 | ![NLK MultiCluster NodePort](../media/nlk-cluster1-add-nodeport.png) 86 | 87 |
88 | 89 | MultiCluster Load Balancing 90 | 91 | ![NLK MultiCluster Dashboard](../media/nlk-multicluster-upstreams.png) 92 | 93 |
94 | 95 | NGINX HTTP Split Clients with Dynamic Ratio -- 10% Cluster1 : 90% Cluster2 96 | 97 | ![NGINX Grafana Split 10](../media/nlk-clusters-10.png) 98 | 99 |
100 | 101 | ### Grafana Charts - Examples showing 10, 90, 50% Split Ratios 102 | 103 |
104 | 105 | NGINX HTTP Split Clients with Dynamic Ratio -- 10% Cluster1 : 90% Cluster2 106 | 107 | ![NGINX HTTP Split 10](../media/nlk-grafana-reqs-10.png) 108 | 109 |
110 | 111 | NGINX HTTP Split Clients with Dynamic Ratio -- 90% Cluster1 : 10% Cluster2 112 | 113 | ![NGINX Grafana Split 10](../media/nlk-grafana-reqs-90.png) 114 |
115 | 116 | NGINX HTTP Split Clients with Dynamic Ratio -- 50% Cluster1 : 50% Cluster2 117 | 118 | ![NGINX Grafana Split 10](../media/nlk-grafana-reqs-50.png) 119 | 120 |
121 | 122 | The `Installation Guide` for HTTP MultiCluster Solution is located in the docs/http folder: 123 | 124 | [HTTP MultiCluster Loadbalancing Guide](../http/http-installation-guide.md) 125 | 126 |
127 | 128 | ## Authors 129 | - Chris Akker - Solutions Architect - Community and Alliances @ F5, Inc. 130 | - Steve Wagner - Solutions Architect - Community and Alliances @ F5, Inc. 131 | -------------------------------------------------------------------------------- /docs/http/loadbalancer-cluster1.yaml: -------------------------------------------------------------------------------- 1 | # NLK LoadBalancer Service file 2 | # Spec -ports name must be in the format of 3 | # nlk- 4 | # externalIPs are set to Nginx LB Servers 5 | # Chris Akker, Apr 2023 6 | # 7 | apiVersion: v1 8 | kind: Service 9 | metadata: 10 | name: nginx-ingress 11 | namespace: nginx-ingress 12 | annotations: 13 | nginxinc.io/nlk-cluster1-https: "http" # Must be added 14 | spec: 15 | type: LoadBalancer 16 | externalIPs: 17 | - 10.1.1.4 18 | - 10.1.1.5 19 | ports: 20 | - port: 443 21 | targetPort: 443 22 | protocol: TCP 23 | name: nlk-cluster1-https # Must match Nginx upstream name 24 | selector: 25 | app: nginx-ingress 26 | -------------------------------------------------------------------------------- /docs/http/loadbalancer-cluster2.yaml: -------------------------------------------------------------------------------- 1 | # NLK LoadBalancer Service file 2 | # Spec -ports name must be in the format of 3 | # nlk- 4 | # externalIPs are set to Nginx LB Servers 5 | # Chris Akker, Apr 2023 6 | # 7 | apiVersion: v1 8 | kind: Service 9 | metadata: 10 | name: nginx-ingress 11 | namespace: nginx-ingress 12 | annotations: 13 | nginxinc.io/nlk-cluster2-https: "http" # Must be added 14 | spec: 15 | type: LoadBalancer 16 | externalIPs: 17 | - 10.1.1.4 18 | - 10.1.1.5 19 | ports: 20 | - port: 443 21 | targetPort: 443 22 | protocol: TCP 23 | name: nlk-cluster2-https # Must match Nginx upstream name 24 | selector: 25 | app: nginx-ingress 26 | -------------------------------------------------------------------------------- /docs/http/nginx.conf: -------------------------------------------------------------------------------- 1 | # NGINX Loadbalancer for K8s Solution 2 | # Chris Akker, Apr 2023 3 | # Example nginx.conf 4 | # Enable NJS module for Prometheus, increase output buffer size 5 | # Enable Stream context, add /var/log/nginx/stream.log 6 | # 7 | user nginx; 8 | worker_processes auto; 9 | 10 | error_log /var/log/nginx/error.log notice; 11 | pid /var/run/nginx.pid; 12 | 13 | load_module modules/ngx_http_js_module.so; # Added for Prometheus 14 | 15 | worker_rlimit_nofile 2048; 16 | 17 | events { 18 | worker_connections 2048; 19 | } 20 | 21 | 22 | http { 23 | include /etc/nginx/mime.types; 24 | default_type application/octet-stream; 25 | 26 | log_format main '$remote_addr - $upstream_addr - $upstream_status - $remote_user [$time_local] $host - "$request" ' 27 | '$status $body_bytes_sent "$http_referer" ' 28 | '"$http_user_agent" "$http_x_forwarded_for"'; 29 | 30 | access_log /var/log/nginx/access.log main; 31 | 32 | sendfile on; 33 | #tcp_nopush on; 34 | 35 | keepalive_timeout 65; 36 | 37 | #gzip on; 38 | 39 | include /etc/nginx/conf.d/*.conf; 40 | 41 | subrequest_output_buffer_size 32k; #added for Prometheus 42 | 43 | } 44 | 45 | # TCP load balancing block 46 | # 47 | stream { 48 | 49 | include /etc/nginx/stream/*.conf; 50 | 51 | log_format stream '$remote_addr - $server_addr [$time_local] $status $upstream_addr $upstream_bytes_sent'; 52 | 53 | access_log /var/log/nginx/stream.log stream; 54 | } 55 | -------------------------------------------------------------------------------- /docs/http/nodeport-cluster1.yaml: -------------------------------------------------------------------------------- 1 | # NLK Nodeport Service file 2 | # NodePort -ports name must be in the format of 3 | # nlk- 4 | # The nginxinc.io Annotation must be added 5 | # Chris Akker, Apr 2023 6 | # 7 | apiVersion: v1 8 | kind: Service 9 | metadata: 10 | name: nginx-ingress 11 | namespace: nginx-ingress 12 | annotations: 13 | nginxinc.io/nlk-cluster1-https: "http" # Must be added 14 | spec: 15 | type: NodePort 16 | ports: 17 | - port: 443 18 | targetPort: 443 19 | protocol: TCP 20 | name: nlk-cluster1-https 21 | selector: 22 | app: nginx-ingress 23 | -------------------------------------------------------------------------------- /docs/http/nodeport-cluster2.yaml: -------------------------------------------------------------------------------- 1 | # NLK Nodeport Service file 2 | # NodePort -ports name must be in the format of 3 | # nlk- 4 | # The nginxinc.io Annotation must be added 5 | # Chris Akker, Apr 2023 6 | # 7 | apiVersion: v1 8 | kind: Service 9 | metadata: 10 | name: nginx-ingress 11 | namespace: nginx-ingress 12 | annotations: 13 | nginxinc.io/nlk-cluster2-https: "http" # Must be added 14 | spec: 15 | type: NodePort 16 | ports: 17 | - port: 443 18 | targetPort: 443 19 | protocol: TCP 20 | name: nlk-cluster2-https 21 | selector: 22 | app: nginx-ingress 23 | -------------------------------------------------------------------------------- /docs/http/prometheus.conf: -------------------------------------------------------------------------------- 1 | # NGINX Loadbalancer for K8s - Prometheus configuration, for HTTP scraper page 2 | # Chris Akker, Apr 2023 3 | # https://www.nginx.com/blog/how-to-visualize-nginx-plus-with-prometheus-and-grafana/ 4 | # 5 | #### prometheus.conf 6 | 7 | js_import /usr/share/nginx-plus-module-prometheus/prometheus.js; 8 | 9 | server { 10 | listen 9113; 11 | status_zone prometheus; 12 | 13 | location = /metrics { 14 | js_content prometheus.metrics; 15 | } 16 | 17 | location /api { 18 | api; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /docs/http/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 15s 3 | 4 | external_labels: 5 | monitor: 'codelab-monitor' 6 | 7 | scrape_configs: 8 | - job_name: 'prometheus' 9 | 10 | scrape_interval: 5s 11 | 12 | static_configs: 13 | - targets: ['10.1.1.4:9113', '10.1.1.5:9113'] 14 | -------------------------------------------------------------------------------- /docs/http/single-cluster.conf: -------------------------------------------------------------------------------- 1 | # NGINX Loadbalancer for K8s HTTP configuration, for L7 load balancing 2 | # Chris Akker, Oct 2023 3 | # HTTP Proxy and load balancing 4 | # Single Cluster Load Balancing 5 | # Upstream servers managed by NLK Controller 6 | # 7 | #### single-clusters.conf 8 | 9 | # Main Nginx Server Block for cafe.example.com, with TLS 10 | 11 | server { 12 | listen 443 ssl; 13 | status_zone https://cafe.example.com; 14 | server_name cafe.example.com; 15 | 16 | ssl_certificate /etc/ssl/nginx/default.crt; # self-signed for example only 17 | ssl_certificate_key /etc/ssl/nginx/default.key; 18 | 19 | location / { 20 | status_zone /; 21 | 22 | proxy_set_header Host $host; 23 | proxy_http_version 1.1; 24 | proxy_set_header "Connection" ""; 25 | proxy_pass https://cluster1-https; 26 | 27 | } 28 | 29 | location @health_check_cluster1_cafe { 30 | 31 | health_check interval=10 match=cafe; 32 | proxy_connect_timeout 2s; 33 | proxy_read_timeout 3s; 34 | proxy_set_header Host cafe.example.com; 35 | proxy_pass https://cluster1-https; 36 | } 37 | 38 | } 39 | 40 | match cafe { 41 | status 200-399; 42 | } 43 | 44 | # Cluster1 upstreams 45 | 46 | upstream cluster1-https { 47 | zone cluster1-https 256k; 48 | least_time last_byte; 49 | keepalive 16; 50 | #servers managed by NLK Controller 51 | state /var/lib/nginx/state/cluster1-https.state; 52 | } 53 | -------------------------------------------------------------------------------- /docs/http/zonesync.conf: -------------------------------------------------------------------------------- 1 | # NGINX Loadbalancer for Kubernetes 2 | # NGINX Zone Sync configuration, for KVstore split 3 | # Chris Akker, Apr 2023 4 | # Stream Zone Sync block 5 | # 2 NGINX Plus nodes 6 | # https://docs.nginx.com/nginx/admin-guide/high-availability/zone_sync/ 7 | # 8 | #### zonesync.conf 9 | 10 | server { 11 | zone_sync; 12 | 13 | listen 9001; 14 | 15 | # cluster of 2 nodes 16 | zone_sync_server 10.1.1.4:9001; 17 | zone_sync_server 10.1.1.5:9001; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /docs/media/cafe-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/cafe-dashboard.png -------------------------------------------------------------------------------- /docs/media/grafana-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/grafana-icon.png -------------------------------------------------------------------------------- /docs/media/kubernetes-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/kubernetes-icon.png -------------------------------------------------------------------------------- /docs/media/linux-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/linux-icon.png -------------------------------------------------------------------------------- /docs/media/nginx-2020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nginx-2020.png -------------------------------------------------------------------------------- /docs/media/nginx-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nginx-icon.png -------------------------------------------------------------------------------- /docs/media/nginx-ingress-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nginx-ingress-icon.png -------------------------------------------------------------------------------- /docs/media/nginx-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nginx-logo.png -------------------------------------------------------------------------------- /docs/media/nginx-plus-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nginx-plus-icon.png -------------------------------------------------------------------------------- /docs/media/nginx-red-plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nginx-red-plus.png -------------------------------------------------------------------------------- /docs/media/nkl-blog-diagram-v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nkl-blog-diagram-v2.png -------------------------------------------------------------------------------- /docs/media/nkl-desktop-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nkl-desktop-background.png -------------------------------------------------------------------------------- /docs/media/nlk-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-banner.png -------------------------------------------------------------------------------- /docs/media/nlk-blog-diagram-v1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-blog-diagram-v1.png -------------------------------------------------------------------------------- /docs/media/nlk-cluster1-add-loadbalancer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-cluster1-add-loadbalancer.png -------------------------------------------------------------------------------- /docs/media/nlk-cluster1-add-nodeport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-cluster1-add-nodeport.png -------------------------------------------------------------------------------- /docs/media/nlk-cluster1-delete-nodeport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-cluster1-delete-nodeport.png -------------------------------------------------------------------------------- /docs/media/nlk-cluster1-nodeport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-cluster1-nodeport.png -------------------------------------------------------------------------------- /docs/media/nlk-cluster1-upstreams.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-cluster1-upstreams.png -------------------------------------------------------------------------------- /docs/media/nlk-clusters-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-clusters-10.png -------------------------------------------------------------------------------- /docs/media/nlk-clusters-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-clusters-50.png -------------------------------------------------------------------------------- /docs/media/nlk-configmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-configmap.png -------------------------------------------------------------------------------- /docs/media/nlk-desktop-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-desktop-background.png -------------------------------------------------------------------------------- /docs/media/nlk-grafana-reqs-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-grafana-reqs-10.png -------------------------------------------------------------------------------- /docs/media/nlk-grafana-reqs-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-grafana-reqs-50.png -------------------------------------------------------------------------------- /docs/media/nlk-grafana-reqs-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-grafana-reqs-90.png -------------------------------------------------------------------------------- /docs/media/nlk-grafana-resp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-grafana-resp.png -------------------------------------------------------------------------------- /docs/media/nlk-http-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-http-dashboard.png -------------------------------------------------------------------------------- /docs/media/nlk-keyval-split.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-keyval-split.png -------------------------------------------------------------------------------- /docs/media/nlk-logo-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-logo-favicon.png -------------------------------------------------------------------------------- /docs/media/nlk-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-logo.png -------------------------------------------------------------------------------- /docs/media/nlk-multicluster-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-multicluster-config.png -------------------------------------------------------------------------------- /docs/media/nlk-multicluster-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-multicluster-diagram.png -------------------------------------------------------------------------------- /docs/media/nlk-multicluster-upstreams.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-multicluster-upstreams.png -------------------------------------------------------------------------------- /docs/media/nlk-stream-add-loadbalancer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-stream-add-loadbalancer.png -------------------------------------------------------------------------------- /docs/media/nlk-stream-create-nodeport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-stream-create-nodeport.png -------------------------------------------------------------------------------- /docs/media/nlk-stream-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-stream-dashboard.png -------------------------------------------------------------------------------- /docs/media/nlk-stream-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-stream-diagram.png -------------------------------------------------------------------------------- /docs/media/nlk-stream-logs-created.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-stream-logs-created.png -------------------------------------------------------------------------------- /docs/media/nlk-stream-logs-deleted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-stream-logs-deleted.png -------------------------------------------------------------------------------- /docs/media/nlk-stream-no-nodeport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-stream-no-nodeport.png -------------------------------------------------------------------------------- /docs/media/nlk-stream-nodeport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-stream-nodeport.png -------------------------------------------------------------------------------- /docs/media/nlk-stream-upstreams.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-stream-upstreams.png -------------------------------------------------------------------------------- /docs/media/nlk-zone-sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/nlk-zone-sync.png -------------------------------------------------------------------------------- /docs/media/prometheus-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/prometheus-icon.png -------------------------------------------------------------------------------- /docs/media/prometheus-upstreams.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/049b252fc58fa327a12883794c3c4b212000b5f6/docs/media/prometheus-upstreams.png -------------------------------------------------------------------------------- /docs/tcp/dashboard.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 9000; 3 | 4 | location /api { 5 | api write=on; 6 | } 7 | 8 | location = /dashboard.html { 9 | root /usr/share/nginx/html; 10 | } 11 | 12 | # Redirect requests for "/" to "/dashboard.html" 13 | location / { 14 | return 301 /dashboard.html; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /docs/tcp/default-tcp.conf: -------------------------------------------------------------------------------- 1 | # NGINX Loadbalancer for K8s Solution 2 | # Chris Akker, Apr 2023 3 | # Example default.conf 4 | # Change default_server to port 8080 5 | # 6 | server { 7 | listen 8080 default_server; # Changed to 8080 8 | server_name localhost; 9 | 10 | #access_log /var/log/nginx/host.access.log main; 11 | 12 | location / { 13 | root /usr/share/nginx/html; 14 | index index.html index.htm; 15 | } 16 | 17 | #error_page 404 /404.html; 18 | 19 | # redirect server error pages to the static page /50x.html 20 | # 21 | error_page 500 502 503 504 /50x.html; 22 | location = /50x.html { 23 | root /usr/share/nginx/html; 24 | } 25 | 26 | ### other sections removed for clarity 27 | 28 | } -------------------------------------------------------------------------------- /docs/tcp/loadbalancer-nlk.yaml: -------------------------------------------------------------------------------- 1 | # NLK LoadBalancer Service file 2 | # Spec -ports name must be in the format of 3 | # nlk- 4 | # The nginxinc.io Annotation must be added 5 | # Chris Akker, Apr 2023 6 | # 7 | apiVersion: v1 8 | kind: Service 9 | metadata: 10 | name: nginx-ingress 11 | namespace: nginx-ingress 12 | annotations: 13 | nginxinc.io/nlk-nginx-lb-http: "stream" # Must be added 14 | nginxinc.io/nlk-nginx-lb-https: "stream" # Must be added 15 | spec: 16 | type: LoadBalancer 17 | externalIPs: 18 | - 10.1.1.4 19 | - 10.1.1.5 20 | ports: 21 | - port: 80 22 | targetPort: 80 23 | protocol: TCP 24 | name: nlk-nginx-lb-http # Must be changed 25 | - port: 443 26 | targetPort: 443 27 | protocol: TCP 28 | name: nlk-nginx-lb-https # Must be changed 29 | selector: 30 | app: nginx-ingress 31 | -------------------------------------------------------------------------------- /docs/tcp/nginx.conf: -------------------------------------------------------------------------------- 1 | # NGINX Loadbalancer for K8s Solution 2 | # Chris Akker, Apr 2023 3 | # Example nginx.conf 4 | # Enable Stream context, add /var/log/nginx/stream.log 5 | # 6 | 7 | user nginx; 8 | worker_processes auto; 9 | 10 | error_log /var/log/nginx/error.log notice; 11 | pid /var/run/nginx.pid; 12 | 13 | events { 14 | worker_connections 1024; 15 | } 16 | 17 | http { 18 | include /etc/nginx/mime.types; 19 | default_type application/octet-stream; 20 | 21 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 22 | '$status $body_bytes_sent "$http_referer" ' 23 | '"$http_user_agent" "$http_x_forwarded_for"'; 24 | 25 | access_log /var/log/nginx/access.log main; 26 | 27 | sendfile on; 28 | #tcp_nopush on; 29 | 30 | keepalive_timeout 65; 31 | 32 | #gzip on; 33 | 34 | include /etc/nginx/conf.d/*.conf; 35 | } 36 | 37 | # TCP/UDP proxy and load balancing block 38 | # 39 | stream { 40 | 41 | include /etc/nginx/stream/*.conf; 42 | 43 | log_format stream '$remote_addr - $server_addr [$time_local] $status $upstream_addr $upstream_bytes_sent'; 44 | 45 | access_log /var/log/nginx/stream.log stream; 46 | } 47 | -------------------------------------------------------------------------------- /docs/tcp/nginxk8slb.conf: -------------------------------------------------------------------------------- 1 | # NGINX Loadbalancer for K8s Solution - Stream configuration, for L4 load balancing 2 | # Chris Akker, Apr 2023 3 | # TCP Proxy and load balancing block 4 | # NGINX Loadbalancer for Kubernetes 5 | # State File for persistent reloads/restarts 6 | # Health Check Match example for cafe.example.com 7 | # 8 | #### nginxk8slb.conf 9 | 10 | upstream nginx-lb-http { 11 | zone nginx-lb-http 256k; 12 | #servers managed by NLK Controller 13 | state /var/lib/nginx/state/nginx-lb-http.state; 14 | } 15 | 16 | upstream nginx-lb-https { 17 | least_time last_byte; 18 | zone nginx-lb-https 256k; 19 | #servers managed by NLK Controller 20 | state /var/lib/nginx/state/nginx-lb-https.state; 21 | } 22 | 23 | server { 24 | listen 80; 25 | status_zone nginx-lb-http; 26 | proxy_pass nginx-lb-http; 27 | health_check match=cafe; 28 | } 29 | 30 | server { 31 | listen 443; 32 | status_zone nginx-lb-https; 33 | proxy_pass nginx-lb-https; 34 | health_check match=cafe; 35 | } 36 | 37 | match cafe { 38 | send "GET cafe.example.com/ HTTP/1.0\r\n"; 39 | expect ~ "30*"; 40 | } 41 | 42 | # Nginx State Files Required for Upstreams 43 | # state file /var/lib/nginx/state/nginx-lb-http.state 44 | # state file /var/lib/nginx/state/nginx-lb-https.state 45 | -------------------------------------------------------------------------------- /docs/tcp/nodeport-nlk.yaml: -------------------------------------------------------------------------------- 1 | # NLK Nodeport Service file 2 | # NodePort -ports name must be in the format of 3 | # nlk- 4 | # The nginxinc.io Annotation must be added 5 | # Chris Akker, Apr 2023 6 | # 7 | apiVersion: v1 8 | kind: Service 9 | metadata: 10 | name: nginx-ingress 11 | namespace: nginx-ingress 12 | annotations: 13 | nginxinc.io/nlk-nginx-lb-http: "stream" # Must be added 14 | nginxinc.io/nlk-nginx-lb-https: "stream" # Must be added 15 | spec: 16 | type: NodePort 17 | ports: 18 | - port: 80 19 | targetPort: 80 20 | protocol: TCP 21 | name: nlk-nginx-lb-http # Must be changed 22 | - port: 443 23 | targetPort: 443 24 | protocol: TCP 25 | name: nlk-nginx-lb-https # Must be changed 26 | selector: 27 | app: nginx-ingress 28 | -------------------------------------------------------------------------------- /docs/tcp/nodeport.yaml: -------------------------------------------------------------------------------- 1 | # This the default nodeport.yaml manifest for nginx-ingress. 2 | # The port name MUST be changed to work the new NLK Controller. 3 | # See the new loadbalancer-nlk.yaml or nodeport-nlk.yaml file example. 4 | # 5 | apiVersion: v1 6 | kind: Service 7 | metadata: 8 | name: nginx-ingress 9 | namespace: nginx-ingress 10 | spec: 11 | type: NodePort 12 | ports: 13 | - port: 80 14 | targetPort: 80 15 | protocol: TCP 16 | name: http 17 | - port: 443 18 | targetPort: 443 19 | protocol: TCP 20 | name: https 21 | selector: 22 | app: nginx-ingress 23 | -------------------------------------------------------------------------------- /docs/tls/CA-MTLS.md: -------------------------------------------------------------------------------- 1 | # Mutual TLS with Certificate Authority (CA) certificates 2 | 3 | This mode allows NLK to verify it is connecting to the correct NGINX Plus instance, allows NGINX Plus to verify it is connecting to the correct NLK, and encrypts the data between NLK and NGINX Plus. 4 | 5 | ## Overview 6 | 7 | Mutual TLS is used to encrypt the traffic between NLK and NGINX Plus, to ensure NLK verifies the NGINX Plus server, and to ensure NGINX Plus verifies NLK. 8 | 9 | ## Certificates 10 | 11 | To configure this mode, the following certificates are required: 12 | 13 | - Server Certificate 14 | - Client Certificate 15 | 16 | See the following sections for instructions on how to create these certificates. 17 | 18 | ### Certificate Authority (CA) 19 | 20 | Provided by the user. 21 | 22 | ### Server Certificate (NGINX Plus) 23 | 24 | Use your own certificate authority (CA) to generate a server certificate and key. 25 | 26 | ### Client Certificate (NLK) 27 | 28 | Use your own certificate authority (CA) to generate a client certificate and key. 29 | 30 | ## Kubernetes Secrets 31 | 32 | NLK accesses the necessary certificates for each mode from Kubernetes Secrets. For this mode, the following Kubernetes Secret(s) are required: 33 | - Client Certificate 34 | 35 | To create the Kubernetes Secret containing the CA certificate, refer to the [Kubernetes Secrets](./KUBERNETES-SECRETS.md) guide; 36 | the name and location of the certificate(s) created above should be used. The name of the Secret will be needed for the ConfigMap (discussed below). 37 | 38 | ## ConfigMap 39 | 40 | 41 | NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is located in the `nlk` namespace. 42 | 43 | Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. 44 | 45 | For this mode, the `tls-mode` and `clientCertificate` fields need to be included. The `tls-mode` field should be set to `ca-mtls` 46 | and the `clientCertificate` field should be set to the name of the Kubernetes Secret containing the Client certificate created above. 47 | 48 | The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints): 49 | 50 | ```yaml 51 | apiVersion: v1 52 | kind: ConfigMap 53 | metadata: 54 | name: nlk-config 55 | namespace: nlk 56 | data: 57 | nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" 58 | tls-mode: "ca-mtls" 59 | clientCertificate: "nlk-tls-client-secret" 60 | ``` 61 | 62 | ## Deployment 63 | 64 | Save the above ConfigMap definition to a file named `ca-mtls-configmap.yaml`, then deploy the ConfigMap using the following command: 65 | 66 | ```bash 67 | kubectl apply -f docs/tls/ca-mtls-configmap.yaml 68 | ``` 69 | 70 | ## Configuring NGINX Plus 71 | 72 | Refer to the [NGINX Plus Configuration](./NGINX-PLUS-CONFIGURATION.md) guide for instructions on configuring NGINX Plus to use the certificates created above. 73 | 74 | The steps in both the ["One-way TLS"](./NGINX-PLUS-CONFIGURATION.md#one-way-tls) section and the ["Mutual TLS"](./NGINX-PLUS-CONFIGURATION.md#mutual-tls) section are required for this mode. 75 | 76 | ## Verification 77 | 78 | To verify the ConfigMap was deployed correctly, run the following command: 79 | 80 | ```bash 81 | kubectl get configmap -n nlk nlk-config -o yaml 82 | ``` 83 | 84 | The output should match the ConfigMap above. 85 | 86 | To verify NLK is running, follow the instructions in either the [TCP](../tcp/tcp-installation-guide.md) or [HTTP](../http/http-installation-guide.md) guides. 87 | 88 | -------------------------------------------------------------------------------- /docs/tls/CA-TLS.md: -------------------------------------------------------------------------------- 1 | # One-way TLS with Certificate Authority (CA) certificates 2 | 3 | This mode allows NLK to verify it is connecting to the correct NGINX Plus instance, and encrypts the data between NLK and NGINX Plus. 4 | 5 | ## Overview 6 | 7 | One-way TLS is used to encrypt the traffic between NLK and NGINX Plus, and to ensure NLK verifies the NGINX Plus server; 8 | however, the NGINX Plus server _does not_ validate NLK. 9 | 10 | ## Certificates 11 | 12 | To configure this mode, the following certificates are required: 13 | 14 | - Server Certificate 15 | 16 | See the following sections for instructions on how to create these certificates. 17 | 18 | ### Certificate Authority (CA) 19 | 20 | Provided by the user. 21 | 22 | ### Server Certificate (NGINX Plus) 23 | 24 | Use your certificate authority (CA) to generate a server certificate and key. 25 | 26 | ## Kubernetes Secrets 27 | 28 | No Kubernetes Secrets are required for this mode. 29 | 30 | ## ConfigMap 31 | 32 | NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is located in the `nlk` namespace. 33 | 34 | Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. 35 | 36 | For this mode, only the `tls-mode` fields needs to be included. The `tls-mode` field should be set to `ca-tls`. 37 | 38 | The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints): 39 | 40 | 41 | ```yaml 42 | apiVersion: v1 43 | kind: ConfigMap 44 | metadata: 45 | name: nlk-config 46 | namespace: nlk 47 | data: 48 | nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" 49 | tls-mode: "ca-tls" 50 | ``` 51 | 52 | ## Deployment 53 | 54 | Save the above ConfigMap definition to a file named `ca-tls-configmap.yaml`, then deploy the ConfigMap using the following command: 55 | 56 | ```bash 57 | kubectl apply -f docs/tls/ca-tls-configmap.yaml 58 | ``` 59 | 60 | ## Configuring NGINX Plus 61 | 62 | Refer to the [NGINX Plus Configuration](./NGINX-PLUS-CONFIGURATION.md) guide for instructions on configuring NGINX Plus to use the certificates created above. 63 | 64 | Only the steps in the ["One-way TLS"](./NGINX-PLUS-CONFIGURATION.md#one-way-tls) section are required for this mode. 65 | Use the certificate and key from your CA to configure NGINX Plus. 66 | 67 | ## Verification 68 | 69 | To verify the ConfigMap was deployed correctly, run the following command: 70 | 71 | ```bash 72 | kubectl get configmap -n nlk nlk-config -o yaml 73 | ``` 74 | 75 | The output should match the ConfigMap above. 76 | 77 | To verify NLK is running, follow the instructions in either the [TCP](../tcp/tcp-installation-guide.md) or [HTTP](../http/http-installation-guide.md) guides. 78 | -------------------------------------------------------------------------------- /docs/tls/CERTIFICATE-AUTHORITY.md: -------------------------------------------------------------------------------- 1 | # Generate a Certificate Authority (CA) 2 | 3 | When using self-signed certificates, the first step is to generate the Certificate Authority (CA). 4 | 5 | The following commands will generate the CA certificate and key: 6 | 7 | ```bash 8 | openssl req -newkey rsa:2048 -nodes -x509 -out ca.crt -keyout ca.key 9 | ``` 10 | 11 | You will be prompted to enter the Distinguished Name (DN) information for the CA. 12 | 13 | Alternatively, you can provide the DN information in a file, an example is shown below: 14 | 15 | ```bash 16 | [ req ] 17 | distinguished_name = dn 18 | prompt = no 19 | req_extensions = req_ext 20 | 21 | [ req_ext ] 22 | basicConstraints = CA:TRUE 23 | keyUsage = critical, keyCertSign, cRLSign 24 | 25 | [ dn ] 26 | C=[COUNTRY] 27 | ST=[STATE] 28 | L=[LOCALITY] 29 | O=[ORGANIZATION] 30 | OU=[ORGANIZATION_UNIT] 31 | ``` 32 | 33 | Create a file using the above as a template and update the values in the `[ dn ]` section; then use following command to generate the CA certificate and key: 34 | 35 | ```bash 36 | openssl req -newkey rsa:2048 -nodes -x509 -config ca.cnf -out ca.crt -keyout ca.key 37 | ``` 38 | 39 | The output of the above command will be the CA certificate (`ca.crt`) and key (`ca.key`). 40 | 41 | ## References 42 | 43 | - [Distinguished Name reference](http://certificate.fyicenter.com/2098_OpenSSL_req_-distinguished_name_Configuration_Section.html) 44 | 45 | -------------------------------------------------------------------------------- /docs/tls/CLIENT-CERTIFICATE.md: -------------------------------------------------------------------------------- 1 | # Generate a client certificate 2 | 3 | When using self-signed certificates in the `ss-mtls` mode, a certificate needs to be generated for NLK. 4 | 5 | The certificate has the same basic field as the CA certificate, with the addition of `clientAuth` in the `extendedKeyUsage` field: 6 | 7 | ```bash 8 | [ req ] 9 | distinguished_name = dn 10 | prompt = no 11 | 12 | [ dn ] 13 | C=[COUNTRY] 14 | ST=[STATE] 15 | L=[LOCALITY] 16 | O=[ORGANIZATION] 17 | OU=[ORGANIZATION_UNIT] 18 | 19 | [ client ] 20 | extendedKeyUsage = clientAuth 21 | ``` 22 | 23 | Create a file using the above as a template and update the values in the `[ dn ]` section; then use following command to generate the certificate request and key: 24 | 25 | ```bash 26 | openssl genrsa -out client.key 2048 27 | openssl req -new -key client.key -config client.cnf -out client.csr 28 | ``` 29 | 30 | The output of the above commands will be the client certificate request (`client.csr`) and key (`client.key`). 31 | 32 | ##### Sign the client certificate 33 | 34 | ```bash 35 | openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365 -sha256 -extfile client.cnf -extensions client 36 | ``` 37 | 38 | The output of the above command will be the client certificate (`client.crt`). 39 | 40 | #### Verify the Client Certificate has the correct extendedKeyUsage 41 | 42 | ```bash 43 | openssl x509 -in client.crt -noout -purpose | grep 'SSL client :' 44 | ``` 45 | 46 | Look for `SSL client : Yes` in the output. 47 | 48 | ## References 49 | 50 | - [Distinguished Name reference](http://certificate.fyicenter.com/2098_OpenSSL_req_-distinguished_name_Configuration_Section.html) -------------------------------------------------------------------------------- /docs/tls/DOCUMENT-HIERARCHY.md: -------------------------------------------------------------------------------- 1 | ```mermaid 2 | graph LR 3 | README[README.md] 4 | SS-TLS[SS-TLS.md] 5 | SS-MTLS[SS-MTLS.md] 6 | CA-TLS[CA-TLS.md] 7 | CA-MTLS[CA-MTLS.md] 8 | NO-TLS[NO-TLS.md] 9 | CERTIFICATE-AUTHORITY[CERTIFICATE-AUTHORITY.md] 10 | CLIENT-CERTIFICATE[CLIENT-CERTIFICATE.md] 11 | SERVER-CERTIFICATE[SERVER-CERTIFICATE.md] 12 | KUBERNETES-SECRETS[KUBERNETES-SECRETS.md] 13 | NGINX-PLUS-CONFIGURATION[NGINX-PLUS-CONFIGURATION.md] 14 | KUBERNETES-SECRETS[KUBERNETES-SECRETS.md] 15 | 16 | subgraph "README.md" 17 | README 18 | end 19 | 20 | subgraph "NO-TLS links" 21 | README --> NO-TLS 22 | end 23 | 24 | subgraph "SS-TLS links" 25 | README --> SS-TLS 26 | SS-TLS --> CERTIFICATE-AUTHORITY 27 | SS-TLS --> SERVER-CERTIFICATE 28 | SS-TLS --> KUBERNETES-SECRETS 29 | SS-TLS --> NGINX-PLUS-CONFIGURATION 30 | end 31 | 32 | subgraph "SS-MTLS links" 33 | README --> SS-MTLS 34 | SS-MTLS --> CERTIFICATE-AUTHORITY 35 | SS-MTLS --> SERVER-CERTIFICATE 36 | SS-MTLS --> CLIENT-CERTIFICATE 37 | SS-MTLS --> KUBERNETES-SECRETS 38 | SS-MTLS --> NGINX-PLUS-CONFIGURATION 39 | end 40 | 41 | subgraph "CA-TLS links" 42 | README --> CA-TLS 43 | CA-TLS --> NGINX-PLUS-CONFIGURATION 44 | end 45 | 46 | subgraph "CA-MTLS links" 47 | README --> CA-MTLS 48 | CA-MTLS --> KUBERNETES-SECRETS 49 | CA-MTLS --> NGINX-PLUS-CONFIGURATION 50 | end 51 | ``` -------------------------------------------------------------------------------- /docs/tls/KUBERNETES-SECRETS.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Secrets 2 | 3 | ## Overview 4 | 5 | Kubernetes Secrets are used to provide the required certificates to NLK. There are two ways to create the Secrets: 6 | - Using `kubectl` 7 | - Using yaml files 8 | 9 | The filenames for the certificates created are required for both methods. The examples below assume the certificates were generated in `/tmp` 10 | and follow the naming conventions in the documentation. 11 | 12 | ## Using `kubectl` 13 | 14 | The easiest way to create the Secret(s) is by using `kubectl`: 15 | 16 | ```bash 17 | kubectl create secret tls -n nlk nlk-tls-ca-secret --cert=/tmp/ca.crt --key=/tmp/ca.key 18 | kubectl create secret tls -n nlk nlk-tls-server-secret --cert=/tmp/server.crt --key=/tmp/server.key 19 | kubectl create secret tls -n nlk nlk-tls-client-secret --cert=/tmp/client.crt --key=/tmp/client.key 20 | ``` 21 | 22 | ## Using yaml files 23 | 24 | The Secrets can also be created using yaml files. The following is an example of a yaml file for the Client Secret (note that the `data` values are truncated): 25 | 26 | ```yaml 27 | apiVersion: v1 28 | data: 29 | tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVCVEN... 30 | tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0l... 31 | kind: Secret 32 | metadata: 33 | name: nlk-tls-ca-secret 34 | type: kubernetes.io/tls 35 | ``` 36 | 37 | Note: While it is possible to generate the values for `tls.crt` and `tls.key` manually, the above yaml can be generated using the following command: 38 | 39 | ```bash 40 | kubectl create secret tls -n nlk nlk-tls-ca-secret --cert=/tmp/ca.crt --key=/tmp/ca.key --dry-run=client -o yaml > ca-secret.yaml 41 | kubectl create secret tls -n nlk nlk-tls-server-secret --cert=/tmp/server.crt --key=/tmp/server.key --dry-run=client -o yaml > server-secret.yaml 42 | kubectl create secret tls -n nlk nlk-tls-client-secret --cert=/tmp/client.crt --key=/tmp/client.key --dry-run=client -o yaml > client-secret.yaml 43 | ``` 44 | 45 | > [!WARNING] 46 | > It is important that these files do not make their way into a public repository or other storage location where they can be accessed by unauthorized users. 47 | 48 | 49 | Once the yaml files are generated they can be applied using `kubectl`: 50 | 51 | ```bash 52 | kubectl apply -f ca-secret.yaml 53 | kubectl apply -f server-secret.yaml 54 | kubectl apply -f client-secret.yaml 55 | ``` 56 | 57 | # Verification 58 | 59 | The Secrets can be verified using `kubectl`: 60 | 61 | ```bash 62 | kubectl describe secret -n nlk nlk-tls-ca-secret 63 | kubectl describe secret -n nlk nlk-tls-server-secret 64 | kubectl describe secret -n nlk nlk-tls-client-secret 65 | ``` 66 | 67 | The output should look similar to the example above. 68 | 69 | To see the actual values of the certificates, the following command can be used: 70 | 71 | ```bash 72 | kubectl get secret -n nlk nlk-tls-ca-secret -o json | jq -r '.data["tls.crt"], .data["tls.key"]' | base64 -d 73 | kubectl get secret -n nlk nlk-tls-server-secret -o json | jq -r '.data["tls.crt"], .data["tls.key"]' | base64 -d 74 | kubectl get secret -n nlk nlk-tls-client-secret -o json | jq -r '.data["tls.crt"], .data["tls.key"]' | base64 -d 75 | ``` 76 | 77 | Note that this requires `jq` to be installed. 78 | 79 | ## References 80 | 81 | - [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/) 82 | - [kubectl dry run flags](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#-em-dry-run-em-) -------------------------------------------------------------------------------- /docs/tls/NGINX-PLUS-CONFIGURATION.md: -------------------------------------------------------------------------------- 1 | # Configuring NGINX Plus 2 | 3 | ## Configuring the server certificate 4 | 5 | Depending on the mode chosen, some files will need to be copied to the NGINX Plus host. The following table shows which files are required for each mode: 6 | 7 | | Mode | Server Certificate | CA Certificate | 8 | | ---- | ------------------ |--------------------| 9 | | no-tls | | | | 10 | | ss-tls | :heavy_check_mark: | :heavy_check_mark: | 11 | | ss-mtls | :heavy_check_mark: | :heavy_check_mark: | 12 | | ca-tls | :heavy_check_mark: | | 13 | | ca-mtls | :heavy_check_mark: | | 14 | 15 | 16 | Copy the necessary server files for the chosen mode to the NGINX host; place them in the `/etc/ssl/certs/nginx` directory. 17 | 18 | ### One-way TLS 19 | 20 | The following applies to modes `ss-tls`, `ss-mtls`, `ca-tls`, and `ca-mtls`. 21 | 22 | To configure NGINX Plus to use the `server.crt` and `server.key` files for TLS, 23 | add the following to the `http` or `server` context in the `/etc/nginx/nginx.conf` file: 24 | 25 | ```bash 26 | http { 27 | ssl_certificate /etc/ssl/certs/nginx/server.crt; 28 | ssl_certificate_key /etc/ssl/certs/nginx/server.key; 29 | } 30 | ``` 31 | 32 | Reload the NGINX Plus configuration to apply the changes. 33 | 34 | ```bash 35 | nginx -s reload 36 | ``` 37 | 38 | ### Mutual TLS 39 | 40 | In the `/etc/nginx/nginx.conf` file, add the following to the `http` or `server` context: 41 | 42 | #### Self-signed certificates 43 | 44 | When using `ss-mtls` mode, the CA certificate must be provided to NGINX Plus: 45 | 46 | ```bash 47 | http { 48 | ssl_client_certificate /etc/ssl/certs/nginx/ca.crt; 49 | ssl_verify_client on; 50 | ssl_verify_depth 3; 51 | } 52 | ``` 53 | 54 | This will configure NGINX Plus to use the `ca.crt` file for client authentication. 55 | 56 | #### CA-signed certificates 57 | 58 | When using `ca-mtls` mode, the `ssl_client_certificate` directive is not required: 59 | 60 | ```bash 61 | http { 62 | ssl_verify_client on; 63 | ssl_verify_depth 3; 64 | } 65 | ``` 66 | 67 | Reload the NGINX Plus configuration to apply the changes. 68 | 69 | ```bash 70 | nginx -s reload 71 | ``` 72 | 73 | Test with curl: 74 | 75 | ```bash 76 | curl --cert client.crt --key client.key --cacert ca.crt https:///api 77 | ``` 78 | 79 | ## References 80 | 81 | - [NGINX `ssl_certificate` directive](https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate) 82 | - [NGINX `ssl_client_certificate` directive](https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_client_certificate) -------------------------------------------------------------------------------- /docs/tls/NO-TLS.md: -------------------------------------------------------------------------------- 1 | # No TLS Mode 2 | 3 | This mode is the easiest to configure, but provides no security. Choose this for development environments, or test 4 | environments where security is not strictly necessary. 5 | 6 | Note: you should test with one of the TLS / mTLS modes before deploying to production to ensure familiarity with the configuration and process. 7 | 8 | ## Overview 9 | 10 | This is the default mode of operation for NLK. It offers no verification of either side of the connection, nor any encryption of the data. 11 | 12 | ## Certificates 13 | 14 | No certificates are required for this mode. 15 | 16 | ## Kubernetes Secrets 17 | 18 | No Kubernetes Secrets are required for this mode. 19 | 20 | ## ConfigMap 21 | 22 | NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is located in the `nlk` namespace. Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. 23 | 24 | For this mode, only the `tls-mode` field needs to be included, and should be set to `no-tls` (or omitted altogether as this is the default mode). 25 | 26 | The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints) 27 | 28 | ```yaml 29 | apiVersion: v1 30 | kind: ConfigMap 31 | metadata: 32 | name: nlk-config 33 | namespace: nlk 34 | data: 35 | nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" 36 | tls-mode: "no-tls" 37 | ``` 38 | 39 | ## Deployment 40 | 41 | Save the above ConfigMap definition to a file named `no-tls-configmap.yaml`, then deploy the ConfigMap using the following command: 42 | 43 | ```bash 44 | kubectl apply -f docs/tls/no-tls-configmap.yaml 45 | ``` 46 | 47 | ## Verification 48 | 49 | To verify the ConfigMap was deployed correctly, run the following command: 50 | 51 | ```bash 52 | kubectl get configmap -n nlk nlk-config -o yaml 53 | ``` 54 | 55 | The output should match the ConfigMap above. 56 | 57 | To verify NLK is running, follow the instructions in either the [TCP](../tcp/tcp-installation-guide.md) or [HTTP](../http/http-installation-guide.md) guides. 58 | -------------------------------------------------------------------------------- /docs/tls/SERVER-CERTIFICATE.md: -------------------------------------------------------------------------------- 1 | # Generate a server certificate 2 | 3 | When using self-signed certificates in the `ss-tls` and `ss-mtls` modes, a certificate needs to be generated for the NGINX Plus server. 4 | 5 | The certificate has the same basic fields as the CA certificate, but with a few additional fields. 6 | 7 | The following is an example of a configuration file for server certificates: 8 | 9 | ```bash 10 | [ req ] 11 | distinguished_name = dn 12 | req_extensions = req_ext 13 | prompt = no 14 | 15 | [ dn ] 16 | C=[COUNTRY] 17 | ST=[STATE] 18 | L=[LOCALITY] 19 | O=[ORGANIZATION] 20 | OU=[ORGANIZATION_UNIT] 21 | CN=[COMMON_NAME] 22 | 23 | [ req_ext ] 24 | basicConstraints = CA:FALSE 25 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 26 | subjectAltName = @alt_names 27 | extendedKeyUsage = serverAuth 28 | 29 | [ alt_names ] 30 | DNS.1 = mydomain.com 31 | DNS.2 = server.mydomain.com 32 | DNS.3 = *.mydomain.com 33 | IP.1 = 10.0.0.10 34 | IP.2 = 10.0.0.11 35 | ``` 36 | 37 | Create a file using this as a template, the following example commands use the name `server.cnf`. 38 | 39 | Be sure to update the Distinguished Name (DN) information and the SAN information (DNS / IP entries in the `alt_names` section) as appropriate. 40 | Doing so ensures that the certificate is valid for the server by providing an unambiguous match between the server (IP addresses and/or domain names) and the certificate. 41 | 42 | The following commands will generate the server certificate request and key: 43 | 44 | ```bash 45 | openssl genrsa -out server.key 2048 46 | openssl req -new -key server.key -config server.cnf -out server.csr 47 | ``` 48 | 49 | The output of the above commands will be the server certificate request (`server.csr`) and key (`server.key`). 50 | 51 | ##### Sign the server certificate 52 | 53 | ```bash 54 | openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365 -sha256 -extensions req_ext -extfile server.cnf 55 | ``` 56 | 57 | The output of the above command will be the server certificate (`server.crt`). 58 | 59 | ##### Verify the Server Certificate has the SAN 60 | 61 | ```bash 62 | openssl x509 -in server.crt -text -noout | grep -A 1 "Subject Alternative Name" 63 | ``` 64 | 65 | Look for the DNS / IP entries in the `Subject Alternative Name` section in the output; the values entered in the `alt_names` section of the `server.cnf` file should be present. 66 | 67 | ## References 68 | 69 | - [OpenSSL extensions documentation](https://www.openssl.org/docs/manmaster/man5/x509v3_config.html) 70 | - [Distinguished Name reference](http://certificate.fyicenter.com/2098_OpenSSL_req_-distinguished_name_Configuration_Section.html) 71 | -------------------------------------------------------------------------------- /docs/tls/SS-MTLS.md: -------------------------------------------------------------------------------- 1 | # Mutual TLS with self-signed certificates 2 | 3 | This mode allows NLK to verify it is connecting to the correct NGINX Plus instance, allows NGINX Plus to verify it is connecting to the correct NLK, and encrypts the data between NLK and NGINX Plus. 4 | 5 | 6 | ## Overview 7 | 8 | Mutual TLS is used to encrypt the traffic between NLK and NGINX Plus, to ensure NLK verifies the NGINX Plus server, and to ensure NGINX Plus verifies NLK. 9 | 10 | 11 | ## Certificates 12 | 13 | To configure this mode, the following certificates are required: 14 | 15 | - CA Certificate 16 | - Server Certificate 17 | - Client Certificate 18 | 19 | See the following sections for instructions on how to create these certificates. 20 | 21 | ### Certificate Authority (CA) 22 | 23 | Follow the instructions in the [Certificate Authority](./CERTIFICATE-AUTHORITY.md) guide to create a self-signed CA certificate and key. 24 | 25 | ### Server Certificate (NGINX Plus) 26 | 27 | Follow the instructions in the [Server Certificate](./SERVER-CERTIFICATE.md) guide to create a self-signed server certificate and key. 28 | 29 | ### Client Certificate (NLK) 30 | 31 | Follow the instructions in the [Client Certificate](./CLIENT-CERTIFICATE.md) guide to create a self-signed client certificate and key. 32 | 33 | ## Kubernetes Secrets 34 | 35 | NLK accesses the necessary certificates for each mode from Kubernetes Secrets. For this mode, the following Kubernetes Secret(s) are required: 36 | - CA Certificate 37 | - Client Certificate 38 | 39 | To create the Kubernetes Secret containing the CA certificate, refer to the [Kubernetes Secrets](./KUBERNETES-SECRETS.md) guide; 40 | the name and location of the certificate(s) created above should be used. The name of the Secret will be needed for the ConfigMap (discussed below). 41 | 42 | ## ConfigMap 43 | 44 | NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is located in the `nlk` namespace. 45 | 46 | Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. 47 | 48 | For this mode, the `tls-mode`, `ca-certificates`, and `client-certificate` fields need to be included. The `tls-mode` field should be set to `ss-mtls`, 49 | the `ca-certificates` field should be set to the name of the Kubernetes Secret containing the CA certificate created above, 50 | and the `client-certificate` field should be set to the name of the Kubernetes Secret containing the Client certificate created above. 51 | 52 | The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints): 53 | 54 | ```yaml 55 | apiVersion: v1 56 | kind: ConfigMap 57 | metadata: 58 | name: nlk-config 59 | namespace: nlk 60 | data: 61 | nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" 62 | tls-mode: "ss-mtls" 63 | ca-certificate: "nlk-tls-ca-secret" 64 | client-certificate: "nlk-tls-client-secret" 65 | ``` 66 | 67 | ## Deployment 68 | 69 | Save the above ConfigMap definition to a file named `ss-mtls-configmap.yaml`, then deploy the ConfigMap using the following command: 70 | 71 | ```bash 72 | kubectl apply -f docs/tls/ss-mtls-configmap.yaml 73 | ``` 74 | 75 | ## Configuring NGINX Plus 76 | 77 | Refer to the [NGINX Plus Configuration](./NGINX-PLUS-CONFIGURATION.md) guide for instructions on configuring NGINX Plus to use the certificates created above. 78 | 79 | The steps in both the ["One-way TLS"](./NGINX-PLUS-CONFIGURATION.md#one-way-tls) section and the ["Mutual TLS"](./NGINX-PLUS-CONFIGURATION.md#mutual-tls) section are required for this mode. 80 | 81 | ## Verification 82 | 83 | To verify the ConfigMap was deployed correctly, run the following command: 84 | 85 | ```bash 86 | kubectl get configmap -n nlk nlk-config -o yaml 87 | ``` 88 | 89 | The output should match the ConfigMap above. 90 | 91 | To verify NLK is running, follow the instructions in either the [TCP](../tcp/tcp-installation-guide.md) or [HTTP](../http/http-installation-guide.md) guides. 92 | -------------------------------------------------------------------------------- /docs/tls/SS-TLS.md: -------------------------------------------------------------------------------- 1 | # One-way TLS with self-signed certificates 2 | 3 | This mode allows NLK to verify it is connecting to the correct NGINX Plus instance, and encrypts the data between NLK and NGINX Plus. 4 | 5 | ## Overview 6 | 7 | One-way TLS is used to encrypt the traffic between NLK and NGINX Plus, and to ensure NLK verifies the NGINX Plus server; 8 | however, the NGINX Plus server _does not_ validate NLK. 9 | 10 | ## Certificates 11 | 12 | To configure this mode, the following certificates are required: 13 | 14 | - CA Certificate 15 | - Server Certificate 16 | 17 | See the following sections for instructions on how to create these certificates. 18 | 19 | ### Certificate Authority (CA) 20 | 21 | Follow the instructions in the [Certificate Authority](./CERTIFICATE-AUTHORITY.md) guide to create a self-signed CA certificate and key. 22 | 23 | ### Server Certificate (NGINX Plus) 24 | 25 | Follow the instructions in the [Server Certificate](./SERVER-CERTIFICATE.md) guide to create a self-signed server certificate and key. 26 | 27 | ## Kubernetes Secrets 28 | 29 | NLK accesses the necessary certificates for each mode from Kubernetes Secrets. For this mode, the following Kubernetes Secret(s) are required: 30 | - CA Certificate 31 | 32 | To create the Kubernetes Secret containing the CA certificate, refer to the [Kubernetes Secrets](./KUBERNETES-SECRETS.md) guide; 33 | the name and location of the certificate(s) created above should be used. The name of the Secret will be needed for the ConfigMap (discussed below). 34 | 35 | ## ConfigMap 36 | 37 | NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is located in the `nlk` namespace. 38 | 39 | Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. 40 | 41 | For this mode, both the `tls-mode` and `ca-certificates` fields need to be included. The `tls-mode` field should be set to `ss-tls`, 42 | and the `ca-certificates` field should be set to the name of the Kubernetes Secret containing the CA certificate created above. 43 | 44 | The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints): 45 | 46 | 47 | ```yaml 48 | apiVersion: v1 49 | kind: ConfigMap 50 | metadata: 51 | name: nlk-config 52 | namespace: nlk 53 | data: 54 | nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" 55 | tls-mode: "ss-tls" 56 | ca-certificate: "nlk-tls-ca-secret" 57 | ``` 58 | 59 | ## Deployment 60 | 61 | Save the above ConfigMap definition to a file named `ss-tls-configmap.yaml`, then deploy the ConfigMap using the following command: 62 | 63 | ```bash 64 | kubectl apply -f docs/tls/ss-tls-configmap.yaml 65 | ``` 66 | 67 | ## Configuring NGINX Plus 68 | 69 | Refer to the [NGINX Plus Configuration](./NGINX-PLUS-CONFIGURATION.md) guide for instructions on configuring NGINX Plus to use the certificates created above. 70 | 71 | Only the steps in the ["One-way TLS"](./NGINX-PLUS-CONFIGURATION.md#one-way-tls) section are required for this mode. 72 | 73 | ## Verification 74 | 75 | To verify the ConfigMap was deployed correctly, run the following command: 76 | 77 | ```bash 78 | kubectl get configmap -n nlk nlk-config -o yaml 79 | ``` 80 | 81 | The output should match the ConfigMap above. 82 | 83 | To verify NLK is running, follow the instructions in either the [TCP](../tcp/tcp-installation-guide.md) or [HTTP](../http/http-installation-guide.md) guides. 84 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | // Copyright 2023 f5 Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 3 | // license that can be found in the LICENSE file. 4 | 5 | module github.com/nginxinc/kubernetes-nginx-ingress 6 | 7 | go 1.23.3 8 | 9 | require ( 10 | github.com/sirupsen/logrus v1.9.3 11 | k8s.io/api v0.31.2 12 | k8s.io/apimachinery v0.31.2 13 | k8s.io/client-go v0.31.2 14 | github.com/nginxinc/nginx-plus-go-client/v2 v2.0.1 15 | ) 16 | 17 | require ( 18 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 19 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 20 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 21 | github.com/go-logr/logr v1.4.2 // indirect 22 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 23 | github.com/go-openapi/jsonreference v0.20.2 // indirect 24 | github.com/go-openapi/swag v0.22.4 // indirect 25 | github.com/gogo/protobuf v1.3.2 // indirect 26 | github.com/golang/protobuf v1.5.4 // indirect 27 | github.com/google/gnostic-models v0.6.8 // indirect 28 | github.com/google/go-cmp v0.6.0 // indirect 29 | github.com/google/gofuzz v1.2.0 // indirect 30 | github.com/google/uuid v1.6.0 // indirect 31 | github.com/imdario/mergo v0.3.6 // indirect 32 | github.com/josharian/intern v1.0.0 // indirect 33 | github.com/json-iterator/go v1.1.12 // indirect 34 | github.com/mailru/easyjson v0.7.7 // indirect 35 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 36 | github.com/modern-go/reflect2 v1.0.2 // indirect 37 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 38 | github.com/pkg/errors v0.9.1 // indirect 39 | github.com/spf13/pflag v1.0.5 // indirect 40 | github.com/x448/float16 v0.8.4 // indirect 41 | golang.org/x/net v0.26.0 // indirect 42 | golang.org/x/oauth2 v0.21.0 // indirect 43 | golang.org/x/sync v0.8.0 // indirect 44 | golang.org/x/sys v0.21.0 // indirect 45 | golang.org/x/term v0.21.0 // indirect 46 | golang.org/x/text v0.16.0 // indirect 47 | golang.org/x/time v0.3.0 // indirect 48 | google.golang.org/protobuf v1.34.2 // indirect 49 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 50 | gopkg.in/inf.v0 v0.9.1 // indirect 51 | gopkg.in/yaml.v2 v2.4.0 // indirect 52 | gopkg.in/yaml.v3 v3.0.1 // indirect 53 | k8s.io/klog/v2 v2.130.1 // indirect 54 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect 55 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect 56 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 57 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 58 | sigs.k8s.io/yaml v1.4.0 // indirect 59 | ) 60 | 61 | replace ( 62 | github.com/nginxinc/kubernetes-nginx-ingress/internal/config => ./internal/config 63 | github.com/nginxinc/kubernetes-nginx-ingress/internal/translation => ./internal/translation 64 | github.com/nginxinc/kubernetes-nginx-ingress/internal/translation/nginxplus => ./internal/translation/nginxplus 65 | ) 66 | -------------------------------------------------------------------------------- /internal/application/application_common_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package application 7 | 8 | import ( 9 | "errors" 10 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" 11 | "github.com/nginxinc/kubernetes-nginx-ingress/test/mocks" 12 | ) 13 | 14 | const ( 15 | deletedEventType = core.Deleted 16 | createEventType = core.Created 17 | upstreamName = "upstreamName" 18 | server = "server" 19 | ) 20 | 21 | func buildTerrorizingBorderClient(clientType string) (Interface, *mocks.MockNginxClient, error) { 22 | nginxClient := mocks.NewErroringMockClient(errors.New(`something went horribly horribly wrong`)) 23 | bc, err := NewBorderClient(clientType, nginxClient) 24 | 25 | return bc, nginxClient, err 26 | } 27 | 28 | func buildBorderClient(clientType string) (Interface, *mocks.MockNginxClient, error) { 29 | nginxClient := mocks.NewMockNginxClient() 30 | bc, err := NewBorderClient(clientType, nginxClient) 31 | 32 | return bc, nginxClient, err 33 | } 34 | 35 | func buildServerUpdateEvent(eventType core.EventType, clientType string) *core.ServerUpdateEvent { 36 | upstreamServers := core.UpstreamServers{ 37 | { 38 | Host: server, 39 | }, 40 | } 41 | 42 | return core.NewServerUpdateEvent(eventType, upstreamName, clientType, upstreamServers) 43 | } 44 | -------------------------------------------------------------------------------- /internal/application/application_constants.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package application 7 | 8 | // These constants are intended for use in the Annotations field of the Service definition. 9 | // They determine which Border Server client will be used. 10 | // To use these values, add the following annotation to the Service definition: 11 | // 12 | // annotations: 13 | // nginxinc.io/nlk-: 14 | // 15 | // where is the name of the upstream in the NGINX Plus configuration and is one of the constants below. 16 | // 17 | // Note, this is an extensibility point. To add a Border Server client... 18 | // 1. Create a module that implements the BorderClient interface; 19 | // 2. Add a new constant to this group that acts as a key for selecting the client; 20 | // 3. Update the NewBorderClient factory method in border_client.go that returns the client; 21 | const ( 22 | 23 | // ClientTypeNginxStream creates a NginxStreamBorderClient that uses the Stream* methods of the NGINX Plus client. 24 | ClientTypeNginxStream = "stream" 25 | 26 | // ClientTypeNginxHttp creates an NginxHttpBorderClient that uses the HTTP* methods of the NGINX Plus client. 27 | ClientTypeNginxHttp = "http" 28 | ) 29 | -------------------------------------------------------------------------------- /internal/application/border_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package application 7 | 8 | import ( 9 | "fmt" 10 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | // Interface defines the functions required to implement a Border Client. 15 | type Interface interface { 16 | Update(*core.ServerUpdateEvent) error 17 | Delete(*core.ServerUpdateEvent) error 18 | } 19 | 20 | // BorderClient defines any state need by the Border Client. 21 | type BorderClient struct { 22 | } 23 | 24 | // NewBorderClient is the Factory function for creating a Border Client. 25 | // 26 | // Note, this is an extensibility point. To add a Border Server client... 27 | // 1. Create a module that implements the BorderClient interface; 28 | // 2. Add a new constant in application_constants.go that acts as a key for selecting the client; 29 | // 3. Update the NewBorderClient factory method in border_client.go that returns the client; 30 | func NewBorderClient(clientType string, borderClient interface{}) (Interface, error) { 31 | logrus.Debugf(`NewBorderClient for type: %s`, clientType) 32 | 33 | switch clientType { 34 | case ClientTypeNginxStream: 35 | return NewNginxStreamBorderClient(borderClient) 36 | 37 | case ClientTypeNginxHttp: 38 | return NewNginxHttpBorderClient(borderClient) 39 | 40 | default: 41 | borderClient, _ := NewNullBorderClient() 42 | return borderClient, fmt.Errorf(`unknown border client type: %s`, clientType) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /internal/application/border_client_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package application 7 | 8 | import ( 9 | "github.com/nginxinc/kubernetes-nginx-ingress/test/mocks" 10 | "testing" 11 | ) 12 | 13 | func TestBorderClient_CreatesHttpBorderClient(t *testing.T) { 14 | borderClient := mocks.MockNginxClient{} 15 | client, err := NewBorderClient("http", borderClient) 16 | if err != nil { 17 | t.Errorf(`error creating border client: %v`, err) 18 | } 19 | 20 | if _, ok := client.(*NginxHttpBorderClient); !ok { 21 | t.Errorf(`expected client to be of type NginxHttpBorderClient`) 22 | } 23 | } 24 | 25 | func TestBorderClient_CreatesTcpBorderClient(t *testing.T) { 26 | borderClient := mocks.MockNginxClient{} 27 | client, err := NewBorderClient("stream", borderClient) 28 | if err != nil { 29 | t.Errorf(`error creating border client: %v`, err) 30 | } 31 | 32 | if _, ok := client.(*NginxStreamBorderClient); !ok { 33 | t.Errorf(`expected client to be of type NginxStreamBorderClient`) 34 | } 35 | } 36 | 37 | func TestBorderClient_UnknownClientType(t *testing.T) { 38 | unknownClientType := "unknown" 39 | borderClient := mocks.MockNginxClient{} 40 | client, err := NewBorderClient(unknownClientType, borderClient) 41 | if err == nil { 42 | t.Errorf(`expected error creating border client`) 43 | } 44 | 45 | if err.Error() != `unknown border client type: unknown` { 46 | t.Errorf(`expected error to be 'unknown border client type: unknown', got: %v`, err) 47 | } 48 | 49 | if _, ok := client.(*NullBorderClient); !ok { 50 | t.Errorf(`expected client to be of type NullBorderClient`) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /internal/application/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | /* 7 | Package application includes support for applying updates to the Border servers. 8 | 9 | "Border Servers" are servers that are exposed to the outside world and direct traffic into the cluster. 10 | 11 | The BorderClient module defines an interface that can be implemented to support other Border Server types. 12 | To add a Border Server client... 13 | 1. Create a module that implements the BorderClient interface; 14 | 2. Add a new constant in application_constants.go that acts as a key for selecting the client; 15 | 3. Update the NewBorderClient factory method in border_client.go that returns the client; 16 | 17 | At this time the only supported Border Servers are NGINX Plus servers. 18 | 19 | The two Border Server clients for NGINX Plus are: 20 | - NginxHttpBorderClient: updates NGINX Plus servers using HTTP Upstream methods on the NGINX Plus API. 21 | - NginxStreamBorderClient: updates NGINX Plus servers using Stream Upstream methods on the NGINX Plus API. 22 | 23 | Both of these implementations use the NGINX Plus client module to communicate with the NGINX Plus server. 24 | 25 | Selection of the appropriate client is based on the Annotations present on the Service definition, e.g.: 26 | 27 | annotations: 28 | nginxinc.io/nlk-: 29 | 30 | where is the name of the upstream in the NGINX Plus configuration and is one of the constants in application_constants.go. 31 | */ 32 | 33 | package application 34 | -------------------------------------------------------------------------------- /internal/application/nginx_client_interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package application 7 | 8 | import ( 9 | "context" 10 | 11 | nginxClient "github.com/nginxinc/nginx-plus-go-client/v2/client" 12 | ) 13 | 14 | // NginxClientInterface defines the functions used on the NGINX Plus client, abstracting away the full details of that client. 15 | type NginxClientInterface interface { 16 | // DeleteStreamServer is used by the NginxStreamBorderClient. 17 | DeleteStreamServer(ctx context.Context, upstream string, server string) error 18 | 19 | // UpdateStreamServers is used by the NginxStreamBorderClient. 20 | UpdateStreamServers(ctx context.Context, upstream string, servers []nginxClient.StreamUpstreamServer) ([]nginxClient.StreamUpstreamServer, []nginxClient.StreamUpstreamServer, []nginxClient.StreamUpstreamServer, error) 21 | 22 | // DeleteHTTPServer is used by the NginxHttpBorderClient. 23 | DeleteHTTPServer(ctx context.Context, upstream string, server string) error 24 | 25 | // UpdateHTTPServers is used by the NginxHttpBorderClient. 26 | UpdateHTTPServers(ctx context.Context, upstream string, servers []nginxClient.UpstreamServer) ([]nginxClient.UpstreamServer, []nginxClient.UpstreamServer, []nginxClient.UpstreamServer, error) 27 | } 28 | -------------------------------------------------------------------------------- /internal/application/nginx_http_border_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package application 7 | 8 | import ( 9 | "context" 10 | "fmt" 11 | 12 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" 13 | nginxClient "github.com/nginxinc/nginx-plus-go-client/v2/client" 14 | ) 15 | 16 | // NginxHttpBorderClient implements the BorderClient interface for HTTP upstreams. 17 | type NginxHttpBorderClient struct { 18 | BorderClient 19 | nginxClient NginxClientInterface 20 | ctx context.Context 21 | } 22 | 23 | // NewNginxHttpBorderClient is the Factory function for creating an NginxHttpBorderClient. 24 | func NewNginxHttpBorderClient(client interface{}) (Interface, error) { 25 | ngxClient, ok := client.(NginxClientInterface) 26 | if !ok { 27 | return nil, fmt.Errorf(`expected a NginxClientInterface, got a %v`, client) 28 | } 29 | 30 | return &NginxHttpBorderClient{ 31 | nginxClient: ngxClient, 32 | ctx: context.Background(), 33 | }, nil 34 | } 35 | 36 | // Update manages the Upstream servers for the Upstream Name given in the ServerUpdateEvent. 37 | func (hbc *NginxHttpBorderClient) Update(event *core.ServerUpdateEvent) error { 38 | httpUpstreamServers := asNginxHttpUpstreamServers(event.UpstreamServers) 39 | _, _, _, err := hbc.nginxClient.UpdateHTTPServers(hbc.ctx, event.UpstreamName, httpUpstreamServers) 40 | if err != nil { 41 | return fmt.Errorf(`error occurred updating the nginx+ upstream server: %w`, err) 42 | } 43 | 44 | return nil 45 | } 46 | 47 | // Delete deletes the Upstream server for the Upstream Name given in the ServerUpdateEvent. 48 | func (hbc *NginxHttpBorderClient) Delete(event *core.ServerUpdateEvent) error { 49 | err := hbc.nginxClient.DeleteHTTPServer(hbc.ctx, event.UpstreamName, event.UpstreamServers[0].Host) 50 | if err != nil { 51 | return fmt.Errorf(`error occurred deleting the nginx+ upstream server: %w`, err) 52 | } 53 | 54 | return nil 55 | } 56 | 57 | // asNginxHttpUpstreamServer converts a core.UpstreamServer to a nginxClient.UpstreamServer. 58 | func asNginxHttpUpstreamServer(server *core.UpstreamServer) nginxClient.UpstreamServer { 59 | return nginxClient.UpstreamServer{ 60 | Server: server.Host, 61 | } 62 | } 63 | 64 | // asNginxHttpUpstreamServers converts a core.UpstreamServers to a []nginxClient.UpstreamServer. 65 | func asNginxHttpUpstreamServers(servers core.UpstreamServers) []nginxClient.UpstreamServer { 66 | var upstreamServers []nginxClient.UpstreamServer 67 | 68 | for _, server := range servers { 69 | upstreamServers = append(upstreamServers, asNginxHttpUpstreamServer(server)) 70 | } 71 | 72 | return upstreamServers 73 | } 74 | -------------------------------------------------------------------------------- /internal/application/nginx_http_border_client_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package application 7 | 8 | import ( 9 | "testing" 10 | ) 11 | 12 | func TestHttpBorderClient_Delete(t *testing.T) { 13 | event := buildServerUpdateEvent(deletedEventType, ClientTypeNginxHttp) 14 | borderClient, nginxClient, err := buildBorderClient(ClientTypeNginxHttp) 15 | if err != nil { 16 | t.Fatalf(`error occurred creating a new border client: %v`, err) 17 | } 18 | 19 | err = borderClient.Delete(event) 20 | if err != nil { 21 | t.Fatalf(`error occurred deleting the nginx+ upstream server: %v`, err) 22 | } 23 | 24 | if !nginxClient.CalledFunctions["DeleteHTTPServer"] { 25 | t.Fatalf(`expected DeleteHTTPServer to be called`) 26 | } 27 | } 28 | 29 | func TestHttpBorderClient_Update(t *testing.T) { 30 | event := buildServerUpdateEvent(createEventType, ClientTypeNginxHttp) 31 | borderClient, nginxClient, err := buildBorderClient(ClientTypeNginxHttp) 32 | if err != nil { 33 | t.Fatalf(`error occurred creating a new border client: %v`, err) 34 | } 35 | 36 | err = borderClient.Update(event) 37 | if err != nil { 38 | t.Fatalf(`error occurred deleting the nginx+ upstream server: %v`, err) 39 | } 40 | 41 | if !nginxClient.CalledFunctions["UpdateHTTPServers"] { 42 | t.Fatalf(`expected UpdateHTTPServers to be called`) 43 | } 44 | } 45 | 46 | func TestHttpBorderClient_BadNginxClient(t *testing.T) { 47 | var emptyInterface interface{} 48 | _, err := NewBorderClient(ClientTypeNginxHttp, emptyInterface) 49 | if err == nil { 50 | t.Fatalf(`expected an error to occur when creating a new border client`) 51 | } 52 | } 53 | 54 | func TestHttpBorderClient_DeleteReturnsError(t *testing.T) { 55 | event := buildServerUpdateEvent(deletedEventType, ClientTypeNginxHttp) 56 | borderClient, _, err := buildTerrorizingBorderClient(ClientTypeNginxHttp) 57 | if err != nil { 58 | t.Fatalf(`error occurred creating a new border client: %v`, err) 59 | } 60 | 61 | err = borderClient.Delete(event) 62 | 63 | if err == nil { 64 | t.Fatalf(`expected an error to occur when deleting the nginx+ upstream server`) 65 | } 66 | } 67 | 68 | func TestHttpBorderClient_UpdateReturnsError(t *testing.T) { 69 | event := buildServerUpdateEvent(createEventType, ClientTypeNginxHttp) 70 | borderClient, _, err := buildTerrorizingBorderClient(ClientTypeNginxHttp) 71 | if err != nil { 72 | t.Fatalf(`error occurred creating a new border client: %v`, err) 73 | } 74 | 75 | err = borderClient.Update(event) 76 | 77 | if err == nil { 78 | t.Fatalf(`expected an error to occur when deleting the nginx+ upstream server`) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /internal/application/nginx_stream_border_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package application 7 | 8 | import ( 9 | "context" 10 | "fmt" 11 | 12 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" 13 | nginxClient "github.com/nginxinc/nginx-plus-go-client/v2/client" 14 | ) 15 | 16 | // NginxStreamBorderClient implements the BorderClient interface for stream upstreams. 17 | type NginxStreamBorderClient struct { 18 | BorderClient 19 | nginxClient NginxClientInterface 20 | ctx context.Context 21 | } 22 | 23 | // NewNginxStreamBorderClient is the Factory function for creating an NginxStreamBorderClient. 24 | func NewNginxStreamBorderClient(client interface{}) (Interface, error) { 25 | ngxClient, ok := client.(NginxClientInterface) 26 | if !ok { 27 | return nil, fmt.Errorf(`expected a NginxClientInterface, got a %v`, client) 28 | } 29 | 30 | return &NginxStreamBorderClient{ 31 | nginxClient: ngxClient, 32 | ctx: context.Background(), 33 | }, nil 34 | } 35 | 36 | // Update manages the Upstream servers for the Upstream Name given in the ServerUpdateEvent. 37 | func (tbc *NginxStreamBorderClient) Update(event *core.ServerUpdateEvent) error { 38 | streamUpstreamServers := asNginxStreamUpstreamServers(event.UpstreamServers) 39 | _, _, _, err := tbc.nginxClient.UpdateStreamServers(tbc.ctx, event.UpstreamName, streamUpstreamServers) 40 | if err != nil { 41 | return fmt.Errorf(`error occurred updating the nginx+ upstream server: %w`, err) 42 | } 43 | 44 | return nil 45 | } 46 | 47 | // Delete deletes the Upstream server for the Upstream Name given in the ServerUpdateEvent. 48 | func (tbc *NginxStreamBorderClient) Delete(event *core.ServerUpdateEvent) error { 49 | err := tbc.nginxClient.DeleteStreamServer(tbc.ctx, event.UpstreamName, event.UpstreamServers[0].Host) 50 | if err != nil { 51 | return fmt.Errorf(`error occurred deleting the nginx+ upstream server: %w`, err) 52 | } 53 | 54 | return nil 55 | } 56 | 57 | func asNginxStreamUpstreamServer(server *core.UpstreamServer) nginxClient.StreamUpstreamServer { 58 | return nginxClient.StreamUpstreamServer{ 59 | Server: server.Host, 60 | } 61 | } 62 | 63 | func asNginxStreamUpstreamServers(servers core.UpstreamServers) []nginxClient.StreamUpstreamServer { 64 | var upstreamServers []nginxClient.StreamUpstreamServer 65 | 66 | for _, server := range servers { 67 | upstreamServers = append(upstreamServers, asNginxStreamUpstreamServer(server)) 68 | } 69 | 70 | return upstreamServers 71 | } 72 | -------------------------------------------------------------------------------- /internal/application/nginx_stream_border_client_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package application 7 | 8 | import ( 9 | "testing" 10 | ) 11 | 12 | func TestTcpBorderClient_Delete(t *testing.T) { 13 | event := buildServerUpdateEvent(deletedEventType, ClientTypeNginxStream) 14 | borderClient, nginxClient, err := buildBorderClient(ClientTypeNginxStream) 15 | if err != nil { 16 | t.Fatalf(`error occurred creating a new border client: %v`, err) 17 | } 18 | 19 | err = borderClient.Delete(event) 20 | if err != nil { 21 | t.Fatalf(`error occurred deleting the nginx+ upstream server: %v`, err) 22 | } 23 | 24 | if !nginxClient.CalledFunctions["DeleteStreamServer"] { 25 | t.Fatalf(`expected DeleteStreamServer to be called`) 26 | } 27 | } 28 | 29 | func TestTcpBorderClient_Update(t *testing.T) { 30 | event := buildServerUpdateEvent(createEventType, ClientTypeNginxStream) 31 | borderClient, nginxClient, err := buildBorderClient(ClientTypeNginxStream) 32 | if err != nil { 33 | t.Fatalf(`error occurred creating a new border client: %v`, err) 34 | } 35 | 36 | err = borderClient.Update(event) 37 | if err != nil { 38 | t.Fatalf(`error occurred deleting the nginx+ upstream server: %v`, err) 39 | } 40 | 41 | if !nginxClient.CalledFunctions["UpdateStreamServers"] { 42 | t.Fatalf(`expected UpdateStreamServers to be called`) 43 | } 44 | } 45 | 46 | func TestTcpBorderClient_BadNginxClient(t *testing.T) { 47 | var emptyInterface interface{} 48 | _, err := NewBorderClient(ClientTypeNginxStream, emptyInterface) 49 | if err == nil { 50 | t.Fatalf(`expected an error to occur when creating a new border client`) 51 | } 52 | } 53 | 54 | func TestTcpBorderClient_DeleteReturnsError(t *testing.T) { 55 | event := buildServerUpdateEvent(deletedEventType, ClientTypeNginxStream) 56 | borderClient, _, err := buildTerrorizingBorderClient(ClientTypeNginxStream) 57 | if err != nil { 58 | t.Fatalf(`error occurred creating a new border client: %v`, err) 59 | } 60 | 61 | err = borderClient.Delete(event) 62 | 63 | if err == nil { 64 | t.Fatalf(`expected an error to occur when deleting the nginx+ upstream server`) 65 | } 66 | } 67 | 68 | func TestTcpBorderClient_UpdateReturnsError(t *testing.T) { 69 | event := buildServerUpdateEvent(createEventType, ClientTypeNginxStream) 70 | borderClient, _, err := buildTerrorizingBorderClient(ClientTypeNginxStream) 71 | if err != nil { 72 | t.Fatalf(`error occurred creating a new border client: %v`, err) 73 | } 74 | 75 | err = borderClient.Update(event) 76 | 77 | if err == nil { 78 | t.Fatalf(`expected an error to occur when deleting the nginx+ upstream server`) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /internal/application/null_border_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package application 7 | 8 | import ( 9 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" 10 | "github.com/sirupsen/logrus" 11 | ) 12 | 13 | // NullBorderClient is a BorderClient that does nothing. 14 | // / It serves only to prevent a panic if the BorderClient is not set correctly and errors from the factory methods are ignored. 15 | type NullBorderClient struct { 16 | } 17 | 18 | // NewNullBorderClient is the Factory function for creating a NullBorderClient 19 | func NewNullBorderClient() (Interface, error) { 20 | return &NullBorderClient{}, nil 21 | } 22 | 23 | // Update logs a Warning. It is, after all, a NullObject Pattern implementation. 24 | func (nbc *NullBorderClient) Update(_ *core.ServerUpdateEvent) error { 25 | logrus.Warn("NullBorderClient.Update called") 26 | return nil 27 | } 28 | 29 | // Delete logs a Warning. It is, after all, a NullObject Pattern implementation. 30 | func (nbc *NullBorderClient) Delete(_ *core.ServerUpdateEvent) error { 31 | logrus.Warn("NullBorderClient.Delete called") 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /internal/application/null_border_client_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package application 7 | 8 | import "testing" 9 | 10 | func TestNullBorderClient_Delete(t *testing.T) { 11 | client := NullBorderClient{} 12 | err := client.Delete(nil) 13 | if err != nil { 14 | t.Errorf(`expected no error deleting border client, got: %v`, err) 15 | } 16 | } 17 | 18 | func TestNullBorderClient_Update(t *testing.T) { 19 | client := NullBorderClient{} 20 | err := client.Update(nil) 21 | if err != nil { 22 | t.Errorf(`expected no error updating border client, got: %v`, err) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /internal/authentication/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | /* 7 | Package authentication includes functionality to secure communications between NLK and NGINX Plus hosts. 8 | */ 9 | 10 | package authentication 11 | -------------------------------------------------------------------------------- /internal/authentication/factory.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | * 5 | * Factory for creating tls.Config objects based on the provided `tls-mode`. 6 | */ 7 | 8 | package authentication 9 | 10 | import ( 11 | "crypto/tls" 12 | "crypto/x509" 13 | "encoding/pem" 14 | "fmt" 15 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" 16 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" 17 | "github.com/sirupsen/logrus" 18 | ) 19 | 20 | func NewTlsConfig(settings *configuration.Settings) (*tls.Config, error) { 21 | logrus.Debugf("authentication::NewTlsConfig Creating TLS config for mode: '%s'", settings.TlsMode) 22 | switch settings.TlsMode { 23 | 24 | case configuration.NoTLS: 25 | return buildBasicTlsConfig(true), nil 26 | 27 | case configuration.SelfSignedTLS: // needs ca cert 28 | return buildSelfSignedTlsConfig(settings.Certificates) 29 | 30 | case configuration.SelfSignedMutualTLS: // needs ca cert and client cert 31 | return buildSelfSignedMtlsConfig(settings.Certificates) 32 | 33 | case configuration.CertificateAuthorityTLS: // needs nothing 34 | return buildBasicTlsConfig(false), nil 35 | 36 | case configuration.CertificateAuthorityMutualTLS: // needs client cert 37 | return buildCaTlsConfig(settings.Certificates) 38 | 39 | default: 40 | return nil, fmt.Errorf("unknown TLS mode: %s", settings.TlsMode) 41 | } 42 | } 43 | 44 | func buildSelfSignedTlsConfig(certificates *certification.Certificates) (*tls.Config, error) { 45 | logrus.Debug("authentication::buildSelfSignedTlsConfig Building self-signed TLS config") 46 | certPool, err := buildCaCertificatePool(certificates.GetCACertificate()) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | return &tls.Config{ 52 | InsecureSkipVerify: false, 53 | RootCAs: certPool, 54 | }, nil 55 | } 56 | 57 | func buildSelfSignedMtlsConfig(certificates *certification.Certificates) (*tls.Config, error) { 58 | logrus.Debug("authentication::buildSelfSignedMtlsConfig Building self-signed mTLS config") 59 | certPool, err := buildCaCertificatePool(certificates.GetCACertificate()) 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | certificate, err := buildCertificates(certificates.GetClientCertificate()) 65 | if err != nil { 66 | return nil, err 67 | } 68 | logrus.Debugf("buildSelfSignedMtlsConfig Certificate: %v", certificate) 69 | 70 | return &tls.Config{ 71 | InsecureSkipVerify: false, 72 | RootCAs: certPool, 73 | ClientAuth: tls.RequireAndVerifyClientCert, 74 | Certificates: []tls.Certificate{certificate}, 75 | }, nil 76 | } 77 | 78 | func buildBasicTlsConfig(skipVerify bool) *tls.Config { 79 | logrus.Debugf("authentication::buildBasicTlsConfig skipVerify(%v)", skipVerify) 80 | return &tls.Config{ 81 | InsecureSkipVerify: skipVerify, 82 | } 83 | } 84 | 85 | func buildCaTlsConfig(certificates *certification.Certificates) (*tls.Config, error) { 86 | logrus.Debug("authentication::buildCaTlsConfig") 87 | certificate, err := buildCertificates(certificates.GetClientCertificate()) 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | return &tls.Config{ 93 | InsecureSkipVerify: false, 94 | Certificates: []tls.Certificate{certificate}, 95 | }, nil 96 | } 97 | 98 | func buildCertificates(privateKeyPEM []byte, certificatePEM []byte) (tls.Certificate, error) { 99 | logrus.Debug("authentication::buildCertificates") 100 | return tls.X509KeyPair(certificatePEM, privateKeyPEM) 101 | } 102 | 103 | func buildCaCertificatePool(caCert []byte) (*x509.CertPool, error) { 104 | logrus.Debug("authentication::buildCaCertificatePool") 105 | block, _ := pem.Decode(caCert) 106 | if block == nil { 107 | return nil, fmt.Errorf("failed to decode PEM block containing CA certificate") 108 | } 109 | 110 | cert, err := x509.ParseCertificate(block.Bytes) 111 | if err != nil { 112 | return nil, fmt.Errorf("error parsing certificate: %w", err) 113 | } 114 | 115 | caCertPool := x509.NewCertPool() 116 | caCertPool.AddCert(cert) 117 | 118 | return caCertPool, nil 119 | } 120 | -------------------------------------------------------------------------------- /internal/certification/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | /* 7 | Package certification includes functionality to access the Secrets containing the TLS Certificates. 8 | */ 9 | 10 | package certification 11 | -------------------------------------------------------------------------------- /internal/communication/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | /* 7 | Package communication contains helper methods / modules to support creating and configuring standard 8 | net/communication Http Client modules. 9 | */ 10 | 11 | package communication 12 | -------------------------------------------------------------------------------- /internal/communication/factory.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package communication 7 | 8 | import ( 9 | "crypto/tls" 10 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/authentication" 11 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" 12 | "github.com/sirupsen/logrus" 13 | netHttp "net/http" 14 | "time" 15 | ) 16 | 17 | // NewHttpClient is a factory method to create a new Http Client with a default configuration. 18 | // RoundTripper is a wrapper around the default net/communication Transport to add additional headers, in this case, 19 | // the Headers are configured for JSON. 20 | func NewHttpClient(settings *configuration.Settings) (*netHttp.Client, error) { 21 | headers := NewHeaders() 22 | tlsConfig := NewTlsConfig(settings) 23 | transport := NewTransport(tlsConfig) 24 | roundTripper := NewRoundTripper(headers, transport) 25 | 26 | return &netHttp.Client{ 27 | Transport: roundTripper, 28 | CheckRedirect: nil, 29 | Jar: nil, 30 | Timeout: time.Second * 10, 31 | }, nil 32 | } 33 | 34 | // NewHeaders is a factory method to create a new basic Http Headers slice. 35 | func NewHeaders() []string { 36 | return []string{ 37 | "Content-Type: application/json", 38 | "Accept: application/json", 39 | } 40 | } 41 | 42 | // NewTlsConfig is a factory method to create a new basic Tls Config. 43 | // More attention should be given to the use of `InsecureSkipVerify: true`, as it is not recommended for production use. 44 | func NewTlsConfig(settings *configuration.Settings) *tls.Config { 45 | tlsConfig, err := authentication.NewTlsConfig(settings) 46 | if err != nil { 47 | logrus.Warnf("Failed to create TLS config: %v", err) 48 | return &tls.Config{InsecureSkipVerify: true} 49 | } 50 | 51 | return tlsConfig 52 | } 53 | 54 | // NewTransport is a factory method to create a new basic Http Transport. 55 | func NewTransport(config *tls.Config) *netHttp.Transport { 56 | transport := netHttp.DefaultTransport.(*netHttp.Transport) 57 | transport.TLSClientConfig = config 58 | 59 | return transport 60 | } 61 | -------------------------------------------------------------------------------- /internal/communication/factory_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package communication 7 | 8 | import ( 9 | "context" 10 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" 11 | "k8s.io/client-go/kubernetes/fake" 12 | "testing" 13 | ) 14 | 15 | func TestNewHttpClient(t *testing.T) { 16 | k8sClient := fake.NewSimpleClientset() 17 | settings, err := configuration.NewSettings(context.Background(), k8sClient) 18 | client, err := NewHttpClient(settings) 19 | 20 | if err != nil { 21 | t.Fatalf(`Unexpected error: %v`, err) 22 | } 23 | 24 | if client == nil { 25 | t.Fatalf(`client should not be nil`) 26 | } 27 | } 28 | 29 | func TestNewHeaders(t *testing.T) { 30 | headers := NewHeaders() 31 | 32 | if headers == nil { 33 | t.Fatalf(`headers should not be nil`) 34 | } 35 | 36 | if len(headers) != 2 { 37 | t.Fatalf(`headers should have 2 elements`) 38 | } 39 | 40 | if headers[0] != "Content-Type: application/json" { 41 | t.Fatalf(`headers[0] should be "Content-Type: application/json"`) 42 | } 43 | 44 | if headers[1] != "Accept: application/json" { 45 | t.Fatalf(`headers[1] should be "Accept: application/json"`) 46 | } 47 | } 48 | 49 | func TestNewTransport(t *testing.T) { 50 | k8sClient := fake.NewSimpleClientset() 51 | settings, _ := configuration.NewSettings(context.Background(), k8sClient) 52 | config := NewTlsConfig(settings) 53 | transport := NewTransport(config) 54 | 55 | if transport == nil { 56 | t.Fatalf(`transport should not be nil`) 57 | } 58 | 59 | if transport.TLSClientConfig == nil { 60 | t.Fatalf(`transport.TLSClientConfig should not be nil`) 61 | } 62 | 63 | if transport.TLSClientConfig != config { 64 | t.Fatalf(`transport.TLSClientConfig should be the same as config`) 65 | } 66 | 67 | if !transport.TLSClientConfig.InsecureSkipVerify { 68 | t.Fatalf(`transport.TLSClientConfig.InsecureSkipVerify should be true`) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /internal/communication/roundtripper.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package communication 7 | 8 | import ( 9 | "net/http" 10 | netHttp "net/http" 11 | "strings" 12 | ) 13 | 14 | // RoundTripper is a simple type that wraps the default net/communication RoundTripper to add additional headers. 15 | type RoundTripper struct { 16 | Headers []string 17 | RoundTripper http.RoundTripper 18 | } 19 | 20 | // NewRoundTripper is a factory method to create a new RoundTripper. 21 | func NewRoundTripper(headers []string, transport *netHttp.Transport) *RoundTripper { 22 | return &RoundTripper{ 23 | Headers: headers, 24 | RoundTripper: transport, 25 | } 26 | } 27 | 28 | // RoundTrip This simply adds our default headers to the request before passing it on to the default RoundTripper. 29 | func (roundTripper *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 30 | newRequest := new(http.Request) 31 | *newRequest = *req 32 | newRequest.Header = make(http.Header, len(req.Header)) 33 | for k, s := range req.Header { 34 | newRequest.Header[k] = append([]string(nil), s...) 35 | } 36 | for _, s := range roundTripper.Headers { 37 | split := strings.SplitN(s, ":", 2) 38 | if len(split) >= 2 { 39 | newRequest.Header[split[0]] = append([]string(nil), split[1]) 40 | } 41 | } 42 | return roundTripper.RoundTripper.RoundTrip(newRequest) 43 | } 44 | -------------------------------------------------------------------------------- /internal/communication/roundtripper_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package communication 7 | 8 | import ( 9 | "bytes" 10 | "context" 11 | "fmt" 12 | netHttp "net/http" 13 | "net/http/httptest" 14 | "testing" 15 | 16 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" 17 | "k8s.io/client-go/kubernetes/fake" 18 | ) 19 | 20 | func TestNewRoundTripper(t *testing.T) { 21 | k8sClient := fake.NewSimpleClientset() 22 | settings, _ := configuration.NewSettings(context.Background(), k8sClient) 23 | headers := NewHeaders() 24 | transport := NewTransport(NewTlsConfig(settings)) 25 | roundTripper := NewRoundTripper(headers, transport) 26 | 27 | if roundTripper == nil { 28 | t.Fatalf(`roundTripper should not be nil`) 29 | } 30 | 31 | if roundTripper.Headers == nil { 32 | t.Fatalf(`roundTripper.Headers should not be nil`) 33 | } 34 | 35 | if len(roundTripper.Headers) != 2 { 36 | t.Fatalf(`roundTripper.Headers should have 2 elements`) 37 | } 38 | 39 | if roundTripper.Headers[0] != "Content-Type: application/json" { 40 | t.Fatalf(`roundTripper.Headers[0] should be "Content-Type: application/json"`) 41 | } 42 | 43 | if roundTripper.Headers[1] != "Accept: application/json" { 44 | t.Fatalf(`roundTripper.Headers[1] should be "Accept: application/json"`) 45 | } 46 | 47 | if roundTripper.RoundTripper == nil { 48 | t.Fatalf(`roundTripper.RoundTripper should not be nil`) 49 | } 50 | } 51 | 52 | func TestRoundTripperRoundTrip(t *testing.T) { 53 | // Create a mock HTTP server 54 | mockServer := httptest.NewServer(netHttp.HandlerFunc(func(w netHttp.ResponseWriter, r *netHttp.Request) { 55 | w.Header().Set("Content-Type", "application/json") 56 | w.Header().Set("x-mock-header", "test-value") 57 | w.WriteHeader(netHttp.StatusOK) 58 | fmt.Fprintln(w, `{"message": "mock response"}`) 59 | })) 60 | defer mockServer.Close() 61 | 62 | // Initialize dependencies 63 | k8sClient := fake.NewSimpleClientset() 64 | settings, err := configuration.NewSettings(context.Background(), k8sClient) 65 | if err != nil { 66 | t.Fatalf("Unexpected error creating settings: %v", err) 67 | } 68 | 69 | headers := NewHeaders() 70 | transport := NewTransport(NewTlsConfig(settings)) 71 | roundTripper := NewRoundTripper(headers, transport) 72 | 73 | // Use the mock server URL 74 | request, err := NewRequest("GET", mockServer.URL, nil) 75 | if err != nil { 76 | t.Fatalf("Unexpected error: %v", err) 77 | } 78 | 79 | request.Header.Set("Content-Type", "application/json") 80 | request.Header.Set("x-nginx-loadbalancer-kubernetes", "nlk") 81 | 82 | // Perform the request 83 | response, err := roundTripper.RoundTrip(request) 84 | if err != nil { 85 | t.Fatalf("Unexpected error: %v", err) 86 | } 87 | 88 | if response == nil { 89 | t.Fatalf("Response should not be nil") 90 | } 91 | 92 | // Validate response headers 93 | headerLen := len(response.Header) 94 | if headerLen <= 2 { 95 | t.Fatalf("Response headers should have at least 2 elements, found %d", headerLen) 96 | } 97 | } 98 | 99 | func NewRequest(method string, url string, body []byte) (*netHttp.Request, error) { 100 | request, err := netHttp.NewRequest(method, url, bytes.NewBuffer(body)) 101 | if err != nil { 102 | return nil, err 103 | } 104 | 105 | return request, nil 106 | } 107 | -------------------------------------------------------------------------------- /internal/configuration/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | /* 7 | Package config includes application configuration. 8 | */ 9 | 10 | package configuration 11 | -------------------------------------------------------------------------------- /internal/configuration/tlsmodes.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package configuration 7 | 8 | const ( 9 | NoTLS TLSMode = iota 10 | CertificateAuthorityTLS 11 | CertificateAuthorityMutualTLS 12 | SelfSignedTLS 13 | SelfSignedMutualTLS 14 | ) 15 | 16 | const ( 17 | NoTLSString = "no-tls" 18 | CertificateAuthorityTLSString = "ca-tls" 19 | CertificateAuthorityMutualTLSString = "ca-mtls" 20 | SelfSignedTLSString = "ss-tls" 21 | SelfSignedMutualTLSString = "ss-mtls" 22 | ) 23 | 24 | type TLSMode int 25 | 26 | var TLSModeMap = map[string]TLSMode{ 27 | NoTLSString: NoTLS, 28 | CertificateAuthorityTLSString: CertificateAuthorityTLS, 29 | CertificateAuthorityMutualTLSString: CertificateAuthorityMutualTLS, 30 | SelfSignedTLSString: SelfSignedTLS, 31 | SelfSignedMutualTLSString: SelfSignedMutualTLS, 32 | } 33 | 34 | func (t TLSMode) String() string { 35 | modes := []string{ 36 | NoTLSString, 37 | CertificateAuthorityTLSString, 38 | CertificateAuthorityMutualTLSString, 39 | SelfSignedTLSString, 40 | SelfSignedMutualTLSString, 41 | } 42 | if t < NoTLS || t > SelfSignedMutualTLS { 43 | return "" 44 | } 45 | return modes[t] 46 | } 47 | -------------------------------------------------------------------------------- /internal/configuration/tlsmodes_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package configuration 7 | 8 | import ( 9 | "testing" 10 | ) 11 | 12 | func Test_String(t *testing.T) { 13 | mode := NoTLS.String() 14 | if mode != "no-tls" { 15 | t.Errorf("Expected TLSModeNoTLS to be 'no-tls', got '%s'", mode) 16 | } 17 | 18 | mode = CertificateAuthorityTLS.String() 19 | if mode != "ca-tls" { 20 | t.Errorf("Expected TLSModeCaTLS to be 'ca-tls', got '%s'", mode) 21 | } 22 | 23 | mode = CertificateAuthorityMutualTLS.String() 24 | if mode != "ca-mtls" { 25 | t.Errorf("Expected TLSModeCaMTLS to be 'ca-mtls', got '%s'", mode) 26 | } 27 | 28 | mode = SelfSignedTLS.String() 29 | if mode != "ss-tls" { 30 | t.Errorf("Expected TLSModeSsTLS to be 'ss-tls', got '%s'", mode) 31 | } 32 | 33 | mode = SelfSignedMutualTLS.String() 34 | if mode != "ss-mtls" { 35 | t.Errorf("Expected TLSModeSsMTLS to be 'ss-mtls', got '%s',", mode) 36 | } 37 | 38 | mode = TLSMode(5).String() 39 | if mode != "" { 40 | t.Errorf("Expected TLSMode(5) to be '', got '%s'", mode) 41 | } 42 | } 43 | 44 | func Test_TLSModeMap(t *testing.T) { 45 | mode := TLSModeMap["no-tls"] 46 | if mode != NoTLS { 47 | t.Errorf("Expected TLSModeMap['no-tls'] to be TLSModeNoTLS, got '%d'", mode) 48 | } 49 | 50 | mode = TLSModeMap["ca-tls"] 51 | if mode != CertificateAuthorityTLS { 52 | t.Errorf("Expected TLSModeMap['ca-tls'] to be TLSModeCaTLS, got '%d'", mode) 53 | } 54 | 55 | mode = TLSModeMap["ca-mtls"] 56 | if mode != CertificateAuthorityMutualTLS { 57 | t.Errorf("Expected TLSModeMap['ca-mtls'] to be TLSModeCaMTLS, got '%d'", mode) 58 | } 59 | 60 | mode = TLSModeMap["ss-tls"] 61 | if mode != SelfSignedTLS { 62 | t.Errorf("Expected TLSModeMap['ss-tls'] to be TLSModeSsTLS, got '%d'", mode) 63 | } 64 | 65 | mode = TLSModeMap["ss-mtls"] 66 | if mode != SelfSignedMutualTLS { 67 | t.Errorf("Expected TLSModeMap['ss-mtls'] to be TLSModeSsMTLS, got '%d'", mode) 68 | } 69 | 70 | mode = TLSModeMap["invalid"] 71 | if mode != TLSMode(0) { 72 | t.Errorf("Expected TLSModeMap['invalid'] to be TLSMode(0), got '%d'", mode) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /internal/core/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | /* 7 | Package core includes core application structures and logic. 8 | */ 9 | 10 | package core 11 | -------------------------------------------------------------------------------- /internal/core/event.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package core 7 | 8 | import v1 "k8s.io/api/core/v1" 9 | 10 | type EventType int 11 | 12 | // Event types 13 | const ( 14 | 15 | // Created Represents the event type when a service is created 16 | Created EventType = iota 17 | 18 | // Updated Represents the event type when a service is updated 19 | Updated 20 | 21 | // Deleted Represents the event type when a service is deleted 22 | Deleted 23 | ) 24 | 25 | // Event represents a service event 26 | type Event struct { 27 | 28 | // Type represents the event type, one of the constant values defined above. 29 | Type EventType 30 | 31 | // Service represents the service object in its current state 32 | Service *v1.Service 33 | 34 | // PreviousService represents the service object in its previous state 35 | PreviousService *v1.Service 36 | 37 | // NodeIps represents the list of node IPs in the Cluster. This is populated by the Watcher when an event is created. 38 | // The Node IPs are needed by the BorderClient. 39 | NodeIps []string 40 | } 41 | 42 | // NewEvent factory method to create a new Event 43 | func NewEvent(eventType EventType, service *v1.Service, previousService *v1.Service, nodeIps []string) Event { 44 | return Event{ 45 | Type: eventType, 46 | Service: service, 47 | PreviousService: previousService, 48 | NodeIps: nodeIps, 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /internal/core/event_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | v1 "k8s.io/api/core/v1" 5 | "testing" 6 | ) 7 | 8 | func TestNewEvent(t *testing.T) { 9 | expectedType := Created 10 | expectedService := &v1.Service{} 11 | expectedPreviousService := &v1.Service{} 12 | expectedNodeIps := []string{"127.0.0.1"} 13 | 14 | event := NewEvent(expectedType, expectedService, expectedPreviousService, expectedNodeIps) 15 | 16 | if event.Type != expectedType { 17 | t.Errorf("expected Created, got %v", event.Type) 18 | } 19 | 20 | if event.Service != expectedService { 21 | t.Errorf("expected service, got %#v", event.Service) 22 | } 23 | 24 | if event.PreviousService != expectedPreviousService { 25 | t.Errorf("expected previous service, got %#v", event.PreviousService) 26 | } 27 | 28 | if event.NodeIps[0] != expectedNodeIps[0] { 29 | t.Errorf("expected node ips, got %#v", event.NodeIps) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /internal/core/secret_bytes.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // Wraps byte slices which potentially could contain 8 | // sensitive data that should not be output to the logs. 9 | // This will output [REDACTED] if attempts are made 10 | // to print this type in logs, serialize to JSON, or 11 | // otherwise convert it to a string. 12 | // Usage: core.SecretBytes(myByteSlice) 13 | type SecretBytes []byte 14 | 15 | func (sb SecretBytes) String() string { 16 | return "[REDACTED]" 17 | } 18 | 19 | func (sb SecretBytes) MarshalJSON() ([]byte, error) { 20 | return json.Marshal("[REDACTED]") 21 | } 22 | -------------------------------------------------------------------------------- /internal/core/secret_bytes_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package core 7 | 8 | import ( 9 | "encoding/json" 10 | "fmt" 11 | "testing" 12 | ) 13 | 14 | func TestSecretBytesToString(t *testing.T) { 15 | sensitive := SecretBytes([]byte("If you can see this we have a problem")) 16 | 17 | expected := "foo [REDACTED] bar" 18 | result := fmt.Sprintf("foo %v bar", sensitive) 19 | if result != expected { 20 | t.Errorf("Expected %s, got %s", expected, result) 21 | } 22 | } 23 | 24 | func TestSecretBytesToJSON(t *testing.T) { 25 | sensitive, _ := json.Marshal(SecretBytes([]byte("If you can see this we have a problem"))) 26 | expected := `foo "[REDACTED]" bar` 27 | result := fmt.Sprintf("foo %v bar", string(sensitive)) 28 | if result != expected { 29 | t.Errorf("Expected %s, got %s", expected, result) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /internal/core/server_update_event.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package core 7 | 8 | // ServerUpdateEvent is an internal representation of an event. The Translator produces these events 9 | // from Events received from the Handler. These are then consumed by the Synchronizer and passed along to 10 | // the appropriate BorderClient. 11 | type ServerUpdateEvent struct { 12 | 13 | // ClientType is the type of BorderClient that should handle this event. This is configured via Service Annotations. 14 | // See application_constants.go for the list of supported types. 15 | ClientType string 16 | 17 | // Id is the unique identifier for this event. 18 | Id string 19 | 20 | // NginxHost is the host name of the NGINX Plus instance that should handle this event. 21 | NginxHost string 22 | 23 | // Type is the type of event. See EventType for the list of supported types. 24 | Type EventType 25 | 26 | // UpstreamName is the name of the upstream in the Border Server. 27 | UpstreamName string 28 | 29 | // UpstreamServers is the list of servers in the Upstream. 30 | UpstreamServers UpstreamServers 31 | } 32 | 33 | // ServerUpdateEvents is a list of ServerUpdateEvent. 34 | type ServerUpdateEvents = []*ServerUpdateEvent 35 | 36 | // NewServerUpdateEvent creates a new ServerUpdateEvent. 37 | func NewServerUpdateEvent(eventType EventType, upstreamName string, clientType string, upstreamServers UpstreamServers) *ServerUpdateEvent { 38 | return &ServerUpdateEvent{ 39 | ClientType: clientType, 40 | Type: eventType, 41 | UpstreamName: upstreamName, 42 | UpstreamServers: upstreamServers, 43 | } 44 | } 45 | 46 | // ServerUpdateEventWithIdAndHost creates a new ServerUpdateEvent with the specified Id and Host. 47 | func ServerUpdateEventWithIdAndHost(event *ServerUpdateEvent, id string, nginxHost string) *ServerUpdateEvent { 48 | return &ServerUpdateEvent{ 49 | ClientType: event.ClientType, 50 | Id: id, 51 | NginxHost: nginxHost, 52 | Type: event.Type, 53 | UpstreamName: event.UpstreamName, 54 | UpstreamServers: event.UpstreamServers, 55 | } 56 | } 57 | 58 | // TypeName returns the string representation of the EventType. 59 | func (e *ServerUpdateEvent) TypeName() string { 60 | switch e.Type { 61 | case Created: 62 | return "Created" 63 | case Updated: 64 | return "Updated" 65 | case Deleted: 66 | return "Deleted" 67 | default: 68 | return "Unknown" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /internal/core/server_update_event_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package core 7 | 8 | import ( 9 | "testing" 10 | ) 11 | 12 | const clientType = "clientType" 13 | 14 | var emptyUpstreamServers UpstreamServers 15 | 16 | func TestServerUpdateEventWithIdAndHost(t *testing.T) { 17 | event := NewServerUpdateEvent(Created, "upstream", clientType, emptyUpstreamServers) 18 | 19 | if event.Id != "" { 20 | t.Errorf("expected empty Id, got %s", event.Id) 21 | } 22 | 23 | if event.NginxHost != "" { 24 | t.Errorf("expected empty NginxHost, got %s", event.NginxHost) 25 | } 26 | 27 | eventWithIdAndHost := ServerUpdateEventWithIdAndHost(event, "id", "host") 28 | 29 | if eventWithIdAndHost.Id != "id" { 30 | t.Errorf("expected Id to be 'id', got %s", eventWithIdAndHost.Id) 31 | } 32 | 33 | if eventWithIdAndHost.NginxHost != "host" { 34 | t.Errorf("expected NginxHost to be 'host', got %s", eventWithIdAndHost.NginxHost) 35 | } 36 | 37 | if eventWithIdAndHost.ClientType != clientType { 38 | t.Errorf("expected ClientType to be '%s', got %s", clientType, eventWithIdAndHost.ClientType) 39 | } 40 | } 41 | 42 | func TestTypeNameCreated(t *testing.T) { 43 | event := NewServerUpdateEvent(Created, "upstream", clientType, emptyUpstreamServers) 44 | 45 | if event.TypeName() != "Created" { 46 | t.Errorf("expected 'Created', got %s", event.TypeName()) 47 | } 48 | } 49 | 50 | func TestTypeNameUpdated(t *testing.T) { 51 | event := NewServerUpdateEvent(Updated, "upstream", clientType, emptyUpstreamServers) 52 | 53 | if event.TypeName() != "Updated" { 54 | t.Errorf("expected 'Updated', got %s", event.TypeName()) 55 | } 56 | } 57 | 58 | func TestTypeNameDeleted(t *testing.T) { 59 | event := NewServerUpdateEvent(Deleted, "upstream", clientType, emptyUpstreamServers) 60 | 61 | if event.TypeName() != "Deleted" { 62 | t.Errorf("expected 'Deleted', got %s", event.TypeName()) 63 | } 64 | } 65 | 66 | func TestTypeNameUnknown(t *testing.T) { 67 | event := NewServerUpdateEvent(EventType(100), "upstream", clientType, emptyUpstreamServers) 68 | 69 | if event.TypeName() != "Unknown" { 70 | t.Errorf("expected 'Unknown', got %s", event.TypeName()) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /internal/core/upstream_server.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package core 7 | 8 | // UpstreamServer represents a single upstream server. This is an internal representation used to abstract the definition 9 | // of an upstream server from any specific client. 10 | type UpstreamServer struct { 11 | 12 | // Host is the host name or IP address of the upstream server. 13 | Host string 14 | } 15 | 16 | // UpstreamServers is a slice of UpstreamServer. 17 | type UpstreamServers = []*UpstreamServer 18 | 19 | // NewUpstreamServer creates a new UpstreamServer. 20 | func NewUpstreamServer(host string) *UpstreamServer { 21 | return &UpstreamServer{ 22 | Host: host, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /internal/core/upstream_server_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package core 7 | 8 | import "testing" 9 | 10 | func TestNewUpstreamServer(t *testing.T) { 11 | host := "localhost" 12 | us := NewUpstreamServer(host) 13 | if us.Host != host { 14 | t.Errorf("NewUpstreamServer(%s) = %s; want %s", host, us.Host, host) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /internal/observation/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | /* 7 | Package observation includes functionality to add k8s event handlers to Ingress resources. 8 | */ 9 | 10 | package observation 11 | -------------------------------------------------------------------------------- /internal/observation/handler_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package observation 7 | 8 | import ( 9 | "context" 10 | "fmt" 11 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" 12 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" 13 | "github.com/nginxinc/kubernetes-nginx-ingress/test/mocks" 14 | v1 "k8s.io/api/core/v1" 15 | "k8s.io/client-go/util/workqueue" 16 | "testing" 17 | ) 18 | 19 | func TestHandler_AddsEventToSynchronizer(t *testing.T) { 20 | _, _, synchronizer, handler, err := buildHandler() 21 | if err != nil { 22 | t.Errorf(`should have been no error, %v`, err) 23 | } 24 | 25 | event := &core.Event{ 26 | Type: core.Created, 27 | Service: &v1.Service{ 28 | Spec: v1.ServiceSpec{ 29 | Ports: []v1.ServicePort{ 30 | { 31 | Name: "nlk-back", 32 | }, 33 | }, 34 | }, 35 | }, 36 | } 37 | 38 | handler.AddRateLimitedEvent(event) 39 | 40 | handler.handleNextEvent() 41 | 42 | if len(synchronizer.Events) != 1 { 43 | t.Errorf(`handler.AddRateLimitedEvent did not add the event to the queue`) 44 | } 45 | } 46 | 47 | func buildHandler() (*configuration.Settings, workqueue.RateLimitingInterface, *mocks.MockSynchronizer, *Handler, error) { 48 | settings, err := configuration.NewSettings(context.Background(), nil) 49 | if err != nil { 50 | return nil, nil, nil, nil, fmt.Errorf(`should have been no error, %v`, err) 51 | } 52 | 53 | eventQueue := &mocks.MockRateLimiter{} 54 | synchronizer := &mocks.MockSynchronizer{} 55 | 56 | handler := NewHandler(settings, synchronizer, eventQueue) 57 | 58 | return settings, eventQueue, synchronizer, handler, nil 59 | } 60 | -------------------------------------------------------------------------------- /internal/observation/watcher_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package observation 7 | 8 | import ( 9 | "context" 10 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" 11 | "github.com/nginxinc/kubernetes-nginx-ingress/test/mocks" 12 | "k8s.io/client-go/kubernetes" 13 | "testing" 14 | ) 15 | 16 | func TestWatcher_MustInitialize(t *testing.T) { 17 | watcher, _ := buildWatcher() 18 | if err := watcher.Watch(); err == nil { 19 | t.Errorf("Expected error, got %s", err) 20 | } 21 | } 22 | 23 | func buildWatcher() (*Watcher, error) { 24 | k8sClient := &kubernetes.Clientset{} 25 | settings, _ := configuration.NewSettings(context.Background(), k8sClient) 26 | handler := &mocks.MockHandler{} 27 | 28 | return NewWatcher(settings, handler) 29 | } 30 | -------------------------------------------------------------------------------- /internal/probation/check.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package probation 7 | 8 | // Check defines a single method that can be implemented for various health checks. 9 | type Check interface { 10 | Check() bool 11 | } 12 | 13 | // LiveCheck is a check that can be used for the k8s "livez" endpoint. 14 | type LiveCheck struct { 15 | } 16 | 17 | // ReadyCheck is a check that can be used for the k8s "readyz" endpoint. 18 | type ReadyCheck struct { 19 | } 20 | 21 | // StartupCheck is a check that can be used for the k8s "startupz" endpoint. 22 | type StartupCheck struct { 23 | } 24 | 25 | // Check implements the Check interface for the LiveCheck type. 26 | func (l *LiveCheck) Check() bool { 27 | return true 28 | } 29 | 30 | // Check implements the Check interface for the ReadyCheck type. 31 | func (r *ReadyCheck) Check() bool { 32 | return true 33 | } 34 | 35 | // Check implements the Check interface for the StartupCheck type. 36 | func (s *StartupCheck) Check() bool { 37 | return true 38 | } 39 | -------------------------------------------------------------------------------- /internal/probation/check_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package probation 7 | 8 | import "testing" 9 | 10 | func TestCheck_LiveCheck(t *testing.T) { 11 | check := LiveCheck{} 12 | if !check.Check() { 13 | t.Errorf("LiveCheck should return true") 14 | } 15 | } 16 | 17 | func TestCheck_ReadyCheck(t *testing.T) { 18 | check := ReadyCheck{} 19 | if !check.Check() { 20 | t.Errorf("ReadyCheck should return true") 21 | } 22 | } 23 | 24 | func TestCheck_StartupCheck(t *testing.T) { 25 | check := StartupCheck{} 26 | if !check.Check() { 27 | t.Errorf("StartupCheck should return true") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /internal/probation/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | /* 7 | Package probation includes support for Kubernetes health and wellness checks. 8 | */ 9 | 10 | package probation 11 | -------------------------------------------------------------------------------- /internal/probation/server.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package probation 7 | 8 | import ( 9 | "fmt" 10 | "github.com/sirupsen/logrus" 11 | "net/http" 12 | ) 13 | 14 | const ( 15 | 16 | // Ok is the message returned when a check passes. 17 | Ok = "OK" 18 | 19 | // ServiceNotAvailable is the message returned when a check fails. 20 | ServiceNotAvailable = "Service Not Available" 21 | 22 | // ListenPort is the port on which the health server will listen. 23 | ListenPort = 51031 24 | ) 25 | 26 | // HealthServer is a server that spins up endpoints for the various k8s health checks. 27 | type HealthServer struct { 28 | 29 | // The underlying HTTP server. 30 | httpServer *http.Server 31 | 32 | // Support for the "livez" endpoint. 33 | LiveCheck LiveCheck 34 | 35 | // Support for the "readyz" endpoint. 36 | ReadyCheck ReadyCheck 37 | 38 | // Support for the "startupz" endpoint. 39 | StartupCheck StartupCheck 40 | } 41 | 42 | // NewHealthServer creates a new HealthServer. 43 | func NewHealthServer() *HealthServer { 44 | return &HealthServer{ 45 | LiveCheck: LiveCheck{}, 46 | ReadyCheck: ReadyCheck{}, 47 | StartupCheck: StartupCheck{}, 48 | } 49 | } 50 | 51 | // Start spins up the health server. 52 | func (hs *HealthServer) Start() { 53 | logrus.Debugf("Starting probe listener on port %d", ListenPort) 54 | 55 | address := fmt.Sprintf(":%d", ListenPort) 56 | 57 | mux := http.NewServeMux() 58 | mux.HandleFunc("/livez", hs.HandleLive) 59 | mux.HandleFunc("/readyz", hs.HandleReady) 60 | mux.HandleFunc("/startupz", hs.HandleStartup) 61 | hs.httpServer = &http.Server{Addr: address, Handler: mux} 62 | 63 | go func() { 64 | if err := hs.httpServer.ListenAndServe(); err != nil { 65 | logrus.Errorf("unable to start probe listener on %s: %v", hs.httpServer.Addr, err) 66 | } 67 | }() 68 | 69 | logrus.Info("Started probe listener on", hs.httpServer.Addr) 70 | } 71 | 72 | // Stop shuts down the health server. 73 | func (hs *HealthServer) Stop() { 74 | if err := hs.httpServer.Close(); err != nil { 75 | logrus.Errorf("unable to stop probe listener on %s: %v", hs.httpServer.Addr, err) 76 | } 77 | } 78 | 79 | // HandleLive is the handler for the "livez" endpoint. 80 | func (hs *HealthServer) HandleLive(writer http.ResponseWriter, request *http.Request) { 81 | hs.handleProbe(writer, request, &hs.LiveCheck) 82 | } 83 | 84 | // HandleReady is the handler for the "readyz" endpoint. 85 | func (hs *HealthServer) HandleReady(writer http.ResponseWriter, request *http.Request) { 86 | hs.handleProbe(writer, request, &hs.ReadyCheck) 87 | } 88 | 89 | // HandleStartup is the handler for the "startupz" endpoint. 90 | func (hs *HealthServer) HandleStartup(writer http.ResponseWriter, request *http.Request) { 91 | hs.handleProbe(writer, request, &hs.StartupCheck) 92 | } 93 | 94 | // handleProbe handles calling the appropriate Check method and writes the result to the client. 95 | func (hs *HealthServer) handleProbe(writer http.ResponseWriter, _ *http.Request, check Check) { 96 | if check.Check() { 97 | writer.WriteHeader(http.StatusOK) 98 | 99 | if _, err := fmt.Fprint(writer, Ok); err != nil { 100 | logrus.Error(err) 101 | } 102 | 103 | } else { 104 | writer.WriteHeader(http.StatusServiceUnavailable) 105 | 106 | if _, err := fmt.Fprint(writer, ServiceNotAvailable); err != nil { 107 | logrus.Error(err) 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /internal/probation/server_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package probation 7 | 8 | import ( 9 | "github.com/nginxinc/kubernetes-nginx-ingress/test/mocks" 10 | "github.com/sirupsen/logrus" 11 | "net/http" 12 | "testing" 13 | ) 14 | 15 | func TestHealthServer_HandleLive(t *testing.T) { 16 | server := NewHealthServer() 17 | writer := mocks.NewMockResponseWriter() 18 | server.HandleLive(writer, nil) 19 | 20 | if string(writer.Body()) != Ok { 21 | t.Errorf("HandleLive should return %s", Ok) 22 | } 23 | } 24 | 25 | func TestHealthServer_HandleReady(t *testing.T) { 26 | server := NewHealthServer() 27 | writer := mocks.NewMockResponseWriter() 28 | server.HandleReady(writer, nil) 29 | 30 | if string(writer.Body()) != Ok { 31 | t.Errorf("HandleReady should return %s", Ok) 32 | } 33 | } 34 | 35 | func TestHealthServer_HandleStartup(t *testing.T) { 36 | server := NewHealthServer() 37 | writer := mocks.NewMockResponseWriter() 38 | server.HandleStartup(writer, nil) 39 | 40 | if string(writer.Body()) != Ok { 41 | t.Errorf("HandleStartup should return %s", Ok) 42 | } 43 | } 44 | 45 | func TestHealthServer_HandleFailCheck(t *testing.T) { 46 | failCheck := mocks.NewMockCheck(false) 47 | server := NewHealthServer() 48 | writer := mocks.NewMockResponseWriter() 49 | server.handleProbe(writer, nil, failCheck) 50 | 51 | body := string(writer.Body()) 52 | if body != "Service Not Available" { 53 | t.Errorf("Expected 'Service Not Available', got %v", body) 54 | } 55 | } 56 | 57 | func TestHealthServer_Start(t *testing.T) { 58 | server := NewHealthServer() 59 | server.Start() 60 | 61 | defer server.Stop() 62 | 63 | response, err := http.Get("http://localhost:51031/livez") 64 | if err != nil { 65 | t.Error(err) 66 | } 67 | 68 | if response.StatusCode != http.StatusOK { 69 | t.Errorf("Expected status code %v, got %v", http.StatusAccepted, response.StatusCode) 70 | } 71 | 72 | logrus.Infof("received a response from the probe server: %v", response) 73 | } 74 | -------------------------------------------------------------------------------- /internal/synchronization/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | /* 7 | Package synchronization includes functionality to synchronize Ingress events to NGINX+ Configuration API commands. 8 | */ 9 | 10 | package synchronization 11 | -------------------------------------------------------------------------------- /internal/synchronization/rand.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package synchronization 7 | 8 | import ( 9 | "math/rand" 10 | "time" 11 | ) 12 | 13 | // charset contains all characters that can be used in random string generation 14 | var charset = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 15 | 16 | // number contains all numbers that can be used in random string generation 17 | var number = []byte("0123456789") 18 | 19 | // alphaNumeric contains all characters and numbers that can be used in random string generation 20 | var alphaNumeric = append(charset, number...) 21 | 22 | // RandomString where n is the length of random string we want to generate 23 | func RandomString(n int) string { 24 | b := make([]byte, n) 25 | for i := range b { 26 | // randomly select 1 character from given charset 27 | b[i] = alphaNumeric[rand.Intn(len(alphaNumeric))] 28 | } 29 | return string(b) 30 | } 31 | 32 | // RandomMilliseconds returns a random duration between min and max milliseconds 33 | func RandomMilliseconds(min, max int) time.Duration { 34 | randomizer := rand.New(rand.NewSource(time.Now().UnixNano())) 35 | random := randomizer.Intn(max-min) + min 36 | 37 | return time.Millisecond * time.Duration(random) 38 | } 39 | -------------------------------------------------------------------------------- /internal/translation/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | /* 7 | Package translation includes functionality to translate Ingress events to target system definitions. 8 | */ 9 | 10 | package translation 11 | -------------------------------------------------------------------------------- /internal/translation/translator.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package translation 7 | 8 | import ( 9 | "fmt" 10 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/application" 11 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" 12 | "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" 13 | "github.com/sirupsen/logrus" 14 | v1 "k8s.io/api/core/v1" 15 | "strings" 16 | ) 17 | 18 | // Translate transforms event data into an intermediate format that can be consumed by the BorderClient implementations 19 | // and used to update the Border Servers. 20 | func Translate(event *core.Event) (core.ServerUpdateEvents, error) { 21 | logrus.Debug("Translate::Translate") 22 | 23 | portsOfInterest := filterPorts(event.Service.Spec.Ports) 24 | 25 | return buildServerUpdateEvents(portsOfInterest, event) 26 | } 27 | 28 | // filterPorts returns a list of ports that have the NlkPrefix in the port name. 29 | func filterPorts(ports []v1.ServicePort) []v1.ServicePort { 30 | var portsOfInterest []v1.ServicePort 31 | 32 | for _, port := range ports { 33 | if strings.HasPrefix(port.Name, configuration.NlkPrefix) { 34 | portsOfInterest = append(portsOfInterest, port) 35 | } 36 | } 37 | 38 | return portsOfInterest 39 | } 40 | 41 | // buildServerUpdateEvents builds a list of ServerUpdateEvents based on the event type 42 | // The NGINX+ Client uses a list of servers for Created and Updated events; the client performs reconciliation between 43 | // the list of servers in the NGINX+ Client call and the list of servers in NGINX+. 44 | // The NGINX+ Client uses a single server for Deleted events; so the list of servers is broken up into individual events. 45 | func buildServerUpdateEvents(ports []v1.ServicePort, event *core.Event) (core.ServerUpdateEvents, error) { 46 | logrus.Debugf("Translate::buildServerUpdateEvents(ports=%#v)", ports) 47 | 48 | events := core.ServerUpdateEvents{} 49 | for _, port := range ports { 50 | ingressName := fixIngressName(port.Name) 51 | upstreamServers, _ := buildUpstreamServers(event.NodeIps, port) 52 | clientType := getClientType(port.Name, event.Service.Annotations) 53 | 54 | switch event.Type { 55 | case core.Created: 56 | fallthrough 57 | 58 | case core.Updated: 59 | events = append(events, core.NewServerUpdateEvent(event.Type, ingressName, clientType, upstreamServers)) 60 | 61 | case core.Deleted: 62 | for _, server := range upstreamServers { 63 | events = append(events, core.NewServerUpdateEvent(event.Type, ingressName, clientType, core.UpstreamServers{server})) 64 | } 65 | 66 | default: 67 | logrus.Warnf(`Translator::buildServerUpdateEvents: unknown event type: %d`, event.Type) 68 | } 69 | 70 | } 71 | 72 | return events, nil 73 | } 74 | 75 | func buildUpstreamServers(nodeIps []string, port v1.ServicePort) (core.UpstreamServers, error) { 76 | var servers core.UpstreamServers 77 | 78 | for _, nodeIp := range nodeIps { 79 | host := fmt.Sprintf("%s:%d", nodeIp, port.NodePort) 80 | server := core.NewUpstreamServer(host) 81 | servers = append(servers, server) 82 | } 83 | 84 | return servers, nil 85 | } 86 | 87 | // fixIngressName removes the NlkPrefix from the port name 88 | func fixIngressName(name string) string { 89 | return name[4:] 90 | } 91 | 92 | // getClientType returns the client type for the port, defaults to ClientTypeNginxHttp if no Annotation is found. 93 | func getClientType(portName string, annotations map[string]string) string { 94 | key := fmt.Sprintf("%s/%s", configuration.PortAnnotationPrefix, portName) 95 | logrus.Infof("getClientType: key=%s", key) 96 | if annotations != nil { 97 | if clientType, ok := annotations[key]; ok { 98 | return clientType 99 | } 100 | } 101 | 102 | return application.ClientTypeNginxHttp 103 | } 104 | -------------------------------------------------------------------------------- /test/mocks/mock_check.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package mocks 7 | 8 | type MockCheck struct { 9 | result bool 10 | } 11 | 12 | func NewMockCheck(result bool) *MockCheck { 13 | return &MockCheck{result: result} 14 | } 15 | 16 | func (m *MockCheck) Check() bool { 17 | return m.result 18 | } 19 | -------------------------------------------------------------------------------- /test/mocks/mock_handler.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package mocks 7 | 8 | import "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" 9 | 10 | type MockHandler struct { 11 | } 12 | 13 | func (h *MockHandler) AddRateLimitedEvent(_ *core.Event) { 14 | 15 | } 16 | 17 | func (h *MockHandler) Initialize() { 18 | 19 | } 20 | 21 | func (h *MockHandler) Run(_ <-chan struct{}) { 22 | 23 | } 24 | 25 | func (h *MockHandler) ShutDown() { 26 | 27 | } 28 | -------------------------------------------------------------------------------- /test/mocks/mock_nginx_plus_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package mocks 7 | 8 | import ( 9 | "context" 10 | 11 | nginxClient "github.com/nginxinc/nginx-plus-go-client/v2/client" 12 | ) 13 | 14 | type MockNginxClient struct { 15 | CalledFunctions map[string]bool 16 | Error error 17 | } 18 | 19 | func NewMockNginxClient() *MockNginxClient { 20 | return &MockNginxClient{ 21 | CalledFunctions: make(map[string]bool), 22 | Error: nil, 23 | } 24 | } 25 | 26 | func NewErroringMockClient(err error) *MockNginxClient { 27 | return &MockNginxClient{ 28 | CalledFunctions: make(map[string]bool), 29 | Error: err, 30 | } 31 | } 32 | 33 | func (m MockNginxClient) DeleteStreamServer(ctx context.Context, string, _ string) error { 34 | m.CalledFunctions["DeleteStreamServer"] = true 35 | 36 | if m.Error != nil { 37 | return m.Error 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func (m MockNginxClient) UpdateStreamServers(ctx context.Context, _ string, _ []nginxClient.StreamUpstreamServer) ([]nginxClient.StreamUpstreamServer, []nginxClient.StreamUpstreamServer, []nginxClient.StreamUpstreamServer, error) { 44 | m.CalledFunctions["UpdateStreamServers"] = true 45 | 46 | if m.Error != nil { 47 | return nil, nil, nil, m.Error 48 | } 49 | 50 | return nil, nil, nil, nil 51 | } 52 | 53 | func (m MockNginxClient) DeleteHTTPServer(ctx context.Context, _ string, _ string) error { 54 | m.CalledFunctions["DeleteHTTPServer"] = true 55 | 56 | if m.Error != nil { 57 | return m.Error 58 | } 59 | 60 | return nil 61 | } 62 | 63 | func (m MockNginxClient) UpdateHTTPServers(ctx context.Context, _ string, _ []nginxClient.UpstreamServer) ([]nginxClient.UpstreamServer, []nginxClient.UpstreamServer, []nginxClient.UpstreamServer, error) { 64 | m.CalledFunctions["UpdateHTTPServers"] = true 65 | 66 | if m.Error != nil { 67 | return nil, nil, nil, m.Error 68 | } 69 | 70 | return nil, nil, nil, nil 71 | } 72 | -------------------------------------------------------------------------------- /test/mocks/mock_ratelimitinginterface.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package mocks 7 | 8 | import "time" 9 | 10 | type MockRateLimiter struct { 11 | items []interface{} 12 | } 13 | 14 | func (m *MockRateLimiter) Add(_ interface{}) { 15 | } 16 | 17 | func (m *MockRateLimiter) Len() int { 18 | return len(m.items) 19 | } 20 | 21 | func (m *MockRateLimiter) Get() (item interface{}, shutdown bool) { 22 | if len(m.items) > 0 { 23 | item = m.items[0] 24 | m.items = m.items[1:] 25 | return item, false 26 | } 27 | return nil, false 28 | } 29 | 30 | func (m *MockRateLimiter) Done(_ interface{}) { 31 | } 32 | 33 | func (m *MockRateLimiter) ShutDown() { 34 | } 35 | 36 | func (m *MockRateLimiter) ShutDownWithDrain() { 37 | } 38 | 39 | func (m *MockRateLimiter) ShuttingDown() bool { 40 | return true 41 | } 42 | 43 | func (m *MockRateLimiter) AddAfter(item interface{}, _ time.Duration) { 44 | m.items = append(m.items, item) 45 | } 46 | 47 | func (m *MockRateLimiter) AddRateLimited(item interface{}) { 48 | m.items = append(m.items, item) 49 | } 50 | 51 | func (m *MockRateLimiter) Forget(_ interface{}) { 52 | 53 | } 54 | 55 | func (m *MockRateLimiter) NumRequeues(_ interface{}) int { 56 | return 0 57 | } 58 | -------------------------------------------------------------------------------- /test/mocks/mock_responsewriter.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package mocks 7 | 8 | import "net/http" 9 | 10 | type MockResponseWriter struct { 11 | body []byte 12 | } 13 | 14 | func NewMockResponseWriter() *MockResponseWriter { 15 | return &MockResponseWriter{} 16 | } 17 | 18 | func (m *MockResponseWriter) Header() http.Header { 19 | return nil 20 | } 21 | 22 | func (m *MockResponseWriter) Write(body []byte) (int, error) { 23 | m.body = append(m.body, body...) 24 | return len(m.body), nil 25 | } 26 | 27 | func (m *MockResponseWriter) WriteHeader(int) { 28 | 29 | } 30 | 31 | func (m *MockResponseWriter) Body() []byte { 32 | return m.body 33 | } 34 | -------------------------------------------------------------------------------- /test/mocks/mock_synchronizer.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 F5 Inc. All rights reserved. 3 | * Use of this source code is governed by the Apache License that can be found in the LICENSE file. 4 | */ 5 | 6 | package mocks 7 | 8 | import "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" 9 | 10 | type MockSynchronizer struct { 11 | Events []core.ServerUpdateEvent 12 | } 13 | 14 | func (s *MockSynchronizer) AddEvents(events core.ServerUpdateEvents) { 15 | for _, event := range events { 16 | s.Events = append(s.Events, *event) 17 | } 18 | } 19 | 20 | func (s *MockSynchronizer) AddEvent(event *core.ServerUpdateEvent) { 21 | s.Events = append(s.Events, *event) 22 | } 23 | 24 | func (s *MockSynchronizer) Initialize() error { 25 | return nil 26 | } 27 | 28 | func (s *MockSynchronizer) Run(stopCh <-chan struct{}) { 29 | <-stopCh 30 | } 31 | 32 | func (s *MockSynchronizer) ShutDown() { 33 | 34 | } 35 | --------------------------------------------------------------------------------