├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── .DS_Store │ ├── build-images.yaml │ ├── main-branch-push-workflow.yaml │ ├── manual-dev-release-workflow.yaml │ └── pull-request-workflow.yaml ├── .gitignore ├── .releaserc.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── SECURITY.md ├── bundle ├── manifests │ ├── vpn.wireguard-operator.io_wireguardpeers.yaml │ ├── vpn.wireguard-operator.io_wireguards.yaml │ ├── wireguard-controller-manager-metrics-service_v1_service.yaml │ ├── wireguard-manager-config_v1_configmap.yaml │ ├── wireguard-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml │ └── wireguard-operator.clusterserviceversion.yaml ├── metadata │ └── annotations.yaml └── tests │ └── scorecard │ └── config.yaml ├── cmd ├── agent │ └── main.go └── manager │ └── main.go ├── config ├── crd │ ├── bases │ │ ├── vpn.wireguard-operator.io_wireguardpeers.yaml │ │ └── vpn.wireguard-operator.io_wireguards.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_wireguardpeers.yaml │ │ ├── cainjection_in_wireguards.yaml │ │ ├── webhook_in_wireguardpeers.yaml │ │ └── webhook_in_wireguards.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ ├── manager_auth_proxy_patch.yaml.template │ └── manager_config_patch.yaml ├── manager │ ├── controller_manager_config.yaml │ ├── kustomization.yaml │ └── manager.yaml ├── manifests │ ├── bases │ │ └── wireguard-operator.clusterserviceversion.yaml │ └── kustomization.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ ├── role_binding.yaml │ ├── service_account.yaml │ ├── wireguard_editor_role.yaml │ ├── wireguard_viewer_role.yaml │ ├── wireguardpeer_editor_role.yaml │ └── wireguardpeer_viewer_role.yaml ├── samples │ ├── kustomization.yaml │ ├── vpn_v1alpha1_wireguard.yaml │ └── vpn_v1alpha1_wireguardpeer.yaml └── scorecard │ ├── bases │ └── config.yaml │ ├── kustomization.yaml │ └── patches │ ├── basic.config.yaml │ └── olm.config.yaml ├── examples ├── peer.yaml ├── server.yaml ├── serverWithIpForwardEnable.yaml └── serverWithNodePortService.yaml ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt ├── images ├── agent │ └── Dockerfile └── manager │ └── Dockerfile ├── internal ├── iptables │ ├── iptables.go │ └── iptables_test.go └── it │ ├── it_test.go │ └── suite_test.go ├── pkg ├── agent │ └── agent.go ├── api │ └── v1alpha1 │ │ ├── groupversion_info.go │ │ ├── wireguard_types.go │ │ ├── wireguardpeer_types.go │ │ └── zz_generated.deepcopy.go ├── controllers │ ├── suite_test.go │ ├── test.yaml │ ├── wireguard_controller.go │ ├── wireguard_controller_test.go │ └── wireguardpeer_controller.go └── wireguard │ └── wireguard.go ├── readme ├── diagrams │ └── main.drawio ├── main.drawio.svg └── main.png ├── release.yaml └── renovate.json /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | testbin/ 5 | -------------------------------------------------------------------------------- /.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 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.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 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jodevsa/wireguard-operator/1ac0303136c4c50dd1329d149b319495194d823a/.github/workflows/.DS_Store -------------------------------------------------------------------------------- /.github/workflows/build-images.yaml: -------------------------------------------------------------------------------- 1 | name: Build Docker Images 2 | env: 3 | REGISTRY: ghcr.io 4 | IMAGE_NAME: ${{ github.repository }} 5 | 6 | on: 7 | workflow_call: 8 | inputs: 9 | ref: 10 | required: true 11 | type: string 12 | repository: 13 | description: 'Repository name with owner.' 14 | required: false 15 | default: ${{ github.repository }} 16 | type: string 17 | tag: 18 | required: false 19 | default: dev-${{ github.sha }} 20 | type: string 21 | push: 22 | required: true 23 | type: boolean 24 | latest: 25 | required: false 26 | default: false 27 | type: boolean 28 | upload_images: 29 | required: false 30 | default: false 31 | type: boolean 32 | platforms: 33 | description: 'Docker image platforms' 34 | required: false 35 | default: 'linux/amd64, linux/arm64' 36 | type: string 37 | 38 | 39 | permissions: 40 | contents: read 41 | packages: write 42 | 43 | jobs: 44 | build-images: 45 | strategy: 46 | matrix: 47 | image: 48 | - manager 49 | - agent 50 | runs-on: ubuntu-latest 51 | 52 | steps: 53 | - name: Checkout repository 54 | uses: actions/checkout@v4 55 | with: 56 | repository: ${{ inputs.repository }} 57 | ref: ${{ inputs.ref }} 58 | submodules: true 59 | 60 | - name: Set up QEMU 61 | uses: docker/setup-qemu-action@v3 62 | 63 | - name: Set up Docker Buildx 64 | id: buildx 65 | uses: docker/setup-buildx-action@v3 66 | with: 67 | install: true 68 | 69 | - name: Login to GitHub Container Registry 70 | uses: docker/login-action@v3 71 | with: 72 | registry: ghcr.io 73 | username: ${{ github.repository_owner }} 74 | password: ${{ secrets.GITHUB_TOKEN }} 75 | 76 | - name: Generate docker metadata 77 | id: image-meta 78 | uses: docker/metadata-action@v5 79 | with: 80 | tags: | 81 | type=raw,value=latest, enable=${{ inputs.latest}} 82 | type=raw,value=${{ inputs.tag }} 83 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ matrix.image }} 84 | 85 | - uses: actions/setup-go@v5 86 | with: 87 | go-version: '^1.20' 88 | 89 | - name: Build and push docker images 90 | if: ${{ inputs.push == true }} 91 | uses: docker/build-push-action@v6 92 | with: 93 | context: . 94 | file: images/${{ matrix.image }}/Dockerfile 95 | platforms: ${{ inputs.platforms }} 96 | push: true 97 | tags: ${{ steps.image-meta.outputs.tags }} 98 | labels: ${{ steps.image-meta.outputs.labels }} 99 | 100 | - name: Build and upload docker image to job artifact 101 | if: ${{ inputs.upload_images == true }} 102 | uses: docker/build-push-action@v6 103 | with: 104 | context: . 105 | file: images/${{ matrix.image }}/Dockerfile 106 | platforms: ${{ inputs.platforms }} 107 | outputs: type=docker,dest=/tmp/${{matrix.image}}.tar 108 | push: false 109 | tags: ${{ steps.image-meta.outputs.tags }} 110 | labels: ${{ steps.image-meta.outputs.labels }} 111 | 112 | - name: Upload artifact 113 | if: ${{ inputs.upload_images == true }} 114 | uses: actions/upload-artifact@v3 115 | with: 116 | name: images 117 | path: /tmp/${{matrix.image}}.tar -------------------------------------------------------------------------------- /.github/workflows/main-branch-push-workflow.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | permissions: write-all 8 | 9 | jobs: 10 | generate-next-semantic-version: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | name: Checkout repository 15 | with: 16 | submodules: true 17 | - name: Use Node.js 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: '20.8.1' 21 | - name: semantic-release extract new version 22 | id: extract 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | run: | 26 | npm install @semantic-release/commit-analyzer @semantic-release/release-notes-generator @semantic-release/changelog @semantic-release/git semantic-release/exec 27 | echo "version=v$(npx semantic-release --dryRun | grep -oP 'Published release \K.*? ')" 28 | echo "version=v$(npx semantic-release --dryRun | grep -oP 'Published release \K.*? ')" >> "$GITHUB_OUTPUT" 29 | outputs: 30 | version: ${{ steps.extract.outputs.version }} 31 | 32 | build-images: 33 | needs: [generate-next-semantic-version] 34 | uses: ./.github/workflows/build-images.yaml 35 | with: 36 | latest: true 37 | push: true 38 | tag: ${{needs.generate-next-semantic-version.outputs.version}} 39 | ref: ${{ github.sha }} 40 | 41 | release: 42 | needs: [build-images] 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v4 46 | name: Checkout repository 47 | with: 48 | submodules: true 49 | persist-credentials: false 50 | ref: ${{ github.sha }} 51 | 52 | - name: Use Node.js 53 | uses: actions/setup-node@v4 54 | with: 55 | node-version: '20.8.1' 56 | 57 | - name: semantic-release 58 | env: 59 | GITHUB_TOKEN: ${{ secrets.ADMIN_SECRET }} 60 | run : | 61 | npm install @semantic-release/commit-analyzer @semantic-release/release-notes-generator @semantic-release/changelog @semantic-release/git semantic-release/exec 62 | npx semantic-release 63 | -------------------------------------------------------------------------------- /.github/workflows/manual-dev-release-workflow.yaml: -------------------------------------------------------------------------------- 1 | name: Manual DEV Release 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | repository: 6 | description: 'Repository name with owner.' 7 | required: true 8 | default: 'jodevsa/wireguard-operator' 9 | type: string 10 | branch: 11 | description: 'The branch, tag or SHA to checkout.' 12 | required: true 13 | default: 'main' 14 | type: string 15 | platforms: 16 | description: 'Docker image platforms' 17 | required: true 18 | default: 'linux/amd64, linux/arm64' 19 | type: string 20 | tag: 21 | description: 'Docker image tag' 22 | required: true 23 | default: 'feature-1' 24 | type: string 25 | 26 | env: 27 | REGISTRY: ghcr.io 28 | IMAGE_NAME: ${{ github.repository }} 29 | 30 | permissions: 31 | contents: read 32 | packages: write 33 | 34 | jobs: 35 | build-images: 36 | uses: ./.github/workflows/build-images.yaml 37 | with: 38 | push: true 39 | latest: false 40 | ref: ${{ inputs.branch }} 41 | repository: ${{ inputs.repository }} 42 | tag: dev-${{ inputs.tag }} 43 | 44 | save-release: 45 | needs: [build-images] 46 | runs-on: ubuntu-latest 47 | steps: 48 | - name: Set up Go 49 | uses: actions/setup-go@v5 50 | with: 51 | go-version: 1.20 52 | 53 | - name: Checkout repository 54 | uses: actions/checkout@v4 55 | with: 56 | repository: ${{ inputs.repository }} 57 | ref: ${{ inputs.branch }} 58 | submodules: true 59 | 60 | - name: prepare new release 61 | env: 62 | MANAGER_IMAGE: ghcr.io/${{ inputs.repository }}/manager:dev-${{ inputs.tag }} 63 | AGENT_IMAGE: ghcr.io/${{ inputs.repository }}/agent:dev-${{ inputs.tag }} 64 | run : | 65 | make generate-release-file AGENT_IMAGE="$AGENT_IMAGE" MANAGER_IMAGE="$MANAGER_IMAGE" 66 | - name: upload release 67 | uses: actions/upload-artifact@v3 68 | with: 69 | name: release.yaml 70 | path: ${{ github.workspace }}/release.yaml -------------------------------------------------------------------------------- /.github/workflows/pull-request-workflow.yaml: -------------------------------------------------------------------------------- 1 | name: PR pipeline 2 | on: 3 | pull_request: {} 4 | 5 | permissions: 6 | contents: read 7 | packages: write 8 | 9 | jobs: 10 | build-images: 11 | uses: ./.github/workflows/build-images.yaml 12 | with: 13 | push: false 14 | upload_images: true 15 | platforms: "linux/amd64" 16 | ref: ${{ github.ref }} 17 | tag: dev 18 | 19 | e2e: 20 | runs-on: ubuntu-latest 21 | needs: build-images 22 | steps: 23 | - name: Set up Docker Buildx 24 | uses: docker/setup-buildx-action@v3 25 | 26 | - name: Download artifact 27 | uses: actions/download-artifact@v3 28 | with: 29 | name: images 30 | path: /tmp 31 | 32 | - name: Load image 33 | run: | 34 | docker load --input /tmp/agent.tar 35 | docker load --input /tmp/manager.tar 36 | docker image ls -a 37 | 38 | - name: Checkout repository 39 | uses: actions/checkout@v4 40 | with: 41 | repository: ${{ inputs.repository }} 42 | ref: ${{ inputs.ref }} 43 | submodules: true 44 | 45 | - uses: actions/setup-go@v5 46 | with: 47 | go-version: '^1.20' 48 | 49 | - uses: azure/setup-kubectl@v4 50 | 51 | - name: run e2e 52 | env: 53 | REGISTRY: ghcr.io 54 | AGENT_IMAGE: ghcr.io/${{ github.repository }}/agent:dev 55 | MANAGER_IMAGE: ghcr.io/${{ github.repository }}/manager:dev 56 | run: | 57 | make kind 58 | make run-e2e AGENT_IMAGE="$AGENT_IMAGE" MANAGER_IMAGE="$MANAGER_IMAGE" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin 9 | testbin/* 10 | 11 | # Test binary, build with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Kubernetes Generated files - skip generated files, except for vendored files 18 | 19 | !vendor/**/zz_generated.* 20 | 21 | # editor and IDE paraphernalia 22 | .idea 23 | *.swp 24 | *.swo 25 | *~ 26 | 27 | 28 | # 29 | release_it.yaml -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "branches": [ 3 | "main" 4 | ], 5 | "plugins": [ 6 | ["@semantic-release/exec", { 7 | "publishCmd": "make generate-release-file AGENT_IMAGE=\"ghcr.io/jodevsa/wireguard-operator/agent:v${nextRelease.version}\" MANAGER_IMAGE=\"ghcr.io/jodevsa/wireguard-operator/manager:v${nextRelease.version}\"" 8 | }], 9 | "@semantic-release/commit-analyzer", 10 | "@semantic-release/release-notes-generator", 11 | ["@semantic-release/changelog", 12 | { 13 | "changelogFile": "CHANGELOG.md" 14 | } 15 | ], 16 | ["@semantic-release/git", 17 | { 18 | "assets": ["CHANGELOG.md"] 19 | } 20 | ], 21 | [ 22 | "@semantic-release/github", 23 | { 24 | "assets": [ 25 | { 26 | "path": "./release.yaml", 27 | "label": "k8s release file" 28 | } 29 | ] 30 | } 31 | ] 32 | ] 33 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | wireguard-operator@protonmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Documentation will be added when the project gets more mature. Feel free to contribute 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Subhi Al Hasan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # VERSION defines the project version for the bundle. 2 | # Update this value when you upgrade the version of your project. 3 | # To re-generate a bundle for another specific version without changing the standard setup, you can: 4 | # - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) 5 | # - use environment variables to overwrite this value (e.g export VERSION=0.0.2) 6 | VERSION ?= 0.0.1 7 | SHELL = /bin/bash 8 | 9 | 10 | # setup 11 | 12 | ## Location to install dependencies to 13 | LOCALBIN ?= $(shell pwd)/bin 14 | $(LOCALBIN): 15 | mkdir -p $(LOCALBIN) 16 | 17 | ## Tool Binaries 18 | KUSTOMIZE ?= $(LOCALBIN)/kustomize 19 | CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen 20 | KIND ?= $(LOCALBIN)/kind 21 | ENVTEST ?= $(LOCALBIN)/setup-envtest 22 | 23 | ## Tool Versions 24 | KUSTOMIZE_VERSION ?= v3.8.7 25 | CONTROLLER_TOOLS_VERSION ?= v0.14.0 26 | KIND_VERSION ?= v0.19.0 27 | 28 | 29 | # images 30 | AGENT_IMAGE ?= "agent:dev" 31 | MANAGER_IMAGE ?= "manager:dev" 32 | 33 | # CHANNELS define the bundle channels used in the bundle. 34 | # Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable") 35 | # To re-generate a bundle for other specific channels without changing the standard setup, you can: 36 | # - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=candidate,fast,stable) 37 | # - use environment variables to overwrite this value (e.g export CHANNELS="candidate,fast,stable") 38 | ifneq ($(origin CHANNELS), undefined) 39 | BUNDLE_CHANNELS := --channels=$(CHANNELS) 40 | endif 41 | 42 | # DEFAULT_CHANNEL defines the default channel used in the bundle. 43 | # Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable") 44 | # To re-generate a bundle for any other default channel without changing the default setup, you can: 45 | # - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable) 46 | # - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable") 47 | ifneq ($(origin DEFAULT_CHANNEL), undefined) 48 | BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL) 49 | endif 50 | BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) 51 | 52 | # IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images. 53 | # This variable is used to construct full image tags for bundle and catalog images. 54 | # 55 | # For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both 56 | # wireguard-operator.io/manager-bundle:$VERSION and wireguard-operator.io/manager-catalog:$VERSION. 57 | IMAGE_TAG_BASE ?= ghcr.io/jodevsa/wireguard-operator 58 | 59 | # BUNDLE_IMG defines the image:tag used for the bundle. 60 | # You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) 61 | BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-operator-bundle:main 62 | 63 | # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. 64 | ENVTEST_K8S_VERSION = 1.22 65 | 66 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 67 | ifeq (,$(shell go env GOBIN)) 68 | GOBIN=$(shell go env GOPATH)/bin 69 | else 70 | GOBIN=$(shell go env GOBIN) 71 | endif 72 | 73 | # Setting SHELL to bash allows bash commands to be executed by recipes. 74 | # This is a requirement for 'setup-envtest.sh' in the test target. 75 | # Options are set to exit when a recipe line exits non-zero or a piped command fails. 76 | SHELL = /usr/bin/env bash -o pipefail 77 | .SHELLFLAGS = -ec 78 | 79 | all: build 80 | 81 | ##@ General 82 | 83 | # The help target prints out all targets with their descriptions organized 84 | # beneath their categories. The categories are represented by '##@' and the 85 | # target descriptions by '##'. The awk commands is responsible for reading the 86 | # entire set of makefiles included in this invocation, looking for lines of the 87 | # file as xyz: ## something, and then pretty-format the target and help. Then, 88 | # if there's a line with ##@ something, that gets pretty-printed as a category. 89 | # More info on the usage of ANSI control characters for terminal formatting: 90 | # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters 91 | # More info on the awk command: 92 | # http://linuxcommand.org/lc3_adv_awk.php 93 | 94 | help: ## Display this help. 95 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 96 | 97 | ##@ Development 98 | 99 | manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. 100 | $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases 101 | 102 | generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. 103 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 104 | 105 | fmt: ## Run go fmt against code. 106 | go fmt ./... 107 | 108 | vet: ## Run go vet against code. 109 | go vet ./... 110 | 111 | test: manifests generate fmt vet envtest ## Run tests. 112 | KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out 113 | 114 | ##@ Build 115 | build: build-agent build-manager 116 | 117 | build-agent: generate fmt vet ## Build manager binary. 118 | go build -o bin/agent ./cmd/agent/main.go 119 | 120 | build-manager: generate fmt vet ## Build manager binary. 121 | go build -o bin/manager ./cmd/manager/main.go 122 | 123 | run: manifests generate fmt vet ## Run a controller from your host. 124 | go run ./cmd/manager/main.go 125 | 126 | 127 | 128 | docker-build-agent: ## Build docker image with the manager. 129 | docker build -t ${AGENT_IMAGE} . -f ./images/agent/Dockerfile 130 | 131 | docker-build-manager: ## Build docker image with the manager. 132 | docker build -t ${MANAGER_IMAGE} . -f ./images/manager/Dockerfile 133 | 134 | docker-build-integration-test: docker-build-manager 135 | $(MAKE) docker-build-agent 136 | $(MAKE) docker-build-manager 137 | 138 | 139 | run-e2e: 140 | AGENT_IMAGE=${AGENT_IMAGE} $(MAKE) update-agent-image 141 | MANAGER_IMAGE=${MANAGER_IMAGE} $(MAKE) update-manager-image 142 | $(KUSTOMIZE) build config/default > release_it.yaml 143 | git checkout ./config/default/manager_auth_proxy_patch.yaml 144 | git checkout ./config/manager/kustomization.yaml 145 | KUBE_CONFIG=$(HOME)/.kube/config KIND_BIN=${KIND} WIREGUARD_OPERATOR_RELEASE_PATH="../../release_it.yaml" AGENT_IMAGE=${AGENT_IMAGE} MANAGER_IMAGE=${MANAGER_IMAGE} go test ./internal/it/ -v -count=1 146 | 147 | docker-push: ## Push docker image with the manager. 148 | docker push ${IMG} 149 | 150 | ##@ Deployment 151 | 152 | install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. 153 | $(KUSTOMIZE) build config/crd | kubectl apply -f - 154 | 155 | uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. 156 | $(KUSTOMIZE) build config/crd | kubectl delete -f - 157 | 158 | 159 | update-agent-image: kustomize 160 | ## TODO: Simplify later 161 | AGENT_IMAGE=$(AGENT_IMAGE) envsubst < ./config/default/manager_auth_proxy_patch.yaml.template > ./config/default/manager_auth_proxy_patch.yaml 162 | 163 | update-manager-image: kustomize 164 | $(info MANAGER_IMAGE: "$(MANAGER_IMAGE)") 165 | cd config/manager && $(KUSTOMIZE) edit set image controller=${MANAGER_IMAGE} 166 | 167 | generate-release-file: kustomize update-agent-image update-manager-image 168 | $(KUSTOMIZE) build config/default > release.yaml 169 | git checkout ./config/default/manager_auth_proxy_patch.yaml 170 | git checkout ./config/manager/kustomization.yaml 171 | 172 | deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. 173 | cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} 174 | $(KUSTOMIZE) build config/default | kubectl apply -f - 175 | 176 | undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. 177 | $(KUSTOMIZE) build config/default | kubectl delete -f - 178 | 179 | .PHONY: controller-gen 180 | controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. 181 | $(CONTROLLER_GEN): $(LOCALBIN) 182 | GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION) 183 | 184 | kind: $(KIND) ## Download kind locally if necessary. 185 | $(KIND): $(LOCALBIN) 186 | GOBIN=$(LOCALBIN) go install sigs.k8s.io/kind/cmd/kind@$(KIND_VERSION) 187 | 188 | KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" 189 | .PHONY: kustomize 190 | kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. 191 | $(KUSTOMIZE): $(LOCALBIN) 192 | curl -s $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN) 193 | 194 | .PHONY: envtest 195 | envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. 196 | $(ENVTEST): $(LOCALBIN) 197 | GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest 198 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: wireguard-operator.io 2 | layout: 3 | - go.kubebuilder.io/v3 4 | plugins: 5 | manifests.sdk.operatorframework.io/v2: {} 6 | scorecard.sdk.operatorframework.io/v2: {} 7 | projectName: wireguard-operator 8 | repo: github.com/jodevsa/wireguard-operator 9 | resources: 10 | - api: 11 | crdVersion: v1 12 | namespaced: true 13 | controller: true 14 | domain: wireguard-operator.io 15 | group: vpn 16 | kind: Wireguard 17 | path: github.com/jodevsa/wireguard-operator/api/v1alpha1 18 | version: v1alpha1 19 | - api: 20 | crdVersion: v1 21 | namespaced: true 22 | controller: true 23 | domain: wireguard-operator.io 24 | group: vpn 25 | kind: WireguardPeer 26 | path: github.com/jodevsa/wireguard-operator/api/v1alpha1 27 | version: v1alpha1 28 | version: "3" 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wireguard Operator 2 | Screenshot 2022-02-26 at 02 05 29 3 | 4 | Painless deployment of wireguard on kubernetes 5 | 6 | ## Support and discussions 7 | 8 | If you are facing any problems please open an [issue](https://github.com/jodevsa/wireguard-operator/issues) or start a 9 | [discussion](https://github.com/jodevsa/wireguard-operator/discussions) 10 | 11 | ## Tested with 12 | - [x] IBM Cloud Kubernetes Service 13 | - [x] Gcore Labs KMP 14 | * requires `spec.enableIpForwardOnPodInit: true` 15 | - [x] Google Kubernetes Engine 16 | * requires `spec.mtu: "1380"` 17 | * Not compatible with "Container-Optimized OS with containerd" node images 18 | * Not compatible with autopilot 19 | - [x] DigitalOcean Kubernetes 20 | * requires `spec.serviceType: "NodePort"`. DigitalOcean LoadBalancer does not support UDP. 21 | - [ ] Amazon EKS 22 | - [ ] Azure Kubernetes Service 23 | - [ ] ...? 24 | 25 | ## Architecture 26 | 27 | ![alt text](./readme/main.png) 28 | 29 | ## Features 30 | * Falls back to userspace implementation of wireguard [wireguard-go](https://github.com/WireGuard/wireguard-go) if wireguard kernal module is missing 31 | * Automatic key generation 32 | * Automatic IP allocation 33 | * Does not need persistance. peer/server keys are stored as k8s secrets and loaded into the wireguard pod 34 | * Exposes a metrics endpoint by utilizing [prometheus_wireguard_exporter](https://github.com/MindFlavor/prometheus_wireguard_exporter) 35 | 36 | ## Example 37 | 38 | ### Server 39 | 40 | ``` 41 | apiVersion: vpn.wireguard-operator.io/v1alpha1 42 | kind: Wireguard 43 | metadata: 44 | name: "my-cool-vpn" 45 | spec: 46 | mtu: "1380" 47 | ``` 48 | 49 | ### Peer 50 | 51 | ``` 52 | apiVersion: vpn.wireguard-operator.io/v1alpha1 53 | kind: WireguardPeer 54 | metadata: 55 | name: peer1 56 | spec: 57 | wireguardRef: "my-cool-vpn" 58 | ``` 59 | 60 | #### Peer configuration 61 | 62 | Peer configuration can be retrieved using the following command: 63 | 64 | ```console 65 | kubectl get wireguardpeer peer1 --template={{.status.config}} | bash 66 | ``` 67 | 68 | After executing it, something similar to the following will be shown. Use this config snippet to configure your 69 | preferred Wireguard client: 70 | 71 | ```console 72 | [Interface] 73 | PrivateKey = WOhR7uTMAqmZamc1umzfwm8o4ZxLdR5LjDcUYaW/PH8= 74 | Address = 10.8.0.3 75 | DNS = 10.48.0.10, default.svc.cluster.local 76 | MTU = 1380 77 | 78 | [Peer] 79 | PublicKey = sO3ZWhnIT8owcdsfwiMRu2D8LzKmae2gUAxAmhx5GTg= 80 | AllowedIPs = 0.0.0.0/0 81 | Endpoint = 32.121.45.102:51820 82 | ``` 83 | 84 | ## How to deploy 85 | ``` 86 | kubectl apply -f https://github.com/jodevsa/wireguard-operator/releases/download/v2.1.0/release.yaml 87 | ``` 88 | 89 | ## How to remove 90 | ``` 91 | kubectl delete -f https://github.com/jodevsa/wireguard-operator/releases/download/v2.1.0/release.yaml 92 | ``` 93 | 94 | ## How to collaborate 95 | 96 | This project is done on top of [Kubebuilder](https://github.com/kubernetes-sigs/kubebuilder), so read about that project 97 | before collaborating. Of course, we are open to external collaborations for this project. For doing it you must fork the 98 | repository, make your changes to the code and open a PR. The code will be reviewed and tested (always) 99 | 100 | > We are developers and hate bad code. For that reason we ask you the highest quality on each line of code to improve 101 | > this project on each iteration. 102 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | We attach great importance to the security of our users, but we are humans and not infallible. 3 | That's why we rely on you, the open source contributors, to inform us about actual and possible security vulnerabilities. 4 | Please follow the guideline below to get in touch with us, even if you're not sure, if your issue is regarding security. 5 | 6 | ## Reporting a Vulnerability 7 | **Please do not open GitHub issues for security vulnerabilities, as GitHub issues are publicly accessible!!!** 8 | 9 | Instead, contact us per mail ([jodevsa@gmail.com](mailto:jodevsa@gmail.com)). We guarantee a response within two workdays and a security patch as fast as possible. 10 | 11 | Thanks for your cooperation and your understanding. 12 | -------------------------------------------------------------------------------- /bundle/manifests/vpn.wireguard-operator.io_wireguardpeers.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | annotations: 5 | controller-gen.kubebuilder.io/version: v0.7.0 6 | creationTimestamp: null 7 | name: wireguardpeers.vpn.wireguard-operator.io 8 | spec: 9 | group: vpn.wireguard-operator.io 10 | names: 11 | kind: WireguardPeer 12 | listKind: WireguardPeerList 13 | plural: wireguardpeers 14 | singular: wireguardpeer 15 | scope: Namespaced 16 | versions: 17 | - name: v1alpha1 18 | schema: 19 | openAPIV3Schema: 20 | description: WireguardPeer is the Schema for the wireguardpeers API 21 | properties: 22 | apiVersion: 23 | description: 'APIVersion defines the versioned schema of this representation 24 | of an object. Servers should convert recognized schemas to the latest 25 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 26 | type: string 27 | kind: 28 | description: 'Kind is a string value representing the REST resource this 29 | object represents. Servers may infer this from the endpoint the client 30 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 31 | type: string 32 | metadata: 33 | type: object 34 | spec: 35 | description: WireguardPeerSpec defines the desired state of WireguardPeer 36 | properties: 37 | PrivateKeyRef: 38 | properties: 39 | secretKeyRef: 40 | description: SecretKeySelector selects a key of a Secret. 41 | properties: 42 | key: 43 | description: The key of the secret to select from. Must be 44 | a valid secret key. 45 | type: string 46 | name: 47 | description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names 48 | TODO: Add other useful fields. apiVersion, kind, uid?' 49 | type: string 50 | optional: 51 | description: Specify whether the Secret or its key must be 52 | defined 53 | type: boolean 54 | required: 55 | - key 56 | type: object 57 | required: 58 | - secretKeyRef 59 | type: object 60 | address: 61 | type: string 62 | dns: 63 | type: string 64 | publicKey: 65 | type: string 66 | wireguardRef: 67 | description: Foo is an example field of WireguardPeer. Edit wireguardpeer_types.go 68 | to remove/update 69 | type: string 70 | required: 71 | - wireguardRef 72 | type: object 73 | status: 74 | description: WireguardPeerStatus defines the observed state of WireguardPeer 75 | properties: 76 | config: 77 | description: 'INSERT ADDITIONAL STATUS FIELD - define observed state 78 | of cluster Important: Run "make" to regenerate code after modifying 79 | this file' 80 | type: string 81 | message: 82 | type: string 83 | status: 84 | type: string 85 | type: object 86 | type: object 87 | served: true 88 | storage: true 89 | subresources: 90 | status: {} 91 | status: 92 | acceptedNames: 93 | kind: "" 94 | plural: "" 95 | conditions: [] 96 | storedVersions: [] 97 | -------------------------------------------------------------------------------- /bundle/manifests/vpn.wireguard-operator.io_wireguards.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | annotations: 5 | controller-gen.kubebuilder.io/version: v0.7.0 6 | creationTimestamp: null 7 | name: wireguards.vpn.wireguard-operator.io 8 | spec: 9 | group: vpn.wireguard-operator.io 10 | names: 11 | kind: Wireguard 12 | listKind: WireguardList 13 | plural: wireguards 14 | singular: wireguard 15 | scope: Namespaced 16 | versions: 17 | - name: v1alpha1 18 | schema: 19 | openAPIV3Schema: 20 | description: Wireguard is the Schema for the wireguards API 21 | properties: 22 | apiVersion: 23 | description: 'APIVersion defines the versioned schema of this representation 24 | of an object. Servers should convert recognized schemas to the latest 25 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 26 | type: string 27 | kind: 28 | description: 'Kind is a string value representing the REST resource this 29 | object represents. Servers may infer this from the endpoint the client 30 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 31 | type: string 32 | metadata: 33 | type: object 34 | spec: 35 | description: WireguardSpec defines the desired state of Wireguard 36 | properties: 37 | hostname: 38 | type: string 39 | mtu: 40 | type: string 41 | required: 42 | - mtu 43 | type: object 44 | status: 45 | description: WireguardStatus defines the observed state of Wireguard 46 | properties: 47 | hostname: 48 | description: 'INSERT ADDITIONAL STATUS FIELD - define observed state 49 | of cluster Important: Run "make" to regenerate code after modifying 50 | this file' 51 | type: string 52 | message: 53 | type: string 54 | port: 55 | type: string 56 | status: 57 | type: string 58 | type: object 59 | type: object 60 | served: true 61 | storage: true 62 | subresources: 63 | status: {} 64 | status: 65 | acceptedNames: 66 | kind: "" 67 | plural: "" 68 | conditions: [] 69 | storedVersions: [] 70 | -------------------------------------------------------------------------------- /bundle/manifests/wireguard-controller-manager-metrics-service_v1_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | control-plane: controller-manager 7 | name: wireguard-controller-manager-metrics-service 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | protocol: TCP 13 | targetPort: https 14 | selector: 15 | control-plane: controller-manager 16 | status: 17 | loadBalancer: {} 18 | -------------------------------------------------------------------------------- /bundle/manifests/wireguard-manager-config_v1_configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | controller_manager_config.yaml: | 4 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 5 | kind: ControllerManagerConfig 6 | health: 7 | healthProbeBindAddress: :8081 8 | metrics: 9 | bindAddress: 127.0.0.1:8080 10 | webhook: 11 | port: 9443 12 | leaderElection: 13 | leaderElect: true 14 | resourceName: a6d3bffc.wireguard-operator.io 15 | kind: ConfigMap 16 | metadata: 17 | name: wireguard-manager-config 18 | -------------------------------------------------------------------------------- /bundle/manifests/wireguard-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | creationTimestamp: null 5 | name: wireguard-metrics-reader 6 | rules: 7 | - nonResourceURLs: 8 | - /metrics 9 | verbs: 10 | - get 11 | -------------------------------------------------------------------------------- /bundle/manifests/wireguard-operator.clusterserviceversion.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: operators.coreos.com/v1alpha1 2 | kind: ClusterServiceVersion 3 | metadata: 4 | annotations: 5 | alm-examples: |- 6 | [ 7 | { 8 | "apiVersion": "vpn.wireguard-operator.io/v1alpha1", 9 | "kind": "Wireguard", 10 | "metadata": { 11 | "name": "wireguard-sample" 12 | }, 13 | "spec": null 14 | }, 15 | { 16 | "apiVersion": "vpn.wireguard-operator.io/v1alpha1", 17 | "kind": "WireguardPeer", 18 | "metadata": { 19 | "name": "wireguardpeer-sample" 20 | }, 21 | "spec": null 22 | } 23 | ] 24 | capabilities: Basic Install 25 | operators.operatorframework.io/builder: operator-sdk-v1.17.0 26 | operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 27 | name: wireguard-operator.v0.0.1 28 | namespace: placeholder 29 | spec: 30 | apiservicedefinitions: {} 31 | customresourcedefinitions: 32 | owned: 33 | - description: WireguardPeer is the Schema for the wireguardpeers API 34 | displayName: Wireguard Peer 35 | kind: WireguardPeer 36 | name: wireguardpeers.vpn.wireguard-operator.io 37 | version: v1alpha1 38 | - description: Wireguard is the Schema for the wireguards API 39 | displayName: Wireguard 40 | kind: Wireguard 41 | name: wireguards.vpn.wireguard-operator.io 42 | version: v1alpha1 43 | description: op 44 | displayName: wireguard 45 | icon: 46 | - base64data: "" 47 | mediatype: "" 48 | install: 49 | spec: 50 | clusterPermissions: 51 | - rules: 52 | - apiGroups: 53 | - "" 54 | resources: 55 | - configmaps 56 | verbs: 57 | - create 58 | - delete 59 | - get 60 | - list 61 | - patch 62 | - update 63 | - watch 64 | - apiGroups: 65 | - "" 66 | resources: 67 | - pods 68 | verbs: 69 | - create 70 | - delete 71 | - get 72 | - list 73 | - patch 74 | - update 75 | - watch 76 | - apiGroups: 77 | - "" 78 | resources: 79 | - secrets 80 | verbs: 81 | - create 82 | - delete 83 | - get 84 | - list 85 | - patch 86 | - update 87 | - watch 88 | - apiGroups: 89 | - "" 90 | resources: 91 | - services 92 | verbs: 93 | - create 94 | - delete 95 | - get 96 | - list 97 | - patch 98 | - update 99 | - watch 100 | - apiGroups: 101 | - apps 102 | resources: 103 | - deployments 104 | verbs: 105 | - create 106 | - delete 107 | - get 108 | - list 109 | - patch 110 | - update 111 | - watch 112 | - apiGroups: 113 | - apps 114 | resources: 115 | - pods 116 | verbs: 117 | - create 118 | - delete 119 | - get 120 | - list 121 | - patch 122 | - update 123 | - watch 124 | - apiGroups: 125 | - vpn.wireguard-operator.io 126 | resources: 127 | - wireguardpeers 128 | verbs: 129 | - create 130 | - delete 131 | - get 132 | - list 133 | - patch 134 | - update 135 | - watch 136 | - apiGroups: 137 | - vpn.wireguard-operator.io 138 | resources: 139 | - wireguardpeers/finalizers 140 | verbs: 141 | - update 142 | - apiGroups: 143 | - vpn.wireguard-operator.io 144 | resources: 145 | - wireguardpeers/status 146 | verbs: 147 | - get 148 | - patch 149 | - update 150 | - apiGroups: 151 | - vpn.wireguard-operator.io 152 | resources: 153 | - wireguards 154 | verbs: 155 | - create 156 | - delete 157 | - get 158 | - list 159 | - patch 160 | - update 161 | - watch 162 | - apiGroups: 163 | - vpn.wireguard-operator.io 164 | resources: 165 | - wireguards/finalizers 166 | verbs: 167 | - update 168 | - apiGroups: 169 | - vpn.wireguard-operator.io 170 | resources: 171 | - wireguards/status 172 | verbs: 173 | - get 174 | - patch 175 | - update 176 | - apiGroups: 177 | - authentication.k8s.io 178 | resources: 179 | - tokenreviews 180 | verbs: 181 | - create 182 | - apiGroups: 183 | - authorization.k8s.io 184 | resources: 185 | - subjectaccessreviews 186 | verbs: 187 | - create 188 | serviceAccountName: wireguard-controller-manager 189 | deployments: 190 | - name: wireguard-controller-manager 191 | spec: 192 | replicas: 1 193 | selector: 194 | matchLabels: 195 | control-plane: controller-manager 196 | strategy: {} 197 | template: 198 | metadata: 199 | annotations: 200 | kubectl.kubernetes.io/default-container: manager 201 | labels: 202 | control-plane: controller-manager 203 | spec: 204 | containers: 205 | - args: 206 | - --secure-listen-address=0.0.0.0:8443 207 | - --upstream=http://127.0.0.1:8080/ 208 | - --logtostderr=true 209 | - --v=0 210 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 211 | name: kube-rbac-proxy 212 | ports: 213 | - containerPort: 8443 214 | name: https 215 | protocol: TCP 216 | resources: 217 | limits: 218 | cpu: 500m 219 | memory: 128Mi 220 | requests: 221 | cpu: 5m 222 | memory: 64Mi 223 | - args: 224 | - --health-probe-bind-address=:8081 225 | - --metrics-bind-address=127.0.0.1:8080 226 | - --leader-elect 227 | command: 228 | - /manager 229 | image: ghcr.io/jodevsa/wireguard-operator-operator:main 230 | livenessProbe: 231 | httpGet: 232 | path: /healthz 233 | port: 8081 234 | initialDelaySeconds: 15 235 | periodSeconds: 20 236 | name: manager 237 | readinessProbe: 238 | httpGet: 239 | path: /readyz 240 | port: 8081 241 | initialDelaySeconds: 5 242 | periodSeconds: 10 243 | resources: 244 | limits: 245 | cpu: 500m 246 | memory: 128Mi 247 | requests: 248 | cpu: 10m 249 | memory: 64Mi 250 | securityContext: 251 | allowPrivilegeEscalation: false 252 | securityContext: 253 | runAsNonRoot: true 254 | serviceAccountName: wireguard-controller-manager 255 | terminationGracePeriodSeconds: 10 256 | permissions: 257 | - rules: 258 | - apiGroups: 259 | - "" 260 | resources: 261 | - configmaps 262 | verbs: 263 | - get 264 | - list 265 | - watch 266 | - create 267 | - update 268 | - patch 269 | - delete 270 | - apiGroups: 271 | - coordination.k8s.io 272 | resources: 273 | - leases 274 | verbs: 275 | - get 276 | - list 277 | - watch 278 | - create 279 | - update 280 | - patch 281 | - delete 282 | - apiGroups: 283 | - "" 284 | resources: 285 | - events 286 | verbs: 287 | - create 288 | - patch 289 | serviceAccountName: wireguard-controller-manager 290 | strategy: deployment 291 | installModes: 292 | - supported: false 293 | type: OwnNamespace 294 | - supported: false 295 | type: SingleNamespace 296 | - supported: false 297 | type: MultiNamespace 298 | - supported: true 299 | type: AllNamespaces 300 | keywords: 301 | - e 302 | links: 303 | - name: Wireguard Operator 304 | url: https://wireguard-operator.domain 305 | maturity: alpha 306 | provider: 307 | name: e 308 | url: w 309 | version: 0.0.1 310 | -------------------------------------------------------------------------------- /bundle/metadata/annotations.yaml: -------------------------------------------------------------------------------- 1 | annotations: 2 | # Core bundle annotations. 3 | operators.operatorframework.io.bundle.mediatype.v1: registry+v1 4 | operators.operatorframework.io.bundle.manifests.v1: manifests/ 5 | operators.operatorframework.io.bundle.metadata.v1: metadata/ 6 | operators.operatorframework.io.bundle.package.v1: wireguard-operator 7 | operators.operatorframework.io.bundle.channels.v1: alpha 8 | operators.operatorframework.io.metrics.builder: operator-sdk-v1.17.0 9 | operators.operatorframework.io.metrics.mediatype.v1: metrics+v1 10 | operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v3 11 | 12 | # Annotations for testing. 13 | operators.operatorframework.io.test.mediatype.v1: scorecard+v1 14 | operators.operatorframework.io.test.config.v1: tests/scorecard/ 15 | -------------------------------------------------------------------------------- /bundle/tests/scorecard/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: 8 | - entrypoint: 9 | - scorecard-test 10 | - basic-check-spec 11 | image: quay.io/operator-framework/scorecard-test:v1.17.0 12 | labels: 13 | suite: basic 14 | test: basic-check-spec-test 15 | storage: 16 | spec: 17 | mountPath: {} 18 | - entrypoint: 19 | - scorecard-test 20 | - olm-bundle-validation 21 | image: quay.io/operator-framework/scorecard-test:v1.17.0 22 | labels: 23 | suite: olm 24 | test: olm-bundle-validation-test 25 | storage: 26 | spec: 27 | mountPath: {} 28 | - entrypoint: 29 | - scorecard-test 30 | - olm-crds-have-validation 31 | image: quay.io/operator-framework/scorecard-test:v1.17.0 32 | labels: 33 | suite: olm 34 | test: olm-crds-have-validation-test 35 | storage: 36 | spec: 37 | mountPath: {} 38 | - entrypoint: 39 | - scorecard-test 40 | - olm-crds-have-resources 41 | image: quay.io/operator-framework/scorecard-test:v1.17.0 42 | labels: 43 | suite: olm 44 | test: olm-crds-have-resources-test 45 | storage: 46 | spec: 47 | mountPath: {} 48 | - entrypoint: 49 | - scorecard-test 50 | - olm-spec-descriptors 51 | image: quay.io/operator-framework/scorecard-test:v1.17.0 52 | labels: 53 | suite: olm 54 | test: olm-spec-descriptors-test 55 | storage: 56 | spec: 57 | mountPath: {} 58 | - entrypoint: 59 | - scorecard-test 60 | - olm-status-descriptors 61 | image: quay.io/operator-framework/scorecard-test:v1.17.0 62 | labels: 63 | suite: olm 64 | test: olm-status-descriptors-test 65 | storage: 66 | spec: 67 | mountPath: {} 68 | storage: 69 | spec: 70 | mountPath: {} 71 | -------------------------------------------------------------------------------- /cmd/agent/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "os" 9 | 10 | "github.com/go-logr/stdr" 11 | "github.com/jodevsa/wireguard-operator/internal/iptables" 12 | "github.com/jodevsa/wireguard-operator/pkg/agent" 13 | "github.com/jodevsa/wireguard-operator/pkg/wireguard" 14 | ) 15 | 16 | func main() { 17 | var configFilePath string 18 | var iface string 19 | var verbosity int 20 | var wgUserspaceImplementationFallback string 21 | var wireguardListenPort int 22 | var wgUseUserspaceImpl bool 23 | flag.StringVar(&configFilePath, "state", "./state.json", "The location of the file that states the desired state") 24 | flag.StringVar(&iface, "wg-iface", "wg0", "the wg device name. Default is wg0") 25 | flag.StringVar(&wgUserspaceImplementationFallback, "wg-userspace-implementation-fallback", "wireguard-go", "The userspace implementation of wireguard to fallback to") 26 | flag.IntVar(&wireguardListenPort, "wg-listen-port", 51820, "the UDP port wireguard is listening on") 27 | flag.IntVar(&verbosity, "v", 1, "the verbosity level") 28 | flag.BoolVar(&wgUseUserspaceImpl, "wg-use-userspace-implementation", false, "Use userspace implementation") 29 | flag.Parse() 30 | 31 | println(fmt.Sprintf( 32 | ` 33 | .:::::::::::::::::::::::::...::::::::::::::::::::. 34 | .::::::::::::::::::::.:^7J5PBGY!^::::::::::::::::::::. 35 | :::::::::::::::::::::~?J??5&@@@@@&G!~~~::::::::::::::::. WG Agent Configuration 36 | ::::::::::::::::::::::^7&@@@@@@@@@@@@&&&G^:::::::::::::::. ------------------------------------------ 37 | .::::::::::::::::::::::!J#@@@@@@@BBBGPPG7:::::::::::::::::. wg-iface: %s 38 | .:::::::::::::::::::::^?Y5#@@@@@@5^...::::::::::::::::::::: state: %s 39 | .::::::::::::::::::::::..:!7Y#@@@@@#Y~:.::::::::::::::::::. wg-listen-port: %d 40 | .:::::::::::::::::::.:^!?JYYJ?JG&@@@@@#7::::::::::::::::::. wg-use-userspace-implementation: %v 41 | .:::::::::::::::::.^J#@@@@@@@@@&#B&@@@@@G:::::::::::::::::. wg-userspace-implementation-fallback: %s 42 | .:::::::::::::::::J@@@@@@@@@@@@@@@&G@@@@@J.:::::::::::::::. 43 | .::::::::::::::::5@@@@@#?~~~7P@@@@@&B@@@@P.:::::::::::::::. 44 | .:::::::::::::::^@@@@@P..::::.~@@@@B&@@@@!::::::::::::::::. 45 | .:::::::::::::::~@@@@@J.::::::^@@@#&@@@@P:::::::::::::::::. 46 | .::::::::::::::::B@@@@@P!^:.:~G&&&@@@@@5::::::::::::::::::. 47 | .:::::::::::::::::G@@@@@&#BB&@@@@@@@@B~.::::::::::::::::::. 48 | .::::::::::::::::..~G&&&@@@@@@@@@&&&&&P^.:::::::::::::::::. 49 | .::::::::::::::.:~YGGY&@@@@@&GY7JB@@@@@@7:::::::::::::::::. 50 | .::::::::::::::?&@@@B&@@@@#!:..:::~B@@@@@~::::::::::::::::. 51 | .:::::::::::::J&#P5?5@@@@@:.::::::::&@@@@5.:::::::::::::::. 52 | .:::::::::::::^:....J@@@@@~.::::::.^@@@@@5.:::::::::::::::: 53 | .::::::::::::::::::::&@@@@@Y~::::^J&@@@@&^::::::::::::::::. 54 | ::::::::::::::::::::^B@@@@@@&##&@@@@@@#~:::::::::::::::::. 55 | :::::::::::::::::::::7B@@@@@@@@@@@@#?::::::::::::::::::. 56 | .::::::::::::::::::::.^7YGB##BGY7^:.:::::::::::::::::. 57 | .:::::::::::::::::::::..::::..::::::::::::::::::.. 58 | .....:...............................:..... 59 | 60 | / \ / \/ _____/ / _ \ / ___\ ____ _____/ |_ 61 | \ \/\/ / \ ___ / /_\ \ / /_/ _/ __ \ / \ __\ 62 | \ /\ \_\ \ / | \\___ /\ ___/| | | | 63 | \__/\ / \______ / \____|__ /_____/ \___ |___| |__| 64 | \/ \/ \/ \/ \/ 65 | `, iface, configFilePath, wireguardListenPort, wgUseUserspaceImpl, wgUserspaceImplementationFallback)) 66 | 67 | stdr.SetVerbosity(verbosity) 68 | log := stdr.NewWithOptions(log.New(os.Stderr, "", log.LstdFlags), stdr.Options{LogCaller: stdr.All}) 69 | log = log.WithName("agent") 70 | 71 | wg := wireguard.Wireguard{ 72 | Logger: log.WithName("wireguard"), 73 | Iface: iface, 74 | ListenPort: wireguardListenPort, 75 | WgUserspaceImplementationFallback: wgUserspaceImplementationFallback, 76 | WgUseUserspaceImpl: wgUseUserspaceImpl, 77 | } 78 | it := iptables.Iptables{ 79 | Logger: log.WithName("iptables"), 80 | } 81 | 82 | close, err := agent.OnStateChange(configFilePath, log.WithName("onStateChange"), func(state agent.State) { 83 | log.Info("Received a new state") 84 | err := wg.Sync(state) 85 | if err != nil { 86 | log.Error(err, "Error while sycncing wireguard") 87 | } 88 | 89 | err = it.Sync(state) 90 | if err != nil { 91 | log.Error(err, "Error while syncing network policies") 92 | } 93 | 94 | }) 95 | 96 | if err != nil { 97 | log.Error(err, "Error while watching changes") 98 | os.Exit(1) 99 | } 100 | 101 | defer close() 102 | 103 | httpLog := log.WithName("http") 104 | 105 | http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { 106 | state, _, err := agent.GetDesiredState(configFilePath) 107 | 108 | if err != nil { 109 | httpLog.Error(err, "agent is not ready as it cannot get server state") 110 | w.WriteHeader(http.StatusServiceUnavailable) 111 | return 112 | } 113 | 114 | err = agent.IsStateValid(state) 115 | 116 | if err != nil { 117 | httpLog.Error(err, "agent is not ready as server state not valid") 118 | w.WriteHeader(http.StatusServiceUnavailable) 119 | return 120 | } 121 | 122 | err = wg.Sync(state) 123 | 124 | if err != nil { 125 | httpLog.Error(err, "agent is not ready as it cannot sync wireguard") 126 | w.WriteHeader(http.StatusServiceUnavailable) 127 | return 128 | } 129 | 130 | httpLog.Info("agent is ready") 131 | 132 | w.WriteHeader(http.StatusOK) 133 | }) 134 | http.ListenAndServe(":8080", nil) 135 | } 136 | -------------------------------------------------------------------------------- /cmd/manager/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "fmt" 22 | vpnv1alpha1 "github.com/jodevsa/wireguard-operator/pkg/api/v1alpha1" 23 | "github.com/jodevsa/wireguard-operator/pkg/controllers" 24 | v1 "k8s.io/api/core/v1" 25 | "os" 26 | 27 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 28 | // to ensure that exec-entrypoint and run can make use of them. 29 | _ "k8s.io/client-go/plugin/pkg/client/auth" 30 | 31 | "k8s.io/apimachinery/pkg/runtime" 32 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 33 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 34 | ctrl "sigs.k8s.io/controller-runtime" 35 | "sigs.k8s.io/controller-runtime/pkg/healthz" 36 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 37 | //+kubebuilder:scaffold:imports 38 | ) 39 | 40 | var ( 41 | scheme = runtime.NewScheme() 42 | setupLog = ctrl.Log.WithName("setup") 43 | ) 44 | 45 | func init() { 46 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 47 | 48 | utilruntime.Must(vpnv1alpha1.AddToScheme(scheme)) 49 | //+kubebuilder:scaffold:scheme 50 | } 51 | 52 | func main() { 53 | var agentImagePullPolicy string 54 | var metricsAddr string 55 | var enableLeaderElection bool 56 | var probeAddr string 57 | var wgImage string 58 | flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") 59 | flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") 60 | flag.StringVar(&wgImage, "agent-image", "", "The image used for wireguard server") 61 | flag.BoolVar(&enableLeaderElection, "leader-elect", false, 62 | "Enable leader election for controller manager. "+ 63 | "Enabling this will ensure there is only one active controller manager.") 64 | flag.StringVar(&agentImagePullPolicy, "agent-image-pull-policy", "IfNotPresent", "Use userspace implementation") 65 | opts := zap.Options{ 66 | Development: true, 67 | } 68 | opts.BindFlags(flag.CommandLine) 69 | flag.Parse() 70 | 71 | ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) 72 | 73 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 74 | Scheme: scheme, 75 | MetricsBindAddress: metricsAddr, 76 | Port: 9443, 77 | HealthProbeBindAddress: probeAddr, 78 | LeaderElection: enableLeaderElection, 79 | LeaderElectionID: "a6d3bffc.wireguard-operator.io", 80 | }) 81 | if err != nil { 82 | setupLog.Error(err, "unable to start manager") 83 | os.Exit(1) 84 | } 85 | 86 | if wgImage == "" { 87 | setupLog.Error(fmt.Errorf("--wireguard-container-image flag is not set"), "unable to start manager") 88 | os.Exit(1) 89 | } 90 | 91 | if err = (&controllers.WireguardReconciler{ 92 | Client: mgr.GetClient(), 93 | Scheme: mgr.GetScheme(), 94 | AgentImage: wgImage, 95 | AgentImagePullPolicy: v1.PullPolicy(agentImagePullPolicy), 96 | }).SetupWithManager(mgr); err != nil { 97 | setupLog.Error(err, "unable to create controller", "controller", "Wireguard") 98 | os.Exit(1) 99 | } 100 | if err = (&controllers.WireguardPeerReconciler{ 101 | Client: mgr.GetClient(), 102 | Scheme: mgr.GetScheme(), 103 | }).SetupWithManager(mgr); err != nil { 104 | setupLog.Error(err, "unable to create controller", "controller", "WireguardPeer") 105 | os.Exit(1) 106 | } 107 | //+kubebuilder:scaffold:builder 108 | 109 | if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { 110 | setupLog.Error(err, "unable to set up health check") 111 | os.Exit(1) 112 | } 113 | if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { 114 | setupLog.Error(err, "unable to set up ready check") 115 | os.Exit(1) 116 | } 117 | 118 | setupLog.Info("starting manager") 119 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 120 | setupLog.Error(err, "problem running manager") 121 | os.Exit(1) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /config/crd/bases/vpn.wireguard-operator.io_wireguardpeers.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.14.0 7 | name: wireguardpeers.vpn.wireguard-operator.io 8 | spec: 9 | group: vpn.wireguard-operator.io 10 | names: 11 | kind: WireguardPeer 12 | listKind: WireguardPeerList 13 | plural: wireguardpeers 14 | singular: wireguardpeer 15 | scope: Namespaced 16 | versions: 17 | - name: v1alpha1 18 | schema: 19 | openAPIV3Schema: 20 | description: WireguardPeer is the Schema for the wireguardpeers API 21 | properties: 22 | apiVersion: 23 | description: |- 24 | APIVersion defines the versioned schema of this representation of an object. 25 | Servers should convert recognized schemas to the latest internal value, and 26 | may reject unrecognized values. 27 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 28 | type: string 29 | kind: 30 | description: |- 31 | Kind is a string value representing the REST resource this object represents. 32 | Servers may infer this from the endpoint the client submits requests to. 33 | Cannot be updated. 34 | In CamelCase. 35 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 36 | type: string 37 | metadata: 38 | type: object 39 | spec: 40 | description: The desired state of the peer. 41 | properties: 42 | address: 43 | description: |- 44 | INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 45 | Important: Run "make" to regenerate code after modifying this file 46 | The address of the peer. 47 | type: string 48 | allowedIPs: 49 | description: The AllowedIPs of the peer. 50 | type: string 51 | disabled: 52 | description: Set to true to temporarily disable the peer. 53 | type: boolean 54 | dns: 55 | description: The DNS configuration for the peer. 56 | type: string 57 | downloadSpeed: 58 | properties: 59 | config: 60 | type: integer 61 | unit: 62 | enum: 63 | - mbps 64 | - kbps 65 | type: string 66 | type: object 67 | egressNetworkPolicies: 68 | description: Egress network policies for the peer. 69 | items: 70 | properties: 71 | action: 72 | description: Specifies the action to take when outgoing traffic 73 | from a Wireguard peer matches the policy. This could be 'Accept' 74 | or 'Reject'. 75 | enum: 76 | - ACCEPT 77 | - REJECT 78 | - Accept 79 | - Reject 80 | type: string 81 | protocol: 82 | description: Specifies the protocol to match for this policy. 83 | This could be TCP, UDP, or ICMP. 84 | enum: 85 | - TCP 86 | - UDP 87 | - ICMP 88 | type: string 89 | to: 90 | description: A struct that specifies the destination address 91 | and port for the traffic. This could include IP addresses 92 | or hostnames, as well as specific port numbers or port ranges. 93 | properties: 94 | ip: 95 | description: A string field that specifies the destination 96 | IP address for traffic that matches the policy. 97 | type: string 98 | port: 99 | description: An integer field that specifies the destination 100 | port number for traffic that matches the policy. 101 | format: int32 102 | type: integer 103 | type: object 104 | type: object 105 | type: array 106 | privateKeyRef: 107 | description: The private key of the peer 108 | properties: 109 | secretKeyRef: 110 | description: SecretKeySelector selects a key of a Secret. 111 | properties: 112 | key: 113 | description: The key of the secret to select from. Must be 114 | a valid secret key. 115 | type: string 116 | name: 117 | description: |- 118 | Name of the referent. 119 | More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names 120 | TODO: Add other useful fields. apiVersion, kind, uid? 121 | type: string 122 | optional: 123 | description: Specify whether the Secret or its key must be 124 | defined 125 | type: boolean 126 | required: 127 | - key 128 | type: object 129 | x-kubernetes-map-type: atomic 130 | required: 131 | - secretKeyRef 132 | type: object 133 | publicKey: 134 | description: The key used by the peer to authenticate with the wg 135 | server. 136 | type: string 137 | uploadSpeed: 138 | properties: 139 | config: 140 | type: integer 141 | unit: 142 | enum: 143 | - mbps 144 | - kbps 145 | type: string 146 | type: object 147 | wireguardRef: 148 | description: The name of the Wireguard instance in k8s that the peer 149 | belongs to. The wg instance should be in the same namespace as the 150 | peer. 151 | minLength: 1 152 | type: string 153 | required: 154 | - wireguardRef 155 | type: object 156 | status: 157 | description: A field that defines the observed state of the Wireguard 158 | peer. This includes fields like the current configuration and status 159 | of the peer. 160 | properties: 161 | config: 162 | description: |- 163 | INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 164 | Important: Run "make" to regenerate code after modifying this file 165 | A string field that contains the current configuration for the Wireguard peer. 166 | type: string 167 | message: 168 | description: A string field that provides additional information about 169 | the status of the Wireguard peer. This could include error messages 170 | or other information that helps to diagnose issues with the peer. 171 | type: string 172 | status: 173 | description: A string field that represents the current status of 174 | the Wireguard peer. This could include values like ready, pending, 175 | or error. 176 | type: string 177 | type: object 178 | type: object 179 | served: true 180 | storage: true 181 | subresources: 182 | status: {} 183 | -------------------------------------------------------------------------------- /config/crd/bases/vpn.wireguard-operator.io_wireguards.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.14.0 7 | name: wireguards.vpn.wireguard-operator.io 8 | spec: 9 | group: vpn.wireguard-operator.io 10 | names: 11 | kind: Wireguard 12 | listKind: WireguardList 13 | plural: wireguards 14 | singular: wireguard 15 | scope: Namespaced 16 | versions: 17 | - name: v1alpha1 18 | schema: 19 | openAPIV3Schema: 20 | description: Wireguard is the Schema for the wireguards API 21 | properties: 22 | apiVersion: 23 | description: |- 24 | APIVersion defines the versioned schema of this representation of an object. 25 | Servers should convert recognized schemas to the latest internal value, and 26 | may reject unrecognized values. 27 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 28 | type: string 29 | kind: 30 | description: |- 31 | Kind is a string value representing the REST resource this object represents. 32 | Servers may infer this from the endpoint the client submits requests to. 33 | Cannot be updated. 34 | In CamelCase. 35 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 36 | type: string 37 | metadata: 38 | type: object 39 | spec: 40 | description: WireguardSpec defines the desired state of Wireguard 41 | properties: 42 | address: 43 | description: A string field that specifies the address for the Wireguard 44 | VPN server. This is the public IP address or hostname that peers 45 | will use to connect to the VPN. 46 | type: string 47 | agent: 48 | description: WireguardPodSpec defines spec for respective containers 49 | created for Wireguard 50 | properties: 51 | resources: 52 | description: ResourceRequirements describes the compute resource 53 | requirements. 54 | properties: 55 | claims: 56 | description: |- 57 | Claims lists the names of resources, defined in spec.resourceClaims, 58 | that are used by this container. 59 | 60 | 61 | This is an alpha field and requires enabling the 62 | DynamicResourceAllocation feature gate. 63 | 64 | 65 | This field is immutable. It can only be set for containers. 66 | items: 67 | description: ResourceClaim references one entry in PodSpec.ResourceClaims. 68 | properties: 69 | name: 70 | description: |- 71 | Name must match the name of one entry in pod.spec.resourceClaims of 72 | the Pod where this field is used. It makes that resource available 73 | inside a container. 74 | type: string 75 | required: 76 | - name 77 | type: object 78 | type: array 79 | x-kubernetes-list-map-keys: 80 | - name 81 | x-kubernetes-list-type: map 82 | limits: 83 | additionalProperties: 84 | anyOf: 85 | - type: integer 86 | - type: string 87 | pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ 88 | x-kubernetes-int-or-string: true 89 | description: |- 90 | Limits describes the maximum amount of compute resources allowed. 91 | More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 92 | type: object 93 | requests: 94 | additionalProperties: 95 | anyOf: 96 | - type: integer 97 | - type: string 98 | pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ 99 | x-kubernetes-int-or-string: true 100 | description: |- 101 | Requests describes the minimum amount of compute resources required. 102 | If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, 103 | otherwise to an implementation-defined value. Requests cannot exceed Limits. 104 | More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 105 | type: object 106 | type: object 107 | type: object 108 | dns: 109 | description: A string field that specifies the DNS server(s) to be 110 | used by the peers. 111 | type: string 112 | enableIpForwardOnPodInit: 113 | description: A boolean field that specifies whether IP forwarding 114 | should be enabled on the Wireguard VPN pod at startup. This can 115 | be useful to enable if the peers are having problems with sending 116 | traffic to the internet. 117 | type: boolean 118 | metric: 119 | description: WireguardPodSpec defines spec for respective containers 120 | created for Wireguard 121 | properties: 122 | resources: 123 | description: ResourceRequirements describes the compute resource 124 | requirements. 125 | properties: 126 | claims: 127 | description: |- 128 | Claims lists the names of resources, defined in spec.resourceClaims, 129 | that are used by this container. 130 | 131 | 132 | This is an alpha field and requires enabling the 133 | DynamicResourceAllocation feature gate. 134 | 135 | 136 | This field is immutable. It can only be set for containers. 137 | items: 138 | description: ResourceClaim references one entry in PodSpec.ResourceClaims. 139 | properties: 140 | name: 141 | description: |- 142 | Name must match the name of one entry in pod.spec.resourceClaims of 143 | the Pod where this field is used. It makes that resource available 144 | inside a container. 145 | type: string 146 | required: 147 | - name 148 | type: object 149 | type: array 150 | x-kubernetes-list-map-keys: 151 | - name 152 | x-kubernetes-list-type: map 153 | limits: 154 | additionalProperties: 155 | anyOf: 156 | - type: integer 157 | - type: string 158 | pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ 159 | x-kubernetes-int-or-string: true 160 | description: |- 161 | Limits describes the maximum amount of compute resources allowed. 162 | More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 163 | type: object 164 | requests: 165 | additionalProperties: 166 | anyOf: 167 | - type: integer 168 | - type: string 169 | pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ 170 | x-kubernetes-int-or-string: true 171 | description: |- 172 | Requests describes the minimum amount of compute resources required. 173 | If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, 174 | otherwise to an implementation-defined value. Requests cannot exceed Limits. 175 | More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 176 | type: object 177 | type: object 178 | type: object 179 | mtu: 180 | description: A string field that specifies the maximum transmission 181 | unit (MTU) size for Wireguard packets for all peers. 182 | type: string 183 | nodeSelector: 184 | additionalProperties: 185 | type: string 186 | type: object 187 | port: 188 | description: A field that specifies the value to use for a nodePort 189 | ServiceType 190 | format: int32 191 | type: integer 192 | serviceAnnotations: 193 | additionalProperties: 194 | type: string 195 | description: A map of key value strings for service annotations 196 | type: object 197 | serviceType: 198 | description: A field that specifies the type of Kubernetes service 199 | that should be used for the Wireguard VPN. This could be ClusterIP, 200 | NodePort or LoadBalancer, depending on the needs of the deployment. 201 | type: string 202 | useWgUserspaceImplementation: 203 | description: A boolean field that specifies whether to use the userspace 204 | implementation of Wireguard instead of the kernel one. 205 | type: boolean 206 | type: object 207 | status: 208 | description: WireguardStatus defines the observed state of Wireguard 209 | properties: 210 | address: 211 | description: A string field that specifies the address for the Wireguard 212 | VPN server that is currently being used. 213 | type: string 214 | dns: 215 | type: string 216 | message: 217 | description: A string field that provides additional information about 218 | the status of Wireguard. This could include error messages or other 219 | information that helps to diagnose issues with the wg instance. 220 | type: string 221 | port: 222 | description: A string field that specifies the port for the Wireguard 223 | VPN server that is currently being used. 224 | type: string 225 | status: 226 | description: A string field that represents the current status of 227 | Wireguard. This could include values like ready, pending, or error. 228 | type: string 229 | type: object 230 | type: object 231 | served: true 232 | storage: true 233 | subresources: 234 | status: {} 235 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/vpn.wireguard-operator.io_wireguardpeers.yaml 6 | - bases/vpn.wireguard-operator.io_wireguards.yaml 7 | #+kubebuilder:scaffold:crdkustomizeresource 8 | 9 | patchesStrategicMerge: 10 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 11 | # patches here are for enabling the conversion webhook for each CRD 12 | #- patches/webhook_in_wireguards.yaml 13 | #- patches/webhook_in_wireguardpeers.yaml 14 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 15 | 16 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. 17 | # patches here are for enabling the CA injection for each CRD 18 | #- patches/cainjection_in_wireguards.yaml 19 | #- patches/cainjection_in_wireguardpeers.yaml 20 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 21 | 22 | # the following config is for teaching kustomize how to do kustomization for CRDs. 23 | configurations: 24 | - kustomizeconfig.yaml 25 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_wireguardpeers.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: wireguardpeers.vpn.wireguard-operator.io 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_wireguards.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: wireguards.vpn.wireguard-operator.io 8 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_wireguardpeers.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: wireguardpeers.vpn.wireguard-operator.io 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_wireguards.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: wireguards.vpn.wireguard-operator.io 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: wireguard-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: wireguard- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 20 | # crd/kustomization.yaml 21 | #- ../webhook 22 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 23 | #- ../certmanager 24 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 25 | #- ../prometheus 26 | 27 | patchesStrategicMerge: 28 | # Protect the /metrics endpoint by putting it behind auth. 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, please comment the following line. 31 | - manager_auth_proxy_patch.yaml 32 | 33 | # Mount the controller config file for loading manager configurations 34 | # through a ComponentConfig type 35 | #- manager_config_patch.yaml 36 | 37 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 38 | # crd/kustomization.yaml 39 | #- manager_webhook_patch.yaml 40 | 41 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 42 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 43 | # 'CERTMANAGER' needs to be enabled to use ca injection 44 | #- webhookcainjection_patch.yaml 45 | 46 | # the following config is for teaching kustomize how to do var substitution 47 | vars: 48 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 49 | #- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 50 | # objref: 51 | # kind: Certificate 52 | # group: cert-manager.io 53 | # version: v1 54 | # name: serving-cert # this name should match the one in certificate.yaml 55 | # fieldref: 56 | # fieldpath: metadata.namespace 57 | #- name: CERTIFICATE_NAME 58 | # objref: 59 | # kind: Certificate 60 | # group: cert-manager.io 61 | # version: v1 62 | # name: serving-cert # this name should match the one in certificate.yaml 63 | #- name: SERVICE_NAMESPACE # namespace of the service 64 | # objref: 65 | # kind: Service 66 | # version: v1 67 | # name: webhook-service 68 | # fieldref: 69 | # fieldpath: metadata.namespace 70 | #- name: SERVICE_NAME 71 | # objref: 72 | # kind: Service 73 | # version: v1 74 | # name: webhook-service 75 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 14 | args: 15 | - "--secure-listen-address=0.0.0.0:8443" 16 | - "--upstream=http://127.0.0.1:8080/" 17 | - "--logtostderr=true" 18 | - "--v=0" 19 | ports: 20 | - containerPort: 8443 21 | protocol: TCP 22 | name: https 23 | securityContext: 24 | runAsNonRoot: false 25 | runAsGroup: 65534 26 | runAsUser: 65534 27 | allowPrivilegeEscalation: false 28 | readOnlyRootFilesystem: true 29 | capabilities: 30 | drop: 31 | - ALL 32 | resources: 33 | limits: 34 | cpu: 500m 35 | memory: 128Mi 36 | requests: 37 | cpu: 5m 38 | memory: 64Mi 39 | - name: manager 40 | args: 41 | - "--health-probe-bind-address=:8081" 42 | - "--metrics-bind-address=127.0.0.1:8080" 43 | - "--leader-elect" 44 | - "--agent-image=ghcr.io/jodevsa/wireguard-operator/agent:main" 45 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml.template: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 14 | args: 15 | - "--secure-listen-address=0.0.0.0:8443" 16 | - "--upstream=http://127.0.0.1:8080/" 17 | - "--logtostderr=true" 18 | - "--v=0" 19 | ports: 20 | - containerPort: 8443 21 | protocol: TCP 22 | name: https 23 | securityContext: 24 | runAsNonRoot: false 25 | runAsGroup: 65534 26 | runAsUser: 65534 27 | allowPrivilegeEscalation: false 28 | readOnlyRootFilesystem: true 29 | capabilities: 30 | drop: 31 | - ALL 32 | resources: 33 | limits: 34 | cpu: 500m 35 | memory: 128Mi 36 | requests: 37 | cpu: 5m 38 | memory: 64Mi 39 | - name: manager 40 | args: 41 | - "--health-probe-bind-address=:8081" 42 | - "--metrics-bind-address=127.0.0.1:8080" 43 | - "--leader-elect" 44 | - "--agent-image=${AGENT_IMAGE}" 45 | -------------------------------------------------------------------------------- /config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | args: 12 | - "--config=controller_manager_config.yaml" 13 | volumeMounts: 14 | - name: manager-config 15 | mountPath: /controller_manager_config.yaml 16 | subPath: controller_manager_config.yaml 17 | volumes: 18 | - name: manager-config 19 | configMap: 20 | name: manager-config 21 | -------------------------------------------------------------------------------- /config/manager/controller_manager_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 2 | kind: ControllerManagerConfig 3 | health: 4 | healthProbeBindAddress: :8081 5 | metrics: 6 | bindAddress: 127.0.0.1:8080 7 | webhook: 8 | port: 9443 9 | leaderElection: 10 | leaderElect: true 11 | resourceName: a6d3bffc.wireguard-operator.io 12 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | 4 | generatorOptions: 5 | disableNameSuffixHash: true 6 | 7 | configMapGenerator: 8 | - files: 9 | - controller_manager_config.yaml 10 | name: manager-config 11 | apiVersion: kustomize.config.k8s.io/v1beta1 12 | kind: Kustomization 13 | images: 14 | - name: controller 15 | newName: ghcr.io/jodevsa/wireguard-operator/manager 16 | newTag: main 17 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: system 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: controller-manager 12 | namespace: system 13 | labels: 14 | control-plane: controller-manager 15 | spec: 16 | selector: 17 | matchLabels: 18 | control-plane: controller-manager 19 | replicas: 1 20 | template: 21 | metadata: 22 | annotations: 23 | kubectl.kubernetes.io/default-container: manager 24 | labels: 25 | control-plane: controller-manager 26 | spec: 27 | securityContext: 28 | fsGroup: 65534 29 | runAsNonRoot: true 30 | containers: 31 | - command: 32 | - /manager 33 | args: 34 | - --leader-elect 35 | image: controller:latest 36 | name: manager 37 | securityContext: 38 | runAsNonRoot: false 39 | runAsGroup: 65534 40 | runAsUser: 65534 41 | allowPrivilegeEscalation: false 42 | readOnlyRootFilesystem: true 43 | capabilities: 44 | drop: 45 | - ALL 46 | livenessProbe: 47 | httpGet: 48 | path: /healthz 49 | port: 8081 50 | initialDelaySeconds: 15 51 | periodSeconds: 20 52 | readinessProbe: 53 | httpGet: 54 | path: /readyz 55 | port: 8081 56 | initialDelaySeconds: 5 57 | periodSeconds: 10 58 | # TODO(user): Configure the resources accordingly based on the project requirements. 59 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 60 | resources: 61 | limits: 62 | cpu: 500m 63 | memory: 128Mi 64 | requests: 65 | cpu: 10m 66 | memory: 64Mi 67 | serviceAccountName: controller-manager 68 | terminationGracePeriodSeconds: 10 69 | -------------------------------------------------------------------------------- /config/manifests/bases/wireguard-operator.clusterserviceversion.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: operators.coreos.com/v1alpha1 2 | kind: ClusterServiceVersion 3 | metadata: 4 | annotations: 5 | alm-examples: '[]' 6 | capabilities: Basic Install 7 | name: wireguard-operator.v0.0.0 8 | namespace: placeholder 9 | spec: 10 | apiservicedefinitions: {} 11 | customresourcedefinitions: 12 | owned: 13 | - description: WireguardPeer is the Schema for the wireguardpeers API 14 | displayName: Wireguard Peer 15 | kind: WireguardPeer 16 | name: wireguardpeers.vpn.wireguard-operator.io 17 | version: v1alpha1 18 | - description: Wireguard is the Schema for the wireguards API 19 | displayName: Wireguard 20 | kind: Wireguard 21 | name: wireguards.vpn.wireguard-operator.io 22 | version: v1alpha1 23 | description: op 24 | displayName: wireguard 25 | icon: 26 | - base64data: "" 27 | mediatype: "" 28 | install: 29 | spec: 30 | deployments: null 31 | strategy: "" 32 | installModes: 33 | - supported: false 34 | type: OwnNamespace 35 | - supported: false 36 | type: SingleNamespace 37 | - supported: false 38 | type: MultiNamespace 39 | - supported: true 40 | type: AllNamespaces 41 | keywords: 42 | - e 43 | links: 44 | - name: Wireguard Operator 45 | url: https://wireguard-operator.domain 46 | maturity: alpha 47 | provider: 48 | name: e 49 | url: w 50 | version: 0.0.0 51 | -------------------------------------------------------------------------------- /config/manifests/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # These resources constitute the fully configured set of manifests 2 | # used to generate the 'manifests/' directory in a bundle. 3 | resources: 4 | - bases/wireguard-operator.clusterserviceversion.yaml 5 | - ../default 6 | - ../samples 7 | - ../scorecard 8 | 9 | # [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. 10 | # Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. 11 | # These patches remove the unnecessary "cert" volume and its manager container volumeMount. 12 | #patchesJson6902: 13 | #- target: 14 | # group: apps 15 | # version: v1 16 | # kind: Deployment 17 | # name: controller-manager 18 | # namespace: system 19 | # patch: |- 20 | # # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. 21 | # # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. 22 | # - op: remove 23 | # path: /spec/template/spec/containers/1/volumeMounts/0 24 | # # Remove the "cert" volume, since OLM will create and mount a set of certs. 25 | # # Update the indices in this path if adding or removing volumes in the manager's Deployment. 26 | # - op: remove 27 | # path: /spec/template/spec/volumes/0 28 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | name: controller-manager-metrics-monitor 9 | namespace: system 10 | spec: 11 | endpoints: 12 | - path: /metrics 13 | port: https 14 | scheme: https 15 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 16 | tlsConfig: 17 | insecureSkipVerify: true 18 | selector: 19 | matchLabels: 20 | control-plane: controller-manager 21 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: 7 | - "/metrics" 8 | verbs: 9 | - get 10 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: 7 | - authentication.k8s.io 8 | resources: 9 | - tokenreviews 10 | verbs: 11 | - create 12 | - apiGroups: 13 | - authorization.k8s.io 14 | resources: 15 | - subjectaccessreviews 16 | verbs: 17 | - create 18 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: controller-manager-metrics-service 7 | namespace: system 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | protocol: TCP 13 | targetPort: https 14 | selector: 15 | control-plane: controller-manager 16 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | # Comment the following 4 lines if you want to disable 13 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 14 | # which protects your /metrics endpoint. 15 | - auth_proxy_service.yaml 16 | - auth_proxy_role.yaml 17 | - auth_proxy_role_binding.yaml 18 | - auth_proxy_client_clusterrole.yaml 19 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - coordination.k8s.io 21 | resources: 22 | - leases 23 | verbs: 24 | - get 25 | - list 26 | - watch 27 | - create 28 | - update 29 | - patch 30 | - delete 31 | - apiGroups: 32 | - "" 33 | resources: 34 | - events 35 | verbs: 36 | - create 37 | - patch 38 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: manager-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - "" 21 | resources: 22 | - nodes 23 | verbs: 24 | - list 25 | - watch 26 | - apiGroups: 27 | - "" 28 | resources: 29 | - pods 30 | verbs: 31 | - create 32 | - delete 33 | - get 34 | - list 35 | - patch 36 | - update 37 | - watch 38 | - apiGroups: 39 | - "" 40 | resources: 41 | - secrets 42 | verbs: 43 | - create 44 | - delete 45 | - get 46 | - list 47 | - patch 48 | - update 49 | - watch 50 | - apiGroups: 51 | - "" 52 | resources: 53 | - services 54 | verbs: 55 | - create 56 | - delete 57 | - get 58 | - list 59 | - patch 60 | - update 61 | - watch 62 | - apiGroups: 63 | - apps 64 | resources: 65 | - deployments 66 | verbs: 67 | - create 68 | - delete 69 | - get 70 | - list 71 | - patch 72 | - update 73 | - watch 74 | - apiGroups: 75 | - apps 76 | resources: 77 | - pods 78 | verbs: 79 | - create 80 | - delete 81 | - get 82 | - list 83 | - patch 84 | - update 85 | - watch 86 | - apiGroups: 87 | - vpn.wireguard-operator.io 88 | resources: 89 | - wireguardpeers 90 | verbs: 91 | - create 92 | - delete 93 | - get 94 | - list 95 | - patch 96 | - update 97 | - watch 98 | - apiGroups: 99 | - vpn.wireguard-operator.io 100 | resources: 101 | - wireguardpeers/finalizers 102 | verbs: 103 | - update 104 | - apiGroups: 105 | - vpn.wireguard-operator.io 106 | resources: 107 | - wireguardpeers/status 108 | verbs: 109 | - get 110 | - patch 111 | - update 112 | - apiGroups: 113 | - vpn.wireguard-operator.io 114 | resources: 115 | - wireguards 116 | verbs: 117 | - create 118 | - delete 119 | - get 120 | - list 121 | - patch 122 | - update 123 | - watch 124 | - apiGroups: 125 | - vpn.wireguard-operator.io 126 | resources: 127 | - wireguards/finalizers 128 | verbs: 129 | - update 130 | - apiGroups: 131 | - vpn.wireguard-operator.io 132 | resources: 133 | - wireguards/status 134 | verbs: 135 | - get 136 | - patch 137 | - update 138 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | -------------------------------------------------------------------------------- /config/rbac/wireguard_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit wireguards. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: wireguard-editor-role 6 | rules: 7 | - apiGroups: 8 | - vpn.wireguard-operator.io 9 | resources: 10 | - wireguards 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - vpn.wireguard-operator.io 21 | resources: 22 | - wireguards/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/wireguard_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view wireguards. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: wireguard-viewer-role 6 | rules: 7 | - apiGroups: 8 | - vpn.wireguard-operator.io 9 | resources: 10 | - wireguards 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - vpn.wireguard-operator.io 17 | resources: 18 | - wireguards/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/wireguardpeer_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit wireguardpeers. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: wireguardpeer-editor-role 6 | rules: 7 | - apiGroups: 8 | - vpn.wireguard-operator.io 9 | resources: 10 | - wireguardpeers 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - vpn.wireguard-operator.io 21 | resources: 22 | - wireguardpeers/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/wireguardpeer_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view wireguardpeers. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: wireguardpeer-viewer-role 6 | rules: 7 | - apiGroups: 8 | - vpn.wireguard-operator.io 9 | resources: 10 | - wireguardpeers 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - vpn.wireguard-operator.io 17 | resources: 18 | - wireguardpeers/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples you want in your CSV to this file as resources ## 2 | resources: 3 | - vpn_v1alpha1_wireguard.yaml 4 | - vpn_v1alpha1_wireguardpeer.yaml 5 | #+kubebuilder:scaffold:manifestskustomizesamples 6 | -------------------------------------------------------------------------------- /config/samples/vpn_v1alpha1_wireguard.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: vpn.wireguard-operator.io/v1alpha1 2 | kind: Wireguard 3 | metadata: 4 | name: wireguard-sample 5 | spec: 6 | # TODO(user): Add fields here 7 | -------------------------------------------------------------------------------- /config/samples/vpn_v1alpha1_wireguardpeer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: vpn.wireguard-operator.io/v1alpha1 2 | kind: WireguardPeer 3 | metadata: 4 | name: wireguardpeer-sample 5 | spec: 6 | # TODO(user): Add fields here 7 | -------------------------------------------------------------------------------- /config/scorecard/bases/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: [] 8 | -------------------------------------------------------------------------------- /config/scorecard/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - bases/config.yaml 3 | patchesJson6902: 4 | - path: patches/basic.config.yaml 5 | target: 6 | group: scorecard.operatorframework.io 7 | version: v1alpha3 8 | kind: Configuration 9 | name: config 10 | - path: patches/olm.config.yaml 11 | target: 12 | group: scorecard.operatorframework.io 13 | version: v1alpha3 14 | kind: Configuration 15 | name: config 16 | #+kubebuilder:scaffold:patchesJson6902 17 | -------------------------------------------------------------------------------- /config/scorecard/patches/basic.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - basic-check-spec 7 | image: quay.io/operator-framework/scorecard-test:v1.17.0 8 | labels: 9 | suite: basic 10 | test: basic-check-spec-test 11 | -------------------------------------------------------------------------------- /config/scorecard/patches/olm.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - olm-bundle-validation 7 | image: quay.io/operator-framework/scorecard-test:v1.17.0 8 | labels: 9 | suite: olm 10 | test: olm-bundle-validation-test 11 | - op: add 12 | path: /stages/0/tests/- 13 | value: 14 | entrypoint: 15 | - scorecard-test 16 | - olm-crds-have-validation 17 | image: quay.io/operator-framework/scorecard-test:v1.17.0 18 | labels: 19 | suite: olm 20 | test: olm-crds-have-validation-test 21 | - op: add 22 | path: /stages/0/tests/- 23 | value: 24 | entrypoint: 25 | - scorecard-test 26 | - olm-crds-have-resources 27 | image: quay.io/operator-framework/scorecard-test:v1.17.0 28 | labels: 29 | suite: olm 30 | test: olm-crds-have-resources-test 31 | - op: add 32 | path: /stages/0/tests/- 33 | value: 34 | entrypoint: 35 | - scorecard-test 36 | - olm-spec-descriptors 37 | image: quay.io/operator-framework/scorecard-test:v1.17.0 38 | labels: 39 | suite: olm 40 | test: olm-spec-descriptors-test 41 | - op: add 42 | path: /stages/0/tests/- 43 | value: 44 | entrypoint: 45 | - scorecard-test 46 | - olm-status-descriptors 47 | image: quay.io/operator-framework/scorecard-test:v1.17.0 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /examples/peer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: vpn.wireguard-operator.io/v1alpha1 2 | kind: WireguardPeer 3 | metadata: 4 | name: peer20 5 | spec: 6 | wireguardRef: "vpn" 7 | 8 | -------------------------------------------------------------------------------- /examples/server.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: vpn.wireguard-operator.io/v1alpha1 2 | kind: Wireguard 3 | metadata: 4 | name: vpn 5 | spec: 6 | mtu: "1380" -------------------------------------------------------------------------------- /examples/serverWithIpForwardEnable.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: vpn.wireguard-operator.io/v1alpha1 2 | kind: Wireguard 3 | metadata: 4 | name: vpn 5 | spec: 6 | enableIpForwardOnPodInit: true 7 | -------------------------------------------------------------------------------- /examples/serverWithNodePortService.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: vpn.wireguard-operator.io/v1alpha1 2 | kind: Wireguard 3 | metadata: 4 | name: vpn 5 | spec: 6 | mtu: "1380" 7 | serviceType: "NodePort" -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /images/agent/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG WIREGUARD_GO_SRC=/usr/local/src/wireguard-go 2 | ARG WIREGUARD_AGENT_SRC=/usr/local/src/wireguard-agent 3 | ARG PROMETHEUS_WIREGUARD_EXPORTER_SRC=/usr/local/src/prometheus-wireguard-exporter 4 | 5 | # step 1: Build Agent 6 | FROM --platform=${BUILDPLATFORM} golang:1.22 AS golang-builder 7 | ARG WIREGUARD_AGENT_SRC 8 | WORKDIR $WIREGUARD_AGENT_SRC 9 | # Copy the Go Modules manifests 10 | COPY go.mod go.mod 11 | COPY go.sum go.sum 12 | # cache deps before building and copying source so that we don't need to re-download as much 13 | # and so that source changes don't invalidate our downloaded layer 14 | RUN go mod download 15 | # COPY src 16 | COPY pkg/ pkg/ 17 | COPY cmd/ cmd/ 18 | COPY internal/iptables internal/iptables 19 | # build 20 | ARG TARGETOS TARGETARCH 21 | RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o agent -x cmd/agent/main.go 22 | # end of step 1 23 | 24 | # step 2: Build wireguard-go 25 | ARG WIREGUARD_GO_SRC 26 | WORKDIR $WIREGUARD_GO_SRC 27 | RUN set -eux; \ 28 | git clone https://github.com/WireGuard/wireguard-go.git . ;\ 29 | CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o wireguard-go ; 30 | # end of step 2 31 | 32 | # step 3: Build wireguard exporter 33 | FROM --platform=${BUILDPLATFORM} rust:1-buster AS rust-builder 34 | ARG PROMETHEUS_WIREGUARD_EXPORTER_SRC 35 | ARG TARGETPLATFORM 36 | 37 | WORKDIR $PROMETHEUS_WIREGUARD_EXPORTER_SRC 38 | RUN apt update && apt upgrade -y 39 | RUN apt install -y g++-aarch64-linux-gnu libc6-dev-arm64-cross 40 | 41 | ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++ 42 | 43 | RUN set -eux; \ 44 | git clone https://github.com/MindFlavor/prometheus_wireguard_exporter.git . ;\ 45 | if [ "$TARGETPLATFORM" = "linux/amd64" ] ; then \ 46 | cargo build --release ;\ 47 | cp ./target/release/prometheus_wireguard_exporter . ;\ 48 | else if [ "$TARGETPLATFORM" = "linux/arm64" ] ; then \ 49 | rustup target add aarch64-unknown-linux-gnu ;\ 50 | rustup toolchain install stable-aarch64-unknown-linux-gnu ;\ 51 | cargo build --target aarch64-unknown-linux-gnu --release ;\ 52 | cp ./target/aarch64-unknown-linux-gnu/release/prometheus_wireguard_exporter . ;\ 53 | else \ 54 | echo "$TARGETPLATFORM not supported yet." ;\ 55 | exit 1 ;\ 56 | fi ;\ 57 | fi ; 58 | # end of step 3 59 | 60 | # step 4: prepare image 61 | FROM --platform=${TARGETPLATFORM} debian:bookworm 62 | ARG WIREGUARD_GO_SRC 63 | ARG WIREGUARD_AGENT_SRC 64 | ARG PROMETHEUS_WIREGUARD_EXPORTER_SRC 65 | 66 | RUN apt-get update \ 67 | && apt-get install --no-install-recommends -y iptables wireguard-tools \ 68 | && rm -rf /var/lib/apt/lists/* 69 | 70 | COPY --from=golang-builder $WIREGUARD_GO_SRC/wireguard-go /usr/local/bin 71 | COPY --from=golang-builder $WIREGUARD_AGENT_SRC/agent /usr/local/bin 72 | COPY --from=rust-builder $PROMETHEUS_WIREGUARD_EXPORTER_SRC/prometheus_wireguard_exporter /usr/local/bin 73 | 74 | WORKDIR / 75 | 76 | ENTRYPOINT ["agent"] 77 | # end of step 4 78 | -------------------------------------------------------------------------------- /images/manager/Dockerfile: -------------------------------------------------------------------------------- 1 | # step 1: Build the manager binary 2 | FROM --platform=${BUILDPLATFORM} golang:1.22 as builder 3 | 4 | WORKDIR /workspace 5 | # Copy the Go Modules manifests 6 | COPY go.mod go.mod 7 | COPY go.sum go.sum 8 | # cache deps before building and copying source so that we don't need to re-download as much 9 | # and so that source changes don't invalidate our downloaded layer 10 | RUN go mod download 11 | 12 | # Copy the go source 13 | COPY cmd cmd/ 14 | COPY pkg pkg/ 15 | 16 | # Build 17 | ARG TARGETOS TARGETARCH 18 | RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -x -a -o manager ./cmd/manager/main.go 19 | 20 | # Use distroless as minimal base image to package the manager binary 21 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 22 | FROM --platform=${TARGETPLATFORM} gcr.io/distroless/static:nonroot 23 | WORKDIR / 24 | COPY --from=builder /workspace/manager . 25 | USER 65532:65532 26 | 27 | ENTRYPOINT ["/manager"] 28 | -------------------------------------------------------------------------------- /internal/iptables/iptables.go: -------------------------------------------------------------------------------- 1 | package iptables 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | "strings" 7 | 8 | "github.com/go-logr/logr" 9 | "github.com/jodevsa/wireguard-operator/pkg/agent" 10 | "github.com/jodevsa/wireguard-operator/pkg/api/v1alpha1" 11 | ) 12 | 13 | func ApplyRules(rules string) error { 14 | cmd := exec.Command("iptables-restore") 15 | cmd.Stdin = strings.NewReader(rules) 16 | return cmd.Run() 17 | } 18 | 19 | type Iptables struct { 20 | Logger logr.Logger 21 | } 22 | 23 | func (it *Iptables) Sync(state agent.State) error { 24 | it.Logger.Info("syncing network policies") 25 | wgHostName := state.Server.Status.Address 26 | dns := state.Server.Status.Dns 27 | peers := state.Peers 28 | 29 | cfg := GenerateIptableRulesFromPeers(wgHostName, dns, peers) 30 | 31 | err := ApplyRules(cfg) 32 | 33 | if err != nil { 34 | return err 35 | } 36 | 37 | return nil 38 | } 39 | 40 | func GenerateIptableRulesFromNetworkPolicies(policies v1alpha1.EgressNetworkPolicies, peerIp string, kubeDnsIp string, wgServerIp string) string { 41 | peerChain := strings.ReplaceAll(peerIp, ".", "-") 42 | 43 | rules := []string{ 44 | // add a comment 45 | fmt.Sprintf("# start of rules for peer %s", peerIp), 46 | 47 | // create chain for peer 48 | fmt.Sprintf(":%s - [0:0]", peerChain), 49 | 50 | // associate peer chain to FORWARD chain 51 | fmt.Sprintf("-A FORWARD -s %s -j %s", peerIp, peerChain), 52 | 53 | // allow peer to ping (ICMP) wireguard server for debugging purposes 54 | fmt.Sprintf("-A %s -d %s -p icmp -j ACCEPT", peerChain, wgServerIp), 55 | 56 | // allow peer to communicate with itself 57 | fmt.Sprintf("-A %s -d %s -j ACCEPT", peerChain, peerIp), 58 | 59 | // allow peer to communicate with kube-dns 60 | fmt.Sprintf("-A %s -d %s -p UDP --dport 53 -j ACCEPT", peerChain, kubeDnsIp), 61 | } 62 | 63 | for _, policy := range policies { 64 | rules = append(rules, EgressNetworkPolicyToIpTableRules(policy, peerChain)...) 65 | } 66 | 67 | // if policies are defined impose an implicit deny all 68 | if len(policies) != 0 { 69 | rules = append(rules, fmt.Sprintf("-A %s -j REJECT --reject-with icmp-port-unreachable", peerChain)) 70 | } 71 | 72 | // add a comment 73 | rules = append(rules, fmt.Sprintf("# end of rules for peer %s", peerIp)) 74 | 75 | return strings.Join(rules, "\n") 76 | } 77 | 78 | func GenerateIptableRulesFromPeers(wgHostName string, dns string, peers []v1alpha1.WireguardPeer) string { 79 | var rules []string 80 | 81 | var natTableRules = ` 82 | *nat 83 | :PREROUTING ACCEPT [0:0] 84 | :INPUT ACCEPT [0:0] 85 | :OUTPUT ACCEPT [0:0] 86 | :POSTROUTING ACCEPT [0:0] 87 | -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE 88 | COMMIT` 89 | 90 | for _, peer := range peers { 91 | 92 | //tc(peer.Spec.DownloadSpeed, peer.Spec.UploadSpeed) 93 | rules = append(rules, GenerateIptableRulesFromNetworkPolicies(peer.Spec.EgressNetworkPolicies, peer.Spec.Address, dns, wgHostName)) 94 | } 95 | 96 | var filterTableRules = fmt.Sprintf(` 97 | *filter 98 | :INPUT ACCEPT [0:0] 99 | :FORWARD ACCEPT [0:0] 100 | :OUTPUT ACCEPT [0:0] 101 | %s 102 | COMMIT 103 | `, strings.Join(rules, "\n")) 104 | 105 | return fmt.Sprintf("%s\n%s", natTableRules, filterTableRules) 106 | } 107 | 108 | func EgressNetworkPolicyToIpTableRules(policy v1alpha1.EgressNetworkPolicy, peerChain string) []string { 109 | 110 | var rules []string 111 | 112 | if policy.Protocol == "" && policy.To.Port != 0 { 113 | policy.Protocol = "TCP" 114 | rules = append(rules, EgressNetworkPolicyToIpTableRules(policy, peerChain)[0]) 115 | policy.Protocol = "UDP" 116 | rules = append(rules, EgressNetworkPolicyToIpTableRules(policy, peerChain)[0]) 117 | return rules 118 | } 119 | 120 | // customer rules 121 | var rulePeerChain = "-A " + peerChain 122 | var ruleAction = string("-j " + v1alpha1.EgressNetworkPolicyActionDeny) 123 | var ruleProtocol = "" 124 | var ruleDestIp = "" 125 | var ruleDestPort = "" 126 | 127 | if policy.To.Ip != "" { 128 | ruleDestIp = "-d " + policy.To.Ip 129 | } 130 | 131 | if policy.Protocol != "" { 132 | ruleProtocol = "-p " + strings.ToUpper(string(policy.Protocol)) 133 | } 134 | 135 | if policy.To.Port != 0 { 136 | ruleDestPort = "--dport " + fmt.Sprint(policy.To.Port) 137 | } 138 | 139 | if policy.Action != "" { 140 | ruleAction = "-j " + strings.ToUpper(string(policy.Action)) 141 | } 142 | 143 | var options = []string{rulePeerChain, ruleDestIp, ruleProtocol, ruleDestPort, ruleAction} 144 | var filteredOptions []string 145 | for _, option := range options { 146 | if len(option) != 0 { 147 | filteredOptions = append(filteredOptions, option) 148 | } 149 | } 150 | rules = append(rules, strings.Join(filteredOptions, " ")) 151 | 152 | return rules 153 | 154 | } 155 | -------------------------------------------------------------------------------- /internal/iptables/iptables_test.go: -------------------------------------------------------------------------------- 1 | package iptables 2 | 3 | import ( 4 | "github.com/jodevsa/wireguard-operator/pkg/api/v1alpha1" 5 | "testing" 6 | ) 7 | 8 | // test helpers 9 | 10 | func TestIptableRules(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | peerIp string 14 | kubeDnsIp string 15 | wgServerIp string 16 | networkPolicies v1alpha1.EgressNetworkPolicies 17 | expectedIptableRules string 18 | }{ 19 | { 20 | name: "EgressNetworkPolicy with destination IP address filter", 21 | peerIp: "192.168.1.115", 22 | kubeDnsIp: "69.96.1.42", 23 | wgServerIp: "192.168.1.1", 24 | networkPolicies: v1alpha1.EgressNetworkPolicies{ 25 | v1alpha1.EgressNetworkPolicy{ 26 | Action: v1alpha1.EgressNetworkPolicyActionAccept, 27 | To: v1alpha1.EgressNetworkPolicyTo{Ip: "8.8.8.8"}}, 28 | }, 29 | expectedIptableRules: `# start of rules for peer 192.168.1.115 30 | :192-168-1-115 - [0:0] 31 | -A FORWARD -s 192.168.1.115 -j 192-168-1-115 32 | -A 192-168-1-115 -d 192.168.1.1 -p icmp -j ACCEPT 33 | -A 192-168-1-115 -d 192.168.1.115 -j ACCEPT 34 | -A 192-168-1-115 -d 69.96.1.42 -p UDP --dport 53 -j ACCEPT 35 | -A 192-168-1-115 -d 8.8.8.8 -j ACCEPT 36 | -A 192-168-1-115 -j REJECT --reject-with icmp-port-unreachable 37 | # end of rules for peer 192.168.1.115`, 38 | }, 39 | { 40 | name: "Able to filter egress by UDP", 41 | peerIp: "10.8.0.9", 42 | kubeDnsIp: "100.64.0.10", 43 | wgServerIp: "10.8.0.1", 44 | networkPolicies: v1alpha1.EgressNetworkPolicies{ 45 | v1alpha1.EgressNetworkPolicy{ 46 | Action: v1alpha1.EgressNetworkPolicyActionAccept, 47 | Protocol: "UDP", 48 | To: v1alpha1.EgressNetworkPolicyTo{}}, 49 | }, 50 | expectedIptableRules: `# start of rules for peer 10.8.0.9 51 | :10-8-0-9 - [0:0] 52 | -A FORWARD -s 10.8.0.9 -j 10-8-0-9 53 | -A 10-8-0-9 -d 10.8.0.1 -p icmp -j ACCEPT 54 | -A 10-8-0-9 -d 10.8.0.9 -j ACCEPT 55 | -A 10-8-0-9 -d 100.64.0.10 -p UDP --dport 53 -j ACCEPT 56 | -A 10-8-0-9 -p UDP -j ACCEPT 57 | -A 10-8-0-9 -j REJECT --reject-with icmp-port-unreachable 58 | # end of rules for peer 10.8.0.9`, 59 | }, 60 | { 61 | name: "Empty networkPolicies", 62 | peerIp: "10.8.0.9", 63 | kubeDnsIp: "100.64.0.10", 64 | wgServerIp: "10.8.0.1", 65 | networkPolicies: v1alpha1.EgressNetworkPolicies{}, 66 | expectedIptableRules: `# start of rules for peer 10.8.0.9 67 | :10-8-0-9 - [0:0] 68 | -A FORWARD -s 10.8.0.9 -j 10-8-0-9 69 | -A 10-8-0-9 -d 10.8.0.1 -p icmp -j ACCEPT 70 | -A 10-8-0-9 -d 10.8.0.9 -j ACCEPT 71 | -A 10-8-0-9 -d 100.64.0.10 -p UDP --dport 53 -j ACCEPT 72 | # end of rules for peer 10.8.0.9`, 73 | }, 74 | { 75 | name: "networkPolicies with 1 empty networkPolicy", 76 | peerIp: "10.8.0.11", 77 | kubeDnsIp: "100.64.0.21", 78 | wgServerIp: "10.7.0.1", 79 | networkPolicies: v1alpha1.EgressNetworkPolicies{v1alpha1.EgressNetworkPolicy{}}, 80 | expectedIptableRules: `# start of rules for peer 10.8.0.11 81 | :10-8-0-11 - [0:0] 82 | -A FORWARD -s 10.8.0.11 -j 10-8-0-11 83 | -A 10-8-0-11 -d 10.7.0.1 -p icmp -j ACCEPT 84 | -A 10-8-0-11 -d 10.8.0.11 -j ACCEPT 85 | -A 10-8-0-11 -d 100.64.0.21 -p UDP --dport 53 -j ACCEPT 86 | -A 10-8-0-11 -j Reject 87 | -A 10-8-0-11 -j REJECT --reject-with icmp-port-unreachable 88 | # end of rules for peer 10.8.0.11`, 89 | }, 90 | { 91 | name: "EgressNetworkPolicy with destination port Allowed", 92 | peerIp: "10.8.0.9", 93 | kubeDnsIp: "100.64.0.10", 94 | wgServerIp: "10.8.0.1", 95 | networkPolicies: v1alpha1.EgressNetworkPolicies{v1alpha1.EgressNetworkPolicy{ 96 | Protocol: v1alpha1.EgressNetworkPolicyProtocolTCP, 97 | Action: v1alpha1.EgressNetworkPolicyActionAccept, 98 | To: v1alpha1.EgressNetworkPolicyTo{Port: 8080}, 99 | }}, 100 | expectedIptableRules: `# start of rules for peer 10.8.0.9 101 | :10-8-0-9 - [0:0] 102 | -A FORWARD -s 10.8.0.9 -j 10-8-0-9 103 | -A 10-8-0-9 -d 10.8.0.1 -p icmp -j ACCEPT 104 | -A 10-8-0-9 -d 10.8.0.9 -j ACCEPT 105 | -A 10-8-0-9 -d 100.64.0.10 -p UDP --dport 53 -j ACCEPT 106 | -A 10-8-0-9 -p TCP --dport 8080 -j ACCEPT 107 | -A 10-8-0-9 -j REJECT --reject-with icmp-port-unreachable 108 | # end of rules for peer 10.8.0.9`, 109 | }, 110 | } 111 | 112 | for _, test := range tests { 113 | 114 | t.Run(test.name, func(t *testing.T) { 115 | 116 | rules := GenerateIptableRulesFromNetworkPolicies(test.networkPolicies, test.peerIp, test.kubeDnsIp, test.wgServerIp) 117 | if rules != test.expectedIptableRules { 118 | t.Errorf("got %s, want %s", rules, test.expectedIptableRules) 119 | } 120 | }) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /internal/it/it_test.go: -------------------------------------------------------------------------------- 1 | package it 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | var _ = Describe("wireguard controller", func() { 9 | It("wireguard is able to start", func() { 10 | wireguardYaml := 11 | `apiVersion: vpn.wireguard-operator.io/v1alpha1 12 | kind: Wireguard 13 | metadata: 14 | name: vpn 15 | spec: 16 | mtu: "1380" 17 | serviceType: "NodePort"` 18 | 19 | // kubectl apply -f 20 | output, err := KubectlApply(wireguardYaml, TestNamespace) 21 | Expect(err).NotTo(HaveOccurred()) 22 | Expect(output).To(Equal("wireguard.vpn.wireguard-operator.io/vpn created")) 23 | 24 | wireguardPeerYaml := 25 | `apiVersion: vpn.wireguard-operator.io/v1alpha1 26 | kind: WireguardPeer 27 | metadata: 28 | name: peer20 29 | spec: 30 | wireguardRef: "vpn" 31 | 32 | ` 33 | // kubectl apply -f 34 | output, err = KubectlApply(wireguardPeerYaml, TestNamespace) 35 | Expect(err).NotTo(HaveOccurred()) 36 | Expect(output).To(Equal("wireguardpeer.vpn.wireguard-operator.io/peer20 created")) 37 | 38 | WaitForWireguardToBeReady("vpn", TestNamespace) 39 | WaitForPeerToBeReady("peer20", TestNamespace) 40 | 41 | // TODO: connect to wg 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /internal/it/suite_test.go: -------------------------------------------------------------------------------- 1 | package it 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | "testing" 10 | "time" 11 | 12 | "github.com/go-logr/stdr" 13 | "github.com/jodevsa/wireguard-operator/pkg/api/v1alpha1" 14 | . "github.com/onsi/ginkgo" 15 | . "github.com/onsi/gomega" 16 | v12 "k8s.io/api/apps/v1" 17 | "k8s.io/apimachinery/pkg/types" 18 | "k8s.io/client-go/kubernetes/scheme" 19 | "k8s.io/client-go/tools/clientcmd" 20 | "sigs.k8s.io/controller-runtime/pkg/client" 21 | "sigs.k8s.io/kind/pkg/apis/config/v1alpha4" 22 | kind "sigs.k8s.io/kind/pkg/cluster" 23 | log2 "sigs.k8s.io/kind/pkg/log" 24 | //+kubebuilder:scaffold:imports 25 | ) 26 | 27 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 28 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 29 | 30 | var k8sClient client.Client 31 | var releasePath string 32 | var agentImage string 33 | var managerImage string 34 | var kindBinary string 35 | var kubeConfigPath string 36 | 37 | var testProvider = kind.NewProvider( 38 | kind.ProviderWithDocker()) 39 | 40 | func TestAPIs(t *testing.T) { 41 | RegisterFailHandler(Fail) 42 | 43 | RunSpecs(t, 44 | "Controller Suite") 45 | } 46 | 47 | const ( 48 | Timeout = time.Second * 120 49 | Interval = time.Second * 1 50 | testClusterName = "wg-kind-test" 51 | testKindContextName = "kind-" + testClusterName 52 | TestNamespace = "default" 53 | ) 54 | 55 | func waitForDeploymentTobeReady(name string, namespace string) { 56 | Eventually(func() int { 57 | deploymentKey := types.NamespacedName{ 58 | Namespace: namespace, 59 | Name: name, 60 | } 61 | 62 | deployment := &v12.Deployment{} 63 | k8sClient.Get(context.Background(), deploymentKey, deployment) 64 | return int(deployment.Status.ReadyReplicas) 65 | }, Timeout, Interval).Should(Equal(1)) 66 | 67 | } 68 | 69 | func WaitForWireguardToBeReady(name string, namespace string) { 70 | Eventually(func() string { 71 | wgKey := types.NamespacedName{ 72 | Namespace: namespace, 73 | Name: name, 74 | } 75 | wg := &v1alpha1.Wireguard{} 76 | k8sClient.Get(context.Background(), wgKey, wg) 77 | return wg.Status.Status 78 | }, Timeout, Interval).Should(Equal(v1alpha1.Ready)) 79 | 80 | waitForDeploymentTobeReady(name+"-dep", namespace) 81 | } 82 | func WaitForPeerToBeReady(name string, namespace string) { 83 | Eventually(func() string { 84 | wgKey := types.NamespacedName{ 85 | Namespace: namespace, 86 | Name: name, 87 | } 88 | wg := &v1alpha1.WireguardPeer{} 89 | k8sClient.Get(context.Background(), wgKey, wg) 90 | return wg.Status.Status 91 | }, Timeout, Interval).Should(Equal(v1alpha1.Ready)) 92 | 93 | } 94 | 95 | func KubectlApply(resource string, namespace string) (string, error) { 96 | cmd := exec.Command("kubectl", "apply", 97 | "--context", testKindContextName, 98 | "-n", namespace, 99 | "-f", "-", 100 | ) 101 | cmd.Stdin = strings.NewReader(resource) 102 | 103 | var stdout, stderr strings.Builder 104 | cmd.Stdout = &stdout 105 | cmd.Stderr = &stderr 106 | 107 | if err := cmd.Run(); err != nil { 108 | return stderr.String(), err 109 | } 110 | return strings.TrimSpace(stdout.String()), nil 111 | } 112 | 113 | var _ = BeforeSuite(func() { 114 | releasePath = os.Getenv("WIREGUARD_OPERATOR_RELEASE_PATH") 115 | agentImage = os.Getenv("AGENT_IMAGE") 116 | managerImage = os.Getenv("MANAGER_IMAGE") 117 | kindBinary = os.Getenv("KIND_BIN") 118 | kubeConfigPath = os.Getenv("KUBE_CONFIG") 119 | 120 | Expect(releasePath).NotTo(Equal("")) 121 | Expect(agentImage).NotTo(Equal("")) 122 | Expect(releasePath).NotTo(Equal("")) 123 | Expect(managerImage).NotTo(Equal("")) 124 | Expect(kindBinary).NotTo(Equal("")) 125 | Expect(kubeConfigPath).NotTo(Equal("")) 126 | 127 | config := v1alpha4.Cluster{ 128 | Nodes: []v1alpha4.Node{ 129 | { 130 | Role: v1alpha4.ControlPlaneRole, 131 | ExtraPortMappings: []v1alpha4.PortMapping{ 132 | { 133 | HostPort: 31820, 134 | ContainerPort: 31820, 135 | Protocol: v1alpha4.PortMappingProtocolUDP, 136 | }, 137 | }, 138 | }, 139 | }, 140 | } 141 | 142 | log := stdr.NewWithOptions(log.New(os.Stderr, "", log.LstdFlags), stdr.Options{LogCaller: stdr.All}) 143 | 144 | By("bootstrapping test environment") 145 | 146 | provider := kind.NewProvider( 147 | kind.ProviderWithLogger(log2.NoopLogger{})) 148 | 149 | err := provider.Create(testClusterName, kind.CreateWithV1Alpha4Config(&config)) 150 | if err != nil { 151 | log.Error(err, "unable to create kind cluster") 152 | return 153 | } 154 | 155 | clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( 156 | &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeConfigPath}, 157 | &clientcmd.ConfigOverrides{ 158 | CurrentContext: testKindContextName, 159 | }) 160 | 161 | c, err := clientConfig.ClientConfig() 162 | if err != nil { 163 | log.Error(err, "unable to create kind cluster") 164 | return 165 | } 166 | 167 | // load locally built images 168 | if _, err := exec. 169 | Command(kindBinary, "load", "docker-image", managerImage, "--name", testClusterName). 170 | Output(); err != nil { 171 | if err != nil { 172 | if exitError, ok := err.(*exec.ExitError); ok { 173 | log.Info(string(exitError.Stderr)) 174 | Expect(err).NotTo(HaveOccurred()) 175 | } 176 | } 177 | 178 | log.Error(err, "unable to load local image manager:dev") 179 | Expect(err).NotTo(HaveOccurred()) 180 | } 181 | 182 | if _, err := exec. 183 | Command(kindBinary, "load", "docker-image", agentImage, "--name", testClusterName). 184 | Output(); err != nil { 185 | log.Error(err, "unable to load local image agent:dev") 186 | return 187 | } 188 | 189 | // simulate what users exactly do in real life. 190 | b, err := exec. 191 | Command("kubectl", "apply", "-f", releasePath, "--context", testKindContextName). 192 | Output() 193 | if err != nil { 194 | log.Error(err, "unable to apply release.yaml") 195 | return 196 | } 197 | 198 | expectedResources := []string{ 199 | "namespace/wireguard-system", 200 | "customresourcedefinition.apiextensions.k8s.io/wireguardpeers.vpn.wireguard-operator.io", 201 | "customresourcedefinition.apiextensions.k8s.io/wireguards.vpn.wireguard-operator.io", 202 | "serviceaccount/wireguard-controller-manager", 203 | "role.rbac.authorization.k8s.io/wireguard-leader-election-role", 204 | "clusterrole.rbac.authorization.k8s.io/wireguard-manager-role", 205 | "clusterrole.rbac.authorization.k8s.io/wireguard-metrics-reader", 206 | "clusterrole.rbac.authorization.k8s.io/wireguard-proxy-role", 207 | "rolebinding.rbac.authorization.k8s.io/wireguard-leader-election-rolebinding", 208 | "clusterrolebinding.rbac.authorization.k8s.io/wireguard-manager-rolebinding", 209 | "clusterrolebinding.rbac.authorization.k8s.io/wireguard-proxy-rolebinding", 210 | "configmap/wireguard-manager-config", 211 | "service/wireguard-controller-manager-metrics-service", 212 | "deployment.apps/wireguard-controller-manager", 213 | } 214 | 215 | Expect(strings.Split(strings.Trim(strings.ReplaceAll(string(b), " created", ""), "\n"), "\n")).To(BeEquivalentTo(expectedResources)) 216 | 217 | err = v1alpha1.AddToScheme(scheme.Scheme) 218 | Expect(err).NotTo(HaveOccurred()) 219 | 220 | k8sClient, err = client.New(c, client.Options{Scheme: scheme.Scheme}) 221 | Expect(err).NotTo(HaveOccurred()) 222 | 223 | // wait until operator is ready 224 | Eventually(func() int { 225 | deploymentKey := types.NamespacedName{ 226 | Namespace: "wireguard-system", 227 | Name: "wireguard-controller-manager", 228 | } 229 | 230 | deployment := &v12.Deployment{} 231 | k8sClient.Get(context.Background(), deploymentKey, deployment) 232 | return int(deployment.Status.ReadyReplicas) 233 | }, Timeout, Interval).Should(Equal(1)) 234 | 235 | go func() { 236 | defer GinkgoRecover() 237 | }() 238 | 239 | }, 60) 240 | 241 | var _ = AfterSuite(func() { 242 | By("tearing down the test environment") 243 | err := testProvider.Delete(testClusterName, kubeConfigPath) 244 | Expect(err).NotTo(HaveOccurred()) 245 | }) 246 | -------------------------------------------------------------------------------- /pkg/agent/agent.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "encoding/json" 7 | "fmt" 8 | "os" 9 | "path/filepath" 10 | 11 | "github.com/go-logr/logr" 12 | 13 | "github.com/fsnotify/fsnotify" 14 | "github.com/jodevsa/wireguard-operator/pkg/api/v1alpha1" 15 | ) 16 | 17 | type State struct { 18 | Server v1alpha1.Wireguard 19 | ServerPrivateKey string 20 | Peers []v1alpha1.WireguardPeer 21 | } 22 | 23 | func IsStateValid(state State) error { 24 | 25 | if state.ServerPrivateKey == "" { 26 | return fmt.Errorf("server private key is not defined") 27 | } 28 | 29 | if len(state.ServerPrivateKey) != 44 { 30 | return fmt.Errorf("server private key should be of length 44") 31 | } 32 | 33 | if state.Server.Status.Address == "" { 34 | return fmt.Errorf("server address is not defined") 35 | } 36 | 37 | if state.Server.Status.Dns == "" { 38 | return fmt.Errorf("dns is not defined") 39 | } 40 | 41 | for i, peer := range state.Peers { 42 | if peer.Spec.Address == "" { 43 | return fmt.Errorf("peer with index %d does not have the address defined", i) 44 | } 45 | 46 | if peer.Spec.PublicKey == "" { 47 | return fmt.Errorf("peer with index %d does not have a public key defined", i) 48 | } 49 | } 50 | 51 | return nil 52 | } 53 | 54 | func OnStateChange(path string, logger logr.Logger, onFileChange func(State)) (func(), error) { 55 | 56 | dir := filepath.Dir(path) 57 | 58 | watcher, err := fsnotify.NewWatcher() 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | close := func() { 64 | watcher.Close() 65 | } 66 | 67 | state, hash, err := GetDesiredState(path) 68 | 69 | if err == nil { 70 | err := IsStateValid(state) 71 | 72 | if err != nil { 73 | logger.Error(err, "State is not valid") 74 | } else { 75 | onFileChange(state) 76 | } 77 | } 78 | 79 | // Start listening for events. 80 | go func() { 81 | for { 82 | select { 83 | case event, ok := <-watcher.Events: 84 | if !ok { 85 | return 86 | } 87 | logger.V(9).Info("Received a new event", "filename", event.Name, "operation", event.Op.String()) 88 | if event.Has(fsnotify.Write) || event.Has(fsnotify.Create) { 89 | 90 | state, newHash, err := GetDesiredState(path) 91 | if err != nil { 92 | logger.Error(err, "unable to read or parse state") 93 | continue 94 | } 95 | 96 | if newHash == hash { 97 | logger.V(9).Info("Received a new event but state content did not change") 98 | continue 99 | } 100 | 101 | logger.V(9).Info("State content changed", "oldHash", hash, "newHash", newHash) 102 | hash = newHash 103 | 104 | err = IsStateValid(state) 105 | 106 | if err != nil { 107 | logger.Error(err, "State is not valid") 108 | continue 109 | } 110 | 111 | onFileChange(state) 112 | } 113 | case err, ok := <-watcher.Errors: 114 | if !ok { 115 | return 116 | } 117 | logger.Error(err, "watcher error") 118 | } 119 | } 120 | }() 121 | 122 | err = watcher.Add(dir) 123 | if err != nil { 124 | println(".....") 125 | return close, err 126 | } 127 | return close, nil 128 | } 129 | 130 | func GetDesiredState(path string) (State, string, error) { 131 | var state State 132 | jsonFile, err := os.ReadFile(path) 133 | if err != nil { 134 | return State{}, "", err 135 | } 136 | err = json.Unmarshal(jsonFile, &state) 137 | if err != nil { 138 | return State{}, "", err 139 | } 140 | hash := md5.Sum(jsonFile) 141 | 142 | return state, hex.EncodeToString(hash[:]), nil 143 | } 144 | -------------------------------------------------------------------------------- /pkg/api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the vpn v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=vpn.wireguard-operator.io 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "vpn.wireguard-operator.io", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /pkg/api/v1alpha1/wireguard_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | 22 | corev1 "k8s.io/api/core/v1" 23 | ) 24 | 25 | const ( 26 | Pending = "pending" 27 | Error = "error" 28 | Ready = "ready" 29 | ) 30 | 31 | type WgStatusReport struct { 32 | // A string field that represents the current status of Wireguard. This could include values like ready, pending, or error. 33 | Status string `json:"status,omitempty"` 34 | // A string field that provides additional information about the status of Wireguard. This could include error messages or other information that helps to diagnose issues with the wg instance. 35 | Message string `json:"message,omitempty"` 36 | } 37 | 38 | // WireguardSpec defines the desired state of Wireguard 39 | type WireguardSpec struct { 40 | // A string field that specifies the maximum transmission unit (MTU) size for Wireguard packets for all peers. 41 | Mtu string `json:"mtu,omitempty"` 42 | // A string field that specifies the address for the Wireguard VPN server. This is the public IP address or hostname that peers will use to connect to the VPN. 43 | Address string `json:"address,omitempty"` 44 | // A string field that specifies the DNS server(s) to be used by the peers. 45 | Dns string `json:"dns,omitempty"` 46 | // A field that specifies the type of Kubernetes service that should be used for the Wireguard VPN. This could be ClusterIP, NodePort or LoadBalancer, depending on the needs of the deployment. 47 | ServiceType corev1.ServiceType `json:"serviceType,omitempty"` 48 | // A field that specifies the value to use for a nodePort ServiceType 49 | NodePort int32 `json:"port,omitempty"` 50 | // A map of key value strings for service annotations 51 | ServiceAnnotations map[string]string `json:"serviceAnnotations,omitempty"` 52 | // A boolean field that specifies whether IP forwarding should be enabled on the Wireguard VPN pod at startup. This can be useful to enable if the peers are having problems with sending traffic to the internet. 53 | EnableIpForwardOnPodInit bool `json:"enableIpForwardOnPodInit,omitempty"` 54 | // A boolean field that specifies whether to use the userspace implementation of Wireguard instead of the kernel one. 55 | UseWgUserspaceImplementation bool `json:"useWgUserspaceImplementation,omitempty"` 56 | 57 | NodeSelector map[string]string `json:"nodeSelector,omitempty"` 58 | Agent WireguardPodSpec `json:"agent,omitempty"` 59 | Metric WireguardPodSpec `json:"metric,omitempty"` 60 | } 61 | 62 | // WireguardPodSpec defines spec for respective containers created for Wireguard 63 | type WireguardPodSpec struct { 64 | Resources corev1.ResourceRequirements `json:"resources,omitempty"` 65 | } 66 | 67 | // WireguardStatus defines the observed state of Wireguard 68 | type WireguardStatus struct { 69 | // A string field that specifies the address for the Wireguard VPN server that is currently being used. 70 | Address string `json:"address,omitempty"` 71 | Dns string `json:"dns,omitempty"` 72 | // A string field that specifies the port for the Wireguard VPN server that is currently being used. 73 | Port string `json:"port,omitempty"` 74 | // A string field that represents the current status of Wireguard. This could include values like ready, pending, or error. 75 | Status string `json:"status,omitempty"` 76 | // A string field that provides additional information about the status of Wireguard. This could include error messages or other information that helps to diagnose issues with the wg instance. 77 | Message string `json:"message,omitempty"` 78 | } 79 | 80 | //+kubebuilder:object:root=true 81 | //+kubebuilder:subresource:status 82 | 83 | // Wireguard is the Schema for the wireguards API 84 | type Wireguard struct { 85 | metav1.TypeMeta `json:",inline"` 86 | metav1.ObjectMeta `json:"metadata,omitempty"` 87 | 88 | Spec WireguardSpec `json:"spec,omitempty"` 89 | Status WireguardStatus `json:"status,omitempty"` 90 | } 91 | 92 | //+kubebuilder:object:root=true 93 | 94 | // WireguardList contains a list of Wireguard 95 | type WireguardList struct { 96 | metav1.TypeMeta `json:",inline"` 97 | metav1.ListMeta `json:"metadata,omitempty"` 98 | Items []Wireguard `json:"items"` 99 | } 100 | 101 | func init() { 102 | SchemeBuilder.Register(&Wireguard{}, &WireguardList{}) 103 | } 104 | -------------------------------------------------------------------------------- /pkg/api/v1alpha1/wireguardpeer_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | corev1 "k8s.io/api/core/v1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | type PrivateKey struct { 25 | SecretKeyRef corev1.SecretKeySelector `json:"secretKeyRef"` 26 | } 27 | 28 | type Status struct { 29 | } 30 | 31 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 32 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 33 | 34 | // WireguardPeerSpec defines the desired state of WireguardPeer 35 | type WireguardPeerSpec struct { 36 | // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 37 | // Important: Run "make" to regenerate code after modifying this file 38 | // The address of the peer. 39 | Address string `json:"address,omitempty"` 40 | // The AllowedIPs of the peer. 41 | AllowedIPs string `json:"allowedIPs,omitempty"` 42 | // Set to true to temporarily disable the peer. 43 | Disabled bool `json:"disabled,omitempty"` 44 | // The DNS configuration for the peer. 45 | Dns string `json:"dns,omitempty"` 46 | // The private key of the peer 47 | PrivateKey PrivateKey `json:"privateKeyRef,omitempty"` 48 | // The key used by the peer to authenticate with the wg server. 49 | PublicKey string `json:"publicKey,omitempty"` 50 | // The name of the Wireguard instance in k8s that the peer belongs to. The wg instance should be in the same namespace as the peer. 51 | //+kubebuilder:validation:Required 52 | //+kubebuilder:validation:MinLength=1 53 | WireguardRef string `json:"wireguardRef"` 54 | // Egress network policies for the peer. 55 | EgressNetworkPolicies EgressNetworkPolicies `json:"egressNetworkPolicies,omitempty"` 56 | DownloadSpeed Speed `json:"downloadSpeed,omitempty"` 57 | UploadSpeed Speed `json:"uploadSpeed,omitempty"` 58 | } 59 | 60 | type EgressNetworkPolicies []EgressNetworkPolicy 61 | 62 | // +kubebuilder:validation:Enum=ACCEPT;REJECT;Accept;Reject 63 | type EgressNetworkPolicyAction string 64 | 65 | // +kubebuilder:validation:Enum=TCP;UDP;ICMP 66 | type EgressNetworkPolicyProtocol string 67 | 68 | const ( 69 | EgressNetworkPolicyActionAccept EgressNetworkPolicyAction = "Accept" 70 | EgressNetworkPolicyActionDeny EgressNetworkPolicyAction = "Reject" 71 | ) 72 | 73 | const ( 74 | EgressNetworkPolicyProtocolTCP EgressNetworkPolicyProtocol = "TCP" 75 | EgressNetworkPolicyProtocolUDP EgressNetworkPolicyProtocol = "UDP" 76 | ) 77 | 78 | type EgressNetworkPolicy struct { 79 | // Specifies the action to take when outgoing traffic from a Wireguard peer matches the policy. This could be 'Accept' or 'Reject'. 80 | Action EgressNetworkPolicyAction `json:"action,omitempty"` 81 | // A struct that specifies the destination address and port for the traffic. This could include IP addresses or hostnames, as well as specific port numbers or port ranges. 82 | To EgressNetworkPolicyTo `json:"to,omitempty"` 83 | // Specifies the protocol to match for this policy. This could be TCP, UDP, or ICMP. 84 | Protocol EgressNetworkPolicyProtocol `json:"protocol,omitempty"` 85 | } 86 | 87 | type EgressNetworkPolicyTo struct { 88 | // A string field that specifies the destination IP address for traffic that matches the policy. 89 | Ip string `json:"ip,omitempty"` 90 | // An integer field that specifies the destination port number for traffic that matches the policy. 91 | Port int32 `json:"port,omitempty" protobuf:"varint,3,opt,name=port"` 92 | } 93 | 94 | // WireguardPeerStatus defines the observed state of WireguardPeer 95 | type WireguardPeerStatus struct { 96 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 97 | // Important: Run "make" to regenerate code after modifying this file 98 | // A string field that contains the current configuration for the Wireguard peer. 99 | Config string `json:"config,omitempty"` 100 | // A string field that represents the current status of the Wireguard peer. This could include values like ready, pending, or error. 101 | Status string `json:"status,omitempty"` 102 | // A string field that provides additional information about the status of the Wireguard peer. This could include error messages or other information that helps to diagnose issues with the peer. 103 | Message string `json:"message,omitempty"` 104 | } 105 | 106 | type Speed struct { 107 | Value int `json:"config,omitempty"` 108 | 109 | // +kubebuilder:validation:Enum=mbps;kbps 110 | Unit string `json:"unit,omitempty"` 111 | } 112 | 113 | //+kubebuilder:object:root=true 114 | //+kubebuilder:subresource:status 115 | 116 | // WireguardPeer is the Schema for the wireguardpeers API 117 | type WireguardPeer struct { 118 | metav1.TypeMeta `json:",inline"` 119 | metav1.ObjectMeta `json:"metadata,omitempty"` 120 | // The desired state of the peer. 121 | Spec WireguardPeerSpec `json:"spec,omitempty"` 122 | // A field that defines the observed state of the Wireguard peer. This includes fields like the current configuration and status of the peer. 123 | Status WireguardPeerStatus `json:"status,omitempty"` 124 | } 125 | 126 | //+kubebuilder:object:root=true 127 | 128 | // WireguardPeerList contains a list of WireguardPeer 129 | type WireguardPeerList struct { 130 | metav1.TypeMeta `json:",inline"` 131 | metav1.ListMeta `json:"metadata,omitempty"` 132 | Items []WireguardPeer `json:"items"` 133 | } 134 | 135 | func init() { 136 | SchemeBuilder.Register(&WireguardPeer{}, &WireguardPeerList{}) 137 | } 138 | -------------------------------------------------------------------------------- /pkg/api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | 3 | /* 4 | Copyright 2021. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // Code generated by controller-gen. DO NOT EDIT. 20 | 21 | package v1alpha1 22 | 23 | import ( 24 | runtime "k8s.io/apimachinery/pkg/runtime" 25 | ) 26 | 27 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 28 | func (in EgressNetworkPolicies) DeepCopyInto(out *EgressNetworkPolicies) { 29 | { 30 | in := &in 31 | *out = make(EgressNetworkPolicies, len(*in)) 32 | copy(*out, *in) 33 | } 34 | } 35 | 36 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EgressNetworkPolicies. 37 | func (in EgressNetworkPolicies) DeepCopy() EgressNetworkPolicies { 38 | if in == nil { 39 | return nil 40 | } 41 | out := new(EgressNetworkPolicies) 42 | in.DeepCopyInto(out) 43 | return *out 44 | } 45 | 46 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 47 | func (in *EgressNetworkPolicy) DeepCopyInto(out *EgressNetworkPolicy) { 48 | *out = *in 49 | out.To = in.To 50 | } 51 | 52 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EgressNetworkPolicy. 53 | func (in *EgressNetworkPolicy) DeepCopy() *EgressNetworkPolicy { 54 | if in == nil { 55 | return nil 56 | } 57 | out := new(EgressNetworkPolicy) 58 | in.DeepCopyInto(out) 59 | return out 60 | } 61 | 62 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 63 | func (in *EgressNetworkPolicyTo) DeepCopyInto(out *EgressNetworkPolicyTo) { 64 | *out = *in 65 | } 66 | 67 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EgressNetworkPolicyTo. 68 | func (in *EgressNetworkPolicyTo) DeepCopy() *EgressNetworkPolicyTo { 69 | if in == nil { 70 | return nil 71 | } 72 | out := new(EgressNetworkPolicyTo) 73 | in.DeepCopyInto(out) 74 | return out 75 | } 76 | 77 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 78 | func (in *PrivateKey) DeepCopyInto(out *PrivateKey) { 79 | *out = *in 80 | in.SecretKeyRef.DeepCopyInto(&out.SecretKeyRef) 81 | } 82 | 83 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrivateKey. 84 | func (in *PrivateKey) DeepCopy() *PrivateKey { 85 | if in == nil { 86 | return nil 87 | } 88 | out := new(PrivateKey) 89 | in.DeepCopyInto(out) 90 | return out 91 | } 92 | 93 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 94 | func (in *Speed) DeepCopyInto(out *Speed) { 95 | *out = *in 96 | } 97 | 98 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Speed. 99 | func (in *Speed) DeepCopy() *Speed { 100 | if in == nil { 101 | return nil 102 | } 103 | out := new(Speed) 104 | in.DeepCopyInto(out) 105 | return out 106 | } 107 | 108 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 109 | func (in *Status) DeepCopyInto(out *Status) { 110 | *out = *in 111 | } 112 | 113 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Status. 114 | func (in *Status) DeepCopy() *Status { 115 | if in == nil { 116 | return nil 117 | } 118 | out := new(Status) 119 | in.DeepCopyInto(out) 120 | return out 121 | } 122 | 123 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 124 | func (in *WgStatusReport) DeepCopyInto(out *WgStatusReport) { 125 | *out = *in 126 | } 127 | 128 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WgStatusReport. 129 | func (in *WgStatusReport) DeepCopy() *WgStatusReport { 130 | if in == nil { 131 | return nil 132 | } 133 | out := new(WgStatusReport) 134 | in.DeepCopyInto(out) 135 | return out 136 | } 137 | 138 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 139 | func (in *Wireguard) DeepCopyInto(out *Wireguard) { 140 | *out = *in 141 | out.TypeMeta = in.TypeMeta 142 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 143 | in.Spec.DeepCopyInto(&out.Spec) 144 | out.Status = in.Status 145 | } 146 | 147 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Wireguard. 148 | func (in *Wireguard) DeepCopy() *Wireguard { 149 | if in == nil { 150 | return nil 151 | } 152 | out := new(Wireguard) 153 | in.DeepCopyInto(out) 154 | return out 155 | } 156 | 157 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 158 | func (in *Wireguard) DeepCopyObject() runtime.Object { 159 | if c := in.DeepCopy(); c != nil { 160 | return c 161 | } 162 | return nil 163 | } 164 | 165 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 166 | func (in *WireguardList) DeepCopyInto(out *WireguardList) { 167 | *out = *in 168 | out.TypeMeta = in.TypeMeta 169 | in.ListMeta.DeepCopyInto(&out.ListMeta) 170 | if in.Items != nil { 171 | in, out := &in.Items, &out.Items 172 | *out = make([]Wireguard, len(*in)) 173 | for i := range *in { 174 | (*in)[i].DeepCopyInto(&(*out)[i]) 175 | } 176 | } 177 | } 178 | 179 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WireguardList. 180 | func (in *WireguardList) DeepCopy() *WireguardList { 181 | if in == nil { 182 | return nil 183 | } 184 | out := new(WireguardList) 185 | in.DeepCopyInto(out) 186 | return out 187 | } 188 | 189 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 190 | func (in *WireguardList) DeepCopyObject() runtime.Object { 191 | if c := in.DeepCopy(); c != nil { 192 | return c 193 | } 194 | return nil 195 | } 196 | 197 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 198 | func (in *WireguardPeer) DeepCopyInto(out *WireguardPeer) { 199 | *out = *in 200 | out.TypeMeta = in.TypeMeta 201 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 202 | in.Spec.DeepCopyInto(&out.Spec) 203 | out.Status = in.Status 204 | } 205 | 206 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WireguardPeer. 207 | func (in *WireguardPeer) DeepCopy() *WireguardPeer { 208 | if in == nil { 209 | return nil 210 | } 211 | out := new(WireguardPeer) 212 | in.DeepCopyInto(out) 213 | return out 214 | } 215 | 216 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 217 | func (in *WireguardPeer) DeepCopyObject() runtime.Object { 218 | if c := in.DeepCopy(); c != nil { 219 | return c 220 | } 221 | return nil 222 | } 223 | 224 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 225 | func (in *WireguardPeerList) DeepCopyInto(out *WireguardPeerList) { 226 | *out = *in 227 | out.TypeMeta = in.TypeMeta 228 | in.ListMeta.DeepCopyInto(&out.ListMeta) 229 | if in.Items != nil { 230 | in, out := &in.Items, &out.Items 231 | *out = make([]WireguardPeer, len(*in)) 232 | for i := range *in { 233 | (*in)[i].DeepCopyInto(&(*out)[i]) 234 | } 235 | } 236 | } 237 | 238 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WireguardPeerList. 239 | func (in *WireguardPeerList) DeepCopy() *WireguardPeerList { 240 | if in == nil { 241 | return nil 242 | } 243 | out := new(WireguardPeerList) 244 | in.DeepCopyInto(out) 245 | return out 246 | } 247 | 248 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 249 | func (in *WireguardPeerList) DeepCopyObject() runtime.Object { 250 | if c := in.DeepCopy(); c != nil { 251 | return c 252 | } 253 | return nil 254 | } 255 | 256 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 257 | func (in *WireguardPeerSpec) DeepCopyInto(out *WireguardPeerSpec) { 258 | *out = *in 259 | in.PrivateKey.DeepCopyInto(&out.PrivateKey) 260 | if in.EgressNetworkPolicies != nil { 261 | in, out := &in.EgressNetworkPolicies, &out.EgressNetworkPolicies 262 | *out = make(EgressNetworkPolicies, len(*in)) 263 | copy(*out, *in) 264 | } 265 | out.DownloadSpeed = in.DownloadSpeed 266 | out.UploadSpeed = in.UploadSpeed 267 | } 268 | 269 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WireguardPeerSpec. 270 | func (in *WireguardPeerSpec) DeepCopy() *WireguardPeerSpec { 271 | if in == nil { 272 | return nil 273 | } 274 | out := new(WireguardPeerSpec) 275 | in.DeepCopyInto(out) 276 | return out 277 | } 278 | 279 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 280 | func (in *WireguardPeerStatus) DeepCopyInto(out *WireguardPeerStatus) { 281 | *out = *in 282 | } 283 | 284 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WireguardPeerStatus. 285 | func (in *WireguardPeerStatus) DeepCopy() *WireguardPeerStatus { 286 | if in == nil { 287 | return nil 288 | } 289 | out := new(WireguardPeerStatus) 290 | in.DeepCopyInto(out) 291 | return out 292 | } 293 | 294 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 295 | func (in *WireguardPodSpec) DeepCopyInto(out *WireguardPodSpec) { 296 | *out = *in 297 | in.Resources.DeepCopyInto(&out.Resources) 298 | } 299 | 300 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WireguardPodSpec. 301 | func (in *WireguardPodSpec) DeepCopy() *WireguardPodSpec { 302 | if in == nil { 303 | return nil 304 | } 305 | out := new(WireguardPodSpec) 306 | in.DeepCopyInto(out) 307 | return out 308 | } 309 | 310 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 311 | func (in *WireguardSpec) DeepCopyInto(out *WireguardSpec) { 312 | *out = *in 313 | if in.ServiceAnnotations != nil { 314 | in, out := &in.ServiceAnnotations, &out.ServiceAnnotations 315 | *out = make(map[string]string, len(*in)) 316 | for key, val := range *in { 317 | (*out)[key] = val 318 | } 319 | } 320 | if in.NodeSelector != nil { 321 | in, out := &in.NodeSelector, &out.NodeSelector 322 | *out = make(map[string]string, len(*in)) 323 | for key, val := range *in { 324 | (*out)[key] = val 325 | } 326 | } 327 | in.Agent.DeepCopyInto(&out.Agent) 328 | in.Metric.DeepCopyInto(&out.Metric) 329 | } 330 | 331 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WireguardSpec. 332 | func (in *WireguardSpec) DeepCopy() *WireguardSpec { 333 | if in == nil { 334 | return nil 335 | } 336 | out := new(WireguardSpec) 337 | in.DeepCopyInto(out) 338 | return out 339 | } 340 | 341 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 342 | func (in *WireguardStatus) DeepCopyInto(out *WireguardStatus) { 343 | *out = *in 344 | } 345 | 346 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WireguardStatus. 347 | func (in *WireguardStatus) DeepCopy() *WireguardStatus { 348 | if in == nil { 349 | return nil 350 | } 351 | out := new(WireguardStatus) 352 | in.DeepCopyInto(out) 353 | return out 354 | } 355 | -------------------------------------------------------------------------------- /pkg/controllers/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "path/filepath" 21 | "testing" 22 | "time" 23 | 24 | vpnv1alpha1 "github.com/jodevsa/wireguard-operator/pkg/api/v1alpha1" 25 | 26 | . "github.com/onsi/ginkgo" 27 | . "github.com/onsi/gomega" 28 | "k8s.io/client-go/kubernetes/scheme" 29 | ctrl "sigs.k8s.io/controller-runtime" 30 | "sigs.k8s.io/controller-runtime/pkg/client" 31 | "sigs.k8s.io/controller-runtime/pkg/envtest" 32 | logf "sigs.k8s.io/controller-runtime/pkg/log" 33 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 34 | //+kubebuilder:scaffold:imports 35 | ) 36 | 37 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 38 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 39 | 40 | var k8sClient client.Client 41 | var testEnv *envtest.Environment 42 | var wgTestImage = "test-image" 43 | 44 | func TestAPIs(t *testing.T) { 45 | RegisterFailHandler(Fail) 46 | 47 | RunSpecs(t, 48 | "Controller Suite") 49 | } 50 | 51 | var _ = BeforeSuite(func() { 52 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 53 | 54 | By("bootstrapping test environment") 55 | testEnv = &envtest.Environment{ 56 | CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, 57 | ErrorIfCRDPathMissing: true, 58 | ControlPlaneStopTimeout: time.Second * 120, 59 | } 60 | 61 | cfg, err := testEnv.Start() 62 | Expect(err).NotTo(HaveOccurred()) 63 | Expect(cfg).NotTo(BeNil()) 64 | 65 | err = vpnv1alpha1.AddToScheme(scheme.Scheme) 66 | Expect(err).NotTo(HaveOccurred()) 67 | 68 | //+kubebuilder:scaffold:scheme 69 | 70 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 71 | Expect(err).NotTo(HaveOccurred()) 72 | Expect(k8sClient).NotTo(BeNil()) 73 | 74 | k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ 75 | Scheme: scheme.Scheme, 76 | }) 77 | Expect(err).ToNot(HaveOccurred()) 78 | 79 | err = (&WireguardReconciler{ 80 | Client: k8sManager.GetClient(), 81 | Scheme: k8sManager.GetScheme(), 82 | AgentImagePullPolicy: "IfNotPresent", 83 | AgentImage: wgTestImage, 84 | }).SetupWithManager(k8sManager) 85 | Expect(err).ToNot(HaveOccurred()) 86 | 87 | err = (&WireguardPeerReconciler{ 88 | Client: k8sManager.GetClient(), 89 | Scheme: k8sManager.GetScheme(), 90 | }).SetupWithManager(k8sManager) 91 | Expect(err).ToNot(HaveOccurred()) 92 | 93 | go func() { 94 | defer GinkgoRecover() 95 | err = k8sManager.Start(ctrl.SetupSignalHandler()) 96 | Expect(err).ToNot(HaveOccurred(), "failed to run manager") 97 | }() 98 | 99 | }, 60) 100 | 101 | var _ = AfterSuite(func() { 102 | By("tearing down the test environment") 103 | err := testEnv.Stop() 104 | Expect(err).NotTo(HaveOccurred()) 105 | }) 106 | -------------------------------------------------------------------------------- /pkg/controllers/test.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jodevsa/wireguard-operator/1ac0303136c4c50dd1329d149b319495194d823a/pkg/controllers/test.yaml -------------------------------------------------------------------------------- /pkg/controllers/wireguardpeer_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "github.com/jodevsa/wireguard-operator/pkg/api/v1alpha1" 23 | 24 | wgtypes "golang.zx2c4.com/wireguard/wgctrl/wgtypes" 25 | corev1 "k8s.io/api/core/v1" 26 | "k8s.io/apimachinery/pkg/api/errors" 27 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | "k8s.io/apimachinery/pkg/runtime" 29 | "k8s.io/apimachinery/pkg/types" 30 | ctrl "sigs.k8s.io/controller-runtime" 31 | "sigs.k8s.io/controller-runtime/pkg/client" 32 | ctrllog "sigs.k8s.io/controller-runtime/pkg/log" 33 | ) 34 | 35 | // WireguardPeerReconciler reconciles a WireguardPeer object 36 | type WireguardPeerReconciler struct { 37 | client.Client 38 | Scheme *runtime.Scheme 39 | } 40 | 41 | func (r *WireguardPeerReconciler) updateStatus(ctx context.Context, peer *v1alpha1.WireguardPeer, status string, message string) error { 42 | newPeer := peer.DeepCopy() 43 | if newPeer.Status.Status != status || newPeer.Status.Message != message { 44 | newPeer.Status.Status = status 45 | newPeer.Status.Message = message 46 | 47 | if err := r.Status().Update(ctx, newPeer); err != nil { 48 | return err 49 | } 50 | } 51 | return nil 52 | } 53 | 54 | func (r *WireguardPeerReconciler) secretForPeer(m *v1alpha1.WireguardPeer, privateKey string, publicKey string) *corev1.Secret { 55 | ls := labelsForWireguard(m.Name) 56 | dep := &corev1.Secret{ 57 | ObjectMeta: metav1.ObjectMeta{ 58 | Name: m.Name + "-peer", 59 | Namespace: m.Namespace, 60 | Labels: ls, 61 | }, 62 | Data: map[string][]byte{"privateKey": []byte(privateKey), "publicKey": []byte(publicKey)}, 63 | } 64 | // Set Nodered instance as the owner and controller 65 | ctrl.SetControllerReference(m, dep, r.Scheme) 66 | 67 | return dep 68 | 69 | } 70 | 71 | //+kubebuilder:rbac:groups=vpn.wireguard-operator.io,resources=wireguardpeers,verbs=get;list;watch;create;update;patch;delete 72 | //+kubebuilder:rbac:groups=vpn.wireguard-operator.io,resources=wireguardpeers/status,verbs=get;update;patch 73 | //+kubebuilder:rbac:groups=vpn.wireguard-operator.io,resources=wireguardpeers/finalizers,verbs=update 74 | 75 | // Reconcile is part of the main kubernetes reconciliation loop which aims to 76 | // move the current state of the cluster closer to the desired state. 77 | // TODO(user): Modify the Reconcile function to compare the state specified by 78 | // the WireguardPeer object against the actual cluster state, and then 79 | // perform operations to make the cluster state reflect the state specified by 80 | // the user. 81 | // 82 | // For more details, check Reconcile and its Result here: 83 | // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile 84 | 85 | func (r *WireguardPeerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 86 | log := ctrllog.FromContext(ctx) 87 | peer := &v1alpha1.WireguardPeer{} 88 | err := r.Get(ctx, req.NamespacedName, peer) 89 | if err != nil { 90 | if errors.IsNotFound(err) { 91 | // Request object not found, could have been deleted after reconcile request. 92 | // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 93 | // Return and don't requeue 94 | log.Info("wireguard peer resource not found. Ignoring since object must be deleted") 95 | return ctrl.Result{}, nil 96 | } 97 | // Error reading the object - requeue the request. 98 | log.Error(err, "Failed to get wireguard peer") 99 | return ctrl.Result{}, err 100 | } 101 | 102 | key, err := wgtypes.GeneratePrivateKey() 103 | if err != nil { 104 | log.Error(err, "Failed to generate private key") 105 | return ctrl.Result{}, err 106 | } 107 | 108 | newPeer := peer.DeepCopy() 109 | if newPeer.Status.Status == "" { 110 | err = r.updateStatus(ctx, newPeer, v1alpha1.Pending, "Waiting for wireguard peer to be created") 111 | 112 | if err != nil { 113 | return ctrl.Result{}, err 114 | } 115 | 116 | return ctrl.Result{Requeue: true}, nil 117 | } 118 | 119 | if peer.Spec.PublicKey == "" { 120 | privateKey := key.String() 121 | publicKey := key.PublicKey().String() 122 | 123 | secret := r.secretForPeer(peer, privateKey, publicKey) 124 | 125 | log.Info("Creating a new secret", "secret.Namespace", secret.Namespace, "secret.Name", secret.Name) 126 | err = r.Create(ctx, secret) 127 | if err != nil { 128 | log.Error(err, "Failed to create new secret", "secret.Namespace", secret.Namespace, "secret.Name", secret.Name) 129 | return ctrl.Result{}, err 130 | } 131 | 132 | newPeer.Spec.PublicKey = publicKey 133 | newPeer.Spec.PrivateKey = v1alpha1.PrivateKey{ 134 | SecretKeyRef: corev1.SecretKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: peer.Name + "-peer"}, Key: "privateKey"}} 135 | err = r.Update(ctx, newPeer) 136 | 137 | if err != nil { 138 | log.Error(err, "Failed to create new peer", "secret.Namespace", secret.Namespace, "secret.Name", secret.Name) 139 | return ctrl.Result{}, err 140 | } 141 | 142 | return ctrl.Result{Requeue: true}, nil 143 | 144 | } 145 | 146 | wireguard := &v1alpha1.Wireguard{} 147 | err = r.Get(ctx, types.NamespacedName{Name: newPeer.Spec.WireguardRef, Namespace: newPeer.Namespace}, wireguard) 148 | 149 | if err != nil { 150 | if errors.IsNotFound(err) { 151 | err = r.updateStatus(ctx, newPeer, v1alpha1.Error, fmt.Sprintf("Waiting for wireguard resource '%s' to be created", newPeer.Spec.WireguardRef)) 152 | 153 | if err != nil { 154 | return ctrl.Result{}, err 155 | } 156 | 157 | return ctrl.Result{}, nil 158 | } 159 | 160 | log.Error(err, "Failed to get wireguard") 161 | 162 | return ctrl.Result{}, err 163 | 164 | } 165 | 166 | if wireguard.Status.Status != v1alpha1.Ready { 167 | log.Info("Waiting for wireguard to be ready") 168 | 169 | err = r.updateStatus(ctx, newPeer, v1alpha1.Error, fmt.Sprintf("Waiting for %s to be ready", wireguard.Name)) 170 | 171 | if err != nil { 172 | return ctrl.Result{}, err 173 | } 174 | 175 | return ctrl.Result{}, nil 176 | } 177 | 178 | wireguardSecret := &corev1.Secret{} 179 | err = r.Get(ctx, types.NamespacedName{Name: newPeer.Spec.WireguardRef, Namespace: newPeer.Namespace}, wireguardSecret) 180 | 181 | if len(newPeer.OwnerReferences) == 0 { 182 | log.Info("Waiting for owner reference to be set " + wireguard.Name + " " + newPeer.Name) 183 | ctrl.SetControllerReference(wireguard, newPeer, r.Scheme) 184 | 185 | if err != nil { 186 | log.Error(err, "Failed to update peer with controller reference") 187 | return ctrl.Result{}, err 188 | } 189 | 190 | r.Update(ctx, newPeer) 191 | 192 | return ctrl.Result{Requeue: true}, nil 193 | } 194 | 195 | if newPeer.Status.Config == "" { 196 | err = r.updateStatus(ctx, newPeer, v1alpha1.Pending, "Waiting config to be updated") 197 | 198 | if err != nil { 199 | return ctrl.Result{}, err 200 | } 201 | 202 | } 203 | 204 | return ctrl.Result{}, nil 205 | } 206 | 207 | // SetupWithManager sets up the controller with the Manager. 208 | func (r *WireguardPeerReconciler) SetupWithManager(mgr ctrl.Manager) error { 209 | return ctrl.NewControllerManagedBy(mgr). 210 | For(&v1alpha1.WireguardPeer{}). 211 | Complete(r) 212 | } 213 | -------------------------------------------------------------------------------- /pkg/wireguard/wireguard.go: -------------------------------------------------------------------------------- 1 | package wireguard 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os/exec" 7 | "syscall" 8 | 9 | "github.com/go-logr/logr" 10 | 11 | "github.com/jodevsa/wireguard-operator/pkg/agent" 12 | "github.com/jodevsa/wireguard-operator/pkg/api/v1alpha1" 13 | "github.com/vishvananda/netlink" 14 | "golang.zx2c4.com/wireguard/wgctrl" 15 | "golang.zx2c4.com/wireguard/wgctrl/wgtypes" 16 | ) 17 | 18 | const MTU = 1420 19 | 20 | func syncRoute(_ agent.State, iface string) error { 21 | link, err := netlink.LinkByName(iface) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | routes, err := netlink.RouteList(link, syscall.AF_INET) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | for _, route := range routes { 32 | if route.LinkIndex == link.Attrs().Index { 33 | return nil 34 | } 35 | } 36 | route := netlink.Route{ 37 | LinkIndex: link.Attrs().Index, 38 | Dst: &getIP("10.8.0.0/24")[0], 39 | Gw: net.ParseIP("10.8.0.1"), 40 | } 41 | 42 | err = netlink.RouteAdd(&route) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | return nil 48 | } 49 | 50 | func syncAddress(_ agent.State, iface string) error { 51 | link, err := netlink.LinkByName(iface) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | addresses, err := netlink.AddrList(link, syscall.AF_INET) 57 | if err != nil { 58 | return nil 59 | } 60 | 61 | if len(addresses) != 0 { 62 | return nil 63 | } 64 | 65 | if err := netlink.AddrAdd(link, &netlink.Addr{ 66 | IPNet: &net.IPNet{IP: net.ParseIP("10.8.0.1")}, 67 | }); err != nil { 68 | return fmt.Errorf("netlink addr add: %w", err) 69 | } 70 | 71 | if err := netlink.LinkSetUp(link); err != nil { 72 | return err 73 | } 74 | return nil 75 | } 76 | 77 | func createLinkUsingUserspaceImpl(iface string, wgUserspaceImplementationFallback string) error { 78 | 79 | bashCommand := fmt.Sprintf("mkdir -p /dev/net && if [ ! -c /dev/net/tun ]; then\n mknod /dev/net/tun c 10 200\nfi && %s %s", wgUserspaceImplementationFallback, iface) 80 | cmd := exec.Command("bash", "-c", bashCommand) 81 | 82 | err := cmd.Run() 83 | if err != nil { 84 | return err 85 | } 86 | 87 | return nil 88 | 89 | } 90 | 91 | func createLinkUsingKernalModule(iface string) error { 92 | // link not created 93 | wgLink := &netlink.GenericLink{ 94 | LinkAttrs: netlink.LinkAttrs{ 95 | Name: iface, 96 | MTU: MTU, 97 | }, 98 | LinkType: "wireguard", 99 | } 100 | 101 | if err := netlink.LinkAdd(wgLink); err != nil { 102 | return err 103 | } 104 | return nil 105 | } 106 | 107 | func SyncLink(_ agent.State, iface string, wgUserspaceImplementationFallback string, wgUseUserspaceImpl bool) error { 108 | _, err := netlink.LinkByName(iface) 109 | if err != nil { 110 | if _, ok := err.(netlink.LinkNotFoundError); !ok { 111 | return err 112 | } 113 | } 114 | 115 | if _, ok := err.(netlink.LinkNotFoundError); ok { 116 | if wgUseUserspaceImpl { 117 | err = createLinkUsingUserspaceImpl(iface, wgUserspaceImplementationFallback) 118 | 119 | if err != nil { 120 | return err 121 | } 122 | 123 | } else { 124 | err = createLinkUsingKernalModule(iface) 125 | 126 | if err != nil { 127 | err = createLinkUsingUserspaceImpl(iface, wgUserspaceImplementationFallback) 128 | 129 | if err != nil { 130 | return err 131 | } 132 | } 133 | } 134 | 135 | // TODO: Can this be removed? 136 | link, err := netlink.LinkByName(iface) 137 | if err != nil { 138 | return err 139 | } 140 | if err := netlink.LinkSetUp(link); err != nil { 141 | return err 142 | } 143 | } 144 | 145 | link, err := netlink.LinkByName(iface) 146 | if err != nil { 147 | if _, ok := err.(netlink.LinkNotFoundError); !ok { 148 | return err 149 | } 150 | } 151 | 152 | addresses, err := netlink.AddrList(link, syscall.AF_INET) 153 | if err != nil { 154 | return nil 155 | } 156 | 157 | if len(addresses) != 0 { 158 | return nil 159 | } 160 | 161 | if err := netlink.AddrAdd(link, &netlink.Addr{ 162 | IPNet: &getIP("10.8.0.1/32")[0], 163 | }); err != nil { 164 | return fmt.Errorf("netlink addr add: %w", err) 165 | } 166 | 167 | if err := netlink.LinkSetUp(link); err != nil { 168 | return err 169 | } 170 | return nil 171 | } 172 | 173 | func (wg *Wireguard) syncWireguard(state agent.State, iface string, listenPort int) error { 174 | c, _ := wgctrl.New() 175 | cfg, err := CreateWireguardConfiguration(state, iface, listenPort) 176 | if err != nil { 177 | return err 178 | } 179 | 180 | err = c.ConfigureDevice(iface, cfg) 181 | if err != nil { 182 | return err 183 | } 184 | 185 | for _, peer := range cfg.Peers { 186 | if peer.Remove { 187 | wg.Logger.V(2).Info("Removed peer", "peerIP", peer.AllowedIPs[0].String(), "peerPublicKey", peer.PublicKey.String()) 188 | } else if peer.UpdateOnly { 189 | wg.Logger.V(2).Info("Updated peer", "peerIP", peer.AllowedIPs[0].String(), "peerPublicKey", peer.PublicKey.String()) 190 | } else { 191 | wg.Logger.V(2).Info("Added peer", "peerIP", peer.AllowedIPs[0].String(), "peerPublicKey", peer.PublicKey.String()) 192 | } 193 | } 194 | 195 | return nil 196 | } 197 | 198 | type Wireguard struct { 199 | Logger logr.Logger 200 | Iface string 201 | ListenPort int 202 | WgUserspaceImplementationFallback string 203 | WgUseUserspaceImpl bool 204 | } 205 | 206 | func (wg *Wireguard) Sync(state agent.State) error { 207 | wg.Logger.Info("syncing Wireguard") 208 | // create wg0 link 209 | err := SyncLink(state, wg.Iface, wg.WgUserspaceImplementationFallback, wg.WgUseUserspaceImpl) 210 | if err != nil { 211 | return err 212 | } 213 | 214 | // route all traffic coming from 10.8.0.0/24 via gateway 10.8.0.1 on wg0 215 | err = syncRoute(state, wg.Iface) 216 | 217 | if err != nil { 218 | return err 219 | } 220 | 221 | // set wg0 gateway as 10.8.0.1/32 222 | err = syncAddress(state, wg.Iface) 223 | if err != nil { 224 | return err 225 | } 226 | 227 | // sync wg configuration 228 | err = wg.syncWireguard(state, wg.Iface, wg.ListenPort) 229 | if err != nil { 230 | return err 231 | } 232 | 233 | return nil 234 | } 235 | 236 | func getIP(ip string) []net.IPNet { 237 | _, ipnet, _ := net.ParseCIDR(ip) 238 | 239 | return []net.IPNet{*ipnet} 240 | } 241 | 242 | func createPeersConfiguration(state agent.State, iface string) ([]wgtypes.PeerConfig, error) { 243 | var peersState = make(map[string]v1alpha1.WireguardPeer) 244 | for _, peer := range state.Peers { 245 | peersState[peer.Spec.PublicKey] = peer 246 | } 247 | 248 | c, err := wgctrl.New() 249 | 250 | if err != nil { 251 | return []wgtypes.PeerConfig{}, err 252 | } 253 | 254 | device, err := c.Device(iface) 255 | 256 | if err != nil { 257 | return []wgtypes.PeerConfig{}, err 258 | } 259 | 260 | var peerConfigurationByPublicKey = make(map[string]wgtypes.PeerConfig) 261 | var existingConfgiuredPeersByPublicKey = make(map[string]bool) 262 | 263 | for _, peer := range device.Peers { 264 | 265 | existingConfgiuredPeersByPublicKey[peer.PublicKey.String()] = true 266 | 267 | peerState, ok := peersState[peer.PublicKey.String()] 268 | if !ok { 269 | // delete peer 270 | p := wgtypes.PeerConfig{ 271 | Remove: true, 272 | AllowedIPs: peer.AllowedIPs, 273 | PublicKey: peer.PublicKey, 274 | } 275 | peerConfigurationByPublicKey[p.PublicKey.String()] = p 276 | 277 | } else { 278 | if peerState.Spec.Disabled || peerState.Spec.PublicKey == "" { 279 | // delete peer 280 | p := wgtypes.PeerConfig{ 281 | Remove: true, 282 | AllowedIPs: peer.AllowedIPs, 283 | PublicKey: peer.PublicKey, 284 | } 285 | peerConfigurationByPublicKey[p.PublicKey.String()] = p 286 | } else if peer.AllowedIPs[0].IP.String() != peerState.Spec.Address { 287 | // update peer 288 | p := wgtypes.PeerConfig{ 289 | UpdateOnly: true, 290 | AllowedIPs: getIP(peerState.Spec.Address + "/32"), 291 | PublicKey: peer.PublicKey, 292 | ReplaceAllowedIPs: true, 293 | } 294 | peerConfigurationByPublicKey[p.PublicKey.String()] = p 295 | } 296 | } 297 | } 298 | 299 | // add new peers 300 | for _, peer := range state.Peers { 301 | if peer.Spec.Disabled { 302 | continue 303 | } 304 | if peer.Spec.PublicKey == "" { 305 | continue 306 | } 307 | 308 | if peer.Spec.Address == "" { 309 | continue 310 | } 311 | key, err := wgtypes.ParseKey(peer.Spec.PublicKey) 312 | if err != nil { 313 | return []wgtypes.PeerConfig{}, err 314 | } 315 | 316 | _, ok := existingConfgiuredPeersByPublicKey[key.String()] 317 | if ok { 318 | continue 319 | } 320 | 321 | // create peer 322 | p := wgtypes.PeerConfig{ 323 | AllowedIPs: getIP(peer.Spec.Address + "/32"), 324 | PublicKey: key, 325 | } 326 | peerConfigurationByPublicKey[p.PublicKey.String()] = p 327 | } 328 | 329 | l := make([]wgtypes.PeerConfig, 0, len(peerConfigurationByPublicKey)) 330 | 331 | for _, value := range peerConfigurationByPublicKey { 332 | l = append(l, value) 333 | } 334 | 335 | return l, nil 336 | } 337 | 338 | func CreateWireguardConfiguration(state agent.State, iface string, listenPort int) (wgtypes.Config, error) { 339 | cfg := wgtypes.Config{} 340 | 341 | key, err := wgtypes.ParseKey(state.ServerPrivateKey) 342 | if err != nil { 343 | return wgtypes.Config{}, err 344 | } 345 | cfg.PrivateKey = &key 346 | 347 | // make sure we do not interrupt existing sessions 348 | cfg.ReplacePeers = false 349 | cfg.ListenPort = &listenPort 350 | 351 | peers, err := createPeersConfiguration(state, iface) 352 | if err != nil { 353 | return wgtypes.Config{}, err 354 | } 355 | 356 | cfg.Peers = peers 357 | 358 | return cfg, nil 359 | } 360 | -------------------------------------------------------------------------------- /readme/diagrams/main.drawio: -------------------------------------------------------------------------------- 1 | 7Vvfd6I4FP5rfKxHiCA+am27DzNne7a7O52nnkgiZhsIDaHq/PWbQBCQWJm2qNOpD1Nyyc/vu7n3JpfpgctwfcNhvPzKEKY9e4DWPTDr2bZleUD+UZJNLhm5Xi4IOEG6Uim4Iz+wFg60NCUIJ7WKgjEqSFwX+iyKsC9qMsg5W9WrLRitjxrDADcEdz6kTek3gsQyl3r2qJT/gUmwLEa23HH+Zg79x4CzNNLj9Wxwnf3y1yEs+tILTZYQsVVFBK564JIzJvKncH2JqcK2gC1vd73n7XbeHEeiTYNv43+f0vjm/k86eZisvibJ0z9PF5q8Z0hTjccPPVuxKRBSi4x7YLpgkbjTYr2mZ8wFXpt4gvOi+aA5UWu7fKlWmIVY8I2sUnTk6CZao2xXl1clPy7QsmWFGzDWQqh1Itj2XeIiHzQ0PwGT1YCpgVKmCRhl2IDpakkEvouhr96u5M6RsqUI5aAzS0FJKL1klPGsLUAQewtfyhPB2SOuvHF9D88X8s1evKu47qe4CfbpwBy9M5gUzjGdbndjgV7EImyA2sEeGpqg9uw5cN0uoNa9AKuu15bX1OttnSoV1qgrKuwGFY9eojYxTROBeYMXiYiog1+H0YC5FkFKgkgWfYmg7BhMFb5EmuGJfhEShNQwRrbr+nCA8IqZsrri8gI0ubNM28juijrQ3DUYSc+mi4yLJQtYBOlVKd3BsazzhbFYE/ofFmKj3TRMBavTjddE3Kvm/ZGji991b+p5tq4WNrqgGKlstEH2U5UiNFEevOROSq6JwiFrly9QreoV1ElkWMp9/EK9oQ43IA/wS/25ZlXgmEJBnuuTe3+ewWl5tn8fnp1T8jxsmGKEY8o2YYbBJDM1HAcp5OiI7nLh+dg3RiZzzxk6g2PZ2/HhkHAbNlbNr9uV+XUMQYxLlXNMYhjJ5yB7xvyZKB6mlEE0hxRGvvJ+NTovilq6Azmfah/HZHuxsM1sI3fuOm7rQ8Ab2R7uBEqWgW37mGy7B9iu0OM+pSynrfJUV4Y6+2qGxE8+2S/Ytwct6B8ck37LM/BvPCW/5VT8miMF2LGLdhOqsQGp7g7Kg8NIvacG1yOOq4k7zY5wuQYX9zn2mw51OfkvH6CPqowm1/MxIS662bEHhuugoxJg8gYfmgB7x8qcmgDTDVLuPRVYmbkt8CqdcHEeafjlmKGq9827OIL3PT2vO97DcKdxXF7HZ+pmh+6ZuVn7A7rZ8Xm5Wfu97/zPF+LzdLMF3r8PAWfmZu1mcvDTzb6G1zNzs83D7F84SCnkVM05v3WU2MuRY+lpBxRGKPFhjBss/WrZoM5uLNpk9ixTgNBddqh5nWy6nzx4i6Xgu1jpGat7q4jxENLm7g5ZRATjJAryerdcrWSJU5VO7Nmy3uCGw0UE995znfr6C1vIwSPT9dfYHQG4e/3V2VU3cA6aC9uUadxek72/MrkNGo6Zgqrmn/pFNsqcgZK48819WVEVv1fflc2y0okzV51nHnXTW0Yyf71Hwzyn3kOeUNONdnRnO4s3qFOLY16yVA4HzNKQTnzBqi7ii9rUtywhgjDlKuZMCBYafIhgO+aApYKSSO7s4kuyF9yH+prLu55cX72gHa8MIQ58/TQe1sgZDRvb37T7O4sWhqOTbv5+Nf1sndXmT+T2FTs1WhgELTqctfZaGonTpq1b3ETAJM7324KslVJMY8yJHF5FfjM5BokTfFuKqnrQCBS3X1EqBhBMllstk2GDL8OPvzMNA1JAwuzTz+LvjISBXCIlc/kv9BU0D4hwrMzLRr2AsbQYDz4lcqx+8hycoXGwbeeQcXCGxzQOLRJjvwb7RI0QYSH5Zyk6W/rdGv3bRHmVf9O3nFbf6UoDWjjzT3/RNlhs7RsGLX3DcE8C4I0RpNSy/rjyG41qemkVx88DEaWp31pHYPfL13zBrwhNZbH81D6vXv5/BnD1Pw== -------------------------------------------------------------------------------- /readme/main.drawio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | z
k8s cluster
k8s cluster
deployment: wireguard
deployment: wireguard
service;loadbalancer: wireguard-service
service;loadbalancer...
service: wireguard-metrics
service: wiregua...
pods
pods
pods
pods
Regularly deployed app landscape
Regularly deployed app landscape
monitoring: Prometheus + Grafna
monitoring: Prometheus + Grafna
Text is not SVG - cannot display
-------------------------------------------------------------------------------- /readme/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jodevsa/wireguard-operator/1ac0303136c4c50dd1329d149b319495194d823a/readme/main.png -------------------------------------------------------------------------------- /release.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: wireguard-system 7 | --- 8 | apiVersion: apiextensions.k8s.io/v1 9 | kind: CustomResourceDefinition 10 | metadata: 11 | annotations: 12 | controller-gen.kubebuilder.io/version: v0.8.0 13 | creationTimestamp: null 14 | name: wireguardpeers.vpn.wireguard-operator.io 15 | spec: 16 | group: vpn.wireguard-operator.io 17 | names: 18 | kind: WireguardPeer 19 | listKind: WireguardPeerList 20 | plural: wireguardpeers 21 | singular: wireguardpeer 22 | scope: Namespaced 23 | versions: 24 | - name: v1alpha1 25 | schema: 26 | openAPIV3Schema: 27 | description: WireguardPeer is the Schema for the wireguardpeers API 28 | properties: 29 | apiVersion: 30 | description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 31 | type: string 32 | kind: 33 | description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 34 | type: string 35 | metadata: 36 | type: object 37 | spec: 38 | description: The desired state of the peer. 39 | properties: 40 | PrivateKeyRef: 41 | description: The private key of the peer 42 | properties: 43 | secretKeyRef: 44 | description: SecretKeySelector selects a key of a Secret. 45 | properties: 46 | key: 47 | description: The key of the secret to select from. Must be a valid secret key. 48 | type: string 49 | name: 50 | description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' 51 | type: string 52 | optional: 53 | description: Specify whether the Secret or its key must be defined 54 | type: boolean 55 | required: 56 | - key 57 | type: object 58 | required: 59 | - secretKeyRef 60 | type: object 61 | address: 62 | description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster Important: Run "make" to regenerate code after modifying this file The address of the peer.' 63 | type: string 64 | disabled: 65 | description: Set to true to temporarily disable the peer. 66 | type: boolean 67 | dns: 68 | description: The DNS configuration for the peer. 69 | type: string 70 | downloadSpeed: 71 | properties: 72 | config: 73 | type: integer 74 | unit: 75 | enum: 76 | - mbps 77 | - kbps 78 | type: string 79 | type: object 80 | egressNetworkPolicies: 81 | description: Egress network policies for the peer. 82 | items: 83 | properties: 84 | action: 85 | description: Specifies the action to take when outgoing traffic from a Wireguard peer matches the policy. This could be 'Accept' or 'Reject'. 86 | enum: 87 | - ACCEPT 88 | - REJECT 89 | - Accept 90 | - Reject 91 | type: string 92 | protocol: 93 | description: Specifies the protocol to match for this policy. This could be TCP, UDP, or ICMP. 94 | enum: 95 | - TCP 96 | - UDP 97 | - ICMP 98 | type: string 99 | to: 100 | description: A struct that specifies the destination address and port for the traffic. This could include IP addresses or hostnames, as well as specific port numbers or port ranges. 101 | properties: 102 | ip: 103 | description: A string field that specifies the destination IP address for traffic that matches the policy. 104 | type: string 105 | port: 106 | description: An integer field that specifies the destination port number for traffic that matches the policy. 107 | format: int32 108 | type: integer 109 | type: object 110 | type: object 111 | type: array 112 | publicKey: 113 | description: The key used by the peer to authenticate with the wg server. 114 | type: string 115 | uploadSpeed: 116 | properties: 117 | config: 118 | type: integer 119 | unit: 120 | enum: 121 | - mbps 122 | - kbps 123 | type: string 124 | type: object 125 | wireguardRef: 126 | description: The name of the Wireguard instance in k8s that the peer belongs to. The wg instance should be in the same namespace as the peer. 127 | minLength: 1 128 | type: string 129 | required: 130 | - wireguardRef 131 | type: object 132 | status: 133 | description: A field that defines the observed state of the Wireguard peer. This includes fields like the current configuration and status of the peer. 134 | properties: 135 | config: 136 | description: 'INSERT ADDITIONAL STATUS FIELD - define observed state of cluster Important: Run "make" to regenerate code after modifying this file A string field that contains the current configuration for the Wireguard peer.' 137 | type: string 138 | message: 139 | description: A string field that provides additional information about the status of the Wireguard peer. This could include error messages or other information that helps to diagnose issues with the peer. 140 | type: string 141 | status: 142 | description: A string field that represents the current status of the Wireguard peer. This could include values like ready, pending, or error. 143 | type: string 144 | type: object 145 | type: object 146 | served: true 147 | storage: true 148 | subresources: 149 | status: {} 150 | status: 151 | acceptedNames: 152 | kind: "" 153 | plural: "" 154 | conditions: [] 155 | storedVersions: [] 156 | --- 157 | apiVersion: apiextensions.k8s.io/v1 158 | kind: CustomResourceDefinition 159 | metadata: 160 | annotations: 161 | controller-gen.kubebuilder.io/version: v0.8.0 162 | creationTimestamp: null 163 | name: wireguards.vpn.wireguard-operator.io 164 | spec: 165 | group: vpn.wireguard-operator.io 166 | names: 167 | kind: Wireguard 168 | listKind: WireguardList 169 | plural: wireguards 170 | singular: wireguard 171 | scope: Namespaced 172 | versions: 173 | - name: v1alpha1 174 | schema: 175 | openAPIV3Schema: 176 | description: Wireguard is the Schema for the wireguards API 177 | properties: 178 | apiVersion: 179 | description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 180 | type: string 181 | kind: 182 | description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 183 | type: string 184 | metadata: 185 | type: object 186 | spec: 187 | description: WireguardSpec defines the desired state of Wireguard 188 | properties: 189 | address: 190 | description: A string field that specifies the address for the Wireguard VPN server. This is the public IP address or hostname that peers will use to connect to the VPN. 191 | type: string 192 | dns: 193 | description: A string field that specifies the DNS server(s) to be used by the peers. 194 | type: string 195 | enableIpForwardOnPodInit: 196 | description: A boolean field that specifies whether IP forwarding should be enabled on the Wireguard VPN pod at startup. This can be useful to enable if the peers are having problems with sending traffic to the internet. 197 | type: boolean 198 | mtu: 199 | description: A string field that specifies the maximum transmission unit (MTU) size for Wireguard packets for all peers. 200 | type: string 201 | serviceAnnotations: 202 | additionalProperties: 203 | type: string 204 | description: A map of key value strings for service annotations 205 | type: object 206 | serviceType: 207 | description: A field that specifies the type of Kubernetes service that should be used for the Wireguard VPN. This could be NodePort or LoadBalancer, depending on the needs of the deployment. 208 | type: string 209 | type: object 210 | status: 211 | description: WireguardStatus defines the observed state of Wireguard 212 | properties: 213 | address: 214 | description: A string field that specifies the address for the Wireguard VPN server that is currently being used. 215 | type: string 216 | dns: 217 | type: string 218 | message: 219 | description: A string field that provides additional information about the status of Wireguard. This could include error messages or other information that helps to diagnose issues with the wg instance. 220 | type: string 221 | port: 222 | description: A string field that specifies the port for the Wireguard VPN server that is currently being used. 223 | type: string 224 | status: 225 | description: A string field that represents the current status of Wireguard. This could include values like ready, pending, or error. 226 | type: string 227 | type: object 228 | type: object 229 | served: true 230 | storage: true 231 | subresources: 232 | status: {} 233 | status: 234 | acceptedNames: 235 | kind: "" 236 | plural: "" 237 | conditions: [] 238 | storedVersions: [] 239 | --- 240 | apiVersion: v1 241 | kind: ServiceAccount 242 | metadata: 243 | name: wireguard-controller-manager 244 | namespace: wireguard-system 245 | --- 246 | apiVersion: rbac.authorization.k8s.io/v1 247 | kind: Role 248 | metadata: 249 | name: wireguard-leader-election-role 250 | namespace: wireguard-system 251 | rules: 252 | - apiGroups: 253 | - "" 254 | resources: 255 | - configmaps 256 | verbs: 257 | - get 258 | - list 259 | - watch 260 | - create 261 | - update 262 | - patch 263 | - delete 264 | - apiGroups: 265 | - coordination.k8s.io 266 | resources: 267 | - leases 268 | verbs: 269 | - get 270 | - list 271 | - watch 272 | - create 273 | - update 274 | - patch 275 | - delete 276 | - apiGroups: 277 | - "" 278 | resources: 279 | - events 280 | verbs: 281 | - create 282 | - patch 283 | --- 284 | apiVersion: rbac.authorization.k8s.io/v1 285 | kind: ClusterRole 286 | metadata: 287 | creationTimestamp: null 288 | name: wireguard-manager-role 289 | rules: 290 | - apiGroups: 291 | - "" 292 | resources: 293 | - configmaps 294 | verbs: 295 | - create 296 | - delete 297 | - get 298 | - list 299 | - patch 300 | - update 301 | - watch 302 | - apiGroups: 303 | - "" 304 | resources: 305 | - nodes 306 | verbs: 307 | - list 308 | - watch 309 | - apiGroups: 310 | - "" 311 | resources: 312 | - pods 313 | verbs: 314 | - create 315 | - delete 316 | - get 317 | - list 318 | - patch 319 | - update 320 | - watch 321 | - apiGroups: 322 | - "" 323 | resources: 324 | - secrets 325 | verbs: 326 | - create 327 | - delete 328 | - get 329 | - list 330 | - patch 331 | - update 332 | - watch 333 | - apiGroups: 334 | - "" 335 | resources: 336 | - services 337 | verbs: 338 | - create 339 | - delete 340 | - get 341 | - list 342 | - patch 343 | - update 344 | - watch 345 | - apiGroups: 346 | - apps 347 | resources: 348 | - deployments 349 | verbs: 350 | - create 351 | - delete 352 | - get 353 | - list 354 | - patch 355 | - update 356 | - watch 357 | - apiGroups: 358 | - apps 359 | resources: 360 | - pods 361 | verbs: 362 | - create 363 | - delete 364 | - get 365 | - list 366 | - patch 367 | - update 368 | - watch 369 | - apiGroups: 370 | - vpn.wireguard-operator.io 371 | resources: 372 | - wireguardpeers 373 | verbs: 374 | - create 375 | - delete 376 | - get 377 | - list 378 | - patch 379 | - update 380 | - watch 381 | - apiGroups: 382 | - vpn.wireguard-operator.io 383 | resources: 384 | - wireguardpeers/finalizers 385 | verbs: 386 | - update 387 | - apiGroups: 388 | - vpn.wireguard-operator.io 389 | resources: 390 | - wireguardpeers/status 391 | verbs: 392 | - get 393 | - patch 394 | - update 395 | - apiGroups: 396 | - vpn.wireguard-operator.io 397 | resources: 398 | - wireguards 399 | verbs: 400 | - create 401 | - delete 402 | - get 403 | - list 404 | - patch 405 | - update 406 | - watch 407 | - apiGroups: 408 | - vpn.wireguard-operator.io 409 | resources: 410 | - wireguards/finalizers 411 | verbs: 412 | - update 413 | - apiGroups: 414 | - vpn.wireguard-operator.io 415 | resources: 416 | - wireguards/status 417 | verbs: 418 | - get 419 | - patch 420 | - update 421 | --- 422 | apiVersion: rbac.authorization.k8s.io/v1 423 | kind: ClusterRole 424 | metadata: 425 | name: wireguard-metrics-reader 426 | rules: 427 | - nonResourceURLs: 428 | - /metrics 429 | verbs: 430 | - get 431 | --- 432 | apiVersion: rbac.authorization.k8s.io/v1 433 | kind: ClusterRole 434 | metadata: 435 | name: wireguard-proxy-role 436 | rules: 437 | - apiGroups: 438 | - authentication.k8s.io 439 | resources: 440 | - tokenreviews 441 | verbs: 442 | - create 443 | - apiGroups: 444 | - authorization.k8s.io 445 | resources: 446 | - subjectaccessreviews 447 | verbs: 448 | - create 449 | --- 450 | apiVersion: rbac.authorization.k8s.io/v1 451 | kind: RoleBinding 452 | metadata: 453 | name: wireguard-leader-election-rolebinding 454 | namespace: wireguard-system 455 | roleRef: 456 | apiGroup: rbac.authorization.k8s.io 457 | kind: Role 458 | name: wireguard-leader-election-role 459 | subjects: 460 | - kind: ServiceAccount 461 | name: wireguard-controller-manager 462 | namespace: wireguard-system 463 | --- 464 | apiVersion: rbac.authorization.k8s.io/v1 465 | kind: ClusterRoleBinding 466 | metadata: 467 | name: wireguard-manager-rolebinding 468 | roleRef: 469 | apiGroup: rbac.authorization.k8s.io 470 | kind: ClusterRole 471 | name: wireguard-manager-role 472 | subjects: 473 | - kind: ServiceAccount 474 | name: wireguard-controller-manager 475 | namespace: wireguard-system 476 | --- 477 | apiVersion: rbac.authorization.k8s.io/v1 478 | kind: ClusterRoleBinding 479 | metadata: 480 | name: wireguard-proxy-rolebinding 481 | roleRef: 482 | apiGroup: rbac.authorization.k8s.io 483 | kind: ClusterRole 484 | name: wireguard-proxy-role 485 | subjects: 486 | - kind: ServiceAccount 487 | name: wireguard-controller-manager 488 | namespace: wireguard-system 489 | --- 490 | apiVersion: v1 491 | data: 492 | controller_manager_config.yaml: | 493 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 494 | kind: ControllerManagerConfig 495 | health: 496 | healthProbeBindAddress: :8081 497 | metrics: 498 | bindAddress: 127.0.0.1:8080 499 | webhook: 500 | port: 9443 501 | leaderElection: 502 | leaderElect: true 503 | resourceName: a6d3bffc.wireguard-operator.io 504 | kind: ConfigMap 505 | metadata: 506 | name: wireguard-manager-config 507 | namespace: wireguard-system 508 | --- 509 | apiVersion: v1 510 | kind: Service 511 | metadata: 512 | labels: 513 | control-plane: controller-manager 514 | name: wireguard-controller-manager-metrics-service 515 | namespace: wireguard-system 516 | spec: 517 | ports: 518 | - name: https 519 | port: 8443 520 | protocol: TCP 521 | targetPort: https 522 | selector: 523 | control-plane: controller-manager 524 | --- 525 | apiVersion: apps/v1 526 | kind: Deployment 527 | metadata: 528 | labels: 529 | control-plane: controller-manager 530 | name: wireguard-controller-manager 531 | namespace: wireguard-system 532 | spec: 533 | replicas: 1 534 | selector: 535 | matchLabels: 536 | control-plane: controller-manager 537 | template: 538 | metadata: 539 | annotations: 540 | kubectl.kubernetes.io/default-container: manager 541 | labels: 542 | control-plane: controller-manager 543 | spec: 544 | containers: 545 | - args: 546 | - --secure-listen-address=0.0.0.0:8443 547 | - --upstream=http://127.0.0.1:8080/ 548 | - --logtostderr=true 549 | - --v=0 550 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 551 | name: kube-rbac-proxy 552 | ports: 553 | - containerPort: 8443 554 | name: https 555 | protocol: TCP 556 | resources: 557 | limits: 558 | cpu: 500m 559 | memory: 128Mi 560 | requests: 561 | cpu: 5m 562 | memory: 64Mi 563 | - args: 564 | - --health-probe-bind-address=:8081 565 | - --metrics-bind-address=127.0.0.1:8080 566 | - --leader-elect 567 | - --agent-image=ghcr.io/jodevsa/wireguard-operator/agent:latest 568 | command: 569 | - /manager 570 | image: ghcr.io/jodevsa/wireguard-operator/manager:latest 571 | livenessProbe: 572 | httpGet: 573 | path: /healthz 574 | port: 8081 575 | initialDelaySeconds: 15 576 | periodSeconds: 20 577 | name: manager 578 | readinessProbe: 579 | httpGet: 580 | path: /readyz 581 | port: 8081 582 | initialDelaySeconds: 5 583 | periodSeconds: 10 584 | resources: 585 | limits: 586 | cpu: 500m 587 | memory: 128Mi 588 | requests: 589 | cpu: 10m 590 | memory: 64Mi 591 | securityContext: 592 | allowPrivilegeEscalation: false 593 | securityContext: 594 | runAsNonRoot: true 595 | serviceAccountName: wireguard-controller-manager 596 | terminationGracePeriodSeconds: 10 597 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | --------------------------------------------------------------------------------