From 300dbe98578fe6da078e9d000f3658b6cf3b996f Mon Sep 17 00:00:00 2001 From: dadgam3er Date: Thu, 22 Aug 2024 20:56:17 -0400 Subject: [PATCH] Re-add deployment as a regular directory --- defguardDocker/deployment | 1 - .../deployment/.github/workflows/release.yml | 41 + .../deployment/.github/workflows/test.yml | 46 + defguardDocker/deployment/.gitignore | 3 + defguardDocker/deployment/LICENSE | 13 + defguardDocker/deployment/README.md | 16 + .../charts/defguard-proxy/.helmignore | 23 + .../charts/defguard-proxy/Chart.yaml | 7 + .../charts/defguard-proxy/templates/NOTES.txt | 20 + .../defguard-proxy/templates/_helpers.tpl | 62 ++ .../defguard-proxy/templates/config.yaml | 9 + .../defguard-proxy/templates/deployment.yaml | 67 ++ .../templates/grpc-service.yaml | 17 + .../templates/ingress-grpc.yaml | 52 + .../defguard-proxy/templates/ingress-web.yaml | 52 + .../defguard-proxy/templates/service.yaml | 15 + .../templates/serviceaccount.yaml | 12 + .../charts/defguard-proxy/values.yaml | 42 + .../deployment/charts/defguard/.helmignore | 23 + .../deployment/charts/defguard/Chart.lock | 9 + .../deployment/charts/defguard/Chart.yaml | 17 + .../defguard/charts/defguard-proxy-0.3.5.tgz | Bin 0 -> 3330 bytes .../defguard/charts/postgresql-12.12.10.tgz | Bin 0 -> 62562 bytes .../charts/defguard/templates/NOTES.txt | 20 + .../charts/defguard/templates/_helpers.tpl | 78 ++ .../defguard/templates/defguard-config.yaml | 30 + .../templates/defguard-deployment.yaml | 105 +++ .../defguard/templates/defguard-secret.yaml | 25 + .../defguard/templates/defguard-service.yaml | 15 + .../defguard/templates/grpc-service.yaml | 17 + .../defguard/templates/ingress-grpc.yaml | 52 + .../defguard/templates/ingress-web.yaml | 52 + .../defguard/templates/openid-secret.yaml | 16 + .../defguard/templates/postgresql-secret.yaml | 19 + .../defguard/templates/serviceaccount.yaml | 12 + .../deployment/charts/defguard/values.yaml | 75 ++ .../deployment/docker-compose/.env.template | 31 + .../docker-compose/docker-compose.yaml | 110 +++ .../docker-compose/setup.log.rGByG7 | 0 .../deployment/docker-compose/setup.sh | 890 ++++++++++++++++++ defguardDocker/deployment/docs/header.png | Bin 0 -> 24820 bytes .../deployment/gateway/.env.template | 9 + .../deployment/gateway/docker-compose.yaml | 22 + 43 files changed, 2124 insertions(+), 1 deletion(-) delete mode 160000 defguardDocker/deployment create mode 100644 defguardDocker/deployment/.github/workflows/release.yml create mode 100644 defguardDocker/deployment/.github/workflows/test.yml create mode 100644 defguardDocker/deployment/.gitignore create mode 100644 defguardDocker/deployment/LICENSE create mode 100644 defguardDocker/deployment/README.md create mode 100644 defguardDocker/deployment/charts/defguard-proxy/.helmignore create mode 100644 defguardDocker/deployment/charts/defguard-proxy/Chart.yaml create mode 100644 defguardDocker/deployment/charts/defguard-proxy/templates/NOTES.txt create mode 100644 defguardDocker/deployment/charts/defguard-proxy/templates/_helpers.tpl create mode 100644 defguardDocker/deployment/charts/defguard-proxy/templates/config.yaml create mode 100644 defguardDocker/deployment/charts/defguard-proxy/templates/deployment.yaml create mode 100644 defguardDocker/deployment/charts/defguard-proxy/templates/grpc-service.yaml create mode 100644 defguardDocker/deployment/charts/defguard-proxy/templates/ingress-grpc.yaml create mode 100644 defguardDocker/deployment/charts/defguard-proxy/templates/ingress-web.yaml create mode 100644 defguardDocker/deployment/charts/defguard-proxy/templates/service.yaml create mode 100644 defguardDocker/deployment/charts/defguard-proxy/templates/serviceaccount.yaml create mode 100644 defguardDocker/deployment/charts/defguard-proxy/values.yaml create mode 100644 defguardDocker/deployment/charts/defguard/.helmignore create mode 100644 defguardDocker/deployment/charts/defguard/Chart.lock create mode 100644 defguardDocker/deployment/charts/defguard/Chart.yaml create mode 100644 defguardDocker/deployment/charts/defguard/charts/defguard-proxy-0.3.5.tgz create mode 100644 defguardDocker/deployment/charts/defguard/charts/postgresql-12.12.10.tgz create mode 100644 defguardDocker/deployment/charts/defguard/templates/NOTES.txt create mode 100644 defguardDocker/deployment/charts/defguard/templates/_helpers.tpl create mode 100644 defguardDocker/deployment/charts/defguard/templates/defguard-config.yaml create mode 100644 defguardDocker/deployment/charts/defguard/templates/defguard-deployment.yaml create mode 100644 defguardDocker/deployment/charts/defguard/templates/defguard-secret.yaml create mode 100644 defguardDocker/deployment/charts/defguard/templates/defguard-service.yaml create mode 100644 defguardDocker/deployment/charts/defguard/templates/grpc-service.yaml create mode 100644 defguardDocker/deployment/charts/defguard/templates/ingress-grpc.yaml create mode 100644 defguardDocker/deployment/charts/defguard/templates/ingress-web.yaml create mode 100644 defguardDocker/deployment/charts/defguard/templates/openid-secret.yaml create mode 100644 defguardDocker/deployment/charts/defguard/templates/postgresql-secret.yaml create mode 100644 defguardDocker/deployment/charts/defguard/templates/serviceaccount.yaml create mode 100644 defguardDocker/deployment/charts/defguard/values.yaml create mode 100644 defguardDocker/deployment/docker-compose/.env.template create mode 100644 defguardDocker/deployment/docker-compose/docker-compose.yaml create mode 100644 defguardDocker/deployment/docker-compose/setup.log.rGByG7 create mode 100755 defguardDocker/deployment/docker-compose/setup.sh create mode 100644 defguardDocker/deployment/docs/header.png create mode 100644 defguardDocker/deployment/gateway/.env.template create mode 100644 defguardDocker/deployment/gateway/docker-compose.yaml diff --git a/defguardDocker/deployment b/defguardDocker/deployment deleted file mode 160000 index aa830b0..0000000 --- a/defguardDocker/deployment +++ /dev/null @@ -1 +0,0 @@ -Subproject commit aa830b0412a0335d75942c68081dd852c6537803 diff --git a/defguardDocker/deployment/.github/workflows/release.yml b/defguardDocker/deployment/.github/workflows/release.yml new file mode 100644 index 0000000..5da777d --- /dev/null +++ b/defguardDocker/deployment/.github/workflows/release.yml @@ -0,0 +1,41 @@ +name: Release Charts + +on: + push: + branches: + - main + +jobs: + release: + runs-on: [self-hosted, Linux, X64] + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Configure Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + + - name: Install Helm + uses: azure/setup-helm@v3 + with: + version: v3.14.0 + + # https://github.com/helm/chart-releaser-action/issues/74 + - name: Add repositories + run: | + for dir in $(ls -d charts/*/); do + 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 + done + + - name: Run chart-releaser + uses: helm/chart-releaser-action@v1.6.0 + env: + CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + with: + charts_dir: charts + skip_existing: true diff --git a/defguardDocker/deployment/.github/workflows/test.yml b/defguardDocker/deployment/.github/workflows/test.yml new file mode 100644 index 0000000..65a0a9a --- /dev/null +++ b/defguardDocker/deployment/.github/workflows/test.yml @@ -0,0 +1,46 @@ +name: Test setup script + +on: + push: + branches: + - main + paths: + - 'docker-compose/**' + - '.github/workflows/test.yml' + +jobs: + test: + name: Test setup script + runs-on: [self-hosted, Linux] + steps: + - name: Login to GitHub container registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Create working directory + run: mkdir temp + - name: Run setup script + env: + DEFGUARD_DOMAIN: "id.localhost" + DEFGUARD_ENROLLMENT_DOMAIN: "enrollment.localhost" + DEFGUARD_VPN_NAME: "test_location" + DEFGUARD_VPN_IP: "10.0.60.1/24" + DEFGUARD_VPN_GATEWAY_IP: "10.20.20.40" + DEFGUARD_VPN_GATEWAY_PORT: "50050" + CORE_IMAGE_TAG: latest + PROXY_IMAGE_TAG: latest + GATEWAY_IMAGE_TAG: latest + working-directory: temp + run: curl --proto '=https' --tlsv1.2 -sSf -L https://raw.githubusercontent.com/DefGuard/deployment/main/docker-compose/setup.sh | bash -s - --non-interactive + - name: Sleep for 10 seconds + working-directory: temp + run: sleep 10s + - name: Test defguard is available + working-directory: temp + run: curl -f http://id.localhost/api/v1/health + - name: Stop compose stack + if: always() + working-directory: temp + run: docker-compose down diff --git a/defguardDocker/deployment/.gitignore b/defguardDocker/deployment/.gitignore new file mode 100644 index 0000000..e5c0d50 --- /dev/null +++ b/defguardDocker/deployment/.gitignore @@ -0,0 +1,3 @@ +docker-compose/.env +docker-compose/.volumes +.idea diff --git a/defguardDocker/deployment/LICENSE b/defguardDocker/deployment/LICENSE new file mode 100644 index 0000000..8ddd140 --- /dev/null +++ b/defguardDocker/deployment/LICENSE @@ -0,0 +1,13 @@ +Copyright 2023 teonite ventures sp. z o.o. (teonite) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/defguardDocker/deployment/README.md b/defguardDocker/deployment/README.md new file mode 100644 index 0000000..8e6b830 --- /dev/null +++ b/defguardDocker/deployment/README.md @@ -0,0 +1,16 @@ +

+ defguard +

+ +# Defguard deployment + +Check our [documentation](https://defguard.gitbook.io/defguard/features/setting-up-your-instance) for deployment +instructions. + +## Community and Support + +Find us on Matrix: [#defguard:teonite.com](https://matrix.to/#/#defguard:teonite.com) + +## Contribution + +Please review the [Contributing guide](https://defguard.gitbook.io/defguard/for-developers/contributing) for information on how to get started contributing to the project. You might also find our [environment setup guide](https://defguard.gitbook.io/defguard/for-developers/dev-env-setup) handy. diff --git a/defguardDocker/deployment/charts/defguard-proxy/.helmignore b/defguardDocker/deployment/charts/defguard-proxy/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/defguardDocker/deployment/charts/defguard-proxy/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/defguardDocker/deployment/charts/defguard-proxy/Chart.yaml b/defguardDocker/deployment/charts/defguard-proxy/Chart.yaml new file mode 100644 index 0000000..61a06d9 --- /dev/null +++ b/defguardDocker/deployment/charts/defguard-proxy/Chart.yaml @@ -0,0 +1,7 @@ +apiVersion: v2 +name: defguard-proxy +description: Defguard proxy is a public-facing proxy for core defguard service + +type: application +version: 0.3.5 +appVersion: 0.5.0 diff --git a/defguardDocker/deployment/charts/defguard-proxy/templates/NOTES.txt b/defguardDocker/deployment/charts/defguard-proxy/templates/NOTES.txt new file mode 100644 index 0000000..d8d21a5 --- /dev/null +++ b/defguardDocker/deployment/charts/defguard-proxy/templates/NOTES.txt @@ -0,0 +1,20 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host }}/ +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "defguard-proxy.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "defguard-proxy.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "defguard-proxy.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + 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}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/defguardDocker/deployment/charts/defguard-proxy/templates/_helpers.tpl b/defguardDocker/deployment/charts/defguard-proxy/templates/_helpers.tpl new file mode 100644 index 0000000..b6b625e --- /dev/null +++ b/defguardDocker/deployment/charts/defguard-proxy/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "defguard-proxy.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "defguard-proxy.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "defguard-proxy.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "defguard-proxy.labels" -}} +helm.sh/chart: {{ include "defguard-proxy.chart" . }} +{{ include "defguard-proxy.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "defguard-proxy.selectorLabels" -}} +app.kubernetes.io/name: {{ include "defguard-proxy.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "defguard-proxy.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "defguard-proxy.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/defguardDocker/deployment/charts/defguard-proxy/templates/config.yaml b/defguardDocker/deployment/charts/defguard-proxy/templates/config.yaml new file mode 100644 index 0000000..5efcad2 --- /dev/null +++ b/defguardDocker/deployment/charts/defguard-proxy/templates/config.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "defguard-proxy.fullname" . }}-config + labels: + {{- include "defguard-proxy.labels" . | nindent 4 }} +data: + DEFGUARD_PROXY_HTTP_PORT: {{ .Values.service.ports.http | quote }} + DEFGUARD_PROXY_GRPC_PORT: {{ .Values.service.ports.grpc | quote }} diff --git a/defguardDocker/deployment/charts/defguard-proxy/templates/deployment.yaml b/defguardDocker/deployment/charts/defguard-proxy/templates/deployment.yaml new file mode 100644 index 0000000..4e6da60 --- /dev/null +++ b/defguardDocker/deployment/charts/defguard-proxy/templates/deployment.yaml @@ -0,0 +1,67 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "defguard-proxy.fullname" . }} + labels: + {{- include "defguard-proxy.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "defguard-proxy.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "defguard-proxy.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "defguard-proxy.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + envFrom: + - configMapRef: + name: {{ include "defguard-proxy.fullname" . }}-config + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.ports.http }} + protocol: TCP + - name: grpc + containerPort: {{ .Values.service.ports.grpc }} + protocol: TCP + livenessProbe: + httpGet: + path: /api/v1/health + port: http + readinessProbe: + httpGet: + path: /api/v1/health + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/defguardDocker/deployment/charts/defguard-proxy/templates/grpc-service.yaml b/defguardDocker/deployment/charts/defguard-proxy/templates/grpc-service.yaml new file mode 100644 index 0000000..b698b07 --- /dev/null +++ b/defguardDocker/deployment/charts/defguard-proxy/templates/grpc-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + annotations: + traefik.ingress.kubernetes.io/service.serversscheme: h2c + name: {{ include "defguard-proxy.fullname" . }}-grpc + labels: + {{- include "defguard-proxy.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.ports.grpc }} + targetPort: grpc + protocol: TCP + name: grpc + selector: + {{- include "defguard-proxy.selectorLabels" . | nindent 4 }} diff --git a/defguardDocker/deployment/charts/defguard-proxy/templates/ingress-grpc.yaml b/defguardDocker/deployment/charts/defguard-proxy/templates/ingress-grpc.yaml new file mode 100644 index 0000000..30fdc66 --- /dev/null +++ b/defguardDocker/deployment/charts/defguard-proxy/templates/ingress-grpc.yaml @@ -0,0 +1,52 @@ +{{- if .Values.ingress.grpc.enabled -}} +{{- $fullName := include "defguard-proxy.fullname" . -}} +{{- if and .Values.ingress.grpc.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.grpc.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.grpc.annotations "kubernetes.io/ingress.class" .Values.ingress.grpc.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }}-grpc + labels: + {{- include "defguard-proxy.labels" . | nindent 4 }} + {{- with .Values.ingress.grpc.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.grpc.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.grpc.className }} + {{- end }} + {{- if .Values.ingress.grpc.tls }} + tls: + - hosts: + - {{ .Values.ingress.grpc.host | quote }} + secretName: {{ printf "%s-grpc-tls" .Values.ingress.grpc.host }} + {{- end }} + rules: + - host: {{ .Values.ingress.grpc.host | quote }} + http: + paths: + - path: / + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: ImplementationSpecific + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }}-grpc + port: + number: {{ .Values.service.ports.grpc }} + {{- else }} + serviceName: {{ $fullName }}-grpc + servicePort: {{ .Values.service.ports.grpc }} + {{- end }} +{{- end }} diff --git a/defguardDocker/deployment/charts/defguard-proxy/templates/ingress-web.yaml b/defguardDocker/deployment/charts/defguard-proxy/templates/ingress-web.yaml new file mode 100644 index 0000000..e13c124 --- /dev/null +++ b/defguardDocker/deployment/charts/defguard-proxy/templates/ingress-web.yaml @@ -0,0 +1,52 @@ +{{- if .Values.ingress.web.enabled -}} +{{- $fullName := include "defguard-proxy.fullname" . -}} +{{- if and .Values.ingress.web.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.web.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.web.annotations "kubernetes.io/ingress.class" .Values.ingress.web.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }}-web + labels: + {{- include "defguard-proxy.labels" . | nindent 4 }} + {{- with .Values.ingress.web.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.web.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.web.className }} + {{- end }} + {{- if .Values.ingress.web.tls }} + tls: + - hosts: + - {{ .Values.ingress.web.host | quote }} + secretName: {{ printf "%s-web-tls" .Values.ingress.web.host }} + {{- end }} + rules: + - host: {{ .Values.ingress.web.host | quote }} + http: + paths: + - path: / + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: ImplementationSpecific + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }}-web + port: + number: {{ .Values.service.ports.http }} + {{- else }} + serviceName: {{ $fullName }}-web + servicePort: {{ .Values.service.ports.http }} + {{- end }} +{{- end }} diff --git a/defguardDocker/deployment/charts/defguard-proxy/templates/service.yaml b/defguardDocker/deployment/charts/defguard-proxy/templates/service.yaml new file mode 100644 index 0000000..151f128 --- /dev/null +++ b/defguardDocker/deployment/charts/defguard-proxy/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "defguard-proxy.fullname" . }}-web + labels: + {{- include "defguard-proxy.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.ports.http }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "defguard-proxy.selectorLabels" . | nindent 4 }} diff --git a/defguardDocker/deployment/charts/defguard-proxy/templates/serviceaccount.yaml b/defguardDocker/deployment/charts/defguard-proxy/templates/serviceaccount.yaml new file mode 100644 index 0000000..a77a067 --- /dev/null +++ b/defguardDocker/deployment/charts/defguard-proxy/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "defguard-proxy.serviceAccountName" . }} + labels: + {{- include "defguard-proxy.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/defguardDocker/deployment/charts/defguard-proxy/values.yaml b/defguardDocker/deployment/charts/defguard-proxy/values.yaml new file mode 100644 index 0000000..02d3491 --- /dev/null +++ b/defguardDocker/deployment/charts/defguard-proxy/values.yaml @@ -0,0 +1,42 @@ +affinity: {} +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 +fullnameOverride: "" +image: + pullPolicy: IfNotPresent + repository: ghcr.io/defguard/defguard-proxy + tag: "" +imagePullSecrets: [] +ingress: + grpc: + annotations: {} + className: "" + enabled: true + host: enrollment-grpc.local + tls: false + web: + annotations: {} + className: "" + enabled: true + host: enrollment.local + tls: false +nameOverride: "" +nodeSelector: {} +podAnnotations: {} +podLabels: {} +podSecurityContext: {} +publicUrl: "http://enrollment.local" +replicaCount: 1 +resources: {} +securityContext: {} +service: + ports: + http: 8080 + grpc: 50051 + type: ClusterIP +serviceAccount: + annotations: {} + create: true +tolerations: [] diff --git a/defguardDocker/deployment/charts/defguard/.helmignore b/defguardDocker/deployment/charts/defguard/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/defguardDocker/deployment/charts/defguard/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/defguardDocker/deployment/charts/defguard/Chart.lock b/defguardDocker/deployment/charts/defguard/Chart.lock new file mode 100644 index 0000000..6a57515 --- /dev/null +++ b/defguardDocker/deployment/charts/defguard/Chart.lock @@ -0,0 +1,9 @@ +dependencies: +- name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 12.12.10 +- name: defguard-proxy + repository: https://defguard.github.io/deployment + version: 0.3.5 +digest: sha256:de930b480616cfa369caf7b1447c5b3e729fce3e17994717ab0f64aa02c027e7 +generated: "2024-07-26T09:00:54.309522115+02:00" diff --git a/defguardDocker/deployment/charts/defguard/Chart.yaml b/defguardDocker/deployment/charts/defguard/Chart.yaml new file mode 100644 index 0000000..9444933 --- /dev/null +++ b/defguardDocker/deployment/charts/defguard/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +name: defguard +description: Defguard is an open-source enterprise wireGuard VPN with MFA and SSO + +type: application +version: 0.7.6 +appVersion: 0.11.0 + +dependencies: + - name: postgresql + condition: postgresql.enabled + version: 12.12.10 + repository: https://charts.bitnami.com/bitnami + - name: defguard-proxy + condition: defguard-proxy.enabled + version: 0.3.5 + repository: https://defguard.github.io/deployment diff --git a/defguardDocker/deployment/charts/defguard/charts/defguard-proxy-0.3.5.tgz b/defguardDocker/deployment/charts/defguard/charts/defguard-proxy-0.3.5.tgz new file mode 100644 index 0000000000000000000000000000000000000000..830a28ca838ef9e3bee1bda2628f77a624248fcd GIT binary patch literal 3330 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

VQk(m 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 x-zBAytSHwSn|L8%-%e!`$$Y!;ho1F` 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.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.grpc.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.grpc.annotations "kubernetes.io/ingress.class" .Values.ingress.grpc.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }}-grpc + labels: + {{- include "defguard.labels" . | nindent 4 }} + {{- with .Values.ingress.grpc.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.grpc.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.grpc.className }} + {{- end }} + {{- if .Values.ingress.grpc.tls }} + tls: + - hosts: + - {{ .Values.ingress.grpc.host | quote }} + secretName: {{ printf "%s-grpc-tls" .Values.ingress.grpc.host }} + {{- end }} + rules: + - host: {{ .Values.ingress.grpc.host | quote }} + http: + paths: + - path: / + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: ImplementationSpecific + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }}-grpc + port: + number: {{ .Values.service.ports.grpc }} + {{- else }} + serviceName: {{ $fullName }}-grpc + servicePort: {{ .Values.service.ports.grpc }} + {{- end }} +{{- end }} diff --git a/defguardDocker/deployment/charts/defguard/templates/ingress-web.yaml b/defguardDocker/deployment/charts/defguard/templates/ingress-web.yaml new file mode 100644 index 0000000..c53c7c6 --- /dev/null +++ b/defguardDocker/deployment/charts/defguard/templates/ingress-web.yaml @@ -0,0 +1,52 @@ +{{- if .Values.ingress.web.enabled -}} +{{- $fullName := include "defguard.fullname" . -}} +{{- if and .Values.ingress.web.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.web.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.web.annotations "kubernetes.io/ingress.class" .Values.ingress.web.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }}-web + labels: + {{- include "defguard.labels" . | nindent 4 }} + {{- with .Values.ingress.web.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.web.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.web.className }} + {{- end }} + {{- if .Values.ingress.web.tls }} + tls: + - hosts: + - {{ .Values.ingress.web.host | quote }} + secretName: {{ printf "%s-web-tls" .Values.ingress.web.host }} + {{- end }} + rules: + - host: {{ .Values.ingress.web.host | quote }} + http: + paths: + - path: / + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: ImplementationSpecific + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }}-web + port: + number: {{ .Values.service.ports.http }} + {{- else }} + serviceName: {{ $fullName }}-web + servicePort: {{ .Values.service.ports.http }} + {{- end }} +{{- end }} diff --git a/defguardDocker/deployment/charts/defguard/templates/openid-secret.yaml b/defguardDocker/deployment/charts/defguard/templates/openid-secret.yaml new file mode 100644 index 0000000..ba67053 --- /dev/null +++ b/defguardDocker/deployment/charts/defguard/templates/openid-secret.yaml @@ -0,0 +1,16 @@ +{{ if not .Values.existingOpenIdSecret }} +{{- $openIdKey := (genPrivateKey "rsa") | b64enc | quote }} +{{- $secret := (lookup "v1" "Secret" .Release.Namespace (include "defguard.openidSecretName" .)) }} +{{- if $secret }} +{{- $openIdKey = index $secret.data "openid-key" }} +{{- end }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "defguard.openidSecretName" . }} + labels: + {{- include "defguard.labels" . | nindent 4 }} +type: Opaque +data: + openid-key: {{ $openIdKey }} +{{- end }} diff --git a/defguardDocker/deployment/charts/defguard/templates/postgresql-secret.yaml b/defguardDocker/deployment/charts/defguard/templates/postgresql-secret.yaml new file mode 100644 index 0000000..efdc2c4 --- /dev/null +++ b/defguardDocker/deployment/charts/defguard/templates/postgresql-secret.yaml @@ -0,0 +1,19 @@ +{{ if .Values.postgresql.enabled }} +{{- $password := (randAlpha 16) | b64enc | quote }} +{{- $postgresPassword := (randAlpha 16) | b64enc | quote }} +{{- $secret := (lookup "v1" "Secret" .Release.Namespace .Values.postgresql.auth.existingSecret) }} +{{- if $secret }} +{{- $password = index $secret.data "password" }} +{{- $postgresPassword = index $secret.data "postgres-password" }} +{{- end }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.postgresql.auth.existingSecret }} + labels: + {{- include "defguard.labels" . | nindent 4 }} +type: Opaque +data: + password: {{ $password }} + postgres-password: {{ $postgresPassword }} +{{- end }} diff --git a/defguardDocker/deployment/charts/defguard/templates/serviceaccount.yaml b/defguardDocker/deployment/charts/defguard/templates/serviceaccount.yaml new file mode 100644 index 0000000..1efc1cc --- /dev/null +++ b/defguardDocker/deployment/charts/defguard/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "defguard.serviceAccountName" . }} + labels: + {{- include "defguard.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/defguardDocker/deployment/charts/defguard/values.yaml b/defguardDocker/deployment/charts/defguard/values.yaml new file mode 100644 index 0000000..408019d --- /dev/null +++ b/defguardDocker/deployment/charts/defguard/values.yaml @@ -0,0 +1,75 @@ +affinity: {} +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 +cookie: + domain: "" + insecure: false +fullnameOverride: "" +image: + pullPolicy: IfNotPresent + repository: ghcr.io/defguard/defguard + tag: "" +imagePullSecrets: [] +ingress: + grpc: + annotations: {} + className: "" + enabled: true + host: defguard-grpc.local + tls: false + web: + annotations: {} + className: "" + enabled: true + host: defguard.local + tls: false +existingJwtSecret: "" +ldap: + admin_group: "" + bind_password: "" + bind_username: "" + enabled: false + group_search_base: "" + url: "" + user_search_base: "" +nameOverride: "" +nodeSelector: {} +existingOpenIdSecret: "" +podAnnotations: {} +podLabels: {} +podSecurityContext: {} +# sub-chart bitnami/postgresql +postgresql: + enabled: true + host: "" # set if using external postgresql ~ enabled: false + port: 5432 + auth: + database: defguard + existingSecret: postgres-password + existingSecretPasswordKey: "" # set if using external postgresql ~ enabled: false + username: defguard +proxyUrl: "" +publicUrl: "http://defguard.local" +replicaCount: 1 +resources: {} +securityContext: {} +service: + ports: + grpc: 50055 + http: 80 + type: ClusterIP +serviceAccount: + annotations: {} + create: true +tolerations: [] +# sub-chart defguard-proxy +defguard-proxy: + enabled: false + publicUrl: "http://enrollment.local" + ingress: + grpc: + host: defguard-proxy-grpc.local + web: + host: enrollment.local diff --git a/defguardDocker/deployment/docker-compose/.env.template b/defguardDocker/deployment/docker-compose/.env.template new file mode 100644 index 0000000..61c2e22 --- /dev/null +++ b/defguardDocker/deployment/docker-compose/.env.template @@ -0,0 +1,31 @@ +# The best way to define each secret is to generate random strings with e.g.: +# +# openssl rand -base64 48 #this will generate a 48chars random string +# +# Please provide secret strings (do not share them) for: +# +# Secret used for JWT cryptography +DEFGUARD_AUTH_SECRET= +# Secret used for JWT cryptography in YubiBridge GRPC communication +DEFGUARD_YUBIBRIDGE_SECRET= +# Secret used for JWT cryptography in gateway GRPC communication +DEFGUARD_GATEWAY_SECRET= +# Secret used for private cookies cryptography; must be at least 64 characters long +DEFGUARD_SECRET_KEY= +# Database password +DEFGUARD_DB_PASSWORD= +# Public URL of your Defguard instance +# E.g.: https://defguard.mycompany.com +DEFGUARD_URL= +# Webauthn RP ID (https://w3c.github.io/webauthn/#rp-id) +# E.g.: defguard.mycompany.com (without http/https) +DEFGUARD_WEBAUTHN_RP_ID= +# Public URL of your defguard proxy gRPC server +# DEFGUARD_PROXY_URL= +# Public URL of your enrollment service +# E.g.: https://enrollment.mycompany.com +# DEFGUARD_ENROLLMENT_URL= # [ENROLLMENT] +# Token used for VPN gateway authorization +# DEFGUARD_TOKEN= # [VPN] +# Enable insecure cookies when not using HTTPS +# DEFGUARD_COOKIE_INSECURE=true # [HTTP] diff --git a/defguardDocker/deployment/docker-compose/docker-compose.yaml b/defguardDocker/deployment/docker-compose/docker-compose.yaml new file mode 100644 index 0000000..6c8ee79 --- /dev/null +++ b/defguardDocker/deployment/docker-compose/docker-compose.yaml @@ -0,0 +1,110 @@ +services: + db: + image: postgres:15-alpine + restart: unless-stopped + environment: + POSTGRES_DB: defguard + POSTGRES_USER: defguard + POSTGRES_PASSWORD: ${DEFGUARD_DB_PASSWORD} + volumes: + - ${VOLUME_DIR:-./.volumes}/db:/var/lib/postgresql/data + # ports: + # - "5432:5432" + + caddy: # [PROXY] + image: caddy:2.7-alpine # [PROXY] + restart: unless-stopped # [PROXY] + volumes: # [PROXY] + - ${VOLUME_DIR:-./.volumes}/caddy/data:/data # [PROXY] + - ${VOLUME_DIR:-./.volumes}/caddy/config:/config # [PROXY] + - ${VOLUME_DIR:-./.volumes}/caddy/Caddyfile:/etc/caddy/Caddyfile # [PROXY] + ports: # [PROXY] + #http + - "8002:80" # [PROXY] + #https + - "6443:443" # [PROXY] + + core: + image: ghcr.io/defguard/defguard:${CORE_IMAGE_TAG:-latest} + restart: unless-stopped + environment: + DEFGUARD_AUTH_SECRET: ${DEFGUARD_AUTH_SECRET} + DEFGUARD_GATEWAY_SECRET: ${DEFGUARD_GATEWAY_SECRET} + DEFGUARD_YUBIBRIDGE_SECRET: ${DEFGUARD_YUBIBRIDGE_SECRET} + DEFGUARD_SECRET_KEY: ${DEFGUARD_SECRET_KEY} + DEFGUARD_DEFAULT_ADMIN_PASSWORD: ${DEFGUARD_DEFAULT_ADMIN_PASSWORD} + DEFGUARD_DB_HOST: db + DEFGUARD_DB_PORT: 5432 + DEFGUARD_DB_USER: defguard + DEFGUARD_DB_PASSWORD: ${DEFGUARD_DB_PASSWORD} + DEFGUARD_DB_NAME: defguard + DEFGUARD_URL: ${DEFGUARD_URL} + DEFGUARD_LOG_LEVEL: info + DEFGUARD_WEBAUTHN_RP_ID: ${DEFGUARD_WEBAUTHN_RP_ID} + DEFGUARD_COOKIE_INSECURE: ${DEFGUARD_COOKIE_INSECURE:-false} + DEFGUARD_ENROLLMENT_URL: ${DEFGUARD_ENROLLMENT_URL} # [ENROLLMENT] + DEFGUARD_PROXY_URL: https://proxy:50052 # [ENROLLMENT] + DEFGUARD_PROXY_GRPC_CA: /ssl/defguard-ca.pem # [ENROLLMENT] + DEFGUARD_GRPC_CERT: /ssl/defguard-grpc.crt + DEFGUARD_GRPC_KEY: /ssl/defguard-grpc.key + ## RSA setup guide: https://defguard.gitbook.io/defguard/community-features/setting-up-your-instance/docker-compose#openid-rsa-setup + DEFGUARD_OPENID_KEY: /keys/rsakey.pem + ## LDAP setup guide: https://defguard.gitbook.io/defguard/features/ldap-synchronization-setup + # DEFGUARD_LDAP_URL: ldap://localhost:389 # [LDAP] + # DEFGUARD_LDAP_BIND_USERNAME: cn=admin,dc=example,dc=org # [LDAP] + # DEFGUARD_LDAP_BIND_PASSWORD: password # [LDAP] + ports: + # web + - "9876:8000" + # grpc + - "50055:50055" + depends_on: + - db + volumes: + # SSL setup guide: https://defguard.gitbook.io/defguard/features/setting-up-your-instance/docker-compose#ssl-setup + - ${VOLUME_DIR:-./.volumes}/ssl:/ssl + ## RSA setup guide: https://defguard.gitbook.io/defguard/community-features/setting-up-your-instance/docker-compose#openid-rsa-setup + - ${VOLUME_DIR:-./.volumes}/core/rsakey.pem:/keys/rsakey.pem + proxy: # [ENROLLMENT] + image: ghcr.io/defguard/defguard-proxy:${PROXY_IMAGE_TAG:-latest} # [ENROLLMENT] + restart: unless-stopped # [ENROLLMENT] + environment: # [ENROLLMENT] + DEFGUARD_PROXY_GRPC_PORT: 50052 # [ENROLLMENT] + DEFGUARD_PROXY_GRPC_CERT: /ssl/defguard-proxy-grpc.crt # [ENROLLMENT] + DEFGUARD_PROXY_GRPC_KEY: /ssl/defguard-proxy-grpc.key # [ENROLLMENT] + volumes: # [ENROLLMENT] + #SSL setup guide: https://defguard.gitbook.io/defguard/features/setting-up-your-instance/docker-compose#ssl-setup + - ${VOLUME_DIR:-./.volumes}/ssl:/ssl # [ENROLLMENT] + ports: + # web + - "8588:8080" + depends_on: # [ENROLLMENT] + - core # [ENROLLMENT] + + gateway: # [VPN] + image: ghcr.io/defguard/gateway:${GATEWAY_IMAGE_TAG:-latest} # [VPN] + restart: unless-stopped # [VPN] + network_mode: "host" # [VPN] + environment: # [VPN] + DEFGUARD_GRPC_URL: https://localhost:50055 # [VPN] + DEFGUARD_GRPC_CA: /ssl/defguard-ca.pem # [VPN] + DEFGUARD_STATS_PERIOD: 30 # [VPN] + DEFGUARD_TOKEN: ${DEFGUARD_TOKEN} # [VPN] + volumes: # [VPN] + - ${VOLUME_DIR:-./.volumes}/ssl:/ssl # [VPN] + cap_add: # [VPN] + - NET_ADMIN # [VPN] + + gateway-OpenVPN: # [VPN] + image: ghcr.io/defguard/gateway:${GATEWAY_IMAGE_TAG:-latest} # [VPN] + restart: unless-stopped # [VPN] + network_mode: "host" # [VPN] + environment: # [VPN] + DEFGUARD_GRPC_URL: https://localhost:50055 # [VPN] + DEFGUARD_GRPC_CA: /ssl/defguard-ca.pem # [VPN] + DEFGUARD_STATS_PERIOD: 30 # [VPN] + DEFGUARD_TOKEN: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJEZWZHdWFyZCIsInN1YiI6IkRFRkdVQVJELU5FVFdPUkstNSIsImNsaWVudF9pZCI6IjUiLCJleHAiOjYwMTkyNjE4NDMsIm5iZiI6MTcyNDI5NDU0OH0.cV9dgj0B7hjT7LaLqbZ0sp8u-Fl71X13mnDppFXaD4E + volumes: # [VPN] + - ${VOLUME_DIR:-./.volumes}/ssl:/ssl # [VPN] + cap_add: # [VPN] + - NET_ADMIN # [VPN] diff --git a/defguardDocker/deployment/docker-compose/setup.log.rGByG7 b/defguardDocker/deployment/docker-compose/setup.log.rGByG7 new file mode 100644 index 0000000..e69de29 diff --git a/defguardDocker/deployment/docker-compose/setup.sh b/defguardDocker/deployment/docker-compose/setup.sh new file mode 100755 index 0000000..e99814b --- /dev/null +++ b/defguardDocker/deployment/docker-compose/setup.sh @@ -0,0 +1,890 @@ +#!/usr/bin/env bash +# shellcheck shell=bash + +# This is a script that sets up an entire defguard instance (including core, +# gateway, enrollment proxy and reverse proxy). It's goal is to prepare +# a working instance by running a single command. + +set -o errexit # abort on nonzero exitstatus +set -o pipefail # don't hide errors within pipes + +# Global variables +VERSION="1.0.2" +SECRET_LENGTH=64 +PASSWORD_LENGTH=16 + +VOLUME_DIR=".volumes" +SSL_DIR="${VOLUME_DIR}/ssl" +RSA_DIR="${VOLUME_DIR}/core" + +COMPOSE_FILE="docker-compose.yaml" +ENV_FILE=".env" +LOG_FILE=$(mktemp setup.log.XXXXXX) + +BASE_COMPOSE_FILE_URL="https://raw.githubusercontent.com/DefGuard/deployment/main/docker-compose/docker-compose.yaml" +BASE_ENV_FILE_URL="https://raw.githubusercontent.com/DefGuard/deployment/main/docker-compose/.env.template" + +CORE_IMAGE_TAG="${CORE_IMAGE_TAG:-latest}" +GATEWAY_IMAGE_TAG="${GATEWAY_IMAGE_TAG:-latest}" +PROXY_IMAGE_TAG="${PROXY_IMAGE_TAG:-latest}" + + +##################### +### MAIN FUNCTION ### +##################### + +main() { + is_utf_term + is_term_color + tput reset + print_header + + # display help `--help` argument is found + for i in $*; do + test "$i" == "--help" && print_usage && exit 0 + + # run non interactive + if [[ "$i" == "--non-interactive" ]]; then + CFG_NON_INTERACTIVE=1 + # we need to remove this element from $* or getopt will return an error + set -- $(remove_element "$i" $*) + fi + + # configure https + if [[ "$i" == "--use-https" ]]; then + CFG_USE_HTTPS=1 + # we need to remove this element from $* or getopt will return an error + set -- $(remove_element "$i" $*) + fi + done + + # + # First let's gather the ENV/command line variables + # + + # load configuration from env variables + load_configuration_from_env + + # load configuration from CLI options + load_configuration_from_cli "$@" + + # load configuration from user inputs + if [ X$CFG_VOLUME_DIR != X ]; then + VOLUME_DIR=${CFG_VOLUME_DIR} + SSL_DIR="${VOLUME_DIR}/ssl" + RSA_DIR="${VOLUME_DIR}/core" + fi + + export VOLUME_DIR + + # We have enough to check the enviromnent + # so check if necessary tools are available + check_environment + + # load configuration from user inputs + if ! [ $CFG_NON_INTERACTIVE ]; then + load_configuration_from_input + fi + + # check that all required configuration options are set + validate_required_variables + + # generate external service URLs based on config + generate_external_urls + + # print out config + print_config + + # set current working directory + WORK_DIR_PATH=$(pwd) + + # setup RSA & SSL keys + setup_keys + + # generate caddyfile + create_caddyfile + + # generate `.env` file + generate_env_file + + # enable insecure cookies if not using HTTPS + if ! [ "$CFG_USE_HTTPS" ]; then + uncomment_feature "HTTP" "${PROD_ENV_FILE}" + fi + + # generate base docker-compose file + PROD_COMPOSE_FILE="${WORK_DIR_PATH}/${COMPOSE_FILE}" + if [ -f "$PROD_COMPOSE_FILE" ]; then + echo -n " ${TXT_BEGIN} Using existing docker-compose file at ${PROD_COMPOSE_FILE}... " + print_confirmation + else + fetch_base_compose_file + fi + + # enable reverse proxy in compose file + uncomment_feature "PROXY" "${PROD_COMPOSE_FILE}" + + # enable enrollment service in compose file + if [ "$CFG_ENABLE_ENROLLMENT" ]; then + enable_enrollment + fi + + # fetch latest images + echo " ${TXT_BEGIN} Fetching latest Docker images: " + $COMPOSE_CMD -f "${PROD_COMPOSE_FILE}" --env-file "${PROD_ENV_FILE}" pull + + # enable and setup VPN gateway + if [ "$CFG_ENABLE_VPN" ]; then + enable_vpn_gateway + fi + + # start docker-compose stack + echo " ${TXT_BEGIN} Starting docker-compose stack" + $COMPOSE_CMD -f "${PROD_COMPOSE_FILE}" --env-file "${PROD_ENV_FILE}" up -d + if [ $? -ne 0 ]; then + echo >&2 "ERROR: failed to start docker-compose stack" + exit 1 + fi + + print_instance_summary +} + +######################## +### HELPER FUNCTIONS ### +######################## + +check_character_support() { + local char="$1" + echo -e "$char" | grep -q "$char" +} + +is_utf_term() { + if check_character_support "√"; then + TXT_CHECK="✓" + TXT_BEGIN="▶" + TXT_SUB="▷" + TXT_STAR="★" + TXT_X="✗" + TXT_INPUT="✍" + else + TXT_CHECK="+" + TXT_BEGIN=">>" + TXT_SUB=">" + TXT_STAR="*" + TXT_X="x" + TXT_INPUT=" ::" + fi +} + +is_term_color() { + + if [[ $TERM == *"256"* ]]; then + C_RED="\033[31m" + C_GREEN="\033[32m" + C_YELLOW="\033[33m" + C_BLUE="\033[34m" + C_WHITE="\033[37m" + C_GREY="\033[90m" + + C_LRED="\033[91m" + C_LGREEN="\033[92m" + C_LYELLOW="\033[93m" + C_LBLUE="\033[94m" + + C_BOLD="\033[1m" + C_ITALICS="\033[3m" + C_BG_GREY="\033[100m" + C_END="\033[0m" + else + C_RED="" + C_GREEN="" + C_YELLOW="" + C_BLUE="" + C_WHITE="" + C_GREY="" + + C_LRED="" + C_LGREEN="" + C_LYELLOW="" + C_LBLUE="" + + C_BOLD="" + C_ITALICS="" + C_BG_GREY="" + C_END="" + fi +} + +# remove array element +remove_element() { + local remove=$1 + local result=() + for element in "$@"; do + if [[ "$element" != "$remove" ]]; then + result+=("$element") + fi + done + echo "${result[@]}" +} + +# Function to convert relative path to absolute path +to_absolute_path() { + local path="$1" + if [[ "${path:0:1}" != "/" ]]; then + path="$(cd "$(dirname "$path")" && pwd)/$(basename "$path")" + fi + echo ${path} +} + +print_header() { + echo -e "${C_LBLUE}" + cat << _EOF_ + # + ## # + ## ## # # ## # + ## ## # # # # + # ## # #### # #### ##### #### # # #### ### #### # + # ## ## # ## # ## # # # # # # # # # ## + ## ## # # ######## # # # # # # # # # + # ## ## # # # ## # ##### # # ###### # # # + # ## # # ## # # # # # # # # # # ## + ## ## #### # ##### # ####### #### # #### # # #### # + ## ## # # # + ## # ####### + # +_EOF_ + echo -e "${C_END}" + echo + echo "defguard docker-compose deployment setup script v${VERSION}" + echo -e "Copyright (C) 2023-2024 ${C_BOLD}teonite${C_END} <${C_BG_GREY}${C_YELLOW}https://teonite.com${C_END}>" + echo +} + +print_confirmation() { + echo -e " ${C_LGREEN}${TXT_CHECK}${C_END} " +} + +print_usage() { + + echo "Usage: ${BASENAME} [options]" + echo + echo 'Available options:' + echo + echo -e "\t--help this help message" + echo -e "\t--non-interactive run in non-interactive mode - !REQUIRES SETTING all options/env vars" + echo -e "\t--domain domain where defguard web UI will be available" + echo -e "\t--enrollment-domain domain where enrollment service will be available" + echo -e "\t--use-https configure reverse proxy to use HTTPS" + echo -e "\t--volume Docker volumes directory - default: ${VOLUME_DIR}" + echo -e "\t--vpn-name VPN location name" + echo -e "\t--vpn-ip

VPN server address & netmask (e.g. 10.0.50.1/24)" + echo -e "\t--vpn-gateway-ip VPN gateway external IP (! NOT DOMAIN - IP)" + echo -e "\t--vpn-gateway-port VPN gateway external port (your clients connect here)" + echo +} + +command_exists() { + local command="$1" + command -v "$command" >/dev/null 2>&1 +} + +command_exists_check() { + local command="$1" + if ! command_exists "$command"; then + echo >&2 "ERROR: $command command not found" + echo >&2 "ERROR: dependency failed, exiting..." + exit 2 + fi +} + +check_environment() { + echo -n " ${TXT_BEGIN} Checking if all required tools are available..." + # compose can be provided by newer docker versions or a separate docker-compose + docker compose version >/dev/null 2>&1 + if [ $? = 0 ]; then + COMPOSE_CMD="docker compose" + else + if command_exists docker-compose; then + COMPOSE_CMD="docker-compose" + else + echo + echo >&2 "ERROR: docker-compose or docker compose command not found" + echo >&2 "ERROR: dependency failed, exiting..." + exit 3 + fi + fi + + command_exists_check openssl + command_exists_check curl + command_exists_check grep + + # Check if the volume dir is an absolute path since docker requires it + VOLUME_DIR=$(to_absolute_path "${VOLUME_DIR}") + + if [ -d ${VOLUME_DIR} ]; then + echo + echo >&2 "ERROR: volume directory: ${VOLUME_DIR} exists." + echo >&2 "ERROR: this means, I would overwrite the configuration, database and certificates." + echo >&2 "ERROR: please backup or remove the volume directory." + exit 3 + fi + + # create all necessary directories + for dir in ${VOLUME_DIR} ${SSL_DIR} ${RSA_DIR}; do + mkdir ${dir} + if [ $? -ne 0 ]; then + echo >&2 "ERROR: cloud not create volume directory: ${dir}" + exit 3 + fi + done + + print_confirmation +} + +load_configuration_from_env() { + echo -n " ${TXT_BEGIN} Loading configuration from environment variables... " + # required variables + CFG_DOMAIN="$DEFGUARD_DOMAIN" + + # optional variables + CFG_VOLUME_DIR="$DEFGUARD_VOLUME_DIR" + CFG_VPN_NAME="$DEFGUARD_VPN_NAME" + CFG_VPN_IP="$DEFGUARD_VPN_IP" + CFG_VPN_GATEWAY_IP="$DEFGUARD_VPN_GATEWAY_IP" + CFG_VPN_GATEWAY_PORT="$DEFGUARD_VPN_GATEWAY_PORT" + CFG_ENROLLMENT_DOMAIN="$DEFGUARD_ENROLLMENT_DOMAIN" + if ! [ $CFG_USE_HTTPS ]; then + CFG_USE_HTTPS="$DEFGUARD_USE_HTTPS" + fi + + print_confirmation +} + +load_configuration_from_cli() { + echo -n " ${TXT_BEGIN} Loading configuration from CLI arguments... " + + ARGUMENT_LIST=( + "domain" + "enrollment-domain" + "volume" + "vpn-name" + "vpn-ip" + "vpn-gateway-ip" + "vpn-gateway-port" + ) + + # read arguments + opts=$( + getopt \ + --longoptions "$(printf "%s:," "${ARGUMENT_LIST[@]}")" \ + --name "$(basename "$0")" \ + --options "" \ + -- "$@" + ) + + eval set --$opts + + while [[ $# -gt 0 ]]; do + case "$1" in + --domain) + CFG_DOMAIN=$2 + shift 2 + ;; + + --enrollment-domain) + CFG_ENROLLMENT_DOMAIN=$2 + shift 2 + ;; + + --volume) + CFG_VOLUME_DIR=$2 + shift 2 + ;; + + --vpn-name) + CFG_VPN_NAME=$2 + shift 2 + ;; + + --vpn-ip) + CFG_VPN_IP=$2 + shift 2 + ;; + + --vpn-gateway-ip) + CFG_VPN_GATEWAY_IP=$2 + shift 2 + ;; + + --vpn-gateway-port) + CFG_VPN_GATEWAY_PORT=$2 + shift 2 + ;; + + *) + break + ;; + esac + done + + print_confirmation +} + +load_configuration_from_input() { + echo -ne "${C_ITALICS}${C_LBLUE}" + cat << _EOF_ + +Please provide the values to configure your defguard instance. If you've +already configured some options by setting environment variables or through +CLI options, those will be used as defaults. + +If you prefer to disable this user input section, please restart the script +with --non-interactive CLI flag. + +_EOF_ + +echo -ne "${C_GREY}" +cat << _EOF_ + +Choose domains that will be used to expose your instance through Caddy +reverse proxy. defguard uses a separate domain for the Web UI, and for +the optional enrollment/desktop client configuration/password reset +service. + +If you don't provide any domain for the enrollment service, the service +itself will not be deployed. + +You can also enable HTTPS here (highly recommended), which will configure +Caddy to automatically provision SSL certificates. +_EOF_ + +echo -ne "${C_BOLD}" +cat << _EOF_ + +Please note that this requires your server to have a public IP address +and public DNS records for your chosen domains to be configured +correctly (pointing to your server's IP address). + +_EOF_ + + echo -ne "${C_END}" + + echo -e " ${C_BOLD}${C_GREEN}${TXT_STAR} General config ${TXT_STAR}${C_END}\n" + + while [ X${domain} = "X" ]; do + echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} " + read -p "Enter defguard domain [default: ${CFG_DOMAIN}]: " domain + if [ "$domain" ]; then + CFG_DOMAIN="$domain" + fi + done + + echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} " + read -p "Enter enrollment domain [default: ${CFG_ENROLLMENT_DOMAIN}]: " enroll + if [ "$enroll" ]; then + CFG_ENROLLMENT_DOMAIN="$enroll" + fi + + use_https_bool_value="false" + if [ $CFG_USE_HTTPS ]; then use_https_bool_value="true"; fi + echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} " + read -p "Use HTTPS [default: ${use_https_bool_value}]: " https + if [ "$https" ]; then + CFG_USE_HTTPS=1 + fi + + echo + echo -e " ${C_BOLD}${C_GREEN}${TXT_STAR} WireGuard VPN${TXT_STAR}${C_END}\n" + + echo -ne "${C_ITALICS}${C_GREY}" + cat << _EOF_ + +If you wish to configure and deploy WireGuard VPN gateway, please +provide your VPN location name. To skip, just press enter and VPN will +not be configured. +_EOF_ + + echo -ne "${C_END}\n" + + echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} " + read -p "Enter VPN location name [default: ${CFG_VPN_NAME}]: " vpn_name + if [ "$vpn_name" ]; then + CFG_VPN_NAME="$vpn_name" + fi + + if [ "$CFG_VPN_NAME" ]; then + while [ X${vpn_ip} = "X" ]; do + echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} " + read -p "Enter VPN server address and subnet (e.g. 10.0.60.1/24) [default: ${CFG_VPN_IP}]: " vpn_ip + if [ "$vpn_ip" ]; then + CFG_VPN_IP="$vpn_ip" + fi + done + + echo -ne "${C_ITALICS}${C_GREY}" + cat << _EOF_ + +Now we'll configure a public endpoint (IP + port) that your WireGuard +client devices will use to safely connect to your gateway from the +public internet. + +Since we'll be starting the gateway on this server the IP address should +be the same as your server's public IP address. +_EOF_ + echo -ne "${C_BOLD}" + cat << _EOF_ +Please also remember that your firewall should be configured +to allow incoming UDP traffic on the chosen WireGuard port. +_EOF_ + + echo -ne "${C_END}" + + while [ X${public_ip} = "X" ]; do + echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} " + read -p "Enter VPN gateway public IP (no domains!) [default: ${CFG_VPN_GATEWAY_IP}]: " public_ip + if [ "$public_ip" ]; then + CFG_VPN_GATEWAY_IP="$public_ip" + fi + done + + while [ X${public_port} = "X" ]; do + echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} " + read -p "Enter VPN gateway public port [default: ${CFG_VPN_GATEWAY_PORT}]: " public_port + if [ "$public_port" ]; then + CFG_VPN_GATEWAY_PORT="$public_port" + fi + done + + else + echo -e " ${C_BOLD}${C_RED}${TXT_X} ${C_GREY} WireGuard VPN skipped${C_END}\n" + fi + + echo + echo -e "${C_BOLD}${C_GREEN}Thank you. We'll now proceed with the deployment using provided values.${C_END}" +} + +check_required_variable() { + local var_name="$1" + if [ -z "${!var_name}" ]; then + echo >&2 "ERROR: ${var_name} configuration option not set" + exit 4 + fi +} + +validate_required_variables() { + echo -n " ${TXT_BEGIN} Validating configuration options..." + check_required_variable "CFG_DOMAIN" + + # if VPN name is given validate other VPN configurations are present + if [ "$CFG_VPN_NAME" ]; then + CFG_ENABLE_VPN=1 + check_required_variable "CFG_VPN_IP" + check_required_variable "CFG_VPN_GATEWAY_IP" + check_required_variable "CFG_VPN_GATEWAY_PORT" + fi + + print_confirmation +} + +generate_external_urls() { + # prepare full defguard URL + if [ $CFG_USE_HTTPS ]; then + CFG_DEFGUARD_URL="https://${CFG_DOMAIN}" + else + CFG_DEFGUARD_URL="http://${CFG_DOMAIN}" + fi + + # prepare full enrollment URL + if [ "$CFG_ENROLLMENT_DOMAIN" ]; then + CFG_ENABLE_ENROLLMENT=1 + if [ "$CFG_USE_HTTPS" ]; then + CFG_ENROLLMENT_URL="https://${CFG_ENROLLMENT_DOMAIN}" + else + CFG_ENROLLMENT_URL="http://${CFG_ENROLLMENT_DOMAIN}" + fi + fi +} + +print_config() { + echo + echo " ${TXT_BEGIN} Setting up your defguard instance with following config:" + echo + echo -e " ${TXT_SUB} data volume: ${C_BOLD}${VOLUME_DIR}${C_END}" + echo + echo -e " ${TXT_SUB} domain: ${C_BOLD}${CFG_DOMAIN}${C_END}" + echo -e " ${TXT_SUB} web UI URL: ${C_BOLD}${CFG_DEFGUARD_URL}${C_END}" + + if [ "$CFG_VPN_NAME" ]; then + echo -e " ${TXT_SUB} VPN location name: ${C_BOLD}${CFG_VPN_NAME}${C_END}" + echo -e " ${TXT_SUB} VPN address: ${C_BOLD}${CFG_VPN_IP}${C_END}" + echo -e " ${TXT_SUB} VPN gateway IP: ${C_BOLD}${CFG_VPN_GATEWAY_IP}${C_END}" + echo -e " ${TXT_SUB} VPN gateway port: ${C_BOLD}${CFG_VPN_GATEWAY_PORT}${C_END}" + fi + + if [ "$CFG_ENROLLMENT_DOMAIN" ]; then + echo -e " ${TXT_SUB} Enrollment service domain: ${C_BOLD}${CFG_ENROLLMENT_DOMAIN}${C_END}" + echo -e " ${TXT_SUB} Enrollment service URL: ${C_BOLD}${CFG_ENROLLMENT_URL}${C_END}" + fi + echo + echo -e " ${TXT_BEGIN} All executed command's results are in log file: ${C_BOLD}${LOG_FILE}${C_END}" + echo +} + +setup_keys() { + echo " ${TXT_BEGIN} Setting up SSL certificates and RSA keys..." + if [ -d ${SSL_DIR} -a "$(ls -A ${SSL_DIR})" ]; then + echo " ${TXT_SUB} Using existing SSL certificates from ${SSL_DIR}" + else + generate_certs + fi + + if [ -d ${RSA_DIR} -a "$(ls -A ${RSA_DIR})" ]; then + echo " ${TXT_SUB} Using existing RSA keys from ${RSA_DIR}." + else + generate_rsa + fi +} + +generate_certs() { + echo " ${TXT_BEGIN} Creating new SSL certificates in ${SSL_DIR}..." + mkdir -p ${SSL_DIR} + + PASSPHRASE=$(generate_secret) + + echo "PEM passphrase for SSL certificates set to '${PASSPHRASE}'." + + # generate private key for CA + openssl genrsa -des3 -out ${SSL_DIR}/defguard-ca.key -passout pass:"${PASSPHRASE}" 2048 2>&1 >> ${LOG_FILE} + # generate Root Certificate + # TODO: allow configuring CA parameters + openssl req -x509 -new -nodes -key ${SSL_DIR}/defguard-ca.key -sha256 -days 1825 -out ${SSL_DIR}/defguard-ca.pem -passin pass:"${PASSPHRASE}" -subj "/C=PL/ST=Zachodniopomorskie/L=Szczecin/O=Example/OU=IT Department/CN=${CFG_DOMAIN}" 2>&1 >> ${LOG_FILE} + + # generate CA-signed certificate for defguard gRPC + openssl genrsa -out ${SSL_DIR}/defguard-grpc.key 2048 2>&1 >> ${LOG_FILE} + + openssl req -new -key ${SSL_DIR}/defguard-grpc.key -out ${SSL_DIR}/defguard-grpc.csr -subj "/C=PL/ST=Zachodniopomorskie/L=Szczecin/O=Example/OU=IT Department/CN=${CFG_DOMAIN}" 2>&1 >> ${LOG_FILE} + cat >${SSL_DIR}/defguard-grpc.ext <&1 >> ${LOG_FILE} + + # generate CA-signed certificate for defguard proxy gRPC + openssl genrsa -out ${SSL_DIR}/defguard-proxy-grpc.key 2048 2>&1 >> ${LOG_FILE} + + openssl req -new -key ${SSL_DIR}/defguard-proxy-grpc.key -out ${SSL_DIR}/defguard-proxy-grpc.csr -subj "/C=PL/ST=Zachodniopomorskie/L=Szczecin/O=Example/OU=IT Department/CN=${CFG_DOMAIN}" 2>&1 >> ${LOG_FILE} + cat >${SSL_DIR}/defguard-proxy-grpc.ext <&1 >> ${LOG_FILE} +} + +generate_rsa() { + echo "Generating RSA keys in ${RSA_DIR}..." + mkdir -p ${RSA_DIR} + openssl genpkey -out ${RSA_DIR}/rsakey.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048 2>&1 >> ${LOG_FILE} + +} + +generate_secret() { + generate_secret_inner "${SECRET_LENGTH}" +} + +generate_password() { + generate_secret_inner "${PASSWORD_LENGTH}" +} + +generate_secret_inner() { + local length="$1" + openssl rand -base64 ${length} | tr -d "=+/" | tr -d '\n' | cut -c1-${length-1} +} + +create_caddyfile() { + caddy_volume_path="${VOLUME_DIR}/caddy" + caddyfile_path="${caddy_volume_path}/Caddyfile" + mkdir -p ${caddy_volume_path} + + cat >${caddyfile_path} <>${caddyfile_path} <>${caddyfile_path} <&1 >> ${LOG_FILE} + + print_confirmation +} + +generate_env_file() { + PROD_ENV_FILE="${WORK_DIR_PATH}/${ENV_FILE}" + fetch_base_env_file + update_env_file + + print_confirmation +} + +fetch_base_env_file() { + echo -e " ${TXT_BEGIN} Fetching base ${ENV_FILE} file for compose stack..." + + curl --proto '=https' --tlsv1.2 -sSf "${BASE_ENV_FILE_URL}" -o "${PROD_ENV_FILE}" 2>&1 >> ${LOG_FILE} + print_confirmation +} + +update_env_file() { + echo -n " ${TXT_BEGIN} Setting environment variables in ${ENV_FILE} file for compose stack..." + + # set image versions + set_env_file_value "CORE_IMAGE_TAG" "${CORE_IMAGE_TAG}" + set_env_file_value "PROXY_IMAGE_TAG" "${PROXY_IMAGE_TAG}" + set_env_file_value "GATEWAY_IMAGE_TAG" "${GATEWAY_IMAGE_TAG}" + + # fill in values + set_env_file_secret "DEFGUARD_AUTH_SECRET" + set_env_file_secret "DEFGUARD_YUBIBRIDGE_SECRET" + set_env_file_secret "DEFGUARD_GATEWAY_SECRET" + set_env_file_secret "DEFGUARD_SECRET_KEY" + + # use existing password if set in env variable + if [ "$DEFGUARD_DB_PASSWORD" ]; then + set_env_file_value "DEFGUARD_DB_PASSWORD" "${DEFGUARD_DB_PASSWORD}" + else + set_env_file_password "DEFGUARD_DB_PASSWORD" + fi + + DEFGUARD_DEFAULT_ADMIN_PASSWORD="$(generate_password)" + set_env_file_value "DEFGUARD_DEFAULT_ADMIN_PASSWORD" "${DEFGUARD_DEFAULT_ADMIN_PASSWORD}" + + set_env_file_value "DEFGUARD_URL" "${CFG_DEFGUARD_URL}" + set_env_file_value "DEFGUARD_WEBAUTHN_RP_ID" "${CFG_DOMAIN}" + print_confirmation +} + +set_env_file_value() { + # make sure variable exists in file + grep -qF "${1}=" "${PROD_ENV_FILE}" || echo "${1}=" >>"${PROD_ENV_FILE}" + sed -i "s@\(${1}\)=.*@\1=${2}@" "${PROD_ENV_FILE}" +} + +set_env_file_secret() { + set_env_file_value "${1}" "$(generate_secret)" "${PROD_ENV_FILE}" +} + +set_env_file_password() { + set_env_file_value "${1}" "$(generate_password)" "${PROD_ENV_FILE}" +} + +uncomment_feature() { + sed -i "s@# \(.*\) # \[${1}\]@\1@" "${2}" +} + +enable_enrollment() { + echo -n " ${TXT_BEGIN} Enabling enrollment proxy service in compose file..." + + # update .env file + uncomment_feature "ENROLLMENT" "${PROD_ENV_FILE}" + set_env_file_value "DEFGUARD_ENROLLMENT_URL" "${CFG_ENROLLMENT_URL}" + + # update compose file + uncomment_feature "ENROLLMENT" "${PROD_COMPOSE_FILE}" + + print_confirmation +} + +enable_vpn_gateway() { + echo " ${TXT_BEGIN} Enabling VPN gateway service..." + + uncomment_feature "VPN" "${PROD_COMPOSE_FILE}" + uncomment_feature "VPN" "${PROD_ENV_FILE}" + + # fetch latest image + echo " ${TXT_SUB} Fetching latest gateway image..." + $COMPOSE_CMD -f "${PROD_COMPOSE_FILE}" --env-file "${PROD_ENV_FILE}" pull gateway + + # create VPN location + echo " ${TXT_BEGIN} Adding VPN to core & generating gateway token..." + VPN_NETWORK=`echo ${CFG_VPN_IP} | awk -F'[./]' '{print $1"."$2"."$3".0/"$5}'` + 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) + if [ $? -ne 0 ]; then + echo >&2 "ERROR: failed to create VPN network" + exit 1 + fi + + # add gateway token to .env file + set_env_file_value "DEFGUARD_TOKEN" "${token}" +} + +print_instance_summary() { + echo + echo -e "${C_LGREEN} ${TXT_CHECK} defguard setup finished successfully${C_END}" + echo + echo "If your DNS configuration is correct your defguard instance should be available at:" + echo + echo -e "\t${TXT_SUB} Web UI: ${C_BOLD}${CFG_DEFGUARD_URL}${C_END}" + if [ "$CFG_ENABLE_ENROLLMENT" ]; then + echo -e "\t${TXT_SUB} Enrollment service: ${C_BOLD}${CFG_ENROLLMENT_URL}${C_END}" + fi + echo + echo -e " ${TXT_BEGIN} You can log into the UI using the default admin user:" + echo + echo -e "\t${TXT_SUB} username: ${C_BOLD}admin${C_END}" + echo -e "\t${TXT_SUB} password: ${C_BOLD}${DEFGUARD_DEFAULT_ADMIN_PASSWORD}${C_END}" + echo + if [ "$CFG_ENABLE_VPN" ]; then + echo -e "\t\tVPN server public endpoint is ${C_BOLD}${CFG_VPN_GATEWAY_IP}:${CFG_VPN_GATEWAY_PORT}${C_END}" + echo -e "\t\tVPN network is ${C_BOLD}${VPN_NETWORK}${C_END}" + echo -e "\t\t! Make sure your firewall allows external UDP traffic to port ${C_BOLD}${CFG_VPN_GATEWAY_PORT}${C_END} !" + echo + echo -e "\t\tTo test if the VPN is working: ping ${CFG_VPN_IP} (after connecting to VPN)" + fi + echo + echo -e "Files used to deploy your instance are stored in:" + echo -e "\t docker compose file: ${C_BOLD}${PROD_COMPOSE_FILE}${C_END}" + echo -e "\t docker compose environment: ${C_BOLD}${PROD_ENV_FILE}${C_END}" + echo + echo -e "Persistent data (docker volumes) is stored in ${C_BOLD}${VOLUME_DIR}${C_END}" + echo + echo -e " ${C_YELLOW}${TXT_STAR} To support our work, please star us on GitHub! ${TXT_STAR}${C_END}" + echo -e " ${C_YELLOW}${TXT_STAR} https://github.com/defguard/defguard ${TXT_STAR}${C_END}" + echo +} + +# run main function +main "$@" || exit 1 diff --git a/defguardDocker/deployment/docs/header.png b/defguardDocker/deployment/docs/header.png new file mode 100644 index 0000000000000000000000000000000000000000..3a02a4d5826bd813edede36f83bad4e80d2f6ed7 GIT binary patch literal 24820 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 +# Token from Defguard app to secure gRPC connection, available on network page. +DEFGUARD_TOKEN= +# Defines how often (in seconds) should interface statistics be sent to Defguard server +DEFGUARD_STATS_PERIOD=30 diff --git a/defguardDocker/deployment/gateway/docker-compose.yaml b/defguardDocker/deployment/gateway/docker-compose.yaml new file mode 100644 index 0000000..807add2 --- /dev/null +++ b/defguardDocker/deployment/gateway/docker-compose.yaml @@ -0,0 +1,22 @@ +version: "3" +services: + gateway: + image: ghcr.io/defguard/gateway:latest + restart: unless-stopped + network_mode: "host" + environment: + # load variables from .env file + - DEFGUARD_GRPC_URL + - DEFGUARD_TOKEN + - DEFGUARD_STATS_PERIOD + - RUST_LOG=debug + # SSL setup guide: https://defguard.gitbook.io/defguard/features/setting-up-your-instance/docker-compose#ssl-setup + # - DEFGUARD_GRPC_CA: /ssl/defguard-ca.pem + ports: + # wireguard endpoint + - "50051:50051/udp" + #volumes: + # SSL setup guide: https://defguard.gitbook.io/defguard/features/setting-up-your-instance/docker-compose#ssl-setup + #- ./.volumes/ssl:/ssl + cap_add: + - NET_ADMIN