Skip to content

Commit

Permalink
changing the structure of k8s CRD and updating vaults module to make …
Browse files Browse the repository at this point in the history
…it interoperable with k8s yaml
  • Loading branch information
shibme committed Jan 31, 2024
1 parent 62b8687 commit 2eac92e
Show file tree
Hide file tree
Showing 13 changed files with 204 additions and 106 deletions.
2 changes: 1 addition & 1 deletion .dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ script_dir="$(dirname "$0")"
# Change directory to the location of the script and push the current directory onto a stack
pushd $script_dir
# Build the code
go build -o slv-dev ./cli
go build -o slv-dev ./cli/main
# Go back to the original directory
popd
# Run slv from current directory
Expand Down
5 changes: 5 additions & 0 deletions cli/internal/commands/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ var (
usage: "Preserve a partial secret hash for the purpose of validating secret rotation [Not recommended, though it might be resilient from brute-forcing]",
}

vaultK8sFlag = FlagDef{
name: "k8s",
usage: "Specify a name for the K8s SLV object if the vault is to be used in a K8s environment",
}

// Secret Command Flags

secretNameFlag = FlagDef{
Expand Down
11 changes: 5 additions & 6 deletions cli/internal/commands/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import (
"fmt"
"strings"

"github.com/fatih/color"
"github.com/amagimedia/slv/core/secretkeystore"
"github.com/amagimedia/slv/core/vaults"
"github.com/fatih/color"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -42,7 +41,7 @@ func secretPutCommand() *cobra.Command {
vaultFile := cmd.Flag(vaultFileFlag.name).Value.String()
name := cmd.Flag(secretNameFlag.name).Value.String()
secret := cmd.Flag(secretValueFlag.name).Value.String()
vault, err := vaults.Get(vaultFile)
vault, err := getVault(vaultFile)
if err != nil {
exitOnError(err)
}
Expand Down Expand Up @@ -83,7 +82,7 @@ func secretGetCommand() *cobra.Command {
}
vaultFile := cmd.Flag(vaultFileFlag.name).Value.String()
name := cmd.Flag(secretNameFlag.name).Value.String()
vault, err := vaults.Get(vaultFile)
vault, err := getVault(vaultFile)
if err != nil {
exitOnError(err)
}
Expand Down Expand Up @@ -116,7 +115,7 @@ func secretRefCommand() *cobra.Command {
Short: "References and updates secrets to a vault from a given yaml or json file",
Run: func(cmd *cobra.Command, args []string) {
vaultFile := cmd.Flag(vaultFileFlag.name).Value.String()
vault, err := vaults.Get(vaultFile)
vault, err := getVault(vaultFile)
if err != nil {
exitOnError(err)
}
Expand Down Expand Up @@ -178,7 +177,7 @@ func secretDerefCommand() *cobra.Command {
previewOnly, _ = cmd.Flags().GetBool(secretRefPreviewOnlyFlag.name)
}
for _, vaultFile := range vaultFiles {
vault, err := vaults.Get(vaultFile)
vault, err := getVault(vaultFile)
if err != nil {
exitOnError(err)
}
Expand Down
44 changes: 41 additions & 3 deletions cli/internal/commands/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,46 @@ package commands
import (
"fmt"

"github.com/fatih/color"
"github.com/amagimedia/slv/core/commons"
"github.com/amagimedia/slv/core/crypto"
"github.com/amagimedia/slv/core/profiles"
"github.com/amagimedia/slv/core/secretkeystore"
"github.com/amagimedia/slv/core/vaults"
"github.com/fatih/color"
"github.com/spf13/cobra"
)

const (
k8sApiVersion = "k8s.amagi.com/v1"
k8sKind = "SLV"
k8sVaultField = "spec"
)

func getVault(filePath string) (*vaults.Vault, error) {
vault, err := vaults.Get(filePath)
if err != nil || vault.Config.PublicKey == "" {
vault, err = vaults.GetFromField(filePath, k8sVaultField)
}
return vault, err
}

func newK8sVault(filePath, name string, hashLength uint32, rootPublicKey *crypto.PublicKey, publicKeys ...*crypto.PublicKey) (*vaults.Vault, error) {
vault, err := vaults.New(filePath, k8sVaultField, hashLength, rootPublicKey, publicKeys...)
if err != nil {
return nil, err
}
var obj map[string]interface{}
if err := commons.ReadFromYAML(filePath, &obj); err != nil {
return nil, err
}
obj["apiVersion"] = k8sApiVersion
obj["kind"] = k8sKind
obj["metadata"] = map[string]interface{}{
"name": name,
}
return vault, commons.WriteToYAML(filePath, "", obj)
}

func vaultCommand() *cobra.Command {
if vaultCmd != nil {
return vaultCmd
Expand Down Expand Up @@ -83,7 +115,12 @@ func vaultNewCommand() *cobra.Command {
if err != nil {
exitOnError(err)
}
_, err = vaults.New(vaultFile, hashLength, rootPublicKey, publicKeys...)
k8slvName := cmd.Flag(vaultK8sFlag.name).Value.String()
if k8slvName == "" {
_, err = vaults.New(vaultFile, "", hashLength, rootPublicKey, publicKeys...)
} else {
_, err = newK8sVault(vaultFile, k8slvName, hashLength, rootPublicKey, publicKeys...)
}
if err != nil {
exitOnError(err)
}
Expand All @@ -94,6 +131,7 @@ func vaultNewCommand() *cobra.Command {
vaultNewCmd.Flags().StringP(vaultFileFlag.name, vaultFileFlag.shorthand, "", vaultFileFlag.usage)
vaultNewCmd.Flags().StringSliceP(vaultAccessPublicKeysFlag.name, vaultAccessPublicKeysFlag.shorthand, []string{}, vaultAccessPublicKeysFlag.usage)
vaultNewCmd.Flags().StringP(envSearchFlag.name, envSearchFlag.shorthand, "", envSearchFlag.usage)
vaultNewCmd.Flags().StringP(vaultK8sFlag.name, vaultK8sFlag.shorthand, "", vaultK8sFlag.usage)
vaultNewCmd.Flags().BoolP(vaultEnableHashingFlag.name, vaultEnableHashingFlag.shorthand, false, vaultEnableHashingFlag.usage)
vaultNewCmd.MarkFlagRequired(vaultFileFlag.name)
return vaultNewCmd
Expand Down Expand Up @@ -149,7 +187,7 @@ func vaultShareCommand() *cobra.Command {
exitOnError(fmt.Errorf("no matching environments found for search query: " + query))
}
}
vault, err := vaults.Get(vaultFile)
vault, err := getVault(vaultFile)
if err == nil {
err = vault.Unlock(*envSecretKey)
if err == nil {
Expand Down
17 changes: 11 additions & 6 deletions core/commons/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import (

func WriteToYAML(filePath, notice string, data interface{}) error {
bytes, err := yaml.Marshal(data)
bytes = append([]byte(notice), bytes...)
if notice != "" {
bytes = append([]byte(notice), bytes...)
}
bytes = append([]byte(slvYamlNotice), bytes...)
if err == nil {
if err = WriteToFile(filePath, bytes); err != nil {
Expand All @@ -20,18 +22,18 @@ func WriteToYAML(filePath, notice string, data interface{}) error {
return err
}

func ReadChildFromYAML(filePath string, nodePath string) (interface{}, error) {
func ReadChildFromYAML(filePath, nodePath string, out interface{}) error {
// Read the file
data, err := os.ReadFile(filePath)
if err != nil {
return nil, err
return err
}

// Unmarshal the YAML data into a map
var objMap map[string]interface{}
err = yaml.Unmarshal(data, &objMap)
if err != nil {
return nil, err
return err
}

// Split the node path and traverse the map
Expand All @@ -46,8 +48,11 @@ func ReadChildFromYAML(filePath string, nodePath string) (interface{}, error) {
objMap, _ = objMap[node].(map[string]interface{})
}
}

return objMap, nil
bytes, err := yaml.Marshal(objMap)
if err != nil {
return err
}
return yaml.Unmarshal(bytes, out)
}

func ReadFromYAML(filePath string, out interface{}) error {
Expand Down
76 changes: 48 additions & 28 deletions core/vaults/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type Vault struct {
unlockedBy *string `json:"-"`
decryptedSecrets map[string][]byte `json:"-"`
vaultSecretRefRegex *regexp.Regexp `json:"-"`
objectField string `json:"-"`
}

func (v *Vault) DeepCopy() *Vault {
Expand Down Expand Up @@ -72,17 +73,17 @@ func (vlt *Vault) getPublicKey() (publicKey *crypto.PublicKey, err error) {
return vlt.publicKey, err
}

// Returns new vault instance. The vault file name must end with .slv or .slv.yaml or .slv.yml.
func New(vaultFile string, hashLength uint32, rootPublicKey *crypto.PublicKey, publicKeys ...*crypto.PublicKey) (vlt *Vault, err error) {
if !strings.HasSuffix(vaultFile, vaultFileNameExtension+".yaml") &&
!strings.HasSuffix(vaultFile, vaultFileNameExtension+".yml") &&
!strings.HasSuffix(vaultFile, vaultFileNameExtension) {
// Returns new vault instance and the vault contents set into the specified field. The vault file name must end with .slv or .slv.yaml or .slv.yml.
func New(filePath, objectField string, hashLength uint32, rootPublicKey *crypto.PublicKey, publicKeys ...*crypto.PublicKey) (vlt *Vault, err error) {
if !strings.HasSuffix(filePath, vaultFileNameExtension+".yaml") &&
!strings.HasSuffix(filePath, vaultFileNameExtension+".yml") &&
!strings.HasSuffix(filePath, vaultFileNameExtension) {
return nil, errInvalidVaultFileName
}
if commons.FileExists(vaultFile) {
if commons.FileExists(filePath) {
return nil, errVaultExists
}
if os.MkdirAll(path.Dir(vaultFile), os.FileMode(0755)) != nil {
if os.MkdirAll(path.Dir(filePath), os.FileMode(0755)) != nil {
return nil, errVaultDirPathCreation
}
vaultSecretKey, err := crypto.NewSecretKey(VaultKey)
Expand All @@ -103,8 +104,9 @@ func New(vaultFile string, hashLength uint32, rootPublicKey *crypto.PublicKey, p
PublicKey: vaultPublicKey.String(),
HashLength: hashLen,
},
path: vaultFile,
secretKey: vaultSecretKey,
path: filePath,
secretKey: vaultSecretKey,
objectField: objectField,
}
if rootPublicKey != nil {
if _, err := vlt.Share(rootPublicKey); err != nil {
Expand All @@ -119,22 +121,33 @@ func New(vaultFile string, hashLength uint32, rootPublicKey *crypto.PublicKey, p
return vlt, vlt.commit()
}

// Returns the vault instance for a given vault file. The vault file name must end with .slv or .slv.yaml or .slv.yml.
func Get(vaultFile string) (vlt *Vault, err error) {
if !strings.HasSuffix(vaultFile, vaultFileNameExtension+".yaml") &&
!strings.HasSuffix(vaultFile, vaultFileNameExtension+".yml") &&
!strings.HasSuffix(vaultFile, vaultFileNameExtension) {
// Returns the vault instance from a given yaml. The vault file name must end with .slv or .slv.yaml or .slv.yml.
func Get(filePath string) (vlt *Vault, err error) {
return GetFromField(filePath, "")
}

// Returns the vault instance from a given yaml file considering a field as vault. The vault file name must end with .slv or .slv.yaml or .slv.yml.
func GetFromField(filePath, fieldName string) (vlt *Vault, err error) {
if !strings.HasSuffix(filePath, vaultFileNameExtension+".yaml") &&
!strings.HasSuffix(filePath, vaultFileNameExtension+".yml") &&
!strings.HasSuffix(filePath, vaultFileNameExtension) {
return nil, errInvalidVaultFileName
}
if !commons.FileExists(vaultFile) {
if !commons.FileExists(filePath) {
return nil, errVaultNotFound
}
vlt = &Vault{
path: vaultFile,
}
if err = commons.ReadFromYAML(vlt.path, &vlt); err != nil {
return nil, err
vlt = &Vault{}
if fieldName == "" {
if err = commons.ReadFromYAML(filePath, &vlt); err != nil {
return nil, err
}
} else {
if err = commons.ReadChildFromYAML(filePath, fieldName, &vlt); err != nil {
return nil, err
}
vlt.objectField = fieldName
}
vlt.path = filePath
return vlt, nil
}

Expand Down Expand Up @@ -173,15 +186,22 @@ func (vlt *Vault) Unlock(secretKey crypto.SecretKey) error {
}

func (vlt *Vault) commit() error {
var obj map[string]interface{}
err := commons.ReadFromYAML(vlt.path, &obj)
if err != nil {
return err
if vlt.objectField == "" {
return commons.WriteToYAML(vlt.path,
"# Use the pattern "+vlt.getSecretRef("YOUR_SECRET_NAME")+" as placeholder to reference secrets from this vault into files\n", vlt)
} else {
var obj map[string]interface{}
if commons.FileExists(vlt.path) {
if err := commons.ReadFromYAML(vlt.path, &obj); err != nil {
return err
}
} else {
obj = make(map[string]interface{})
}
obj[vlt.objectField] = vlt
return commons.WriteToYAML(vlt.path,
"# Use the pattern "+vlt.getSecretRef("YOUR_SECRET_NAME")+" as placeholder to reference secrets from this vault into files\n", obj)
}
obj["slvSecrets"] = vlt.Secrets
obj["slvConfig"] = vlt.Config
return commons.WriteToYAML(vlt.path,
"# Use the pattern "+vlt.getSecretRef("YOUR_SECRET_NAME")+" as placeholder to reference secrets from this vault into files\n", obj)
}

func (vlt *Vault) reset() error {
Expand Down
3 changes: 2 additions & 1 deletion k8s/api/v1/groupversion_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ import (
)

const Group = "k8s.amagi.com"
const Version = "v1"

var (
// GroupVersion is group version used to register these objects
GroupVersion = schema.GroupVersion{Group: Group, Version: "v1"}
GroupVersion = schema.GroupVersion{Group: Group, Version: Version}

// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
Expand Down
9 changes: 7 additions & 2 deletions k8s/api/v1/slv_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// SLVSpec defines the desired state of SLV
type SLVSpec struct {
vaults.Vault `json:""`
}

// SLVStatus defines the observed state of SLV
type SLVStatus struct {
Error string `json:"error,omitempty"`
Expand All @@ -34,8 +39,8 @@ type SLV struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata"`

Status SLVStatus `json:"status,omitempty"`
vaults.Vault `json:""`
Spec SLVSpec `json:"spec"`
Status SLVStatus `json:"status,omitempty"`
}

//+kubebuilder:object:root=true
Expand Down
18 changes: 17 additions & 1 deletion k8s/api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 2eac92e

Please sign in to comment.