Skip to content
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
224 changes: 224 additions & 0 deletions pkg/cloud/secrets/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ import (
"context"
"encoding/base64"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"unicode/utf8"

"github.com/google/uuid"
"google.golang.org/grpc/codes"
Expand All @@ -35,6 +39,7 @@ import (

type DevSecretService struct {
secDir string
mu sync.RWMutex
}

var _ secretspb.SecretManagerServer = (*DevSecretService)(nil)
Expand Down Expand Up @@ -75,6 +80,7 @@ func (s *DevSecretService) Put(ctx context.Context, req *secretspb.SecretPutRequ
}

writer.Flush()
file.Close()

// Creates a new file as latest
latestFile, err := os.Create(s.secretFileName(req.Secret, "latest"))
Expand All @@ -98,6 +104,7 @@ func (s *DevSecretService) Put(ctx context.Context, req *secretspb.SecretPutRequ
}

latestWriter.Flush()
latestFile.Close()

return &secretspb.SecretPutResponse{
SecretVersion: &secretspb.SecretVersion{
Expand All @@ -108,6 +115,9 @@ func (s *DevSecretService) Put(ctx context.Context, req *secretspb.SecretPutRequ
}

func (s *DevSecretService) Access(ctx context.Context, req *secretspb.SecretAccessRequest) (*secretspb.SecretAccessResponse, error) {
s.mu.RLock()
defer s.mu.RUnlock()

newErr := grpc_errors.ErrorsWithScope(
"DevSecretService.Access",
)
Expand Down Expand Up @@ -151,6 +161,220 @@ func (s *DevSecretService) Access(ctx context.Context, req *secretspb.SecretAcce
}, nil
}

type SecretVersion struct {
Version string `json:"version"`
Value string `json:"value"`
Latest bool `json:"latest"`
CreatedAt string `json:"createdAt"`
}

// formatUint8Array formats a byte array as a hexadecimal string with a space between each byte.
func formatUint8Array(byteArray []byte) string {
result := ""

for i, b := range byteArray {
// Convert non-printable byte to a two-digit hexadecimal string
hex := fmt.Sprintf("%02x", b)
result += hex + " "

// Add a new line every 16 bytes for the grid format
if (i+1)%16 == 0 {
result += "\n"
}
}

return strings.ToUpper(result)
}

// List all secret versions and values for a given secret, used by dashboard
func (s *DevSecretService) List(ctx context.Context, secretName string) ([]SecretVersion, error) {
newErr := grpc_errors.ErrorsWithScope(
"DevSecretService.List",
)

// Check whether file exists
_, err := os.Stat(s.secDir)
if os.IsNotExist(err) {
return nil, newErr(codes.NotFound, "secret store not found", err)
}

// List all files in the directory
files, err := os.ReadDir(s.secDir)
if err != nil {
return nil, newErr(codes.FailedPrecondition, "error reading secret store", err)
}

// Create a response
resp := []SecretVersion{}

var latestVersion SecretVersion

for _, file := range files {
// Check whether the file is a secret file
if strings.HasSuffix(file.Name(), ".txt") {
// Split the file name to get the secret name and version
splitName := strings.Split(file.Name(), "_")
// Check whether the secret name matches the requested secret
if splitName[0] == secretName {
version := strings.TrimSuffix(splitName[1], ".txt")

info, err := file.Info()
if err != nil {
return nil, newErr(codes.FailedPrecondition, "error reading file info", err)
}

createdAt := info.ModTime().Format("2006-01-02 15:04:05")

valueResp, err := s.Access(ctx, &secretspb.SecretAccessRequest{
SecretVersion: &secretspb.SecretVersion{
Secret: &secretspb.Secret{Name: secretName},
Version: version,
},
})
if err != nil {
// check if not found and add blank value
if strings.HasPrefix(err.Error(), "rpc error: code = NotFound desc") {
resp = append(resp, SecretVersion{
Version: version,
Value: "",
CreatedAt: createdAt,
})

continue
}

return nil, newErr(codes.FailedPrecondition, "error reading version value", err)
}

var value string

if utf8.Valid(valueResp.Value) {
value = string(valueResp.Value)
} else {
value = formatUint8Array(valueResp.Value)
}

// Check whether the version is the latest
if version == "latest" {
latestVersion = SecretVersion{
Value: value,
}

continue
}

// Add the secret to the response
resp = append(resp, SecretVersion{
Version: version,
Value: value,
CreatedAt: createdAt,
})
}
}
}

if len(resp) > 0 {
// sort by created at
sort.Slice(resp, func(i, j int) bool {
return resp[i].CreatedAt > resp[j].CreatedAt
})

// mark latest version
if resp[0].Value == latestVersion.Value {
resp[0].Latest = true
}
}

return resp, nil
}

// Delete a secret version, used by dashboard
func (s *DevSecretService) Delete(ctx context.Context, secretName string, version string, latest bool) error {
s.mu.Lock()
defer s.mu.Unlock()

newErr := grpc_errors.ErrorsWithScope(
"DevSecretService.Delete",
)

// Check whether file exists
_, err := os.Stat(s.secDir)
if os.IsNotExist(err) {
return newErr(codes.NotFound, "secret store not found", err)
}

// delete the version file
err = os.Remove(s.secretFileName(&secretspb.Secret{Name: secretName}, version))
if err != nil {
return newErr(codes.Internal, "error deleting secret version", err)
}

if latest {
// delete the latest file
err = os.Remove(s.secretFileName(&secretspb.Secret{Name: secretName}, "latest"))
if err != nil {
return newErr(codes.Internal, "error deleting latest secret version", err)
}

// get last latest version and create a new latest
entries, err := os.ReadDir(s.secDir)
if err != nil {
return newErr(codes.FailedPrecondition, "error reading secret store", err)
}

var files []os.FileInfo

for _, entry := range entries {
info, err := entry.Info()
if err != nil {
return newErr(codes.FailedPrecondition, "error reading file info", err)
}

files = append(files, info)
}

// sort files by date
sort.Slice(files, func(i, j int) bool {
return files[i].ModTime().Before(files[j].ModTime())
})

for _, file := range files {
if strings.HasSuffix(file.Name(), ".txt") {
splitName := strings.Split(file.Name(), "_")
if splitName[0] == secretName {
version := strings.TrimSuffix(splitName[1], ".txt")

// copy file as new latest file with same contents
destinationFile, err := os.Create(s.secretFileName(&secretspb.Secret{Name: secretName}, "latest"))
if err != nil {
return newErr(codes.FailedPrecondition, "error creating latest secret version", err)
}

sourceFile, err := os.Open(s.secretFileName(&secretspb.Secret{Name: secretName}, version))
if err != nil {
return newErr(codes.FailedPrecondition, "error reading secret version", err)
}

_, err = io.Copy(destinationFile, sourceFile)
if err != nil {
return newErr(codes.FailedPrecondition, "error copying secret version", err)
}

err = destinationFile.Sync()
if err != nil {
return newErr(codes.FailedPrecondition, "error syncing latest secret version", err)
}

sourceFile.Close()
destinationFile.Close()
}
}
}
}

return nil
}

// Create new secret store
func NewSecretService() (*DevSecretService, error) {
secDir := env.LOCAL_SECRETS_DIR.String()
Expand Down
Loading