Skip to content

Commit

Permalink
Merge pull request #34 from h0n9/feature/33-decode-base64-encoded-string
Browse files Browse the repository at this point in the history
Add a feature for decoding base64-encoded string
  • Loading branch information
h0n9 authored Jun 6, 2024
2 parents d746ffa + 66b45cd commit c3bf491
Show file tree
Hide file tree
Showing 12 changed files with 171 additions and 77 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: ci
on:
workflow_dispatch:
push:
branches:
- main
Expand All @@ -9,11 +10,13 @@ on:
- v*
paths-ignore:
- ".github/**"
- "docs/**"
- "*.md"
env:
img-registry: ghcr.io/h0n9
img-repository: cloud-secrets-manager
img-tags: ghcr.io/h0n9/cloud-secrets-manager:tmp
img-push: "false"
img-push: "true"
img-platforms: linux/amd64
jobs:
build-push:
Expand All @@ -22,7 +25,6 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- name: Login to GitHub Container Registry
if: ${{ github.ref_name == 'develop' || startsWith(github.ref_name, 'v') }}
uses: docker/login-action@v2
with:
registry: ${{ env.img-registry }}
Expand All @@ -31,11 +33,9 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: "Set env vars (develop)"
if: ${{ github.ref_name == 'develop' }}
shell: bash
run: |
echo "img-tags=${{ env.img-registry }}/${{ env.img-repository }}:dev-${GITHUB_SHA::6}" >> $GITHUB_ENV
echo "img-push=true" >> $GITHUB_ENV
- name: "Set env vars (tag)"
if: ${{ startsWith(github.ref_name, 'v') }}
shell: bash
Expand Down
38 changes: 31 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,14 @@ of other new charts.
The following annotatins are required to inject `cloud-secrets-injector` into
pods:

| **Key** | **Required** | **Description** | **Example** |
|----------------------------------------------------|--------------|---------------------------|----------------------------------------------------------|
| `cloud-secrets-manager.h0n9.postie.chat/provider` | true | Cloud Provider Name | `aws` |
| `cloud-secrets-manager.h0n9.postie.chat/secret-id` | true | Secret Name | `very-precious-secret` |
| `cloud-secrets-manager.h0n9.postie.chat/template` | true | Template for secret value | ```{{ range $k, $v := . }}{{ $k }}={{ $v }} {{ end }}``` |
| `cloud-secrets-manager.h0n9.postie.chat/output` | true | File path for output | `/secrets/env` |
| `cloud-secrets-manager.h0n9.postie.chat/injected` | false | Identifier for injection | `false` |
| **Key** | **Required** | **Description** | **Example** |
|--------------------------------------------------------|--------------|------------------------------------|----------------------------------------------------------|
| `cloud-secrets-manager.h0n9.postie.chat/provider` | true | Cloud Provider Name | `aws` |
| `cloud-secrets-manager.h0n9.postie.chat/secret-id` | true | Secret Name | `very-precious-secret` |
| `cloud-secrets-manager.h0n9.postie.chat/template` | true | Template for secret value | ```{{ range $k, $v := . }}{{ $k }}={{ $v }} {{ end }}``` |
| `cloud-secrets-manager.h0n9.postie.chat/output` | true | File path for output | `/secrets/env` |
| `cloud-secrets-manager.h0n9.postie.chat/decode-base64` | false | Decode base64-encoded secret value | `true` or `false` |
| `cloud-secrets-manager.h0n9.postie.chat/injected` | false | Identifier for injection | `false` |

#### Annotations for Multiple Secrets Injection

Expand Down Expand Up @@ -117,6 +118,29 @@ cloud-secrets-manager.h0n9.postie.chat/template-config-secrets: |
Just add `<secret-name>` at the end of each annotation key, like
`cloud-secrets-manager.h0n9.postie.chat/provider-<secret-name>`. That's it!

#### Annotation for Decoding Base64-encoded Secret Value

From the version `v0.6`, you can decode base64-encoded secret values by setting
the `cloud-secrets-manager.h0n9.postie.chat/decode-base64` annotation to `true`.

```yaml
cloud-secrets-manager.h0n9.postie.chat/provider-cert: aws
cloud-secrets-manager.h0n9.postie.chat/secret-id-cert: very-precious-secret
cloud-secrets-manager.h0n9.postie.chat/output-cert: /secrets/precious.cer
cloud-secrets-manager.h0n9.postie.chat/template-cert: |
{{ .base64-encoded-precious-cert }}
cloud-secrets-manager.h0n9.postie.chat/decode-base64-cert: "true"
cloud-secrets-manager.h0n9.postie.chat/provider-key: aws
cloud-secrets-manager.h0n9.postie.chat/secret-id-key: very-precious-secret
cloud-secrets-manager.h0n9.postie.chat/output-key: /secrets/precious.key
cloud-secrets-manager.h0n9.postie.chat/template-key: |
{{ .base64-encoded-precious-key }}
cloud-secrets-manager.h0n9.postie.chat/decode-base64-key: "true"
```

This feature is useful when you want to inject a base64-encoded secret value as
a file into a pod.

### Providers

Supported providers require the annotations mentioned above in common. However,
Expand Down
6 changes: 0 additions & 6 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@ import (
"github.com/spf13/cobra"
)

const (
DefaultProviderName = "aws"
DefaultTemplateBase64 = "e3sgcmFuZ2UgJGssICR2IDo9IC4gfX1be3sgJGsgfX1dCnt7ICR2IH19Cgp7eyBlbmQgfX0K"
DefaultOutputFilename = "output"
)

var VersionCmd = &cobra.Command{
Use: "version",
Short: fmt.Sprintf("print '%s' version information", csm.Name),
Expand Down
38 changes: 25 additions & 13 deletions cli/injector/injector.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ import (

const (
DefaultProviderName = "aws"
DefaultSecretID = ""
DefaultTemplateBase64 = "e3sgcmFuZ2UgJGssICR2IDo9IC4gfX1be3sgJGsgfX1dCnt7ICR2IH19Cgp7eyBlbmQgfX0K"
DefaultOutputFilename = "output"
DefaultOutputFilename = ""
DefaultDecodeBase64 = false
)

var Cmd = &cobra.Command{
Expand All @@ -27,10 +29,11 @@ var Cmd = &cobra.Command{
}

var (
providerName string
secretID string
templateBase64 string
output string
providerName string
secretID string
templateBase64 string
outputFilename string
decodeBase64EncodedSecret bool
)

var runCmd = &cobra.Command{
Expand All @@ -45,15 +48,18 @@ var runCmd = &cobra.Command{

logger.Info().Msg("initialized context")

// get envs
// check required flags
if secretID == "" {
return fmt.Errorf("failed to read 'secret-id' flag")
}
if outputFilename == "" {
return fmt.Errorf("failed to read 'output' flag")
}

logger.Info().Msg("read environment variables")

// decode base64-encoded template to string
templateStr, err := util.DecodeBase64(templateBase64)
templateStr, err := util.DecodeBase64StrToStr(templateBase64)
if err != nil {
return err
}
Expand Down Expand Up @@ -92,12 +98,12 @@ var runCmd = &cobra.Command{

logger.Info().Msg("initialized secret handler")

err = secretHandler.Save(secretID, output)
err = secretHandler.Save(secretID, outputFilename, decodeBase64EncodedSecret)
if err != nil {
return err
}

logger.Info().Msg(fmt.Sprintf("saved secret id '%s' values to '%s'", secretID, output))
logger.Info().Msg(fmt.Sprintf("saved secret id '%s' values to '%s'", secretID, outputFilename))

return nil
},
Expand All @@ -107,7 +113,7 @@ func init() {
runCmd.Flags().StringVar(
&providerName,
"provider",
"aws",
DefaultProviderName,
"cloud provider name",
)
runCmd.Flags().StringVar(
Expand All @@ -119,14 +125,20 @@ func init() {
runCmd.Flags().StringVar(
&templateBase64,
"template",
"e3sgcmFuZ2UgJGssICR2IDo9IC4gfX1be3sgJGsgfX1dCnt7ICR2IH19Cgp7eyBlbmQgfX0K",
DefaultTemplateBase64,
"base64 encoded template string",
)
runCmd.Flags().StringVar(
&output,
&outputFilename,
"output",
"secret",
DefaultOutputFilename,
"output filename",
)
runCmd.Flags().BoolVar(
&decodeBase64EncodedSecret,
"decode-b64-secret",
DefaultDecodeBase64,
"decode base64-encoded secret",
)
Cmd.AddCommand(runCmd)
}
2 changes: 1 addition & 1 deletion cli/template/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var decodeCmd = &cobra.Command{
if args[0] == "" {
return fmt.Errorf("failed to decode empty string")
}
template, err := util.DecodeBase64(args[0])
template, err := util.DecodeBase64StrToStr(args[0])
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cli/template/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var encodeCmd = &cobra.Command{
if args[0] == "" {
return fmt.Errorf("failed to encode empty string")
}
fmt.Println(util.EncodeBase64(args[0]))
fmt.Println(util.EncodeBase64StrToStr(args[0]))
return nil
},
}
2 changes: 1 addition & 1 deletion cli/template/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ var testCmd = &cobra.Command{
if args[0] == "" {
return fmt.Errorf("failed to test empty string")
}
templateStr, err := util.DecodeBase64(args[0])
templateStr, err := util.DecodeBase64StrToStr(args[0])
if err != nil {
return err
}
Expand Down
32 changes: 30 additions & 2 deletions handler/handler.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package handler

import (
"bytes"
"encoding/json"
"os"
"text/template"

"github.com/h0n9/cloud-secrets-manager/provider"
"github.com/h0n9/cloud-secrets-manager/util"
)

type SecretHandlerFunc func(string) (string, error)
Expand Down Expand Up @@ -34,17 +36,43 @@ func (handler *SecretHandler) Get(secretID string) (map[string]interface{}, erro
return m, nil
}

func (handler *SecretHandler) Save(secretID, path string) error {
func (handler *SecretHandler) Save(secretID, path string, decodeBase64EncodedSecret bool) error {
// get secret
m, err := handler.Get(secretID)
if err != nil {
return err
}

// create file
file, err := os.Create(path)
if err != nil {
return err
}
defer file.Close()

return handler.template.Execute(file, m)
// if secret is not base64 encoded, write it to file and return
if !decodeBase64EncodedSecret {
return handler.template.Execute(file, m)
}

// execute template
var buff bytes.Buffer
err = handler.template.Execute(&buff, m)
if err != nil {
return err
}

// decode base64 encoded secret
decodedSecret, err := util.DecodeBase64BytesToBytes(buff.Bytes())
if err != nil {
return err
}

// write decoded secret to file
_, err = file.Write(decodedSecret)
if err != nil {
return err
}

return nil
}
19 changes: 16 additions & 3 deletions util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,31 @@ func GetEnv(key, fallback string) string {
return fallback
}

func EncodeBase64(input string) string {
func EncodeBase64StrToStr(input string) string {
return base64.RawStdEncoding.EncodeToString([]byte(input))
}

func DecodeBase64(input string) (string, error) {
output, err := base64.RawStdEncoding.DecodeString(input)
func DecodeBase64StrToStr(input string) (string, error) {
output, err := DecodeBase64StrToBytes(input)
if err != nil {
return "", err
}
return string(output), nil
}

func DecodeBase64StrToBytes(input string) ([]byte, error) {
return base64.RawStdEncoding.DecodeString(input)
}

func DecodeBase64BytesToBytes(input []byte) ([]byte, error) {
output := make([]byte, base64.StdEncoding.DecodedLen(len(input)))
n, err := base64.StdEncoding.Decode(output, input)
if err != nil {
return nil, err
}
return output[:n], nil
}

func ReadFileToBytes(filename string) ([]byte, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
Expand Down
44 changes: 28 additions & 16 deletions webhook/annotation.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,23 @@ import (
)

const (
AnnotationProvider = "provider"
AnnotationSecretID = "secret-id"
AnnotationTemplate = "template"
AnnotationOutput = "output"
AnnotationInjected = "injected"
AnnotationProvider = "provider"
AnnotationSecretID = "secret-id"
AnnotationTemplate = "template"
AnnotationOutput = "output"
AnnotationDecodeB64 = "decode-base64"
AnnotationInjected = "injected"
)

type AnnotationSet map[string]Annotations

var AnnotationMap = map[string]string{
fmt.Sprintf("%s/%s", csm.AnnotationPrefix, AnnotationProvider): AnnotationProvider,
fmt.Sprintf("%s/%s", csm.AnnotationPrefix, AnnotationSecretID): AnnotationSecretID,
fmt.Sprintf("%s/%s", csm.AnnotationPrefix, AnnotationTemplate): AnnotationTemplate,
fmt.Sprintf("%s/%s", csm.AnnotationPrefix, AnnotationOutput): AnnotationOutput,
fmt.Sprintf("%s/%s", csm.AnnotationPrefix, AnnotationInjected): AnnotationInjected,
fmt.Sprintf("%s/%s", csm.AnnotationPrefix, AnnotationProvider): AnnotationProvider,
fmt.Sprintf("%s/%s", csm.AnnotationPrefix, AnnotationSecretID): AnnotationSecretID,
fmt.Sprintf("%s/%s", csm.AnnotationPrefix, AnnotationTemplate): AnnotationTemplate,
fmt.Sprintf("%s/%s", csm.AnnotationPrefix, AnnotationOutput): AnnotationOutput,
fmt.Sprintf("%s/%s", csm.AnnotationPrefix, AnnotationDecodeB64): AnnotationDecodeB64,
fmt.Sprintf("%s/%s", csm.AnnotationPrefix, AnnotationInjected): AnnotationInjected,
}

func ParseAnnotationSet(input map[string]string) AnnotationSet {
Expand Down Expand Up @@ -53,11 +55,12 @@ func ParseAnnotationSet(input map[string]string) AnnotationSet {
type Annotations map[string]string

var annotationsAvailable = map[string]bool{
AnnotationProvider: true,
AnnotationSecretID: true,
AnnotationTemplate: true,
AnnotationOutput: true,
AnnotationInjected: true,
AnnotationProvider: true,
AnnotationSecretID: true,
AnnotationTemplate: true,
AnnotationOutput: true,
AnnotationDecodeB64: true,
AnnotationInjected: true,
}

func ParseAndCheckAnnotations(input Annotations) Annotations {
Expand All @@ -75,7 +78,7 @@ func ParseAndCheckAnnotations(input Annotations) Annotations {
return output
}

func (a Annotations) IsInected() bool {
func (a Annotations) IsInjected() bool {
value, exist := a[AnnotationInjected]
if !exist {
return false
Expand Down Expand Up @@ -110,3 +113,12 @@ func (a Annotations) GetTemplate() (string, error) {
func (a Annotations) GetOutput() (string, error) {
return a.getValue(AnnotationOutput)
}

func (a Annotations) GetDecodeB64() (bool, error) {
value, err := a.getValue(AnnotationDecodeB64)
if err != nil {
// default value and no error
return false, nil
}
return strconv.ParseBool(value)
}
Loading

0 comments on commit c3bf491

Please sign in to comment.