diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..6d67355 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,171 @@ +name: CI + +on: + pull_request: + push: + branches: + - '*' + tags-ignore: + - '*' + paths-ignore: + - 'demo/**' + - 'docs/**' + - 'LICENSE' + - 'README.md' + workflow_dispatch: + +env: + DAGGER_VERSION: "0.14.0" + DOCKER_REGISTRY: ${{ vars.DOCKER_REGISTRY }} + DOCKER_REPOSITORY: ${{ vars.DOCKER_REPOSITORY }} + DOCKER_REGISTRY_USERNAME: ${{ vars.DOCKER_REGISTRY_USERNAME }} + DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_PASSWORD }} + GH_DOCKER_REPOSITORY: ${{ vars.GH_DOCKER_REPOSITORY }} + GH_HELM_REPOSITORY: ${{ vars.GH_HELM_REPOSITORY }} + +jobs: + docker-unstable: + if: github.ref == 'refs/heads/init' && github.event_name == 'push' + + name: Push Docker image + runs-on: ubuntu-latest + + strategy: + matrix: + target: ["debug", "prod"] + + permissions: + contents: read + packages: write + attestations: write + id-token: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set short SHA + id: sha + run: echo "short_sha=${GITHUB_SHA::7}" >> $GITHUB_ENV + + - name: Set image tag + id: tag + run: | + if [ "${{ github.ref }}" == "refs/heads/init" ]; then + if [[ "${{ matrix.target }}" == "debug" ]]; then + echo "tag=unstable-debug" >> $GITHUB_ENV + else + echo "tag=unstable" >> $GITHUB_ENV + fi + else + if [[ "${{ matrix.target }}" == "debug" ]]; then + echo "tag=build-${{ env.short_sha }}-debug" >> $GITHUB_ENV + else + echo "tag=build-${{ env.short_sha }}" >> $GITHUB_ENV + fi + fi + + - name: Publish Docker image to Github + uses: dagger/dagger-for-github@v7 + env: + GH_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + with: + version: ${{ env.DAGGER_VERSION }} + engine-stop: false + module: github.com/opopops/daggerverse/docker@v1.0.0 + verb: call + args: | + --registry=ghcr.io \ + --username=${{ github.actor }} \ + --password=env:GH_REGISTRY_PASSWORD \ + build \ + --context=. \ + --target=${{ matrix.target }} \ + --platform=linux/amd64,linux/arm64 \ + publish \ + --image=ghcr.io/${GH_DOCKER_REPOSITORY}:${{ env.tag }} \ + + - name: Copy Docker image to Docker Hub + uses: dagger/dagger-for-github@v7 + env: + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} + DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_PASSWORD }} + GH_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + with: + version: ${{ env.DAGGER_VERSION }} + engine-stop: false + module: github.com/opopops/daggerverse/crane@v1.0.0 + verb: call + args: | + with-registry-auth \ + --address=ghcr.io \ + --username=${{ github.actor }} \ + --secret=env:GH_REGISTRY_PASSWORD \ + with-registry-auth \ + --address=$DOCKER_REGISTRY \ + --username=$DOCKER_REGISTRY_USERNAME \ + --secret=env:DOCKER_REGISTRY_PASSWORD \ + copy \ + --source=ghcr.io/${GH_DOCKER_REPOSITORY}:${{ env.tag }} \ + --target=${DOCKER_REGISTRY}/${DOCKER_REPOSITORY}:${{ env.tag }} \ + + - name: Scan Docker image + uses: dagger/dagger-for-github@v7 + env: + GH_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + with: + version: ${{ env.DAGGER_VERSION }} + module: github.com/opopops/daggerverse/grype@v1.0.0 + verb: call + args: | + with-registry-auth \ + --address=ghcr.io \ + --username=${{ github.actor }} \ + --secret=env:GH_REGISTRY_PASSWORD \ + scan \ + --source=ghcr.io/${GH_DOCKER_REPOSITORY}:${{ env.tag }} \ + + helm-unstable: + if: github.ref == 'refs/heads/init' && github.event_name == 'push' + name: Push Helm Chart + runs-on: ubuntu-latest + + permissions: + contents: read + packages: write + attestations: write + id-token: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Lint + uses: dagger/dagger-for-github@v7 + with: + version: ${{ env.DAGGER_VERSION }} + engine-stop: false + module: github.com/opopops/daggerverse/helm@add-helm-module + verb: call + args: | + lint \ + --path chart \ + --strict \ + + - name: Publish Helm chart + uses: dagger/dagger-for-github@v7 + env: + GH_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + with: + version: ${{ env.DAGGER_VERSION }} + module: github.com/opopops/daggerverse/helm@add-helm-module + verb: call + args: | + package-push \ + --path chart \ + --version="0.0.0" \ + --app-version="unstable" \ + --registry=ghcr.io/${GH_HELM_REPOSITORY} \ + --username=${{ github.actor }} \ + --password=env:GH_REGISTRY_PASSWORD \ diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..57a3fe6 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,145 @@ +name: Release + +on: + push: + tags: + - 'v*' + +env: + DAGGER_VERSION: "0.14.0" + DOCKER_REGISTRY: ${{ vars.DOCKER_REGISTRY }} + DOCKER_REPOSITORY: ${{ vars.DOCKER_REPOSITORY }} + DOCKER_REGISTRY_USERNAME: ${{ vars.DOCKER_REGISTRY_USERNAME }} + GH_DOCKER_REPOSITORY: ${{ vars.GH_DOCKER_REPOSITORY }} + GH_HELM_REPOSITORY: ${{ vars.GH_HELM_REPOSITORY }} + +jobs: + docker: + if: startsWith(github.event.ref, 'refs/tags/v') + + name: Release Docker image + runs-on: ubuntu-latest + + strategy: + matrix: + target: ["debug", "prod"] + + permissions: + contents: read + packages: write + attestations: write + id-token: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Publish Docker image to GitHub + uses: dagger/dagger-for-github@v7 + env: + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} + GH_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + with: + version: ${{ env.DAGGER_VERSION }} + engine-stop: false + module: github.com/opopops/daggerverse/docker@v1.0.0 + verb: call + args: | + --registry=ghcr.io \ + --username=${{ github.actor }} \ + --password=env:GH_REGISTRY_PASSWORD \ + build \ + --context=. \ + --target=${{ matrix.target }} \ + --platform=linux/amd64,linux/arm64 \ + publish \ + --image=ghcr.io/${GH_DOCKER_REPOSITORY}:${{ github.ref_name }} \ + sign \ + --password=env:COSIGN_PASSWORD \ + --private-key=env:COSIGN_PRIVATE_KEY \ + + - name: Copy Docker image to Docker Hub + uses: dagger/dagger-for-github@v7 + env: + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} + DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_PASSWORD }} + GH_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + with: + version: ${{ env.DAGGER_VERSION }} + engine-stop: false + module: github.com/opopops/daggerverse/crane@v1.0.0 + verb: call + args: | + with-registry-auth \ + --address=ghcr.io \ + --username=${{ github.actor }} \ + --secret=env:GH_REGISTRY_PASSWORD \ + with-registry-auth \ + --address=$DOCKER_REGISTRY \ + --username=$DOCKER_REGISTRY_USERNAME \ + --secret=env:DOCKER_REGISTRY_PASSWORD \ + copy \ + --source=ghcr.io/${GH_DOCKER_REPOSITORY}:${{ github.ref_name }} \ + --target=${DOCKER_REGISTRY}/${DOCKER_REPOSITORY}:${{ github.ref_name }} \ + + - name: Scan Docker image + uses: dagger/dagger-for-github@v7 + env: + GH_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + with: + version: ${{ env.DAGGER_VERSION }} + module: github.com/opopops/daggerverse/grype@v1.0.0 + verb: call + args: | + with-registry-auth \ + --address=ghcr.io \ + --username=${{ github.actor }} \ + --secret=env:GH_REGISTRY_PASSWORD \ + scan \ + --source=ghcr.io/${GH_DOCKER_REPOSITORY}:${{ github.ref_name }} \ + --fail-on=high \ + + + helm: + name: Push Helm Chart + runs-on: ubuntu-latest + + permissions: + contents: read + packages: write + attestations: write + id-token: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Lint + uses: dagger/dagger-for-github@v7 + with: + version: ${{ env.DAGGER_VERSION }} + engine-stop: false + module: github.com/opopops/daggerverse/helm@add-helm-module + verb: call + args: | + lint \ + --path chart \ + --strict \ + + - name: Publish Helm chart + uses: dagger/dagger-for-github@v7 + env: + GH_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + with: + version: ${{ env.DAGGER_VERSION }} + module: github.com/opopops/daggerverse/helm@add-helm-module + verb: call + args: | + package-push \ + --path chart \ + --app-version="${{ github.ref_name }}" \ + --registry=ghcr.io/${GH_HELM_REPOSITORY} \ + --username=${{ github.actor }} \ + --password=env:GH_REGISTRY_PASSWORD \ \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..828537d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +.local/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..98e096b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,94 @@ +# syntax=docker/dockerfile:1 + +ARG REGISTRY="cgr.dev" + +### Base +FROM --platform=$BUILDPLATFORM ${REGISTRY}/chainguard/wolfi-base:latest AS base + +LABEL org.opencontainers.image.authors="GitGuardian SRE Team " + +ARG TARGETOS +ARG TARGETARCH +ARG TARGETVARIANT + +RUN apk add --no-cache \ + curl + +### WSTunnel +FROM base AS wstunnel + +ARG WSTUNNEL_VERSION="10.1.6" +ENV WSTUNNEL_VERSION=$WSTUNNEL_VERSION +RUN curl -fsSL https://github.com/erebe/wstunnel/releases/download/v${WSTUNNEL_VERSION}/wstunnel_${WSTUNNEL_VERSION}_${TARGETOS}_${TARGETARCH}.tar.gz | \ + tar xvzf - -C /usr/bin wstunnel && \ + chmod 755 /usr/bin/wstunnel +USER 65532 + +FROM base AS builder + +RUN apk add --no-cache \ + bash \ + git \ + go + + +### Build +FROM builder AS build + +WORKDIR /build +COPY go.mod . +COPY main.go . +RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ + go build -o ggbridge -ldflags "-w" . + + +### Dev +FROM builder AS dev + +RUN apk add --no-cache \ + nano \ + openssl \ + vim + +COPY --link --from=wstunnel --chmod=755 /usr/bin/wstunnel /usr/bin/wstunnel + + +### Debug +FROM base AS debug + +LABEL org.opencontainers.image.description="ggbridge - connect your on-prem VCS with the GitGuardian Platform" + +RUN apk add --no-cache \ + bash \ + curl \ + nginx-mainline \ + openssl + +RUN install -d -m 755 -o 65532 -g 65532 \ + /var/lib/nginx \ + /var/lib/nginx/html \ + /var/lib/nginx/logs && \ + install -d -m 777 -o 65532 -g 65532 \ + /var/lib/nginx/tmp \ + /var/run + +COPY --link --from=wstunnel --chmod=755 /usr/bin/wstunnel /usr/bin/wstunnel +COPY --link --from=build --chmod=755 /build/ggbridge /usr/bin/ggbridge + +USER 65532 + +ENTRYPOINT [] +CMD ["/bin/sh", "-l"] + + +### Prod +FROM ${REGISTRY}/chainguard/glibc-dynamic:latest AS prod + +LABEL org.opencontainers.image.authors="GitGuardian SRE Team " +LABEL org.opencontainers.image.description="ggbridge - connect your on-prem VCS with the GitGuardian Platform" + +COPY --link --from=wstunnel --chmod=755 /usr/bin/wstunnel /usr/bin/wstunnel +COPY --link --from=build --chmod=755 /build/ggbridge /usr/bin/ggbridge + +ENTRYPOINT ["/usr/bin/ggbridge"] +CMD ["client"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..189412c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Germain + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index b77e343..ddb1ab1 100644 --- a/README.md +++ b/README.md @@ -1 +1,18 @@ -# ggbridge \ No newline at end of file +# ggbridge: connect your on-prem VCS with the GitGuardian Platform + +**ggbridge** is a tool designed to facilitate secure connections between the GitGuardian SaaS platform and your on-premise Version Control Systems (VCS) that are not exposed to the public internet. By acting as a secure bridge, GGBridge enables GitGuardian to access repositories located in isolated environments, ensuring that your sensitive code data remains protected while taking advantage of GitGuardian’s powerful scanning capabilities. + +With ggbirdge, organizations can maintain their internal infrastructure and security protocols without sacrificing the ability to integrate with GitGuardian’s monitoring and alerting features. + +## How it Works + +![ggbridge](./docs/assets/ggbridge.drawio.png) + +**ggbridge** is composed of two main parts: + +- **Server**: Installed on the GitGuardian's network. +- **Client**: Installed on the customer’s private network. + +The client component connects to the server using the WebSocket protocol to establish a secure, mutually authenticated (mTLS) tunnel between the customer’s network and the GitGuardian SaaS platform. This ensures both ends are securely authenticated. + +Once the tunnel is established, a proxy server is deployed on the GitGuardian side, which allows secure access to the client’s on-prem VCS through the tunnel. This proxy connection enables GitGuardian to scan and monitor your repositories without requiring your VCS to be publicly accessible. diff --git a/chart/.gitignore b/chart/.gitignore new file mode 100644 index 0000000..aee4987 --- /dev/null +++ b/chart/.gitignore @@ -0,0 +1,2 @@ +values-local*.yaml +values-local*.yml diff --git a/chart/.helmignore b/chart/.helmignore new file mode 100644 index 0000000..671084a --- /dev/null +++ b/chart/.helmignore @@ -0,0 +1,26 @@ +# 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/ + +values-local.yaml +values-local.yml diff --git a/chart/Chart.yaml b/chart/Chart.yaml new file mode 100644 index 0000000..390f126 --- /dev/null +++ b/chart/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: ggbridge +description: A Helm chart for installing ggbridge +type: application +version: 0.0.0 +appVersion: "v0.0.0" diff --git a/chart/templates/NOTES.txt b/chart/templates/NOTES.txt new file mode 100644 index 0000000..e5322b4 --- /dev/null +++ b/chart/templates/NOTES.txt @@ -0,0 +1,8 @@ +Thank you for installing GitGuardian ggbridge. + +Your release is named {{ .Release.Name }}. + +To learn more about the release, try: + + $ helm status {{ .Release.Name }} + $ helm get all {{ .Release.Name }} diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl new file mode 100644 index 0000000..d40edaf --- /dev/null +++ b/chart/templates/_helpers.tpl @@ -0,0 +1,294 @@ +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "ggbridge.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Expand the name of the chart. +*/}} +{{- define "ggbridge.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 "ggbridge.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 a default fully qualified client name. +{{ include "ggbridge.client.name" . }} +*/}} +{{- define "ggbridge.client.fullname" -}} +{{- printf "%s-client" (include "ggbridge.fullname" .) | trunc 63 | trimSuffix "-" }} +{{- end -}} + +{{/* +Create a default fully qualified server name. +{{ include "ggbridge.server.name" ( dict "name" "server_name" context $ ) }} +*/}} +{{- define "ggbridge.server.fullname" -}} +{{- printf "%s-server" (include "ggbridge.fullname" .) | trunc 63 | trimSuffix "-" }} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "ggbridge.labels" -}} +helm.sh/chart: {{ include "ggbridge.chart" . }} +{{ include "ggbridge.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- with .Values.commonLabels }} +{{ tpl (toYaml .) $ }} +{{- end -}} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "ggbridge.selectorLabels" -}} +app.kubernetes.io/name: {{ include "ggbridge.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Client labels +{{ include "ggbridge.client.labels" . }} +*/}} +{{- define "ggbridge.client.labels" -}} +{{ include "ggbridge.client.selectorLabels" . }} +{{- with .Values.client.labels }} +{{ tpl (toYaml .) $ }} +{{- end }} +{{- end }} + +{{/* +Client selector labels +{{ include "ggbridge.client.selectorLabels" . }} +*/}} +{{- define "ggbridge.client.selectorLabels" -}} +app.kubernetes.io/component: client +{{- end }} + +{{/* +Server labels +{{ include "ggbridge.server.labels" . }} +*/}} +{{- define "ggbridge.server.labels" -}} +{{ include "ggbridge.server.selectorLabels" . }} +{{- with .Values.server.labels }} +{{ tpl (toYaml .) $ }} +{{- end }} +{{- end }} + +{{/* +Server selector labels +{{ include "ggbridge.server.selectorLabels" . }} +*/}} +{{- define "ggbridge.server.selectorLabels" -}} +app.kubernetes.io/component: server +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "ggbridge.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "ggbridge.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Return the proper image name. +If image tag and digest are not defined, termination fallbacks to chart appVersion. +{{ include "ggbridge.image" }} +*/}} +{{- define "ggbridge.image" -}} +{{- $registryName := .Values.image.registry -}} +{{- $repositoryName := .Values.image.repository -}} +{{- $separator := ":" -}} +{{- $termination := .Values.image.tag | toString -}} + +{{- if not .Values.image.tag }} + {{- if .Chart }} + {{- $termination = .Chart.AppVersion | toString -}} + {{- end -}} +{{- end -}} +{{- if .Values.image.digest }} + {{- $separator = "@" -}} + {{- $termination = .Values.image.digest | toString -}} +{{- end -}} +{{- if $registryName }} + {{- printf "%s/%s%s%s" $registryName $repositoryName $separator $termination -}} +{{- else -}} + {{- printf "%s%s%s" $repositoryName $separator $termination -}} +{{- end -}} +{{- end -}} + +{{/* +Returns client pod affinity. +{{ include "ggbridge.client.affinity" $ }} +*/}} +{{- define "ggbridge.client.affinity" -}} +{{- $hostnames := list -}} +{{- range $index, $values := .Values.client.hosts -}} + {{- $hostnames = append $hostnames $values.hostname -}} +{{- end -}} +podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 1 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/component + operator: In + values: + - client + - key: hostname + operator: In + values: {{ $hostnames | uniq | toYaml | nindent 16 }} + topologyKey: "kubernetes.io/hostname" + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/component + operator: In + values: + - client + - key: hostname + operator: In + values: {{ $hostnames | uniq | toYaml | nindent 16 }} + topologyKey: "topology.kubernetes.io/zone" +{{- end -}} + +{{/* +Returns server pod affinity. +{{ include "ggbridge.server.affinity" $ }} +*/}} +{{- define "ggbridge.server.affinity" -}} +{{- $hostnames := list -}} +{{- range $index, $values := .Values.server.hosts -}} + {{- $hostnames = append $hostnames $values.hostname -}} +{{- end -}} +podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 1 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/component + operator: In + values: + - server + - key: hostname + operator: In + values: {{ $hostnames | uniq | toYaml | nindent 16 }} + topologyKey: "kubernetes.io/hostname" + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/component + operator: In + values: + - server + - key: hostname + operator: In + values: {{ $hostnames | uniq | toYaml | nindent 16 }} + topologyKey: "topology.kubernetes.io/zone" +{{- end -}} + +{{/* +Returns ingress annotations +{{ include "ggbridge.server.ingressAnnotations" ( dict "fullname" . "context" $ ) }} +*/}} +{{- define "ggbridge.server.ingressAnnotations" -}} +{{- $context := .context -}} +{{- $fullname := .fullname -}} +{{- $annotations := dict -}} +{{- if $context.Values.server.tls.enabled -}} + {{- if eq $context.Values.server.ingress.controller "traefik" -}} + {{- $_ := set $annotations "traefik.ingress.kubernetes.io/router.entrypoints" "websecure" -}} + {{- $_ := set $annotations "traefik.ingress.kubernetes.io/service.serversscheme" "http" -}} + {{- $_ := set $annotations "traefik.ingress.kubernetes.io/router.tls.options" (printf "%s-%s@kubernetescrd" $context.Release.Namespace $fullname ) -}} + {{- else if eq $context.Values.server.ingress.controller "nginx" -}} + {{- $_ := set $annotations "nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream" "false" -}} + {{- $_ := set $annotations "nginx.ingress.kubernetes.io/backend-protocol" "HTTP" -}} + {{- if eq $context.Values.server.tls.mode "mutual" -}} + {{- $_ := set $annotations "nginx.ingress.kubernetes.io/auth-tls-secret" (printf "%s/%s-crt" $context.Release.Namespace $fullname) -}} + {{- $_ := set $annotations "nginx.ingress.kubernetes.io/auth-tls-verify-client" "on" -}} + {{- $_ := set $annotations "nginx.ingress.kubernetes.io/auth-tls-verify-depth" "1" -}} + {{- end -}} + {{- end -}} +{{- end -}} +{{- $annotations = include "ggbridge.tplvalues.merge" ( dict "values" ( list $context.Values.server.ingress.annotations $annotations $context.Values.commonAnnotations ) "context" $context ) | fromYaml -}} +{{ include "ggbridge.tplvalues.render" ( dict "value" $annotations "context" $context) }} +{{- end -}} + +{{/* +Renders a value that contains template perhaps with scope if the scope is present. +Usage: +{{ include "ggbridge.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $ ) }} +{{ include "ggbridge.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $ "scope" $app ) }} +*/}} +{{- define "ggbridge.tplvalues.render" -}} +{{- $value := typeIs "string" .value | ternary .value (.value | toYaml) }} +{{- if contains "{{" (toJson .value) }} + {{- if .scope }} + {{- tpl (cat "{{- with $.RelativeScope -}}" $value "{{- end }}") (merge (dict "RelativeScope" .scope) .context) }} + {{- else }} + {{- tpl $value .context }} + {{- end }} +{{- else }} + {{- $value }} +{{- end }} +{{- end -}} + +{{/* +Merge a list of values that contains template after rendering them. +Merge precedence is consistent with http://masterminds.github.io/sprig/dicts.html#merge-mustmerge +Usage: +{{ include "ggbridge.tplvalues.merge" ( dict "values" (list .Values.path.to.the.Value1 .Values.path.to.the.Value2) "context" $ ) }} +*/}} +{{- define "ggbridge.tplvalues.merge" -}} +{{- $dst := dict -}} +{{- range .values -}} +{{- $dst = include "ggbridge.tplvalues.render" (dict "value" . "context" $.context "scope" $.scope) | fromYaml | merge $dst -}} +{{- end -}} +{{ $dst | toYaml }} +{{- end -}} + +{{/* +Merge a list of values that contains template after rendering them. +Merge precedence is consistent with https://masterminds.github.io/sprig/dicts.html#mergeoverwrite-mustmergeoverwrite +Usage: +{{ include "ggbridge.tplvalues.merge-overwrite" ( dict "values" (list .Values.path.to.the.Value1 .Values.path.to.the.Value2) "context" $ ) }} +*/}} +{{- define "ggbridge.tplvalues.merge-overwrite" -}} +{{- $dst := dict -}} +{{- range .values -}} +{{- $dst = include "ggbridge.tplvalues.render" (dict "value" . "context" $.context "scope" $.scope) | fromYaml | mergeOverwrite $dst -}} +{{- end -}} +{{ $dst | toYaml }} +{{- end -}} diff --git a/chart/templates/cert-manager.yaml b/chart/templates/cert-manager.yaml new file mode 100644 index 0000000..fa5b4a7 --- /dev/null +++ b/chart/templates/cert-manager.yaml @@ -0,0 +1,55 @@ +{{- if .Values.server.tls.certManager.enabled -}} + {{- $fullname := include "ggbridge.fullname" . -}} + {{- $namespace := ternary (default .Release.namespace .Values.server.istio.gateway.namespace) .Release.namespace .Values.server.istio.enabled -}} + {{- if eq .Values.server.tls.certManager.issuer "selfSigned" }} +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: {{ printf "%s-selfsigned" $fullname }} + namespace: {{ $namespace }} + labels: + {{- include "ggbridge.labels" $ | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "ggbridge.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" . ) | nindent 4 }} + {{- end }} +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ printf "%s-ca" $fullname }} + namespace: {{ $namespace }} + labels: + {{- include "ggbridge.labels" $ | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "ggbridge.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + isCA: true + commonName: {{ printf "%s-ca" $fullname }} + secretName: {{ printf "%s-ca" $fullname }} + duration: 87600h # 10 years + privateKey: + algorithm: RSA + size: 2048 + issuerRef: + name: {{ printf "%s-selfsigned" $fullname }} + kind: Issuer +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: {{ printf "%s-ca" $fullname }} + namespace: {{ $namespace }} + labels: + {{- include "ggbridge.labels" $ | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "ggbridge.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + ca: + secretName: {{ printf "%s-ca" $fullname }} + {{- end }} +{{- end }} diff --git a/chart/templates/client/cert-manager.yaml b/chart/templates/client/cert-manager.yaml new file mode 100644 index 0000000..27e3da0 --- /dev/null +++ b/chart/templates/client/cert-manager.yaml @@ -0,0 +1,35 @@ +{{- if .Values.server.tls.certManager.enabled -}} +{{- $fullname := include "ggbridge.fullname" . -}} +{{- $namespace := ternary (default .Release.namespace .Values.server.istio.gateway.namespace) .Release.namespace .Values.server.istio.enabled -}} + {{- with .Values.client -}} + {{- $clientFullname := include "ggbridge.client.fullname" $ -}} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ printf "%s-crt" $clientFullname }} + namespace: {{ $namespace }} + labels: + {{- include "ggbridge.labels" $ | nindent 4 }} + {{- include "ggbridge.client.labels" $ | nindent 4 }} + {{- if $.Values.commonAnnotations }} + annotations: {{- include "ggbridge.tplvalues.render" ( dict "value" $.Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + secretName: {{ printf "%s-crt" $clientFullname }} + duration: 17520h # 2 years + privateKey: + algorithm: RSA + size: 2048 + commonName: {{ $clientFullname }} + usages: + - client auth + issuerRef: + {{- if eq $.Values.server.tls.certManager.issuer "selfSigned" }} + name: {{ printf "%s-ca" $fullname }} + {{- else }} + name: {{ $.Values.server.tls.certManager.issuer }} + {{- end }} + kind: Issuer + {{- end }} +{{- end }} diff --git a/chart/templates/client/deployment.yaml b/chart/templates/client/deployment.yaml new file mode 100644 index 0000000..fd1c8d7 --- /dev/null +++ b/chart/templates/client/deployment.yaml @@ -0,0 +1,120 @@ +{{- range $index, $host := .Values.client.hosts -}} + {{- with $host -}} + {{- $fullname := include "ggbridge.fullname" $ }} + {{- $clientFullname := include "ggbridge.client.fullname" $ }} + {{- $indexClientFullname := printf "%s-%d" $clientFullname ($index | int) }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $indexClientFullname }} + namespace: {{ $.Release.Namespace }} + labels: + {{- include "ggbridge.labels" $ | nindent 4 }} + {{- include "ggbridge.client.labels" $ | nindent 4 }} + {{- if $.Values.commonAnnotations }} + annotations: {{- include "ggbridge.tplvalues.render" ( dict "value" $.Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + replicas: {{ $.Values.client.replicas }} + {{- with $.Values.client.updateStrategy }} + strategy: + {{- toYaml . | nindent 4 }} + {{- end }} + selector: + matchLabels: + {{- include "ggbridge.selectorLabels" $ | nindent 6 }} + {{- include "ggbridge.client.selectorLabels" $ | nindent 6 }} + hostname: {{ .hostname }} + template: + metadata: + {{- with $.Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "ggbridge.labels" $ | nindent 8 }} + {{- include "ggbridge.client.labels" $ | nindent 8 }} + {{- with $.Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + hostname: {{ .hostname }} + spec: + {{- with $.Values.image.pullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "ggbridge.serviceAccountName" $ }} + {{- if $.Values.podSecurityContext.enabled }} + securityContext: + {{- toYaml (omit $.Values.podSecurityContext "enabled") | nindent 8 }} + {{- end }} + containers: + - name: {{ $.Chart.Name }} + {{- if $.Values.containerSecurityContext.enabled }} + securityContext: + {{- toYaml (omit $.Values.containerSecurityContext "enabled") | nindent 12 }} + {{- end }} + image: {{ include "ggbridge.image" $ }} + imagePullPolicy: {{ $.Values.image.pullPolicy }} + command: ["ggbridge"] + args: ["client"] + env: + - name: SERVER_ADDRESS + value: {{ .hostname }} + - name: TLS_ENABLED + value: {{ $.Values.client.tls.enabled | quote }} + {{- with $.Values.client.dnsResolver }} + - name: DNS_RESOLVER + value: {{ . | quote }} + {{- end }} + {{- with $.Values.client.connectionMinIdle }} + - name: CONNECTION_MIN_IDLE + value: {{ . | quote }} + {{- end }} + {{- with $.Values.logLevel }} + - name: LOG_LEVEL + value: {{ . | quote }} + {{- end }} + {{- if $.Values.client.tls.enabled }} + volumeMounts: + - name: tls-secret + mountPath: /etc/ggbridge/certs + readOnly: true + {{- end }} + resources: + {{- toYaml $.Values.resources | nindent 12 }} + {{- if $.Values.client.tls.enabled }} + volumes: + - name: tls-secret + secret: + {{- if (get (default dict $.Values.client.tls) "existingSecret") }} + secretName: {{ $.Values.client.tls.existingSecret }} + items: + - key: {{ default "tls.crt" $.Values.client.tls.existingSecretKeys.crt }} + path: client.crt + - key: {{ default "tls.key" $.Values.client.tls.existingSecretKeys.key }} + path: client.key + {{- else}} + secretName: {{ printf "%s-crt" $clientFullname }} + items: + - key: tls.crt + path: client.crt + - key: tls.key + path: client.key + {{- end }} + defaultMode: 420 + {{- end }} + {{- with $.Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- $affinity := include "ggbridge.tplvalues.merge" ( dict "values" ( list $.Values.affinity (include "ggbridge.client.affinity" $) ) "context" $ ) }} + affinity: {{- include "ggbridge.tplvalues.render" ( dict "value" $affinity "context" $) | nindent 8 }} + {{- with $.Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + + {{- end }} +{{- end }} diff --git a/chart/templates/client/networkpolicy.yaml b/chart/templates/client/networkpolicy.yaml new file mode 100644 index 0000000..a4b3885 --- /dev/null +++ b/chart/templates/client/networkpolicy.yaml @@ -0,0 +1,36 @@ +{{- if and .Values.client.hosts .Values.client.networkPolicy.enabled }} +{{- $clientFullname := include "ggbridge.client.fullname" . }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ $clientFullname }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "ggbridge.labels" . | nindent 4 }} + {{- include "ggbridge.client.labels" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "ggbridge.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" . ) | nindent 4 }} + {{- end }} +spec: + podSelector: + matchLabels: + {{- include "ggbridge.selectorLabels" . | nindent 6 }} + {{- include "ggbridge.client.selectorLabels" . | nindent 6 }} + policyTypes: + - Ingress + - Egress + egress: + - ports: + # Allow dns resolution + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + - port: 443 + protocol: TCP + {{- if .Values.client.networkPolicy.extraEgress }} + {{- include "ggbridge.tplvalues.render" ( dict "value" .Values.client.networkPolicy.extraEgress "context" . ) | nindent 4 }} + {{- end }} + ingress: [] +{{- end }} diff --git a/chart/templates/client/pdb.yaml b/chart/templates/client/pdb.yaml new file mode 100644 index 0000000..926309c --- /dev/null +++ b/chart/templates/client/pdb.yaml @@ -0,0 +1,34 @@ +{{- if and .enabled $.Values.client.pdb.create }} + {{- range $index, $host := .Values.client.hosts -}} + {{- with $host -}} + {{- $fullname := include "ggbridge.fullname" $ }} + {{- $clientFullname := include "ggbridge.client.fullname" $ }} + {{- $indexClientFullname := printf "%s-%d" $clientFullname ($index | int) }} +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ $indexClientFullname }} + namespace: {{ $.Release.Namespace }} + labels: + {{- include "ggbridge.labels" $ | nindent 4 }} + {{- include "ggbridge.client.labels" $ | nindent 4 }} + hostname: {{ .hostname }} + {{- if $.Values.commonAnnotations }} + annotations: {{- include "ggbridge.tplvalues.render" ( dict "value" $.Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + {{- if $.Values.client.pdb.minAvailable }} + minAvailable: {{ $.Values.client.pdb.minAvailable }} + {{- end }} + {{- if or $.Values.client.pdb.maxUnavailable ( not $.Values.client.pdb.minAvailable ) }} + maxUnavailable: {{ $.Values.client.pdb.maxUnavailable | default 1 }} + {{- end }} + selector: + matchLabels: + {{- include "ggbridge.selectorLabels" $ | nindent 6 }} + {{- include "ggbridge.client.selectorLabels" $ | nindent 6 }} + hostname: {{ .hostname }} + {{- end }} + {{- end }} +{{- end }} diff --git a/chart/templates/server/cert-manager.yaml b/chart/templates/server/cert-manager.yaml new file mode 100644 index 0000000..cea5eea --- /dev/null +++ b/chart/templates/server/cert-manager.yaml @@ -0,0 +1,40 @@ +{{- if .Values.server.tls.certManager.enabled -}} + {{- $fullname := include "ggbridge.fullname" . -}} + {{- $serverFullname := include "ggbridge.server.fullname" . }} + {{- $namespace := ternary (default .Release.namespace .Values.server.istio.gateway.namespace) .Release.namespace .Values.server.istio.enabled -}} + {{- range $index, $host := .Values.server.hosts -}} + {{- with $host }} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ printf "%s-%d-crt" $serverFullname ($index | int) }} + namespace: {{ $namespace }} + labels: + {{- include "ggbridge.labels" $ | nindent 4 }} + {{- include "ggbridge.server.labels" $ | nindent 4 }} + {{- if $.Values.commonAnnotations }} + annotations: {{- include "ggbridge.tplvalues.render" ( dict "value" $.Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + secretName: {{ printf "%s-%d-crt" $serverFullname ($index | int) }} + duration: 17520h # 2 years + privateKey: + algorithm: RSA + size: 2048 + commonName: {{ .hostname }} + dnsNames: + - {{ .hostname }} + usages: + - server auth + issuerRef: + {{- if eq $.Values.server.tls.certManager.issuer "selfSigned" }} + name: {{ printf "%s-ca" $fullname }} + {{- else }} + name: {{ $.Values.server.tls.certManager.issuer }} + {{- end }} + kind: Issuer + + {{- end }} + {{- end }} +{{- end }} diff --git a/chart/templates/server/deployment.yaml b/chart/templates/server/deployment.yaml new file mode 100644 index 0000000..06d2ef8 --- /dev/null +++ b/chart/templates/server/deployment.yaml @@ -0,0 +1,140 @@ +{{- range $index, $host := .Values.server.hosts -}} + {{- with $host -}} + {{- $fullname := include "ggbridge.fullname" $ }} + {{- $serverFullname := include "ggbridge.server.fullname" $ }} + {{- $indexServerFullname := printf "%s-%d" $serverFullname ($index | int) }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $indexServerFullname }} + namespace: {{ $.Release.Namespace }} + labels: + {{- include "ggbridge.labels" $ | nindent 4 }} + {{- include "ggbridge.server.labels" $ | nindent 4 }} + {{- if $.Values.commonAnnotations }} + annotations: {{- include "ggbridge.tplvalues.render" ( dict "value" $.Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + selector: + matchLabels: + {{- include "ggbridge.selectorLabels" $ | nindent 6 }} + {{- include "ggbridge.server.selectorLabels" $ | nindent 6 }} + hostname: {{ .hostname }} + template: + metadata: + {{- with $.Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "ggbridge.labels" $ | nindent 8 }} + {{- include "ggbridge.server.labels" $ | nindent 8 }} + hostname: {{ .hostname }} + {{- with $.Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with $.Values.image.pullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "ggbridge.serviceAccountName" $ }} + {{- if $.Values.podSecurityContext.enabled }} + securityContext: + {{- toYaml (omit $.Values.podSecurityContext "enabled") | nindent 8 }} + {{- end }} + containers: + - name: ggbridge + {{- if $.Values.containerSecurityContext.enabled }} + securityContext: + {{- toYaml (omit $.Values.containerSecurityContext "enabled") | nindent 12 }} + {{- end }} + image: {{ include "ggbridge.image" $ }} + imagePullPolicy: {{ $.Values.image.pullPolicy }} + command: ["ggbridge"] + args: ["server"] + env: + - name: SERVER_ADDRESS + value: {{ printf "0.0.0.0:%d" ($.Values.server.services.external.ports.ws.containerPort | int) }} + {{- if and ($.Values.server.tls.enabled) (not $.Values.server.ingress.enabled) (not $.Values.server.gateway.enabled) (not $.Values.server.istio.enabled) }} + - name: TLS_ENABLED + value: "true" + {{- end }} + {{- with $.Values.logLevel }} + - name: LOG_LEVEL + value: {{ . | quote }} + {{- end }} + ports: + - name: ws + containerPort: {{ $.Values.server.services.external.ports.ws.containerPort }} + protocol: TCP + - name: socks + containerPort: {{ $.Values.server.services.proxy.ports.socks.containerPort }} + protocol: TCP + # readinessProbe: + # tcpSocket: + # port: {{ $.Values.server.services.external.ports.ws.containerPort }} + # initialDelaySeconds: 5 + # periodSeconds: 10 + # livenessProbe: + # tcpSocket: + # port: {{ $.Values.server.services.external.ports.ws.containerPort }} + # initialDelaySeconds: 5 + # periodSeconds: 10 + {{- if $.Values.server.tls.enabled }} + {{- if or (eq $.Values.server.tls.mode "passthrough") (and (not $.Values.server.ingress.enabled) (not $.Values.server.gateway.enabled) (not $.Values.server.istio.enabled)) }} + volumeMounts: + - name: tls-secret + mountPath: /etc/ggbridge/certs + readOnly: true + {{- end }} + {{- end }} + resources: + {{- toYaml $.Values.resources | nindent 12 }} + {{- if $.Values.server.tls.enabled }} + {{- if or (eq $.Values.server.tls.mode "passthrough") (and (not $.Values.server.ingress.enabled) (not $.Values.server.gateway.enabled) (not $.Values.server.istio.enabled)) }} + volumes: + - name: tls-secret + secret: + {{- if (get (default dict .tls) "existingSecret") }} + secretName: {{ .tls.existingSecret }} + items: + - key: {{ default "ca.crt" .tls.existingSecretKeys.caCrt }} + path: ca.crt + - key: {{ default "tls.crt" .tls.existingSecretKeys.crt }} + path: server.crt + - key: {{ default "tls.key" .tls.existingSecretKeys.key }} + path: server.key + {{- else}} + secretName: {{ printf "%s-crt" $indexServerFullname }} + items: + - key: ca.crt + path: ca.crt + - key: tls.crt + path: server.crt + - key: tls.key + path: server.key + {{- end }} + defaultMode: 420 + {{- end }} + {{- end }} + {{- with $.Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- $affinity := include "ggbridge.tplvalues.merge" ( dict "values" ( list $.Values.affinity (include "ggbridge.server.affinity" $) ) "context" $ ) }} + affinity: {{- include "ggbridge.tplvalues.render" ( dict "value" $affinity "context" $) | nindent 8 }} + {{- with $.Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + + {{- end }} +{{- end }} diff --git a/chart/templates/server/gateway.yaml b/chart/templates/server/gateway.yaml new file mode 100644 index 0000000..008c5fb --- /dev/null +++ b/chart/templates/server/gateway.yaml @@ -0,0 +1,80 @@ +{{- if .Values.server.gateway.enabled }} + {{- range $index, $host := .Values.server.hosts -}} + {{- with $host -}} + {{- $fullname := include "ggbridge.fullname" $ }} + {{- $serverFullname := include "ggbridge.server.fullname" $ }} + {{- $indexServerFullname := printf "%s-%d" $serverFullname ($index | int) }} + +{{- if $.Values.server.gateway.gateway.create }} +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: {{ $indexServerFullname }} + namespace: {{ $.Release.Namespace }} + labels: + {{- include "ggbridge.labels" $ | nindent 4 }} + {{- include "ggbridge.server.labels" $ | nindent 4 }} + {{- if or $.Values.commonAnnotations $.Values.server.gateway.annotations }} + {{- $annotations := include "ggbridge.tplvalues.merge" ( dict "values" ( list $.Values.server.gateway.annotations $.Values.commonAnnotations ) "context" $ ) }} + annotations: {{- include "ggbridge.tplvalues.render" ( dict "value" $annotations "context" $) | nindent 4 }} + {{- end }} +spec: + gatewayClassName: {{ $.Values.server.gateway.gateway.className | quote }} + listeners: + - hostname: {{ .hostname | quote }} + allowedRoutes: + namespaces: + from: Same + {{- if $.Values.server.tls.enabled }} + name: https + port: {{ $.Values.server.gateway.gateway.ports.https }} + protocol: HTTPS + tls: + mode: Terminate + certificateRefs: + {{- if (get (default dict .tls) "existingSecret") }} + - name: {{ .tls.existingSecret }} + {{- else }} + - name: {{ printf "%s-crt" $serverFullname }} + {{- end }} + options: + gateway.istio.io/tls-terminate-mode: MUTUAL + {{- else }} + name: http + port: {{ $.Values.server.gateway.gateway.ports.http }} + protocol: HTTP + {{- end }} +{{- end }} +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ $indexServerFullname }} + namespace: {{ $.Release.Namespace }} + labels: + {{- include "ggbridge.labels" $ | nindent 4 }} + {{- include "ggbridge.server.labels" $ | nindent 4 }} + {{- if or $.Values.commonAnnotations $.Values.server.gateway.annotations }} + {{- $annotations := include "ggbridge.tplvalues.merge" ( dict "values" ( list $.Values.server.gateway.annotations $.Values.commonAnnotations ) "context" $ ) }} + annotations: {{- include "ggbridge.tplvalues.render" ( dict "value" $annotations "context" $) | nindent 4 }} + {{- end }} +spec: + {{- if $.Values.server.gateway.gateway.create }} + parentRefs: + - name: {{ $fullname }} + namespace: {{ $.Release.Namespace }} + sectionName: {{ ternary "https" "http" $.Values.server.tls.enabled }} + {{- else }} + parentRefs: {{ toYaml $.Values.server.gateway.parentRefs | nindent 4 }} + {{- end }} + hostnames: + - {{ .hostname }} + rules: + - backendRefs: + - name: {{ $fullname }} + port: {{ $.Values.server.services.external.ports.ws.port }} + + {{- end }} + {{- end }} +{{- end }} diff --git a/chart/templates/server/ingress.yaml b/chart/templates/server/ingress.yaml new file mode 100644 index 0000000..e3ba659 --- /dev/null +++ b/chart/templates/server/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.server.ingress.enabled }} + {{- range $index, $host := .Values.server.hosts -}} + {{- with $host -}} + {{- $fullname := include "ggbridge.fullname" $ }} + {{- $serverFullname := include "ggbridge.server.fullname" $ }} + {{- $indexServerFullname := printf "%s-%d" $serverFullname ($index | int) }} + +{{- if and (eq $.Values.server.ingress.controller "traefik") $.Values.server.tls.enabled }} +--- +apiVersion: traefik.io/v1alpha1 +kind: TLSOption +metadata: + name: {{ $indexServerFullname }} + namespace: {{ $.Release.Namespace }} + labels: + {{- include "ggbridge.labels" $ | nindent 4 }} + {{- include "ggbridge.server.labels" $ | nindent 4 }} +spec: + clientAuth: + secretNames: + - {{ printf "%s-crt" $serverFullname }} + clientAuthType: RequireAndVerifyClientCert + minVersion: VersionTLS12 +{{- end }} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $indexServerFullname }} + namespace: {{ $.Release.Namespace }} + labels: + {{- include "ggbridge.labels" $ | nindent 4 }} + {{- include "ggbridge.server.labels" $ | nindent 4 }} + annotations: {{ include "ggbridge.server.ingressAnnotations" (dict "fullname" $fullname "context" $) | nindent 4 }} +spec: + ingressClassName: {{ $.Values.server.ingress.className | quote }} + rules: + - host: {{ .hostname | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ $fullname }} + port: + number: {{ $.Values.server.services.external.ports.ws.port }} + {{- if $.Values.server.tls.enabled }} + tls: + - hosts: + - {{ .hostname | quote }} + {{- if (get (default dict .tls) "existingSecret") }} + secretName: {{ .tls.existingSecret }} + {{- else }} + secretName: {{ printf "%s-crt" $serverFullname }} + {{- end }} + {{- end }} + + {{- end }} + {{- end }} +{{- end }} diff --git a/chart/templates/server/istio.yaml b/chart/templates/server/istio.yaml new file mode 100644 index 0000000..3f81f1f --- /dev/null +++ b/chart/templates/server/istio.yaml @@ -0,0 +1,93 @@ +{{- if .Values.server.istio.enabled }} + {{- range $index, $host := .Values.server.hosts -}} + {{- with $host -}} + {{- $fullname := include "ggbridge.fullname" $ }} + {{- $serverFullname := include "ggbridge.server.fullname" $ }} + {{- $indexServerFullname := printf "%s-%d" $serverFullname ($index | int) }} + +{{- if $.Values.server.istio.gateway.create }} +--- +apiVersion: networking.istio.io/v1 +kind: Gateway +metadata: + name: {{ $indexServerFullname }} + namespace: {{ default $.Release.Namespace $.Values.server.istio.gateway.namespace }} + labels: + {{- include "ggbridge.labels" $ | nindent 4 }} + {{- include "ggbridge.server.labels" $ | nindent 4 }} + {{- if or $.Values.commonAnnotations $.Values.server.istio.annotations }} + {{- $annotations := include "ggbridge.tplvalues.merge" ( dict "values" ( list $.Values.server.istio.annotations $.Values.commonAnnotations ) "context" $ ) }} + annotations: {{- include "ggbridge.tplvalues.render" ( dict "value" $annotations "context" $) | nindent 4 }} + {{- end }} +spec: + selector: {{ toYaml $.Values.server.istio.gateway.selector | nindent 4 }} + servers: + {{- if $.Values.server.tls.enabled }} + - port: + number: {{ $.Values.server.istio.gateway.ports.https }} + name: https + protocol: HTTPS + tls: + mode: {{ upper $.Values.server.tls.mode }} + minProtocolVersion: {{ $.Values.server.istio.gateway.tls.minProtocolVersion }} + {{- if ne $.Values.server.tls.mode "passthrough" }} + {{- if (get (default dict .tls) "existingSecret") }} + credentialName: {{ .tls.existingSecret }} + {{- else }} + credentialName: {{ printf "%s-crt" $indexServerFullname }} + {{- end }} + {{- end }} + {{- else }} + - port: + number: {{ $.Values.server.istio.gateway.ports.http }} + name: http + protocol: HTTP + {{- end }} + hosts: + - {{ .hostname | quote }} +{{- end }} +--- +apiVersion: networking.istio.io/v1 +kind: VirtualService +metadata: + name: {{ $indexServerFullname }} + namespace: {{ $.Release.Namespace }} + labels: + {{- include "ggbridge.labels" $ | nindent 4 }} + {{- include "ggbridge.server.labels" $ | nindent 4 }} + hostname: {{ .hostname }} + {{- if or $.Values.commonAnnotations $.Values.server.istio.annotations }} + {{- $annotations := include "ggbridge.tplvalues.merge" ( dict "values" ( list $.Values.server.istio.annotations $.Values.commonAnnotations ) "context" $ ) }} + annotations: {{- include "ggbridge.tplvalues.render" ( dict "value" $annotations "context" $) | nindent 4 }} + {{- end }} +spec: + hosts: + - {{ .hostname | quote }} + {{- if $.Values.server.istio.gateway.create }} + gateways: + - {{ printf "%s/%s" (default $.Release.Namespace $.Values.server.istio.gateway.namespace) $indexServerFullname }} + {{- else }} + gateways: {{ toYaml $.Values.server.istio.gateways | nindent 4 }} + {{- end }} + tls: + - match: + - port: {{ $.Values.server.istio.gateway.ports.https }} + sniHosts: + - {{ .hostname | quote }} + route: + - destination: + host: {{ $indexServerFullname }} + port: + number: {{ $.Values.server.services.external.ports.ws.port }} + http: + - match: + - uri: + prefix: "/" + route: + - destination: + host: {{ $indexServerFullname }} + port: + number: {{ $.Values.server.services.external.ports.ws.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/chart/templates/server/networkpolicy.yaml b/chart/templates/server/networkpolicy.yaml new file mode 100644 index 0000000..3041b40 --- /dev/null +++ b/chart/templates/server/networkpolicy.yaml @@ -0,0 +1,65 @@ +{{- if and .Values.server.hosts .Values.server.networkPolicy.enabled }} + {{- $fullname := include "ggbridge.server.fullname" . }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ $fullname }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "ggbridge.labels" . | nindent 4 }} + {{- include "ggbridge.server.labels" . | nindent 4 }} + {{- if $.Values.commonAnnotations }} + annotations: {{- include "ggbridge.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + podSelector: + matchLabels: + {{- include "ggbridge.selectorLabels" $ | nindent 6 }} + {{- include "ggbridge.server.selectorLabels" $ | nindent 6 }} + policyTypes: + - Ingress + - Egress + egress: + - ports: + # Allow dns resolution + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + {{- if .Values.server.networkPolicy.extraEgress }} + {{- include "ggbridge.tplvalues.render" ( dict "value" .Values.server.networkPolicy.extraEgress "context" $ ) | nindent 4 }} + {{- end }} + ingress: + - ports: + # Allow external ws port + - port: {{ $.Values.server.services.external.ports.ws.containerPort }} + protocol: TCP + - ports: + # Allow proxy socks port + - port: {{ $.Values.server.services.proxy.ports.socks.containerPort }} + protocol: TCP + {{- if not .Values.server.networkPolicy.allowExternal }} + from: + - podSelector: + matchLabels: + {{- include "ggbridge.selectorLabels" . | nindent 14 }} + {{- include "ggbridge.client.selectorLabels" . | nindent 14 }} + {{- if .Values.server.networkPolicy.ingressNSMatchLabels }} + - namespaceSelector: + matchLabels: + {{- range $key, $value := .Values.server.networkPolicy.ingressNSMatchLabels }} + {{ $key | quote }}: {{ $value | quote }} + {{- end }} + {{- if .Values.server.networkPolicy.ingressNSPodMatchLabels }} + podSelector: + matchLabels: + {{- range $key, $value := .Values.server.networkPolicy.ingressNSPodMatchLabels }} + {{ $key | quote }}: {{ $value | quote }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.server.networkPolicy.extraIngress }} + {{- include "ggbridge.tplvalues.render" ( dict "value" .Values.server.networkPolicy.extraIngress "context" . ) | nindent 4 }} + {{- end }} +{{- end }} diff --git a/chart/templates/server/pdb.yaml b/chart/templates/server/pdb.yaml new file mode 100644 index 0000000..9caf7a4 --- /dev/null +++ b/chart/templates/server/pdb.yaml @@ -0,0 +1,33 @@ +{{- range $index, $host := .Values.server.hosts -}} + {{- with $host -}} + {{- $fullname := include "ggbridge.fullname" $ }} + {{- $serverFullname := include "ggbridge.server.fullname" $ }} + {{- $indexServerFullname := printf "%s-%d" $serverFullname ($index | int) }} +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ $indexServerFullname }} + namespace: {{ $.Release.Namespace }} + labels: + {{- include "ggbridge.labels" $ | nindent 4 }} + {{- include "ggbridge.server.labels" $ | nindent 4 }} + hostname: {{ .hostname }} + {{- if $.Values.commonAnnotations }} + annotations: {{- include "ggbridge.tplvalues.render" ( dict "value" $.Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + {{- if $.Values.server.pdb.minAvailable }} + minAvailable: {{ $.Values.server.pdb.minAvailable }} + {{- end }} + {{- if or $.Values.server.pdb.maxUnavailable ( not $.Values.server.pdb.minAvailable ) }} + maxUnavailable: {{ $.Values.server.pdb.maxUnavailable | default 1 }} + {{- end }} + selector: + matchLabels: + {{- include "ggbridge.selectorLabels" $ | nindent 6 }} + {{- include "ggbridge.server.selectorLabels" $ | nindent 6 }} + hostname: {{ .hostname }} + + {{- end }} +{{- end }} diff --git a/chart/templates/server/role.yaml b/chart/templates/server/role.yaml new file mode 100644 index 0000000..23e27a1 --- /dev/null +++ b/chart/templates/server/role.yaml @@ -0,0 +1,55 @@ +{{- if .Values.server.hosts }} + {{- $fullname := include "ggbridge.fullname" . }} + {{- if .Values.server.rbac.certManager.create }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ printf "%s-cert-manager" $fullname }} + namespace: {{ .Release.Namespace }} +rules: + - apiGroups: + - '' + resources: + - 'secrets' + verbs: + - 'get' + - 'list' + - 'watch' + - apiGroups: + - 'cert-manager.io' + resources: + - 'certificates' + verbs: + - 'get' + - 'list' + - 'watch' + - 'create' + - apiGroups: + - 'cert-manager.io' + resources: + - 'issuers' + verbs: + - 'get' + - 'list' + - 'watch' + - 'create' + {{- if .Values.server.rbac.certManager.subjects }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ printf "%s-cert-manager" $fullname }} + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ printf "%s-cert-manager" $fullname }} +{{- with .Values.server.rbac.certManager.subjects }} +subjects: + {{- toYaml . | nindent 2 }} +{{- end }} + + {{- end }} + {{- end }} +{{- end }} diff --git a/chart/templates/server/service.yaml b/chart/templates/server/service.yaml new file mode 100644 index 0000000..a2137e0 --- /dev/null +++ b/chart/templates/server/service.yaml @@ -0,0 +1,56 @@ +{{- $fullname := include "ggbridge.fullname" $ }} +{{- $serverFullname := include "ggbridge.server.fullname" $ }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ default (printf "%s-proxy" $serverFullname) .Values.server.services.proxy.name }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "ggbridge.labels" $ | nindent 4 }} + {{- include "ggbridge.server.labels" $ | nindent 4 }} + {{- if or .Values.commonAnnotations .Values.server.services.proxy.annotations }} + {{- $annotations := include "ggbridge.tplvalues.merge" ( dict "values" ( list .Values.server.services.proxy.annotations .Values.commonAnnotations ) "context" . ) }} + annotations: {{- include "ggbridge.tplvalues.render" $annotations | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.server.services.proxy.type }} + ports: + - port: {{ .Values.server.services.proxy.ports.socks.port }} + targetPort: socks + protocol: TCP + name: socks + selector: + {{- include "ggbridge.selectorLabels" . | nindent 4 }} + {{- include "ggbridge.server.selectorLabels" . | nindent 4 }} + +{{- range $index, $host := .Values.server.hosts -}} + {{- with $host -}} + {{- $indexServerFullname := printf "%s-%d" $serverFullname ($index | int) }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ $indexServerFullname }} + namespace: {{ $.Release.Namespace }} + labels: + {{- include "ggbridge.labels" $ | nindent 4 }} + {{- include "ggbridge.server.labels" $ | nindent 4 }} + {{- if or $.Values.commonAnnotations $.Values.server.services.external.annotations }} + {{- $annotations := include "ggbridge.tplvalues.merge" ( dict "values" ( list $.Values.server.services.external.annotations $.Values.commonAnnotations ) "context" $ ) }} + annotations: {{- include "ggbridge.tplvalues.render" $annotations | nindent 4 }} + {{- end }} +spec: + type: {{ $.Values.server.services.external.type }} + ports: + - port: {{ $.Values.server.services.external.ports.ws.port }} + targetPort: ws + protocol: TCP + name: ws + selector: + {{- include "ggbridge.selectorLabels" $ | nindent 4 }} + {{- include "ggbridge.server.selectorLabels" $ | nindent 4 }} + hostname: {{ .hostname }} + + {{- end }} +{{- end }} diff --git a/chart/templates/server/tls-secrets.yaml b/chart/templates/server/tls-secrets.yaml new file mode 100644 index 0000000..afb3793 --- /dev/null +++ b/chart/templates/server/tls-secrets.yaml @@ -0,0 +1,83 @@ +{{- if and (.Values.server.tls.enabled) (not .Values.server.tls.certManager.enabled) -}} + {{- $fullname := include "ggbridge.fullname" . }} + {{- $serverFullname := include "ggbridge.server.fullname" . }} + {{- $clientFullname := include "ggbridge.client.fullname" . }} + {{- $releaseNamespace := $.Release.Namespace }} + {{- $clusterDomain := $.Values.clusterDomain }} + {{- $ca := dict }} + {{- $clientCert := dict }} + {{- if .Values.server.tls.autoGenerated }} + {{- $caSecret := (get (lookup "v1" "Secret" $releaseNamespace (printf "%s-ca" $fullname)) "data" | default dict) }} + {{- if $caSecret }} + {{- $ca = buildCustomCert (get $caSecret "tls.crt") (get $caSecret "tls.key") }} + {{- else }} + {{- $ca = genCA $fullname 3650 }} + {{- end }} + {{- $clientCert = genSignedCert $clientFullname nil nil 365 $ca }} + {{- $namespace := ternary (default .Release.namespace .Values.server.istio.gateway.namespace) .Release.namespace .Values.server.istio.enabled -}} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ printf "%s-ca" $fullname }} + namespace: {{ $namespace }} + labels: + {{- include "ggbridge.labels" . | nindent 4 }} + {{- if $.Values.commonAnnotations }} + annotations: {{- include "ggbridge.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" . ) | nindent 4 }} + {{- end }} +type: kubernetes.io/tls +data: + tls.crt: {{ $ca.Cert | b64enc }} + tls.key: {{ $ca.Key | b64enc }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ printf "%s-crt" $clientFullname }} + namespace: {{ .Release.namespace }} + labels: + {{- include "ggbridge.labels" $ | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "ggbridge.tplvalues.render" ( dict "value" $.Values.commonAnnotations "context" . ) | nindent 4 }} + {{- end }} +type: kubernetes.io/tls +data: + tls.crt: {{ $clientCert.Cert | b64enc }} + tls.key: {{ $clientCert.Key | b64enc }} + {{- end }} + + {{- range $index, $host := .Values.server.hosts -}} + {{- $tls := default dict $host.tls -}} + {{- if not (get $tls "existingSecret") }} + {{- $indexServerFullname := printf "%s-%d" $serverFullname ($index | int) }} + {{- $namespace := ternary (default $.Release.namespace $.Values.server.istio.gateway.namespace) $.Release.namespace $.Values.server.istio.enabled -}} + {{- $cert := dict }} + {{- if $.Values.server.tls.autoGenerated }} + {{- $altNames := list (printf "*.%s.%s.svc.%s" $fullname $releaseNamespace $clusterDomain) $fullname }} + {{- $cert = genSignedCert $fullname nil $altNames 365 $ca }} + {{- else }} + {{- $ca = dict "Cert" $host.tls.caCrt }} + {{- $cert = dict "Cert" $host.tls.crt "Key" $host.tls.key }} + {{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ printf "%s-crt" $indexServerFullname }} + namespace: {{ $namespace }} + labels: + {{- include "ggbridge.labels" $ | nindent 4 }} + {{- include "ggbridge.server.labels" $ | nindent 4 }} + {{- if $.Values.commonAnnotations }} + annotations: {{- include "ggbridge.tplvalues.render" ( dict "value" $.Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +type: kubernetes.io/tls +data: + ca.crt: {{ $ca.Cert | b64enc }} + tls.crt: {{ $cert.Cert | b64enc }} + tls.key: {{ $cert.Key | b64enc }} + {{- end }} + {{- end }} + +{{- end }} diff --git a/chart/templates/serviceaccount.yaml b/chart/templates/serviceaccount.yaml new file mode 100644 index 0000000..6d3bd81 --- /dev/null +++ b/chart/templates/serviceaccount.yaml @@ -0,0 +1,43 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "ggbridge.serviceAccountName" . }} + labels: + {{- include "ggbridge.labels" . | nindent 4 }} + {{- if or .Values.commonAnnotations .Values.serviceAccount.annotations }} + {{- $annotations := include "ggbridge.tplvalues.merge" ( dict "values" ( list .Values.serviceAccount.annotations .Values.commonAnnotations ) "context" . ) }} + annotations: {{- include "ggbridge.tplvalues.render" ( dict "value" $annotations "context" $) | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + {{- include "ggbridge.labels" . | nindent 4 }} + name: {{ include "ggbridge.fullname" . }} +rules: + - apiGroups: + - '' + resources: + - 'configmpas' + - 'secrets' + verbs: + - 'get' + - 'list' + - 'watch' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "ggbridge.fullname" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "ggbridge.fullname" . }} +subjects: +- kind: ServiceAccount + name: {{ include "ggbridge.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/chart/values.yaml b/chart/values.yaml new file mode 100644 index 0000000..3e0e02b --- /dev/null +++ b/chart/values.yaml @@ -0,0 +1,294 @@ +# Default values for ggbridge. + +# -- Override the default chart name +nameOverride: "" +# -- Override the default fully qualified app name +fullnameOverride: "" + +# -- Kubernetes cluster domain +clusterDomain: cluster.local + +# -- Add labels to all the deployed resources +commonLabels: {} +# -- Add annotations to all the deployed resources +commonAnnotations: {} + +# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/ +image: + # -- Image registry + registry: ghcr.io + # -- Image repository + repository: gitguardian/ggbridge + # -- Image tag + tag: "" + # -- Image digest in the way sha256:aa.... + digest: "" + + # -- Image pull policy + pullPolicy: IfNotPresent + # -- Image pull secrets + pullSecrets: [] + +serviceAccount: + # -- Specifies whether a service account should be created + create: true + # @ignored + automount: true + # -- Annotations to add to the service account + annotations: {} + # -- The name of the service account to use + name: "" + +# -- This is for setting Kubernetes Annotations to a Pod +podAnnotations: {} +# -- This is for setting Kubernetes Labels to a Pod +podLabels: {} + +podSecurityContext: + # -- Enable Pod security Context in deployments + enabled: true + # @ignored + fsGroupChangePolicy: Always + # @ignored + sysctls: [] + # @ignored + supplementalGroups: [] + # @ignored + fsGroup: 65532 + +containerSecurityContext: + # -- Enable Container security Context in deployments + enabled: true + # @ignored + seLinuxOptions: {} + # @ignored + runAsUser: 65532 + # @ignored + runAsGroup: 65532 + # @ignored + runAsNonRoot: true + # @ignored + privileged: false + # @ignored + readOnlyRootFilesystem: true + # @ignored + allowPrivilegeEscalation: false + # @ignored + capabilities: + drop: ["ALL"] + # @ignored + seccompProfile: + type: "RuntimeDefault" + +# -- Set container requests and limits +resources: + requests: + cpu: 100m + memory: 128Mi + limits: {} + +# -- Node labels for pod assignment +nodeSelector: {} +# -- Affinity for pod assignment +affinity: {} +# -- Tolerations for pod assignment +tolerations: [] + +# -- Set log level +logLevel: INFO + +server: + # -- Services parameters + services: + # -- External service + external: + annotations: {} + # -- Kubernetes Service type + type: ClusterIP + ports: + # -- WebSocket service port + ws: + port: 8000 + containerPort: 8000 + # -- Internal proxy service + proxy: + # --- override the default service name + name: "" + annotations: {} + # -- Kubernetes Service type + type: ClusterIP + ports: + socks: + port: 1080 + containerPort: 1080 + + # -- Pod Disruption Budget parameters + pdb: + # -- Enable/disable a Pod Disruption Budget creation + create: true + # -- Minimum number of pods that must still be available after the eviction + minAvailable: 1 + # -- Max number of pods that can be unavailable after the eviction + maxUnavailable: "" + + # -- Network Policy parameters + networkPolicy: + # -- Specifies whether a NetworkPolicy should be created + enabled: true + # -- When true, server will accept connections from any source + allowExternal: true + # -- Add extra ingress rules to the NetworkPolicy + extraIngress: [] + # -- Add extra egress rules to the NetworkPolicy + extraEgress: [] + # -- Labels to match to allow traffic to the proxy server from other namespaces + ingressNSMatchLabels: {} + # -- Pod labels to match to allow traffic to the proxy server from other namespaces + ingressNSPodMatchLabels: {} + + # -- RBAC parameters + rbac: + # -- CertManager role + certManager: + # -- Whether to create & use certManager RBAC resources or not + create: false + # -- Grants certManager permissions to the sepcfied subjects + subjects: [] + # - kind: ServiceAccount + # name: gim + # namespace: gim + + # -- Configure the ingress resources that allows you to connect to the server + ingress: + # -- Enable server exposure using Kubernetes Ingress API + enabled: false + # -- Specify the ingress controller + controller: "" + # -- Set the ingerss ClassName + className: "" + # -- Set ingress annotations + annotations: {} + + # -- Configure the gateway resources that allows you to connect to the server + gateway: + # -- Enable server exposure using Kubernetes Gateway API + enabled: false + gateway: + # -- Specifies whether a Gateway resource should be created alongside the routing resource (HTTPRoute) + create: true + # -- Set the gatewayClassName + className: "" + # -- Specify Gateway ports number + ports: + http: 80 + https: 443 + # -- Specify the existing gateway resources + parentRefs: [] + # -- Set gateway annotations + annotations: {} + + # -- Configure the istio resources that allows you to connect to the server + istio: + # -- Enable server exposure using Istio ingress + enabled: false + gateway: + # -- Specifies whether an Istio Gateway resource should be created alongside the Virtual Service + create: true + # -- Specify the gateway namespace + namespace: "" + # -- Set Istio Gateway selector + selector: + istio: ingress + # -- Specify Istio Gateway ports number + ports: + http: 80 + https: 443 + # -- Specify Gateway TLS options + tls: + # -- Set the exising TLS secret + credentialName: "" + minProtocolVersion: TLSV1_2 + # -- Specify the existing gateway resources for Virtual Service + gateways: [] + # -- Set Istio annotations + annotations: {} + + tls: + # -- Enable TLS traffic support + enabled: false + # -- TLS mode (can be "passthrough" or "mutual") + mode: mutual + # -- Generate automatically self-signed TLS certificates + autoGenerated: false + certManager: + # -- Manage certifcates with cert-manager + enabled: false + # -- Cert-manager issuer + issuer: selfSigned + + hosts: [] + # - hostname: server.com + # tls: + # # -- Name of an existing secret that contains the certificates + # existingSecret: "" + # existingSecretKeys: + # # -- Existing secret key storing the Certificate Authority + # caCrt: "" + # # -- Existing secret key storing the server certificate + # crt: "" + # # -- Existing secret key storing the server certificate key + # key: "" + # # -- CA certificate in PEM format + # caCrt: "" + # # -- Server certificate in PEM format + # crt: "" + # # -- Server certificate key in PEM format + # key: "" + +client: + # -- Number of pods of the deployment + replicas: 1 + updateStrategy: + # -- Customize updateStrategy of Deployment or DaemonSet + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + + # -- Pod Disruption Budget configuration + pdb: + # -- Enable/disable a Pod Disruption Budget creation + create: true + # -- Minimum number of pods that must still be available after the eviction + minAvailable: 1 + # -- Max number of pods that can be unavailable after the eviction + maxUnavailable: "" + + # -- Network Policy configuration + networkPolicy: + # -- Specifies whether a NetworkPolicy should be created + enabled: true + # -- Add extra egress rules to the NetworkPolicy + extraEgress: [] + + dnsResolver: "" + connectionMinIdle: 0 + + tls: + # -- Enable TLS traffic support + enabled: false + # -- Name of an existing secret that contains the certificates + existingSecret: "" + existingSecretKeys: + # -- Existing secret key storing the client certificate + crt: "" + # -- Existing secret key storing the client certificate key + key: "" + # -- Client certificate in PEM format + crt: "" + # -- Client certificate key in PEM format + key: "" + + # -- List of server hosts to connect to + hosts: [] + # - hostname: server.com diff --git a/demo/.env b/demo/.env new file mode 100644 index 0000000..5a28190 --- /dev/null +++ b/demo/.env @@ -0,0 +1,8 @@ +COMPOSE_PROJECT_NAME=ggbridge +COMPOSE_FILE=docker-compose.yaml + +GGBRIDGE_IMAGE=ghcr.io/gitguardian/ggbridge:unstable +GGBRIDGE_DEBUG_IMAGE=ghcr.io/gitguardian/ggbridge:unstable-debug + +PUBLIC_NETWORK_SUBNET=10.19.85.0/24 +PUBLIC_NETWORK_GATEWAY=10.19.85.1 diff --git a/demo/README.md b/demo/README.md new file mode 100644 index 0000000..b8209e7 --- /dev/null +++ b/demo/README.md @@ -0,0 +1,76 @@ +# ggbridge demo + +![ggbridge demo](../docs/assets/ggbridge_demo.drawio.png) + +Here is a demo built with [Docker Compose](https://docs.docker.com/compose/) to simulate the deployment of a **ggbridge** tunnel between a client’s network and the GitGuardian platform. The ggbridge proxy allows the GitGuardian platform to securely access the client’s Version Control Systems without requiring direct exposure of the client’s internal services to the internet. + +By using this demo, you can observe how the **ggbridge** proxy facilitates secure access to VCS repositories, demonstrating its potential for managing secure connections in real-world scenarios. The demo includes all necessary components to simulate the client network and proxy behavior, offering an easy-to-use example for testing and learning. + +## Requirements + +To run this demo, you need to have the following installed on your host: + +- [Docker](https://docs.docker.com/engine/install/) +- [Docker Compose](https://docs.docker.com/compose/install/) >= 2.26.0 + +Make sure both tools are installed and properly configured before proceeding with the demo setup. + +## Run the demo + +- Start the demo + +```shell +cd demo +docker-compose up -d +``` + +- Check that the following containers are running + +```shell +docker-compose ps +``` + +expected output: + +```shell +NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS +ggbridge-client-1 gitguardian/ggbridge:latest "/usr/bin/ggbridge c…" client 2 seconds ago Up 1 second +ggbridge-developer-1 gitguardian/ggbridge:latest-debug "sleep infinity" developer 2 seconds ago Up 1 second +ggbridge-gitguardian-1 gitguardian/ggbridge:latest-debug "/usr/sbin/nginx -c …" gitguardian 2 seconds ago Up 1 second 10.19.85.1:80->80/tcp +ggbridge-server-1 gitguardian/ggbridge:latest "/usr/bin/ggbridge s…" server 2 seconds ago Up 1 second +ggbridge-vcs-1 gitguardian/ggbridge:latest-debug "/usr/sbin/nginx -c …" vcs 2 seconds ago Up 1 second +``` + +- Try to make an HTTP request to the client’s VCS from GitGuardian + +```shell +docker-compose exec gitguardian \ + curl http://vcs.client.internal +``` + +expected output: + +```shell +curl: (6) Could not resolve host: vcs.client.internal +``` + +We cannot reach the VCS from GitGuardian because they are on two separate networks. + +- Now, Let's try making an HTTP request to the client’s VCS server from GitGuardian using the **ggbridge** proxy + +```shell +docker-compose exec gitguardian \ + curl --proxy socks5h://proxy.gitguardian.internal http://vcs.client.internal +``` + +Et voilà! The request is now routed through the **ggbirdge** proxy and the internal VCS DNS name is resolved by the proxy. You should have the following JSON response for the VCS server: + +```json +{"message": "Welcome to the VCS server", "code": 200} +``` + +- Stop the demo + +```shell +docker-compose down +``` diff --git a/demo/docker-compose.yaml b/demo/docker-compose.yaml new file mode 100644 index 0000000..6a9898f --- /dev/null +++ b/demo/docker-compose.yaml @@ -0,0 +1,116 @@ +networks: + public: + driver: bridge + driver_opts: + com.docker.network.bridge.enable_ip_masquerade: "true" + com.docker.network.bridge.host_binding_ipv4: "${PUBLIC_NETWORK_GATEWAY}" + ipam: + config: + - subnet: ${PUBLIC_NETWORK_SUBNET} + gateway: ${PUBLIC_NETWORK_GATEWAY} + + gitguardian: + driver: bridge + + client: + driver: bridge + +services: + +### GitGuadian network +####################################### + + # GitGuardian Internal Monitoring (GIM) + gitguardian: + image: ${GGBRIDGE_DEBUG_IMAGE} + entrypoint: + - /usr/sbin/nginx + command: + - -c + - /etc/nginx/nginx.conf + - -g + - daemon off; + volumes: + - ./files/gitguardian/nginx.conf:/etc/nginx/nginx.conf:ro + ports: + - name: gitguardian + target: 80 + host_ip: ${PUBLIC_NETWORK_GATEWAY} + published: 80 + app_protocol: http + mode: host + networks: + gitguardian: + public: + aliases: + # GIM public API endpoint (used by ggshield) + - api.gitguardian.public + # GIM dashboard endpoint + - dashboard.gitguardian.public + # GIM incoming webhooks endpoint + - receiver.gitguardian.public + + # ggbridge server + server: + image: ${GGBRIDGE_IMAGE} + command: server + environment: + SERVER_ADDRESS: 0.0.0.0:80 + LOG_LEVEL: DEBUG + networks: + gitguardian: + aliases: + - proxy.gitguardian.internal + public: + aliases: + # GitGuardian tunnel endpoint + - tunnel.gitguardian.public + +### Client network +####################################### + + # ggbridge client + client: + image: ${GGBRIDGE_IMAGE} + environment: + SERVER_ADDRESS: tunnel.gitguardian.public:80 + LOG_LEVEL: DEBUG + networks: + - client + - public + depends_on: + - server + + # Version Control System server + vcs: + image: ${GGBRIDGE_DEBUG_IMAGE} + entrypoint: + - /usr/sbin/nginx + command: + - -c + - /etc/nginx/nginx.conf + - -g + - daemon off; + volumes: + - ./files/vcs/nginx.conf:/etc/nginx/nginx.conf:ro + networks: + client: + aliases: + - vcs.client.internal + extra_hosts: + # GIM incoming webhooks endpoint + receiver.gitguardian.public: "${PUBLIC_NETWORK_GATEWAY}" + + # Developer host + developer: + image: ${GGBRIDGE_DEBUG_IMAGE} + command: + - sleep + - infinity + networks: + - client + extra_hosts: + # GIM public API endpoint (used by ggshield) + api.gitguardian.public: "${PUBLIC_NETWORK_GATEWAY}" + # GIM dashboard endpoint + dashboard.gitguardian.public: "${PUBLIC_NETWORK_GATEWAY}" diff --git a/demo/files/gitguardian/nginx.conf b/demo/files/gitguardian/nginx.conf new file mode 100644 index 0000000..e93ef42 --- /dev/null +++ b/demo/files/gitguardian/nginx.conf @@ -0,0 +1,48 @@ +user nginx; +worker_processes 1; + +error_log stderr notice; + +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + server { + listen 80; + server_name dashboard.gitguardian.public; + + access_log /dev/stdout; + + location / { + return 200 '{"message": "Welcome to the GitGuardian Dashboard", "code": 200}'; + add_header Content-Type application/json; + } + } + + server { + listen 80; + server_name api.gitguardian.public; + + access_log /dev/stdout; + + location / { + return 200 '{"message": "Welcome to the GitGuardian API", "code": 200}'; + add_header Content-Type application/json; + } + } + + server { + listen 80; + server_name receiver.gitguardian.public; + + access_log /dev/stdout; + + location / { + return 200 '{"message": "Welcome to the GitGuardian Receiver", "code": 200}'; + add_header Content-Type application/json; + } + } +} diff --git a/demo/files/vcs/nginx.conf b/demo/files/vcs/nginx.conf new file mode 100644 index 0000000..b57fe71 --- /dev/null +++ b/demo/files/vcs/nginx.conf @@ -0,0 +1,24 @@ +user nginx; +worker_processes 1; + +error_log stderr notice; + +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + server { + listen 80; + server_name vcs.client.internal; + + access_log /dev/stdout; + + location / { + return 200 '{"message": "Welcome to the VCS server", "code": 200}'; + add_header Content-Type application/json; + } + } +} diff --git a/docs/assets/ggbridge.drawio.png b/docs/assets/ggbridge.drawio.png new file mode 100644 index 0000000..024deee Binary files /dev/null and b/docs/assets/ggbridge.drawio.png differ diff --git a/docs/assets/ggbridge_demo.drawio.png b/docs/assets/ggbridge_demo.drawio.png new file mode 100644 index 0000000..2eda3dd Binary files /dev/null and b/docs/assets/ggbridge_demo.drawio.png differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1fb7500 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module gitguardian/ggbridge + +go 1.23.2 diff --git a/main.go b/main.go new file mode 100644 index 0000000..a471f56 --- /dev/null +++ b/main.go @@ -0,0 +1,123 @@ +package main + +import ( + "fmt" + "os" + "os/exec" +) + +const RemoteProxyProtocol = "socks5" +const RemoteProxyBind = "0.0.0.0" +const RemoteProxyPort = 1080 + +// getEnv retrieves environment variables or default values. +func getEnv(key, defaultValue string) string { + if value, exists := os.LookupEnv(key); exists { + return value + } + return defaultValue +} + +// buildClientCommand builds the command for client mode. +func buildClientCommand() []string { + serverProtocol := getEnv("SERVER_PROTOCOL", "ws") + serverAddress := os.Getenv("SERVER_ADDRESS") + connectionMinIdle := getEnv("CONNECTION_MIN_IDLE", "0") + tlsEnabled := getEnv("TLS_ENABLED", "false") + dnsResolver := os.Getenv("DNS_RESOLVER") + + if serverAddress == "" { + fmt.Println("Error: SERVER_ADDRESS is mandatory") + os.Exit(1) + } + + if tlsEnabled == "true" { + serverProtocol = "wss" + } + + cmd := []string{ + "client", + serverProtocol + "://" + serverAddress, + "--connection-min-idle", connectionMinIdle, + "--remote-to-local", fmt.Sprintf("%s://%s:%d", RemoteProxyProtocol, RemoteProxyBind, RemoteProxyPort), + } + + // Add SSL flags if enabled + if tlsEnabled == "true" { + cmd = append(cmd, "--tls-certificate", "/etc/ggbridge/certs/client.crt") + cmd = append(cmd, "--tls-private-key", "/etc/ggbridge/certs/client.key") + } + + // Add DNS resolver flag if set + if dnsResolver != "" { + cmd = append(cmd, "--dns-resolver", dnsResolver) + } + + return cmd +} + +// buildServerCommand builds the command for server mode. +func buildServerCommand() []string { + serverProtocol := getEnv("SERVER_PROTOCOL", "ws") + serverAddress := getEnv("SERVER_ADDRESS", "0.0.0.0") + tlsEnabled := getEnv("TLS_ENABLED", "false") + + if serverAddress == "" { + fmt.Println("Error: SERVER_ADDRESS is mandatory") + os.Exit(1) + } + + if tlsEnabled == "true" { + serverProtocol = "wss" + } + + cmd := []string{ + "server", + serverProtocol + "://" + serverAddress, + "--restrict-to", + fmt.Sprintf("%s://%s:%d", RemoteProxyProtocol, RemoteProxyBind, RemoteProxyPort), + } + + // Add SSL flags if enabled + if tlsEnabled == "true" { + cmd = append(cmd, "--tls-client-ca-certs", "/etc/ggbridge/certs/ca.crt") + cmd = append(cmd, "--tls-certificate", "/etc/ggbridge/certs/server.crt") + cmd = append(cmd, "--tls-private-key", "/etc/ggbridge/certs/server.key") + } + + return cmd +} + +func run(args []string) { + cmd := exec.Command("wstunnel", args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + fmt.Println("Error running ggbridge:", err) + os.Exit(1) + } +} + +func main() { + if len(os.Args) < 2 { + fmt.Println("Error: Missing argument 'server' or 'client'") + os.Exit(1) + } + + cmd := []string{ + "--log-lvl", + getEnv("LOG_LEVEL", "INFO"), + } + + mode := os.Args[1] + + if mode == "client" { + cmd = append(cmd, buildClientCommand()...) + } else if mode == "server" { + cmd = append(cmd, buildServerCommand()...) + } else { + fmt.Println("Error: You must run 'client' or 'server' mode") + os.Exit(1) + } + run(cmd) +}