From 82a61aef0924488bcb5452cb2624979764afcb90 Mon Sep 17 00:00:00 2001 From: Marc Brugger Date: Sat, 6 Apr 2024 11:46:12 +0200 Subject: [PATCH] support api tls mode (#329) Add support api tls mode --- .github/workflows/e2e.yaml | 6 +- README.md | 15 +++ pkg/sync/http.go | 16 +++- pkg/types/types.go | 26 ++++++ pkg/types/types_test.go | 31 +++++++ testdata/e2e/bin/show-sync-metrics.sh | 2 +- testdata/e2e/bin/wait-for-sync.sh | 2 +- testdata/e2e/templates/configmap-certs.yaml | 93 +++++++++++++++++++ .../e2e/templates/configmap-sync-env.yaml | 3 + .../templates/pod-adguardhome-sync-env.yaml | 7 ++ 10 files changed, 195 insertions(+), 6 deletions(-) create mode 100644 testdata/e2e/templates/configmap-certs.yaml diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index eaf8ee5..06faa04 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -15,7 +15,9 @@ jobs: matrix: build: - mode: env + protocol: https - mode: file + protocol: http steps: - name: Checkout uses: actions/checkout@v4 @@ -32,7 +34,7 @@ jobs: - name: Install Helm Chart run: ./testdata/e2e/bin/install-chart.sh ${{ matrix.build.mode }} - name: Wait for sync to finish - run: ./testdata/e2e/bin/wait-for-sync.sh + run: ./testdata/e2e/bin/wait-for-sync.sh ${{ matrix.build.protocol }} - name: Show origin Logs run: ./testdata/e2e/bin/show-origin-logs.sh - name: Show Replica Logs @@ -40,6 +42,6 @@ jobs: - name: Show Sync Logs run: ./testdata/e2e/bin/show-sync-logs.sh - name: Show Sync Metrics - run: ./testdata/e2e/bin/show-sync-metrics.sh + run: ./testdata/e2e/bin/show-sync-metrics.sh ${{ matrix.build.protocol }} - name: Read latest replica config run: ./testdata/e2e/bin/read-latest-replica-config.sh diff --git a/README.md b/README.md index 23d1462..13ed578 100644 --- a/README.md +++ b/README.md @@ -173,6 +173,12 @@ services: # API_DARK_MODE: true # API_USERNAME: admin # API_PASSWORD: secret + # the directory of the provided tls certs + # API_TLS_CERT_DIR: /path/to/certs + # the name of the cert file (default: tls.crt) + # API_TLS_CERT_NAME: foo.crt + # the name of the key file (default: tls.key) + # API_TLS_KEY_NAME: bar.key # Configure sync features; by default all features are enabled. # FEATURES_GENERAL_SETTINGS: true @@ -244,6 +250,15 @@ api: # scrapeInterval: 30s # queryLogLimit: 10000 + # enable tls for the api server + # tls: + # # the directory of the provided tls certs + # certDir: /path/to/certs + # # the name of the cert file (default: tls.crt) + # certName: foo.crt + # # the name of the key file (default: tls.key) + # keyName: bar.key + # Configure sync features; by default all features are enabled. features: generalSettings: true diff --git a/pkg/sync/http.go b/pkg/sync/http.go index f5e1d01..f1a7c32 100644 --- a/pkg/sync/http.go +++ b/pkg/sync/http.go @@ -55,7 +55,12 @@ func (w *worker) handleStatus(c *gin.Context) { } func (w *worker) listenAndServe() { - l.With("port", w.cfg.API.Port).Info("Starting API server") + sl := l.With("port", w.cfg.API.Port) + if w.cfg.API.TLS.Enabled() { + c, k := w.cfg.API.TLS.Certs() + sl = sl.With("tls-cert", c).With("tls-key", k) + } + sl.Info("Starting API server") ctx, cancel := context.WithCancel(context.Background()) @@ -85,7 +90,14 @@ func (w *worker) listenAndServe() { } go func() { - if err := httpServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { + var err error + if w.cfg.API.TLS.Enabled() { + err = httpServer.ListenAndServeTLS(w.cfg.API.TLS.Certs()) + } else { + err = httpServer.ListenAndServe() + } + + if !errors.Is(err, http.ErrServerClosed) { l.With("error", err).Fatalf("HTTP server ListenAndServe") } }() diff --git a/pkg/types/types.go b/pkg/types/types.go index 416cb3f..4c7d90b 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -3,6 +3,7 @@ package types import ( "fmt" "net/url" + "path/filepath" "strings" "time" @@ -35,6 +36,7 @@ type API struct { Password string `json:"password,omitempty" yaml:"password,omitempty" env:"API_PASSWORD"` DarkMode bool `json:"darkMode,omitempty" yaml:"darkMode,omitempty" env:"API_DARK_MODE"` Metrics Metrics `json:"metrics,omitempty" yaml:"metrics,omitempty" env:"API_METRICS"` + TLS TLS `json:"tls,omitempty" yaml:"tls,omitempty" env:"API_TLS"` } // Metrics configuration @@ -44,6 +46,30 @@ type Metrics struct { QueryLogLimit int `json:"queryLogLimit,omitempty" yaml:"queryLogLimit,omitempty" env:"API_METRICS_QUERY_LOG_LIMIT"` } +// TLS configuration +type TLS struct { + CertDir string `json:"certDir,omitempty" yaml:"certDir,omitempty" env:"API_TLS_CERT_DIR"` + CertName string `json:"certName,omitempty" yaml:"certName,omitempty" env:"API_TLS_CERT_NAME"` + KeyName string `json:"keyName,omitempty" yaml:"keyName,omitempty" env:"API_TLS_KEY_NAME"` +} + +func (t TLS) Enabled() bool { + return strings.TrimSpace(t.CertDir) != "" +} + +func (t TLS) Certs() (cert string, key string) { + cert = filepath.Join(t.CertDir, defaultIfEmpty(t.CertName, "tls.crt")) + key = filepath.Join(t.CertDir, defaultIfEmpty(t.KeyName, "tls.key")) + return +} + +func defaultIfEmpty(val string, fallback string) string { + if strings.TrimSpace(val) == "" { + return fallback + } + return val +} + // Mask maks username and password func (a *API) Mask() { a.Username = mask(a.Username) diff --git a/pkg/types/types_test.go b/pkg/types/types_test.go index 2364565..405ee03 100644 --- a/pkg/types/types_test.go +++ b/pkg/types/types_test.go @@ -96,4 +96,35 @@ var _ = Describe("Types", func() { }) }) }) + Context("TLS", func() { + var t TLS + BeforeEach(func() { + t = TLS{ + CertDir: "/path/to/certs", + } + }) + Context("Enabled", func() { + It("should use enabled", func() { + Ω(t.Enabled()).Should(BeTrue()) + }) + It("should use disabled", func() { + t.CertDir = " " + Ω(t.Enabled()).Should(BeFalse()) + }) + }) + Context("Certs", func() { + It("should use default crt and key", func() { + crt, key := t.Certs() + Ω(crt).Should(Equal("/path/to/certs/tls.crt")) + Ω(key).Should(Equal("/path/to/certs/tls.key")) + }) + It("should use custom crt and key", func() { + t.CertName = "foo.crt" + t.KeyName = "bar.key" + crt, key := t.Certs() + Ω(crt).Should(Equal("/path/to/certs/foo.crt")) + Ω(key).Should(Equal("/path/to/certs/bar.key")) + }) + }) + }) }) diff --git a/testdata/e2e/bin/show-sync-metrics.sh b/testdata/e2e/bin/show-sync-metrics.sh index b403566..91eae81 100755 --- a/testdata/e2e/bin/show-sync-metrics.sh +++ b/testdata/e2e/bin/show-sync-metrics.sh @@ -7,5 +7,5 @@ sleep 30 echo "## Pod adguardhome-sync metrics" >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY -curl http://localhost:9090/metrics -s >> $GITHUB_STEP_SUMMARY +curl ${1}://localhost:9090/metrics -s -k >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY diff --git a/testdata/e2e/bin/wait-for-sync.sh b/testdata/e2e/bin/wait-for-sync.sh index 8bc4946..f5c4602 100755 --- a/testdata/e2e/bin/wait-for-sync.sh +++ b/testdata/e2e/bin/wait-for-sync.sh @@ -6,7 +6,7 @@ kubectl port-forward pod/adguardhome-sync 9090:9090 & for i in {1..6}; do sleep 10 - RUNNING=$(curl http://localhost:9090/api/v1/status -s | jq -r .syncRunning) + RUNNING=$(curl ${1}://localhost:9090/api/v1/status -s -k | jq -r .syncRunning) echo "SyncRunning = ${RUNNING}" if [[ "${RUNNING}" == "false" ]]; then exit 0 diff --git a/testdata/e2e/templates/configmap-certs.yaml b/testdata/e2e/templates/configmap-certs.yaml new file mode 100644 index 0000000..a08bd70 --- /dev/null +++ b/testdata/e2e/templates/configmap-certs.yaml @@ -0,0 +1,93 @@ +{{- if eq .Values.mode "env" }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: certs + namespace: {{ .Release.Namespace }} +data: + my.crt: | + -----BEGIN CERTIFICATE----- + MIIE7zCCAtcCFFthw5GxzLaZC/pTTlbwju6vYr9pMA0GCSqGSIb3DQEBCwUAMDMx + CzAJBgNVBAYTAkNIMRMwEQYDVQQIDApTb21lLVN0YXRlMQ8wDQYDVQQKDAZiYWtp + dG8wIBcNMjQwNDA0MDUxOTMyWhgPMjEyNDAzMTEwNTE5MzJaMDMxCzAJBgNVBAYT + AkNIMRMwEQYDVQQIDApTb21lLVN0YXRlMQ8wDQYDVQQKDAZiYWtpdG8wggIiMA0G + CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCenHJ0SunHqdtBdG8d5Uy8y5KESOPE + GCpcjNetSR53binlV3QjVckrA+6tHQBR3yXpSgHWU424fR3eTdHsbS9/8/pAnV0V + CAaUntbCr5CJ8ww2zRlGSz6IlxkwJkqx4crmYUcTm6XNcEm5kSoBmFR1s8TiQAQ8 + CnJyt4m3S5jSKnexAMi2l2LP4F5P5MwLHdpl2sfRYydWOoJty9nSYuRxarv7G7e3 + N102voL3W2F9Dk8QbKF1Kzxcfmh+4twNz/YfUxxB3tggrCGHfMu4EcvMrFCkKhNv + sFcG9aZ0gNj4x54vCOMK+LuVd8JZQMcGBdcxZUq+q4nyj4dxFDKSUjZ+nVjdbuab + dk72dVbv/r2j6aGFuZDwPeYrKOyGCUlzyPvtX3qDjrebvalkrua0ApJLTiUntQK4 + E+8P658GUzwqhKi6COEmgJgZO1+xIhO68OzPS6qj8OvgOw97VJqWsnTkDeu5Xd3E + qW37lk62EQ75FCLaWAmyBcxTDlIWNfwvSlI57Iyw0ec9ziN4JP+6pX3QTcj69Kvc + ERExUNGWcyQVYiCFigBqL/UtGO73PA4kb8P6Zef0oTx7siw4ysImvQ/RHvbArXPE + iqxg/afjJz/w7fTTwk9DRW12hiWj7+ojZMcoYCj1PcOlmZvchwZRdN8Lp37wm11I + EYxdoBQVRWk0vwIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQB6H+z2IEtiBTOIFnYJ + ++CvZWw0CLPSpuFTwpDRIVUbrfhpFoXMwb6ungaqrGNXv3H4HQXl2EwXwZomsy2K + /pjk/0rHFdleEtJ4beSv44ToGCL+RqeU38sGuxCDQBCxIqGcbkErkqEYJqaSEV9I + elKjBA5PEy+WO/P+J5UYU6aY+59C+ZHrZ8HzoPUGrZ6ESu9Wm/9iOpUn4DWFzOOu + E/14QQqpHzHRvpbfOb/dsnEYv7JoiOaXhOedKmx5k+bLhNCXOJiqyqphNp0Io3Fr + 8i6IxfvOo/6nw9sQ02E6PjNyC5bRq7TUrqffTcCRq7EYhJoK43V7nzLpUBItBF7K + FszFnvBxvgPtUdbhs6O8U8pqoPw1vCnoJLnerGfN1fEclV7Sbf3d1hCYXtHzmpZM + qflEkJIPSgkr8dyi2JjSQR5AJUtB5/VAZwBATNKNYINfnEVzyle+64iVaKteRt9e + yeLoy/ktTZSlQbb7/qg4NtQx9SA5PlYVMLj20S5HwStGQhLIb5lrwzkHNUZmkDWr + LUX1ufOfhNpCH2Egn078pyEn88B5iRTga2auCk3siOs9H3wmbT2r57E7ddX3l9Sf + MzO/8HyfNoa2RUTnJHdyduAT7VytyJSSRmfeapw9ITngZHGmpPhisKhxb6IoZm0J + j10kyQuFfNcZKlNa3D7XFvqzWQ== + -----END CERTIFICATE----- + + my.key: | + -----BEGIN PRIVATE KEY----- + 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 + r8Ww1tZIoBx/s50i7X1JFyuXp0TWQsI= + -----END PRIVATE KEY----- + +{{- end }} diff --git a/testdata/e2e/templates/configmap-sync-env.yaml b/testdata/e2e/templates/configmap-sync-env.yaml index 312724c..468c8a5 100644 --- a/testdata/e2e/templates/configmap-sync-env.yaml +++ b/testdata/e2e/templates/configmap-sync-env.yaml @@ -8,6 +8,9 @@ data: API_PORT: '9090' API_METRICS_ENABLED: 'true' API_METRICS_SCRAPE_INTERVAL: '30s' + API_TLS_CERT_DIR: '/certs' + API_TLS_CERT_NAME: 'my.crt' + API_TLS_KEY_NAME: 'my.key' LOG_FORMAT: 'json' ORIGIN_URL: 'http://service-origin.{{ $.Release.Namespace }}.svc.cluster.local:3000' ORIGIN_PASSWORD: 'password' diff --git a/testdata/e2e/templates/pod-adguardhome-sync-env.yaml b/testdata/e2e/templates/pod-adguardhome-sync-env.yaml index a7227aa..87b3f23 100644 --- a/testdata/e2e/templates/pod-adguardhome-sync-env.yaml +++ b/testdata/e2e/templates/pod-adguardhome-sync-env.yaml @@ -26,5 +26,12 @@ spec: envFrom: - configMapRef: name: sync-conf + volumeMounts: + - name: certs + mountPath: /certs restartPolicy: Never + volumes: + - name: certs + configMap: + name: certs {{- end }}