├── docs └── header.png ├── charts ├── defguard │ ├── charts │ │ ├── postgresql-18.0.8.tgz │ │ ├── defguard-gateway-0.5.0.tgz │ │ └── defguard-proxy-0.8.0.tgz │ ├── templates │ │ ├── serviceaccount.yaml │ │ ├── openid-secret.yaml │ │ ├── postgresql-secret.yaml │ │ ├── defguard-service.yaml │ │ ├── grpc-service.yaml │ │ ├── defguard-secret.yaml │ │ ├── defguard-config.yaml │ │ ├── NOTES.txt │ │ ├── ingress-web.yaml │ │ ├── ingress-grpc.yaml │ │ ├── _helpers.tpl │ │ └── defguard-deployment.yaml │ ├── .helmignore │ ├── Chart.lock │ ├── README.md │ ├── Chart.yaml │ └── values.yaml ├── defguard-gateway │ ├── Chart.yaml │ ├── templates │ │ ├── serviceaccount.yaml │ │ ├── config.yaml │ │ ├── wireguard-service.yaml │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ └── deployment.yaml │ ├── .helmignore │ └── values.yaml └── defguard-proxy │ ├── Chart.yaml │ ├── templates │ ├── serviceaccount.yaml │ ├── config.yaml │ ├── service.yaml │ ├── grpc-service.yaml │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── ingress-web.yaml │ ├── ingress-grpc.yaml │ └── deployment.yaml │ ├── .helmignore │ └── values.yaml ├── terraform ├── modules │ ├── core │ │ ├── outputs.tf │ │ ├── main.tf │ │ ├── variables.tf │ │ └── setup.sh │ ├── gateway │ │ ├── outputs.tf │ │ ├── main.tf │ │ ├── variables.tf │ │ └── setup.sh │ └── proxy │ │ ├── outputs.tf │ │ ├── main.tf │ │ ├── variables.tf │ │ └── setup.sh └── examples │ └── basic │ └── main.tf.example ├── .gitignore ├── gateway ├── .env.template └── docker-compose.yaml ├── LICENSE ├── flake.nix ├── README.md ├── .github └── workflows │ ├── release.yml │ ├── lint_charts.yml │ ├── test.yml │ └── ami.yml ├── cloudformation ├── ami │ ├── defguard-install.sh │ └── defguard.pkr.hcl └── template.yaml ├── docker-compose ├── .env.template ├── docker-compose.yaml └── setup.sh ├── flake.lock └── index.yaml /docs/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DefGuard/deployment/HEAD/docs/header.png -------------------------------------------------------------------------------- /charts/defguard/charts/postgresql-18.0.8.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DefGuard/deployment/HEAD/charts/defguard/charts/postgresql-18.0.8.tgz -------------------------------------------------------------------------------- /charts/defguard/charts/defguard-gateway-0.5.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DefGuard/deployment/HEAD/charts/defguard/charts/defguard-gateway-0.5.0.tgz -------------------------------------------------------------------------------- /charts/defguard/charts/defguard-proxy-0.8.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DefGuard/deployment/HEAD/charts/defguard/charts/defguard-proxy-0.8.0.tgz -------------------------------------------------------------------------------- /terraform/modules/core/outputs.tf: -------------------------------------------------------------------------------- 1 | output "instance_id" { 2 | description = "ID of Defguard Core instance" 3 | value = aws_instance.defguard_core.id 4 | } 5 | -------------------------------------------------------------------------------- /terraform/modules/gateway/outputs.tf: -------------------------------------------------------------------------------- 1 | output "instance_id" { 2 | description = "ID of Defguard Gateway instance" 3 | value = aws_instance.defguard_gateway.id 4 | } 5 | -------------------------------------------------------------------------------- /charts/defguard-gateway/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: defguard-gateway 3 | description: Defguard gateway is a public-facing VPN endpoint. 4 | 5 | type: application 6 | version: 0.5.0 7 | appVersion: 1.6.0 8 | -------------------------------------------------------------------------------- /charts/defguard-proxy/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: defguard-proxy 3 | description: Defguard proxy is a public-facing proxy for core Defguard service 4 | 5 | type: application 6 | version: 0.8.0 7 | appVersion: 1.6.0 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | docker-compose/.env 2 | docker-compose/.volumes 3 | .idea 4 | terraform/**/terraform.tfstate 5 | terraform/**/terraform.tfstate.backup 6 | terraform/**/.* 7 | terraform/**/*.tfvars 8 | .direnv/ 9 | .envrc 10 | -------------------------------------------------------------------------------- /terraform/modules/proxy/outputs.tf: -------------------------------------------------------------------------------- 1 | output "proxy_private_address" { 2 | description = "Private IP address of Defguard Proxy instance" 3 | value = aws_instance.defguard_proxy.private_ip 4 | } 5 | 6 | output "instance_id" { 7 | description = "ID of Defguard Proxy instance" 8 | value = aws_instance.defguard_proxy.id 9 | } 10 | -------------------------------------------------------------------------------- /charts/defguard/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "defguard.serviceAccountName" . }} 6 | labels: 7 | {{- include "defguard.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /charts/defguard-proxy/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "defguard-proxy.serviceAccountName" . }} 6 | labels: 7 | {{- include "defguard-proxy.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /charts/defguard-gateway/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "defguard-gateway.serviceAccountName" . }} 6 | labels: 7 | {{- include "defguard-gateway.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /charts/defguard-proxy/templates/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: {{ include "defguard-proxy.fullname" . }}-config 5 | labels: 6 | {{- include "defguard-proxy.labels" . | nindent 4 }} 7 | data: 8 | DEFGUARD_PROXY_HTTP_PORT: {{ .Values.service.web.port | quote }} 9 | DEFGUARD_PROXY_GRPC_PORT: {{ .Values.service.grpc.port | quote }} 10 | DEFGUARD_PROXY_URL: {{ .Values.publicUrl | quote }} 11 | -------------------------------------------------------------------------------- /charts/defguard-gateway/templates/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: {{ include "defguard-gateway.fullname" . }}-config 5 | labels: 6 | {{- include "defguard-gateway.labels" . | nindent 4 }} 7 | data: 8 | DEFGUARD_USERSPACE: {{ .Values.userspace | quote }} 9 | DEFGUARD_GRPC_URL: {{ .Values.grpcUrl | quote }} 10 | DEFGUARD_STATS_PERIOD: {{ .Values.statsPeriod | quote }} 11 | DEFGUARD_LOG_LEVEL: {{ .Values.logLevel | quote }} 12 | -------------------------------------------------------------------------------- /charts/defguard/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /charts/defguard-proxy/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /charts/defguard-gateway/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /charts/defguard/Chart.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: postgresql 3 | repository: https://charts.bitnami.com/bitnami 4 | version: 18.0.8 5 | - name: defguard-proxy 6 | repository: https://defguard.github.io/deployment 7 | version: 0.8.0 8 | - name: defguard-gateway 9 | repository: https://defguard.github.io/deployment 10 | version: 0.5.0 11 | digest: sha256:42c45b4850f30ab0668102e140918d4db148b8a4b062645af5714b489ab95cba 12 | generated: "2025-12-09T13:38:47.693709029+01:00" 13 | -------------------------------------------------------------------------------- /gateway/.env.template: -------------------------------------------------------------------------------- 1 | # Use userspace wireguard implementation, useful on systems without native wireguard support 2 | # Set to 0/1 3 | DEFGUARD_USERSPACE=0 4 | # Defguard GRPC URL, e.g.: defguard-grpc.mycompany.com 5 | DEFGUARD_GRPC_URL= 6 | # Token from Defguard app to secure gRPC connection, available on network page. 7 | DEFGUARD_TOKEN= 8 | # Defines how often (in seconds) should interface statistics be sent to Defguard server 9 | DEFGUARD_STATS_PERIOD=30 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 teonite ventures sp. z o.o. (teonite) 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Simple devshell flake"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | }; 8 | 9 | outputs = { 10 | nixpkgs, 11 | flake-utils, 12 | ... 13 | }: 14 | flake-utils.lib.eachDefaultSystem (system: let 15 | pkgs = import nixpkgs { 16 | inherit system; 17 | }; 18 | in { 19 | devShells.default = pkgs.mkShell { 20 | packages = with pkgs; [ 21 | kubectl 22 | kubernetes-helm 23 | kubeconform 24 | ]; 25 | }; 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /charts/defguard/templates/openid-secret.yaml: -------------------------------------------------------------------------------- 1 | {{ if not .Values.existingOpenIdSecret }} 2 | {{- $openIdKey := (genPrivateKey "rsa") | b64enc | quote }} 3 | {{- $secret := (lookup "v1" "Secret" .Release.Namespace (include "defguard.openidSecretName" .)) }} 4 | {{- if $secret }} 5 | {{- $openIdKey = index $secret.data "openid-key" }} 6 | {{- end }} 7 | apiVersion: v1 8 | kind: Secret 9 | metadata: 10 | name: {{ include "defguard.openidSecretName" . }} 11 | labels: 12 | {{- include "defguard.labels" . | nindent 4 }} 13 | annotations: 14 | "helm.sh/resource-policy": keep 15 | type: Opaque 16 | data: 17 | openid-key: {{ $openIdKey }} 18 | {{- end }} 19 | -------------------------------------------------------------------------------- /terraform/modules/proxy/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_instance" "defguard_proxy" { 2 | ami = var.ami 3 | instance_type = var.instance_type 4 | 5 | user_data = templatefile("${path.module}/setup.sh", { 6 | proxy_url = var.proxy_url 7 | grpc_port = var.grpc_port 8 | arch = var.arch 9 | package_version = var.package_version 10 | http_port = var.http_port 11 | log_level = var.log_level 12 | }) 13 | user_data_replace_on_change = true 14 | 15 | primary_network_interface { 16 | network_interface_id = var.network_interface_id 17 | } 18 | 19 | tags = { 20 | Name = "defguard-proxy-instance" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | defguard 3 |

4 | 5 | # Defguard deployment 6 | 7 | Check our [documentation](https://docs.defguard.net/deployment-strategies/setting-up-your-instance) for deployment 8 | instructions. 9 | 10 | ## Community and Support 11 | 12 | Find us on Matrix: [#defguard:teonite.com](https://matrix.to/#/#defguard:teonite.com) 13 | 14 | ## Contribution 15 | 16 | Please review the [Contributing guide](https://docs.defguard.net/for-developers/contributing) for information on how to get started contributing to the project. You might also find our [environment setup guide](https://docs.defguard.net/for-developers/dev-env-setup) handy. 17 | -------------------------------------------------------------------------------- /charts/defguard/templates/postgresql-secret.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.postgresql.enabled }} 2 | {{- $secretName := .Values.postgresql.auth.existingSecret | default (printf "%s-postgresql" .Release.Name) }} 3 | {{- $existingSecret := (lookup "v1" "Secret" .Release.Namespace $secretName) }} 4 | 5 | {{- if not $existingSecret }} 6 | apiVersion: v1 7 | kind: Secret 8 | metadata: 9 | name: {{ $secretName }} 10 | labels: 11 | {{- include "defguard.labels" . | nindent 4 }} 12 | annotations: 13 | "helm.sh/resource-policy": keep 14 | type: Opaque 15 | data: 16 | password: {{ randAlpha 16 | b64enc | quote }} 17 | postgres-password: {{ randAlpha 16 | b64enc | quote }} 18 | {{- end }} 19 | {{- end }} 20 | -------------------------------------------------------------------------------- /charts/defguard/templates/defguard-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | annotations: 5 | {{- with .Values.service.web.annotations }} 6 | {{- toYaml . | nindent 4 }} 7 | {{- end }} 8 | name: {{ include "defguard.fullname" . }}-web 9 | labels: 10 | {{- include "defguard.labels" . | nindent 4 }} 11 | {{- with .Values.service.web.labels }} 12 | {{- toYaml . | nindent 4 }} 13 | {{- end }} 14 | spec: 15 | type: {{ .Values.service.web.type }} 16 | ports: 17 | - port: {{ .Values.service.web.port }} 18 | targetPort: http 19 | protocol: TCP 20 | name: http 21 | selector: 22 | {{- include "defguard.selectorLabels" . | nindent 4 }} 23 | -------------------------------------------------------------------------------- /charts/defguard/templates/grpc-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | annotations: 5 | {{- with .Values.service.grpc.annotations }} 6 | {{- toYaml . | nindent 4 }} 7 | {{- end }} 8 | name: {{ include "defguard.fullname" . }}-grpc 9 | labels: 10 | {{- include "defguard.labels" . | nindent 4 }} 11 | {{- with .Values.service.grpc.labels }} 12 | {{- toYaml . | nindent 4 }} 13 | {{- end }} 14 | spec: 15 | type: {{ .Values.service.grpc.type }} 16 | ports: 17 | - port: {{ .Values.service.grpc.port }} 18 | targetPort: grpc 19 | protocol: TCP 20 | name: grpc 21 | selector: 22 | {{- include "defguard.selectorLabels" . | nindent 4 }} 23 | -------------------------------------------------------------------------------- /charts/defguard-proxy/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | annotations: 5 | {{- with .Values.service.web.annotations }} 6 | {{- toYaml . | nindent 4 }} 7 | {{- end }} 8 | name: {{ include "defguard-proxy.fullname" . }}-web 9 | labels: 10 | {{- include "defguard-proxy.labels" . | nindent 4 }} 11 | {{- with .Values.service.web.labels }} 12 | {{- toYaml . | nindent 4 }} 13 | {{- end }} 14 | spec: 15 | type: {{ .Values.service.web.type }} 16 | ports: 17 | - port: {{ .Values.service.web.port }} 18 | targetPort: http 19 | protocol: TCP 20 | name: http 21 | selector: 22 | {{- include "defguard-proxy.selectorLabels" . | nindent 4 }} 23 | -------------------------------------------------------------------------------- /charts/defguard-proxy/templates/grpc-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | annotations: 5 | {{- with .Values.service.grpc.annotations }} 6 | {{- toYaml . | nindent 4 }} 7 | {{- end }} 8 | name: {{ include "defguard-proxy.fullname" . }}-grpc 9 | labels: 10 | {{- include "defguard-proxy.labels" . | nindent 4 }} 11 | {{- with .Values.service.grpc.labels }} 12 | {{- toYaml . | nindent 4 }} 13 | {{- end }} 14 | spec: 15 | type: {{ .Values.service.grpc.type }} 16 | ports: 17 | - port: {{ .Values.service.grpc.port }} 18 | targetPort: grpc 19 | protocol: TCP 20 | name: grpc 21 | selector: 22 | {{- include "defguard-proxy.selectorLabels" . | nindent 4 }} 23 | -------------------------------------------------------------------------------- /charts/defguard/README.md: -------------------------------------------------------------------------------- 1 |

2 | defguard 3 |

4 | 5 | # Defguard Helm chart 6 | 7 | This Helm chart can be used to deploy the whole [Defguard](https://defguard.net/) stack: 8 | 9 | - Defguard Core service 10 | - Postgres database 11 | - Defguard Gateway service 12 | - public Defguard Proxy service 13 | 14 | Check our [documentation](https://docs.defguard.net/deployment-strategies/kubernetes) for deployment 15 | instructions. 16 | 17 | ## ⚠️ Important: Postgres image tags 18 | 19 | Due to changes in Bitnami policy the Postgres subchart now uses the `latest` tag by default. 20 | Remember to set a specific tag in your `values.yaml` to avoid issues with major version upgrades in production environments. 21 | -------------------------------------------------------------------------------- /charts/defguard-gateway/templates/wireguard-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | annotations: 5 | {{- with .Values.service.wireguard.annotations }} 6 | {{- toYaml . | nindent 4 }} 7 | {{- end }} 8 | name: {{ include "defguard-gateway.fullname" . }}-wireguard 9 | labels: 10 | {{- include "defguard-gateway.labels" . | nindent 4 }} 11 | {{- with .Values.service.wireguard.labels }} 12 | {{- toYaml . | nindent 4 }} 13 | {{- end }} 14 | spec: 15 | type: {{ .Values.service.wireguard.type }} 16 | ports: 17 | - port: {{ .Values.service.wireguard.port }} 18 | targetPort: wireguard 19 | protocol: UDP 20 | name: wireguard 21 | selector: 22 | {{- include "defguard-gateway.selectorLabels" . | nindent 4 }} 23 | -------------------------------------------------------------------------------- /charts/defguard/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: defguard 3 | description: Defguard is an open-source enterprise WireGuard VPN with MFA and SSO 4 | 5 | type: application 6 | version: 0.14.0 7 | appVersion: 1.6.0 8 | 9 | dependencies: 10 | - name: postgresql 11 | condition: postgresql.enabled 12 | version: 18.0.8 13 | repository: https://charts.bitnami.com/bitnami 14 | - name: defguard-proxy 15 | condition: defguard-proxy.enabled 16 | version: 0.8.0 17 | repository: https://defguard.github.io/deployment 18 | # repository: "file://../defguard-proxy" 19 | - name: defguard-gateway 20 | condition: defguard-gateway.enabled 21 | version: 0.5.0 22 | repository: https://defguard.github.io/deployment 23 | # repository: "file://../defguard-gateway" 24 | -------------------------------------------------------------------------------- /gateway/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | gateway: 4 | image: ghcr.io/defguard/gateway:latest 5 | restart: unless-stopped 6 | network_mode: "host" 7 | environment: 8 | # load variables from .env file 9 | - DEFGUARD_GRPC_URL 10 | - DEFGUARD_TOKEN 11 | - DEFGUARD_STATS_PERIOD 12 | - RUST_LOG=debug 13 | # SSL setup guide: https://docs.defguard.net/deployment-strategies/grpc-ssl-communication#custom-ssl-ca-and-certificates 14 | # - DEFGUARD_GRPC_CA: /ssl/defguard-ca.pem 15 | ports: 16 | # wireguard endpoint 17 | - "50051:50051/udp" 18 | #volumes: 19 | # SSL setup guide: https://docs.defguard.net/deployment-strategies/grpc-ssl-communication#custom-ssl-ca-and-certificates 20 | #- ./.volumes/ssl:/ssl 21 | cap_add: 22 | - NET_ADMIN 23 | -------------------------------------------------------------------------------- /terraform/modules/gateway/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_instance" "defguard_gateway" { 2 | ami = var.ami 3 | instance_type = var.instance_type 4 | 5 | user_data = templatefile("${path.module}/setup.sh", { 6 | gateway_port = var.gateway_port 7 | gateway_secret = var.gateway_secret 8 | network_id = var.network_id 9 | core_address = var.core_address 10 | core_grpc_port = var.core_grpc_port 11 | package_version = var.package_version 12 | nat = var.nat 13 | gateway_name = "defguard-gateway-${var.network_id}" 14 | arch = var.arch 15 | log_level = var.log_level 16 | }) 17 | user_data_replace_on_change = true 18 | 19 | primary_network_interface { 20 | network_interface_id = var.network_interface_id 21 | } 22 | 23 | tags = { 24 | Name = "defguard-gateway-instance-${var.network_id}" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /charts/defguard/templates/defguard-secret.yaml: -------------------------------------------------------------------------------- 1 | {{ if not .Values.existingJwtSecret }} 2 | {{- $auth := (randAlpha 16) | b64enc | quote }} 3 | {{- $gateway := (randAlpha 16) | b64enc | quote }} 4 | {{- $yubiBridge := (randAlpha 16) | b64enc | quote }} 5 | {{- $secretKey := (randAlpha 64) | b64enc | quote }} 6 | {{- $secret := (lookup "v1" "Secret" .Release.Namespace (include "defguard.jwtSecretName" .)) }} 7 | {{- if $secret }} 8 | {{- $auth = index $secret.data "auth" }} 9 | {{- $gateway = index $secret.data "gateway" }} 10 | {{- $yubiBridge = index $secret.data "yubi-bridge" }} 11 | {{- $secretKey = index $secret.data "secret-key" }} 12 | {{- end }} 13 | apiVersion: v1 14 | kind: Secret 15 | metadata: 16 | name: {{ include "defguard.jwtSecretName" . }} 17 | labels: 18 | {{- include "defguard.labels" . | nindent 4 }} 19 | annotations: 20 | "helm.sh/resource-policy": keep 21 | type: Opaque 22 | data: 23 | auth: {{ $auth }} 24 | gateway: {{ $gateway }} 25 | yubi-bridge: {{ $yubiBridge }} 26 | secret-key: {{ $secretKey }} 27 | {{- end }} 28 | -------------------------------------------------------------------------------- /charts/defguard/templates/defguard-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: {{ include "defguard.fullname" . }}-config 5 | labels: 6 | {{- include "defguard.labels" . | nindent 4 }} 7 | data: 8 | {{- if .Values.cookie.domain }} 9 | DEFGUARD_COOKIE_DOMAIN: {{ .Values.cookie.domain }} 10 | {{- end }} 11 | DEFGUARD_COOKIE_INSECURE: {{ .Values.cookie.insecure | quote }} 12 | DEFGUARD_DB_HOST: {{ .Values.postgresql.host | default (printf "%s-postgresql" (include "defguard.fullname" .)) }} 13 | DEFGUARD_DB_PORT: {{ .Values.postgresql.port | quote}} 14 | DEFGUARD_DB_NAME: {{ .Values.postgresql.auth.database }} 15 | DEFGUARD_DB_USER: {{ .Values.postgresql.auth.username }} 16 | DEFGUARD_GRPC_PORT: {{ .Values.service.grpc.port | quote }} 17 | DEFGUARD_HTTP_PORT: {{ .Values.service.web.port | quote }} 18 | DEFGUARD_ENROLLMENT_URL: {{ index .Values "defguard-proxy" "publicUrl" }} 19 | {{- if .Values.proxyUrl }} 20 | DEFGUARD_PROXY_URL: {{ .Values.proxyUrl }} 21 | {{- end }} 22 | DEFGUARD_URL: {{ .Values.publicUrl }} 23 | DEFGUARD_WEBAUTHN_RP_ID: {{ .Values.ingress.web.host }} 24 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Charts 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release: 10 | runs-on: [self-hosted, Linux, X64] 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Configure Git 19 | run: | 20 | git config user.name "$GITHUB_ACTOR" 21 | git config user.email "$GITHUB_ACTOR@users.noreply.github.com" 22 | 23 | - name: Install Helm 24 | uses: azure/setup-helm@v4 25 | 26 | # https://github.com/helm/chart-releaser-action/issues/74 27 | - name: Add repositories 28 | run: | 29 | for dir in $(ls -d charts/*/); do 30 | helm dependency list $dir 2> /dev/null | tail +2 | head -n -1 | awk '{ print "helm repo add " $1 " " $3 }' | while read cmd; do $cmd; done 31 | done 32 | 33 | - name: Run chart-releaser 34 | uses: helm/chart-releaser-action@v1.6.0 35 | env: 36 | CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 37 | with: 38 | charts_dir: charts 39 | skip_existing: true 40 | -------------------------------------------------------------------------------- /.github/workflows/lint_charts.yml: -------------------------------------------------------------------------------- 1 | name: Lint Helm Charts 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - 'charts/**' 7 | 8 | push: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | release: 14 | runs-on: [self-hosted, Linux, X64] 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Install Helm 23 | uses: azure/setup-helm@v4 24 | 25 | - name: Lint Helm charts 26 | run: | 27 | for chart in charts/*/; do 28 | if [ -f "$chart/Chart.yaml" ]; then 29 | echo "Validating $chart" 30 | helm lint "$chart" 31 | helm template "$chart" --debug 32 | fi 33 | done 34 | 35 | - name: Setup kubeconform 36 | uses: alexellis/arkade-get@master 37 | with: 38 | kubeconform: latest 39 | 40 | - name: Validate Kubernetes manifests 41 | run: | 42 | for chart in charts/*/; do 43 | if [ -f "$chart/Chart.yaml" ]; then 44 | echo "Validating Kubernetes manifests for $chart" 45 | helm template "$chart" | kubeconform -strict -ignore-missing-schemas 46 | fi 47 | done 48 | -------------------------------------------------------------------------------- /terraform/modules/proxy/variables.tf: -------------------------------------------------------------------------------- 1 | variable "ami" { 2 | description = "AMI ID for the instance" 3 | type = string 4 | } 5 | 6 | variable "instance_type" { 7 | description = "Instance type for the instance" 8 | type = string 9 | default = "t3.micro" 10 | } 11 | 12 | variable "proxy_url" { 13 | description = "URL of the Proxy instance" 14 | type = string 15 | } 16 | 17 | variable "grpc_port" { 18 | description = "Port to be used to communicate with Defguard Core" 19 | type = string 20 | } 21 | 22 | variable "network_interface_id" { 23 | description = "Network interface ID for the instance" 24 | type = string 25 | } 26 | 27 | variable "arch" { 28 | description = "Architecture of the Defguard Proxy package to be installed" 29 | type = string 30 | } 31 | 32 | variable "package_version" { 33 | description = "Version of the Defguard Proxy package to be installed" 34 | type = string 35 | } 36 | 37 | variable "http_port" { 38 | description = "Port to be used to access Defguard Proxy via HTTP" 39 | type = number 40 | default = 8080 41 | } 42 | 43 | variable "log_level" { 44 | description = "Log level for Defguard Proxy. Possible values: trace, debug, info, warn, error" 45 | type = string 46 | default = "info" 47 | } 48 | -------------------------------------------------------------------------------- /terraform/modules/proxy/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | LOG_FILE="/var/log/defguard.log" 5 | 6 | log() { 7 | echo "$(date '+%Y-%m-%d %H:%M:%S') $1" 8 | } 9 | 10 | ( 11 | log "Updating apt repositories..." 12 | apt update 13 | 14 | log "Installing curl..." 15 | apt install -y curl 16 | 17 | log "Downloading defguard-proxy package..." 18 | curl -fsSL -o /tmp/defguard-proxy.deb https://github.com/DefGuard/proxy/releases/download/v${package_version}/defguard-proxy-${package_version}-${arch}-unknown-linux-gnu.deb 19 | 20 | log "Installing defguard-proxy package..." 21 | dpkg -i /tmp/defguard-proxy.deb 22 | 23 | log "Writing proxy configuration to /etc/defguard/proxy.toml..." 24 | tee /etc/defguard/proxy.toml <&1 | tee -a "$LOG_FILE" 48 | -------------------------------------------------------------------------------- /terraform/modules/core/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_instance" "defguard_core" { 2 | ami = var.ami 3 | instance_type = var.instance_type 4 | 5 | user_data = templatefile("${path.module}/setup.sh", { 6 | db_address = var.db_details.address 7 | db_password = var.db_details.password 8 | db_username = var.db_details.username 9 | db_name = var.db_details.name 10 | db_port = var.db_details.port 11 | core_url = var.core_url 12 | proxy_address = var.proxy_address 13 | proxy_grpc_port = var.proxy_grpc_port 14 | proxy_url = var.proxy_url 15 | grpc_port = var.grpc_port 16 | gateway_secret = var.gateway_secret 17 | vpn_networks = var.vpn_networks 18 | package_version = var.package_version 19 | arch = var.arch 20 | http_port = var.http_port 21 | default_admin_password = var.default_admin_password 22 | cookie_insecure = var.cookie_insecure 23 | log_level = var.log_level 24 | }) 25 | user_data_replace_on_change = true 26 | 27 | primary_network_interface { 28 | network_interface_id = var.network_interface_id 29 | } 30 | 31 | tags = { 32 | Name = "defguard-core-instance" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /cloudformation/ami/defguard-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "Updating apt repositories..." 5 | sudo apt update 6 | 7 | echo "Installing dependencies..." 8 | sudo apt install -y ca-certificates curl awscli 9 | 10 | echo "Adding Defguard GPG key..." 11 | sudo install -m 0755 -d /etc/apt/keyrings 12 | sudo curl -fsSL https://apt.defguard.net/defguard.asc -o /etc/apt/keyrings/defguard.asc 13 | sudo chmod a+r /etc/apt/keyrings/defguard.asc 14 | 15 | echo "Adding Defguard repository..." 16 | echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/defguard.asc] https://apt.defguard.net/ trixie release " | \ 17 | sudo tee /etc/apt/sources.list.d/defguard.list > /dev/null 18 | 19 | echo "Updating apt repositories after adding Defguard repo..." 20 | sudo apt update 21 | 22 | echo "Installing Defguard packages with specific versions..." 23 | echo " defguard version: ${CORE_VERSION}" 24 | echo " defguard-proxy version: ${PROXY_VERSION}" 25 | echo " defguard-gateway version: ${GATEWAY_VERSION}" 26 | 27 | sudo apt install -y \ 28 | defguard=${CORE_VERSION} \ 29 | defguard-proxy=${PROXY_VERSION} \ 30 | defguard-gateway=${GATEWAY_VERSION} 31 | 32 | sudo systemctl stop defguard 33 | sudo systemctl disable defguard 34 | sudo systemctl stop defguard-proxy 35 | sudo systemctl disable defguard-proxy 36 | sudo systemctl stop defguard-gateway 37 | sudo systemctl disable defguard-gateway 38 | -------------------------------------------------------------------------------- /docker-compose/.env.template: -------------------------------------------------------------------------------- 1 | # The best way to define each secret is to generate random strings with e.g.: 2 | # 3 | # openssl rand -base64 48 #this will generate a 48chars random string 4 | # 5 | # Please provide secret strings (do not share them) for: 6 | # 7 | # Secret used for JWT cryptography 8 | DEFGUARD_AUTH_SECRET= 9 | # Secret used for JWT cryptography in YubiBridge GRPC communication 10 | DEFGUARD_YUBIBRIDGE_SECRET= 11 | # Secret used for JWT cryptography in gateway GRPC communication 12 | DEFGUARD_GATEWAY_SECRET= 13 | # Secret used for private cookies cryptography; must be at least 64 characters long 14 | DEFGUARD_SECRET_KEY= 15 | # Database password 16 | DEFGUARD_DB_PASSWORD= 17 | # Public URL of your Defguard instance 18 | # E.g.: https://defguard.mycompany.com 19 | DEFGUARD_URL= 20 | # Webauthn RP ID (https://w3c.github.io/webauthn/#rp-id) 21 | # E.g.: defguard.mycompany.com (without http/https) 22 | DEFGUARD_WEBAUTHN_RP_ID= 23 | # Public URL of your Defguard proxy gRPC server 24 | # DEFGUARD_PROXY_URL= 25 | # Public URL of your enrollment service 26 | # E.g.: https://enrollment.mycompany.com 27 | # DEFGUARD_ENROLLMENT_URL= # [ENROLLMENT] 28 | # Token used for VPN gateway authorization 29 | # DEFGUARD_TOKEN= # [VPN] 30 | # Enable insecure cookies when not using HTTPS 31 | # DEFGUARD_COOKIE_INSECURE=true # [HTTP] 32 | -------------------------------------------------------------------------------- /charts/defguard-gateway/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if contains "NodePort" .Values.service.wireguard.type }} 3 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "defguard-gateway.fullname" . }}) 4 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 5 | echo http://$NODE_IP:$NODE_PORT 6 | {{- else if contains "LoadBalancer" .Values.service.wireguard.type }} 7 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 8 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "defguard-gateway.fullname" . }}' 9 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "defguard-gateway.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 10 | echo http://$SERVICE_IP:{{ .Values.service.wireguard.port }} 11 | {{- else if contains "ClusterIP" .Values.service.wireguard.type }} 12 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "defguard-gateway.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 13 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 14 | echo "Visit http://127.0.0.1:8080 to use your application" 15 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 16 | {{- end }} 17 | -------------------------------------------------------------------------------- /cloudformation/ami/defguard.pkr.hcl: -------------------------------------------------------------------------------- 1 | packer { 2 | required_plugins { 3 | amazon = { 4 | version = ">= 1.6.0" 5 | source = "github.com/hashicorp/amazon" 6 | } 7 | } 8 | } 9 | 10 | variable "core_version" { 11 | type = string 12 | } 13 | 14 | variable "gateway_version" { 15 | type = string 16 | } 17 | 18 | variable "proxy_version" { 19 | type = string 20 | } 21 | 22 | variable "region" { 23 | type = string 24 | default = "us-east-1" 25 | } 26 | 27 | variable "instance_type" { 28 | type = string 29 | default = "t3.micro" 30 | } 31 | 32 | source "amazon-ebs" "defguard" { 33 | ami_name = "defguard-C-${var.core_version}-PX-${var.gateway_version}-GW-${var.proxy_version}-amd64-{{timestamp}}" 34 | instance_type = var.instance_type 35 | region = var.region 36 | source_ami_filter { 37 | filters = { 38 | name = "debian-13-amd64-*" 39 | root-device-type = "ebs" 40 | virtualization-type = "hvm" 41 | } 42 | most_recent = true 43 | owners = ["136693071363"] 44 | } 45 | ssh_username = "admin" 46 | } 47 | 48 | build { 49 | name = "defguard" 50 | sources = [ 51 | "source.amazon-ebs.defguard" 52 | ] 53 | 54 | provisioner "shell" { 55 | script = "./cloudformation/ami/defguard-install.sh" 56 | environment_vars = [ 57 | "CORE_VERSION=${var.core_version}", 58 | "PROXY_VERSION=${var.proxy_version}", 59 | "GATEWAY_VERSION=${var.gateway_version}" 60 | ] 61 | } 62 | 63 | provisioner "shell" { 64 | inline = ["rm /home/admin/.ssh/authorized_keys"] 65 | } 66 | 67 | provisioner "shell" { 68 | inline = ["sudo rm /root/.ssh/authorized_keys"] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /terraform/modules/gateway/variables.tf: -------------------------------------------------------------------------------- 1 | variable "ami" { 2 | description = "AMI ID for the instance" 3 | type = string 4 | } 5 | 6 | variable "instance_type" { 7 | description = "Instance type for the instance" 8 | type = string 9 | default = "t3.micro" 10 | } 11 | 12 | variable "gateway_port" { 13 | description = "Port to be used by the VPN" 14 | type = number 15 | default = 50051 16 | } 17 | 18 | variable "gateway_secret" { 19 | description = "Secret key for the Defguard Gateway" 20 | type = string 21 | } 22 | 23 | variable "network_id" { 24 | description = "ID of the VPN network" 25 | type = number 26 | } 27 | 28 | variable "core_address" { 29 | description = "Internal address of the Defguard instance" 30 | type = string 31 | } 32 | 33 | variable "core_grpc_port" { 34 | description = "Port to be used to communicate with Defguard Core" 35 | type = number 36 | } 37 | 38 | variable "network_interface_id" { 39 | description = "Network interface ID for the instance" 40 | type = string 41 | } 42 | 43 | variable "package_version" { 44 | description = "Version of the Defguard Gateway package to be installed" 45 | type = string 46 | } 47 | 48 | variable "arch" { 49 | description = "Architecture of the Defguard Gateway package to be installed" 50 | type = string 51 | } 52 | 53 | variable "nat" { 54 | description = "Enable masquerading" 55 | type = bool 56 | default = true 57 | } 58 | 59 | variable "log_level" { 60 | description = "Log level for Defguard Gateway. Possible values: trace, debug, info, warn, error" 61 | type = string 62 | default = "info" 63 | } 64 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test setup script 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'docker-compose/**' 9 | - '.github/workflows/test.yml' 10 | 11 | jobs: 12 | test: 13 | name: Test setup script 14 | runs-on: [self-hosted, Linux, X64] 15 | steps: 16 | - name: Login to GitHub container registry 17 | uses: docker/login-action@v2 18 | with: 19 | registry: ghcr.io 20 | username: ${{ github.actor }} 21 | password: ${{ secrets.GITHUB_TOKEN }} 22 | - name: Create working directory 23 | run: mkdir temp 24 | - name: Run setup script 25 | env: 26 | DEFGUARD_DOMAIN: "id.localhost" 27 | DEFGUARD_ENROLLMENT_DOMAIN: "enrollment.localhost" 28 | DEFGUARD_VPN_NAME: "test_location" 29 | DEFGUARD_VPN_IP: "10.0.60.1/24" 30 | DEFGUARD_VPN_GATEWAY_IP: "10.20.20.40" 31 | DEFGUARD_VPN_GATEWAY_PORT: "50050" 32 | CORE_IMAGE_TAG: latest 33 | PROXY_IMAGE_TAG: latest 34 | GATEWAY_IMAGE_TAG: latest 35 | working-directory: temp 36 | run: curl --proto '=https' --tlsv1.2 -sSf -L https://raw.githubusercontent.com/DefGuard/deployment/main/docker-compose/setup.sh | bash -s - --non-interactive 37 | - name: Sleep for 10 seconds 38 | working-directory: temp 39 | run: sleep 10s 40 | - name: Test Defguard is available 41 | working-directory: temp 42 | run: curl -f http://id.localhost/api/v1/health 43 | - name: Stop compose stack 44 | if: always() 45 | working-directory: temp 46 | run: docker compose down -v 47 | - name: Cleanup temp 48 | run: sudo rm -r temp 49 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1759831965, 24 | "narHash": "sha256-vgPm2xjOmKdZ0xKA6yLXPJpjOtQPHfaZDRtH+47XEBo=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "c9b6fb798541223bbb396d287d16f43520250518", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "ref": "nixos-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs" 41 | } 42 | }, 43 | "systems": { 44 | "locked": { 45 | "lastModified": 1681028828, 46 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 | "owner": "nix-systems", 48 | "repo": "default", 49 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "nix-systems", 54 | "repo": "default", 55 | "type": "github" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /charts/defguard/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host }}/ 5 | {{- end }} 6 | {{- else if contains "NodePort" .Values.service.web.type }} 7 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "defguard.fullname" . }}) 8 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 9 | echo http://$NODE_IP:$NODE_PORT 10 | {{- else if contains "LoadBalancer" .Values.service.web.type }} 11 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 12 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "defguard.fullname" . }}' 13 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "defguard.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 14 | echo http://$SERVICE_IP:{{ .Values.service.web.port }} 15 | {{- else if contains "ClusterIP" .Values.service.web.type }} 16 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "defguard.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 17 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 18 | echo "Visit http://127.0.0.1:8080 to use your application" 19 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 20 | {{- end }} 21 | -------------------------------------------------------------------------------- /charts/defguard-proxy/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host }}/ 5 | {{- end }} 6 | {{- else if contains "NodePort" .Values.service.web.type }} 7 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "defguard-proxy.fullname" . }}) 8 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 9 | echo http://$NODE_IP:$NODE_PORT 10 | {{- else if contains "LoadBalancer" .Values.service.web.type }} 11 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 12 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "defguard-proxy.fullname" . }}' 13 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "defguard-proxy.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 14 | echo http://$SERVICE_IP:{{ .Values.service.web.port }} 15 | {{- else if contains "ClusterIP" .Values.service.web.type }} 16 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "defguard-proxy.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 17 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 18 | echo "Visit http://127.0.0.1:8080 to use your application" 19 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 20 | {{- end }} 21 | -------------------------------------------------------------------------------- /.github/workflows/ami.yml: -------------------------------------------------------------------------------- 1 | name: Build Defguard AMI 2 | 3 | on: 4 | push: 5 | tags: 6 | - "ami_c-*_px-*_gw-*" 7 | 8 | jobs: 9 | build-ami: 10 | name: Build Defguard AMI 11 | runs-on: [self-hosted, Linux, X64] 12 | 13 | steps: 14 | - name: Extract versions 15 | id: versions 16 | run: | 17 | TAG="${GITHUB_REF#refs/tags/}" 18 | CORE_VERSION=$(echo $TAG | sed 's/.*c-\([^_]*\).*/\1/') 19 | PROXY_VERSION=$(echo $TAG | sed 's/.*px-\([^_]*\).*/\1/') 20 | GATEWAY_VERSION=$(echo $TAG | sed 's/.*gw-\([^_]*\).*/\1/') 21 | echo "CORE_VERSION=$CORE_VERSION" >> $GITHUB_OUTPUT 22 | echo "PROXY_VERSION=$PROXY_VERSION" >> $GITHUB_OUTPUT 23 | echo "GATEWAY_VERSION=$GATEWAY_VERSION" >> $GITHUB_OUTPUT 24 | echo "Core version: $CORE_VERSION" 25 | echo "Proxy version: $PROXY_VERSION" 26 | echo "Gateway version: $GATEWAY_VERSION" 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | 30 | - name: Setup `packer` 31 | uses: hashicorp/setup-packer@main 32 | 33 | - name: Run `packer init` 34 | run: "packer init ./cloudformation/ami/defguard.pkr.hcl" 35 | 36 | - name: Build AMI with `packer` 37 | run: | 38 | packer validate --var "core_version=${{ steps.versions.outputs.CORE_VERSION }}" \ 39 | --var "proxy_version=${{ steps.versions.outputs.PROXY_VERSION }}" \ 40 | --var "gateway_version=${{ steps.versions.outputs.GATEWAY_VERSION }}" \ 41 | ./cloudformation/ami/defguard.pkr.hcl 42 | packer build --var "core_version=${{ steps.versions.outputs.CORE_VERSION }}" \ 43 | --var "proxy_version=${{ steps.versions.outputs.PROXY_VERSION }}" \ 44 | --var "gateway_version=${{ steps.versions.outputs.GATEWAY_VERSION }}" \ 45 | ./cloudformation/ami/defguard.pkr.hcl 46 | env: 47 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 48 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 49 | -------------------------------------------------------------------------------- /charts/defguard-proxy/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "defguard-proxy.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "defguard-proxy.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "defguard-proxy.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "defguard-proxy.labels" -}} 37 | helm.sh/chart: {{ include "defguard-proxy.chart" . }} 38 | {{ include "defguard-proxy.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "defguard-proxy.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "defguard-proxy.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "defguard-proxy.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "defguard-proxy.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /charts/defguard-gateway/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "defguard-gateway.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "defguard-gateway.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "defguard-gateway.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "defguard-gateway.labels" -}} 37 | helm.sh/chart: {{ include "defguard-gateway.chart" . }} 38 | {{ include "defguard-gateway.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "defguard-gateway.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "defguard-gateway.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "defguard-gateway.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "defguard-gateway.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /charts/defguard-proxy/values.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # defguard-proxy is turned off by default. enable to allow use of the enrollment interface 3 | enabled: false 4 | # this should be a URL based on defguard-proxy.ingress.web.host 5 | publicUrl: "http://enrollment.local" 6 | # defguard-proxy full name override 7 | fullnameOverride: "" 8 | # defguard-proxy name override 9 | nameOverride: "" 10 | # defguard-proxy pod autoscaling configuration 11 | autoscaling: 12 | enabled: false 13 | minReplicas: 1 14 | maxReplicas: 10 15 | # defguard-proxy container image configuration 16 | image: 17 | pullPolicy: IfNotPresent 18 | repository: ghcr.io/defguard/defguard-proxy 19 | tag: "" # overrides .Chart.AppVersion 20 | # defguard-proxy container image pull secrets configuration 21 | imagePullSecrets: [] 22 | # defguard-proxy ingress configuration 23 | ingress: 24 | grpc: 25 | annotations: {} 26 | className: "" 27 | enabled: true 28 | host: enrollment-grpc.local 29 | labels: {} 30 | tls: false 31 | web: 32 | annotations: {} 33 | className: "" 34 | enabled: true 35 | host: enrollment.local 36 | labels: {} 37 | tls: false 38 | # defguard-proxy pod affinity 39 | affinity: {} 40 | # defguard-proxy pod node selection 41 | nodeSelector: {} 42 | # defguard-proxy pod tolerations 43 | tolerations: [] 44 | # defguard-proxy pod annotations 45 | podAnnotations: {} 46 | # defguard-proxy pod labels 47 | podLabels: {} 48 | # defguard-proxy pod security context 49 | podSecurityContext: {} 50 | # defguard-proxy container security context 51 | securityContext: {} 52 | # defguard-proxy container replica count 53 | replicaCount: 1 54 | # defguard-proxy pod resource configuration 55 | resources: {} 56 | # defguard-proxy service configuration 57 | service: 58 | grpc: 59 | annotations: 60 | traefik.ingress.kubernetes.io/service.serversscheme: h2c 61 | labels: {} 62 | port: 50051 63 | type: ClusterIP 64 | web: 65 | annotations: {} 66 | labels: {} 67 | port: 8080 68 | type: ClusterIP 69 | # defguard-proxy service account configuration 70 | serviceAccount: 71 | annotations: {} 72 | create: true 73 | # defguard-proxy additional ENV from configmap 74 | additionalEnvFromConfigMap: "" 75 | -------------------------------------------------------------------------------- /charts/defguard/templates/ingress-web.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.web.enabled -}} 2 | {{- $fullName := include "defguard.fullname" . -}} 3 | {{- if and .Values.ingress.web.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} 4 | {{- if not (hasKey .Values.ingress.web.annotations "kubernetes.io/ingress.class") }} 5 | {{- $_ := set .Values.ingress.web.annotations "kubernetes.io/ingress.class" .Values.ingress.web.className}} 6 | {{- end }} 7 | {{- end }} 8 | {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} 9 | apiVersion: networking.k8s.io/v1 10 | {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 11 | apiVersion: networking.k8s.io/v1beta1 12 | {{- else -}} 13 | apiVersion: extensions/v1beta1 14 | {{- end }} 15 | kind: Ingress 16 | metadata: 17 | name: {{ $fullName }}-web 18 | labels: 19 | {{- include "defguard.labels" . | nindent 4 }} 20 | {{- with .Values.ingress.web.labels }} 21 | {{- toYaml . | nindent 4 }} 22 | {{- end }} 23 | {{- with .Values.ingress.web.annotations }} 24 | annotations: 25 | {{- toYaml . | nindent 4 }} 26 | {{- end }} 27 | spec: 28 | {{- if and .Values.ingress.web.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} 29 | ingressClassName: {{ .Values.ingress.web.className }} 30 | {{- end }} 31 | {{- if .Values.ingress.web.tls }} 32 | tls: 33 | - hosts: 34 | - {{ .Values.ingress.web.host | quote }} 35 | secretName: {{ printf "%s-web-tls" .Values.ingress.web.host }} 36 | {{- end }} 37 | rules: 38 | - host: {{ .Values.ingress.web.host | quote }} 39 | http: 40 | paths: 41 | - path: / 42 | {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} 43 | pathType: ImplementationSpecific 44 | {{- end }} 45 | backend: 46 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 47 | service: 48 | name: {{ $fullName }}-web 49 | port: 50 | number: {{ .Values.service.web.port }} 51 | {{- else }} 52 | serviceName: {{ $fullName }}-web 53 | servicePort: {{ .Values.service.web.port }} 54 | {{- end }} 55 | {{- end }} 56 | -------------------------------------------------------------------------------- /charts/defguard-proxy/templates/ingress-web.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.web.enabled -}} 2 | {{- $fullName := include "defguard-proxy.fullname" . -}} 3 | {{- if and .Values.ingress.web.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} 4 | {{- if not (hasKey .Values.ingress.web.annotations "kubernetes.io/ingress.class") }} 5 | {{- $_ := set .Values.ingress.web.annotations "kubernetes.io/ingress.class" .Values.ingress.web.className}} 6 | {{- end }} 7 | {{- end }} 8 | {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} 9 | apiVersion: networking.k8s.io/v1 10 | {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 11 | apiVersion: networking.k8s.io/v1beta1 12 | {{- else -}} 13 | apiVersion: extensions/v1beta1 14 | {{- end }} 15 | kind: Ingress 16 | metadata: 17 | name: {{ $fullName }}-web 18 | labels: 19 | {{- include "defguard-proxy.labels" . | nindent 4 }} 20 | {{- with .Values.ingress.web.labels }} 21 | {{- toYaml . | nindent 4 }} 22 | {{- end }} 23 | {{- with .Values.ingress.web.annotations }} 24 | annotations: 25 | {{- toYaml . | nindent 4 }} 26 | {{- end }} 27 | spec: 28 | {{- if and .Values.ingress.web.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} 29 | ingressClassName: {{ .Values.ingress.web.className }} 30 | {{- end }} 31 | {{- if .Values.ingress.web.tls }} 32 | tls: 33 | - hosts: 34 | - {{ .Values.ingress.web.host | quote }} 35 | secretName: {{ printf "%s-web-tls" .Values.ingress.web.host }} 36 | {{- end }} 37 | rules: 38 | - host: {{ .Values.ingress.web.host | quote }} 39 | http: 40 | paths: 41 | - path: / 42 | {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} 43 | pathType: ImplementationSpecific 44 | {{- end }} 45 | backend: 46 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 47 | service: 48 | name: {{ $fullName }}-web 49 | port: 50 | number: {{ .Values.service.web.port }} 51 | {{- else }} 52 | serviceName: {{ $fullName }}-web 53 | servicePort: {{ .Values.service.web.port }} 54 | {{- end }} 55 | {{- end }} 56 | -------------------------------------------------------------------------------- /charts/defguard/templates/ingress-grpc.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.grpc.enabled -}} 2 | {{- $fullName := include "defguard.fullname" . -}} 3 | {{- if and .Values.ingress.grpc.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} 4 | {{- if not (hasKey .Values.ingress.grpc.annotations "kubernetes.io/ingress.class") }} 5 | {{- $_ := set .Values.ingress.grpc.annotations "kubernetes.io/ingress.class" .Values.ingress.grpc.className}} 6 | {{- end }} 7 | {{- end }} 8 | {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} 9 | apiVersion: networking.k8s.io/v1 10 | {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 11 | apiVersion: networking.k8s.io/v1beta1 12 | {{- else -}} 13 | apiVersion: extensions/v1beta1 14 | {{- end }} 15 | kind: Ingress 16 | metadata: 17 | name: {{ $fullName }}-grpc 18 | labels: 19 | {{- include "defguard.labels" . | nindent 4 }} 20 | {{- with .Values.ingress.grpc.labels }} 21 | {{- toYaml . | nindent 4 }} 22 | {{- end }} 23 | {{- with .Values.ingress.grpc.annotations }} 24 | annotations: 25 | {{- toYaml . | nindent 4 }} 26 | {{- end }} 27 | spec: 28 | {{- if and .Values.ingress.grpc.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} 29 | ingressClassName: {{ .Values.ingress.grpc.className }} 30 | {{- end }} 31 | {{- if .Values.ingress.grpc.tls }} 32 | tls: 33 | - hosts: 34 | - {{ .Values.ingress.grpc.host | quote }} 35 | secretName: {{ printf "%s-grpc-tls" .Values.ingress.grpc.host }} 36 | {{- end }} 37 | rules: 38 | - host: {{ .Values.ingress.grpc.host | quote }} 39 | http: 40 | paths: 41 | - path: / 42 | {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} 43 | pathType: ImplementationSpecific 44 | {{- end }} 45 | backend: 46 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 47 | service: 48 | name: {{ $fullName }}-grpc 49 | port: 50 | number: {{ .Values.service.grpc.port }} 51 | {{- else }} 52 | serviceName: {{ $fullName }}-grpc 53 | servicePort: {{ .Values.service.grpc.port }} 54 | {{- end }} 55 | {{- end }} 56 | -------------------------------------------------------------------------------- /charts/defguard-proxy/templates/ingress-grpc.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.grpc.enabled -}} 2 | {{- $fullName := include "defguard-proxy.fullname" . -}} 3 | {{- if and .Values.ingress.grpc.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} 4 | {{- if not (hasKey .Values.ingress.grpc.annotations "kubernetes.io/ingress.class") }} 5 | {{- $_ := set .Values.ingress.grpc.annotations "kubernetes.io/ingress.class" .Values.ingress.grpc.className}} 6 | {{- end }} 7 | {{- end }} 8 | {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} 9 | apiVersion: networking.k8s.io/v1 10 | {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 11 | apiVersion: networking.k8s.io/v1beta1 12 | {{- else -}} 13 | apiVersion: extensions/v1beta1 14 | {{- end }} 15 | kind: Ingress 16 | metadata: 17 | name: {{ $fullName }}-grpc 18 | labels: 19 | {{- include "defguard-proxy.labels" . | nindent 4 }} 20 | {{- with .Values.ingress.grpc.labels }} 21 | {{- toYaml . | nindent 4 }} 22 | {{- end }} 23 | {{- with .Values.ingress.grpc.annotations }} 24 | annotations: 25 | {{- toYaml . | nindent 4 }} 26 | {{- end }} 27 | spec: 28 | {{- if and .Values.ingress.grpc.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} 29 | ingressClassName: {{ .Values.ingress.grpc.className }} 30 | {{- end }} 31 | {{- if .Values.ingress.grpc.tls }} 32 | tls: 33 | - hosts: 34 | - {{ .Values.ingress.grpc.host | quote }} 35 | secretName: {{ printf "%s-grpc-tls" .Values.ingress.grpc.host }} 36 | {{- end }} 37 | rules: 38 | - host: {{ .Values.ingress.grpc.host | quote }} 39 | http: 40 | paths: 41 | - path: / 42 | {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} 43 | pathType: ImplementationSpecific 44 | {{- end }} 45 | backend: 46 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 47 | service: 48 | name: {{ $fullName }}-grpc 49 | port: 50 | number: {{ .Values.service.grpc.port }} 51 | {{- else }} 52 | serviceName: {{ $fullName }}-grpc 53 | servicePort: {{ .Values.service.grpc.port }} 54 | {{- end }} 55 | {{- end }} 56 | -------------------------------------------------------------------------------- /charts/defguard/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "defguard.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "defguard.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "defguard.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "defguard.labels" -}} 37 | helm.sh/chart: {{ include "defguard.chart" . }} 38 | {{ include "defguard.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "defguard.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "defguard.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "defguard.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "defguard.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | 64 | {{/* 65 | Define OpenID secret name 66 | */}} 67 | {{- define "defguard.openidSecretName" -}} 68 | {{- $name := "openid-key" }} 69 | {{- $name }} 70 | {{- end }} 71 | 72 | {{/* 73 | Define JWT secret name 74 | */}} 75 | {{- define "defguard.jwtSecretName" -}} 76 | {{- $name := "jwt-secrets" }} 77 | {{- $name }} 78 | {{- end }} 79 | -------------------------------------------------------------------------------- /index.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | entries: 3 | defguard-gateway: 4 | - apiVersion: v2 5 | appVersion: 1.2.0 6 | created: "2025-01-22T13:08:49.181643+01:00" 7 | description: Defguard gateway is a public-facing VPN endpoint. 8 | digest: 074c22d3f5714ad0484ad7ff5a1faf2759597fc858a97b099525df0bb84e73f8 9 | name: defguard-gateway 10 | type: application 11 | urls: 12 | - charts/defguard-gateway-0.1.3.tgz 13 | version: 0.1.3 14 | defguard-proxy: 15 | - apiVersion: v2 16 | appVersion: 1.2.0 17 | created: "2025-01-22T13:08:49.18194+01:00" 18 | description: Defguard proxy is a public-facing proxy for core Defguard service 19 | digest: 4da4f264bea0fc94741abf1d31be308a8824f80f65a7edef2d7beb3f2ee3c0bb 20 | name: defguard-proxy 21 | type: application 22 | urls: 23 | - charts/defguard-proxy-0.5.4.tgz 24 | version: 0.5.4 25 | postgresql: 26 | - annotations: 27 | category: Database 28 | images: | 29 | - name: os-shell 30 | image: docker.io/bitnami/os-shell:11-debian-11-r77 31 | - name: postgres-exporter 32 | image: docker.io/bitnami/postgres-exporter:0.14.0-debian-11-r2 33 | - name: postgresql 34 | image: docker.io/bitnami/postgresql:15.4.0-debian-11-r45 35 | licenses: Apache-2.0 36 | apiVersion: v2 37 | appVersion: 15.4.0 38 | created: "2025-01-22T13:08:49.185002+01:00" 39 | dependencies: 40 | - name: common 41 | repository: oci://registry-1.docker.io/bitnamicharts 42 | tags: 43 | - bitnami-common 44 | version: 2.x.x 45 | description: PostgreSQL (Postgres) is an open source object-relational database 46 | known for reliability and data integrity. ACID-compliant, it supports foreign 47 | keys, joins, views, triggers and stored procedures. 48 | digest: 6e7c4f44ebf2606b9a2c3339a5e3317e98e492c6ec2e732654382cf4e5726d07 49 | home: https://bitnami.com 50 | icon: https://bitnami.com/assets/stacks/postgresql/img/postgresql-stack-220x234.png 51 | keywords: 52 | - postgresql 53 | - postgres 54 | - database 55 | - sql 56 | - replication 57 | - cluster 58 | maintainers: 59 | - name: VMware, Inc. 60 | url: https://github.com/bitnami/charts 61 | name: postgresql 62 | sources: 63 | - https://github.com/bitnami/charts/tree/main/bitnami/postgresql 64 | urls: 65 | - charts/postgresql-12.12.10.tgz 66 | version: 12.12.10 67 | generated: "2025-01-22T13:08:49.181223+01:00" 68 | -------------------------------------------------------------------------------- /charts/defguard-proxy/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "defguard-proxy.fullname" . }} 5 | labels: 6 | {{- include "defguard-proxy.labels" . | nindent 4 }} 7 | spec: 8 | {{- if not .Values.autoscaling.enabled }} 9 | replicas: {{ .Values.replicaCount }} 10 | {{- end }} 11 | selector: 12 | matchLabels: 13 | {{- include "defguard-proxy.selectorLabels" . | nindent 6 }} 14 | template: 15 | metadata: 16 | {{- with .Values.podAnnotations }} 17 | annotations: 18 | {{- toYaml . | nindent 8 }} 19 | {{- end }} 20 | labels: 21 | {{- include "defguard-proxy.selectorLabels" . | nindent 8 }} 22 | spec: 23 | {{- with .Values.imagePullSecrets }} 24 | imagePullSecrets: 25 | {{- toYaml . | nindent 8 }} 26 | {{- end }} 27 | serviceAccountName: {{ include "defguard-proxy.serviceAccountName" . }} 28 | securityContext: 29 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 30 | containers: 31 | - name: {{ .Chart.Name }} 32 | envFrom: 33 | - configMapRef: 34 | name: {{ include "defguard-proxy.fullname" . }}-config 35 | {{- if .Values.additionalEnvFromConfigMap }} 36 | - configMapRef: 37 | name: {{ .Values.additionalEnvFromConfigMap }} 38 | {{- end }} 39 | securityContext: 40 | {{- toYaml .Values.securityContext | nindent 12 }} 41 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 42 | imagePullPolicy: {{ .Values.image.pullPolicy }} 43 | ports: 44 | - name: http 45 | containerPort: {{ .Values.service.web.port }} 46 | protocol: TCP 47 | - name: grpc 48 | containerPort: {{ .Values.service.grpc.port }} 49 | protocol: TCP 50 | livenessProbe: 51 | httpGet: 52 | path: /api/v1/health 53 | port: http 54 | readinessProbe: 55 | httpGet: 56 | path: /api/v1/health 57 | port: http 58 | resources: 59 | {{- toYaml .Values.resources | nindent 12 }} 60 | {{- with .Values.nodeSelector }} 61 | nodeSelector: 62 | {{- toYaml . | nindent 8 }} 63 | {{- end }} 64 | {{- with .Values.affinity }} 65 | affinity: 66 | {{- toYaml . | nindent 8 }} 67 | {{- end }} 68 | {{- with .Values.tolerations }} 69 | tolerations: 70 | {{- toYaml . | nindent 8 }} 71 | {{- end }} 72 | -------------------------------------------------------------------------------- /charts/defguard-gateway/values.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Use userspace wireguard implementation, useful on systems without native wireguard support. Set to true/false 3 | userspace: "false" 4 | # Defguard GRPC URL, e.g.: defguard-grpc.mycompany.com 5 | grpcUrl: "" 6 | # Token from Defguard app to secure gRPC connection, available on network page. 7 | # It is not recommended to use this. Create a secret yourself and use existingTokenSecret instead 8 | token: "" 9 | # Secret to get the token from 10 | existingTokenSecret: "" 11 | # Key to extract the token from in existingTokenSecret 12 | existingTokenSecretKey: "" 13 | # Defines how often (in seconds) should interface statistics be sent to Defguard server 14 | statsPeriod: 30 15 | # rust log level, default is debug 16 | logLevel: "debug" 17 | # defguard-gateway full name override 18 | fullnameOverride: "" 19 | # defguard-gateway name override 20 | nameOverride: "" 21 | # defguard-gateway container image configuration 22 | image: 23 | pullPolicy: IfNotPresent 24 | repository: ghcr.io/defguard/gateway 25 | tag: "" # overrides .Chart.AppVersion 26 | # defguard-gateway container image pull secrets 27 | imagePullSecrets: [] 28 | # defguard-gateway pod affinity configuration 29 | affinity: {} 30 | # defguard-gateway node selector configuration 31 | nodeSelector: {} 32 | # defguard-gateway pod tolerations 33 | tolerations: [] 34 | # defguard-gateway pod annotations 35 | podAnnotations: {} 36 | # defguard-gateway pod labels 37 | podLabels: {} 38 | # defguard-gateway pod replica count 39 | replicaCount: 1 40 | # defguard-gateway pod resources 41 | resources: {} 42 | # defguard-gateway pod security context 43 | podSecurityContext: {} 44 | # defguard-gateway container security context 45 | # elevated priveleges are required for managing network interfaces 46 | securityContext: 47 | allowPrivilegeEscalation: true 48 | privileged: true 49 | capabilities: 50 | add: 51 | - NET_ADMIN 52 | - SYS_MODULE 53 | # defguard-gateway pod additional ENV from configmap 54 | additionalEnvFromConfigMap: "" 55 | # defguard-gateway health check configuration 56 | healthCheck: 57 | enabled: false 58 | port: 35053 59 | livenessProbe: 60 | initialDelaySeconds: 30 61 | periodSeconds: 10 62 | timeoutSeconds: 5 63 | failureThreshold: 3 64 | readinessProbe: 65 | initialDelaySeconds: 10 66 | periodSeconds: 10 67 | timeoutSeconds: 5 68 | failureThreshold: 3 69 | # defguard-gateway service configuration 70 | service: 71 | wireguard: 72 | annotations: {} 73 | labels: {} 74 | port: 32140 75 | type: ClusterIP 76 | # defguard-gateway serviceaccount configuration 77 | serviceAccount: 78 | annotations: {} 79 | create: true 80 | -------------------------------------------------------------------------------- /terraform/modules/core/variables.tf: -------------------------------------------------------------------------------- 1 | variable "ami" { 2 | description = "AMI ID for the instance" 3 | type = string 4 | } 5 | 6 | variable "instance_type" { 7 | description = "Instance type for the instance" 8 | type = string 9 | default = "t3.micro" 10 | } 11 | 12 | variable "db_details" { 13 | description = "Details of the database connection" 14 | type = object({ 15 | name = string 16 | username = string 17 | password = string 18 | port = number 19 | address = string 20 | }) 21 | } 22 | 23 | variable "core_url" { 24 | description = "URL of the Defguard instance" 25 | type = string 26 | } 27 | 28 | variable "proxy_address" { 29 | description = "The IP address of the Defguard Proxy instance" 30 | type = string 31 | } 32 | 33 | variable "proxy_grpc_port" { 34 | description = "Port to be used to communicate with Defguard Proxy" 35 | type = string 36 | } 37 | 38 | variable "proxy_url" { 39 | description = "The URL of the Defguard Proxy instance where enrollment is performed" 40 | type = string 41 | } 42 | 43 | variable "grpc_port" { 44 | description = "Port to be used to communicate with Defguard Core" 45 | type = number 46 | } 47 | 48 | variable "http_port" { 49 | description = "Port to be used to access Defguard Core via HTTP" 50 | type = number 51 | default = 8000 52 | } 53 | 54 | variable "gateway_secret" { 55 | description = "Secret for the Defguard Gateway" 56 | type = string 57 | } 58 | 59 | variable "network_interface_id" { 60 | description = "Network interface ID for the instance" 61 | type = string 62 | } 63 | 64 | variable "vpn_networks" { 65 | description = "List of VPN networks" 66 | type = list(object({ 67 | name = string 68 | address = string 69 | port = number 70 | endpoint = string 71 | id = number 72 | })) 73 | } 74 | 75 | variable "package_version" { 76 | description = "Version of the Defguard Core package to be installed" 77 | type = string 78 | } 79 | 80 | variable "arch" { 81 | description = "Architecture of the Defguard Core package to be installed" 82 | type = string 83 | } 84 | 85 | variable "default_admin_password" { 86 | description = "Default admin password for the Defguard Core" 87 | type = string 88 | default = "pass123" 89 | } 90 | 91 | variable "cookie_insecure" { 92 | description = "Whether to use insecure cookies for the Defguard Core" 93 | type = bool 94 | } 95 | 96 | variable "log_level" { 97 | description = "Log level for Defguard Core. Possible values: trace, debug, info, warn, error" 98 | type = string 99 | default = "info" 100 | } 101 | -------------------------------------------------------------------------------- /terraform/modules/core/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | LOG_FILE="/var/log/defguard.log" 5 | 6 | log() { 7 | echo "$(date '+%Y-%m-%d %H:%M:%S') $1" 8 | } 9 | 10 | generate_secret_inner() { 11 | local length="$1" 12 | openssl rand -base64 $${length} | tr -d "=+/" | tr -d '\n' | cut -c1-$${length-1} 13 | } 14 | 15 | ( 16 | log "Updating apt repositories..." 17 | apt update 18 | 19 | log "Installing curl..." 20 | apt install -y curl 21 | 22 | log "Downloading defguard-core package..." 23 | curl -fsSL -o /tmp/defguard-core.deb https://github.com/DefGuard/defguard/releases/download/v${package_version}/defguard-${package_version}-${arch}-unknown-linux-gnu.deb 24 | 25 | log "Installing defguard-core package..." 26 | dpkg -i /tmp/defguard-core.deb 27 | 28 | log "Writing Core configuration to /etc/defguard/core.conf..." 29 | tee /etc/defguard/core.conf <> "$LOG_FILE" 2>&1 69 | log "Created VPN location ${network.name} with address ${network.address} and endpoint ${network.endpoint} and port ${network.port}" 70 | %{ endfor ~} 71 | 72 | log "Cleaning up after installing Defguard Core..." 73 | rm -f /tmp/defguard-core.deb 74 | 75 | log "Setup completed." 76 | ) 2>&1 | tee -a "$LOG_FILE" 77 | -------------------------------------------------------------------------------- /charts/defguard-gateway/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "defguard-gateway.fullname" . }} 5 | labels: 6 | {{- include "defguard-gateway.labels" . | nindent 4 }} 7 | spec: 8 | replicas: {{ .Values.replicaCount }} 9 | selector: 10 | matchLabels: 11 | {{- include "defguard-gateway.selectorLabels" . | nindent 6 }} 12 | template: 13 | metadata: 14 | {{- with .Values.podAnnotations }} 15 | annotations: 16 | {{- toYaml . | nindent 8 }} 17 | {{- end }} 18 | labels: 19 | {{- include "defguard-gateway.selectorLabels" . | nindent 8 }} 20 | spec: 21 | {{- with .Values.imagePullSecrets }} 22 | imagePullSecrets: 23 | {{- toYaml . | nindent 8 }} 24 | {{- end }} 25 | serviceAccountName: {{ include "defguard-gateway.serviceAccountName" . }} 26 | securityContext: 27 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 28 | containers: 29 | - name: {{ .Chart.Name }} 30 | envFrom: 31 | - configMapRef: 32 | name: {{ include "defguard-gateway.fullname" . }}-config 33 | {{- if .Values.additionalEnvFromConfigMap }} 34 | - configMapRef: 35 | name: {{ .Values.additionalEnvFromConfigMap }} 36 | {{- end }} 37 | securityContext: 38 | {{- toYaml .Values.securityContext | nindent 12 }} 39 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 40 | imagePullPolicy: {{ .Values.image.pullPolicy }} 41 | ports: 42 | - name: wireguard 43 | containerPort: {{ .Values.service.wireguard.port }} 44 | protocol: UDP 45 | resources: 46 | {{- toYaml .Values.resources | nindent 12 }} 47 | {{- if .Values.healthCheck.enabled }} 48 | livenessProbe: 49 | httpGet: 50 | path: / 51 | port: {{ .Values.healthCheck.port }} 52 | initialDelaySeconds: {{ .Values.healthCheck.livenessProbe.initialDelaySeconds }} 53 | periodSeconds: {{ .Values.healthCheck.livenessProbe.periodSeconds }} 54 | timeoutSeconds: {{ .Values.healthCheck.livenessProbe.timeoutSeconds }} 55 | failureThreshold: {{ .Values.healthCheck.livenessProbe.failureThreshold }} 56 | readinessProbe: 57 | httpGet: 58 | path: / 59 | port: {{ .Values.healthCheck.port }} 60 | initialDelaySeconds: {{ .Values.healthCheck.readinessProbe.initialDelaySeconds }} 61 | periodSeconds: {{ .Values.healthCheck.readinessProbe.periodSeconds }} 62 | timeoutSeconds: {{ .Values.healthCheck.readinessProbe.timeoutSeconds }} 63 | failureThreshold: {{ .Values.healthCheck.readinessProbe.failureThreshold }} 64 | {{- end }} 65 | {{- if .Values.token }} 66 | env: 67 | - name: DEFGUARD_TOKEN 68 | value: {{ .Values.token }} 69 | {{- else if .Values.existingTokenSecret }} 70 | env: 71 | - name: DEFGUARD_TOKEN 72 | valueFrom: 73 | secretKeyRef: 74 | name: {{ .Values.existingTokenSecret }} 75 | key: {{ .Values.existingTokenSecretKey }} 76 | {{- end }} 77 | {{- if .Values.healthCheck.enabled }} 78 | env: 79 | - name: HEALTH_PORT 80 | value: {{ .Values.healthCheck.port }} 81 | {{- end }} 82 | {{- with .Values.nodeSelector }} 83 | nodeSelector: 84 | {{- toYaml . | nindent 8 }} 85 | {{- end }} 86 | {{- with .Values.affinity }} 87 | affinity: 88 | {{- toYaml . | nindent 8 }} 89 | {{- end }} 90 | {{- with .Values.tolerations }} 91 | tolerations: 92 | {{- toYaml . | nindent 8 }} 93 | {{- end }} 94 | -------------------------------------------------------------------------------- /charts/defguard/templates/defguard-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "defguard.fullname" . }} 5 | labels: 6 | {{- include "defguard.labels" . | nindent 4 }} 7 | spec: 8 | {{- if not .Values.autoscaling.enabled }} 9 | replicas: {{ .Values.replicaCount }} 10 | {{- end }} 11 | selector: 12 | matchLabels: 13 | {{- include "defguard.selectorLabels" . | nindent 6 }} 14 | template: 15 | metadata: 16 | {{- with .Values.podAnnotations }} 17 | annotations: 18 | {{- toYaml . | nindent 8 }} 19 | {{- end }} 20 | labels: 21 | {{- include "defguard.selectorLabels" . | nindent 8 }} 22 | spec: 23 | {{- with .Values.imagePullSecrets }} 24 | imagePullSecrets: 25 | {{- toYaml . | nindent 8 }} 26 | {{- end }} 27 | serviceAccountName: {{ include "defguard.serviceAccountName" . }} 28 | securityContext: 29 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 30 | containers: 31 | - name: {{ .Chart.Name }} 32 | env: 33 | - name: DEFGUARD_DB_PASSWORD 34 | valueFrom: 35 | secretKeyRef: 36 | name: {{ .Values.postgresql.auth.existingSecret }} 37 | key: {{ .Values.postgresql.auth.existingSecretPasswordKey | default "password" }} 38 | - name: DEFGUARD_AUTH_SECRET 39 | valueFrom: 40 | secretKeyRef: 41 | name: {{ .Values.existingJwtSecret | default (include "defguard.jwtSecretName" .) }} 42 | key: auth 43 | - name: DEFGUARD_GATEWAY_SECRET 44 | valueFrom: 45 | secretKeyRef: 46 | name: {{ .Values.existingJwtSecret | default (include "defguard.jwtSecretName" .) }} 47 | key: gateway 48 | - name: DEFGUARD_YUBIBRIDGE_SECRET 49 | valueFrom: 50 | secretKeyRef: 51 | name: {{ .Values.existingJwtSecret | default (include "defguard.jwtSecretName" .) }} 52 | key: yubi-bridge 53 | - name: DEFGUARD_SECRET_KEY 54 | valueFrom: 55 | secretKeyRef: 56 | name: {{ .Values.existingJwtSecret | default (include "defguard.jwtSecretName" .) }} 57 | key: secret-key 58 | - name: DEFGUARD_OPENID_KEY 59 | value: "/etc/defguard-openid-key.pem" 60 | envFrom: 61 | - configMapRef: 62 | name: {{ include "defguard.fullname" . }}-config 63 | {{- if .Values.additionalEnvFromConfigMap }} 64 | - configMapRef: 65 | name: {{ .Values.additionalEnvFromConfigMap }} 66 | {{- end }} 67 | securityContext: 68 | {{- toYaml .Values.securityContext | nindent 12 }} 69 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 70 | imagePullPolicy: {{ .Values.image.pullPolicy }} 71 | ports: 72 | - name: http 73 | containerPort: {{ .Values.service.web.port }} 74 | protocol: TCP 75 | - name: grpc 76 | containerPort: {{ .Values.service.grpc.port }} 77 | protocol: TCP 78 | livenessProbe: 79 | httpGet: 80 | path: /api/v1/health 81 | port: http 82 | readinessProbe: 83 | httpGet: 84 | path: /api/v1/health 85 | port: http 86 | resources: 87 | {{- toYaml .Values.resources | nindent 12 }} 88 | volumeMounts: 89 | - name: openid-key 90 | mountPath: "/etc/defguard-openid-key.pem" 91 | readOnly: true 92 | subPath: openid-key 93 | {{- with .Values.nodeSelector }} 94 | nodeSelector: 95 | {{- toYaml . | nindent 8 }} 96 | {{- end }} 97 | {{- with .Values.affinity }} 98 | affinity: 99 | {{- toYaml . | nindent 8 }} 100 | {{- end }} 101 | {{- with .Values.tolerations }} 102 | tolerations: 103 | {{- toYaml . | nindent 8 }} 104 | {{- end }} 105 | volumes: 106 | - name: openid-key 107 | secret: 108 | secretName: {{ .Values.existingOpenIdSecret | default (include "defguard.openidSecretName" .) }} 109 | optional: false 110 | -------------------------------------------------------------------------------- /docker-compose/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | db: 5 | image: postgres:17-alpine 6 | restart: unless-stopped 7 | environment: 8 | POSTGRES_DB: defguard 9 | POSTGRES_USER: defguard 10 | POSTGRES_PASSWORD: ${DEFGUARD_DB_PASSWORD} 11 | volumes: 12 | - ${VOLUME_DIR:-./.volumes}/db:/var/lib/postgresql/data 13 | # ports: 14 | # - "5432:5432" 15 | 16 | # caddy: # [PROXY] 17 | # image: caddy:2-alpine # [PROXY] 18 | # restart: unless-stopped # [PROXY] 19 | # volumes: # [PROXY] 20 | # - ${VOLUME_DIR:-./.volumes}/caddy/data:/data # [PROXY] 21 | # - ${VOLUME_DIR:-./.volumes}/caddy/config:/config # [PROXY] 22 | # - ${VOLUME_DIR:-./.volumes}/caddy/Caddyfile:/etc/caddy/Caddyfile # [PROXY] 23 | # ports: # [PROXY] 24 | # # http # [PROXY] 25 | # - "80:80" # [PROXY] 26 | # # https # [PROXY] 27 | # - "443:443" # [PROXY] 28 | 29 | core: 30 | image: ghcr.io/defguard/defguard:${CORE_IMAGE_TAG:-latest} 31 | restart: unless-stopped 32 | environment: 33 | DEFGUARD_AUTH_SECRET: ${DEFGUARD_AUTH_SECRET} 34 | DEFGUARD_GATEWAY_SECRET: ${DEFGUARD_GATEWAY_SECRET} 35 | DEFGUARD_YUBIBRIDGE_SECRET: ${DEFGUARD_YUBIBRIDGE_SECRET} 36 | DEFGUARD_SECRET_KEY: ${DEFGUARD_SECRET_KEY} 37 | DEFGUARD_DEFAULT_ADMIN_PASSWORD: ${DEFGUARD_DEFAULT_ADMIN_PASSWORD} 38 | DEFGUARD_DB_HOST: db 39 | DEFGUARD_DB_PORT: 5432 40 | DEFGUARD_DB_USER: defguard 41 | DEFGUARD_DB_PASSWORD: ${DEFGUARD_DB_PASSWORD} 42 | DEFGUARD_DB_NAME: defguard 43 | DEFGUARD_URL: ${DEFGUARD_URL} 44 | DEFGUARD_LOG_LEVEL: info 45 | DEFGUARD_WEBAUTHN_RP_ID: ${DEFGUARD_WEBAUTHN_RP_ID} 46 | DEFGUARD_COOKIE_INSECURE: ${DEFGUARD_COOKIE_INSECURE:-false} 47 | # DEFGUARD_ENROLLMENT_URL: ${DEFGUARD_ENROLLMENT_URL} # [ENROLLMENT] 48 | # DEFGUARD_PROXY_URL: https://proxy:50052 # [ENROLLMENT] 49 | # DEFGUARD_PROXY_GRPC_CA: /ssl/defguard-ca.pem # [ENROLLMENT] 50 | DEFGUARD_GRPC_CERT: /ssl/defguard-grpc.crt 51 | DEFGUARD_GRPC_KEY: /ssl/defguard-grpc.key 52 | ## RSA setup guide: https://docs.defguard.net/deployment-strategies/openid-rsa-key 53 | DEFGUARD_OPENID_KEY: /keys/rsakey.pem 54 | ports: 55 | # web 56 | # - "8000:8000" 57 | # grpc 58 | - "50055:50055" 59 | depends_on: 60 | - db 61 | volumes: 62 | # SSL setup guide: https://docs.defguard.net/deployment-strategies/grpc-ssl-communication#custom-ssl-ca-and-certificates 63 | - ${VOLUME_DIR:-./.volumes}/ssl:/ssl 64 | ## RSA setup guide: https://docs.defguard.net/deployment-strategies/openid-rsa-key 65 | - ${VOLUME_DIR:-./.volumes}/core/rsakey.pem:/keys/rsakey.pem 66 | 67 | # proxy: # [ENROLLMENT] 68 | # image: ghcr.io/defguard/defguard-proxy:${PROXY_IMAGE_TAG:-latest} # [ENROLLMENT] 69 | # restart: unless-stopped # [ENROLLMENT] 70 | # environment: # [ENROLLMENT] 71 | # DEFGUARD_PROXY_GRPC_PORT: 50052 # [ENROLLMENT] 72 | # DEFGUARD_PROXY_GRPC_CERT: /ssl/defguard-proxy-grpc.crt # [ENROLLMENT] 73 | # DEFGUARD_PROXY_GRPC_KEY: /ssl/defguard-proxy-grpc.key # [ENROLLMENT] 74 | # DEFGUARD_PROXY_URL: ${DEFGUARD_ENROLLMENT_URL} # [ENROLLMENT] 75 | # volumes: # [ENROLLMENT] 76 | # SSL setup guide: https://docs.defguard.net/deployment-strategies/grpc-ssl-communication#custom-ssl-ca-and-certificates 77 | # - ${VOLUME_DIR:-./.volumes}/ssl:/ssl # [ENROLLMENT] 78 | # ports: 79 | # # web 80 | # - "8080:8080" 81 | # depends_on: # [ENROLLMENT] 82 | # - core # [ENROLLMENT] 83 | 84 | # gateway: # [VPN] 85 | # image: ghcr.io/defguard/gateway:${GATEWAY_IMAGE_TAG:-latest} # [VPN] 86 | # restart: unless-stopped # [VPN] 87 | # network_mode: "host" # [VPN] 88 | # environment: # [VPN] 89 | # DEFGUARD_GRPC_URL: https://localhost:50055 # [VPN] 90 | # DEFGUARD_GRPC_CA: /ssl/defguard-ca.pem # [VPN] 91 | # DEFGUARD_STATS_PERIOD: 30 # [VPN] 92 | # DEFGUARD_TOKEN: ${DEFGUARD_TOKEN} # [VPN] 93 | ## This makes our rules run before Docker's rules to avoid conflicts 94 | # DEFGUARD_FW_PRIORITY: -1 # [VPN] 95 | # volumes: # [VPN] 96 | # SSL setup guide: https://docs.defguard.net/deployment-strategies/grpc-ssl-communication#custom-ssl-ca-and-certificates 97 | # - ${VOLUME_DIR:-./.volumes}/ssl:/ssl # [VPN] 98 | # cap_add: # [VPN] 99 | # - NET_ADMIN # [VPN] 100 | -------------------------------------------------------------------------------- /terraform/modules/gateway/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | LOG_FILE="/var/log/defguard.log" 5 | 6 | log() { 7 | echo "$(date '+%Y-%m-%d %H:%M:%S') $1" 8 | } 9 | 10 | LOG_FILE="/var/log/defguard.log" 11 | 12 | log() { 13 | echo "$(date '+%Y-%m-%d %H:%M:%S') $1" 14 | } 15 | 16 | base64url_encode() { 17 | echo -n "$1" | openssl base64 -e -A | tr '+/' '-_' | tr -d '=' 18 | } 19 | 20 | ( 21 | log "Updating apt repositories..." 22 | apt update 23 | 24 | log "Installing curl..." 25 | apt install -y curl 26 | 27 | log "Downloading defguard-gateway package..." 28 | curl -fsSL -o /tmp/defguard-gateway.deb https://github.com/DefGuard/gateway/releases/download/v${package_version}/defguard-gateway_${package_version}_${arch}-unknown-linux-gnu.deb 29 | 30 | log "Installing defguard-gateway package..." 31 | dpkg -i /tmp/defguard-gateway.deb 32 | 33 | log "Generating gateway token..." 34 | NETWORK_ID="${network_id}" 35 | SECRET="${gateway_secret}" 36 | ISSUER="DefGuard" 37 | 38 | HEADER='{"alg":"HS256","typ":"JWT"}' 39 | NOW=$(date +%s) 40 | EXPIRATION=$(($NOW + 315360000)) 41 | PAYLOAD=$(cat <&1 | tee -a "$LOG_FILE" 145 | -------------------------------------------------------------------------------- /charts/defguard/values.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # this should be a URL based on defguard-proxy.ingress.grpc.host 3 | proxyUrl: "" 4 | # this should be a URL based on ingress.web.host 5 | publicUrl: "http://defguard.local" 6 | # defguard-core pod autoscaling configuration 7 | autoscaling: 8 | enabled: false 9 | minReplicas: 1 10 | maxReplicas: 10 11 | # defguard-core cookie configuration 12 | cookie: 13 | domain: "" 14 | insecure: false 15 | # Defguard full name override 16 | fullnameOverride: "" 17 | # Defguard name override 18 | nameOverride: "" 19 | # Defguard-core container image configuration 20 | image: 21 | pullPolicy: IfNotPresent 22 | repository: ghcr.io/defguard/defguard 23 | tag: "" # overrides .Chart.AppVersion 24 | # Defguard-core container image pull secrets 25 | imagePullSecrets: [] 26 | # Defguard-core ingress configuration 27 | ingress: 28 | grpc: 29 | annotations: {} 30 | className: "" 31 | enabled: true 32 | host: defguard-grpc.local 33 | labels: {} 34 | tls: false 35 | web: 36 | annotations: {} 37 | className: "" 38 | enabled: true 39 | host: defguard.local 40 | labels: {} 41 | tls: false 42 | # defguard-core existing JWT secret 43 | existingJwtSecret: "" 44 | # defguard-core pod affinity configuration 45 | affinity: {} 46 | # defguard-core node selector cnfiguration 47 | nodeSelector: {} 48 | # defguard-core existing OpenID secret 49 | existingOpenIdSecret: "" 50 | # defguard-core pod annotation 51 | podAnnotations: {} 52 | # defguard-core pod labels 53 | podLabels: {} 54 | # defguard-core pod security context 55 | podSecurityContext: {} 56 | # defguard-core container security context 57 | securityContext: {} 58 | # defguard-core container replica count 59 | replicaCount: 1 60 | # defguard-core pod resource configuration 61 | resources: {} 62 | # defguard-core service configuration 63 | service: 64 | grpc: 65 | annotations: 66 | traefik.ingress.kubernetes.io/service.serversscheme: h2c 67 | labels: {} 68 | port: 50055 69 | type: ClusterIP 70 | web: 71 | annotations: {} 72 | labels: {} 73 | port: 80 74 | type: ClusterIP 75 | # defguard-core serviceaccount configuration 76 | serviceAccount: 77 | annotations: {} 78 | create: true 79 | # defguard-core pod tolerations 80 | tolerations: [] 81 | # defguard-core additional ENV config from config map 82 | additionalEnvFromConfigMap: "" 83 | 84 | # 85 | # sub-chart bitnami/postgresql 86 | # 87 | postgresql: 88 | enabled: true 89 | host: "" # set if using external postgresql ~ enabled: false 90 | port: 5432 91 | auth: 92 | database: defguard 93 | existingSecret: postgres-password 94 | existingSecretPasswordKey: "" # set if using external postgresql ~ enabled: false 95 | username: defguard 96 | image: 97 | repository: bitnami/postgresql 98 | tag: latest # IMPORTANT: set to a specific tag to avoid issues with major version upgrades 99 | 100 | # 101 | # sub-chart defguard-proxy 102 | # 103 | defguard-proxy: 104 | # defguard-proxy is turned off by default. enable to allow use of the enrollment interface 105 | enabled: false 106 | # this should be a URL based on defguard-proxy.ingress.web.host 107 | publicUrl: "http://enrollment.local" 108 | # defguard-proxy full name override 109 | fullnameOverride: "" 110 | # defguard-proxy name override 111 | nameOverride: "" 112 | # defguard-proxy pod autoscaling configuration 113 | autoscaling: 114 | enabled: false 115 | minReplicas: 1 116 | maxReplicas: 10 117 | # defguard-proxy container image configuration 118 | image: 119 | pullPolicy: IfNotPresent 120 | repository: ghcr.io/defguard/defguard-proxy 121 | tag: "" # overrides .Chart.AppVersion 122 | # defguard-proxy container image pull secrets configuration 123 | imagePullSecrets: [] 124 | # defguard-proxy ingress configuration 125 | ingress: 126 | grpc: 127 | annotations: {} 128 | className: "" 129 | enabled: true 130 | host: enrollment-grpc.local 131 | labels: {} 132 | tls: false 133 | web: 134 | annotations: {} 135 | className: "" 136 | enabled: true 137 | host: enrollment.local 138 | labels: {} 139 | tls: false 140 | # defguard-proxy pod affinity 141 | affinity: {} 142 | # defguard-proxy pod node selection 143 | nodeSelector: {} 144 | # defguard-proxy pod tolerations 145 | tolerations: [] 146 | # defguard-proxy pod annotations 147 | podAnnotations: {} 148 | # defguard-proxy pod labels 149 | podLabels: {} 150 | # defguard-proxy pod security context 151 | podSecurityContext: {} 152 | # defguard-proxy container security context 153 | securityContext: {} 154 | # defguard-proxy container replica count 155 | replicaCount: 1 156 | # defguard-proxy pod resource configuration 157 | resources: {} 158 | # defguard-proxy service configuration 159 | service: 160 | grpc: 161 | annotations: 162 | traefik.ingress.kubernetes.io/service.serversscheme: h2c 163 | labels: {} 164 | port: 50051 165 | type: ClusterIP 166 | web: 167 | annotations: {} 168 | labels: {} 169 | port: 8080 170 | type: ClusterIP 171 | # defguard-proxy service account configuration 172 | serviceAccount: 173 | annotations: {} 174 | create: true 175 | # defguard-proxy additional ENV from configmap 176 | additionalEnvFromConfigMap: "" 177 | 178 | # 179 | # sub-chart defguard-gateway 180 | # 181 | defguard-gateway: 182 | enabled: false 183 | # Use userspace wireguard implementation, useful on systems without native wireguard support. Set to true/false 184 | userspace: "false" 185 | # Defguard GRPC URL, e.g.: defguard-grpc.mycompany.com 186 | grpcUrl: "" 187 | # Token from Defguard app to secure gRPC connection, available on network page. 188 | # It is not recommended to use this. Create a secret yourself and use existingTokenSecret instead 189 | token: "" 190 | # Secret to get the token from 191 | existingTokenSecret: "" 192 | # Key to extract the token from in existingTokenSecret 193 | existingTokenSecretKey: "" 194 | # Defines how often (in seconds) should interface statistics be sent to Defguard server 195 | statsPeriod: 30 196 | # rust log level, default is debug 197 | logLevel: "debug" 198 | # defguard-gateway full name override 199 | fullnameOverride: "" 200 | # defguard-gateway name override 201 | nameOverride: "" 202 | # defguard-gateway container image configuration 203 | image: 204 | pullPolicy: IfNotPresent 205 | repository: ghcr.io/defguard/gateway 206 | tag: "" # overrides .Chart.AppVersion 207 | # defguard-gateway container image pull secrets 208 | imagePullSecrets: [] 209 | # defguard-gateway pod affinity configuration 210 | affinity: {} 211 | # defguard-gateway node selector configuration 212 | nodeSelector: {} 213 | # defguard-gateway pod tolerations 214 | tolerations: [] 215 | # defguard-gateway pod annotations 216 | podAnnotations: {} 217 | # defguard-gateway pod labels 218 | podLabels: {} 219 | # defguard-gateway pod replica count 220 | replicaCount: 1 221 | # defguard-gateway pod resources 222 | resources: {} 223 | # defguard-gateway pod security context 224 | podSecurityContext: {} 225 | # defguard-gateway container security context 226 | securityContext: {} 227 | # defguard-gateway pod additional ENV from configmap 228 | additionalEnvFromConfigMap: "" 229 | # defguard-gateway service configuration 230 | service: 231 | wireguard: 232 | annotations: {} 233 | labels: {} 234 | port: 32140 235 | type: ClusterIP 236 | # defguard-gateway serviceaccount configuration 237 | serviceAccount: 238 | annotations: {} 239 | create: true 240 | -------------------------------------------------------------------------------- /terraform/examples/basic/main.tf.example: -------------------------------------------------------------------------------- 1 | locals { 2 | ############################ AWS configuration ############################ 3 | 4 | # The AWS region where the Defguard infrastructure will be deployed. 5 | region = "eu-north-1" 6 | 7 | ############################ Core configuration ########################### 8 | 9 | # The URL for the Defguard Core. This is the URL under which the Defguard Core should be accessible. 10 | core_url = "https://defguard.example.com" 11 | 12 | # The gRPC port for the Defguard Core. This is the port that the core will use to communicate with Defguard Gateways. 13 | core_grpc_port = 50055 14 | 15 | # The HTTP port for the Defguard Core web UI. This is the port that the core will listen on for incoming HTTP requests. 16 | core_http_port = 8000 17 | 18 | # Whether to allow insecure cookies for the Defguard Core web UI. Set to true if you 19 | # are using HTTP to access the Defguard Core web UI. 20 | core_cookie_insecure = false 21 | 22 | # The initial password for the "admin" user. Use it to login to the Defguard Core web UI for the first time. 23 | default_admin_password = "pass123" 24 | 25 | # The deb package version of the Defguard Core that will be installed on the instance. 26 | # Must be a valid, released version of Defguard Core. 27 | core_package_version = "1.6.0" 28 | 29 | # The architecture of the Defguard Core server instance. 30 | # Supported values: "x86_64", "aarch64" 31 | core_arch = "x86_64" 32 | 33 | # The instance type for the Defguard Core server. 34 | core_instance_type = "t3.micro" 35 | 36 | ########################### Proxy configuration ########################### 37 | 38 | # The URL for the Defguard Proxy. This is the URL under which the Defguard Proxy should be accessible. 39 | proxy_url = "https://proxy.example.com" 40 | 41 | # The gRPC port for the Defguard Proxy. This is the port that the proxy will use to communicate with the Defguard Core. 42 | proxy_grpc_port = 50051 43 | 44 | # The HTTP port for the Defguard Proxy. This is the port that the proxy will listen on for incoming HTTP requests. 45 | proxy_http_port = 8000 46 | 47 | # The deb package version of the proxy that will be installed on the instance. 48 | # Must be a valid, released version of Defguard Proxy. 49 | proxy_package_version = "1.6.0" 50 | 51 | # The architecture of the Defguard Proxy server instance. 52 | # Supported values: "x86_64", "aarch64" 53 | proxy_arch = "x86_64" 54 | 55 | # The instance type for the Defguard Proxy server. 56 | proxy_instance_type = "t3.micro" 57 | 58 | ###################### VPN and Gateway configuration ###################### 59 | 60 | # List of VPN networks to be created. A gateway will be created for each network. 61 | vpn_networks = [ 62 | { 63 | # The ID of the VPN network. Must start with 1 and be incremented for each new network. 64 | # Used for modifying the network configuration later. 65 | id = 1 66 | # The name of the VPN network. Displayed to the users. 67 | name = "vpn1" 68 | # The CIDR address of the VPN network. Clients will be assigned IP addresses from this range. 69 | address = "10.10.10.1/24" 70 | # The UDP port for the WireGuard VPN. Gateways will listen on this port for incoming VPN connections. 71 | port = 51820 72 | # Whether to enable NAT for the VPN network. If true, the gateway will perform NAT (masquerade) for the VPN clients. 73 | # This is useful if you want the VPN clients to access the internet through the gateway or to access other resources in the VPC. 74 | nat = true 75 | } 76 | ] 77 | 78 | # The gateway deb package version that will be installed on the instance. 79 | # Must be a valid, released version of Defguard Gateway. 80 | gateway_package_version = "1.6.0" 81 | 82 | # The architecture of the Defguard Gateway server instance. 83 | # Supported values: "x86_64", "aarch64" 84 | gateway_arch = "x86_64" 85 | 86 | # The instance type for the Defguard Gateway server. 87 | gateway_instance_type = "t3.micro" 88 | 89 | ########################## Database configuration ######################### 90 | 91 | # The name of the database that will be created for the Defguard Core. 92 | db_name = "defguard" 93 | 94 | # The username for the database that will be created for the Defguard Core. 95 | db_username = "defguard" 96 | 97 | # The port on which the database will listen for incoming connections. 98 | db_port = 5432 99 | 100 | # The password for the database user. This will be used by the Defguard Core to connect to the database. 101 | db_password = "defguard" 102 | 103 | # The amount of storage allocated for the database in GB. The minimum amount for this example required by AWS is 20 GB. 104 | db_storage = 20 # GB 105 | 106 | # The instance class for the database. This defines the performance characteristics of the database instance. 107 | db_instance_class = "db.t3.micro" 108 | 109 | ############################ VPC configuration ############################ 110 | 111 | # The name of the VPC that will be created for the Defguard infrastructure. 112 | vpc_name = "defguard-vpc" 113 | 114 | # The CIDR block for the VPC. This defines the IP address range for the VPC and its subnets. 115 | vpc_cidr = "10.0.0.0/16" 116 | 117 | # The tags to be applied to the Defguard VPC. 118 | vpc_tags = { 119 | Name = local.vpc_name 120 | } 121 | 122 | # The private subnets for the VPC. These subnets are used for the database instance. 123 | # Note: 2 subnets are required for high availability of the database instance. 124 | vpc_private_subnets = ["10.0.2.0/24", "10.0.3.0/24"] 125 | 126 | # The subnets for the VPC that will be used for the Defguard Core, proxy, and gateways. 127 | vpc_public_subnets = ["10.0.1.0/24"] 128 | 129 | # The availability zones for the VPC. This is used mainly for the database instance to ensure high availability. 130 | azs = ["eu-north-1a", "eu-north-1b"] 131 | } 132 | 133 | variable "aws_access_key" { 134 | description = "AWS access key" 135 | type = string 136 | } 137 | 138 | variable "aws_secret_key" { 139 | description = "AWS secret key" 140 | type = string 141 | } 142 | 143 | terraform { 144 | required_providers { 145 | aws = { 146 | source = "hashicorp/aws" 147 | version = "~> 6.0" 148 | } 149 | } 150 | } 151 | 152 | data "aws_ami" "ubuntu" { 153 | most_recent = true 154 | owners = ["099720109477"] 155 | 156 | filter { 157 | name = "name" 158 | values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"] 159 | } 160 | } 161 | 162 | provider "aws" { 163 | region = local.region 164 | access_key = var.aws_access_key 165 | secret_key = var.aws_secret_key 166 | } 167 | 168 | resource "random_password" "gateway_secret" { 169 | length = 64 170 | special = false 171 | } 172 | 173 | module "defguard_core" { 174 | # source = "../../modules/core" 175 | source = "github.com/DefGuard/deployment//terraform/modules/core?ref=main" 176 | instance_type = local.core_instance_type 177 | package_version = local.core_package_version 178 | arch = local.core_arch 179 | ami = data.aws_ami.ubuntu.id 180 | 181 | core_url = local.core_url 182 | proxy_grpc_port = local.proxy_grpc_port 183 | proxy_url = local.proxy_url 184 | grpc_port = local.core_grpc_port 185 | http_port = local.core_http_port 186 | cookie_insecure = local.core_cookie_insecure 187 | vpn_networks = [for network in local.vpn_networks : { 188 | id = network.id 189 | name = network.name 190 | address = network.address 191 | port = network.port 192 | endpoint = aws_eip.defguard_gateway_endpoint[network.id - 1].public_ip 193 | }] 194 | db_details = { 195 | name = local.db_name 196 | username = local.db_username 197 | password = local.db_password 198 | port = local.db_port 199 | address = aws_db_instance.defguard_core_db.address 200 | } 201 | proxy_address = module.defguard_proxy.proxy_private_address 202 | gateway_secret = random_password.gateway_secret.result 203 | network_interface_id = aws_network_interface.defguard_core_network_interface.id 204 | # log_level = "info" 205 | } 206 | 207 | module "defguard_proxy" { 208 | # source = "../../modules/proxy" 209 | source = "github.com/DefGuard/deployment//terraform/modules/proxy?ref=main" 210 | 211 | instance_type = local.proxy_instance_type 212 | package_version = local.proxy_package_version 213 | arch = local.proxy_arch 214 | grpc_port = local.proxy_grpc_port 215 | http_port = local.proxy_http_port 216 | proxy_url = local.proxy_url 217 | # log_level = "info" 218 | 219 | ami = data.aws_ami.ubuntu.id 220 | network_interface_id = aws_network_interface.defguard_proxy_network_interface.id 221 | } 222 | 223 | module "defguard_gateway" { 224 | count = length(local.vpn_networks) 225 | # source = "../../modules/gateway" 226 | source = "github.com/DefGuard/deployment//terraform/modules/gateway?ref=main" 227 | 228 | ami = data.aws_ami.ubuntu.id 229 | instance_type = local.gateway_instance_type 230 | package_version = local.gateway_package_version 231 | arch = local.gateway_arch 232 | 233 | core_grpc_port = local.core_grpc_port 234 | nat = local.vpn_networks[count.index].nat 235 | network_id = local.vpn_networks[count.index].id 236 | gateway_secret = random_password.gateway_secret.result 237 | network_interface_id = aws_network_interface.defguard_gateway_network_interface[count.index].id 238 | core_address = aws_network_interface.defguard_core_network_interface.private_ip 239 | # log_level = "info" 240 | 241 | depends_on = [module.defguard_core] 242 | } 243 | 244 | module "vpc" { 245 | source = "terraform-aws-modules/vpc/aws" 246 | 247 | name = local.vpc_name 248 | cidr = local.vpc_cidr 249 | azs = local.azs 250 | private_subnets = local.vpc_private_subnets 251 | public_subnets = local.vpc_public_subnets 252 | 253 | enable_dns_hostnames = true 254 | tags = local.vpc_tags 255 | } 256 | 257 | ########################################################################### 258 | ####################### Core database configuration ####################### 259 | ########################################################################### 260 | 261 | resource "aws_db_instance" "defguard_core_db" { 262 | engine = "postgres" 263 | instance_class = local.db_instance_class 264 | username = local.db_username 265 | password = local.db_password 266 | db_name = local.db_name 267 | port = local.db_port 268 | skip_final_snapshot = true 269 | allocated_storage = local.db_storage 270 | db_subnet_group_name = aws_db_subnet_group.defguard.name 271 | vpc_security_group_ids = [aws_security_group.defguard_db_sg.id] 272 | parameter_group_name = aws_db_parameter_group.defguard_db_parameter_group.name 273 | } 274 | 275 | resource "aws_db_parameter_group" "defguard_db_parameter_group" { 276 | name = "defguard-db-parameter-group" 277 | family = "postgres17" 278 | 279 | parameter { 280 | name = "rds.force_ssl" 281 | value = "0" 282 | } 283 | } 284 | 285 | resource "aws_db_subnet_group" "defguard" { 286 | name = "defguard-db-subnet-group" 287 | subnet_ids = module.vpc.private_subnets 288 | } 289 | 290 | ########################################################################### 291 | ######################## Core network configuration ####################### 292 | ########################################################################### 293 | 294 | # Public IP address for the Defguard Core instance 295 | # Remove this if you want to use a private IP only 296 | # or you are running Defguard behind a reverse proxy. 297 | resource "aws_eip" "defguard_core_endpoint" { 298 | domain = "vpc" 299 | } 300 | 301 | resource "aws_eip_association" "defguard_core_endpoint_association" { 302 | network_interface_id = aws_network_interface.defguard_core_network_interface.id 303 | allocation_id = aws_eip.defguard_core_endpoint.id 304 | } 305 | 306 | resource "aws_security_group" "defguard_core_sg" { 307 | name = "defguard-core-sg" 308 | description = "Core access" 309 | vpc_id = module.vpc.vpc_id 310 | 311 | ########################### General access rules ########################## 312 | 313 | # (optional) SSH access to the Defguard Core server instance 314 | # ingress { 315 | # from_port = 22 316 | # to_port = 22 317 | # protocol = "tcp" 318 | # cidr_blocks = ["0.0.0.0/0"] 319 | # } 320 | 321 | # HTTP access to the Defguard Core web UI from connected VPN clients 322 | # Remove this rule if you want to run core only behind a reverse proxy 323 | # Note that the core access should be limited to the VPN clients and internal networks only. 324 | ingress { 325 | from_port = local.core_http_port 326 | to_port = local.core_http_port 327 | protocol = "tcp" 328 | cidr_blocks = [ 329 | for eip in aws_eip.defguard_gateway_endpoint : "${eip.public_ip}/32" 330 | ] 331 | } 332 | 333 | # Example access from a reverse proxy 334 | # ingress { 335 | # from_port = local.core_http_port 336 | # to_port = local.core_http_port 337 | # protocol = "tcp" 338 | # security_groups = [ ] 339 | # } 340 | 341 | ########## Security group rules essential for Defguard operation ########## 342 | 343 | # gRPC communication with Defguard Gateways 344 | ingress { 345 | from_port = local.core_grpc_port 346 | to_port = local.core_grpc_port 347 | protocol = "tcp" 348 | security_groups = [ 349 | for sg in aws_security_group.defguard_gateway_sg : sg.id 350 | ] 351 | } 352 | 353 | # General egress 354 | egress { 355 | from_port = 0 356 | to_port = 0 357 | protocol = "-1" 358 | cidr_blocks = ["0.0.0.0/0"] 359 | } 360 | } 361 | 362 | resource "aws_network_interface" "defguard_core_network_interface" { 363 | subnet_id = module.vpc.public_subnets[0] 364 | security_groups = [aws_security_group.defguard_core_sg.id] 365 | 366 | tags = { 367 | Name = "defguard-core-network-interface" 368 | } 369 | } 370 | 371 | ########################################################################### 372 | ###################### Gateway network configuration ###################### 373 | ########################################################################### 374 | 375 | # Public IP addresses for the Defguard Gateway instances 376 | # Gateways must be accessible externally to allow VPN clients to connect. 377 | 378 | resource "aws_eip" "defguard_gateway_endpoint" { 379 | count = length(local.vpn_networks) 380 | domain = "vpc" 381 | } 382 | 383 | resource "aws_eip_association" "defguard_gateway_endpoint_association" { 384 | count = length(local.vpn_networks) 385 | network_interface_id = aws_network_interface.defguard_gateway_network_interface[count.index].id 386 | allocation_id = aws_eip.defguard_gateway_endpoint[count.index].id 387 | } 388 | 389 | resource "aws_security_group" "defguard_gateway_sg" { 390 | count = length(local.vpn_networks) 391 | name = "defguard-gateway-sg-${count.index}" 392 | description = "Gateway access" 393 | vpc_id = module.vpc.vpc_id 394 | 395 | ########################### General access rules ########################## 396 | 397 | # (optional) SSH access to the Defguard Gateway server instance 398 | # ingress { 399 | # from_port = 22 400 | # to_port = 22 401 | # protocol = "tcp" 402 | # cidr_blocks = ["0.0.0.0/0"] 403 | # } 404 | 405 | ########## Security group rules essential for Defguard operation ########## 406 | 407 | # VPN traffic coming from connected clients 408 | ingress { 409 | from_port = local.vpn_networks[count.index].port 410 | to_port = local.vpn_networks[count.index].port 411 | protocol = "udp" 412 | cidr_blocks = ["0.0.0.0/0"] 413 | } 414 | 415 | # General egress 416 | egress { 417 | from_port = 0 418 | to_port = 0 419 | protocol = "-1" 420 | cidr_blocks = ["0.0.0.0/0"] 421 | } 422 | } 423 | 424 | resource "aws_network_interface" "defguard_gateway_network_interface" { 425 | count = length(local.vpn_networks) 426 | subnet_id = module.vpc.public_subnets[0] 427 | security_groups = [aws_security_group.defguard_gateway_sg[count.index].id] 428 | 429 | tags = { 430 | Name = "defguard-gateway-network-interface-${count.index}" 431 | } 432 | } 433 | 434 | ########################################################################### 435 | ####################### Proxy network configuration ####################### 436 | ########################################################################### 437 | 438 | # Public IP address for the Defguard Proxy instance 439 | # Remove this if you want to run proxy behind a reverse proxy 440 | resource "aws_eip" "defguard_proxy_endpoint" { 441 | domain = "vpc" 442 | } 443 | 444 | resource "aws_eip_association" "defguard_proxy_endpoint_association" { 445 | network_interface_id = aws_network_interface.defguard_proxy_network_interface.id 446 | allocation_id = aws_eip.defguard_proxy_endpoint.id 447 | } 448 | 449 | resource "aws_security_group" "defguard_proxy_sg" { 450 | name = "defguard-proxy-sg" 451 | description = "Proxy access" 452 | vpc_id = module.vpc.vpc_id 453 | 454 | ########################### General access rules ########################## 455 | 456 | # (optional) SSH access to the Defguard Proxy server instance 457 | # ingress { 458 | # from_port = 22 459 | # to_port = 22 460 | # protocol = "tcp" 461 | # cidr_blocks = ["0.0.0.0/0"] 462 | # } 463 | 464 | # HTTP access to the Defguard Proxy web UI from anywhere 465 | # Remove this rule if you want to run proxy behind a reverse proxy 466 | # Note that the proxy should be accessible externally to allow Defguard clients 467 | # to communicate through it and to allow the enrollment of new users. 468 | ingress { 469 | from_port = local.proxy_http_port 470 | to_port = local.proxy_http_port 471 | protocol = "tcp" 472 | cidr_blocks = ["0.0.0.0/0"] 473 | } 474 | 475 | # Example access from a reverse proxy 476 | # ingress { 477 | # from_port = local.proxy_http_port 478 | # to_port = local.proxy_http_port 479 | # protocol = "tcp" 480 | # security_groups = [ ] 481 | # } 482 | 483 | ########## Security group rules essential for Defguard operation ########## 484 | 485 | # Internal communication with Defguard Core 486 | ingress { 487 | from_port = local.proxy_grpc_port 488 | to_port = local.proxy_grpc_port 489 | protocol = "tcp" 490 | security_groups = [aws_security_group.defguard_core_sg.id] 491 | } 492 | 493 | # General egress 494 | egress { 495 | from_port = 0 496 | to_port = 0 497 | protocol = "-1" 498 | cidr_blocks = ["0.0.0.0/0"] 499 | } 500 | } 501 | 502 | resource "aws_network_interface" "defguard_proxy_network_interface" { 503 | subnet_id = module.vpc.public_subnets[0] 504 | security_groups = [aws_security_group.defguard_proxy_sg.id] 505 | 506 | tags = { 507 | Name = "defguard-proxy-network-interface" 508 | } 509 | } 510 | 511 | 512 | ########################################################################### 513 | ###################### Database network configuration ##################### 514 | ########################################################################### 515 | 516 | resource "aws_security_group" "defguard_db_sg" { 517 | name = "defguard-db-sg" 518 | description = "Access to the database" 519 | vpc_id = module.vpc.vpc_id 520 | 521 | # Allows access to the database from the Defguard Core instance 522 | ingress { 523 | from_port = local.db_port 524 | to_port = local.db_port 525 | protocol = "tcp" 526 | security_groups = [aws_security_group.defguard_core_sg.id] 527 | } 528 | 529 | tags = { 530 | Name = "defguard-db-sg" 531 | } 532 | } 533 | 534 | ########################################################################### 535 | ################################# Outputs ################################# 536 | ########################################################################### 537 | 538 | output "defguard_core_private_address" { 539 | description = "IP address of Defguard Core instance in the internal network" 540 | value = aws_network_interface.defguard_core_network_interface.private_ip 541 | } 542 | 543 | output "defguard_core_public_address" { 544 | description = "Public IP address of Defguard Core instance" 545 | value = aws_eip.defguard_core_endpoint.public_ip 546 | } 547 | 548 | output "defguard_proxy_public_address" { 549 | description = "Public IP address of Defguard Proxy instance" 550 | value = aws_eip.defguard_proxy_endpoint.public_ip 551 | } 552 | 553 | output "defguard_proxy_private_address" { 554 | description = "Private IP address of Defguard Proxy instance" 555 | value = aws_network_interface.defguard_proxy_network_interface.private_ip 556 | } 557 | 558 | output "defguard_gateway_public_addresses" { 559 | description = "Public IP addresses of Defguard Gateway instances" 560 | value = [for gw in aws_eip.defguard_gateway_endpoint : gw.public_ip] 561 | } 562 | 563 | output "defguard_gateway_private_addresses" { 564 | description = "Private IP addresses of Defguard Gateway instances" 565 | value = [for gw in aws_network_interface.defguard_gateway_network_interface : gw.private_ip] 566 | } 567 | -------------------------------------------------------------------------------- /docker-compose/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # shellcheck shell=bash 3 | 4 | # This is a script that sets up an entire Defguard instance (including core, 5 | # gateway, enrollment proxy and reverse proxy). It's goal is to prepare 6 | # a working instance by running a single command. 7 | 8 | set -o errexit # abort on nonzero exitstatus 9 | set -o pipefail # don't hide errors within pipes 10 | 11 | # Global variables 12 | VERSION="1.2.2" 13 | SECRET_LENGTH=64 14 | PASSWORD_LENGTH=16 15 | 16 | VOLUME_DIR='.volumes' 17 | SSL_DIR="${VOLUME_DIR}/ssl" 18 | RSA_DIR="${VOLUME_DIR}/core" 19 | 20 | COMPOSE_FILE='docker-compose.yaml' 21 | ENV_FILE='.env' 22 | LOG_FILE=$(mktemp setup.log.XXXXXX) 23 | 24 | BASE_COMPOSE_FILE_URL='https://raw.githubusercontent.com/DefGuard/deployment/main/docker-compose/docker-compose.yaml' 25 | BASE_ENV_FILE_URL='https://raw.githubusercontent.com/DefGuard/deployment/main/docker-compose/.env.template' 26 | 27 | if [ "$(uname)" = 'Darwin' ]; then 28 | SED=(sed -i '') 29 | else 30 | SED=(sed -i) 31 | fi 32 | 33 | ##################### 34 | ### MAIN FUNCTION ### 35 | ##################### 36 | 37 | main() { 38 | is_utf_term 39 | is_term_color 40 | tput reset 41 | print_header 42 | 43 | # display help `--help` argument is found 44 | for i in $*; do 45 | test "$i" = '--help' && print_usage && exit 0 46 | 47 | # run non interactive 48 | if [ "$i" = '--non-interactive' ]; then 49 | CFG_NON_INTERACTIVE=1 50 | # we need to remove this element from $* or getopt will return an error 51 | set -- $(remove_element "$i" $*) 52 | fi 53 | 54 | # configure https 55 | if [ "$i" = '--use-https' ]; then 56 | CFG_USE_HTTPS=1 57 | # we need to remove this element from $* or getopt will return an error 58 | set -- $(remove_element "$i" $*) 59 | fi 60 | done 61 | 62 | # 63 | # First let's gather the ENV/command line variables 64 | # 65 | 66 | # load configuration from env variables 67 | load_configuration_from_env 68 | 69 | # load configuration from CLI options 70 | load_configuration_from_cli "$@" 71 | 72 | # load configuration from user inputs 73 | if [ X$CFG_VOLUME_DIR != X ]; then 74 | VOLUME_DIR=${CFG_VOLUME_DIR} 75 | SSL_DIR="${VOLUME_DIR}/ssl" 76 | RSA_DIR="${VOLUME_DIR}/core" 77 | fi 78 | 79 | export VOLUME_DIR 80 | 81 | # set current working directory 82 | WORK_DIR_PATH=$(pwd) 83 | 84 | # set docker compose file directory 85 | PROD_COMPOSE_FILE="${WORK_DIR_PATH}/${COMPOSE_FILE}" 86 | 87 | # We have enough to check the enviromnent 88 | # so check if necessary tools are available 89 | check_environment 90 | 91 | # Set the correct docker image version based on passed arguments/env variables 92 | # either latest, pre-release or dev 93 | setup_docker_image_version 94 | 95 | # Print architecture for debugging purposes 96 | echo " ${TXT_BEGIN} Identified architecture as: $(uname -m)" 97 | 98 | # load configuration from user inputs 99 | if ! [ $CFG_NON_INTERACTIVE ]; then 100 | load_configuration_from_input 101 | fi 102 | 103 | # check that all required configuration options are set 104 | validate_required_variables 105 | 106 | # generate external service URLs based on config 107 | generate_external_urls 108 | 109 | # print out config 110 | print_config 111 | 112 | # setup RSA & SSL keys 113 | setup_keys 114 | 115 | # generate caddyfile 116 | create_caddyfile 117 | 118 | # generate `.env` file 119 | generate_env_file 120 | 121 | # enable insecure cookies if not using HTTPS 122 | if ! [ "$CFG_USE_HTTPS" ]; then 123 | uncomment_feature "HTTP" "${PROD_ENV_FILE}" 124 | fi 125 | 126 | # generate base docker-compose file 127 | fetch_base_compose_file 128 | 129 | # enable reverse proxy in compose file 130 | uncomment_feature "PROXY" "${PROD_COMPOSE_FILE}" 131 | 132 | # enable enrollment service in compose file 133 | if [ "$CFG_ENABLE_ENROLLMENT" ]; then 134 | enable_enrollment 135 | fi 136 | 137 | # fetch latest images 138 | 139 | echo -e " ${TXT_BEGIN} Fetching ${IMAGE_TYPE_NAME} Docker images: " 140 | $COMPOSE_CMD -f "${PROD_COMPOSE_FILE}" --env-file "${PROD_ENV_FILE}" pull 141 | 142 | # enable and setup VPN gateway 143 | if [ "$CFG_ENABLE_VPN" ]; then 144 | enable_vpn_gateway 145 | fi 146 | 147 | # start docker-compose stack 148 | echo " ${TXT_BEGIN} Starting docker-compose stack" 149 | $COMPOSE_CMD -f "${PROD_COMPOSE_FILE}" --env-file "${PROD_ENV_FILE}" up -d 150 | if [ $? -ne 0 ]; then 151 | echo >&2 "ERROR: failed to start docker-compose stack" 152 | exit 1 153 | fi 154 | 155 | print_instance_summary 156 | } 157 | 158 | ######################## 159 | ### HELPER FUNCTIONS ### 160 | ######################## 161 | 162 | check_character_support() { 163 | local char="$1" 164 | echo -e "$char" | grep -q "$char" 165 | } 166 | 167 | is_utf_term() { 168 | if check_character_support "√"; then 169 | TXT_CHECK="✓" 170 | TXT_BEGIN="▶" 171 | TXT_SUB="▷" 172 | TXT_STAR="★" 173 | TXT_X="✗" 174 | TXT_INPUT="✍" 175 | else 176 | TXT_CHECK="+" 177 | TXT_BEGIN=">>" 178 | TXT_SUB=">" 179 | TXT_STAR="*" 180 | TXT_X="x" 181 | TXT_INPUT=" ::" 182 | fi 183 | } 184 | 185 | is_term_color() { 186 | if [[ $TERM == *"256"* ]]; then 187 | C_RED="\033[31m" 188 | C_GREEN="\033[32m" 189 | C_YELLOW="\033[33m" 190 | C_BLUE="\033[34m" 191 | C_WHITE="\033[37m" 192 | C_GREY="\033[90m" 193 | 194 | C_LRED="\033[91m" 195 | C_LGREEN="\033[92m" 196 | C_LYELLOW="\033[93m" 197 | C_LBLUE="\033[94m" 198 | 199 | C_BOLD="\033[1m" 200 | C_ITALICS="\033[3m" 201 | C_BG_GREY="\033[100m" 202 | C_END="\033[0m" 203 | else 204 | C_RED="" 205 | C_GREEN="" 206 | C_YELLOW="" 207 | C_BLUE="" 208 | C_WHITE="" 209 | C_GREY="" 210 | 211 | C_LRED="" 212 | C_LGREEN="" 213 | C_LYELLOW="" 214 | C_LBLUE="" 215 | 216 | C_BOLD="" 217 | C_ITALICS="" 218 | C_BG_GREY="" 219 | C_END="" 220 | fi 221 | } 222 | 223 | # remove array element 224 | remove_element() { 225 | local remove=$1 226 | local result=() 227 | for element in "$@"; do 228 | if [ "$element" != "$remove" ]; then 229 | result+=("$element") 230 | fi 231 | done 232 | echo "${result[@]}" 233 | } 234 | 235 | # Function to convert relative path to absolute path 236 | to_absolute_path() { 237 | local path="$1" 238 | if [ "${path:0:1}" != '/' ]; then 239 | path="$(cd "$(dirname "$path")" && pwd)/$(basename "$path")" 240 | fi 241 | echo ${path} 242 | } 243 | 244 | print_header() { 245 | echo -e "${C_LBLUE}" 246 | cat <<_EOF_ 247 | # 248 | ## # 249 | ## ## # # ## # 250 | ## ## # # # # 251 | # ## # #### # #### ##### #### # # #### ### #### # 252 | # ## ## # ## # ## # # # # # # # # # ## 253 | ## ## # # ######## # # # # # # # # # 254 | # ## ## # # # ## # ##### # # ###### # # # 255 | # ## # # ## # # # # # # # # # # ## 256 | ## ## #### # ##### # ####### #### # #### # # #### # 257 | ## ## # # # 258 | ## # ####### 259 | # 260 | _EOF_ 261 | echo -e "${C_END}" 262 | echo 263 | echo "Defguard docker-compose deployment setup script v${VERSION}" 264 | echo -e "Copyright ©2023-2025 ${C_BOLD}defguard sp. z o.o.${C_END} <${C_BG_GREY}${C_YELLOW}https://defguard.net/${C_END}>" 265 | echo 266 | } 267 | 268 | print_confirmation() { 269 | echo -e " ${C_LGREEN}${TXT_CHECK}${C_END} " 270 | } 271 | 272 | print_usage() { 273 | 274 | echo "Usage: ${BASENAME} [options]" 275 | echo 276 | echo 'Available options:' 277 | echo 278 | echo -e "\t--help this help message" 279 | echo -e "\t--non-interactive run in non-interactive mode - !REQUIRES SETTING all options/env vars" 280 | echo -e "\t--domain domain where Defguard web UI will be available" 281 | echo -e "\t--enrollment-domain domain where enrollment service will be available" 282 | echo -e "\t--use-https configure reverse proxy to use HTTPS" 283 | echo -e "\t--volume Docker volumes directory - default: ${VOLUME_DIR}" 284 | echo -e "\t--vpn-name VPN location name" 285 | echo -e "\t--vpn-ip
VPN server address & netmask (e.g. 10.0.50.1/24)" 286 | echo -e "\t--vpn-gateway-ip VPN gateway external IP (! NOT DOMAIN - IP)" 287 | echo -e "\t--vpn-gateway-port VPN gateway external port (your clients connect here)" 288 | echo -e "\t--dev use development images" 289 | echo -e "\t--pre-release use pre-release images" 290 | echo 291 | } 292 | 293 | command_exists() { 294 | local command="$1" 295 | command -v "$command" >/dev/null 2>&1 296 | } 297 | 298 | command_exists_check() { 299 | local command="$1" 300 | if ! command_exists "$command"; then 301 | echo >&2 "ERROR: $command command not found" 302 | echo >&2 "ERROR: dependency failed, exiting..." 303 | exit 2 304 | fi 305 | } 306 | 307 | check_environment() { 308 | echo -n " ${TXT_BEGIN} Checking if all required tools are available..." 309 | # compose can be provided by newer docker versions or a separate docker-compose 310 | docker compose version >/dev/null 2>&1 311 | if [ $? = 0 ]; then 312 | COMPOSE_CMD="docker compose" 313 | else 314 | if command_exists docker-compose; then 315 | COMPOSE_CMD="docker-compose" 316 | else 317 | echo 318 | echo >&2 "ERROR: docker-compose or docker compose command not found" 319 | echo >&2 "ERROR: dependency failed, exiting..." 320 | exit 3 321 | fi 322 | fi 323 | 324 | command_exists_check openssl 325 | command_exists_check curl 326 | command_exists_check grep 327 | 328 | # Check if the volume dir is an absolute path since docker requires it 329 | VOLUME_DIR=$(to_absolute_path "${VOLUME_DIR}") 330 | 331 | if [ -d ${VOLUME_DIR} ]; then 332 | echo 333 | echo >&2 "ERROR: volume directory: ${VOLUME_DIR} exists." 334 | echo >&2 "ERROR: this means the configuration, database and certificates would be overwritten." 335 | echo >&2 "ERROR: please backup or remove the volume directory." 336 | exit 3 337 | fi 338 | 339 | if [ -f "$PROD_COMPOSE_FILE" ]; then 340 | echo 341 | echo >&2 "ERROR: docker compose file: ${PROD_COMPOSE_FILE} already exists." 342 | echo >&2 "ERROR: this means the previous configuration would be overwritten." 343 | echo >&2 "ERROR: please backup or remove the docker compose file." 344 | exit 3 345 | fi 346 | 347 | # create all necessary directories 348 | for dir in ${VOLUME_DIR} ${SSL_DIR} ${RSA_DIR}; do 349 | mkdir ${dir} 350 | if [ $? -ne 0 ]; then 351 | echo >&2 "ERROR: cloud not create volume directory: ${dir}" 352 | exit 3 353 | fi 354 | done 355 | 356 | print_confirmation 357 | } 358 | 359 | setup_docker_image_version() { 360 | if [[ $CFG_DEV == 1 ]]; then 361 | IMAGE_TYPE_NAME="${C_RED}development${C_END}" 362 | CORE_IMAGE_TAG="dev" 363 | GATEWAY_IMAGE_TAG="dev" 364 | PROXY_IMAGE_TAG="dev" 365 | elif [[ $CFG_PRE_RELEASE == 1 ]]; then 366 | IMAGE_TYPE_NAME="${C_YELLOW}pre-release${C_END}" 367 | CORE_IMAGE_TAG="pre-release" 368 | GATEWAY_IMAGE_TAG="pre-release" 369 | PROXY_IMAGE_TAG="pre-release" 370 | else 371 | IMAGE_TYPE_NAME="${C_GREEN}latest production${C_END}" 372 | CORE_IMAGE_TAG="${CORE_IMAGE_TAG:-latest}" 373 | GATEWAY_IMAGE_TAG="${GATEWAY_IMAGE_TAG:-latest}" 374 | PROXY_IMAGE_TAG="${PROXY_IMAGE_TAG:-latest}" 375 | fi 376 | 377 | echo -e " ${TXT_BEGIN} ${IMAGE_TYPE_NAME} Docker images will be used" 378 | } 379 | 380 | load_configuration_from_env() { 381 | echo -n " ${TXT_BEGIN} Loading configuration from environment variables... " 382 | # required variables 383 | CFG_DOMAIN="$DEFGUARD_DOMAIN" 384 | 385 | # optional variables 386 | CFG_VOLUME_DIR="$DEFGUARD_VOLUME_DIR" 387 | CFG_VPN_NAME="$DEFGUARD_VPN_NAME" 388 | CFG_VPN_IP="$DEFGUARD_VPN_IP" 389 | CFG_VPN_GATEWAY_IP="$DEFGUARD_VPN_GATEWAY_IP" 390 | CFG_VPN_GATEWAY_PORT="$DEFGUARD_VPN_GATEWAY_PORT" 391 | CFG_ENROLLMENT_DOMAIN="$DEFGUARD_ENROLLMENT_DOMAIN" 392 | CFG_PRE_RELEASE="$DEFGUARD_PRE_RELEASE" 393 | CFG_DEV="$DEFGUARD_DEV" 394 | if ! [ $CFG_USE_HTTPS ]; then 395 | CFG_USE_HTTPS="$DEFGUARD_USE_HTTPS" 396 | fi 397 | 398 | print_confirmation 399 | } 400 | 401 | load_configuration_from_cli() { 402 | echo -n " ${TXT_BEGIN} Loading configuration from CLI arguments... " 403 | 404 | # ":" means that the option has to have an argument (e.g. --domain example.com) 405 | ARGUMENT_LIST=( 406 | "domain:" 407 | "enrollment-domain:" 408 | "volume:" 409 | "vpn-name:" 410 | "vpn-ip:" 411 | "vpn-gateway-ip:" 412 | "vpn-gateway-port:" 413 | "dev" 414 | "pre-release" 415 | ) 416 | 417 | # read arguments 418 | opts=$( 419 | getopt \ 420 | --longoptions "$(printf "%s," "${ARGUMENT_LIST[@]}")" \ 421 | --name "$(basename "$0")" \ 422 | --options "" \ 423 | -- "$@" 424 | ) 425 | 426 | eval set --$opts 427 | 428 | while [[ $# -gt 0 ]]; do 429 | case "$1" in 430 | --domain) 431 | CFG_DOMAIN=$2 432 | shift 2 433 | ;; 434 | 435 | --enrollment-domain) 436 | CFG_ENROLLMENT_DOMAIN=$2 437 | shift 2 438 | ;; 439 | 440 | --volume) 441 | CFG_VOLUME_DIR=$2 442 | shift 2 443 | ;; 444 | 445 | --vpn-name) 446 | CFG_VPN_NAME=$2 447 | shift 2 448 | ;; 449 | 450 | --vpn-ip) 451 | CFG_VPN_IP=$2 452 | shift 2 453 | ;; 454 | 455 | --vpn-gateway-ip) 456 | CFG_VPN_GATEWAY_IP=$2 457 | shift 2 458 | ;; 459 | 460 | --vpn-gateway-port) 461 | CFG_VPN_GATEWAY_PORT=$2 462 | shift 2 463 | ;; 464 | 465 | --pre-release) 466 | CFG_PRE_RELEASE=1 467 | shift 468 | ;; 469 | 470 | --dev) 471 | CFG_DEV=1 472 | shift 473 | ;; 474 | 475 | *) 476 | break 477 | ;; 478 | esac 479 | done 480 | 481 | if [ $CFG_DEV ] && [ $CFG_PRE_RELEASE ]; then 482 | echo >&2 "ERROR: both --dev and --pre-release flags cannot be set at the same time. You can only either use the dev builds or the pre-release builds." 483 | exit 4 484 | fi 485 | 486 | print_confirmation 487 | } 488 | 489 | load_configuration_from_input() { 490 | echo -ne "${C_ITALICS}${C_LBLUE}" 491 | cat <<_EOF_ 492 | 493 | Please provide the values to configure your Defguard instance. If you've 494 | already configured some options by setting environment variables or through 495 | CLI options, those will be used as defaults. 496 | 497 | If you prefer to disable this user input section, please restart the script 498 | with --non-interactive CLI flag. 499 | 500 | _EOF_ 501 | 502 | echo -ne "${C_GREY}" 503 | cat <<_EOF_ 504 | 505 | Choose domains that will be used to expose your instance through Caddy 506 | reverse proxy. Defguard uses a separate domain for the Web UI, and for 507 | the optional enrollment/desktop client configuration/password reset 508 | service. 509 | 510 | If you don't provide any domain for the enrollment service, the service 511 | itself will not be deployed. 512 | 513 | You can also enable HTTPS here (highly recommended), which will configure 514 | Caddy to automatically provision SSL certificates. 515 | _EOF_ 516 | 517 | echo -ne "${C_BOLD}" 518 | cat <<_EOF_ 519 | 520 | Please note that this requires your server to have a public IP address 521 | and public DNS records for your chosen domains to be configured 522 | correctly (pointing to your server's IP address). 523 | 524 | _EOF_ 525 | 526 | echo -ne "${C_END}" 527 | 528 | echo -e " ${C_BOLD}${C_GREEN}${TXT_STAR} General config ${TXT_STAR}${C_END}\n" 529 | 530 | while [ X${domain} = "X" ]; do 531 | echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} " 532 | read -p "Enter Defguard domain [default: ${CFG_DOMAIN}]: " domain 533 | if [ "$domain" ]; then 534 | CFG_DOMAIN="$domain" 535 | fi 536 | done 537 | 538 | echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} " 539 | read -p "Enter enrollment domain [default: ${CFG_ENROLLMENT_DOMAIN}]: " enroll 540 | if [ "$enroll" ]; then 541 | CFG_ENROLLMENT_DOMAIN="$enroll" 542 | fi 543 | 544 | use_https_bool_value="false" 545 | if [ $CFG_USE_HTTPS ]; then use_https_bool_value="true"; fi 546 | echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} " 547 | read -p "Use HTTPS [default: ${use_https_bool_value}]: " https 548 | if [ "$https" ]; then 549 | CFG_USE_HTTPS=1 550 | fi 551 | 552 | echo 553 | echo -e " ${C_BOLD}${C_GREEN}${TXT_STAR} WireGuard VPN${TXT_STAR}${C_END}\n" 554 | 555 | echo -ne "${C_ITALICS}${C_GREY}" 556 | cat <<_EOF_ 557 | 558 | If you wish to configure and deploy WireGuard VPN gateway, please 559 | provide your VPN location name. To skip, just press enter and VPN will 560 | not be configured. 561 | _EOF_ 562 | 563 | echo -ne "${C_END}\n" 564 | 565 | echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} " 566 | read -p "Enter VPN location name [default: ${CFG_VPN_NAME}]: " vpn_name 567 | if [ "$vpn_name" ]; then 568 | CFG_VPN_NAME="$vpn_name" 569 | fi 570 | 571 | if [ "$CFG_VPN_NAME" ]; then 572 | while [ X${vpn_ip} = "X" ]; do 573 | echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} " 574 | read -p "Enter VPN server address and subnet (e.g. 10.0.60.1/24) [default: ${CFG_VPN_IP}]: " vpn_ip 575 | if [ "$vpn_ip" ]; then 576 | CFG_VPN_IP="$vpn_ip" 577 | fi 578 | done 579 | 580 | echo -ne "${C_ITALICS}${C_GREY}" 581 | cat <<_EOF_ 582 | 583 | Now we'll configure a public endpoint (IP + port) that your WireGuard 584 | client devices will use to safely connect to your gateway from the 585 | public internet. 586 | 587 | Since we'll be starting the gateway on this server the IP address should 588 | be the same as your server's public IP address. 589 | _EOF_ 590 | echo -ne "${C_BOLD}" 591 | cat <<_EOF_ 592 | Please also remember that your firewall should be configured 593 | to allow incoming UDP traffic on the chosen WireGuard port. 594 | _EOF_ 595 | 596 | echo -ne "${C_END}" 597 | 598 | while [ X${public_ip} = "X" ]; do 599 | echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} " 600 | read -p "Enter VPN gateway public IP (no domains!) [default: ${CFG_VPN_GATEWAY_IP}]: " public_ip 601 | if [ "$public_ip" ]; then 602 | CFG_VPN_GATEWAY_IP="$public_ip" 603 | fi 604 | done 605 | 606 | while [ X${public_port} = "X" ]; do 607 | echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} " 608 | read -p "Enter VPN gateway public port [default: ${CFG_VPN_GATEWAY_PORT}]: " public_port 609 | if [ "$public_port" ]; then 610 | CFG_VPN_GATEWAY_PORT="$public_port" 611 | fi 612 | done 613 | 614 | else 615 | echo -e " ${C_BOLD}${C_RED}${TXT_X} ${C_GREY} WireGuard VPN skipped${C_END}\n" 616 | fi 617 | 618 | echo 619 | echo -e "${C_BOLD}${C_GREEN}Thank you. We'll now proceed with the deployment using provided values.${C_END}" 620 | } 621 | 622 | check_required_variable() { 623 | local var_name="$1" 624 | if [ -z "${!var_name}" ]; then 625 | echo >&2 "ERROR: ${var_name} configuration option not set" 626 | exit 4 627 | fi 628 | } 629 | 630 | validate_required_variables() { 631 | echo -n " ${TXT_BEGIN} Validating configuration options..." 632 | check_required_variable "CFG_DOMAIN" 633 | 634 | # if VPN name is given validate other VPN configurations are present 635 | if [ "$CFG_VPN_NAME" ]; then 636 | CFG_ENABLE_VPN=1 637 | check_required_variable "CFG_VPN_IP" 638 | check_required_variable "CFG_VPN_GATEWAY_IP" 639 | check_required_variable "CFG_VPN_GATEWAY_PORT" 640 | fi 641 | 642 | print_confirmation 643 | } 644 | 645 | generate_external_urls() { 646 | # prepare full Defguard URL 647 | if [ $CFG_USE_HTTPS ]; then 648 | CFG_DEFGUARD_URL="https://${CFG_DOMAIN}" 649 | else 650 | CFG_DEFGUARD_URL="http://${CFG_DOMAIN}" 651 | fi 652 | 653 | # prepare full enrollment URL 654 | if [ "$CFG_ENROLLMENT_DOMAIN" ]; then 655 | CFG_ENABLE_ENROLLMENT=1 656 | if [ "$CFG_USE_HTTPS" ]; then 657 | CFG_ENROLLMENT_URL="https://${CFG_ENROLLMENT_DOMAIN}" 658 | else 659 | CFG_ENROLLMENT_URL="http://${CFG_ENROLLMENT_DOMAIN}" 660 | fi 661 | fi 662 | } 663 | 664 | print_config() { 665 | echo 666 | echo " ${TXT_BEGIN} Setting up your Defguard instance with following config:" 667 | echo 668 | echo -e " ${TXT_SUB} data volume: ${C_BOLD}${VOLUME_DIR}${C_END}" 669 | echo 670 | echo -e " ${TXT_SUB} domain: ${C_BOLD}${CFG_DOMAIN}${C_END}" 671 | echo -e " ${TXT_SUB} web UI URL: ${C_BOLD}${CFG_DEFGUARD_URL}${C_END}" 672 | 673 | if [ "$CFG_VPN_NAME" ]; then 674 | echo -e " ${TXT_SUB} VPN location name: ${C_BOLD}${CFG_VPN_NAME}${C_END}" 675 | echo -e " ${TXT_SUB} VPN address: ${C_BOLD}${CFG_VPN_IP}${C_END}" 676 | echo -e " ${TXT_SUB} VPN gateway IP: ${C_BOLD}${CFG_VPN_GATEWAY_IP}${C_END}" 677 | echo -e " ${TXT_SUB} VPN gateway port: ${C_BOLD}${CFG_VPN_GATEWAY_PORT}${C_END}" 678 | fi 679 | 680 | if [ "$CFG_ENROLLMENT_DOMAIN" ]; then 681 | echo -e " ${TXT_SUB} Enrollment service domain: ${C_BOLD}${CFG_ENROLLMENT_DOMAIN}${C_END}" 682 | echo -e " ${TXT_SUB} Enrollment service URL: ${C_BOLD}${CFG_ENROLLMENT_URL}${C_END}" 683 | fi 684 | echo 685 | echo -e " ${TXT_BEGIN} All executed command's results are in log file: ${C_BOLD}${LOG_FILE}${C_END}" 686 | echo 687 | } 688 | 689 | setup_keys() { 690 | echo " ${TXT_BEGIN} Setting up SSL certificates and RSA keys..." 691 | if [ -d ${SSL_DIR} -a "$(ls -A ${SSL_DIR})" ]; then 692 | echo " ${TXT_SUB} Using existing SSL certificates from ${SSL_DIR}" 693 | else 694 | generate_certs 695 | fi 696 | 697 | if [ -d ${RSA_DIR} -a "$(ls -A ${RSA_DIR})" ]; then 698 | echo " ${TXT_SUB} Using existing RSA keys from ${RSA_DIR}." 699 | else 700 | generate_rsa 701 | fi 702 | } 703 | 704 | generate_certs() { 705 | echo " ${TXT_BEGIN} Creating new SSL certificates in ${SSL_DIR}..." 706 | mkdir -p ${SSL_DIR} 707 | 708 | PASSPHRASE=$(generate_secret) 709 | 710 | echo "PEM passphrase for SSL certificates set to '${PASSPHRASE}'." 711 | 712 | # generate private key for CA 713 | openssl genrsa -des3 -out ${SSL_DIR}/defguard-ca.key -passout pass:"${PASSPHRASE}" 2048 2>&1 >>${LOG_FILE} 714 | # generate Root Certificate 715 | # TODO: allow configuring CA parameters 716 | openssl req -x509 -new -nodes -key ${SSL_DIR}/defguard-ca.key -sha256 -days 1825 -out ${SSL_DIR}/defguard-ca.pem -passin pass:"${PASSPHRASE}" -subj "/CN=${CFG_DOMAIN}" 2>&1 >>${LOG_FILE} 717 | 718 | # generate CA-signed certificate for Defguard gRPC 719 | openssl genrsa -out ${SSL_DIR}/defguard-grpc.key 2048 2>&1 >>${LOG_FILE} 720 | 721 | openssl req -new -key ${SSL_DIR}/defguard-grpc.key -out ${SSL_DIR}/defguard-grpc.csr -subj "/CN=${CFG_DOMAIN}" 2>&1 >>${LOG_FILE} 722 | cat >${SSL_DIR}/defguard-grpc.ext <&1 >>${LOG_FILE} 734 | 735 | # generate CA-signed certificate for Defguard proxy gRPC 736 | openssl genrsa -out ${SSL_DIR}/defguard-proxy-grpc.key 2048 2>&1 >>${LOG_FILE} 737 | 738 | openssl req -new -key ${SSL_DIR}/defguard-proxy-grpc.key -out ${SSL_DIR}/defguard-proxy-grpc.csr -subj "/CN=${CFG_DOMAIN}" 2>&1 >>${LOG_FILE} 739 | cat >${SSL_DIR}/defguard-proxy-grpc.ext <&1 >>${LOG_FILE} 750 | } 751 | 752 | generate_rsa() { 753 | echo "Generating RSA keys in ${RSA_DIR}..." 754 | mkdir -p ${RSA_DIR} 755 | openssl genpkey -out ${RSA_DIR}/rsakey.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048 2>&1 >>${LOG_FILE} 756 | 757 | } 758 | 759 | generate_secret() { 760 | generate_secret_inner "${SECRET_LENGTH}" 761 | } 762 | 763 | generate_password() { 764 | generate_secret_inner "${PASSWORD_LENGTH}" 765 | } 766 | 767 | generate_secret_inner() { 768 | local length="$1" 769 | openssl rand -base64 ${length} | tr -d '=+/' | tr -d '\n' | cut -c1-${length-1} 770 | } 771 | 772 | create_caddyfile() { 773 | caddy_volume_path="${VOLUME_DIR}/caddy" 774 | caddyfile_path="${caddy_volume_path}/Caddyfile" 775 | mkdir -p ${caddy_volume_path} 776 | 777 | cat >${caddyfile_path} <>${caddyfile_path} <>${caddyfile_path} <&1 >>${LOG_FILE} 808 | 809 | print_confirmation 810 | } 811 | 812 | generate_env_file() { 813 | PROD_ENV_FILE="${WORK_DIR_PATH}/${ENV_FILE}" 814 | fetch_base_env_file 815 | update_env_file 816 | 817 | print_confirmation 818 | } 819 | 820 | fetch_base_env_file() { 821 | echo -e " ${TXT_BEGIN} Fetching base ${ENV_FILE} file for compose stack..." 822 | 823 | curl --proto '=https' --tlsv1.2 -sSf "${BASE_ENV_FILE_URL}" -o "${PROD_ENV_FILE}" 2>&1 >>${LOG_FILE} 824 | print_confirmation 825 | } 826 | 827 | update_env_file() { 828 | echo -n " ${TXT_BEGIN} Setting environment variables in ${ENV_FILE} file for compose stack..." 829 | 830 | # set image versions 831 | set_env_file_value "CORE_IMAGE_TAG" "${CORE_IMAGE_TAG}" 832 | set_env_file_value "PROXY_IMAGE_TAG" "${PROXY_IMAGE_TAG}" 833 | set_env_file_value "GATEWAY_IMAGE_TAG" "${GATEWAY_IMAGE_TAG}" 834 | 835 | # fill in values 836 | set_env_file_secret "DEFGUARD_AUTH_SECRET" 837 | set_env_file_secret "DEFGUARD_YUBIBRIDGE_SECRET" 838 | set_env_file_secret "DEFGUARD_GATEWAY_SECRET" 839 | set_env_file_secret "DEFGUARD_SECRET_KEY" 840 | 841 | # use existing password if set in env variable 842 | if [ "$DEFGUARD_DB_PASSWORD" ]; then 843 | set_env_file_value "DEFGUARD_DB_PASSWORD" "${DEFGUARD_DB_PASSWORD}" 844 | else 845 | set_env_file_password "DEFGUARD_DB_PASSWORD" 846 | fi 847 | 848 | DEFGUARD_DEFAULT_ADMIN_PASSWORD="$(generate_password)" 849 | set_env_file_value "DEFGUARD_DEFAULT_ADMIN_PASSWORD" "${DEFGUARD_DEFAULT_ADMIN_PASSWORD}" 850 | 851 | set_env_file_value "DEFGUARD_URL" "${CFG_DEFGUARD_URL}" 852 | set_env_file_value "DEFGUARD_WEBAUTHN_RP_ID" "${CFG_DOMAIN}" 853 | print_confirmation 854 | } 855 | 856 | set_env_file_value() { 857 | # make sure variable exists in file 858 | grep -qF "${1}=" "${PROD_ENV_FILE}" || echo "${1}=" >>"${PROD_ENV_FILE}" 859 | "${SED[@]}" "s@\(${1}\)=.*@\1=${2}@" "${PROD_ENV_FILE}" 860 | } 861 | 862 | set_env_file_secret() { 863 | set_env_file_value "${1}" "$(generate_secret)" "${PROD_ENV_FILE}" 864 | } 865 | 866 | set_env_file_password() { 867 | set_env_file_value "${1}" "$(generate_password)" "${PROD_ENV_FILE}" 868 | } 869 | 870 | uncomment_feature() { 871 | "${SED[@]}" "s@# \(.*\) # \[${1}\]@\1@" "${2}" 872 | } 873 | 874 | enable_enrollment() { 875 | echo -n " ${TXT_BEGIN} Enabling enrollment proxy service in compose file..." 876 | 877 | # update .env file 878 | uncomment_feature "ENROLLMENT" "${PROD_ENV_FILE}" 879 | set_env_file_value "DEFGUARD_ENROLLMENT_URL" "${CFG_ENROLLMENT_URL}" 880 | 881 | # update compose file 882 | uncomment_feature "ENROLLMENT" "${PROD_COMPOSE_FILE}" 883 | 884 | print_confirmation 885 | } 886 | 887 | enable_vpn_gateway() { 888 | echo " ${TXT_BEGIN} Enabling VPN gateway service..." 889 | 890 | uncomment_feature "VPN" "${PROD_COMPOSE_FILE}" 891 | uncomment_feature "VPN" "${PROD_ENV_FILE}" 892 | 893 | # fetch image 894 | echo -e " ${TXT_SUB} Fetching ${IMAGE_TYPE_NAME} gateway image..." 895 | $COMPOSE_CMD -f "${PROD_COMPOSE_FILE}" --env-file "${PROD_ENV_FILE}" pull gateway 896 | 897 | # create VPN location 898 | echo " ${TXT_BEGIN} Adding VPN to core & generating gateway token..." 899 | VPN_NETWORK=$(echo ${CFG_VPN_IP} | awk -F'[./]' '{print $1"."$2"."$3".0/"$5}') 900 | token=$($COMPOSE_CMD -f "${PROD_COMPOSE_FILE}" --env-file "${PROD_ENV_FILE}" run core init-vpn-location --name "${CFG_VPN_NAME}" --address "${CFG_VPN_IP}" --endpoint "${CFG_VPN_GATEWAY_IP}" --port "${CFG_VPN_GATEWAY_PORT}" --allowed-ips "${VPN_NETWORK}" | tail -n 1) 901 | if [ $? -ne 0 ]; then 902 | echo >&2 "ERROR: failed to create VPN network" 903 | exit 1 904 | fi 905 | 906 | # add gateway token to .env file 907 | set_env_file_value "DEFGUARD_TOKEN" "${token}" 908 | } 909 | 910 | print_instance_summary() { 911 | echo 912 | echo -e "${C_LGREEN} ${TXT_CHECK} Defguard setup finished successfully${C_END}. The Docker image version used for the setup was: ${IMAGE_TYPE_NAME}" 913 | echo 914 | echo "If your DNS configuration is correct your Defguard instance should be available at:" 915 | echo 916 | echo -e "\t${TXT_SUB} Web UI: ${C_BOLD}${CFG_DEFGUARD_URL}${C_END}" 917 | if [ "$CFG_ENABLE_ENROLLMENT" ]; then 918 | echo -e "\t${TXT_SUB} Enrollment service: ${C_BOLD}${CFG_ENROLLMENT_URL}${C_END}" 919 | fi 920 | echo 921 | echo -e " ${TXT_BEGIN} You can log into the UI using the default admin user:" 922 | echo 923 | echo -e "\t${TXT_SUB} username: ${C_BOLD}admin${C_END}" 924 | echo -e "\t${TXT_SUB} password: ${C_BOLD}${DEFGUARD_DEFAULT_ADMIN_PASSWORD}${C_END}" 925 | echo 926 | if [ "$CFG_ENABLE_VPN" ]; then 927 | echo -e "\t\tVPN server public endpoint is ${C_BOLD}${CFG_VPN_GATEWAY_IP}:${CFG_VPN_GATEWAY_PORT}${C_END}" 928 | echo -e "\t\tVPN network is ${C_BOLD}${VPN_NETWORK}${C_END}" 929 | echo -e "\t\t! Make sure your firewall allows external UDP traffic to port ${C_BOLD}${CFG_VPN_GATEWAY_PORT}${C_END} !" 930 | echo 931 | echo -e "\t\tTo test if the VPN is working: ping ${CFG_VPN_IP} (after connecting to VPN)" 932 | fi 933 | echo 934 | echo -e "Files used to deploy your instance are stored in:" 935 | echo -e "\t docker compose file: ${C_BOLD}${PROD_COMPOSE_FILE}${C_END}" 936 | echo -e "\t docker compose environment: ${C_BOLD}${PROD_ENV_FILE}${C_END}" 937 | echo 938 | echo -e "Persistent data (docker volumes) is stored in ${C_BOLD}${VOLUME_DIR}${C_END}" 939 | echo 940 | echo -e " ${C_YELLOW}${TXT_STAR} To support our work, please star us on GitHub! ${TXT_STAR}${C_END}" 941 | echo -e " ${C_YELLOW}${TXT_STAR} https://github.com/defguard/defguard ${TXT_STAR}${C_END}" 942 | echo 943 | } 944 | 945 | # run main function 946 | main "$@" || exit 1 947 | -------------------------------------------------------------------------------- /cloudformation/template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Description: Full Defguard stack with Core, Proxy, Gateway, and a Database. 3 | Parameters: 4 | StackPrefix: 5 | Type: String 6 | Default: defguard 7 | Description: Prefix for all resource names, if you want to allow multiple Defguard deployments in the same region 8 | AllowedPattern: ^[a-z0-9-]+$ 9 | ConstraintDescription: Must contain only lowercase letters, numbers, and hyphens 10 | CoreUrl: 11 | Type: String 12 | Description: The URL for the Defguard Core (e.g., https://core.example.com) 13 | CoreGrpcPort: 14 | Type: Number 15 | Default: 50055 16 | Description: The gRPC port for the Defguard Core 17 | CoreHttpPort: 18 | Type: Number 19 | Default: 8000 20 | Description: The HTTP port for the Defguard Core web UI 21 | CoreCookieInsecure: 22 | Type: String 23 | Default: "false" 24 | AllowedValues: 25 | - "true" 26 | - "false" 27 | Description: Whether to allow insecure cookies for the Defguard Core web UI. Set to "true" only if accessing the web UI over HTTP (not recommended for production). 28 | CoreDefaultAdminPassword: 29 | Type: String 30 | NoEcho: true 31 | Description: The initial password for the Defguard dashboard admin user 32 | CoreInstanceType: 33 | Type: String 34 | Default: t3.micro 35 | Description: The instance type for the Defguard Core server 36 | ProxyUrl: 37 | Type: String 38 | Description: The URL for the Defguard Proxy (e.g., https://proxy.example.com) 39 | ProxyGrpcPort: 40 | Type: Number 41 | Default: 50051 42 | Description: The gRPC port for the Defguard Proxy 43 | ProxyHttpPort: 44 | Type: Number 45 | Default: 8000 46 | Description: The HTTP port for the Defguard Proxy 47 | ProxyInstanceType: 48 | Type: String 49 | Default: t3.micro 50 | Description: The instance type for the Defguard Proxy server 51 | GatewayInstanceType: 52 | Type: String 53 | Default: t3.micro 54 | Description: The instance type for the Defguard Gateway server 55 | DbName: 56 | Type: String 57 | Default: defguard 58 | Description: The name of the database 59 | DbUsername: 60 | Type: String 61 | Default: defguard 62 | Description: The username for the database 63 | DbPassword: 64 | Type: String 65 | NoEcho: true 66 | Description: The password for the database user 67 | DbPort: 68 | Type: Number 69 | Default: 5432 70 | Description: The port on which the database will listen 71 | DbStorage: 72 | Type: Number 73 | Default: 20 74 | Description: The amount of storage allocated for the database in GB 75 | DbInstanceClass: 76 | Type: String 77 | Default: db.t3.micro 78 | Description: The instance class for the database 79 | VpcName: 80 | Type: String 81 | Default: defguard-vpc 82 | Description: The name of the VPC 83 | VpcCidr: 84 | Type: String 85 | Default: 10.0.0.0/16 86 | Description: The CIDR block for the VPC 87 | PublicSubnet1Cidr: 88 | Type: String 89 | Default: 10.0.1.0/24 90 | Description: The CIDR block for the first public subnet. Must be within the VPC CIDR range. 91 | PublicSubnet2Cidr: 92 | Type: String 93 | Default: 10.0.4.0/24 94 | Description: The CIDR block for the second public subnet. Must be within the VPC CIDR range. 95 | PrivateSubnet1Cidr: 96 | Type: String 97 | Default: 10.0.2.0/24 98 | Description: The CIDR block for the first private subnet. Must be within the VPC CIDR range. 99 | PrivateSubnet2Cidr: 100 | Type: String 101 | Default: 10.0.3.0/24 102 | Description: The CIDR block for the second private subnet. Must be within the VPC CIDR range. 103 | VpnNetworkName: 104 | Type: String 105 | Default: vpn1 106 | Description: The name of the VPN network (location) 107 | VpnNetworkAddress: 108 | Type: String 109 | Default: 10.10.10.1/24 110 | Description: The CIDR address of the VPN network (location). 111 | VpnNetworkPort: 112 | Type: Number 113 | Default: 51820 114 | Description: The UDP port for the Gateway to accept VPN connections 115 | VpnNetworkNat: 116 | Type: String 117 | Default: "true" 118 | AllowedValues: 119 | - "true" 120 | - "false" 121 | Description: Whether to enable NAT for the VPN network. This may allow VPN clients to access the internet through the Gateway. 122 | CoreLogLevel: 123 | Type: String 124 | Default: info 125 | AllowedValues: 126 | - trace 127 | - debug 128 | - info 129 | - warn 130 | - error 131 | Description: Log level for Defguard Core component 132 | ProxyLogLevel: 133 | Type: String 134 | Default: info 135 | AllowedValues: 136 | - trace 137 | - debug 138 | - info 139 | - warn 140 | - error 141 | Description: Log level for Defguard Proxy component 142 | GatewayLogLevel: 143 | Type: String 144 | Default: info 145 | AllowedValues: 146 | - trace 147 | - debug 148 | - info 149 | - warn 150 | - error 151 | Description: Log level for Defguard Gateway component 152 | DefguardAmiId: 153 | Type: String 154 | Description: AMI ID for Defguard. 155 | Default: "ami-030fa11d061f26d50" 156 | SshKeyName: 157 | Type: String 158 | Description: (Optional) EC2 Key Pair name for SSH access to instances. If not provided, SSH access will not be available. Requires a manual setup of SSH security group rules afterwards. 159 | Default: "" 160 | SSLCertificateArn: 161 | Type: String 162 | Description: (Optional) ARN of the SSL certificate for HTTPS termination on the reverse proxies. If not provided, only HTTP will be used. 163 | Default: "" 164 | Conditions: 165 | UseSshKey: !Not [!Equals [!Ref SshKeyName, ""]] 166 | UseSSLCertificate: !Not [!Equals [!Ref SSLCertificateArn, ""]] 167 | Resources: 168 | VPC: 169 | Type: AWS::EC2::VPC 170 | Properties: 171 | CidrBlock: !Ref VpcCidr 172 | EnableDnsHostnames: true 173 | EnableDnsSupport: true 174 | Tags: 175 | - Key: Name 176 | Value: !Ref VpcName 177 | InternetGateway: 178 | Type: AWS::EC2::InternetGateway 179 | Properties: 180 | Tags: 181 | - Key: Name 182 | Value: !Sub ${VpcName}-igw 183 | VPCGatewayAttachment: 184 | Type: AWS::EC2::VPCGatewayAttachment 185 | Properties: 186 | VpcId: !Ref VPC 187 | InternetGatewayId: !Ref InternetGateway 188 | DefguardPublicSubnet: 189 | Type: AWS::EC2::Subnet 190 | Properties: 191 | VpcId: !Ref VPC 192 | CidrBlock: !Ref PublicSubnet1Cidr 193 | AvailabilityZone: !Select 194 | - 0 195 | - !GetAZs "" 196 | MapPublicIpOnLaunch: true 197 | Tags: 198 | - Key: Name 199 | Value: !Sub ${VpcName}-public-subnet-1 200 | 201 | DefguardPublicSubnet2: 202 | Type: AWS::EC2::Subnet 203 | Properties: 204 | VpcId: !Ref VPC 205 | CidrBlock: !Ref PublicSubnet2Cidr 206 | AvailabilityZone: !Select 207 | - 1 208 | - !GetAZs "" 209 | MapPublicIpOnLaunch: true 210 | Tags: 211 | - Key: Name 212 | Value: !Sub ${VpcName}-public-subnet-2 213 | DefguardPrivateSubnet1: 214 | Type: AWS::EC2::Subnet 215 | Properties: 216 | VpcId: !Ref VPC 217 | CidrBlock: !Ref PrivateSubnet1Cidr 218 | AvailabilityZone: !Select 219 | - 0 220 | - !GetAZs "" 221 | Tags: 222 | - Key: Name 223 | Value: !Sub ${VpcName}-private-subnet-1 224 | DefguardPrivateSubnet2: 225 | Type: AWS::EC2::Subnet 226 | Properties: 227 | VpcId: !Ref VPC 228 | CidrBlock: !Ref PrivateSubnet2Cidr 229 | AvailabilityZone: !Select 230 | - 1 231 | - !GetAZs "" 232 | Tags: 233 | - Key: Name 234 | Value: !Sub ${VpcName}-private-subnet-2 235 | PublicRouteTable: 236 | Type: AWS::EC2::RouteTable 237 | Properties: 238 | VpcId: !Ref VPC 239 | Tags: 240 | - Key: Name 241 | Value: !Sub ${VpcName}-public-rt 242 | PublicRoute: 243 | Type: AWS::EC2::Route 244 | DependsOn: VPCGatewayAttachment 245 | Properties: 246 | RouteTableId: !Ref PublicRouteTable 247 | DestinationCidrBlock: 0.0.0.0/0 248 | GatewayId: !Ref InternetGateway 249 | DefguardPublicSubnetRouteTableAssociation: 250 | Type: AWS::EC2::SubnetRouteTableAssociation 251 | Properties: 252 | SubnetId: !Ref DefguardPublicSubnet 253 | RouteTableId: !Ref PublicRouteTable 254 | 255 | DefguardPublicSubnet2RouteTableAssociation: 256 | Type: AWS::EC2::SubnetRouteTableAssociation 257 | Properties: 258 | SubnetId: !Ref DefguardPublicSubnet2 259 | RouteTableId: !Ref PublicRouteTable 260 | NATGatewayEIP: 261 | Type: AWS::EC2::EIP 262 | Properties: 263 | Domain: vpc 264 | Tags: 265 | - Key: Name 266 | Value: !Sub ${VpcName}-nat-eip 267 | NATGateway: 268 | Type: AWS::EC2::NatGateway 269 | Properties: 270 | AllocationId: !GetAtt NATGatewayEIP.AllocationId 271 | SubnetId: !Ref DefguardPublicSubnet 272 | Tags: 273 | - Key: Name 274 | Value: !Sub ${VpcName}-nat-gateway 275 | PrivateRouteTable: 276 | Type: AWS::EC2::RouteTable 277 | Properties: 278 | VpcId: !Ref VPC 279 | Tags: 280 | - Key: Name 281 | Value: !Sub ${VpcName}-private-rt 282 | PrivateRoute: 283 | Type: AWS::EC2::Route 284 | Properties: 285 | RouteTableId: !Ref PrivateRouteTable 286 | DestinationCidrBlock: 0.0.0.0/0 287 | NatGatewayId: !Ref NATGateway 288 | DefguardPrivateSubnet1RouteTableAssociation: 289 | Type: AWS::EC2::SubnetRouteTableAssociation 290 | Properties: 291 | SubnetId: !Ref DefguardPrivateSubnet1 292 | RouteTableId: !Ref PrivateRouteTable 293 | DefguardPrivateSubnet2RouteTableAssociation: 294 | Type: AWS::EC2::SubnetRouteTableAssociation 295 | Properties: 296 | SubnetId: !Ref DefguardPrivateSubnet2 297 | RouteTableId: !Ref PrivateRouteTable 298 | DBSubnetGroup: 299 | Type: AWS::RDS::DBSubnetGroup 300 | Properties: 301 | DBSubnetGroupName: !Sub ${StackPrefix}-db-subnet-group 302 | DBSubnetGroupDescription: Subnet group for Defguard database 303 | SubnetIds: 304 | - !Ref DefguardPrivateSubnet1 305 | - !Ref DefguardPrivateSubnet2 306 | Tags: 307 | - Key: Name 308 | Value: !Sub ${StackPrefix}-db-subnet-group 309 | DBParameterGroup: 310 | Type: AWS::RDS::DBParameterGroup 311 | Properties: 312 | DBParameterGroupName: !Sub ${StackPrefix}-db-parameter-group 313 | Description: Parameter group for Defguard database 314 | Family: postgres17 315 | Parameters: 316 | rds.force_ssl: "0" 317 | Tags: 318 | - Key: Name 319 | Value: !Sub ${StackPrefix}-db-parameter-group 320 | DatabaseSecurityGroup: 321 | Type: AWS::EC2::SecurityGroup 322 | Properties: 323 | GroupName: !Sub ${StackPrefix}-db-sg 324 | GroupDescription: Access to the database 325 | VpcId: !Ref VPC 326 | SecurityGroupIngress: 327 | - IpProtocol: tcp 328 | FromPort: !Ref DbPort 329 | ToPort: !Ref DbPort 330 | SourceSecurityGroupId: !Ref CoreSecurityGroup 331 | Tags: 332 | - Key: Name 333 | Value: !Sub ${StackPrefix}-db-sg 334 | LambdaSecurityGroup: 335 | Type: AWS::EC2::SecurityGroup 336 | Properties: 337 | GroupName: !Sub ${StackPrefix}-lambda-sg 338 | GroupDescription: Lambda function access 339 | VpcId: !Ref VPC 340 | SecurityGroupEgress: 341 | - IpProtocol: "-1" 342 | CidrIp: 0.0.0.0/0 343 | Tags: 344 | - Key: Name 345 | Value: !Sub ${StackPrefix}-lambda-sg 346 | Database: 347 | Type: AWS::RDS::DBInstance 348 | Properties: 349 | DBName: !Ref DbName 350 | DBInstanceIdentifier: !Sub ${StackPrefix}-core-db 351 | DBInstanceClass: !Ref DbInstanceClass 352 | Engine: postgres 353 | MasterUsername: !Ref DbUsername 354 | MasterUserPassword: !Ref DbPassword 355 | Port: !Ref DbPort 356 | AllocatedStorage: !Ref DbStorage 357 | DBSubnetGroupName: !Ref DBSubnetGroup 358 | VPCSecurityGroups: 359 | - !Ref DatabaseSecurityGroup 360 | DBParameterGroupName: !Ref DBParameterGroup 361 | DeletionProtection: false 362 | BackupRetentionPeriod: 7 363 | Tags: 364 | - Key: Name 365 | Value: !Sub ${StackPrefix}-core-db 366 | CoreSecurityGroup: 367 | Type: AWS::EC2::SecurityGroup 368 | Properties: 369 | GroupName: !Sub ${StackPrefix}-core-sg 370 | GroupDescription: Core access 371 | VpcId: !Ref VPC 372 | SecurityGroupIngress: 373 | - IpProtocol: tcp 374 | FromPort: !Ref CoreGrpcPort 375 | ToPort: !Ref CoreGrpcPort 376 | SourceSecurityGroupId: !Ref GatewaySecurityGroup 377 | Description: gRPC communication with gateways 378 | - IpProtocol: tcp 379 | FromPort: !Ref CoreHttpPort 380 | ToPort: !Ref CoreHttpPort 381 | SourceSecurityGroupId: !Ref LambdaSecurityGroup 382 | Description: HTTP access from Lambda function 383 | - IpProtocol: tcp 384 | FromPort: !Ref CoreHttpPort 385 | ToPort: !Ref CoreHttpPort 386 | SourceSecurityGroupId: !Ref InternalProxyALBSecurityGroup 387 | Description: HTTP access from internal reverse proxy ALB 388 | SecurityGroupEgress: 389 | - IpProtocol: "-1" 390 | CidrIp: 0.0.0.0/0 391 | Tags: 392 | - Key: Name 393 | Value: !Sub ${StackPrefix}-core-sg 394 | ProxySecurityGroup: 395 | Type: AWS::EC2::SecurityGroup 396 | Properties: 397 | GroupName: !Sub ${StackPrefix}-proxy-sg 398 | GroupDescription: Proxy access 399 | VpcId: !Ref VPC 400 | SecurityGroupIngress: 401 | - IpProtocol: tcp 402 | FromPort: !Ref ProxyHttpPort 403 | ToPort: !Ref ProxyHttpPort 404 | SourceSecurityGroupId: !Ref PublicProxyALBSecurityGroup 405 | Description: HTTP access from public reverse proxy ALB 406 | - IpProtocol: tcp 407 | FromPort: !Ref ProxyGrpcPort 408 | ToPort: !Ref ProxyGrpcPort 409 | SourceSecurityGroupId: !Ref CoreSecurityGroup 410 | Description: Internal communication with core 411 | SecurityGroupEgress: 412 | - IpProtocol: "-1" 413 | CidrIp: 0.0.0.0/0 414 | Tags: 415 | - Key: Name 416 | Value: !Sub ${StackPrefix}-proxy-sg 417 | GatewaySecurityGroup: 418 | Type: AWS::EC2::SecurityGroup 419 | Properties: 420 | GroupName: !Sub ${StackPrefix}-gateway-sg 421 | GroupDescription: Gateway access 422 | VpcId: !Ref VPC 423 | SecurityGroupIngress: 424 | - IpProtocol: udp 425 | FromPort: !Ref VpnNetworkPort 426 | ToPort: !Ref VpnNetworkPort 427 | CidrIp: 0.0.0.0/0 428 | Description: VPN traffic from clients 429 | SecurityGroupEgress: 430 | - IpProtocol: "-1" 431 | CidrIp: 0.0.0.0/0 432 | Tags: 433 | - Key: Name 434 | Value: !Sub ${StackPrefix}-gateway-sg 435 | 436 | # Security group for the public reverse proxy (ALB) 437 | PublicProxyALBSecurityGroup: 438 | Type: AWS::EC2::SecurityGroup 439 | Properties: 440 | GroupName: !Sub ${StackPrefix}-public-proxy-alb-sg 441 | GroupDescription: Public reverse proxy ALB access 442 | VpcId: !Ref VPC 443 | SecurityGroupIngress: 444 | - IpProtocol: tcp 445 | FromPort: 80 446 | ToPort: 80 447 | CidrIp: 0.0.0.0/0 448 | Description: HTTP access from internet 449 | - IpProtocol: tcp 450 | FromPort: 443 451 | ToPort: 443 452 | CidrIp: 0.0.0.0/0 453 | Description: HTTPS access from internet 454 | SecurityGroupEgress: 455 | - IpProtocol: "-1" 456 | CidrIp: 0.0.0.0/0 457 | Tags: 458 | - Key: Name 459 | Value: !Sub ${StackPrefix}-public-proxy-alb-sg 460 | 461 | # Security group for the internal reverse proxy (ALB) 462 | InternalProxyALBSecurityGroup: 463 | Type: AWS::EC2::SecurityGroup 464 | Properties: 465 | GroupName: !Sub ${StackPrefix}-internal-proxy-alb-sg 466 | GroupDescription: Internal reverse proxy ALB access 467 | VpcId: !Ref VPC 468 | SecurityGroupIngress: 469 | - IpProtocol: tcp 470 | FromPort: 80 471 | ToPort: 80 472 | SourceSecurityGroupId: !Ref GatewaySecurityGroup 473 | Description: HTTP access from gateways 474 | - IpProtocol: tcp 475 | FromPort: 443 476 | ToPort: 443 477 | SourceSecurityGroupId: !Ref GatewaySecurityGroup 478 | Description: HTTPS access from gateways 479 | SecurityGroupEgress: 480 | - IpProtocol: "-1" 481 | CidrIp: 0.0.0.0/0 482 | Tags: 483 | - Key: Name 484 | Value: !Sub ${StackPrefix}-internal-proxy-alb-sg 485 | CoreNetworkInterface: 486 | Type: AWS::EC2::NetworkInterface 487 | Properties: 488 | SubnetId: !Ref DefguardPrivateSubnet1 489 | GroupSet: 490 | - !Ref CoreSecurityGroup 491 | Tags: 492 | - Key: Name 493 | Value: !Sub ${StackPrefix}-core-network-interface 494 | ProxyNetworkInterface: 495 | Type: AWS::EC2::NetworkInterface 496 | Properties: 497 | SubnetId: !Ref DefguardPublicSubnet 498 | GroupSet: 499 | - !Ref ProxySecurityGroup 500 | Tags: 501 | - Key: Name 502 | Value: !Sub ${StackPrefix}-proxy-network-interface 503 | ProxyElasticIP: 504 | Type: AWS::EC2::EIP 505 | Properties: 506 | Domain: vpc 507 | Tags: 508 | - Key: Name 509 | Value: !Sub ${StackPrefix}-proxy-eip 510 | ProxyEIPAssociation: 511 | Type: AWS::EC2::EIPAssociation 512 | Properties: 513 | NetworkInterfaceId: !Ref ProxyNetworkInterface 514 | AllocationId: !GetAtt ProxyElasticIP.AllocationId 515 | GatewayNetworkInterface: 516 | Type: AWS::EC2::NetworkInterface 517 | Properties: 518 | SubnetId: !Ref DefguardPublicSubnet 519 | GroupSet: 520 | - !Ref GatewaySecurityGroup 521 | Tags: 522 | - Key: Name 523 | Value: !Sub ${StackPrefix}-gateway-network-interface 524 | GatewayElasticIP: 525 | Type: AWS::EC2::EIP 526 | Properties: 527 | Domain: vpc 528 | Tags: 529 | - Key: Name 530 | Value: !Sub ${StackPrefix}-gateway-eip 531 | GatewayEIPAssociation: 532 | Type: AWS::EC2::EIPAssociation 533 | Properties: 534 | NetworkInterfaceId: !Ref GatewayNetworkInterface 535 | AllocationId: !GetAtt GatewayElasticIP.AllocationId 536 | 537 | # Public reverse proxy (ALB) for Defguard Proxy 538 | PublicProxyALB: 539 | Type: AWS::ElasticLoadBalancingV2::LoadBalancer 540 | Properties: 541 | Name: !Sub ${StackPrefix}-public-proxy-alb 542 | Type: application 543 | Scheme: internet-facing 544 | IpAddressType: ipv4 545 | Subnets: 546 | - !Ref DefguardPublicSubnet 547 | - !Ref DefguardPublicSubnet2 548 | SecurityGroups: 549 | - !Ref PublicProxyALBSecurityGroup 550 | Tags: 551 | - Key: Name 552 | Value: !Sub ${StackPrefix}-public-proxy-alb 553 | 554 | # Target group for the public reverse proxy 555 | PublicProxyTargetGroup: 556 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 557 | Properties: 558 | Name: !Sub ${StackPrefix}-public-proxy-tg 559 | Port: !Ref ProxyHttpPort 560 | Protocol: HTTP 561 | VpcId: !Ref VPC 562 | HealthCheckEnabled: true 563 | HealthCheckPath: /api/v1/health 564 | HealthCheckIntervalSeconds: 30 565 | HealthCheckTimeoutSeconds: 5 566 | HealthyThresholdCount: 2 567 | UnhealthyThresholdCount: 5 568 | TargetType: ip 569 | Targets: 570 | - Id: !GetAtt ProxyNetworkInterface.PrimaryPrivateIpAddress 571 | Port: !Ref ProxyHttpPort 572 | Tags: 573 | - Key: Name 574 | Value: !Sub ${StackPrefix}-public-proxy-tg 575 | 576 | # HTTP listener for public reverse proxy 577 | PublicProxyHTTPListener: 578 | Type: AWS::ElasticLoadBalancingV2::Listener 579 | Properties: 580 | DefaultActions: 581 | - Type: forward 582 | TargetGroupArn: !Ref PublicProxyTargetGroup 583 | LoadBalancerArn: !Ref PublicProxyALB 584 | Port: 80 585 | Protocol: HTTP 586 | 587 | # HTTPS listener for public reverse proxy (only if SSL certificate is provided) 588 | PublicProxyHTTPSListener: 589 | Type: AWS::ElasticLoadBalancingV2::Listener 590 | Condition: UseSSLCertificate 591 | Properties: 592 | DefaultActions: 593 | - Type: forward 594 | TargetGroupArn: !Ref PublicProxyTargetGroup 595 | LoadBalancerArn: !Ref PublicProxyALB 596 | Port: 443 597 | Protocol: HTTPS 598 | Certificates: 599 | - CertificateArn: !Ref SSLCertificateArn 600 | 601 | # Internal reverse proxy (ALB) for Defguard Core 602 | InternalProxyALB: 603 | Type: AWS::ElasticLoadBalancingV2::LoadBalancer 604 | Properties: 605 | Name: !Sub ${StackPrefix}-internal-proxy-alb 606 | Type: application 607 | Scheme: internal 608 | IpAddressType: ipv4 609 | Subnets: 610 | - !Ref DefguardPrivateSubnet1 611 | - !Ref DefguardPrivateSubnet2 612 | SecurityGroups: 613 | - !Ref InternalProxyALBSecurityGroup 614 | Tags: 615 | - Key: Name 616 | Value: !Sub ${StackPrefix}-internal-proxy-alb 617 | 618 | # Target group for the internal reverse proxy (HTTP) 619 | InternalProxyHTTPTargetGroup: 620 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 621 | Properties: 622 | Name: !Sub ${StackPrefix}-internal-proxy-http-tg 623 | Port: !Ref CoreHttpPort 624 | Protocol: HTTP 625 | VpcId: !Ref VPC 626 | HealthCheckEnabled: true 627 | HealthCheckPath: /api/v1/health 628 | HealthCheckIntervalSeconds: 30 629 | HealthCheckTimeoutSeconds: 5 630 | HealthyThresholdCount: 2 631 | UnhealthyThresholdCount: 5 632 | TargetType: ip 633 | Targets: 634 | - Id: !GetAtt CoreNetworkInterface.PrimaryPrivateIpAddress 635 | Port: !Ref CoreHttpPort 636 | Tags: 637 | - Key: Name 638 | Value: !Sub ${StackPrefix}-internal-proxy-http-tg 639 | 640 | # HTTP listener for internal reverse proxy 641 | InternalProxyHTTPListener: 642 | Type: AWS::ElasticLoadBalancingV2::Listener 643 | Properties: 644 | DefaultActions: 645 | - Type: forward 646 | TargetGroupArn: !Ref InternalProxyHTTPTargetGroup 647 | LoadBalancerArn: !Ref InternalProxyALB 648 | Port: 80 649 | Protocol: HTTP 650 | 651 | # HTTPS listener for internal reverse proxy (only if SSL certificate is provided) 652 | InternalProxyHTTPSListener: 653 | Type: AWS::ElasticLoadBalancingV2::Listener 654 | Condition: UseSSLCertificate 655 | Properties: 656 | DefaultActions: 657 | - Type: forward 658 | TargetGroupArn: !Ref InternalProxyHTTPTargetGroup 659 | LoadBalancerArn: !Ref InternalProxyALB 660 | Port: 443 661 | Protocol: HTTPS 662 | Certificates: 663 | - CertificateArn: !Ref SSLCertificateArn 664 | 665 | # IAM Role for Core Instance 666 | CoreInstanceRole: 667 | Type: AWS::IAM::Role 668 | Properties: 669 | AssumeRolePolicyDocument: 670 | Version: '2012-10-17' 671 | Statement: 672 | - Effect: Allow 673 | Principal: 674 | Service: ec2.amazonaws.com 675 | Action: sts:AssumeRole 676 | Policies: 677 | - PolicyName: SecretsManagerAccess 678 | PolicyDocument: 679 | Version: '2012-10-17' 680 | Statement: 681 | - Effect: Allow 682 | Action: 683 | - secretsmanager:CreateSecret 684 | - secretsmanager:PutSecretValue 685 | - secretsmanager:GetSecretValue 686 | Resource: 687 | - !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/${AWS::StackName}/grpc/* 688 | - !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/${AWS::StackName}/gateway/* 689 | 690 | CoreInstanceProfile: 691 | Type: AWS::IAM::InstanceProfile 692 | Properties: 693 | Roles: 694 | - !Ref CoreInstanceRole 695 | 696 | # IAM Role for Proxy Instance 697 | ProxyInstanceRole: 698 | Type: AWS::IAM::Role 699 | Properties: 700 | AssumeRolePolicyDocument: 701 | Version: '2012-10-17' 702 | Statement: 703 | - Effect: Allow 704 | Principal: 705 | Service: ec2.amazonaws.com 706 | Action: sts:AssumeRole 707 | Policies: 708 | - PolicyName: SecretsManagerReadAccess 709 | PolicyDocument: 710 | Version: '2012-10-17' 711 | Statement: 712 | - Effect: Allow 713 | Action: 714 | - secretsmanager:GetSecretValue 715 | Resource: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/${AWS::StackName}/grpc/* 716 | 717 | ProxyInstanceProfile: 718 | Type: AWS::IAM::InstanceProfile 719 | Properties: 720 | Roles: 721 | - !Ref ProxyInstanceRole 722 | 723 | # IAM Role for Gateway Instance 724 | GatewayInstanceRole: 725 | Type: AWS::IAM::Role 726 | Properties: 727 | AssumeRolePolicyDocument: 728 | Version: '2012-10-17' 729 | Statement: 730 | - Effect: Allow 731 | Principal: 732 | Service: ec2.amazonaws.com 733 | Action: sts:AssumeRole 734 | Policies: 735 | - PolicyName: SecretsManagerReadAccess 736 | PolicyDocument: 737 | Version: '2012-10-17' 738 | Statement: 739 | - Effect: Allow 740 | Action: 741 | - secretsmanager:GetSecretValue 742 | Resource: 743 | - !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/${AWS::StackName}/grpc/* 744 | - !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/${AWS::StackName}/gateway/* 745 | 746 | GatewayInstanceProfile: 747 | Type: AWS::IAM::InstanceProfile 748 | Properties: 749 | Roles: 750 | - !Ref GatewayInstanceRole 751 | 752 | CoreInstance: 753 | Type: AWS::EC2::Instance 754 | Properties: 755 | ImageId: !Ref DefguardAmiId 756 | InstanceType: !Ref CoreInstanceType 757 | KeyName: !If [UseSshKey, !Ref SshKeyName, !Ref "AWS::NoValue"] 758 | IamInstanceProfile: !Ref CoreInstanceProfile 759 | NetworkInterfaces: 760 | - NetworkInterfaceId: !Ref CoreNetworkInterface 761 | DeviceIndex: "0" 762 | UserData: 763 | Fn::Base64: 764 | Fn::Sub: | 765 | #!/bin/bash 766 | set -e 767 | LOG_FILE="/var/log/defguard.log" 768 | 769 | log() { 770 | echo "$(date '+%Y-%m-%d %H:%M:%S') $1" 771 | } 772 | 773 | generate_secret_inner() { 774 | local length="$1" 775 | openssl rand -base64 "$length" | tr -d "=+/" | tr -d '\n' | cut -c1-"$length" 776 | } 777 | 778 | ( 779 | 780 | log "Generating gateway secret..." 781 | GATEWAY_SECRET=$(generate_secret_inner 64) 782 | 783 | log "Storing gateway secret in Secrets Manager..." 784 | aws secretsmanager create-secret --name "/${AWS::StackName}/gateway/secret" --secret-string "$GATEWAY_SECRET" --region ${AWS::Region} || \ 785 | aws secretsmanager put-secret-value --secret-id "/${AWS::StackName}/gateway/secret" --secret-string "$GATEWAY_SECRET" --region ${AWS::Region} 786 | 787 | log "Writing Core configuration to /etc/defguard/core.conf..." 788 | mkdir -p /etc/defguard 789 | tee /etc/defguard/core.conf <&1 831 | openssl req -x509 -new -nodes -key defguard-ca.key -sha256 -days 1825 -out defguard-ca.pem -passin pass:"${!PASSPHRASE}" -subj "/CN=${!DOMAIN}" 2>&1 832 | openssl genrsa -out defguard-grpc.key 2048 2>&1 833 | openssl req -new -key defguard-grpc.key -out defguard-grpc.csr -subj "/CN=${!DOMAIN}" 2>&1 834 | cat >defguard-grpc.ext <&1 846 | 847 | openssl genrsa -out defguard-proxy-grpc.key 2048 2>&1 848 | 849 | openssl req -new -key defguard-proxy-grpc.key -out defguard-proxy-grpc.csr -subj "/CN=${!DOMAIN}" 2>&1 850 | cat >defguard-proxy-grpc.ext <&1 861 | 862 | # Set permissions 863 | chmod 644 /etc/defguard/ssl/*.key 864 | chmod 644 /etc/defguard/ssl/*.pem /etc/defguard/ssl/*.crt 865 | 866 | # Store certificates in Secrets Manager for other instances 867 | log "Storing certificates in Secrets Manager..." 868 | aws secretsmanager create-secret --name "/${AWS::StackName}/grpc/ca-pem" --secret-string file://defguard-ca.pem --region ${AWS::Region} || \ 869 | aws secretsmanager put-secret-value --secret-id "/${AWS::StackName}/grpc/ca-pem" --secret-string file://defguard-ca.pem --region ${AWS::Region} 870 | 871 | aws secretsmanager create-secret --name "/${AWS::StackName}/grpc/ca-passphrase" --secret-string "${!PASSPHRASE}" --region ${AWS::Region} || \ 872 | aws secretsmanager put-secret-value --secret-id "/${AWS::StackName}/grpc/ca-passphrase" --secret-string "${!PASSPHRASE}" --region ${AWS::Region} 873 | 874 | aws secretsmanager create-secret --name "/${AWS::StackName}/grpc/proxy-key" --secret-string file://defguard-proxy-grpc.key --region ${AWS::Region} || \ 875 | aws secretsmanager put-secret-value --secret-id "/${AWS::StackName}/grpc/proxy-key" --secret-string file://defguard-proxy-grpc.key --region ${AWS::Region} 876 | 877 | aws secretsmanager create-secret --name "/${AWS::StackName}/grpc/proxy-crt" --secret-string file://defguard-proxy-grpc.crt --region ${AWS::Region} || \ 878 | aws secretsmanager put-secret-value --secret-id "/${AWS::StackName}/grpc/proxy-crt" --secret-string file://defguard-proxy-grpc.crt --region ${AWS::Region} 879 | 880 | # Clean up temporary files 881 | rm -f defguard-grpc.csr defguard-grpc.ext defguard-proxy-grpc.csr defguard-proxy-grpc.ext defguard-ca.key defguard-ca.srl 882 | 883 | log "gRPC SSL certificates generated and stored" 884 | 885 | log "Enabling defguard service..." 886 | systemctl enable defguard 887 | 888 | log "Starting defguard service..." 889 | systemctl start defguard 890 | 891 | log "Creating VPN location with address ${VpnNetworkAddress} and endpoint ${GatewayElasticIP} and port ${VpnNetworkPort}..." 892 | export $(grep -v '^#' /etc/defguard/core.conf | xargs) && /usr/bin/defguard --secret-key "$GATEWAY_SECRET" init-vpn-location --name ${VpnNetworkName} --address ${VpnNetworkAddress} --endpoint ${GatewayElasticIP} --port ${VpnNetworkPort} --id 1 --allowed-ips ${VpnNetworkAddress} --allowed-ips ${VpcCidr} >> "$LOG_FILE" 2>&1 893 | 894 | log "Created VPN location ${VpnNetworkName} with address ${VpnNetworkAddress} and endpoint ${GatewayElasticIP} and port ${VpnNetworkPort}" 895 | 896 | log "Setup completed." 897 | ) 2>&1 | tee -a "$LOG_FILE" 898 | Tags: 899 | - Key: Name 900 | Value: !Sub ${StackPrefix}-core-instance 901 | DependsOn: 902 | - Database 903 | ProxyInstance: 904 | Type: AWS::EC2::Instance 905 | Properties: 906 | ImageId: !Ref DefguardAmiId 907 | InstanceType: !Ref ProxyInstanceType 908 | KeyName: !If [UseSshKey, !Ref SshKeyName, !Ref "AWS::NoValue"] 909 | IamInstanceProfile: !Ref ProxyInstanceProfile 910 | NetworkInterfaces: 911 | - NetworkInterfaceId: !Ref ProxyNetworkInterface 912 | DeviceIndex: "0" 913 | UserData: 914 | Fn::Base64: 915 | Fn::Sub: | 916 | #!/bin/bash 917 | set -e 918 | LOG_FILE="/var/log/defguard.log" 919 | 920 | log() { 921 | echo "$(date '+%Y-%m-%d %H:%M:%S') $1" 922 | } 923 | 924 | ( 925 | 926 | log "Writing proxy configuration to /etc/defguard/proxy.toml..." 927 | mkdir -p /etc/defguard 928 | tee /etc/defguard/proxy.toml < /etc/defguard/ssl/defguard-proxy-grpc.key 948 | aws secretsmanager get-secret-value --secret-id "/${AWS::StackName}/grpc/proxy-crt" --region ${AWS::Region} --query 'SecretString' --output text > /etc/defguard/ssl/defguard-proxy-grpc.crt 949 | chmod 644 /etc/defguard/ssl/*.key /etc/defguard/ssl/*.crt 950 | log "gRPC SSL certificates retrieved and configured" 951 | 952 | log "Enabling defguard-proxy service..." 953 | systemctl enable defguard-proxy 954 | 955 | log "Starting defguard-proxy service..." 956 | systemctl start defguard-proxy 957 | 958 | log "Setup completed." 959 | ) 2>&1 | tee -a "$LOG_FILE" 960 | Tags: 961 | - Key: Name 962 | Value: !Sub ${StackPrefix}-proxy-instance 963 | DependsOn: 964 | - CoreInstance 965 | GatewayInstance: 966 | Type: AWS::EC2::Instance 967 | Properties: 968 | ImageId: !Ref DefguardAmiId 969 | InstanceType: !Ref GatewayInstanceType 970 | KeyName: !If [UseSshKey, !Ref SshKeyName, !Ref "AWS::NoValue"] 971 | IamInstanceProfile: !Ref GatewayInstanceProfile 972 | NetworkInterfaces: 973 | - NetworkInterfaceId: !Ref GatewayNetworkInterface 974 | DeviceIndex: "0" 975 | UserData: 976 | Fn::Base64: 977 | Fn::Sub: 978 | - | 979 | #!/bin/bash 980 | set -e 981 | LOG_FILE="/var/log/defguard.log" 982 | 983 | log() { 984 | echo "$(date '+%Y-%m-%d %H:%M:%S') $1" 985 | } 986 | 987 | base64url_encode() { 988 | echo -n "$1" | openssl base64 -e -A | tr '+/' '-_' | tr -d '=' 989 | } 990 | 991 | ( 992 | 993 | log "Retrieving gRPC CA certificate from Secrets Manager..." 994 | mkdir -p /etc/defguard/ssl 995 | aws secretsmanager get-secret-value --secret-id "/${AWS::StackName}/grpc/ca-pem" --region ${AWS::Region} --query 'SecretString' --output text > /etc/defguard/ssl/defguard-ca.pem 996 | chmod 644 /etc/defguard/ssl/defguard-ca.pem 997 | log "gRPC CA certificate retrieved" 998 | 999 | log "Retrieving gateway secret from Secrets Manager..." 1000 | SECRET=$(aws secretsmanager get-secret-value --secret-id "/${AWS::StackName}/gateway/secret" --region ${AWS::Region} --query 'SecretString' --output text) 1001 | log "Gateway secret retrieved" 1002 | 1003 | log "Generating gateway token..." 1004 | NETWORK_ID="1" 1005 | ISSUER="DefGuard" 1006 | 1007 | HEADER='{"alg":"HS256","typ":"JWT"}' 1008 | NOW=$(date +%s) 1009 | EXPIRATION=$(($NOW + 315360000)) 1010 | PAYLOAD=$(cat <&1 | tee -a "$LOG_FILE" 1113 | - { CorePrivateIP: !GetAtt CoreNetworkInterface.PrimaryPrivateIpAddress } 1114 | Tags: 1115 | - Key: Name 1116 | Value: !Sub ${StackPrefix}-gateway-instance 1117 | DependsOn: 1118 | - CoreInstance 1119 | 1120 | # Lambda function to call Defguard API for device creation 1121 | DefguardFirstDeviceFunction: 1122 | Type: AWS::Lambda::Function 1123 | Properties: 1124 | FunctionName: !Sub ${StackPrefix}-defguard-device-setup 1125 | Runtime: python3.9 1126 | Handler: index.handler 1127 | VpcConfig: 1128 | SecurityGroupIds: 1129 | - !Ref LambdaSecurityGroup 1130 | SubnetIds: 1131 | - !Ref DefguardPrivateSubnet1 1132 | Code: 1133 | ZipFile: | 1134 | import json 1135 | import urllib.request 1136 | import urllib.parse 1137 | import urllib.error 1138 | import time 1139 | 1140 | def send_response(event, context, response_status, response_data=None, physical_resource_id=None): 1141 | if response_data is None: 1142 | response_data = {} 1143 | 1144 | # Use existing ID for updates/deletes, let CloudFormation generate one for creates 1145 | if physical_resource_id is None and event['RequestType'] != 'Create': 1146 | physical_resource_id = event.get('PhysicalResourceId', 'DefguardDeviceSetup') 1147 | elif physical_resource_id is None: 1148 | physical_resource_id = 'DefguardDeviceSetup' 1149 | 1150 | response_url = event['ResponseURL'] 1151 | 1152 | response_body = { 1153 | 'Status': response_status, 1154 | 'Reason': f'See CloudWatch Log Stream: {context.log_stream_name}', 1155 | 'PhysicalResourceId': physical_resource_id, 1156 | 'StackId': event['StackId'], 1157 | 'RequestId': event['RequestId'], 1158 | 'LogicalResourceId': event['LogicalResourceId'], 1159 | 'Data': response_data 1160 | } 1161 | 1162 | json_response_body = json.dumps(response_body).encode('utf-8') 1163 | 1164 | print(f"Sending response to {response_url}") 1165 | print(f"Response body: {json.dumps(response_body, indent=2)}") 1166 | 1167 | try: 1168 | request = urllib.request.Request( 1169 | response_url, 1170 | data=json_response_body, 1171 | headers={ 1172 | 'Content-Type': '', 1173 | 'Content-Length': str(len(json_response_body)) 1174 | }, 1175 | method='PUT' 1176 | ) 1177 | 1178 | response = urllib.request.urlopen(request, timeout=30) 1179 | print(f"Response sent successfully. Status: {response.status}") 1180 | return True 1181 | 1182 | except Exception as e: 1183 | print(f"Failed to send response: {str(e)}") 1184 | return False 1185 | 1186 | def handler(event, context): 1187 | print(f"Event: {json.dumps(event, indent=2)}") 1188 | 1189 | try: 1190 | if event['RequestType'] == 'Create': 1191 | core_ip = event['ResourceProperties']['CoreIP'] 1192 | core_port = event['ResourceProperties']['CorePort'] 1193 | admin_password = event['ResourceProperties']['AdminPassword'] 1194 | 1195 | max_retries = 5 1196 | for attempt in range(max_retries): 1197 | try: 1198 | print(f"Attempt {attempt + 1}/{max_retries} to create device") 1199 | token = create_user_device(core_ip, core_port, admin_password) 1200 | 1201 | response_data = { 1202 | 'Token': token, 1203 | 'Message': 'Device created successfully' 1204 | } 1205 | 1206 | print(f"Sending SUCCESS response with token: {token}") 1207 | return send_response(event, context, 'SUCCESS', response_data) 1208 | 1209 | except Exception as e: 1210 | print(f"Attempt {attempt + 1} failed: {str(e)}") 1211 | if attempt < max_retries - 1: 1212 | print(f"Waiting 30 seconds before retry...") 1213 | time.sleep(30) 1214 | else: 1215 | raise e 1216 | 1217 | elif event['RequestType'] in ['Update', 'Delete']: 1218 | response_data = {'Message': f'{event["RequestType"]} operation completed'} 1219 | print(f"Sending SUCCESS response for {event['RequestType']}") 1220 | return send_response(event, context, 'SUCCESS', response_data, event.get('PhysicalResourceId')) 1221 | 1222 | except Exception as e: 1223 | error_msg = str(e) 1224 | print(f"Error occurred: {error_msg}") 1225 | response_data = {'Error': error_msg} 1226 | return send_response(event, context, 'FAILED', response_data) 1227 | 1228 | def create_user_device(core_ip, core_port, admin_password): 1229 | base_url = f"http://{core_ip}:{core_port}" 1230 | 1231 | print("Authenticating as admin...") 1232 | auth_data = json.dumps({ 1233 | "username": "admin", 1234 | "password": admin_password 1235 | }).encode('utf-8') 1236 | 1237 | auth_request = urllib.request.Request( 1238 | f"{base_url}/api/v1/auth", 1239 | data=auth_data, 1240 | headers={ 1241 | 'Content-Type': 'application/json', 1242 | 'User-Agent': 'Defguard-CloudFormation-Setup/1.0' 1243 | }, 1244 | method='POST' 1245 | ) 1246 | 1247 | try: 1248 | auth_response = urllib.request.urlopen(auth_request) 1249 | if auth_response.status != 200: 1250 | raise Exception(f"Authentication failed: {auth_response.status}") 1251 | except urllib.error.HTTPError as e: 1252 | raise Exception(f"Authentication failed: {e.code} - {e.read().decode()}") 1253 | 1254 | session_cookie = None 1255 | for header_name, header_value in auth_response.headers.items(): 1256 | if header_name.lower() == 'set-cookie' and 'defguard_session=' in header_value: 1257 | session_cookie = header_value.split('defguard_session=')[1].split(';')[0] 1258 | break 1259 | 1260 | if not session_cookie: 1261 | raise Exception("No session cookie received from authentication") 1262 | 1263 | print(f"Authentication successful, session cookie: {session_cookie[:20]}...") 1264 | 1265 | print("Creating enrollment token...") 1266 | token_data = json.dumps({ 1267 | "email": "", 1268 | "send_enrollment_notification": False, 1269 | "username": "admin" 1270 | }).encode('utf-8') 1271 | 1272 | token_request = urllib.request.Request( 1273 | f"{base_url}/api/v1/user/admin/start_desktop", 1274 | data=token_data, 1275 | headers={ 1276 | 'Content-Type': 'application/json', 1277 | 'User-Agent': 'Defguard-CloudFormation-Setup/1.0', 1278 | 'Cookie': f'defguard_session={session_cookie}' 1279 | }, 1280 | method='POST' 1281 | ) 1282 | 1283 | try: 1284 | token_response = urllib.request.urlopen(token_request) 1285 | response_data = json.loads(token_response.read().decode('utf-8')) 1286 | enrollment_token = response_data.get('enrollment_token', 'TOKEN_NOT_FOUND') 1287 | enrollment_url = response_data.get('enrollment_url', 'URL_NOT_FOUND') 1288 | print(f"Enrollment token created successfully: {enrollment_token}") 1289 | print(f"Enrollment URL: {enrollment_url}") 1290 | return enrollment_token 1291 | except urllib.error.HTTPError as e: 1292 | error_msg = e.read().decode() 1293 | print(f"Token creation failed: {e.code} - {error_msg}") 1294 | return f"API_CALL_FAILED_{e.code}_{error_msg[:50]}" 1295 | Role: !GetAtt DefguardApiRole.Arn 1296 | Timeout: 300 1297 | DependsOn: 1298 | - CoreInstance 1299 | - CoreNetworkInterface 1300 | - NATGateway 1301 | - LambdaSecurityGroup 1302 | - DefguardPrivateSubnet1 1303 | - PrivateRouteTable 1304 | - PrivateRoute 1305 | - DefguardPrivateSubnet1RouteTableAssociation 1306 | - DefguardPrivateSubnet2 1307 | - DefguardPrivateSubnet2RouteTableAssociation 1308 | - InternetGateway 1309 | - VPCGatewayAttachment 1310 | - VPC 1311 | - PublicRouteTable 1312 | - PublicRoute 1313 | - DefguardPublicSubnet 1314 | - DefguardPublicSubnetRouteTableAssociation 1315 | - DefguardPublicSubnet2 1316 | - DefguardPublicSubnet2RouteTableAssociation 1317 | 1318 | DefguardApiRole: 1319 | Type: AWS::IAM::Role 1320 | Properties: 1321 | AssumeRolePolicyDocument: 1322 | Version: '2012-10-17' 1323 | Statement: 1324 | - Effect: Allow 1325 | Principal: 1326 | Service: lambda.amazonaws.com 1327 | Action: sts:AssumeRole 1328 | ManagedPolicyArns: 1329 | - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 1330 | - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole 1331 | 1332 | DeviceSetup: 1333 | Type: AWS::CloudFormation::CustomResource 1334 | Properties: 1335 | ServiceToken: !GetAtt DefguardFirstDeviceFunction.Arn 1336 | CoreIP: !GetAtt CoreNetworkInterface.PrimaryPrivateIpAddress 1337 | CorePort: !Ref CoreHttpPort 1338 | AdminPassword: !Ref CoreDefaultAdminPassword 1339 | DependsOn: 1340 | - CoreInstance 1341 | 1342 | ########################################################################### 1343 | ############################# Template outputs ############################ 1344 | ########################################################################### 1345 | Outputs: 1346 | DefguardCorePrivateAddress: 1347 | Description: The IP address of the Defguard Core instance in the internal network 1348 | Value: !GetAtt CoreNetworkInterface.PrimaryPrivateIpAddress 1349 | Export: 1350 | Name: !Sub ${AWS::StackName}-core-private-ip 1351 | DefguardProxyPrivateAddress: 1352 | Description: The private IP address of the Defguard Proxy instance 1353 | Value: !GetAtt ProxyNetworkInterface.PrimaryPrivateIpAddress 1354 | Export: 1355 | Name: !Sub ${AWS::StackName}-proxy-private-ip 1356 | DefguardProxyPublicAddress: 1357 | Description: The public IP address of the Defguard Proxy instance 1358 | Value: !Ref ProxyElasticIP 1359 | Export: 1360 | Name: !Sub ${AWS::StackName}-proxy-public-ip 1361 | DefguardGatewayPublicAddress: 1362 | Description: The public IP address of the Defguard Gateway instance 1363 | Value: !Ref GatewayElasticIP 1364 | Export: 1365 | Name: !Sub ${AWS::StackName}-gateway-public-ip 1366 | DefguardGatewayPrivateAddress: 1367 | Description: The private IP address of the Defguard Gateway instance 1368 | Value: !GetAtt GatewayNetworkInterface.PrimaryPrivateIpAddress 1369 | Export: 1370 | Name: !Sub ${AWS::StackName}-gateway-private-ip 1371 | DatabaseEndpoint: 1372 | Description: The endpoint of the RDS database 1373 | Value: !GetAtt Database.Endpoint.Address 1374 | Export: 1375 | Name: !Sub ${AWS::StackName}-database-endpoint 1376 | VPCId: 1377 | Description: The ID of the VPC 1378 | Value: !Ref VPC 1379 | Export: 1380 | Name: !Sub ${AWS::StackName}-vpc-id 1381 | PublicSubnetId: 1382 | Description: The ID of the public subnet 1383 | Value: !Ref DefguardPublicSubnet 1384 | Export: 1385 | Name: !Sub ${AWS::StackName}-public-subnet-id 1386 | AdminFirstDeviceToken: 1387 | Description: Enrollment token for admin's first device. Use it to add the first device to Defguard and access the VPN. 1388 | Value: !GetAtt DeviceSetup.Token 1389 | Export: 1390 | Name: !Sub ${AWS::StackName}-admin-first-device-token 1391 | 1392 | PublicProxyALBDNSName: 1393 | Description: DNS name of the public reverse proxy (ALB) for Defguard Proxy 1394 | Value: !GetAtt PublicProxyALB.DNSName 1395 | Export: 1396 | Name: !Sub ${AWS::StackName}-public-proxy-alb-dns-name 1397 | 1398 | PublicProxyALBHostedZoneID: 1399 | Description: Hosted Zone ID of the public reverse proxy (ALB) for Route53 alias records 1400 | Value: !GetAtt PublicProxyALB.CanonicalHostedZoneID 1401 | Export: 1402 | Name: !Sub ${AWS::StackName}-public-proxy-alb-hosted-zone-id 1403 | 1404 | PublicProxyURL: 1405 | Description: URL of the public reverse proxy for Defguard Proxy (uses ProxyUrl parameter) 1406 | Value: !Ref ProxyUrl 1407 | Export: 1408 | Name: !Sub ${AWS::StackName}-public-proxy-url 1409 | 1410 | InternalProxyALBDNSName: 1411 | Description: DNS name of the internal reverse proxy (ALB) for Defguard Core 1412 | Value: !GetAtt InternalProxyALB.DNSName 1413 | Export: 1414 | Name: !Sub ${AWS::StackName}-internal-proxy-alb-dns-name 1415 | 1416 | InternalProxyALBHostedZoneID: 1417 | Description: Hosted Zone ID of the internal reverse proxy (ALB) for Route53 alias records 1418 | Value: !GetAtt InternalProxyALB.CanonicalHostedZoneID 1419 | Export: 1420 | Name: !Sub ${AWS::StackName}-internal-proxy-alb-hosted-zone-id 1421 | 1422 | InternalProxyURL: 1423 | Description: URL of the internal reverse proxy for Defguard Core (uses CoreUrl parameter) 1424 | Value: !Ref CoreUrl 1425 | Export: 1426 | Name: !Sub ${AWS::StackName}-internal-proxy-url 1427 | --------------------------------------------------------------------------------