├── .dockerignore ├── .github ├── dependabot.yml └── workflows │ └── main.yml ├── .gitignore ├── .goreleaser.yml ├── .tool-versions ├── LICENSE ├── Makefile ├── README.md ├── _examples └── docker │ ├── base │ ├── Dockerfile │ ├── README.md │ └── entrypoint.sh │ └── copy │ ├── Dockerfile │ └── README.md ├── docker ├── alpine │ └── Dockerfile └── scratch │ └── Dockerfile ├── go.mod ├── go.sum ├── internal ├── api │ ├── api.go │ └── v1alpha1 │ │ ├── vault_secrets.go │ │ └── vault_secrets_test.go ├── envexec │ ├── config.go │ └── envexec.go ├── loaders │ ├── fake │ │ └── fake.go │ ├── loader.go │ └── vault │ │ ├── vault_secrets.go │ │ └── vault_secrets_test.go ├── providers │ └── vault │ │ ├── auth.go │ │ ├── auth_kubernetes.go │ │ ├── auth_token.go │ │ └── vault.go └── spec │ ├── loader.go │ └── loader_test.go └── main.go /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !/docker/ 3 | !/internal/ 4 | !/vendor/ 5 | !/.dockerignore 6 | !/go.mod 7 | !/go.sum 8 | !/main.go 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | open-pull-requests-limit: 10 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "monthly" 12 | open-pull-requests-limit: 10 13 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - 'v*' 9 | pull_request: 10 | branches: 11 | - '*' 12 | 13 | jobs: 14 | build: 15 | name: Build 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 20 | 21 | - name: Setup Go 22 | uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 23 | with: 24 | go-version-file: 'go.mod' 25 | 26 | - name: Download dependencies 27 | run: | 28 | go mod download 29 | 30 | - name: Run unit tests 31 | run: | 32 | go test -v -race -coverprofile=coverage.txt -covermode=atomic $(go list ./...) 33 | 34 | # TODO: enable after fixing linter issues 35 | # - name: Run linter 36 | # uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0 37 | # with: 38 | # version: v1.52.2 39 | # args: --verbose 40 | # # See: https://github.com/golangci/golangci-lint-action/issues/244 41 | # skip-pkg-cache: true 42 | # skip-build-cache: true 43 | 44 | - name: Build 45 | run: | 46 | go build -v -o ./bin/envexec . 47 | 48 | docker: 49 | name: Docker 50 | runs-on: ubuntu-latest 51 | needs: 52 | - build 53 | env: 54 | IMAGE_NAME: hypnoglow/envexec 55 | # For PR, we only build for AMD64, just to be sure that Docker build works. 56 | # For main branch and tags we also build for ARM64. 57 | # Note that building for ARM64 is very slow. 58 | IMAGE_PLATFORMS: | 59 | linux/amd64 60 | ${{ github.event_name != 'pull_request' && 'linux/arm64' || '' }} 61 | steps: 62 | - name: Checkout 63 | uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 64 | 65 | - name: Setup Go 66 | uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 67 | with: 68 | go-version-file: 'go.mod' 69 | 70 | - name: Download dependencies 71 | run: | 72 | go mod download 73 | go mod vendor 74 | 75 | - name: Login to Docker Hub 76 | uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 77 | with: 78 | username: ${{ secrets.DOCKERHUB_USERNAME }} 79 | password: ${{ secrets.DOCKERHUB_TOKEN }} 80 | if: github.event_name != 'pull_request' 81 | 82 | # Used for arm images. 83 | - name: Set up QEMU 84 | uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 85 | with: 86 | platforms: ${{ env.IMAGE_PLATFORMS }} 87 | 88 | - name: Set up Docker Buildx 89 | uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 90 | 91 | - name: Extract Docker image metadata (alpine) 92 | id: docker_meta_alpine 93 | uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1 94 | with: 95 | images: | 96 | ${{ env.IMAGE_NAME }} 97 | flavor: | 98 | suffix=-alpine 99 | tags: | 100 | type=ref,event=pr 101 | type=ref,event=branch,enable={{ is_default_branch }} 102 | type=semver,pattern={{ version }} 103 | 104 | - name: Extract Docker image metadata (scratch) 105 | id: docker_meta_scratch 106 | uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1 107 | with: 108 | images: | 109 | ${{ env.IMAGE_NAME }} 110 | flavor: | 111 | suffix=-scratch 112 | tags: | 113 | type=ref,event=pr 114 | type=ref,event=branch,enable={{ is_default_branch }} 115 | type=semver,pattern={{ version }} 116 | 117 | - name: Build and push Docker image (alpine) 118 | uses: docker/build-push-action@1a162644f9a7e87d8f4b053101d1d9a712edc18c # v6.3.0 119 | with: 120 | file: docker/alpine/Dockerfile 121 | context: . 122 | platforms: ${{ env.IMAGE_PLATFORMS }} 123 | tags: ${{ steps.docker_meta_alpine.outputs.tags }} 124 | labels: ${{ steps.docker_meta_alpine.outputs.labels }} 125 | push: ${{ github.event_name != 'pull_request' }} 126 | 127 | - name: Build and push Docker image (scratch) 128 | uses: docker/build-push-action@1a162644f9a7e87d8f4b053101d1d9a712edc18c # v6.3.0 129 | with: 130 | file: docker/scratch/Dockerfile 131 | context: . 132 | platforms: ${{ env.IMAGE_PLATFORMS }} 133 | tags: ${{ steps.docker_meta_scratch.outputs.tags }} 134 | labels: ${{ steps.docker_meta_scratch.outputs.labels }} 135 | push: ${{ github.event_name != 'pull_request' }} 136 | 137 | goreleaser: 138 | name: GoReleaser 139 | runs-on: ubuntu-latest 140 | needs: 141 | - build 142 | permissions: 143 | contents: write 144 | if: startsWith(github.ref, 'refs/tags/') 145 | steps: 146 | - name: Checkout 147 | uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 148 | with: 149 | fetch-depth: 0 150 | 151 | - name: Setup Go 152 | uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 153 | with: 154 | go-version-file: 'go.mod' 155 | 156 | - name: Download dependencies 157 | run: | 158 | go mod download 159 | 160 | - name: Run GoReleaser 161 | uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0 162 | with: 163 | version: latest 164 | args: release --clean 165 | env: 166 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 167 | GORELEASER_PREVIOUS_TAG: "" 168 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /vendor/ 3 | 4 | /envexec 5 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | builds: 4 | - flags: 5 | - -trimpath 6 | env: 7 | - CGO_ENABLED=0 8 | 9 | archives: 10 | - id: tar 11 | format: tar.gz 12 | 13 | release: 14 | draft: true 15 | 16 | # The lines beneath this are called `modelines`. See `:help modeline` 17 | # Feel free to remove those if you don't want/use them. 18 | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json 19 | # vim: set ts=2 sw=2 tw=0 fo=cnqoj 20 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | golang 1.22.2 2 | golangci-lint 1.57.2 3 | goreleaser 2.0.1 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Igor Zibarev 2 | # Copyright 2018 The envexec Authors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | .PHONY: all 17 | all: deps test 18 | 19 | .PHONY: deps 20 | deps: 21 | go mod tidy 22 | go mod vendor 23 | 24 | .PHONY: test 25 | test: 26 | go test -v ./... 27 | 28 | .PHONY: images 29 | images: 30 | docker build -f docker/scratch/Dockerfile -t hypnoglow/envexec:latest-scratch . 31 | docker build -f docker/alpine/Dockerfile -t hypnoglow/envexec:latest-alpine . 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # envexec 2 | 3 |
11 | 12 | **envexec** helps to provision an application by taking values from 13 | sources like Vault and bringing them as environment variables. 14 | 15 | ## Status 16 | 17 | > [!IMPORTANT] 18 | > The project has been discontinued. 19 | > 20 | > Recommended replacements: 21 | > - [vals](https://github.com/helmfile/vals) 22 | 23 | ## Features 24 | 25 | - Out of process (no code dependency) 26 | - Works with any app written in any language 27 | - One small static binary (💙 Golang) 28 | - Familiar configuration format, with versions 29 | - No supervising, just replaces the process with `exec` 30 | - Simple Docker integration 31 | 32 | ## Usage 33 | 34 | ### Docker 35 | 36 | The easiest way to embed **envexec** into your Docker image is to just 37 | copy the binary from the prebuilt image: 38 | 39 | ```docker 40 | FROM alpine:3.19 41 | 42 | COPY --from=hypnoglow/envexec:latest-scratch /envexec /usr/local/bin/envexec 43 | 44 | ENTRYPOINT ["envexec", "--"] 45 | CMD ["echo", "Hello from envexec!"] 46 | ``` 47 | 48 | An alternative approach is to build your image with **envexec** image 49 | as a base: 50 | 51 | ``` 52 | FROM hypnoglow/envexec:latest-alpine 53 | 54 | ENTRYPOINT ["envexec", "--"] 55 | CMD ["echo", "Hello from envexec!"] 56 | ``` 57 | 58 | *NOTE: Using "latest" tags is not recommended. Prefer tagged versions.* 59 | 60 | See [examples](_examples/docker/) for more info. 61 | 62 | ## Providers 63 | 64 | ### Vault 65 | 66 | To fetch secrets from Vault and export values as environment 67 | variables, you need to prepare a spec. Example: 68 | 69 | ```yaml 70 | apiVersion: envexec/v1alpha1 71 | kind: VaultSecrets 72 | secrets: 73 | - path: secret/namespace/service/some 74 | key: api_key 75 | env: SOME_API_KEY 76 | - path: secret/namespace/service/db 77 | key: password 78 | env: DB_PASSWORD 79 | ``` 80 | 81 | Store this spec in the file `vaultsecrets.yaml`. 82 | 83 | Next you need to prepare environment variables to authenticate 84 | in Vault. This depends on the Vault Auth Method. Lets consider the 85 | simplest token authentication method: 86 | 87 | ```bash 88 | export VAULT_ADDR="https://vault.company.tld" 89 | export VAULT_METHOD="token" 90 | export VAULT_TOKEN="put-vault-token-here" 91 | ``` 92 | 93 | Now you just run your app through **envexec**: 94 | 95 | ```bash 96 | envexec --spec-file vaultsecrets.yaml -- /usr/bin/env 97 | ``` 98 | 99 | #### Auth Methods 100 | 101 | ##### Token 102 | 103 | See: https://www.vaultproject.io/docs/auth/token.html 104 | 105 | ```bash 106 | export VAULT_ADDR="https://vault.company.tld" 107 | export VAULT_METHOD="token" 108 | export VAULT_TOKEN="put-vault-token-here" 109 | 110 | envexec --spec-file vaultsecrets.yaml -- /usr/bin/env 111 | ``` 112 | 113 | ##### Kubernetes 114 | 115 | See: https://www.vaultproject.io/docs/auth/kubernetes.html 116 | 117 | ```bash 118 | export VAULT_ADDR="https://vault.company.tld" 119 | export VAULT_AUTH_METHOD="kubernetes" 120 | export VAULT_AUTH_KUBERNETES_ROLE="foo-app" 121 | 122 | envexec --spec-file vaultsecrets.yaml /usr/bin/env 123 | ``` 124 | 125 | ## Acknowledgements 126 | 127 | Inspired by: 128 | 129 | - https://github.com/hashicorp/envconsul 130 | - https://github.com/channable/vaultenv 131 | -------------------------------------------------------------------------------- /_examples/docker/base/Dockerfile: -------------------------------------------------------------------------------- 1 | # NOTE: Using "latest" tags is not recommended. Prefer tagged versions. 2 | FROM hypnoglow/envexec:latest-alpine 3 | 4 | COPY entrypoint.sh /usr/local/bin/ 5 | 6 | ENTRYPOINT ["envexec", "--"] 7 | CMD ["entrypoint.sh"] 8 | -------------------------------------------------------------------------------- /_examples/docker/base/README.md: -------------------------------------------------------------------------------- 1 | # envexec - examples / docker / base 2 | 3 | This example demonstrates how to build your docker image based on 4 | envexec image using FROM directive. 5 | 6 | ## Run 7 | 8 | ```bash 9 | docker build -t envexec-example-base . 10 | docker run --rm envexec-example-base 11 | ``` 12 | -------------------------------------------------------------------------------- /_examples/docker/base/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | echo "Hello from envexec!" 4 | -------------------------------------------------------------------------------- /_examples/docker/copy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.19 2 | 3 | # NOTE: Using "latest" tags is not recommended. Prefer tagged versions. 4 | COPY --from=hypnoglow/envexec:latest-scratch /envexec /usr/local/bin/envexec 5 | 6 | ENTRYPOINT ["envexec", "--"] 7 | CMD ["echo", "Hello from envexec!"] 8 | -------------------------------------------------------------------------------- /_examples/docker/copy/README.md: -------------------------------------------------------------------------------- 1 | # envexec - examples / docker / copy 2 | 3 | This example demonstrates how to obtain envexec in your docker image 4 | using COPY directive. 5 | 6 | ## Run 7 | 8 | ```bash 9 | docker build -t envexec-example-copy . 10 | docker run --rm envexec-example-copy 11 | ``` 12 | -------------------------------------------------------------------------------- /docker/alpine/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Igor Zibarev 2 | # Copyright 2018 The envexec Authors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | FROM golang:1.22-alpine3.19 AS build 17 | 18 | WORKDIR /opt/envexec/ 19 | 20 | COPY . . 21 | 22 | RUN CGO_ENABLED=0 go build -mod=vendor 23 | 24 | FROM alpine:3.19 25 | 26 | COPY --from=build /opt/envexec/envexec /usr/local/bin/envexec 27 | -------------------------------------------------------------------------------- /docker/scratch/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Igor Zibarev 2 | # Copyright 2018 The envexec Authors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | FROM golang:1.22-alpine3.19 AS build 17 | 18 | WORKDIR /opt/envexec/ 19 | 20 | COPY . . 21 | 22 | RUN CGO_ENABLED=0 go build -mod=vendor 23 | 24 | FROM scratch 25 | 26 | COPY --from=build /opt/envexec/envexec /envexec 27 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hypnoglow/envexec 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 7 | github.com/hashicorp/go-hclog v1.6.3 8 | github.com/hashicorp/vault v1.17.1 9 | github.com/hashicorp/vault/api v1.14.0 10 | github.com/integrii/flaggy v1.5.2 11 | github.com/stretchr/testify v1.9.0 12 | ) 13 | 14 | require ( 15 | cloud.google.com/go v0.113.0 // indirect 16 | cloud.google.com/go/auth v0.4.1 // indirect 17 | cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect 18 | cloud.google.com/go/cloudsqlconn v1.4.3 // indirect 19 | cloud.google.com/go/compute/metadata v0.3.0 // indirect 20 | cloud.google.com/go/iam v1.1.8 // indirect 21 | cloud.google.com/go/kms v1.17.0 // indirect 22 | cloud.google.com/go/longrunning v0.5.7 // indirect 23 | cloud.google.com/go/monitoring v1.19.0 // indirect 24 | github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect 25 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 // indirect 26 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 // indirect 27 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 // indirect 28 | github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 // indirect 29 | github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect 30 | github.com/Azure/go-autorest v14.2.0+incompatible // indirect 31 | github.com/Azure/go-autorest/autorest v0.11.29 // indirect 32 | github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect 33 | github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect 34 | github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect 35 | github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect 36 | github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect 37 | github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect 38 | github.com/Azure/go-autorest/logger v0.2.1 // indirect 39 | github.com/Azure/go-autorest/tracing v0.6.0 // indirect 40 | github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect 41 | github.com/BurntSushi/toml v1.3.2 // indirect 42 | github.com/DataDog/datadog-go v3.2.0+incompatible // indirect 43 | github.com/Jeffail/gabs/v2 v2.1.0 // indirect 44 | github.com/Masterminds/goutils v1.1.1 // indirect 45 | github.com/Masterminds/semver/v3 v3.2.1 // indirect 46 | github.com/Masterminds/sprig/v3 v3.2.3 // indirect 47 | github.com/Microsoft/go-winio v0.6.1 // indirect 48 | github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect 49 | github.com/aliyun/alibaba-cloud-sdk-go v1.62.737 // indirect 50 | github.com/armon/go-metrics v0.4.1 // indirect 51 | github.com/armon/go-radix v1.0.0 // indirect 52 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect 53 | github.com/aws/aws-sdk-go v1.53.5 // indirect 54 | github.com/axiomhq/hyperloglog v0.0.0-20220105174342-98591331716a // indirect 55 | github.com/benbjohnson/immutable v0.4.0 // indirect 56 | github.com/beorn7/perks v1.0.1 // indirect 57 | github.com/bgentry/speakeasy v0.1.0 // indirect 58 | github.com/boltdb/bolt v1.3.1 // indirect 59 | github.com/boombuler/barcode v1.0.1 // indirect 60 | github.com/cenkalti/backoff/v3 v3.2.2 // indirect 61 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 62 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 63 | github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible // indirect 64 | github.com/circonus-labs/circonusllhist v0.1.3 // indirect 65 | github.com/cloudflare/circl v1.3.7 // indirect 66 | github.com/coreos/etcd v3.3.27+incompatible // indirect 67 | github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect 68 | github.com/coreos/pkg v0.0.0-20220810130054-c7d1c02cb6cf // indirect 69 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 70 | github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba // indirect 71 | github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc // indirect 72 | github.com/digitalocean/godo v1.7.5 // indirect 73 | github.com/dimchansky/utfbom v1.1.1 // indirect 74 | github.com/distribution/reference v0.6.0 // indirect 75 | github.com/dnaeon/go-vcr v1.2.0 // indirect 76 | github.com/docker/docker v25.0.5+incompatible // indirect 77 | github.com/docker/go-connections v0.4.0 // indirect 78 | github.com/docker/go-units v0.5.0 // indirect 79 | github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74 // indirect 80 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 81 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 82 | github.com/fatih/color v1.17.0 // indirect 83 | github.com/felixge/httpsnoop v1.0.4 // indirect 84 | github.com/gammazero/deque v0.2.1 // indirect 85 | github.com/gammazero/workerpool v1.1.3 // indirect 86 | github.com/go-jose/go-jose/v3 v3.0.3 // indirect 87 | github.com/go-jose/go-jose/v4 v4.0.2 // indirect 88 | github.com/go-logr/logr v1.4.1 // indirect 89 | github.com/go-logr/stdr v1.2.2 // indirect 90 | github.com/go-ole/go-ole v1.2.6 // indirect 91 | github.com/go-openapi/analysis v0.21.4 // indirect 92 | github.com/go-openapi/errors v0.20.4 // indirect 93 | github.com/go-openapi/jsonpointer v0.20.0 // indirect 94 | github.com/go-openapi/jsonreference v0.20.2 // indirect 95 | github.com/go-openapi/loads v0.21.2 // indirect 96 | github.com/go-openapi/spec v0.20.9 // indirect 97 | github.com/go-openapi/strfmt v0.21.7 // indirect 98 | github.com/go-openapi/swag v0.22.4 // indirect 99 | github.com/go-openapi/validate v0.22.2 // indirect 100 | github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect 101 | github.com/go-sql-driver/mysql v1.7.1 // indirect 102 | github.com/go-test/deep v1.1.0 // indirect 103 | github.com/gogo/protobuf v1.3.2 // indirect 104 | github.com/golang-jwt/jwt/v4 v4.5.0 // indirect 105 | github.com/golang-jwt/jwt/v5 v5.2.1 // indirect 106 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 107 | github.com/golang/protobuf v1.5.4 // indirect 108 | github.com/golang/snappy v0.0.4 // indirect 109 | github.com/google/gnostic-models v0.6.8 // indirect 110 | github.com/google/go-cmp v0.6.0 // indirect 111 | github.com/google/go-metrics-stackdriver v0.2.0 // indirect 112 | github.com/google/go-querystring v1.1.0 // indirect 113 | github.com/google/gofuzz v1.2.0 // indirect 114 | github.com/google/s2a-go v0.1.7 // indirect 115 | github.com/google/tink/go v1.7.0 // indirect 116 | github.com/google/uuid v1.6.0 // indirect 117 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect 118 | github.com/googleapis/gax-go/v2 v2.12.4 // indirect 119 | github.com/gophercloud/gophercloud v0.1.0 // indirect 120 | github.com/hashicorp-forge/bbolt v1.3.8-hc3 // indirect 121 | github.com/hashicorp/cli v1.1.6 // indirect 122 | github.com/hashicorp/consul/sdk v0.15.0 // indirect 123 | github.com/hashicorp/errwrap v1.1.0 // indirect 124 | github.com/hashicorp/eventlogger v0.2.9 // indirect 125 | github.com/hashicorp/go-bexpr v0.1.12 // indirect 126 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 127 | github.com/hashicorp/go-discover v0.0.0-20210818145131-c573d69da192 // indirect 128 | github.com/hashicorp/go-immutable-radix v1.3.1 // indirect 129 | github.com/hashicorp/go-kms-wrapping/entropy/v2 v2.0.1 // indirect 130 | github.com/hashicorp/go-kms-wrapping/v2 v2.0.16 // indirect 131 | github.com/hashicorp/go-kms-wrapping/wrappers/aead/v2 v2.0.9 // indirect 132 | github.com/hashicorp/go-kms-wrapping/wrappers/alicloudkms/v2 v2.0.3 // indirect 133 | github.com/hashicorp/go-kms-wrapping/wrappers/awskms/v2 v2.0.9 // indirect 134 | github.com/hashicorp/go-kms-wrapping/wrappers/azurekeyvault/v2 v2.0.11 // indirect 135 | github.com/hashicorp/go-kms-wrapping/wrappers/gcpckms/v2 v2.0.12 // indirect 136 | github.com/hashicorp/go-kms-wrapping/wrappers/ocikms/v2 v2.0.7 // indirect 137 | github.com/hashicorp/go-kms-wrapping/wrappers/transit/v2 v2.0.11 // indirect 138 | github.com/hashicorp/go-memdb v1.3.4 // indirect 139 | github.com/hashicorp/go-msgpack/v2 v2.1.1 // indirect 140 | github.com/hashicorp/go-multierror v1.1.1 // indirect 141 | github.com/hashicorp/go-plugin v1.6.0 // indirect 142 | github.com/hashicorp/go-raftchunking v0.6.3-0.20191002164813-7e9e8525653a // indirect 143 | github.com/hashicorp/go-retryablehttp v0.7.7 // indirect 144 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 145 | github.com/hashicorp/go-secure-stdlib/awsutil v0.3.0 // indirect 146 | github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 // indirect 147 | github.com/hashicorp/go-secure-stdlib/mlock v0.1.3 // indirect 148 | github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 // indirect 149 | github.com/hashicorp/go-secure-stdlib/plugincontainer v0.3.0 // indirect 150 | github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 // indirect 151 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect 152 | github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3 // indirect 153 | github.com/hashicorp/go-sockaddr v1.0.6 // indirect 154 | github.com/hashicorp/go-syslog v1.0.0 // indirect 155 | github.com/hashicorp/go-uuid v1.0.3 // indirect 156 | github.com/hashicorp/go-version v1.6.0 // indirect 157 | github.com/hashicorp/golang-lru v1.0.2 // indirect 158 | github.com/hashicorp/hcl v1.0.1-vault-5 // indirect 159 | github.com/hashicorp/hcp-sdk-go v0.75.0 // indirect 160 | github.com/hashicorp/mdns v1.0.4 // indirect 161 | github.com/hashicorp/raft v1.6.1 // indirect 162 | github.com/hashicorp/raft-autopilot v0.2.0 // indirect 163 | github.com/hashicorp/raft-boltdb/v2 v2.3.0 // indirect 164 | github.com/hashicorp/raft-snapshot v1.0.4 // indirect 165 | github.com/hashicorp/raft-wal v0.4.0 // indirect 166 | github.com/hashicorp/vault-plugin-secrets-kv v0.19.0 // indirect 167 | github.com/hashicorp/vault/sdk v0.13.0 // indirect 168 | github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 // indirect 169 | github.com/hashicorp/yamux v0.1.1 // indirect 170 | github.com/huandu/xstrings v1.4.0 // indirect 171 | github.com/imdario/mergo v0.3.16 // indirect 172 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect 173 | github.com/jackc/pgconn v1.14.3 // indirect 174 | github.com/jackc/pgio v1.0.0 // indirect 175 | github.com/jackc/pgpassfile v1.0.0 // indirect 176 | github.com/jackc/pgproto3/v2 v2.3.3 // indirect 177 | github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect 178 | github.com/jackc/pgtype v1.14.3 // indirect 179 | github.com/jackc/pgx/v4 v4.18.3 // indirect 180 | github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f // indirect 181 | github.com/jefferai/jsonx v1.0.1 // indirect 182 | github.com/jmespath/go-jmespath v0.4.0 // indirect 183 | github.com/josharian/intern v1.0.0 // indirect 184 | github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531 // indirect 185 | github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f // indirect 186 | github.com/json-iterator/go v1.1.12 // indirect 187 | github.com/kelseyhightower/envconfig v1.4.0 // indirect 188 | github.com/klauspost/compress v1.17.8 // indirect 189 | github.com/kylelemons/godebug v1.1.0 // indirect 190 | github.com/linode/linodego v0.7.1 // indirect 191 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect 192 | github.com/mailru/easyjson v0.7.7 // indirect 193 | github.com/mattn/go-colorable v0.1.13 // indirect 194 | github.com/mattn/go-isatty v0.0.20 // indirect 195 | github.com/miekg/dns v1.1.50 // indirect 196 | github.com/mitchellh/copystructure v1.2.0 // indirect 197 | github.com/mitchellh/go-homedir v1.1.0 // indirect 198 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 199 | github.com/mitchellh/mapstructure v1.5.0 // indirect 200 | github.com/mitchellh/pointerstructure v1.2.1 // indirect 201 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 202 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 203 | github.com/modern-go/reflect2 v1.0.2 // indirect 204 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 205 | github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 // indirect 206 | github.com/oklog/run v1.1.0 // indirect 207 | github.com/oklog/ulid v1.3.1 // indirect 208 | github.com/okta/okta-sdk-golang/v2 v2.12.1 // indirect 209 | github.com/opencontainers/go-digest v1.0.0 // indirect 210 | github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect 211 | github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect 212 | github.com/oracle/oci-go-sdk/v60 v60.0.0 // indirect 213 | github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c // indirect 214 | github.com/patrickmn/go-cache v2.1.0+incompatible // indirect 215 | github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect 216 | github.com/pierrec/lz4 v2.6.1+incompatible // indirect 217 | github.com/pires/go-proxyproto v0.7.0 // indirect 218 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect 219 | github.com/pkg/errors v0.9.1 // indirect 220 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 221 | github.com/posener/complete v1.2.3 // indirect 222 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect 223 | github.com/pquerna/otp v1.2.1-0.20191009055518-468c2dd2b58d // indirect 224 | github.com/prometheus/client_golang v1.19.0 // indirect 225 | github.com/prometheus/client_model v0.6.0 // indirect 226 | github.com/prometheus/common v0.49.0 // indirect 227 | github.com/prometheus/procfs v0.12.0 // indirect 228 | github.com/rboyer/safeio v0.2.1 // indirect 229 | github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 // indirect 230 | github.com/ryanuber/go-glob v1.0.0 // indirect 231 | github.com/sasha-s/go-deadlock v0.2.0 // indirect 232 | github.com/segmentio/fasthash v1.0.3 // indirect 233 | github.com/sethvargo/go-limiter v0.7.1 // indirect 234 | github.com/shirou/gopsutil/v3 v3.22.6 // indirect 235 | github.com/shopspring/decimal v1.3.1 // indirect 236 | github.com/sirupsen/logrus v1.9.3 // indirect 237 | github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d // indirect 238 | github.com/sony/gobreaker v0.5.0 // indirect 239 | github.com/spf13/cast v1.6.0 // indirect 240 | github.com/spf13/pflag v1.0.5 // indirect 241 | github.com/stretchr/objx v0.5.2 // indirect 242 | github.com/tencentcloud/tencentcloud-sdk-go v1.0.162 // indirect 243 | github.com/tklauser/go-sysconf v0.3.10 // indirect 244 | github.com/tklauser/numcpus v0.4.0 // indirect 245 | github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c // indirect 246 | github.com/vmware/govmomi v0.18.0 // indirect 247 | github.com/yusufpapurcu/wmi v1.2.2 // indirect 248 | go.etcd.io/bbolt v1.3.7 // indirect 249 | go.mongodb.org/mongo-driver v1.14.0 // indirect 250 | go.opencensus.io v0.24.0 // indirect 251 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect 252 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect 253 | go.opentelemetry.io/otel v1.27.0 // indirect 254 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect 255 | go.opentelemetry.io/otel/metric v1.27.0 // indirect 256 | go.opentelemetry.io/otel/trace v1.27.0 // indirect 257 | go.uber.org/atomic v1.11.0 // indirect 258 | golang.org/x/crypto v0.24.0 // indirect 259 | golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect 260 | golang.org/x/mod v0.17.0 // indirect 261 | golang.org/x/net v0.26.0 // indirect 262 | golang.org/x/oauth2 v0.20.0 // indirect 263 | golang.org/x/sync v0.7.0 // indirect 264 | golang.org/x/sys v0.21.0 // indirect 265 | golang.org/x/term v0.21.0 // indirect 266 | golang.org/x/text v0.16.0 // indirect 267 | golang.org/x/time v0.5.0 // indirect 268 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect 269 | google.golang.org/api v0.181.0 // indirect 270 | google.golang.org/genproto v0.0.0-20240520151616-dc85e6b867a5 // indirect 271 | google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect 272 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect 273 | google.golang.org/grpc v1.64.0 // indirect 274 | google.golang.org/protobuf v1.34.1 // indirect 275 | gopkg.in/inf.v0 v0.9.1 // indirect 276 | gopkg.in/ini.v1 v1.67.0 // indirect 277 | gopkg.in/resty.v1 v1.12.0 // indirect 278 | gopkg.in/square/go-jose.v2 v2.6.0 // indirect 279 | gopkg.in/yaml.v2 v2.4.0 // indirect 280 | gopkg.in/yaml.v3 v3.0.1 // indirect 281 | k8s.io/api v0.29.3 // indirect 282 | k8s.io/apimachinery v0.29.3 // indirect 283 | k8s.io/client-go v0.29.3 // indirect 284 | k8s.io/klog/v2 v2.110.1 // indirect 285 | k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect 286 | k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect 287 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 288 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 289 | sigs.k8s.io/yaml v1.3.0 // indirect 290 | ) 291 | -------------------------------------------------------------------------------- /internal/api/api.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Igor Zibarev 3 | Copyright 2018 The envexec Authors 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package api 19 | 20 | // Prefix is a common namespace prefix for versions. 21 | const Prefix = "envexec" 22 | 23 | const ( 24 | // KindVaultSecrets is a VaultSecrets kind. 25 | KindVaultSecrets = "VaultSecrets" 26 | ) 27 | 28 | const ( 29 | // VersionV1alpha1 is a v1alpha1 api version. 30 | VersionV1alpha1 = "v1alpha1" 31 | ) 32 | 33 | // Spec is a versioned kind of spec. 34 | type Spec struct { 35 | ApiVersion string `json:"apiVersion"` 36 | Kind string `json:"kind"` 37 | } 38 | -------------------------------------------------------------------------------- /internal/api/v1alpha1/vault_secrets.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Igor Zibarev 3 | Copyright 2018 The envexec Authors 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package v1alpha1 19 | 20 | // VaultSecretsSpec is a spec for Vault secrets. 21 | type VaultSecretsSpec struct { 22 | Secrets []VaultSecret `json:"secrets"` 23 | } 24 | 25 | // VaultSecret defines Vault secret. 26 | type VaultSecret struct { 27 | Path string `json:"path"` 28 | Key string `json:"key"` 29 | Env string `json:"env"` 30 | } 31 | -------------------------------------------------------------------------------- /internal/api/v1alpha1/vault_secrets_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Igor Zibarev 3 | Copyright 2018 The envexec Authors 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | "testing" 22 | 23 | "github.com/ghodss/yaml" 24 | "github.com/stretchr/testify/assert" 25 | ) 26 | 27 | func TestVaultSecretsSpec_fromYaml(t *testing.T) { 28 | testCases := []struct { 29 | input string 30 | spec VaultSecretsSpec 31 | expectError bool 32 | }{ 33 | { 34 | input: ` 35 | apiVersion: envexec/v1alpha1 36 | kind: VaultSecrets 37 | secrets: 38 | - path: secret/namespace/service/some 39 | key: api_key 40 | env: SOME_API_KEY 41 | - path: secret/namespace/service/db 42 | key: password 43 | env: DB_PASSWORD 44 | 45 | `, 46 | spec: VaultSecretsSpec{ 47 | Secrets: []VaultSecret{ 48 | { 49 | Path: "secret/namespace/service/some", 50 | Key: "api_key", 51 | Env: "SOME_API_KEY", 52 | }, 53 | { 54 | Path: "secret/namespace/service/db", 55 | Key: "password", 56 | Env: "DB_PASSWORD", 57 | }, 58 | }, 59 | }, 60 | expectError: false, 61 | }, 62 | } 63 | 64 | for _, tc := range testCases { 65 | var spec VaultSecretsSpec 66 | err := yaml.Unmarshal([]byte(tc.input), &spec) 67 | 68 | if tc.expectError { 69 | assert.Error(t, err) 70 | } else { 71 | assert.NoError(t, err) 72 | } 73 | 74 | assert.Equal(t, tc.spec, spec) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /internal/envexec/config.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Igor Zibarev 3 | Copyright 2018 The envexec Authors 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package envexec 19 | 20 | import ( 21 | "fmt" 22 | "os" 23 | "strings" 24 | 25 | "github.com/integrii/flaggy" 26 | ) 27 | 28 | // Config defines configuration for envexec. 29 | type Config struct { 30 | bin string 31 | args []string 32 | 33 | specs []string 34 | } 35 | 36 | // LoadConfig configures envexec using environment variables and CLI flags. 37 | func LoadConfig() (*Config, error) { 38 | c := &Config{} 39 | 40 | if err := c.parseEnvironment(); err != nil { 41 | return nil, err 42 | } 43 | 44 | p := c.newCLI() 45 | if err := c.parseCLI(p); err != nil { 46 | return nil, err 47 | } 48 | 49 | return c, nil 50 | } 51 | 52 | func (c *Config) newCLI() *flaggy.Parser { 53 | p := flaggy.NewParser("envexec") 54 | p.Description = `a tool that executes any command or application provisioning its 55 | environment with variables from providers (like secrets from Vault).` 56 | p.AdditionalHelpPrepend = ` 57 | https://github.com/hypnoglow/envexec` 58 | p.AdditionalHelpAppend = ` 59 | Environment Variables: 60 | ENVEXEC_SPEC_FILES - A list of specification files in YAML format (can specify multiple, delimited by comma). 61 | 62 | Examples: 63 | envexec --spec-file vaultsecrets.yaml /usr/bin/env 64 | envexec /usr/bin/myapp --spec-file spec.yaml -- --arg-for myapp` 65 | 66 | return p 67 | } 68 | 69 | func (c *Config) parseEnvironment() error { 70 | specs := strings.TrimSpace(os.Getenv("ENVEXEC_SPEC_FILES")) 71 | if specs != "" { 72 | c.specs = strings.Split(specs, ",") 73 | } 74 | return nil 75 | } 76 | 77 | func (c *Config) parseCLI(p *flaggy.Parser) error { 78 | var specFiles []string 79 | var binary string 80 | 81 | p.StringSlice(&specFiles, "", "spec-file", "A list of specification files in YAML format (can specify multiple). Takes precedence over ENVEXEC_SPEC_FILES.") 82 | p.AddPositionalValue(&binary, "cmd", 1, false, "A command or an application to exec.") 83 | 84 | err := p.Parse() 85 | if err != nil { 86 | return fmt.Errorf("parse command: %v", err) 87 | } 88 | 89 | args := p.TrailingArguments 90 | if binary == "" && 91 | len(args) > 0 && 92 | !strings.HasPrefix(args[0], "-") { 93 | binary = args[0] 94 | args = args[1:] 95 | } 96 | 97 | if binary == "" { 98 | return fmt.Errorf("binary to run not specified") 99 | } 100 | 101 | c.bin = binary 102 | c.args = args 103 | 104 | if len(specFiles) > 0 { 105 | // NOTE: this overwrites ENVEXEC_SPEC_FILES env var. 106 | c.specs = specFiles 107 | } 108 | 109 | return nil 110 | } 111 | -------------------------------------------------------------------------------- /internal/envexec/envexec.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Igor Zibarev 3 | Copyright 2018 The envexec Authors 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package envexec 19 | 20 | import ( 21 | "fmt" 22 | "log" 23 | "os" 24 | "os/exec" 25 | "syscall" 26 | 27 | "github.com/hypnoglow/envexec/internal/spec" 28 | ) 29 | 30 | // New returns a new envexec application. 31 | func New(conf *Config) *Envexec { 32 | return &Envexec{ 33 | conf: conf, 34 | } 35 | } 36 | 37 | // Envexec defines the application. 38 | type Envexec struct { 39 | conf *Config 40 | } 41 | 42 | // Run runs envexec. 43 | func (e Envexec) Run() error { 44 | envs := make(map[string]string) 45 | 46 | for _, fpath := range e.conf.specs { 47 | if err := e.loadEnvsFromFile(fpath, envs); err != nil { 48 | return err 49 | } 50 | } 51 | 52 | if err := e.execBinary(e.conf.bin, e.conf.args, envs); err != nil { 53 | return err 54 | } 55 | 56 | return nil 57 | } 58 | 59 | func (e Envexec) loadEnvsFromFile(fpath string, envs map[string]string) error { 60 | f, err := os.Open(fpath) 61 | if err != nil { 62 | return fmt.Errorf("open spec file: %v", err) 63 | } 64 | defer f.Close() 65 | 66 | l, err := spec.Loader(f) 67 | if err != nil { 68 | return fmt.Errorf("create loader for spec file %v: %s", fpath, err) 69 | } 70 | 71 | ee, err := l.Load() 72 | if err != nil { 73 | return fmt.Errorf("load envs by spec file %s: %v", fpath, err) 74 | } 75 | 76 | for k, v := range ee { 77 | if _, ok := envs[k]; ok { 78 | log.Printf("WARNING: duplicate environment variable name %s - will keep last value", k) 79 | } 80 | envs[k] = v 81 | } 82 | 83 | return nil 84 | } 85 | 86 | func (e Envexec) execBinary(bin string, args []string, envs map[string]string) error { 87 | binary, err := exec.LookPath(bin) 88 | if err != nil { 89 | return fmt.Errorf("look path: %v", err) 90 | } 91 | 92 | args = append([]string{binary}, args...) 93 | 94 | environ := os.Environ() 95 | for k, v := range envs { 96 | environ = append(environ, fmt.Sprintf("%s=%s", k, v)) 97 | } 98 | 99 | err = syscall.Exec(binary, args, environ) 100 | if err != nil { 101 | return fmt.Errorf("exec: %v", err) 102 | } 103 | 104 | return nil 105 | } 106 | -------------------------------------------------------------------------------- /internal/loaders/fake/fake.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Igor Zibarev 3 | Copyright 2018 The envexec Authors 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package fake 19 | 20 | // FakeLoader is a simple loader that returns predefined values. 21 | // Only useful for testing. 22 | type FakeLoader struct { 23 | Values map[string]string 24 | Error error 25 | } 26 | 27 | // Load returns predefined key-values. 28 | func (l FakeLoader) Load() (map[string]string, error) { 29 | return l.Values, l.Error 30 | } 31 | -------------------------------------------------------------------------------- /internal/loaders/loader.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Igor Zibarev 3 | Copyright 2018 The envexec Authors 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package loaders 19 | 20 | // Loader can load key-values from its source. 21 | type Loader interface { 22 | Load() (map[string]string, error) 23 | } 24 | -------------------------------------------------------------------------------- /internal/loaders/vault/vault_secrets.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Igor Zibarev 3 | Copyright 2018 The envexec Authors 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package vault 19 | 20 | import ( 21 | "fmt" 22 | 23 | "github.com/hypnoglow/envexec/internal/api/v1alpha1" 24 | "github.com/hypnoglow/envexec/internal/providers/vault" 25 | ) 26 | 27 | // NewSecretsLoader returns a new SecretsLoader. 28 | func NewSecretsLoader(spec v1alpha1.VaultSecretsSpec) *SecretsLoader { 29 | return &SecretsLoader{spec: spec} 30 | } 31 | 32 | // SecretsLoader is a loader that loads secrets from Vault. 33 | type SecretsLoader struct { 34 | spec v1alpha1.VaultSecretsSpec 35 | } 36 | 37 | // Load loads secrets from Vault and returns them as key-values. 38 | func (l *SecretsLoader) Load() (map[string]string, error) { 39 | vlt, err := vault.NewClient() 40 | if err != nil { 41 | return nil, fmt.Errorf("create vault client: %v", err) 42 | } 43 | 44 | envs := make(map[string]string) 45 | 46 | for path, ss := range mapByPath(l.spec.Secrets) { 47 | secret, err := vlt.Logical().Read(path) 48 | if err != nil { 49 | return nil, fmt.Errorf("read secret from vault: %v", err) 50 | } 51 | if secret == nil { 52 | return nil, fmt.Errorf("no secret found by path %v", path) 53 | } 54 | 55 | for _, s := range ss { 56 | v, ok := secret.Data[s.Key] 57 | if !ok { 58 | return nil, fmt.Errorf("key %s not found in vault secret", s.Key) 59 | } 60 | 61 | envs[s.Env] = fmt.Sprintf("%v", v) 62 | } 63 | } 64 | 65 | return envs, nil 66 | } 67 | 68 | func mapByPath(secrets []v1alpha1.VaultSecret) map[string][]v1alpha1.VaultSecret { 69 | m := make(map[string][]v1alpha1.VaultSecret) 70 | for _, secret := range secrets { 71 | m[secret.Path] = append(m[secret.Path], secret) 72 | } 73 | return m 74 | } 75 | -------------------------------------------------------------------------------- /internal/loaders/vault/vault_secrets_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Igor Zibarev 3 | Copyright 2018 The envexec Authors 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package vault 19 | 20 | import ( 21 | "os" 22 | "testing" 23 | 24 | hclog "github.com/hashicorp/go-hclog" 25 | "github.com/hashicorp/vault/api" 26 | "github.com/hashicorp/vault/http" 27 | "github.com/hashicorp/vault/vault" 28 | "github.com/stretchr/testify/assert" 29 | "github.com/stretchr/testify/require" 30 | 31 | "github.com/hypnoglow/envexec/internal/api/v1alpha1" 32 | ) 33 | 34 | func TestSecretsLoader_Load(t *testing.T) { 35 | 36 | // init vault & secrets 37 | 38 | vlt, teardown := setup(t) 39 | defer teardown() 40 | 41 | var err error 42 | _, err = vlt.Logical().Write("secret/foo", map[string]interface{}{ 43 | "passwd": "k8jghl37s;fn2hs", 44 | }) 45 | require.NoError(t, err) 46 | _, err = vlt.Logical().Write("secret/bar", map[string]interface{}{ 47 | "a_token": "super-secret-token", 48 | "secret_number": 123, 49 | }) 50 | require.NoError(t, err) 51 | 52 | // prepare spec 53 | 54 | s := v1alpha1.VaultSecretsSpec{ 55 | Secrets: []v1alpha1.VaultSecret{ 56 | { 57 | Path: "secret/foo", 58 | Key: "passwd", 59 | Env: "PASSWD", 60 | }, 61 | { 62 | Path: "secret/bar", 63 | Key: "a_token", 64 | Env: "TOKEN", 65 | }, 66 | { 67 | Path: "secret/bar", 68 | Key: "secret_number", 69 | Env: "SECRET_NUMBER", 70 | }, 71 | }, 72 | } 73 | 74 | // get envs & compare 75 | 76 | l := NewSecretsLoader(s) 77 | envs, err := l.Load() 78 | assert.NoError(t, err) 79 | 80 | expected := map[string]string{ 81 | "PASSWD": "k8jghl37s;fn2hs", 82 | "TOKEN": "super-secret-token", 83 | "SECRET_NUMBER": "123", 84 | } 85 | assert.Equal(t, expected, envs) 86 | } 87 | 88 | func setup(t *testing.T) (vlt *api.Client, teardown func()) { 89 | // Disable noisy logger of vault core. 90 | dn, err := os.Open(os.DevNull) 91 | assert.NoError(t, err) 92 | hclog.DefaultOutput = dn 93 | 94 | core, _, token := vault.TestCoreUnsealed(t) 95 | 96 | ln, addr := http.TestServer(t, core) 97 | http.TestServerAuth(t, addr, token) 98 | 99 | vlt, err = api.NewClient(&api.Config{ 100 | Address: addr, 101 | }) 102 | require.NoError(t, err) 103 | 104 | vlt.SetToken(token) 105 | 106 | os.Setenv("VAULT_ADDR", addr) 107 | os.Setenv("VAULT_AUTH_METHOD", "token") 108 | os.Setenv("VAULT_TOKEN", token) 109 | 110 | return vlt, func() { 111 | ln.Close() 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /internal/providers/vault/auth.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Igor Zibarev 3 | Copyright 2018 The envexec Authors 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package vault 19 | 20 | import "fmt" 21 | 22 | // AuthMethod is vault authentication method. 23 | // See: https://www.vaultproject.io/docs/auth/index.html 24 | type AuthMethod string 25 | 26 | const ( 27 | // AuthMethodToken represents authentication method using static token. 28 | // See: https://www.vaultproject.io/docs/auth/token.html 29 | AuthMethodToken AuthMethod = "token" 30 | 31 | // AuthMethodKubernetes represents authentication method using kubernetes. 32 | // See: https://www.vaultproject.io/docs/auth/kubernetes.html 33 | AuthMethodKubernetes AuthMethod = "kubernetes" 34 | ) 35 | 36 | // UnmarshalText implements encoding.TextUnmarshaler. 37 | func (m *AuthMethod) UnmarshalText(text []byte) error { 38 | txt := string(text) 39 | switch txt { 40 | case "token": 41 | *m = AuthMethodToken 42 | case "kubernetes": 43 | *m = AuthMethodKubernetes 44 | case "": // by default use token method 45 | *m = AuthMethodToken 46 | default: 47 | return fmt.Errorf("unknown auth method: %q", txt) 48 | } 49 | 50 | return nil 51 | } 52 | 53 | // authMethod can get authentication token for Vault. 54 | type authMethod interface { 55 | GetToken() (string, error) 56 | } 57 | -------------------------------------------------------------------------------- /internal/providers/vault/auth_kubernetes.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Igor Zibarev 3 | Copyright 2018 The envexec Authors 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package vault 19 | 20 | import ( 21 | "fmt" 22 | "io/ioutil" 23 | "os" 24 | "strings" 25 | 26 | "github.com/hashicorp/vault/api" 27 | ) 28 | 29 | const defaultTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" 30 | 31 | // newKubernetesAuth returns new Kubernetes auth method. 32 | func newKubernetesAuth(vc *api.Client) *kubernetesAuth { 33 | return &kubernetesAuth{ 34 | vault: vc, 35 | } 36 | } 37 | 38 | // kubernetesAuth implements authMethod using Kubernetes. 39 | // See: https://www.vaultproject.io/docs/auth/kubernetes.html 40 | type kubernetesAuth struct { 41 | vault *api.Client 42 | } 43 | 44 | // GetToken returns token using Kubernetes auth method. 45 | func (k *kubernetesAuth) GetToken() (string, error) { 46 | role := os.Getenv("VAULT_AUTH_KUBERNETES_ROLE") 47 | if role == "" { 48 | return "", fmt.Errorf("vault Kubernetes auth role is empty, use VAULT_AUTH_KUBERNETES_ROLE to set") 49 | } 50 | 51 | fpath := os.Getenv("VAULT_AUTH_KUBERNETES_TOKEN_FILE") 52 | if fpath == "" { 53 | fpath = defaultTokenFile 54 | } 55 | 56 | b, err := ioutil.ReadFile(fpath) 57 | if err != nil { 58 | return "", fmt.Errorf("read token file: %v", err) 59 | } 60 | jwt := strings.TrimSpace(string(b)) 61 | 62 | token, err := k.login(jwt, role) 63 | if err != nil { 64 | return "", fmt.Errorf("login to vault: %v", err) 65 | } 66 | 67 | return token, nil 68 | } 69 | 70 | func (k *kubernetesAuth) login(jwt, role string) (token string, err error) { 71 | data := map[string]interface{}{ 72 | "role": role, 73 | "jwt": jwt, 74 | } 75 | 76 | response, err := k.vault.Logical().Write("auth/kubernetes/login", data) 77 | if err != nil { 78 | return "", fmt.Errorf("vault login: %v", err) 79 | } 80 | if response.Auth == nil { 81 | return "", fmt.Errorf("no authentication information attached to login response") 82 | } 83 | 84 | return response.Auth.ClientToken, nil 85 | } 86 | -------------------------------------------------------------------------------- /internal/providers/vault/auth_token.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Igor Zibarev 3 | Copyright 2018 The envexec Authors 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package vault 19 | 20 | import "os" 21 | 22 | // newEnvironmentAuth returns new token auth method. 23 | func newTokenAuth() *tokenAuth { 24 | return &tokenAuth{} 25 | } 26 | 27 | // tokenAuth implements authMethod using static token. 28 | // See: https://www.vaultproject.io/docs/auth/token.html 29 | type tokenAuth struct{} 30 | 31 | // GetToken returns token from the environment variable. 32 | func (e *tokenAuth) GetToken() (string, error) { 33 | return os.Getenv("VAULT_TOKEN"), nil 34 | } 35 | -------------------------------------------------------------------------------- /internal/providers/vault/vault.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Igor Zibarev 3 | Copyright 2018 The envexec Authors 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package vault 19 | 20 | import ( 21 | "fmt" 22 | "os" 23 | 24 | "github.com/hashicorp/vault/api" 25 | ) 26 | 27 | // NewClient is a constructor for Vault client. 28 | func NewClient() (*api.Client, error) { 29 | vc, err := defaultVaultClient() 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | m := os.Getenv("VAULT_AUTH_METHOD") 35 | var method AuthMethod 36 | if err = method.UnmarshalText([]byte(m)); err != nil { 37 | return nil, err 38 | } 39 | 40 | var auth authMethod 41 | switch method { 42 | case AuthMethodToken: 43 | auth = newTokenAuth() 44 | case AuthMethodKubernetes: 45 | auth = newKubernetesAuth(vc) 46 | default: 47 | return nil, fmt.Errorf("unknown auth method: %v", method) 48 | } 49 | 50 | token, err := auth.GetToken() 51 | if err != nil { 52 | return nil, fmt.Errorf("get token: %v", err) 53 | } 54 | 55 | vc.SetToken(token) 56 | 57 | return vc, nil 58 | } 59 | 60 | func defaultVaultClient() (*api.Client, error) { 61 | // Read config from env (VAULT_ADDR, VAULT_SKIP_VERIFY, etc) 62 | config := api.DefaultConfig() 63 | err := config.ReadEnvironment() 64 | if err != nil { 65 | return nil, fmt.Errorf("read environment for vault config: %v", err) 66 | } 67 | 68 | return api.NewClient(config) 69 | } 70 | -------------------------------------------------------------------------------- /internal/spec/loader.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Igor Zibarev 3 | Copyright 2018 The envexec Authors 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package spec 19 | 20 | import ( 21 | "fmt" 22 | "io" 23 | "io/ioutil" 24 | "strings" 25 | 26 | "github.com/ghodss/yaml" 27 | 28 | "github.com/hypnoglow/envexec/internal/api" 29 | "github.com/hypnoglow/envexec/internal/api/v1alpha1" 30 | "github.com/hypnoglow/envexec/internal/loaders" 31 | "github.com/hypnoglow/envexec/internal/loaders/vault" 32 | ) 33 | 34 | // Loader returns a loader that can load values defined by spec in reader r. 35 | func Loader(r io.Reader) (loaders.Loader, error) { 36 | b, err := ioutil.ReadAll(r) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | var spec api.Spec 42 | if err = yaml.Unmarshal(b, &spec); err != nil { 43 | return nil, err 44 | } 45 | 46 | ver := strings.TrimPrefix(spec.ApiVersion, api.Prefix+"/") 47 | 48 | switch spec.Kind { 49 | case api.KindVaultSecrets: 50 | switch ver { 51 | case api.VersionV1alpha1: 52 | var s v1alpha1.VaultSecretsSpec 53 | if err = yaml.Unmarshal(b, &s); err != nil { 54 | return nil, err 55 | } 56 | return vault.NewSecretsLoader(s), nil 57 | } 58 | } 59 | 60 | return nil, fmt.Errorf("unknown kind and/or apiVersion: %s %s", spec.Kind, spec.ApiVersion) 61 | } 62 | -------------------------------------------------------------------------------- /internal/spec/loader_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Igor Zibarev 3 | Copyright 2018 The envexec Authors 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package spec 19 | 20 | import ( 21 | "bytes" 22 | "testing" 23 | 24 | "github.com/stretchr/testify/assert" 25 | ) 26 | 27 | func TestLoader_vaultSecrets(t *testing.T) { 28 | r := bytes.NewBufferString(` 29 | apiVersion: envexec/v1alpha1 30 | kind: VaultSecrets 31 | secrets: 32 | - path: secret/namespace/service/some 33 | key: api_key 34 | env: SOME_API_KEY 35 | - path: secret/namespace/service/db 36 | key: password 37 | env: DB_PASSWORD 38 | `) 39 | _, err := Loader(r) 40 | assert.NoError(t, err) 41 | } 42 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Igor Zibarev 3 | Copyright 2018 The envexec Authors 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package main 19 | 20 | import ( 21 | "log" 22 | 23 | "github.com/hypnoglow/envexec/internal/envexec" 24 | ) 25 | 26 | func main() { 27 | conf, err := envexec.LoadConfig() 28 | if err != nil { 29 | log.Fatal("Load configuration: ", err) 30 | } 31 | 32 | ee := envexec.New(conf) 33 | if err = ee.Run(); err != nil { 34 | log.Fatal(err) 35 | } 36 | } 37 | --------------------------------------------------------------------------------