From d00bd6d37199f1051569a9137ffd10f0a9182221 Mon Sep 17 00:00:00 2001 From: Dmitriy Altuhov Date: Sun, 28 Apr 2024 23:04:03 +0300 Subject: [PATCH] First commit --- .github/workflows/release.yml | 39 ++++++++ .gitignore | 1 + .goreleaser.yaml | 21 ++++ Dockerfile | 23 +++++ Dockerfile.windows | 15 +++ Makefile | 35 +++++++ README.md | 87 ++++++++++++++++- go.mod | 35 +++++++ go.sum | 163 ++++++++++++++++++++++++++++++++ main.go | 96 +++++++++++++++++++ utils/extsvcs.go | 22 +++++ utils/logger.go | 19 ++++ utils/prestart.go | 72 ++++++++++++++ utils/secr_to_reg_unix.go | 7 ++ utils/secr_to_reg_windows.go | 25 +++++ utils/secrets.go | 98 +++++++++++++++++++ utils/structures.go | 7 ++ utils/vault-client-go.go.future | 55 +++++++++++ utils/vault.go | 54 +++++++++++ 19 files changed, 873 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 .goreleaser.yaml create mode 100644 Dockerfile create mode 100644 Dockerfile.windows create mode 100644 Makefile create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 utils/extsvcs.go create mode 100644 utils/logger.go create mode 100644 utils/prestart.go create mode 100644 utils/secr_to_reg_unix.go create mode 100644 utils/secr_to_reg_windows.go create mode 100644 utils/secrets.go create mode 100644 utils/structures.go create mode 100644 utils/vault-client-go.go.future create mode 100644 utils/vault.go diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..8afe975 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,39 @@ +name: goreleaser + +on: + push: + branches: + - 'master' + tags: + - 'v*' + pull_request: + +permissions: + contents: write + # packages: write + # issues: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.22.2' + # More assembly might be required: Docker logins, GPG, etc. + # It all depends on your needs. + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v5 + with: + # either 'goreleaser' (default) or 'goreleaser-pro' + distribution: goreleaser + # 'latest', 'nightly', or a semver + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e660fd9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bin/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..65a7681 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,21 @@ +before: + hooks: + - go mod tidy + +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + - windows + goarch: + - amd64 + - arm64 + goarm: + - "7" + mod_timestamp: "{{ .CommitTimestamp }}" + flags: + - -trimpath + ldflags: + - -s -w -X main.version={{.Version}} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4e14ffb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +ARG NODE_VERSION_IMAGE +FROM golang:1.22.2 as build +ARG VERSION_STRING="unknown" + +COPY . /go + +RUN cd /go && set -x; CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X main.version=$VERSION_STRING" -o run + +FROM ${NODE_VERSION_IMAGE} +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends openssl ca-certificates curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +RUN chown -R node:node /app + +COPY --from=build /go/run /app/run +RUN chmod +x /app/run + +USER node + +RUN sh -c 'whoami && /app/run true' + +ENTRYPOINT ["/app/run"] diff --git a/Dockerfile.windows b/Dockerfile.windows new file mode 100644 index 0000000..7686cf4 --- /dev/null +++ b/Dockerfile.windows @@ -0,0 +1,15 @@ +ARG NODE_VERSION_IMAGE +FROM golang:1.22.2-windowsservercore-ltsc2022 as build +ARG VERSION_STRING="unknown" + +COPY . /go + +RUN cd /go && set -x; CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X main.version=$VERSION_STRING" -o entrypoint.exe + +FROM ${DOTNET_VERSION_IMAGE} +ADD https://aka.ms/vs/17/release/vc_redist.x64.exe /vc_redist.x64.exe +RUN c:\vc_redist.x64.exe /install /quiet /norestart + +COPY --from=build /go/entrypoint.exe /entrypoint/entrypoint.exe + +ENTRYPOINT ["/entrypoint/entrypoint.exe"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..91f4123 --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +ifeq ($(strip $(VERSION_STRING)),) +VERSION_STRING := $(shell git rev-parse --short HEAD) +endif + +BINDIR := $(CURDIR)/bin +PLATFORMS := linux/amd64/entrypoint/osusergo*netgo*static_build windows/amd64/entrypoint.exe/osusergo*static_build +BUILDCOMMAND := go build -trimpath -ldflags "-s -w -X main.version=${VERSION_STRING}" +temp = $(subst /, ,$@) +os = $(word 1, $(temp)) +arch = $(word 2, $(temp)) +label = $(word 3, $(temp)) +tags = $(subst *, ,$(word 4, $(temp))) + +UNAME := $(shell uname) +ifeq ($(UNAME), Darwin) +SHACOMMAND := shasum -a 256 +else +SHACOMMAND := sha256sum +endif + +.DEFAULT_GOAL := build + +.PHONY: release +release: $(PLATFORMS) +$(PLATFORMS): + GOOS=$(os) GOARCH=$(arch) CGO_ENABLED=0 $(BUILDCOMMAND) -tags "$(tags)" -o "bin/$(label)" + $(SHACOMMAND) "bin/$(label)" > "bin/$(label).sha256" + +.PHONY: build +build: + $(BUILDCOMMAND) -o bin/entrypoint + +.PHONY: dep +dep: + go mod tidy diff --git a/README.md b/README.md index 74cfb43..917f336 100644 --- a/README.md +++ b/README.md @@ -1 +1,86 @@ -# go-entrypoint \ No newline at end of file +# Entrypoint for docker containers + +Entrypoint for running apps in containers with: +1. Optional generation env variables (only for child process) from Vault secrets. Windows version also set env variables in Registry system-wide +2. SIGTERM and SIGINT propagation to child process +3. Wait for child process for finish and exit with child's exit code + +## Entrypoint binaries delivery + +### With built-in base image + +You could use next Dockerfiles as example to build your base image: +- [Dockerfile for Linux for Node.JS apps](Dockerfile) +- [Dockerfile for Windows for Dot.Net apps](Dockerfile.windows) + +Applications CI will use those base images in `FROM` + +### With S3 storage and Kubernetes host_mount + +1. Create an S3 bucket (like `infra-binaries`) +2. Upload binaries (for linux and windows) to the S3 bucket + 1. New binary should be uploaded to the temp name like `entrypoint.tmp` + 2. Old binary should be renamed to the `entrypoint.old` + 3. New binary should be renamed from temp name `entrypoint.tmp` to `entrypoint` +3. Every k8s node contains a bootstrap code to download relevant entrypoint binary + 1. For linux nodes: + ``` + pre_bootstrap_user_data = <<-EOT + #!/bin/bash + mkdir -p /entrypoint + aws s3 cp s3://infra-binaries/entrypoint/entrypoint /entrypoint/entrypoint || aws s3 cp s3://infra-binaries/entrypoint/entrypoint.old /entrypoint/entrypoint + chmod +x /entrypoint/entrypoint + EOT + ``` + 2. For windows nodes: + ``` + pre_bootstrap_user_data = <<-EOT + Read-S3Object -BucketName "infra-binaries" -Key "entrypoint/entrypoint.exe" -Region "eu-west-2" -File "/entrypoint/entrypoint.exe"; if (-not $?) { Read-S3Object -BucketName "infra-binaries" -Key "entrypoint/entrypoint.exe.old" -Region "eu-west-2" -File "/entrypoint/entrypoint.exe" } + EOT + ``` +4. Configure POD with host volume mount `/entrypoint/` +5. Configure POD's `command` (entrypoint) changed to `/entrypoint/entrypoint` for linix and `/entrypoint/entrypoint.exe` for windows +6. To update `entrypoint` on nodes, could use project [go-entrypoint-updater](https://github.com/alt-dima/go-entrypoint-updater) + +## Entrypoint logic workflow +1. Check if `VAULT_ADDR` env var configured and Vault is reacheble and ready by endpoint `/v1/sys/health` +3. If list with required Vault secrets is not empty: + 1. Read secrets list from `SECRETS_SOURCE_CONFIG` env var, by default: `./secrets_config.json#secrets_list` (`./secrets_config.json` - json file path, `secrets_list` - json path inside file) + 2. Init Vault Client with credentials (env vars `VAULT_APPROLE_RID` and `VAULT_APPROLE_SID`) + 3. Read required secrets from Vault and set env varibales with these values to the child +4. Run child app process with defined arguments +6. Wait until process will be terminated (with signals propagation) or exited by itself + +### Vault secrets: +Regular `/secret/{secret_path}` will be used. + +Required secrets configuration (`secrets_config.json` example): +```json +{ + "secrets_list": [ + "mongodb", + "rabbitmq", + { + "secretname": "mysql#local", + "envvarname": "env1" + }, + ] +} +``` +## Usage: +```bash +export SECRETS_SOURCE_CONFIG=./secrets_config.json#secrets_list +export VAULT_APPROLE_SID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +export VAULT_APPROLE_RID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +export VAULT_ADDR=https://vault-api-address + +entrypoint node app.js appparam1 appparam2 appparam3 +``` +Listed secrets from `secrets_config.json` file will be provided as a child's process env vars (and container-wide for windows) in the following format: +Non `[^a-zA-Z0-9_]` characters in the secret path will be replaced with `_` (like envconsul did) + +```bash +echo $secret_mongodb_url1 +secret_mongodb_url1="xxx" +``` +if one of listed secret's path doesn't exist in Vault - entrypoint will fail. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..091cc4d --- /dev/null +++ b/go.mod @@ -0,0 +1,35 @@ +module github.com/alt-dima/go-entrypoint + +go 1.22.2 + +require ( + github.com/PaesslerAG/jsonpath v0.1.1 + github.com/hashicorp/vault/api v1.13.0 + github.com/hashicorp/vault/api/auth/approle v0.6.0 + golang.org/x/sys v0.19.0 +) + +require ( + github.com/PaesslerAG/gval v1.2.2 // indirect + github.com/cenkalti/backoff/v3 v3.2.2 // indirect + github.com/go-jose/go-jose/v4 v4.0.1 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-retryablehttp v0.7.5 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.6 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/stretchr/testify v1.9.0 // indirect + golang.org/x/crypto v0.22.0 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..f79f750 --- /dev/null +++ b/go.sum @@ -0,0 +1,163 @@ +github.com/PaesslerAG/gval v1.0.0/go.mod h1:y/nm5yEyTeX6av0OfKJNp9rBNj2XrGhAf5+v24IBN1I= +github.com/PaesslerAG/gval v1.2.2 h1:Y7iBzhgE09IGTt5QgGQ2IdaYYYOU134YGHBThD+wm9E= +github.com/PaesslerAG/gval v1.2.2/go.mod h1:XRFLwvmkTEdYziLdaCeCa5ImcGVrfQbeNUbVR+C6xac= +github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8= +github.com/PaesslerAG/jsonpath v0.1.1 h1:c1/AToHQMVsduPAa4Vh6xp2U0evy4t8SWp8imEsylIk= +github.com/PaesslerAG/jsonpath v0.1.1/go.mod h1:lVboNxFGal/VwW6d9JzIy56bUsYAP6tH/x80vjnCseY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= +github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= +github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= +github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= +github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= +github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.16.2 h1:K4ev2ib4LdQETX5cSZBG0DVLk1jwGqSPXBjdah3veNs= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= +github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSYmuZJGizr6/x/AEizP0CQc= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I= +github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/vault/api v1.12.0/go.mod h1:si+lJCYO7oGkIoNPAN8j3azBLTn9SjMGS+jFaHd1Cck= +github.com/hashicorp/vault/api v1.13.0 h1:RTCGpE2Rgkn9jyPcFlc7YmNocomda44k5ck8FKMH41Y= +github.com/hashicorp/vault/api v1.13.0/go.mod h1:0cb/uZUv1w2cVu9DIvuW1SMlXXC6qtATJt+LXJRx+kg= +github.com/hashicorp/vault/api/auth/approle v0.6.0 h1:ELfFFQlTM/e97WJKu1HvNFa7lQ3tlTwwzrR1NJE1V7Y= +github.com/hashicorp/vault/api/auth/approle v0.6.0/go.mod h1:CCoIl1xBC3lAWpd1HV+0ovk76Z8b8Mdepyk21h3pGk0= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..00c3f04 --- /dev/null +++ b/main.go @@ -0,0 +1,96 @@ +package main + +import ( + "log/slog" + "os" + "os/exec" + "os/signal" + "strconv" + "strings" + "syscall" + "time" + + "github.com/alt-dima/go-entrypoint/utils" +) + +var ( + version string = "unspecified" +) + +func main() { + var logger = utils.Logger + slog.SetDefault(logger) + + if len(os.Args) == 1 { + logger.Error("Entrypoint nothing to be executed") + os.Exit(1) + } else if os.Args[1] == "" { + logger.Error("Entrypoint empty command") + os.Exit(1) + } + cmdToExec := os.Args[1] + + argsToExec := []string{} + for _, value := range os.Args[2:] { + if value != "" { + argsToExec = append(argsToExec, value) + } + } + + sigs := make(chan os.Signal, 2) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) + + logger.Debug("Entrypoint version " + version) + + logger.Debug("Entrypoint starting child: " + cmdToExec + " " + strings.Join(argsToExec, " ")) + cmd := exec.Command(cmdToExec, argsToExec...) + cmd.Env = utils.GenerateChildEnvs() + // pipe the commands output to the applications + // standard output + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Start() + if err != nil { + logger.Error("Entrypoint failed to start child: " + err.Error()) + os.Exit(1) + } + + var shutdownStartTime time.Time + + go func() { + sig := <-sigs + logger.Debug("Entrypoint got signal " + sig.String()) + shutdownStartTime = time.Now() + cmd.Process.Signal(sig) + }() + //log.Println("awaiting signal") + + err = cmd.Wait() + exitCodeFinal := 0 + if err != nil && cmd.ProcessState.ExitCode() < 0 { + exitCodeFinal = 1 + logger.Warn("Entrypoint child failed: " + err.Error()) + } else if cmd.ProcessState.ExitCode() == 143 { + exitCodeFinal = 0 + } else { + exitCodeFinal = cmd.ProcessState.ExitCode() + } + + // Could be used to stop sidecars such envoy proxy by sending specific HTTP-request + //utils.StopExtSvcs() + + if !shutdownStartTime.IsZero() { + shutdownElapsedDuration := time.Since(shutdownStartTime) + if shutdownElapsedDuration.Seconds() > 85 { + logger.Warn("Entrypoint slow child termination took " + shutdownElapsedDuration.String()) + } else if shutdownElapsedDuration.Milliseconds() < 50 { + logger.Info("Entrypoint fast child termination took " + shutdownElapsedDuration.String()) + } + } + if exitCodeFinal == 0 { + logger.Debug("Entrypoint exiting with code " + strconv.Itoa(exitCodeFinal)) + } else { + logger.Warn("Entrypoint exiting with code " + strconv.Itoa(exitCodeFinal)) + } + os.Exit(exitCodeFinal) +} diff --git a/utils/extsvcs.go b/utils/extsvcs.go new file mode 100644 index 0000000..7539b93 --- /dev/null +++ b/utils/extsvcs.go @@ -0,0 +1,22 @@ +package utils + +// func sendSimpleHttpReq(method string, url string) { +// newReq, err := http.NewRequest(method, url, nil) +// if err != nil { +// Logger.Debug(err.Error()) +// } +// resp, err := http.DefaultClient.Do(newReq) +// if err == nil { +// io.Copy(io.Discard, resp.Body) +// resp.Body.Close() +// } +// } + +// func StopExtSvcs() { +// //Pause/sleep disable because envoy/istio-proxy does not intercept any traffic from consul-sidecar +// //time.Sleep(5 * time.Second) + +// //Send to envoy +// //logger.Debug("Entrypoint sending quitquitquit signal to envoy-sidecar") +// sendSimpleHttpReq(http.MethodPost, "http://127.0.0.1:15000/quitquitquit") +// } diff --git a/utils/logger.go b/utils/logger.go new file mode 100644 index 0000000..38fca68 --- /dev/null +++ b/utils/logger.go @@ -0,0 +1,19 @@ +package utils + +import ( + "log/slog" + "os" +) + +var Logger = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelDebug, + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { + if a.Key == slog.TimeKey { + return slog.Attr{Key: "timestamp", Value: a.Value} + } + if a.Key == slog.MessageKey { + return slog.Attr{Key: "message", Value: a.Value} + } + return a + }, +})) diff --git a/utils/prestart.go b/utils/prestart.go new file mode 100644 index 0000000..d9cd89e --- /dev/null +++ b/utils/prestart.go @@ -0,0 +1,72 @@ +package utils + +import ( + "fmt" + "io" + "net/http" + "os" + "strings" + "time" +) + +func GenerateChildEnvs() []string { + vaultAddr := os.Getenv("VAULT_ADDR") + removeEnvVars := []string{"VAULT_APPROLE_RID", "VAULT_APPROLE_SID"} + var childEnvs []string + for _, removeEnv := range removeEnvVars { + os.Unsetenv(removeEnv) + } + childEnvs = os.Environ() + + if vaultAddr != "" { + checkCriticalSvcReady(vaultAddr + "/v1/sys/health?standbyok=true") + + var vaultStruct vaultStruct + vaultStruct.initVaultClient(vaultAddr) + + secretSourceConfig := os.Getenv("SECRETS_SOURCE_CONFIG") + + secretsFile := "./secrets_config.json" + secretsJsonPath := "secrets_list" + + if secretSourceConfig != "" { + splitedSourceConfig := strings.SplitN(secretSourceConfig, "#", 2) + secretsFile = splitedSourceConfig[0] + secretsJsonPath = splitedSourceConfig[1] + } + + secretsFromSource := readSecretsfromSource(secretsFile, secretsJsonPath) + secretsEnvList := vaultStruct.getSecretsEnvList(secretsFromSource) + + childEnvs = append(childEnvs, secretsEnvList...) + } + + return childEnvs +} + +func checkCriticalSvcReady(addrToCheck string) { + retryCnt := 5 + waitTime := 4 + for { + resp, err := http.Get(addrToCheck) + if err == nil { + io.Copy(io.Discard, resp.Body) + resp.Body.Close() + } + if err == nil && resp.StatusCode == http.StatusOK { + return + } else if retryCnt < 1 { + //stopExtSvcs() + var reqError string + if err != nil { + reqError = err.Error() + } else { + reqError = fmt.Sprint(resp.StatusCode) + } + Logger.Error("Entrypoint critical svc check not passed: " + reqError) + os.Exit(1) + } + retryCnt-- + time.Sleep(time.Duration(waitTime) * time.Second) + } +} diff --git a/utils/secr_to_reg_unix.go b/utils/secr_to_reg_unix.go new file mode 100644 index 0000000..e64e7d1 --- /dev/null +++ b/utils/secr_to_reg_unix.go @@ -0,0 +1,7 @@ +//go:build !windows +// +build !windows + +package utils + +func addSecretsToReg(secrets map[string]string) { +} diff --git a/utils/secr_to_reg_windows.go b/utils/secr_to_reg_windows.go new file mode 100644 index 0000000..f1eec37 --- /dev/null +++ b/utils/secr_to_reg_windows.go @@ -0,0 +1,25 @@ +package utils + +import ( + "os" + + "golang.org/x/sys/windows/registry" +) + +func addSecretsToReg(secrets map[string]string) { + k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `System\CurrentControlSet\Control\Session Manager\Environment`, registry.CREATE_SUB_KEY|registry.SET_VALUE) + if err != nil { + Logger.Error("Entrypoint failed to create a key in registry: " + err.Error()) + os.Exit(1) + } + for name, secret := range secrets { + if err := k.SetStringValue(name, secret); err != nil { + Logger.Error("Entrypoint failed to set kv in registry: " + err.Error()) + os.Exit(1) + } + } + if err := k.Close(); err != nil { + Logger.Error("Entrypoint failed to close registry: " + err.Error()) + os.Exit(1) + } +} diff --git a/utils/secrets.go b/utils/secrets.go new file mode 100644 index 0000000..d3e597d --- /dev/null +++ b/utils/secrets.go @@ -0,0 +1,98 @@ +package utils + +import ( + "encoding/json" + "os" + "regexp" + "runtime" + + "github.com/PaesslerAG/jsonpath" +) + +func (vaultStruct *vaultStruct) getSecretsEnvList(secretsFromSource *map[string]string) []string { + + // Convert the secrets to a slice of strings. + secretsEnvList := []string{} + + if len(*secretsFromSource) > 0 { + secretsEnvMap := make(map[string]string) + + for secretPath, secretEnvVarNamePrefix := range *secretsFromSource { + getSecret := vaultStruct.readSecretVault(secretPath) + for secretKey, secretValue := range getSecret { + finalSecretEnvVar := secretEnvVarNamePrefix + "_" + secretKey + Logger.Debug("Entrypoint got secret " + finalSecretEnvVar) + secretsEnvList = append(secretsEnvList, finalSecretEnvVar+"="+secretValue.(string)) + secretsEnvMap[finalSecretEnvVar] = secretValue.(string) + } + } + + if runtime.GOOS == "windows" { + addSecretsToReg(secretsEnvMap) + } + } + + return secretsEnvList +} + +func readSecretsfromSource(secretsFile string, secretsJsonPath string) *map[string]string { + + // invalidRegexp is a regexp for invalid characters in keys + var invalidRegexp = regexp.MustCompile(`[^a-zA-Z0-9_]`) + + // Open menifest json from platform inventory folder + piManifestByte, err := os.ReadFile(secretsFile) + if err != nil { + Logger.Error("entrypoint failed read json with secrets: " + err.Error()) + os.Exit(1) + } + + var secretsStrings = make(map[string]string) + + // Get the path to the secrets field. + path := "$." + secretsJsonPath + + var manifest interface{} + json.Unmarshal(piManifestByte, &manifest) + secrets, err := jsonpath.Get(path, manifest) + if err != nil { + Logger.Warn("Entrypoint failed with specified path in json: " + err.Error()) + } else { + + for _, value := range secrets.([]interface{}) { + switch v := value.(type) { + case string: + fixedSecretPath := invalidRegexp.ReplaceAllString(v, "_") + //fixedSecretPath := strings.ReplaceAll(v, "/", "_") + //fixedSecretPath = strings.ReplaceAll(fixedSecretPath, "-", "_") + finalSecretEnvVarPrefix := "secret_" + fixedSecretPath + secretsStrings[v] = finalSecretEnvVarPrefix + case map[string]interface{}: + if secretPathString, ok := v["secretname"].(string); ok { + var finalSecretEnvVarPrefix string + + if secretnameString, ok := v["envvarname"].(string); ok { + finalSecretEnvVarPrefix = secretnameString + } else { + fixedSecretPath := invalidRegexp.ReplaceAllString(secretPathString, "_") + //fixedSecretPath := strings.ReplaceAll(secretPathString, "/", "_") + //fixedSecretPath = strings.ReplaceAll(fixedSecretPath, "-", "_") + finalSecretEnvVarPrefix = "secret_" + fixedSecretPath + } + + secretsStrings[secretPathString] = finalSecretEnvVarPrefix + + } else { + Logger.Error("Entrypoint wrong secrets list, secretname does not exists or not string") + os.Exit(1) + } + default: + Logger.Error("Entrypoint wrong secrets list") + os.Exit(1) + } + } + + } + + return &secretsStrings +} diff --git a/utils/structures.go b/utils/structures.go new file mode 100644 index 0000000..7d9d658 --- /dev/null +++ b/utils/structures.go @@ -0,0 +1,7 @@ +package utils + +import vault "github.com/hashicorp/vault/api" + +type vaultStruct struct { + vaultClient *vault.Client +} diff --git a/utils/vault-client-go.go.future b/utils/vault-client-go.go.future new file mode 100644 index 0000000..3f22275 --- /dev/null +++ b/utils/vault-client-go.go.future @@ -0,0 +1,55 @@ +package main + +import ( + "context" + "os" + "time" + + "github.com/hashicorp/vault-client-go" + "github.com/hashicorp/vault-client-go/schema" +) + +func initVaultClient() *vault.Client { + // prepare a client with the given base address + client, err := vault.New( + vault.WithAddress(os.Getenv("VAULT_ADDR")), + vault.WithRequestTimeout(10*time.Second), + ) + + if err != nil { + logger.Error(err.Error()) + os.Exit(1) + } + + resp, err := client.Auth.AppRoleLogin( + context.Background(), + schema.AppRoleLoginRequest{ + RoleId: os.Getenv("VAULT_APPROLE_RID"), + SecretId: os.Getenv("VAULT_APPROLE_SID"), + }, + ) + if err != nil { + logger.Error(err.Error()) + os.Exit(1) + } + + if err := client.SetToken(resp.Auth.ClientToken); err != nil { + logger.Error(err.Error()) + os.Exit(1) + } + + return client +} + +func readSecretVault(client *vault.Client, secretName string) map[string]interface{} { + + secretResp, err := client.Read(context.Background(), "/secret/"+secretName) + if err != nil { + logger.Error(err.Error()) + os.Exit(1) + } + + data := secretResp.Data + + return data +} diff --git a/utils/vault.go b/utils/vault.go new file mode 100644 index 0000000..e451629 --- /dev/null +++ b/utils/vault.go @@ -0,0 +1,54 @@ +package utils + +import ( + "context" + "os" + + vault "github.com/hashicorp/vault/api" + auth "github.com/hashicorp/vault/api/auth/approle" +) + +func (VaultStruct *vaultStruct) initVaultClient(vaultAddr string) { + config := vault.DefaultConfig() + config.Address = vaultAddr + + client, err := vault.NewClient(config) + if err != nil { + Logger.Error("Entrypoint failed create Vault client: " + err.Error()) + os.Exit(1) + } + + appRoleAuth, err := auth.NewAppRoleAuth( + os.Getenv("VAULT_APPROLE_RID"), + &auth.SecretID{FromEnv: "VAULT_APPROLE_SID"}, + ) + if err != nil { + Logger.Error("Entrypoint failed to create AppRoleAuth: " + err.Error()) + os.Exit(1) + } + + authInfo, err := client.Auth().Login(context.Background(), appRoleAuth) + if err != nil { + Logger.Error("Entrypoint failed to login to Vault with appRoleAuth: " + err.Error()) + os.Exit(1) + } + if authInfo == nil { + Logger.Error("Entrypoint failed empty authInfo: " + err.Error()) + os.Exit(1) + } + + VaultStruct.vaultClient = client +} + +func (vaultStruct *vaultStruct) readSecretVault(secretPath string) map[string]interface{} { + + secretResp, err := vaultStruct.vaultClient.KVv1("secret").Get(context.Background(), secretPath) //client.Read(context.Background(), "/secret/"+secretName) + if err != nil { + Logger.Error("Entrypoint failed to get secret from Vault: " + err.Error()) + os.Exit(1) + } + + data := secretResp.Data + + return data +}