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

Command to support listing secrets #8

Merged
merged 6 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 29 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package client

import (
"context"
"fmt"
"time"

"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
Expand All @@ -23,6 +25,13 @@ type EncryptedConfig struct {
Data string `json:"data"`
}

type SecretObject struct {
Name string `json:"name"`
Location string `json:"location"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

func (c *Client) Init(ctx context.Context, bucket string, cfg Configuration) error {
return c.Service.Init(ctx, bucket, cfg)
}
Expand Down Expand Up @@ -77,6 +86,26 @@ func (c *Client) GetOrgPublicKeys(env string) (OrgPublicKeys, error) {
return OrgPublicKeys{Recipients: recps}, nil
}

type SecretListConfig struct {
Environment string `json:"environment"`
}
type SecretListResponse struct {
Secrets []SecretObject `json:"secrets"`
}

func (c *Client) GetSecretList(_ SecretListConfig) ([]SecretObject, error) {
resp, err := c.Service.ListObject(c.ctx, c.bucket, c.prefix)
if err != nil {
return nil, err
}
objs := make([]SecretObject, 0)
for _, obj := range resp {
o := SecretObject{Name: obj.Name, CreatedAt: obj.Created, UpdatedAt: obj.Updated, Location: fmt.Sprintf("%s/%s", obj.Bucket, obj.Name)}
objs = append(objs, o)
}
return objs, nil
}

func New(ctx context.Context, cfg config.Client) (*Client, error) {
if err := cfg.Valid(); err != nil {
return nil, err
Expand Down
15 changes: 12 additions & 3 deletions client/gcs_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

"github.com/rs/zerolog/log"
"github.com/scalescape/dolores/config"
"github.com/scalescape/dolores/store/google"
)

var ErrInvalidPublicKeys = errors.New("invalid public keys")
Expand All @@ -23,7 +24,7 @@
type gcsStore interface {
WriteToObject(ctx context.Context, bucketName, fileName string, data []byte) error
ReadObject(ctx context.Context, bucketName, fileName string) ([]byte, error)
ListOjbect(ctx context.Context, bucketName, path string) ([]string, error)
ListObject(ctx context.Context, bucketName, path string) ([]google.Object, error)
ExistsObject(ctx context.Context, bucketName, fileName string) (bool, error)
}

Expand Down Expand Up @@ -72,18 +73,18 @@
return s.store.WriteToObject(ctx, bucket, fileName, data)
}

func (s Service) GetOrgPublicKeys(ctx context.Context, env, bucketName, path string) ([]string, error) {

Check warning on line 76 in client/gcs_service.go

View workflow job for this annotation

GitHub Actions / golangci-lint

unused-parameter: parameter 'env' seems to be unused, consider removing or renaming it as _ (revive)
pubKey := os.Getenv("DOLORES_PUBLIC_KEY")
if pubKey != "" {
return []string{pubKey}, nil
}
resp, err := s.store.ListOjbect(ctx, bucketName, path)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's meant for abstraction/local, keep it private.
Also i don't see any logic handled under GetObjList except just returning error and repeated here as well, why do we need this method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GetObjList is added beause i dont wnat client to directly call store.ListObject

resp, err := s.ListObject(ctx, bucketName, path)
if err != nil {
return nil, fmt.Errorf("error listing objects: %w", err)
}
keys := make([]string, len(resp))
for i, obj := range resp {
key, err := s.store.ReadObject(ctx, bucketName, obj)
key, err := s.store.ReadObject(ctx, bucketName, obj.Name)
if err != nil {
return nil, fmt.Errorf("failed to read object %s: %w", obj, err)
}
Expand All @@ -108,7 +109,7 @@
return data, nil
}

func (s Service) getObjectPrefix(ctx context.Context, env, bucket string) (string, error) {

Check warning on line 112 in client/gcs_service.go

View workflow job for this annotation

GitHub Actions / golangci-lint

unused-parameter: parameter 'env' seems to be unused, consider removing or renaming it as _ (revive)
md, err := s.readMetadata(ctx, bucket, metadataFile)
if err != nil {
return "", fmt.Errorf("failed to read metadata: %w", err)
Expand Down Expand Up @@ -137,6 +138,14 @@
return s.store.WriteToObject(ctx, bucket, fname, data)
}

func (s Service) ListObject(ctx context.Context, bucket, path string) ([]google.Object, error) {
resp, err := s.store.ListObject(ctx, bucket, path)
if err != nil {
return nil, err
}
return resp, nil
}

func NewService(st gcsStore) Service {
return Service{store: st}
}
13 changes: 13 additions & 0 deletions client/monart.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@
}

type MonartClient struct {
ctx context.Context

Check failure on line 22 in client/monart.go

View workflow job for this annotation

GitHub Actions / golangci-lint

found a struct that contains a context.Context field (containedctx)
cred credentials
cli *http.Client
}

var ErrMethodUndefined = errors.New("Method not yet implemented")

Check failure on line 27 in client/monart.go

View workflow job for this annotation

GitHub Actions / golangci-lint

ST1005: error strings should not be capitalized (stylecheck)

func (s MonartClient) Init(ctx context.Context, bucket string, cfg Configuration) error {

Check warning on line 29 in client/monart.go

View workflow job for this annotation

GitHub Actions / golangci-lint

unused-parameter: parameter 'ctx' seems to be unused, consider removing or renaming it as _ (revive)
return ErrMethodUndefined
}

Expand Down Expand Up @@ -86,6 +86,19 @@
return sec, nil
}

func (s MonartClient) GetSecretList(cfg SecretListConfig) ([]SecretObject, error) {
path := fmt.Sprintf("environment/%s/secrets", cfg.Environment)
req, err := http.NewRequest(http.MethodGet, s.serverURL(path), nil)
if err != nil {
return nil, fmt.Errorf("unable to build Get SecretList request: %w", err)
}
result := new(SecretListResponse)
if _, err := s.call(req, &result); err != nil {
return nil, err
}
return result.Secrets, nil
}

func (s MonartClient) serverURL(path string) string {
return "https://relyonmetrics.com/api/" + path
}
Expand Down
9 changes: 5 additions & 4 deletions client/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/scalescape/dolores/client"
"github.com/scalescape/dolores/config"
"github.com/scalescape/dolores/store/google"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
Expand Down Expand Up @@ -38,9 +39,9 @@ func (m *mockGCS) ReadObject(ctx context.Context, bucketName, fileName string) (
return args.Get(0).([]byte), args.Error(1)
}

func (m *mockGCS) ListOjbect(ctx context.Context, bucketName, path string) ([]string, error) {
func (m *mockGCS) ListObject(ctx context.Context, bucketName, path string) ([]google.Object, error) {
args := m.Called(ctx, bucketName, path)
return args.Get(0).([]string), args.Error(1)
return args.Get(0).([]google.Object), args.Error(1)
}

func (m *mockGCS) ExistsObject(ctx context.Context, bucketName, fileName string) (bool, error) {
Expand Down Expand Up @@ -69,8 +70,8 @@ func (s *serviceSuite) TestShouldNotOverwriteMetadata() {
PublicKey: "public_key",
Metadata: config.Metadata{Location: "secrets"},
UserID: "test_user"}
s.gcs.On("ExistsObject", mock.AnythingOfType("*context.emptyCtx"), s.bucket, name).Return(true, nil).Once()
s.gcs.On("WriteToObject", mock.AnythingOfType("*context.emptyCtx"), s.bucket, "secrets/keys/test_user.key", []byte(cfg.PublicKey)).Return(nil).Once()
s.gcs.On("ExistsObject", mock.AnythingOfType("context.backgroundCtx"), s.bucket, name).Return(true, nil).Once()
s.gcs.On("WriteToObject", mock.AnythingOfType("context.backgroundCtx"), s.bucket, "secrets/keys/test_user.key", []byte(cfg.PublicKey)).Return(nil).Once()

err := s.Service.Init(s.ctx, s.bucket, cfg)

Expand Down
21 changes: 21 additions & 0 deletions cmd/dolores/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

import (
"context"
"os"

"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/scalescape/dolores/secrets"

Check failure on line 9 in cmd/dolores/config.go

View workflow job for this annotation

GitHub Actions / golangci-lint

could not import github.com/scalescape/dolores/secrets (-: # github.com/scalescape/dolores/secrets
"github.com/urfave/cli/v2"
)

Expand All @@ -30,6 +31,7 @@
cfg.Subcommands = append(cfg.Subcommands, EncryptCommand(cfg.encryptAction))
cfg.Subcommands = append(cfg.Subcommands, DecryptCommand(cfg.decryptAction))
cfg.Subcommands = append(cfg.Subcommands, EditCommand(cfg.editAction))
cfg.Subcommands = append(cfg.Subcommands, ListSecretCommand(cfg.listSecretAction))
return cfg
}

Expand Down Expand Up @@ -74,6 +76,17 @@
return sec.Decrypt(req)
}

func (c *ConfigCommand) listSecretAction(ctx *cli.Context) error {
env := ctx.String("environment")
log := c.log.With().Str("cmd", "config.list").Str("environment", env).Logger()
secMan := secrets.NewSecretsManager(log, c.rcli(ctx.Context))
req := secrets.ListSecretConfig{Environment: env, Out: os.Stdout}
if err := secMan.ListSecret(req); err != nil {
return err
}
return nil
}

func EncryptCommand(action cli.ActionFunc) *cli.Command {
return &cli.Command{
Name: "encrypt",
Expand Down Expand Up @@ -134,3 +147,11 @@
Action: action,
}
}

func ListSecretCommand(action cli.ActionFunc) *cli.Command {
return &cli.Command{
Name: "list",
Usage: "shows the list of secrets uploaded in cloud",
Action: action,
}
}
1 change: 1 addition & 0 deletions cmd/dolores/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type secretsClient interface {
FetchSecrets(req client.FetchSecretRequest) ([]byte, error)
GetOrgPublicKeys(env string) (client.OrgPublicKeys, error)
Init(ctx context.Context, bucket string, cfg client.Configuration) error
GetSecretList(req client.SecretListConfig) ([]client.SecretObject, error)
}

type CtxKey string
Expand Down
55 changes: 55 additions & 0 deletions secrets/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"io"
"os"
"strings"
"time"

"github.com/rs/zerolog"
"github.com/scalescape/dolores"
Expand All @@ -17,6 +18,7 @@
FetchSecrets(req client.FetchSecretRequest) ([]byte, error)
UploadSecrets(req client.EncryptedConfig) error
GetOrgPublicKeys(env string) (client.OrgPublicKeys, error)
GetSecretList(cfg client.SecretListConfig) ([]client.SecretObject, error)
}

type EncryptConfig struct {
Expand Down Expand Up @@ -130,6 +132,59 @@
return nil
}

type ListSecretConfig struct {
Environment string
Out io.Writer
}

func (c ListSecretConfig) output() io.Writer {
if c.Out == nil {
return os.Stdout
}
return c.Out
}

func (c ListSecretConfig) Valid() error {
env := strings.ToLower(c.Environment)
if env != "production" && env != "staging" {
return ErrInvalidEnvironment
}
return nil
}

func (sm SecretManager) ListSecret(cfg ListSecretConfig) error {
if err := cfg.Valid(); err != nil {
return fmt.Errorf("invalid config: %w", err)
}
req := client.SecretListConfig{Environment: cfg.Environment}
resp, err := sm.client.GetSecretList(req)
if err != nil {
return fmt.Errorf("failed to get secrets: %w", err)
}

lineFormat := "%-10s %-65s %-30s %-30s\n"
header := []byte(fmt.Sprintf(lineFormat, "Name", "Location", "Created At (UTC)", "Updated At (UTC)"))
if _, err := cfg.output().Write(header); err != nil {
return err
}
shubhamvernekar marked this conversation as resolved.
Show resolved Hide resolved
for _, obj := range resp {
if !strings.HasSuffix(obj.Name, ".key") && !strings.HasSuffix(obj.Name, "/") {
arr := strings.SplitN(obj.Name, "/", 2)
name := obj.Name
if len(arr) == 2 {
name = arr[1]
}
createdAt := obj.CreatedAt.Format(time.DateTime)

Check failure on line 177 in secrets/manager.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: time.DateTime

Check failure on line 177 in secrets/manager.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: time.DateTime

Check failure on line 177 in secrets/manager.go

View workflow job for this annotation

GitHub Actions / Build app

undefined: time.DateTime
updatedAt := obj.UpdatedAt.Format(time.DateTime)

Check failure on line 178 in secrets/manager.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: time.DateTime (typecheck)

Check failure on line 178 in secrets/manager.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: time.DateTime) (typecheck)

Check failure on line 178 in secrets/manager.go

View workflow job for this annotation

GitHub Actions / Build app

undefined: time.DateTime
line := []byte(fmt.Sprintf(lineFormat, name, obj.Location, createdAt, updatedAt))
if _, err := cfg.output().Write(line); err != nil {
return err
}
}
}
return nil
}

func NewSecretsManager(log zerolog.Logger, rcli secClient) SecretManager {
return SecretManager{client: rcli, log: log}
}
15 changes: 12 additions & 3 deletions store/google/gcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"fmt"
"io"
"os"
"time"

"cloud.google.com/go/storage"
"github.com/rs/zerolog/log"
Expand All @@ -25,6 +26,13 @@
ServiceAccountFile string
}

type Object struct {
Name string `json:"name"`
Bucket string `json:"bucket"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}

type ServiceAccount struct {
Type string `json:"type"`
ProjectID string `json:"project_id"`
Expand Down Expand Up @@ -105,12 +113,12 @@
return buckets, nil
}

func (s StorageClient) ListOjbect(ctx context.Context, bucketName, path string) ([]string, error) {
func (s StorageClient) ListObject(ctx context.Context, bucketName, path string) ([]Object, error) {
bucket := s.Client.Bucket(bucketName)
shubhamvernekar marked this conversation as resolved.
Show resolved Hide resolved
if _, err := bucket.Attrs(ctx); err != nil {
return nil, fmt.Errorf("failed to get bucket: %w", err)
}
objs := make([]string, 0)
objs := make([]Object, 0)
iter := bucket.Objects(ctx, &storage.Query{Prefix: path})
for {
attrs, err := iter.Next()
Expand All @@ -120,7 +128,8 @@
if err != nil {
return nil, fmt.Errorf("failed to iterate object list: %w", err)
}
objs = append(objs, attrs.Name)
o := Object{Name: attrs.Name, Created: attrs.Created, Updated: attrs.Updated, Bucket: attrs.Bucket}
objs = append(objs, o)
}
log.Trace().Msgf("list of objects from path: %s %+v", path, objs)
return objs, nil
Expand Down Expand Up @@ -162,7 +171,7 @@
func NewStore(ctx context.Context, cfg Config) (StorageClient, error) {
data, err := os.ReadFile(cfg.ServiceAccountFile)
if err != nil {
return StorageClient{}, fmt.Errorf("failed to read service account file with error %v %w", err, ErrInvalidServiceAccount)

Check failure on line 174 in store/google/gcs.go

View workflow job for this annotation

GitHub Actions / golangci-lint

non-wrapping format verb for fmt.Errorf. Use `%w` to format errors (errorlint)
}
sa := new(ServiceAccount)
if err := json.Unmarshal(data, sa); err != nil {
Expand Down
Loading