From 6ffe1a90aeef8edc22a9bbc7a2f3672e9439bc07 Mon Sep 17 00:00:00 2001 From: Dmytro Bondar Date: Sun, 22 Sep 2024 13:25:08 +0200 Subject: [PATCH] feat: TLS support for web (#301) * Added TLS support for web - Added optional configurations `cert_file` and `key_file` to run web server with https Signed-off-by: Dmytro Bondar * Helm chart update - Refactored Ingress to use one host only (`config.web.external_url` is required) - Added Certificate resource template (secret is mounted to container into `/app/certs/`) - Added support for service with mixed protocols (exposes UI and Wireguard ports on same IP) - Added helm-docs target to makefile - Changed pod labels to use selectorLabels - Removed default probes (app runs without healthy web) - Removed sections from README Signed-off-by: Dmytro Bondar * Fix chart workflow path filter * Fix chart lint issue * Skip clean-up tested chart * Try k3d cluster --------- Signed-off-by: Dmytro Bondar --- .github/workflows/chart.yml | 24 +-- Makefile | 7 +- README.md | 3 +- deploy/helm/Chart.yaml | 4 +- deploy/helm/README.md | 132 +++++++--------- deploy/helm/templates/_helpers.tpl | 9 ++ deploy/helm/templates/_pod.tpl | 13 +- deploy/helm/templates/_service.tpl | 53 +++++++ deploy/helm/templates/certificate.yaml | 54 +++++++ deploy/helm/templates/ingress.yaml | 33 +--- deploy/helm/templates/secret.yaml | 4 - deploy/helm/templates/service-web.yaml | 16 -- deploy/helm/templates/service-wireguard.yaml | 27 ---- deploy/helm/templates/service.yaml | 14 ++ deploy/helm/values.yaml | 157 ++++++------------- internal/app/api/core/server.go | 11 +- internal/config/web.go | 2 + 17 files changed, 289 insertions(+), 274 deletions(-) create mode 100644 deploy/helm/templates/_service.tpl create mode 100644 deploy/helm/templates/certificate.yaml delete mode 100644 deploy/helm/templates/service-web.yaml delete mode 100644 deploy/helm/templates/service-wireguard.yaml create mode 100644 deploy/helm/templates/service.yaml diff --git a/.github/workflows/chart.yml b/.github/workflows/chart.yml index 946f71a..11bd74c 100644 --- a/.github/workflows/chart.yml +++ b/.github/workflows/chart.yml @@ -10,10 +10,10 @@ name: Chart on: pull_request: branches: [master] - paths: [deploy/helm] + paths: ['deploy/helm/**'] push: branches: [master] - paths: [deploy/helm] + paths: ['deploy/helm/**'] jobs: lint-test: @@ -24,6 +24,14 @@ jobs: with: fetch-depth: 0 + - name: Check docs + run: | + make helm-docs + if ! git diff --exit-code; then + echo "error::Documentation is not up to date. Please run helm-docs and commit changes." + exit 1 + fi + # ct lint requires Python 3.x to run following packages: # - yamale (https://github.com/23andMe/Yamale) # - yamllint (https://github.com/adrienverge/yamllint) @@ -36,15 +44,9 @@ jobs: - name: Run chart-testing (lint) run: ct lint --config ct.yaml - - name: Check docs - run: | - docker run --rm --volume "${PWD}/deploy:/helm-docs" -u "$(id -u)" jnorwood/helm-docs - if ! git diff --exit-code; then - echo "error::Documentation is not up to date. Please run helm-docs and commit changes." - exit 1 - fi - - - uses: helm/kind-action@v1 + - uses: nolar/setup-k3d-k3s@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} - name: Run chart-testing (install) run: ct install --config ct.yaml diff --git a/Makefile b/Makefile index 7334b32..0b72e16 100644 --- a/Makefile +++ b/Makefile @@ -127,4 +127,9 @@ build-docker: docker build --progress=plain \ --build-arg BUILD_IDENTIFIER=${ENV_BUILD_IDENTIFIER} --build-arg BUILD_VERSION=${ENV_BUILD_VERSION} \ --build-arg TARGETPLATFORM=unknown . \ - -t h44z/wg-portal:local \ No newline at end of file + -t h44z/wg-portal:local + +#< helm-docs: Generate the helm chart documentation +.PHONY: helm-docs +helm-docs: + docker run --rm --volume "${PWD}/deploy:/helm-docs" -u "$$(id -u)" jnorwood/helm-docs -s file diff --git a/README.md b/README.md index e9fd180..acc50e9 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,8 @@ The following configuration options are available: | csrf_secret | web | extremely_secret | The CSRF secret. | | site_title | web | WireGuard Portal | The title that is shown in the web frontend. | | site_company_name | web | WireGuard Portal | The company name that is shown at the bottom of the web frontend. | - +| cert_file | web | | (Optional) Path to the TLS certificate file | +| key_file | web | | (Optional) Path to the TLS certificate key file | ## Upgrading from V1 diff --git a/deploy/helm/Chart.yaml b/deploy/helm/Chart.yaml index e17febb..2f2bb87 100644 --- a/deploy/helm/Chart.yaml +++ b/deploy/helm/Chart.yaml @@ -16,10 +16,10 @@ annotations: # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 +version: 0.2.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: v2.0.0-alpha.2 +appVersion: latest diff --git a/deploy/helm/README.md b/deploy/helm/README.md index 59869ef..8cadb51 100644 --- a/deploy/helm/README.md +++ b/deploy/helm/README.md @@ -1,6 +1,6 @@ # wg-portal -![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v2.0.0-alpha.2](https://img.shields.io/badge/AppVersion-v2.0.0--alpha.2-informational?style=flat-square) +![Version: 0.2.0](https://img.shields.io/badge/Version-0.2.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: latest](https://img.shields.io/badge/AppVersion-latest-informational?style=flat-square) WireGuard Configuration Portal with LDAP, OAuth, OIDC authentication @@ -27,90 +27,80 @@ The [Values](#values) section lists the parameters that can be configured during ## Values -### Parameters - -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| affinity | object | `{}` | Affinity configuration | -| args | list | `[]` | Additional pod arguments | -| command | list | `[]` | Overwrite pod command | -| dnsPolicy | string | `"ClusterFirst"` | Set DNS policy for the pod. Valid values are `ClusterFirstWithHostNet`, `ClusterFirst`, `Default` or `None`. | -| env | tpl/list | `[]` | Additional environment variables | -| envFrom | tpl/list | `[]` | Additional environment variables from a secret or configMap | -| hostNetwork | string | `false`. | Use the host's network namespace. | -| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy | -| image.repository | string | `"ghcr.io/h44z/wg-portal"` | Image repository | -| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion | -| imagePullSecrets | list | `[]` | Image pull secrets | -| initContainers | tpl/list | `[]` | Pod init containers | -| nodeSelector | object | `{"kubernetes.io/os":"linux"}` | Node Selector configuration | -| podAnnotations | tpl/object | `{}` | Extra annotations to add to the pod | -| podLabels | object | `{}` | Extra labels to add to the pod | -| podSecurityContext | object | `{}` | Pod Security Context | -| resources | object | `{}` | Resources requests and limits | -| restartPolicy | string | `"Always"` | Restart policy for all containers within the pod. Valid values are `Always`, `OnFailure` or `Never`. | -| revisionHistoryLimit | string | `10` | The number of old ReplicaSets to retain to allow rollback. | -| securityContext.capabilities.add | list | `["NET_ADMIN"]` | Add capabilities to the container | -| sidecarContainers | tpl/list | `[]` | Pod sidecar containers | -| strategy | object | `{"type":"RollingUpdate"}` | Update strategy for the workload Valid values are: `RollingUpdate` or `Recreate` for Deployment, `RollingUpdate` or `OnDelete` for StatefulSet | -| tolerations | list | `[]` | Tolerations configuration | -| volumeMounts | tpl/list | `[]` | Additional volumeMounts | -| volumes | tpl/list | `[]` | Additional volumes | -| workloadType | string | `"Deployment"` | Workload type - `Deployment` or `StatefulSet` | - -### Configuration - | Key | Type | Default | Description | |-----|------|---------|-------------| +| nameOverride | string | `""` | Partially override resource names (adds suffix) | +| fullnameOverride | string | `""` | Fully override resource names | +| extraDeploy | list | `[]` | Array of extra objects to deploy with the release | | config.advanced | tpl/object | `{}` | Advanced configuration options. | | config.auth | tpl/object | `{}` | Auth configuration options. | | config.core | tpl/object | `{}` | Core configuration options.
If external admins in `auth` are not defined and there are no `admin_user` and `admin_password` defined here, the default credentials will be generated. | | config.database | tpl/object | `{}` | Database configuration options | | config.mail | tpl/object | `{}` | Mail configuration options | | config.statistics | tpl/object | `{}` | Statistics configuration options | -| config.web | tpl/object | `{}` | Web configuration options.
The chart will set `listening_address` automatically from `service.web.port`, and `external_url` from `ingress.host` if enabled. | - -### Common - -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| extraDeploy | list | `[]` | Array of extra objects to deploy with the release | -| fullnameOverride | string | `""` | Fully override resource names | -| nameOverride | string | `""` | Partially override resource names (adds suffix) | - -### Traffic exposure - -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| ingress.annotations | object | `{}` | Ingress annotations | -| ingress.className | string | `""` | Ingress class name | -| ingress.enabled | bool | `false` | Specifies whether an ingress resource should be created | -| ingress.host | string | `""` | Ingress host FQDN | -| ingress.path | string | `"/"` | Ingress path | -| ingress.pathType | string | `"ImplementationSpecific"` | Ingress path type | -| ingress.tls | list | `[]` | Ingress TLS configuration | +| config.web | tpl/object | `{}` | Web configuration options.
`listening_address` will be set automatically from `service.web.port`. `external_url` is required to enable ingress and certificate resources. | +| revisionHistoryLimit | string | `10` | The number of old ReplicaSets to retain to allow rollback. | +| workloadType | string | `"Deployment"` | Workload type - `Deployment` or `StatefulSet` | +| strategy | object | `{"type":"RollingUpdate"}` | Update strategy for the workload Valid values are: `RollingUpdate` or `Recreate` for Deployment, `RollingUpdate` or `OnDelete` for StatefulSet | +| image.repository | string | `"ghcr.io/h44z/wg-portal"` | Image repository | +| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy | +| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion | +| imagePullSecrets | list | `[]` | Image pull secrets | +| podAnnotations | tpl/object | `{}` | Extra annotations to add to the pod | +| podLabels | object | `{}` | Extra labels to add to the pod | +| podSecurityContext | object | `{}` | Pod Security Context | +| securityContext.capabilities.add | list | `["NET_ADMIN"]` | Add capabilities to the container | +| initContainers | tpl/list | `[]` | Pod init containers | +| sidecarContainers | tpl/list | `[]` | Pod sidecar containers | +| dnsPolicy | string | `"ClusterFirst"` | Set DNS policy for the pod. Valid values are `ClusterFirstWithHostNet`, `ClusterFirst`, `Default` or `None`. | +| restartPolicy | string | `"Always"` | Restart policy for all containers within the pod. Valid values are `Always`, `OnFailure` or `Never`. | +| hostNetwork | string | `false`. | Use the host's network namespace. | +| resources | object | `{}` | Resources requests and limits | +| command | list | `[]` | Overwrite pod command | +| args | list | `[]` | Additional pod arguments | +| env | tpl/list | `[]` | Additional environment variables | +| envFrom | tpl/list | `[]` | Additional environment variables from a secret or configMap | +| livenessProbe | object | `{}` | Liveness probe configuration | +| readinessProbe | object | `{}` | Readiness probe configuration | +| startupProbe | object | `{}` | Startup probe configuration | +| volumes | tpl/list | `[]` | Additional volumes | +| volumeMounts | tpl/list | `[]` | Additional volumeMounts | +| nodeSelector | object | `{"kubernetes.io/os":"linux"}` | Node Selector configuration | +| tolerations | list | `[]` | Tolerations configuration | +| affinity | object | `{}` | Affinity configuration | +| service.mixed.enabled | bool | `false` | Whether to create a single service for the web and wireguard interfaces | +| service.mixed.type | string | `"LoadBalancer"` | Service type | | service.web.annotations | object | `{}` | Annotations for the web service | -| service.web.port | int | `8888` | Web service port Used for the web interface listener | | service.web.type | string | `"ClusterIP"` | Web service type | +| service.web.port | int | `8888` | Web service port Used for the web interface listener | | service.wireguard.annotations | object | `{}` | Annotations for the WireGuard service | -| service.wireguard.ports | list | `[51820]` | Wireguard service ports. Exposes the WireGuard ports for created interfaces. Lowerest port is selected as start port for the first interface. Increment next port by 1 for each additional interface. | | service.wireguard.type | string | `"LoadBalancer"` | Wireguard service type | - -### Persistence - -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| persistence.accessMode | string | `"ReadWriteOnce"` | Persistent Volume Access Mode | -| persistence.annotations | object | `{}` | Persistent Volume Claim annotations | +| service.wireguard.ports | list | `[51820]` | Wireguard service ports. Exposes the WireGuard ports for created interfaces. Lowerest port is selected as start port for the first interface. Increment next port by 1 for each additional interface. | +| ingress.enabled | bool | `false` | Specifies whether an ingress resource should be created | +| ingress.className | string | `""` | Ingress class name | +| ingress.annotations | object | `{}` | Ingress annotations | +| ingress.tls | bool | `false` | Ingress TLS configuration. Enable certificate resource or add ingress annotation to create required secret | +| certificate.enabled | bool | `false` | Specifies whether a certificate resource should be created | +| certificate.issuer.name | string | `""` | Certificate issuer name | +| certificate.issuer.kind | string | `""` | Certificate issuer kind (ClusterIssuer or Issuer) | +| certificate.issuer.group | string | `"cert-manager.io"` | Certificate issuer group | +| certificate.duration | string | `""` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) | +| certificate.renewBefore | string | `""` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) | +| certificate.commonName | string | `""` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) | +| certificate.emailAddresses | list | `[]` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) | +| certificate.ipAddresses | list | `[]` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) | +| certificate.keystores | object | `{}` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) | +| certificate.privateKey | object | `{}` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) | +| certificate.secretTemplate | object | `{}` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) | +| certificate.subject | object | `{}` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) | +| certificate.uris | list | `[]` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) | +| certificate.usages | list | `[]` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) | | persistence.enabled | bool | `false` | Specifies whether an persistent volume should be created | -| persistence.size | string | `"1Gi"` | Persistent Volume size | +| persistence.annotations | object | `{}` | Persistent Volume Claim annotations | | persistence.storageClass | string | `""` | Persistent Volume storage class. If undefined (the default) cluster's default provisioner will be used. | - -### RBAC - -| Key | Type | Default | Description | -|-----|------|---------|-------------| +| persistence.accessMode | string | `"ReadWriteOnce"` | Persistent Volume Access Mode | +| persistence.size | string | `"1Gi"` | Persistent Volume size | +| serviceAccount.create | bool | `true` | Specifies whether a service account should be created | | serviceAccount.annotations | object | `{}` | Service account annotations | | serviceAccount.automount | bool | `false` | Automatically mount a ServiceAccount's API credentials | -| serviceAccount.create | bool | `true` | Specifies whether a service account should be created | | serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template | diff --git a/deploy/helm/templates/_helpers.tpl b/deploy/helm/templates/_helpers.tpl index cfeb4cf..a8c9dde 100644 --- a/deploy/helm/templates/_helpers.tpl +++ b/deploy/helm/templates/_helpers.tpl @@ -98,3 +98,12 @@ resources: requests: storage: {{ .Values.persistence.size | quote }} {{- end -}} + +{{/* +Define hostname +*/}} +{{- define "wg-portal.hostname" -}} +{{- if .Values.config.web.external_url -}} + {{- (urlParse (tpl .Values.config.web.external_url .)).hostname -}} +{{- end -}} +{{- end -}} diff --git a/deploy/helm/templates/_pod.tpl b/deploy/helm/templates/_pod.tpl index da52f3d..99bd6c2 100644 --- a/deploy/helm/templates/_pod.tpl +++ b/deploy/helm/templates/_pod.tpl @@ -7,7 +7,7 @@ metadata: {{- tpl (toYaml .) $ | nindent 4 }} {{- end }} labels: - {{- include "wg-portal.labels" . | nindent 4 }} + {{- include "wg-portal.selectorLabels" . | nindent 4 }} {{- with .Values.podLabels }} {{- toYaml . | nindent 4 }} {{- end }} @@ -36,7 +36,7 @@ spec: envFrom: {{- tpl (toYaml .) $ | nindent 8 }} {{- end }} ports: - - name: http + - name: web containerPort: {{ .Values.service.web.port }} protocol: TCP {{- range $index, $port := .Values.service.wireguard.ports }} @@ -65,6 +65,10 @@ spec: readOnly: true - name: data mountPath: /app/data + {{- if and .Values.certificate.enabled (include "wg-portal.hostname" .) }} + - name: certs + mountPath: /app/certs + {{- end }} {{- with .Values.volumeMounts }} {{- tpl (toYaml .) $ | nindent 8 }} {{- end }} @@ -97,6 +101,11 @@ spec: - name: config secret: secretName: {{ include "wg-portal.fullname" . }} + {{- if and .Values.certificate.enabled (include "wg-portal.hostname" .) }} + - name: certs + secret: + secretName: {{ include "wg-portal.fullname" . }}-tls + {{- end }} {{- if not .Values.persistence.enabled }} - name: data emptyDir: {} diff --git a/deploy/helm/templates/_service.tpl b/deploy/helm/templates/_service.tpl new file mode 100644 index 0000000..72234be --- /dev/null +++ b/deploy/helm/templates/_service.tpl @@ -0,0 +1,53 @@ +{{/* +Define the service template +{{- include "wg-portal.service" (dict "context" $ "scope" .Values.service. "ports" list "name" "") -}} +*/}} +{{- define "wg-portal.service.tpl" -}} +apiVersion: v1 +kind: Service +metadata: + {{- with .scope.annotations }} + annotations: {{- toYaml . | nindent 4 }} + {{- end }} + labels: {{- include "wg-portal.labels" .context | nindent 4 }} + name: {{ include "wg-portal.fullname" .context }}{{ ternary "" (printf "-%s" .name) (empty .name) }} +spec: + {{- with .scope.clusterIP }} + clusterIP: {{ . }} + {{- end }} + {{- with .scope.externalIPs }} + externalIPs: {{ toYaml . | nindent 4 }} + {{- end }} + {{- with .scope.externalName }} + externalName: {{ . }} + {{- end }} + {{- with .scope.externalTrafficPolicy }} + externalTrafficPolicy: {{ . }} + {{- end }} + {{- with .scope.healthCheckNodePort }} + healthCheckNodePort: {{ . }} + {{- end }} + {{- with .scope.loadBalancerIP }} + loadBalancerIP: {{ . }} + {{- end }} + {{- with .scope.loadBalancerSourceRanges }} + loadBalancerSourceRanges: {{ toYaml . | nindent 4 }} + {{- end }} + ports: {{- toYaml .ports | nindent 4 }} + {{- with .scope.publishNotReadyAddresses }} + publishNotReadyAddresses: {{ . }} + {{- end }} + {{- with .scope.sessionAffinity }} + sessionAffinity: {{ . }} + {{- end }} + {{- with .scope.sessionAffinityConfig }} + sessionAffinityConfig: {{ toYaml . | nindent 4 }} + {{- end }} + {{- with .scope.topologyKeys }} + topologyKeys: {{ toYaml . | nindent 4 }} + {{- end }} + {{- with .scope.type }} + type: {{ . }} + {{- end }} + selector: {{- include "wg-portal.selectorLabels" .context | nindent 4 }} +{{- end -}} diff --git a/deploy/helm/templates/certificate.yaml b/deploy/helm/templates/certificate.yaml new file mode 100644 index 0000000..03190f3 --- /dev/null +++ b/deploy/helm/templates/certificate.yaml @@ -0,0 +1,54 @@ +{{/* https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources */}} +{{- if and .Values.certificate.enabled (include "wg-portal.hostname" .) -}} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ include "wg-portal.fullname" . }} + labels: {{- include "wg-portal.labels" . | nindent 4 }} +spec: + secretName: {{ include "wg-portal.fullname" . }}-tls + {{- with .Values.certificate.secretTemplate }} + secretTemplate: {{ toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.certificate.privateKey }} + privateKey: {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.certificate.keystores }} + keystores: {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.certificate.duration }} + duration: {{ . }} + {{- end }} + {{- with .Values.certificate.renewBefore }} + renewBefore: {{ . }} + {{- end }} + {{- with .Values.certificate.usages }} + usages: {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.certificate.subject }} + subject: {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.certificate.commonName }} + commonName: {{ . }} + {{- end }} + dnsNames: + - {{ include "wg-portal.hostname" . }} + {{- with .Values.certificate.uris }} + uris: {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.certificate.emailAddresses }} + emailAddresses: {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.certificate.ipAddresses }} + ipAddresses: {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.certificate.otherNames }} + otherNames: {{- toYaml . | nindent 4 }} + {{- end }} + issuerRef: + {{- with .Values.certificate.issuer.group }} + group: {{ . }} + {{- end }} + kind: {{ .Values.certificate.issuer.kind }} + name: {{ .Values.certificate.issuer.name }} +{{- end -}} diff --git a/deploy/helm/templates/ingress.yaml b/deploy/helm/templates/ingress.yaml index 7413c78..be0370e 100644 --- a/deploy/helm/templates/ingress.yaml +++ b/deploy/helm/templates/ingress.yaml @@ -1,4 +1,5 @@ -{{- if .Values.ingress.enabled -}} +{{- $hostname := include "wg-portal.hostname" . -}} +{{- if and .Values.ingress.enabled $hostname -}} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: @@ -10,38 +11,20 @@ metadata: spec: ingressClassName: {{ .Values.ingress.className }} rules: - - host: {{ .Values.ingress.host }} + - host: {{ $hostname }} http: paths: - - path: / + - path: {{ default "/" (urlParse (tpl .Values.config.web.external_url .)).path }} pathType: {{ default "ImplementationSpecific" .pathType }} backend: service: - name: {{ include "wg-portal.fullname" . }}-web + name: {{ include "wg-portal.fullname" . }} port: - name: http - {{- range .Values.ingress.extraHosts }} - - host: {{ .host | quote }} - http: - paths: - {{- range .paths }} - - path: {{ .path }} - pathType: {{ default "ImplementationSpecific" .pathType }} - backend: - service: - name: {{ include "wg-portal.fullname" . }}-web - port: - name: http - {{- end }} - {{- end }} + name: web {{- if .Values.ingress.tls }} tls: - {{- range .Values.ingress.tls }} - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} + - {{ $hostname | quote }} + secretName: {{ include "wg-portal.fullname" . }}-tls {{- end }} {{- end }} diff --git a/deploy/helm/templates/secret.yaml b/deploy/helm/templates/secret.yaml index d8936f5..93d2719 100644 --- a/deploy/helm/templates/secret.yaml +++ b/deploy/helm/templates/secret.yaml @@ -32,10 +32,6 @@ stringData: {{- end }} web: listening_address: :{{ .Values.service.web.port }} - {{- if and .Values.ingress.enabled (not (hasKey .Values.config.web "external_url")) }} - {{- $proto := ternary "http" "https" (empty .Values.ingress.tls) }} - external_url: {{ trimSuffix "/" (printf "%s://%s%s" $proto .Values.ingress.host .Values.ingress.path) }} - {{- end }} {{- with .Values.config.web }} {{- tpl (toYaml (omit . "listening_address")) $ | nindent 6 }} {{- end }} diff --git a/deploy/helm/templates/service-web.yaml b/deploy/helm/templates/service-web.yaml deleted file mode 100644 index 531ba70..0000000 --- a/deploy/helm/templates/service-web.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - {{- with .Values.service.web.annotations }} - annotations: {{- toYaml . | nindent 4 }} - {{- end }} - name: {{ include "wg-portal.fullname" . }}-web - labels: {{- include "wg-portal.labels" . | nindent 4 }} -spec: - ports: - - port: {{ .Values.service.web.port }} - targetPort: http - protocol: TCP - name: http - selector: {{- include "wg-portal.selectorLabels" . | nindent 4 }} - type: {{ .Values.service.web.type }} diff --git a/deploy/helm/templates/service-wireguard.yaml b/deploy/helm/templates/service-wireguard.yaml deleted file mode 100644 index 3a01cf9..0000000 --- a/deploy/helm/templates/service-wireguard.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - {{- with .Values.service.wireguard.annotations }} - annotations: {{- toYaml . | nindent 4 }} - {{- end }} - name: {{ include "wg-portal.fullname" . }}-wireguard - labels: {{- include "wg-portal.labels" . | nindent 4 }} -spec: - {{- with .Values.service.wireguard.externalTrafficPolicy }} - externalTrafficPolicy: {{ . }} - {{- end }} - {{- with .Values.service.wireguard.loadBalancerSourceRanges }} - loadBalancerSourceRanges: {{- toYaml . | nindent 4 }} - {{- end }} - ports: - {{- range $index, $port := .Values.service.wireguard.ports }} - - port: {{ $port }} - targetPort: wg{{ $index }} - protocol: UDP - name: wg{{ $index }} - {{- end }} - selector: {{- include "wg-portal.selectorLabels" . | nindent 4 }} - {{- with .Values.service.wireguard.sessionAffinity }} - sessionAffinity: {{ . }} - {{- end }} - type: {{ .Values.service.wireguard.type }} diff --git a/deploy/helm/templates/service.yaml b/deploy/helm/templates/service.yaml new file mode 100644 index 0000000..1b38d3f --- /dev/null +++ b/deploy/helm/templates/service.yaml @@ -0,0 +1,14 @@ +{{- $portsWeb := list (dict "name" "web" "port" .Values.service.web.port "protocol" "TCP" "targetPort" "web") -}} +{{- $ports := list -}} +{{- range $idx, $port := .Values.service.wireguard.ports -}} +{{- $name := printf "wg%d" $idx -}} +{{- $ports = append $ports (dict "name" $name "port" $port "protocol" "UDP" "targetPort" $name) -}} +{{- end -}} + +{{- if .Values.service.mixed.enabled -}} +{{ include "wg-portal.service.tpl" (dict "context" . "scope" .Values.service.mixed "ports" (concat $portsWeb $ports)) }} +{{- else }} +{{ include "wg-portal.service.tpl" (dict "context" . "scope" .Values.service.web "ports" $portsWeb) }} +--- +{{ include "wg-portal.service.tpl" (dict "context" . "scope" .Values.service.wireguard "ports" $ports "name" "wireguard") }} +{{- end -}} diff --git a/deploy/helm/values.yaml b/deploy/helm/values.yaml index 142c990..c1d4b90 100644 --- a/deploy/helm/values.yaml +++ b/deploy/helm/values.yaml @@ -3,267 +3,202 @@ # Declare variables to be passed into your templates. # -- Partially override resource names (adds suffix) -# @section -- Common nameOverride: '' # -- Fully override resource names -# @section -- Common fullnameOverride: '' # -- Array of extra objects to deploy with the release -# @section -- Common extraDeploy: [] # https://github.com/h44z/wg-portal/blob/master/README.md#configuration-options config: # -- (tpl/object) Advanced configuration options. - # @section -- Configuration advanced: {} # -- (tpl/object) Auth configuration options. - # @section -- Configuration auth: {} # -- (tpl/object) Core configuration options.
- # @section -- Configuration # If external admins in `auth` are not defined and # there are no `admin_user` and `admin_password` defined here, # the default credentials will be generated. core: {} # -- (tpl/object) Database configuration options - # @section -- Configuration database: {} # -- (tpl/object) Mail configuration options - # @section -- Configuration mail: {} # -- (tpl/object) Statistics configuration options - # @section -- Configuration statistics: {} # -- (tpl/object) Web configuration options.
- # @section -- Configuration - # The chart will set `listening_address` automatically from `service.web.port`, - # and `external_url` from `ingress.host` if enabled. + # `listening_address` will be set automatically from `service.web.port`. + # `external_url` is required to enable ingress and certificate resources. web: {} # -- The number of old ReplicaSets to retain to allow rollback. -# @section -- Parameters # @default -- `10` revisionHistoryLimit: '' # -- Workload type - `Deployment` or `StatefulSet` -# @section -- Parameters workloadType: Deployment # -- Update strategy for the workload # Valid values are: # `RollingUpdate` or `Recreate` for Deployment, # `RollingUpdate` or `OnDelete` for StatefulSet -# @section -- Parameters strategy: type: RollingUpdate image: # -- Image repository - # @section -- Parameters repository: ghcr.io/h44z/wg-portal # -- Image pull policy - # @section -- Parameters pullPolicy: IfNotPresent # -- Overrides the image tag whose default is the chart appVersion - # @section -- Parameters tag: '' # -- Image pull secrets -# @section -- Parameters imagePullSecrets: [] - # -- (tpl/object) Extra annotations to add to the pod -# @section -- Parameters podAnnotations: {} - # -- Extra labels to add to the pod -# @section -- Parameters podLabels: {} - # -- Pod Security Context -# @section -- Parameters podSecurityContext: {} - # Container Security Context securityContext: capabilities: # -- Add capabilities to the container - # @section -- Parameters add: - NET_ADMIN # -- (tpl/list) Pod init containers -# @section -- Parameters initContainers: [] # -- (tpl/list) Pod sidecar containers -# @section -- Parameters sidecarContainers: [] - # -- Set DNS policy for the pod. # Valid values are `ClusterFirstWithHostNet`, `ClusterFirst`, `Default` or `None`. # @default -- `"ClusterFirst"` -# @section -- Parameters dnsPolicy: '' - # -- Restart policy for all containers within the pod. # Valid values are `Always`, `OnFailure` or `Never`. # @default -- `"Always"` -# @section -- Parameters restartPolicy: '' - # -- Use the host's network namespace. # @default -- `false`. -# @section -- Parameters hostNetwork: '' - # -- Resources requests and limits -# @section -- Parameters resources: {} - # -- Overwrite pod command -# @section -- Parameters command: [] - # -- Additional pod arguments -# @section -- Parameters args: [] - # -- (tpl/list) Additional environment variables -# @section -- Parameters env: [] - # -- (tpl/list) Additional environment variables from a secret or configMap -# @section -- Parameters envFrom: [] - # -- Liveness probe configuration -# @ignore -livenessProbe: - failureThreshold: 10 - httpGet: - path: / - port: http - +livenessProbe: {} # -- Readiness probe configuration -# @ignore -readinessProbe: - httpGet: - path: / - port: http - +readinessProbe: {} # -- Startup probe configuration -# @ignore -startupProbe: - initialDelaySeconds: 5 - failureThreshold: 10 - httpGet: - path: / - port: http - scheme: HTTP - +startupProbe: {} # -- (tpl/list) Additional volumes -# @section -- Parameters volumes: [] - # -- (tpl/list) Additional volumeMounts -# @section -- Parameters volumeMounts: [] - # -- Node Selector configuration -# @section -- Parameters nodeSelector: kubernetes.io/os: linux - # -- Tolerations configuration -# @section -- Parameters tolerations: [] - # -- Affinity configuration -# @section -- Parameters affinity: {} service: + mixed: + # -- Whether to create a single service for the web and wireguard interfaces + enabled: false + # -- Service type + type: LoadBalancer web: # -- Annotations for the web service - # @section -- Traffic exposure annotations: {} # -- Web service type - # @section -- Traffic exposure type: ClusterIP # -- Web service port # Used for the web interface listener - # @section -- Traffic exposure port: 8888 wireguard: # -- Annotations for the WireGuard service - # @section -- Traffic exposure annotations: {} # -- Wireguard service type - # @section -- Traffic exposure type: LoadBalancer # -- Wireguard service ports. # Exposes the WireGuard ports for created interfaces. # Lowerest port is selected as start port for the first interface. # Increment next port by 1 for each additional interface. - # @section -- Traffic exposure ports: - 51820 ingress: # -- Specifies whether an ingress resource should be created - # @section -- Traffic exposure enabled: false # -- Ingress class name - # @section -- Traffic exposure className: '' # -- Ingress annotations - # @section -- Traffic exposure - # @section -- Traffic exposure annotations: {} - # -- Ingress host FQDN - # @section -- Traffic exposure - host: '' - # -- Ingress path type - # @section -- Traffic exposure - pathType: ImplementationSpecific - # -- Ingress path - # @section -- Traffic exposure - path: / - # -- Ingress TLS configuration - # @section -- Traffic exposure - tls: [] - # - secretName: wg-portal-example-tls - # hosts: - # - wg-portal.example.local + # -- Ingress TLS configuration. + # Enable certificate resource or add ingress annotation to create required secret + tls: false + +certificate: + # -- Specifies whether a certificate resource should be created + enabled: false + issuer: + # -- Certificate issuer name + name: '' + # -- Certificate issuer kind (ClusterIssuer or Issuer) + kind: '' + # -- Certificate issuer group + group: cert-manager.io + # -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) + duration: '' + # -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) + renewBefore: '' + # -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) + commonName: '' + # -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) + emailAddresses: [] + # -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) + ipAddresses: [] + # -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) + keystores: {} + # -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) + privateKey: {} + # -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) + secretTemplate: {} + # -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) + subject: {} + # -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) + uris: [] + # -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) + usages: [] persistence: # -- Specifies whether an persistent volume should be created - # @section -- Persistence enabled: false # -- Persistent Volume Claim annotations - # @section -- Persistence annotations: {} # -- Persistent Volume storage class. # If undefined (the default) cluster's default provisioner will be used. - # @section -- Persistence storageClass: '' # -- Persistent Volume Access Mode - # @section -- Persistence accessMode: ReadWriteOnce # -- Persistent Volume size - # @section -- Persistence size: 1Gi serviceAccount: # -- Specifies whether a service account should be created - # @section -- RBAC create: true # -- Service account annotations - # @section -- RBAC annotations: {} # -- Automatically mount a ServiceAccount's API credentials - # @section -- RBAC automount: false # -- The name of the service account to use. # If not set and create is true, a name is generated using the fullname template - # @section -- RBAC name: '' diff --git a/internal/app/api/core/server.go b/internal/app/api/core/server.go index b9aa3e8..2c1f378 100644 --- a/internal/app/api/core/server.go +++ b/internal/app/api/core/server.go @@ -68,8 +68,7 @@ func NewServer(cfg *config.Config, endpoints ...ApiEndpointSetupFunc) (*Server, c.Writer.Header().Set("X-Served-By", hostname) c.Next() }).Use(func(c *gin.Context) { - var xRequestID string - xRequestID = uuid(16) + xRequestID := uuid(16) c.Request.Header.Set(RequestIDKey, xRequestID) c.Set(RequestIDKey, xRequestID) @@ -106,7 +105,13 @@ func (s *Server) Run(ctx context.Context, listenAddress string) { srvContext, cancelFn := context.WithCancel(ctx) go func() { - if err := srv.ListenAndServe(); err != nil { + var err error + if s.cfg.Web.CertFile != "" && s.cfg.Web.KeyFile != "" { + err = srv.ListenAndServeTLS(s.cfg.Web.CertFile, s.cfg.Web.KeyFile) + } else { + err = srv.ListenAndServe() + } + if err != nil { logrus.Infof("web service on %s exited: %v", listenAddress, err) cancelFn() } diff --git a/internal/config/web.go b/internal/config/web.go index 5a8ed85..9a7508f 100644 --- a/internal/config/web.go +++ b/internal/config/web.go @@ -9,4 +9,6 @@ type WebConfig struct { CsrfSecret string `yaml:"csrf_secret"` SiteTitle string `yaml:"site_title"` SiteCompanyName string `yaml:"site_company_name"` + CertFile string `yaml:"cert_file"` + KeyFile string `yaml:"key_file"` }