From a83e58307e917a0212c932f1f99d0c869cb42059 Mon Sep 17 00:00:00 2001 From: Bojan Raic Date: Sat, 17 Feb 2024 16:04:54 +0100 Subject: [PATCH] Code optimizations & CiliumNetworkPolicy Support --- .github/workflows/docker-builds.yaml | 29 +++-------- .github/workflows/docker-nightly.yaml | 26 +++------- .github/workflows/gh-pages.yaml | 7 ++- artifacthub-repo.yml | 2 +- charts/bwsm-eso-provider/Chart.yaml | 4 +- charts/bwsm-eso-provider/README.md | 4 +- .../templates/cluster-secret-store.yaml | 2 +- .../templates/network-policy.yaml | 48 +++++++++++++++--- charts/bwsm-eso-provider/values.yaml | 5 +- src/Dockerfile | 1 - src/controllers/secretidController.js | 2 +- src/index.js | 50 ++++++++++++------- src/package-lock.json | 4 +- src/package.json | 4 +- src/server.js | 50 +++++++++---------- src/util/config.json | 11 ---- src/util/constants.js | 22 +++++--- 17 files changed, 145 insertions(+), 126 deletions(-) delete mode 100644 src/util/config.json diff --git a/.github/workflows/docker-builds.yaml b/.github/workflows/docker-builds.yaml index c52ca1f..6988925 100644 --- a/.github/workflows/docker-builds.yaml +++ b/.github/workflows/docker-builds.yaml @@ -75,21 +75,6 @@ jobs: images: ${{ env.DOCKER_IMAGE_NAME }}:${{ inputs.image_tag }}-amd64, ${{ env.DOCKER_IMAGE_NAME }}:${{ inputs.image_tag }}-arm64 push: true - - name: Cleanup Internediate Manual Platform-Specific Images - if: ${{ github.event_name == 'workflow_dispatch' }} - run: | - REPO=${{ env.DOCKER_IMAGE_NAME }} - DH_USER=${{ env.DOCKERHUB_USERNAME }} - DH_PASS=${{ env.DOCKERHUB_PAT }} - JWT=$(curl -s -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -d "{\"username\":\"${DH_USER}\",\"password\":\"${DH_PASS}\"}" -L 'https://hub.docker.com/v2/users/login' | jq -r '.token') - - TAG=${{ inputs.image_tag }}-arm64 - curl -s "https://hub.docker.com/v2/repositories/${REPO}/tags/${TAG}/" -X DELETE -H "Authorization: JWT ${JWT}" - - TAG=${{ inputs.image_tag }}-amd64 - curl -s "https://hub.docker.com/v2/repositories/${REPO}/tags/${TAG}/" -X DELETE -H "Authorization: JWT ${JWT}" - - - name: Tagged Build/Push - amd64 if: ${{ github.event_name != 'workflow_dispatch' }} uses: docker/build-push-action@v5 @@ -131,16 +116,16 @@ jobs: images: ${{ env.DOCKER_IMAGE_NAME }}:${{ github.ref_name }}-amd64, ${{ env.DOCKER_IMAGE_NAME }}:${{ github.ref_name }}-arm64 push: true - - name: Cleanup Internediate Tagged Platform-Specific Images - if: ${{ github.event_name != 'workflow_dispatch' }} + - name: Cleanup Internediate Images run: | REPO=${{ env.DOCKER_IMAGE_NAME }} DH_USER=${{ env.DOCKERHUB_USERNAME }} DH_PASS=${{ env.DOCKERHUB_PAT }} JWT=$(curl -s -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -d "{\"username\":\"${DH_USER}\",\"password\":\"${DH_PASS}\"}" -L 'https://hub.docker.com/v2/users/login' | jq -r '.token') + tags=$(curl -L -s 'https://hub.docker.com/v2/repositories/${REPO}/tags?page_size=1024' | jq -r '.results[].name' | grep -E '\-arm64|\-amd64') - TAG=${{ github.ref_name }}-amd64 - curl -s "https://hub.docker.com/v2/repositories/${REPO}/tags/${TAG}/" -X DELETE -H "Authorization: JWT ${JWT}" - - TAG=${{ github.ref_name }}-arm64 - curl -s "https://hub.docker.com/v2/repositories/${REPO}/tags/${TAG}/" -X DELETE -H "Authorization: JWT ${JWT}" \ No newline at end of file + # Iterate over tags + for tag in $tags; do + echo "Deleting intermediate tag: $tag" + curl -s "https://hub.docker.com/v2/repositories/${REPO}/tags/${tag}/" -X DELETE -H "Authorization: JWT ${JWT}" + done \ No newline at end of file diff --git a/.github/workflows/docker-nightly.yaml b/.github/workflows/docker-nightly.yaml index b5ec359..284f562 100644 --- a/.github/workflows/docker-nightly.yaml +++ b/.github/workflows/docker-nightly.yaml @@ -59,19 +59,6 @@ jobs: images: ${{ env.DOCKER_IMAGE_NAME }}:nightly-amd64,${{ env.DOCKER_IMAGE_NAME }}:nightly-arm64 push: true - - name: Cleanup Internediate Nightly Platform-Specific Images - run: | - REPO=${{ env.DOCKER_IMAGE_NAME }} - DH_USER=${{ env.DOCKERHUB_USERNAME }} - DH_PASS=${{ env.DOCKERHUB_PAT }} - JWT=$(curl -s -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -d "{\"username\":\"${DH_USER}\",\"password\":\"${DH_PASS}\"}" -L 'https://hub.docker.com/v2/users/login' | jq -r '.token') - - TAG=nightly-arm64 - curl -s "https://hub.docker.com/v2/repositories/${REPO}/tags/${TAG}/" -X DELETE -H "Authorization: JWT ${JWT}" - - TAG=nightly-amd64 - curl -s "https://hub.docker.com/v2/repositories/${REPO}/tags/${TAG}/" -X DELETE -H "Authorization: JWT ${JWT}" - - name: Most Recent Semver Tag uses: actions-ecosystem/action-get-latest-tag@v1 with: @@ -111,15 +98,16 @@ jobs: images: ${{ env.DOCKER_IMAGE_NAME }}:${{ steps.most-recent-tag.outputs.tag }}-amd64, ${{ env.DOCKER_IMAGE_NAME }}:${{ steps.most-recent-tag.outputs.tag }}-arm64 push: true - - name: Cleanup Internediate Tagged Platform-Specific Images + - name: Cleanup Internediate Images run: | REPO=${{ env.DOCKER_IMAGE_NAME }} DH_USER=${{ env.DOCKERHUB_USERNAME }} DH_PASS=${{ env.DOCKERHUB_PAT }} JWT=$(curl -s -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -d "{\"username\":\"${DH_USER}\",\"password\":\"${DH_PASS}\"}" -L 'https://hub.docker.com/v2/users/login' | jq -r '.token') + tags=$(curl -L -s 'https://hub.docker.com/v2/repositories/${REPO}/tags?page_size=`1024`' | jq -r '.results[].name' | grep -E '\-arm64|\-amd64') - TAG=${{ steps.most-recent-tag.outputs.tag }}-amd64 - curl -s "https://hub.docker.com/v2/repositories/${REPO}/tags/${TAG}/" -X DELETE -H "Authorization: JWT ${JWT}" - - TAG=${{ steps.most-recent-tag.outputs.tag }}-amd64 - curl -s "https://hub.docker.com/v2/repositories/${REPO}/tags/${TAG}/" -X DELETE -H "Authorization: JWT ${JWT}" + # Iterate over tags + for tag in $tags; do + echo "Deleting intermediate tag: $tag" + curl -s "https://hub.docker.com/v2/repositories/${REPO}/tags/${tag}/" -X DELETE -H "Authorization: JWT ${JWT}" + done \ No newline at end of file diff --git a/.github/workflows/gh-pages.yaml b/.github/workflows/gh-pages.yaml index 04d3103..d31fa61 100644 --- a/.github/workflows/gh-pages.yaml +++ b/.github/workflows/gh-pages.yaml @@ -1,10 +1,9 @@ name: Deploy GH Pages on: - release: - types: - - published - - created + push: + tags: + - 'bwsm-eso-provider-*' paths: - README.md - charts/**/README.md diff --git a/artifacthub-repo.yml b/artifacthub-repo.yml index 4782dce..b45c564 100644 --- a/artifacthub-repo.yml +++ b/artifacthub-repo.yml @@ -7,4 +7,4 @@ owners: ignore: - name: bwsm-eso-provider - version: beta* + version: *-beta diff --git a/charts/bwsm-eso-provider/Chart.yaml b/charts/bwsm-eso-provider/Chart.yaml index 0c052dc..d2d9a1f 100644 --- a/charts/bwsm-eso-provider/Chart.yaml +++ b/charts/bwsm-eso-provider/Chart.yaml @@ -2,9 +2,9 @@ apiVersion: v2 name: bwsm-eso-provider description: Helm chart to use Bitwarden Secrets Manaager (BWSM) as a Provider for External Secrets Operator (ESO) type: application -version: 0.0.9 +version: 0.1.0-beta # renovate: image=bojanraic/bwsm-eso -appVersion: "0.0.9" +appVersion: "0.1.0-beta" icon: https://bojanraic.github.io/bitwarden-secrets-manager-eso/chart-icon.png maintainers: - name: "Bojan Raic" diff --git a/charts/bwsm-eso-provider/README.md b/charts/bwsm-eso-provider/README.md index 33ff438..38067a5 100644 --- a/charts/bwsm-eso-provider/README.md +++ b/charts/bwsm-eso-provider/README.md @@ -1,6 +1,6 @@ # bwsm-eso-provider -![Version: 0.0.9](https://img.shields.io/badge/Version-0.0.9-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.0.9](https://img.shields.io/badge/AppVersion-0.0.9-informational?style=flat-square) +![Version: 0.1.0-beta](https://img.shields.io/badge/Version-0.1.0--beta-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.1.0-beta](https://img.shields.io/badge/AppVersion-0.1.0--beta-informational?style=flat-square) Helm chart to use Bitwarden Secrets Manaager (BWSM) as a Provider for External Secrets Operator (ESO) @@ -23,6 +23,8 @@ Helm chart to use Bitwarden Secrets Manaager (BWSM) as a Provider for External S | bwsm_eso_provider.auth.existingSecret | string | `""` | use an existing secret for bitwarden secrets manager credentials; ignores above credentials if this is set | | bwsm_eso_provider.auth.secretKeys.accessToken | string | `"BWS_ACCESS_TOKEN"` | secret key for bitwarden secrets manager access token to use to authenticate BWS CLI and fetch secrets in the pod; do not change unless customizing the Express.JS wrapper code | | bwsm_eso_provider.create_cluster_secret_store | bool | `true` | if set to True, we'll create a cluster-wide Cluster Secret Store see: https://external-secrets.io/latest/introduction/overview/#clustersecretstore | +| bwsm_eso_provider.eso_namespace | string | `"external-secrets"` | specify namespace where ESO is installed | +| bwsm_eso_provider.network_policy.cilium | bool | `false` | if Cilium is used (for creating a CiliumNetworkPolicy) | | bwsm_eso_provider.network_policy.enabled | bool | `true` | enable a network policy between BWSM pod(s) and ESO namespace; highly recommended as the Express.js App provides no authentication | | bwsm_eso_provider.network_policy.labels | object | `{"app.kubernetes.io/name":"external-secrets"}` | specify the labels to match against for the network policy | | bwsm_eso_provider.sample_secret.create | bool | `false` | create a sample external secret for quick verification; works only when create_cluster_secret_store is True | diff --git a/charts/bwsm-eso-provider/templates/cluster-secret-store.yaml b/charts/bwsm-eso-provider/templates/cluster-secret-store.yaml index 0c9c94b..42442ce 100644 --- a/charts/bwsm-eso-provider/templates/cluster-secret-store.yaml +++ b/charts/bwsm-eso-provider/templates/cluster-secret-store.yaml @@ -3,7 +3,7 @@ apiVersion: external-secrets.io/v1beta1 kind: ClusterSecretStore metadata: - name: bwsm-cluster-store + name: {{ .Release.Name }}-cluster-store spec: provider: webhook: diff --git a/charts/bwsm-eso-provider/templates/network-policy.yaml b/charts/bwsm-eso-provider/templates/network-policy.yaml index fcdbe1b..ea39c9c 100644 --- a/charts/bwsm-eso-provider/templates/network-policy.yaml +++ b/charts/bwsm-eso-provider/templates/network-policy.yaml @@ -1,18 +1,50 @@ -{{- if .Values.bwsm_eso_provider.network_policy.enabled }} +{{- if and .Values.bwsm_eso_provider.network_policy.enabled (not .Values.bwsm_eso_provider.network_policy.cilium) }} --- kind: NetworkPolicy apiVersion: networking.k8s.io/v1 metadata: - name: external-secrets-operator-ns-to-{{ .Release.Name }}-policy + name: eso-ns-to-{{ .Release.Name }}-policy namespace: {{ .Release.Namespace }} spec: - podSelector: - matchLabels: - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/name: {{ .Release.Name }} + podSelector: {} + policyTypes: + - Ingress + - Egress ingress: - from: - - namespaceSelector: - matchLabels: + - namespaceSelector: + matchExpressions: + - key: namespace + operator: In + values: + - {{ .Values.bwsm_eso_provider.eso_namespace }} + ports: + - port: 8080 + egress: + - to: + - podSelector: + matchLabels: + {{ toYaml .Values.bwsm_eso_provider.network_policy.labels | indent 2 }} +{{- end }} +{{- if and .Values.bwsm_eso_provider.network_policy.enabled .Values.bwsm_eso_provider.network_policy.cilium }} +--- +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: eso-ns-to-{{ .Release.Name }}-policy + namespace: {{ .Release.Namespace }} +spec: + endpointSelector: {} + ingress: + - fromEndpoints: + - matchLabels: + io.kubernetes.pod.namespace: {{ .Values.bwsm_eso_provider.eso_namespace }} + toPorts: + - ports: + - port: "8080" + egress: + - toEndpoints: + - matchLabels: {{ toYaml .Values.bwsm_eso_provider.network_policy.labels | indent 2 }} + {{- end }} diff --git a/charts/bwsm-eso-provider/values.yaml b/charts/bwsm-eso-provider/values.yaml index f8c7f10..a309090 100644 --- a/charts/bwsm-eso-provider/values.yaml +++ b/charts/bwsm-eso-provider/values.yaml @@ -6,6 +6,8 @@ bwsm_eso_provider: # -- if set to True, we'll create a cluster-wide Cluster Secret Store # see: https://external-secrets.io/latest/introduction/overview/#clustersecretstore create_cluster_secret_store: true + # -- specify namespace where ESO is installed + eso_namespace: external-secrets auth: # -- bitwarden secrets manager access token to use to authenticate BWS CLI and fetch secrets in the pod; ignored if existingSecret is set accessToken: "" @@ -32,7 +34,8 @@ bwsm_eso_provider: # -- specify the labels to match against for the network policy labels: app.kubernetes.io/name: external-secrets - + # -- if Cilium is used (for creating a CiliumNetworkPolicy) + cilium: false # -- number of replicas to deploy replicaCount: 1 diff --git a/src/Dockerfile b/src/Dockerfile index 7a581c6..ae669af 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -32,7 +32,6 @@ WORKDIR $WORKDIR COPY --from=base $WORKDIR/ $WORKDIR/ COPY --from=bws /bin/sh /bin/sh -COPY --from=bws /bin/wget /bin/wget COPY --from=bws $WORKDIR/bws $WORKDIR/bws EXPOSE $PORT diff --git a/src/controllers/secretidController.js b/src/controllers/secretidController.js index 63462d9..665dc0b 100644 --- a/src/controllers/secretidController.js +++ b/src/controllers/secretidController.js @@ -1,5 +1,5 @@ import * as service from '../services/bwsService.js'; -export function getById(req, res, next) { +export async function getById(req, res, next) { service.getById(req, res); }; diff --git a/src/index.js b/src/index.js index 9996f2b..35e236d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,27 +1,39 @@ import server from "./server.js"; -const env = process.env.NODE_ENV ? process.env.NODE_ENV : "production"; +const env = process.env.NODE_ENV || "production"; // Use a default value if NODE_ENV is not set -server.deploy(env).catch((err) => { - console.log(err); -}); +// Start the server +async function startServer() { + try { + await server.deploy(env); + console.log(`Server deployed successfully in ${env} mode.`); + } catch (error) { + console.error(`Error deploying server: ${error}`); + process.exit(1); + } +} + +startServer(); -// quit on ctrl-c when running docker in terminal -process.on("SIGINT", function onSigint() { - console.log( - `[${new Date().toISOString()}] Got SIGINT (aka ctrl-c in docker). Graceful shutdown` - ); - shutdown(); +// Graceful shutdown on SIGINT (Ctrl+C) or SIGTERM (docker container stop) +process.on("SIGINT", () => { + console.log(`[${new Date().toISOString()}] Got SIGINT (Ctrl+C). Graceful shutdown.`); + shutdown(); }); -// quit properly on docker stop -process.on("SIGTERM", function onSigterm() { - console.log( - `[${new Date().toISOString()}] Got SIGTERM (docker container stop). Graceful shutdown` - ); - shutdown(); +process.on("SIGTERM", () => { + console.log(`[${new Date().toISOString()}] Got SIGTERM (docker container stop). Graceful shutdown.`); + shutdown(); }); -const shutdown = () => { - server.undeploy(); -}; +// Function to gracefully shutdown the server +async function shutdown() { + try { + server.undeploy(); + console.log("Server shutdown completed."); + process.exit(0); + } catch (error) { + console.error(`Error during server shutdown: ${error}`); + process.exit(1); + } +} diff --git a/src/package-lock.json b/src/package-lock.json index 729d5cc..e6a0731 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "bwsm-eso", - "version": "0.0.9", + "version": "0.1.0-beta", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bwsm-eso", - "version": "0.0.9", + "version": "0.1.0-beta", "license": "AGPL-3.0-only", "dependencies": { "@oas-tools/core": "^3.1.0", diff --git a/src/package.json b/src/package.json index 943bfbd..ed78c4d 100644 --- a/src/package.json +++ b/src/package.json @@ -1,6 +1,6 @@ { "name": "bwsm-eso", - "version": "0.0.9", + "version": "0.1.0-beta", "description": "This is a Bitwarden Secrets Manager ESO wrapper based on the OpenAPI 3.0 specification.", "license": "AGPL-3.0-only", "type": "module", @@ -27,4 +27,4 @@ "devDependencies": { "@types/express": "^4.17.21" } -} \ No newline at end of file +} diff --git a/src/server.js b/src/server.js index c21fdc1..8bb1c7d 100644 --- a/src/server.js +++ b/src/server.js @@ -4,6 +4,7 @@ import fs from "fs/promises"; import { initialize } from "@oas-tools/core"; import constants from "./util/constants.js"; +// Function to read and parse JSON file async function readAndParse(filePath) { try { const configFileContent = await fs.readFile(filePath, 'utf8'); @@ -14,38 +15,36 @@ async function readAndParse(filePath) { } } +// Deploy function to start the server const deploy = async () => { try { - // application name and version + // Read package.json for application name and version const packageJSON = await readAndParse('./package.json'); - const appName = packageJSON.name; - const appVersion = packageJSON.version; - - // application configuration - const defaultConfigPath = constants.defaults.CONFIG_FILE_PATH; - const configPath = process.env.CONFIG_FILE_PATH || defaultConfigPath; - const config = await readAndParse(configPath); - const serverPort = config?.server?.port || constants.defaults.SERVER_PORT; - const appMessage = config.server.defaultMessage || constants.defaults.DEFAULT_MESSAGE; - config.oasFile = constants.defaults.OAS_FILE_PATH; - - // application setup + const { name: appName, version: appVersion } = packageJSON; + + // Extract server configuration + const serverPort = constants.config?.server?.port || constants.defaults.SERVER_PORT; + const appMessage = constants.config?.server?.defaultMessage || constants.defaults.DEFAULT_MESSAGE; + constants.config.oasFile = constants.defaults.OAS_FILE_PATH; + + // Setup Express application const app = express(); - app.use(express.json(config?.server?.express?.json?.config || constants.defaults.EXPRESS_JSON_CONFIG)); + app.use(express.json(constants.config?.server?.express?.json?.config || constants.defaults.EXPRESS_JSON_CONFIG)); app.get('/', (req, res) => { - res.send({ "name": appName, "version": appVersion, "url": req.originalUrl, "message": appMessage }); + res.send({ name: appName, version: appVersion, url: req.originalUrl, message: appMessage }); }); - // application initialization - initialize(app, config).then(() => { - http.createServer(app).listen(serverPort, () => { - console.log(`\n[${appName} v${appVersion}] Running at http://localhost:${serverPort}`); + // Initialize application + await initialize(app, constants.config); + + // Start HTTP server + http.createServer(app).listen(serverPort, () => { + console.log(`\n[${appName} v${appVersion}] Running at http://localhost:${serverPort}`); + console.log(`________________________________________________________________`); + if (!constants.config?.middleware?.swagger?.disable) { + console.log(`API docs (Swagger UI) available on http://localhost:${serverPort}/docs`); console.log(`________________________________________________________________`); - if (!config?.middleware?.swagger?.disable) { - console.log(`API docs (Swagger UI) available on http://localhost:${serverPort}/docs`); - console.log(`________________________________________________________________`); - } - }); + } }); } catch (error) { console.error(`Error during deployment: ${error.message}`); @@ -53,8 +52,9 @@ const deploy = async () => { } } +// Function to gracefully shutdown the server const undeploy = () => { process.exit(); }; -export default { deploy, undeploy } +export default { deploy, undeploy }; diff --git a/src/util/config.json b/src/util/config.json deleted file mode 100644 index 4e81e84..0000000 --- a/src/util/config.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "logger": { "level": "info" }, - "server": { - "port": 8080, - "defaultMessage": "Bitwarden Secrets Manager ESO Wrapper", - "express": { "json": { "config": { "limit": "1mb" } } } - }, - "middleware": { - "swagger": { "disable": true, "path": "/docs" } - } -} diff --git a/src/util/constants.js b/src/util/constants.js index 07253ab..9651334 100644 --- a/src/util/constants.js +++ b/src/util/constants.js @@ -1,12 +1,22 @@ const constants = { - BWS_ACCESS_TOKEN: process.env.BWS_ACCESS_TOKEN || undefined, - BWS_CLI_PATH: process.env.BWS_CLI_PATH || "/usr/local/bin/bws", + BWS_ACCESS_TOKEN: process.env.BWS_ACCESS_TOKEN || undefined, + BWS_CLI_PATH: process.env.BWS_CLI_PATH || "/usr/local/bin/bws", defaults: { - SERVER_PORT: 8080, - CONFIG_FILE_PATH: "./util/config.json", - OAS_FILE_PATH: "./api/spec.yaml", - EXPRESS_JSON_CONFIG: { "limit": "1mb" }, + SERVER_PORT: 8080, + OAS_FILE_PATH: "./api/spec.yaml", + EXPRESS_JSON_CONFIG: { "limit": "1mb" }, DEFAULT_MESSAGE: "Bitwarden Secrets Manager ESO Wrapper" + }, + config: { + "logger": { "level": "info" }, + "server": { + "port": 8080, + "defaultMessage": "Bitwarden Secrets Manager ESO Wrapper", + "express": { "json": { "config": { "limit": "1mb" } } } + }, + "middleware": { + "swagger": { "disable": true, "path": "/docs" } + } } }