Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement CLI features for getting list of secrets and setting a given key's value #26

Merged
merged 50 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
83f1b5f
Modify RootCmd commands' order
h0n9 Mar 12, 2024
0f77bbf
Add secrets package and edit command
h0n9 Mar 13, 2024
5f1d50f
Add cliSecrets package to root.go
h0n9 Mar 13, 2024
80e0107
Add SetSecretValue method to SecretProvider interface
h0n9 Mar 13, 2024
2866a0b
Add SetSecretValue method to AWS and GCP providers
h0n9 Mar 13, 2024
a628e7f
Refactor secrets package and add secret editing functionality
h0n9 Mar 13, 2024
24e6d00
Update editor variable to use DefaultEditor constant
h0n9 Mar 13, 2024
0060eed
Update secret value conversion from YAML to JSON
h0n9 Mar 13, 2024
e8444c6
Update secret value in secrets.go
h0n9 Mar 13, 2024
5e5138b
Update SetSecretValue param in SecretProvider interface
h0n9 Mar 13, 2024
26527ff
Update SetSecretValue method param in AWS and GCP providers
h0n9 Mar 13, 2024
ac1e659
Add SetSecretValue method to AWS provider
h0n9 Mar 13, 2024
c863786
Set secret value to provider in secrets.go
h0n9 Mar 13, 2024
bcd7998
Add support for unmarshaling secret value to YAML format
h0n9 Mar 13, 2024
968c259
Add AWS SDK import and update GetSecretValue method
h0n9 Mar 13, 2024
0ae16dd
Update dependencies in go.mod file
h0n9 Mar 29, 2024
a60b984
Convert JSON to YAML and YAML to JSON in secrets.go
h0n9 Mar 29, 2024
a40537b
Update secrets.go to check if tmp file is updated before reading it
h0n9 Apr 16, 2024
07f666f
Move os.Remove() into defer statement
h0n9 Apr 16, 2024
379fb22
Add ListSecrets method to SecretProvider interface
h0n9 Apr 17, 2024
da46762
Add ListSecrets method to AWS provider
h0n9 Apr 17, 2024
fd45a71
Add ListSecrets method to GCP provider
h0n9 Apr 17, 2024
d3201b6
Update GCP import in provider/gcp.go
h0n9 Apr 17, 2024
89ad4ab
Extract editCmd out of secrets.go
h0n9 Apr 17, 2024
80e4f20
Refactor ListSecrets method in AWS provider to handle pagination
h0n9 Apr 17, 2024
c1457c2
Add list command to CLI for listing secrets
h0n9 Apr 17, 2024
8407bc8
Refactor editCmd out of secrets.go
h0n9 Apr 17, 2024
081e992
Move google.golang.org/genproto to indirect
h0n9 May 2, 2024
8103e05
Add int param to limit the number of secrets to ListSecrets()
h0n9 May 2, 2024
0d47a7b
Refactor ListSecrets method in AWS provider to handle pagination and …
h0n9 May 2, 2024
0b31bc4
Refactor ListSecrets method in GCP provider to handle pagination and …
h0n9 May 2, 2024
f7d5ca9
Add limit flag to list command for CLI
h0n9 May 2, 2024
59a4870
Refactor ListSecrets method in AWS provider to limit the number of se…
h0n9 May 2, 2024
b5a2a9c
feat: Refactor ListSecrets method in GCP provider to handle paginatio…
h0n9 May 2, 2024
844ed2a
Update default limit of secrets to 10
h0n9 May 3, 2024
a6c9f64
Refactor SecretProvider interface to use consistent parameter names
h0n9 May 3, 2024
17735da
Truncate secrets if exceeded the limit
h0n9 May 3, 2024
fd3f569
Refactor list command to handle secret limit constraints
h0n9 May 3, 2024
918c92b
Add CLI feature to introduction
h0n9 May 3, 2024
2ba6927
docs: Add table of contents to README.md
h0n9 May 3, 2024
73f7561
Refactor table of contents in README.md for clarity
h0n9 May 3, 2024
a5acb44
docs: Add CLI tool section
h0n9 May 3, 2024
f3e0125
docs: Add CLI tool section and update README.md table of contents
h0n9 May 3, 2024
6242147
docs: Add CLI tool installation instructions to README.md
h0n9 May 3, 2024
d21b87e
docs: Update README.md with CLI tool installation instructions and ve…
h0n9 May 3, 2024
7c4b786
chore: Update CLI tool command in README.md for secrets management
h0n9 May 3, 2024
ee46bc6
chore: Update Docker image tags in CI workflow
h0n9 May 3, 2024
be1bfc2
chore: Update Docker image tags in CI workflow
h0n9 May 3, 2024
63abfa3
chore: Update Docker image tags and platforms in CI workflow
h0n9 May 3, 2024
ff71f61
chore: Update Docker image tags in CI workflow
h0n9 May 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ on:
env:
img-registry: ghcr.io/h0n9
img-repository: cloud-secrets-manager
img-tag: latest
img-tags: ghcr.io/h0n9/cloud-secrets-manager:tmp
img-push: "false"
img-platforms: linux/amd64
jobs:
build-push:
runs-on: ubuntu-22.04
Expand All @@ -33,18 +34,20 @@ jobs:
if: ${{ github.ref_name == 'develop' }}
shell: bash
run: |
echo "img-tag=dev-${GITHUB_SHA::6}" >> $GITHUB_ENV
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
run: |
echo "img-tag=${GITHUB_REF_NAME}" >> $GITHUB_ENV
echo "img-tags=${{ env.img-registry }}/${{ env.img-repository }}:${GITHUB_REF_NAME},${{ env.img-registry }}/${{ env.img-repository }}:latest" >> $GITHUB_ENV
echo "img-push=true" >> $GITHUB_ENV
echo "img-platforms=linux/amd64,linux/arm64" >> $GITHUB_ENV
- name: Build Docker image
uses: docker/build-push-action@v2
with:
platforms: ${{ env.img-platforms }}
push: ${{ env.img-push }}
tags: ${{ env.img-registry }}/${{ env.img-repository }}:${{ env.img-tag }}
tags: ${{ env.img-tags }}
cache-from: type=gha,scope=cloud-secrets-manager
cache-to: type=gha,mode=max,scope=cloud-secrets-manager
71 changes: 67 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,32 @@ to inject secrets strored on Cloud-based secrets managers into Kubernetes Pods,
functioning as [HashiCorp Vault's Agent Sidecar
Injector](https://www.vaultproject.io/docs/platform/k8s/injector).

## Cloud Providers

### Currently Supported
Also, it provides a convenient CLI tool with features like `list` and `edit` to
make secret management easier than using the Cloud Console. If you want to jump
into the CLI tool, please refer to the [CLI Tool](#cli-tool) section right away.

## Contents
- [Supported Cloud Providers](#cloud-providers)
- [Current](#current)
- [Planned](#planned)
- [Concept](#concept)
- [Constitution](#constitution)
- [Step-by-step](#step-by-step)
- [Installation](#installation)
- [Prerequisites](#prerequisites)
- [Using Helm chart](#using-helm-chart)
- [Usage](#usage)
- [Annotations](#annotations)
- [Providers](#providers)
- [CLI Tool](#cli-tool)

## Supported Cloud Providers

### Current
- AWS(Amazon Web Services): [Secrets Manager](https://aws.amazon.com/secrets-manager/)
- GCP(Google Cloud Platform): [Secret Manager](https://cloud.google.com/secret-manager) `(BETA)`

### TO-BE Supported
### Planned
- Azure: [Key Vault](https://azure.microsoft.com/services/key-vault/#getting-started)
- Hashicorp: [Vault](https://www.vaultproject.io)

Expand Down Expand Up @@ -106,3 +125,47 @@ following explanation.

- [AWS(Amazon Web Services)](docs/aws.md)
- [GCP(Google Cloud Platform)](docs/gcp.md)

### CLI Tool

#### Installation

As Cloud Secrets Manager is available as a Docker image, there is no need to
install the CLI tool. Just run the Docker container as follows:

```bash
$ dokcer pull ghcr.io/h0n9/cloud-secrets-manager:latest
```

You can change the tag to a specific version if you want like following:

```bash
$ dokcer pull ghcr.io/h0n9/cloud-secrets-manager:v0.5
```

#### List Secrets

```bash
$ docker run --rm -it ghcr.io/h0n9/cloud-secrets-manager:latest secrets list --provider aws --limit 3
dev/hello-world
dev/very-precious-secret
dev/another-secret
```
The `--limit` option is available to limit the number of secrets to be listed.

#### Edit Secret

```bash
$ docker run --rm -it ghcr.io/h0n9/cloud-secrets-manager:latest secrets edit --provider aws --secret-id dev/very-precious-secret
```

A text editor will be opened with the secret value. After editing, save and
close the editor to update the secret value. If you want to cancel the editing,
just close the editor without saving.

If you want to use a specific editor, set the `EDITOR` environment variable.

```bash
$ export EDITOR=nano
$ docker run --rm -it ghcr.io/h0n9/cloud-secrets-manager:latest secrets edit --provider aws --secret-id dev/very-precious-secret
```
4 changes: 3 additions & 1 deletion cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/h0n9/cloud-secrets-manager/cli/cert"
"github.com/h0n9/cloud-secrets-manager/cli/controller"
"github.com/h0n9/cloud-secrets-manager/cli/injector"
cliSecrets "github.com/h0n9/cloud-secrets-manager/cli/secrets"
cliTemplate "github.com/h0n9/cloud-secrets-manager/cli/template"
)

Expand All @@ -23,9 +24,10 @@ func init() {
RootCmd.AddCommand(
controller.Cmd,
injector.Cmd,
cert.Cmd,
newLineCmd,
cliSecrets.Cmd,
cliTemplate.Cmd,
cert.Cmd,
newLineCmd,
VersionCmd,
)
Expand Down
147 changes: 147 additions & 0 deletions cli/secrets/edit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package secrets

import (
"context"
"crypto/sha1"
"fmt"
"os"
"os/exec"
"path"
"strings"

"github.com/spf13/cobra"
"sigs.k8s.io/yaml"

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

var (
secretID string
)

var editCmd = &cobra.Command{
Use: "edit",
Short: "edit a secret",
RunE: func(cmd *cobra.Command, args []string) error {
// check secretID
if secretID == "" {
return fmt.Errorf("failed to read 'secret-id' flag")
}

// define variables
var (
err error
secretProvider provider.SecretProvider
)

// init ctx
ctx := context.Background()

// init secret provider
switch strings.ToLower(providerName) {
case "aws":
secretProvider, err = provider.NewAWS(ctx)
case "gcp":
secretProvider, err = provider.NewGCP(ctx)
default:
return fmt.Errorf("failed to figure out secret provider")
}
if err != nil {
return err
}
defer secretProvider.Close()

// get secret value
secretValue, err := secretProvider.GetSecretValue(secretID)
if err != nil {
return err
}

// convert json to yaml
data, err := yaml.JSONToYAML([]byte(secretValue))
if err != nil {
return err
}

// write data to tmp file
UserCacheDir, err := os.UserCacheDir()
if err != nil {
return err
}
hash := sha1.Sum([]byte(secretID))
tmpFilePath := path.Join(UserCacheDir, fmt.Sprintf("%x", hash))
err = os.WriteFile(tmpFilePath, data, 0644)
if err != nil {
return err
}
defer os.Remove(tmpFilePath)

// get initial stat of tmp file
initialStat, err := os.Stat(tmpFilePath)
if err != nil {
return err
}

// open tmp file with editor(e.g. vim)
editor := util.GetEnv("EDITOR", DefaultEditor)
execCmd := exec.Command(editor, tmpFilePath)
execCmd.Stdin = os.Stdin
execCmd.Stdout = os.Stdout
err = execCmd.Run()
if err != nil {
return err
}

// get updated stat of tmp file
updatedStat, err := os.Stat(tmpFilePath)
if err != nil {
return err
}

// check if tmp file is updated. if not, return nil
if initialStat.ModTime().Equal(updatedStat.ModTime()) &&
initialStat.Size() == updatedStat.Size() {
fmt.Println("found nothing to update")
return nil
}

// read tmp file
data, err = os.ReadFile(tmpFilePath)
if err != nil {
return err
}

// convert yaml to json
data, err = yaml.YAMLToJSON(data)
if err != nil {
return err
}

// update secret value
secretValue = string(data)

// set secret value to provider
err = secretProvider.SetSecretValue(secretID, secretValue)
if err != nil {
return err
}

return nil
},
}

func init() {
editCmd.Flags().StringVar(
&providerName,
"provider",
DefaultProviderName,
"cloud provider name",
)
editCmd.Flags().StringVar(
&secretID,
"secret-id",
"",
"secret id",
)
}
81 changes: 81 additions & 0 deletions cli/secrets/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package secrets

import (
"context"
"fmt"
"strings"

"github.com/spf13/cobra"

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

const (
DefaultListSecretsLimit = 10
)

var (
listSecretsLimit int
)

var listCmd = &cobra.Command{
Use: "list",
Short: "list secrets",
RunE: func(cmd *cobra.Command, args []string) error {
// define variables
var (
err error
secretProvider provider.SecretProvider
)

// check constraints
if listSecretsLimit <= 0 {
return fmt.Errorf("'--limit' flag value must be greater than 0")
}

// init ctx
ctx := context.Background()

// init secret provider
switch strings.ToLower(providerName) {
case "aws":
secretProvider, err = provider.NewAWS(ctx)
case "gcp":
secretProvider, err = provider.NewGCP(ctx)
default:
return fmt.Errorf("failed to figure out secret provider")
}
if err != nil {
return err
}
defer secretProvider.Close()

// list secrets
secrets, err := secretProvider.ListSecrets(listSecretsLimit)
if err != nil {
return err
}

// print secrets
for _, secret := range secrets {
fmt.Println(secret)
}

return nil
},
}

func init() {
listCmd.Flags().StringVar(
&providerName,
"provider",
DefaultProviderName,
"cloud provider name",
)
listCmd.Flags().IntVar(
&listSecretsLimit,
"limit",
DefaultListSecretsLimit,
"limit the number of secrets to list",
)
}
24 changes: 24 additions & 0 deletions cli/secrets/secrets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package secrets

import (
"github.com/spf13/cobra"
)

const (
DefaultProviderName = "aws"
DefaultEditor = "vim"
)

var (
providerName string
)

var Cmd = &cobra.Command{
Use: "secrets",
Short: "CLI for managing secrets",
}

func init() {
Cmd.AddCommand(listCmd)
Cmd.AddCommand(editCmd)
}
Loading
Loading