diff --git a/.dev.sh b/.dev.sh index 85f47f5..62b4d5d 100644 --- a/.dev.sh +++ b/.dev.sh @@ -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 diff --git a/cli/internal/commands/flags.go b/cli/internal/commands/flags.go index 7a6ca95..72fa2d9 100644 --- a/cli/internal/commands/flags.go +++ b/cli/internal/commands/flags.go @@ -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{ diff --git a/cli/internal/commands/secret.go b/cli/internal/commands/secret.go index 5f2af00..31404cb 100644 --- a/cli/internal/commands/secret.go +++ b/cli/internal/commands/secret.go @@ -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" ) @@ -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) } @@ -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) } @@ -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) } @@ -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) } diff --git a/cli/internal/commands/vault.go b/cli/internal/commands/vault.go index 11327b1..7ddb6cb 100644 --- a/cli/internal/commands/vault.go +++ b/cli/internal/commands/vault.go @@ -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 @@ -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) } @@ -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 @@ -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 { diff --git a/core/commons/files.go b/core/commons/files.go index c28f42f..662edf0 100644 --- a/core/commons/files.go +++ b/core/commons/files.go @@ -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 { @@ -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 @@ -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 { diff --git a/core/vaults/vault.go b/core/vaults/vault.go index 3123e81..c0c94e7 100644 --- a/core/vaults/vault.go +++ b/core/vaults/vault.go @@ -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 { @@ -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) @@ -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 { @@ -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 } @@ -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 { diff --git a/k8s/api/v1/groupversion_info.go b/k8s/api/v1/groupversion_info.go index 3a33e3d..5ef467e 100644 --- a/k8s/api/v1/groupversion_info.go +++ b/k8s/api/v1/groupversion_info.go @@ -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} diff --git a/k8s/api/v1/slv_types.go b/k8s/api/v1/slv_types.go index c3c9fc8..99cef33 100644 --- a/k8s/api/v1/slv_types.go +++ b/k8s/api/v1/slv_types.go @@ -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"` @@ -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 diff --git a/k8s/api/v1/zz_generated.deepcopy.go b/k8s/api/v1/zz_generated.deepcopy.go index 86b8f31..f4c49da 100644 --- a/k8s/api/v1/zz_generated.deepcopy.go +++ b/k8s/api/v1/zz_generated.deepcopy.go @@ -29,8 +29,8 @@ func (in *SLV) DeepCopyInto(out *SLV) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status - in.Vault.DeepCopyInto(&out.Vault) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SLV. @@ -83,6 +83,22 @@ func (in *SLVList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SLVSpec) DeepCopyInto(out *SLVSpec) { + *out = *in + in.Vault.DeepCopyInto(&out.Vault) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SLVSpec. +func (in *SLVSpec) DeepCopy() *SLVSpec { + if in == nil { + return nil + } + out := new(SLVSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SLVStatus) DeepCopyInto(out *SLVStatus) { *out = *in diff --git a/k8s/config/crd/bases/k8s.amagi.com_slvs.yaml b/k8s/config/crd/bases/k8s.amagi.com_slvs.yaml index 497d669..4d0b2e8 100644 --- a/k8s/config/crd/bases/k8s.amagi.com_slvs.yaml +++ b/k8s/config/crd/bases/k8s.amagi.com_slvs.yaml @@ -31,24 +31,31 @@ spec: type: string metadata: type: object - slvConfig: + spec: + description: SLVSpec defines the desired state of SLV properties: - hashLength: - format: int32 - type: integer - publicKey: - type: string - wrappedKeys: - items: + slvConfig: + properties: + hashLength: + format: int32 + type: integer + publicKey: + type: string + wrappedKeys: + items: + type: string + type: array + required: + - publicKey + - wrappedKeys + type: object + slvSecrets: + additionalProperties: type: string - type: array + type: object required: - - publicKey - - wrappedKeys - type: object - slvSecrets: - additionalProperties: - type: string + - slvConfig + - slvSecrets type: object status: description: SLVStatus defines the observed state of SLV @@ -58,8 +65,7 @@ spec: type: object required: - metadata - - slvConfig - - slvSecrets + - spec type: object served: true storage: true diff --git a/k8s/controller/slv_controller.go b/k8s/controller/slv_controller.go index 70141b5..3e22caa 100644 --- a/k8s/controller/slv_controller.go +++ b/k8s/controller/slv_controller.go @@ -119,7 +119,7 @@ func (r *SLVReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R return ctrl.Result{}, client.IgnoreNotFound(err) } - vault := slvObj.Vault + vault := slvObj.Spec.Vault if err := vault.Unlock(*secretKey); err != nil { return r.returnError(ctx, req, &slvObj, &logger, err, "Failed to unlock vault") } @@ -148,9 +148,6 @@ func (r *SLVReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R if err = controllerutil.SetControllerReference(&slvObj, secret, r.Scheme); err != nil { return r.returnError(ctx, req, &slvObj, &logger, err, "Failed to set controller reference for secret") } - if err = controllerutil.SetOwnerReference(&slvObj, secret, r.Scheme); err != nil { - return r.returnError(ctx, req, &slvObj, &logger, err, "Failed to set owner reference for secret") - } if err := r.Create(ctx, secret); err != nil { return r.returnError(ctx, req, &slvObj, &logger, err, "Failed to create secret") } @@ -188,11 +185,10 @@ func (r *SLVReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R } var msg string if updateRequired { - if err = controllerutil.SetControllerReference(&slvObj, secret, r.Scheme); err != nil { - return r.returnError(ctx, req, &slvObj, &logger, err, "Failed to set controller reference for secret") - } - if err = controllerutil.SetOwnerReference(&slvObj, secret, r.Scheme); err != nil { - return r.returnError(ctx, req, &slvObj, &logger, err, "Failed to set owner reference for secret") + if !controllerutil.HasControllerReference(secret) { + if err = controllerutil.SetControllerReference(&slvObj, secret, r.Scheme); err != nil { + return r.returnError(ctx, req, &slvObj, &logger, err, "Failed to set controller reference for secret") + } } if err = r.Update(ctx, secret); err != nil { return r.returnError(ctx, req, &slvObj, &logger, err, "Failed to update secret") diff --git a/k8s/deploy-slv-operator.yaml b/k8s/deploy-slv-operator.yaml index 6c18677..acc2f9c 100644 --- a/k8s/deploy-slv-operator.yaml +++ b/k8s/deploy-slv-operator.yaml @@ -31,24 +31,31 @@ spec: type: string metadata: type: object - slvConfig: + spec: + description: SLVSpec defines the desired state of SLV properties: - hashLength: - format: int32 - type: integer - publicKey: - type: string - wrappedKeys: - items: + slvConfig: + properties: + hashLength: + format: int32 + type: integer + publicKey: + type: string + wrappedKeys: + items: + type: string + type: array + required: + - publicKey + - wrappedKeys + type: object + slvSecrets: + additionalProperties: type: string - type: array + type: object required: - - publicKey - - wrappedKeys - type: object - slvSecrets: - additionalProperties: - type: string + - slvConfig + - slvSecrets type: object status: description: SLVStatus defines the observed state of SLV @@ -58,8 +65,7 @@ spec: type: object required: - metadata - - slvConfig - - slvSecrets + - spec type: object served: true storage: true diff --git a/k8s/sample-cr.slv.yaml b/k8s/sample-cr.slv.yaml index d36463c..14914f7 100644 --- a/k8s/sample-cr.slv.yaml +++ b/k8s/sample-cr.slv.yaml @@ -4,18 +4,19 @@ apiVersion: k8s.amagi.com/v1 kind: SLV metadata: name: pets -slvConfig: - publicKey: SLV_VPK_AEAVMAAJ7TBB4BGX3MOMNIJR7E6ZCCGRFFHGGID5JZ3OVMMNYL7AAVJYP4 - wrappedKeys: - - SLV_EWK_AFCQAJABAFCQBC4M2C6WCF3L73OE6EDB5F77YNSWS54SV3PBR4VBAQ3JCP5ZJASZAAJGRI244SFFNTE5UH5OPB62AYYZU53KSRKVKGLUHXY2CXH6FNVUQVMAPD2EPYFVXWLILNXKVRK5TCNI3CTSZQVAPIOACDQPL4C2RV4MXDWAZECNGVNZOTR26KUVIAKLVQBPV5QW5NESXMLWVQOTPDNG5EEE4CIUVY3WJKWIVSLWQBUGWHY6RTWBCU3FODLFVIKLU -slvSecrets: - mycat1: SLV_VSS_AFLAAJABAFLAACP4YIPAJV63DTDKCMPZHWIQRUJJJZRSA7KOO3VLDDOC7YAFKOD7ADG5X7FVMUHCKBNT2LUDZURNCWZWYPAFA7FSMZSJSZVORVZ6OD6TZVDGIDB4OVRGLZA5MJIHQHEXZ5LWGZDKAKSS5GRADL3C5EICG4GTNNSYAWM4GU6JE6EATQLCY3YJJITWAMDU6LZLBMOKEM - mycat2: SLV_VSS_AFLAAJABAFLAACP4YIPAJV63DTDKCMPZHWIQRUJJJZRSA7KOO3VLDDOC7YAFKOD7ACOBPNU5R7VBRRKIN7T3763GGFYWLSZH32UOAWY7HGEZBIDKN3DDSBVDRIJPFEVASGVTO62VUDMR7FPTPVU4WVJY22PADJ6RTXICTHKILIW6RCOZJ4EI6T4LGXFIX3ASBPPXQOWL6HIYVWEYCOQSI5Y - mydog1: SLV_VSS_AFLAAJABAFLAACP4YIPAJV63DTDKCMPZHWIQRUJJJZRSA7KOO3VLDDOC7YAFKOD7AAOAKFQ4XLNIHUWEOT5DHVLZZBCT7ICBWWEKRTPCDODA5XCE3QFVSIIQWPBQZT4J2DIDKKF4U7BYNPA4IPIQCYNSXIWQDMNNYROW3BQNTEUXXSINVAQXLRGMUWLP3NM2I3OQZAD6MDWJAAZ6PY - mydog2: SLV_VSS_AFLAAJABAFLAACP4YIPAJV63DTDKCMPZHWIQRUJJJZRSA7KOO3VLDDOC7YAFKOD7AAHZYBHREC2Y4D6XZ5DUEZJPDBS2IMUPM6JDVMTV7BSXMZXAPX6WZTLZVHA6J6U6H4OZF2EDA7N66ESIN6TJTLWUDMUADBEC6JLH2KXDTDJNHMAQYCONVHET45S7EZMOXFZGHTST2O3XWFJYE4ZXK - mydog3: SLV_VSS_AFLAAJABAFLAACP4YIPAJV63DTDKCMPZHWIQRUJJJZRSA7KOO3VLDDOC7YAFKOD7AAVI3LGDLJ7FWYYXMPWQJ6MJ7TFJ7OFD6AJBDPOOZN7MOLRXAU3GIPQC55T3D5FGHYSUACLVDLLUMOTPEWV3MW5JAFMACGBTF5SKII7BRR6VHCIQRZSJBGUVSZWHFTOPV2TT7X4SNQ7XDR2I - mydog4: SLV_VSS_AFLAAJABAFLAACP4YIPAJV63DTDKCMPZHWIQRUJJJZRSA7KOO3VLDDOC7YAFKOD7AAFLP6ZUYHHB3UDJ2X6IGIOEIV43NICDSYEEKEXKM3LCEZVSTU5QWTZWMFD7BAPEWJV2POHBUGQYIBLMBHNQ6VBQZUSQDOFM3TTMZRWWZ4RA65IBPRNLJN5GCASVNOAWCQSEUGZ7S5O4TVUWR4 - mydog5: SLV_VSS_AFLAAJABAFLAACP4YIPAJV63DTDKCMPZHWIQRUJJJZRSA7KOO3VLDDOC7YAFKOD7ACAT2D2HBRDYWOCSWSSBRH2FKEVFWQHWWYXKXX2LUQY77PZETVHHHKTZ2VBIZBIBROYJ4VDFX2BWCKQTCIOXXP24B4ZQDWK26CWIGMW4NRZA7JYHQMPT4F2ULRFVYEYICZIU3DSWQIY3UILS734A - mydog6: SLV_VSS_AFLAAJABAFLAACP4YIPAJV63DTDKCMPZHWIQRUJJJZRSA7KOO3VLDDOC7YAFKOD7ACXD43QBPCXHV2N4NCK7NOCLKVBEOFP3DFVNUYMKKCUYHEMQHI6CIN27GNI3T7V4LHU3NMKPSW5KJTKQTANAA6J32MYACSTKMXXLQA2NEYCTYDJDJO2NNIOTCWLLLRFYYLJQL4RYOT3OFQ4G65KNO - mydog7: SLV_VSS_AFLAAJABAFLAACP4YIPAJV63DTDKCMPZHWIQRUJJJZRSA7KOO3VLDDOC7YAFKOD7AAGUZNYRO33NT3YZYFHZECLAC56X6QINFELQY74HIQRGSMENRWSDK4A7GB3RHNBTWHUK45Y5E3ZMGK7GPCDDFNPDS6SQDNXRQXVRSRBGERXTXGQAMNZLE2HD6O6JZJP5H6AQNFHBOCCL4KAQ - mydog8: SLV_VSS_AFLAAJABAFLAACP4YIPAJV63DTDKCMPZHWIQRUJJJZRSA7KOO3VLDDOC7YAFKOD7ABOECT65CLZU4ACQNX45VQTUT7N6EH2L3HNTHOOB3YHJHUSJLM2ARBJC4KUWU3GYGSJEZGBAIYJCAKYPRBYLP7QAFPEQCBGBV6DW52SL4GFKI644UTF35TDEVMJUA7GRCATJQOHH6FJED6ZG +spec: + slvSecrets: + mycat1: SLV_VSS_AFLAAJABAFLAACP4YIPAJV63DTDKCMPZHWIQRUJJJZRSA7KOO3VLDDOC7YAFKOD7ADG5X7FVMUHCKBNT2LUDZURNCWZWYPAFA7FSMZSJSZVORVZ6OD6TZVDGIDB4OVRGLZA5MJIHQHEXZ5LWGZDKAKSS5GRADL3C5EICG4GTNNSYAWM4GU6JE6EATQLCY3YJJITWAMDU6LZLBMOKEM + mycat2: SLV_VSS_AFLAAJABAFLAACP4YIPAJV63DTDKCMPZHWIQRUJJJZRSA7KOO3VLDDOC7YAFKOD7ACOBPNU5R7VBRRKIN7T3763GGFYWLSZH32UOAWY7HGEZBIDKN3DDSBVDRIJPFEVASGVTO62VUDMR7FPTPVU4WVJY22PADJ6RTXICTHKILIW6RCOZJ4EI6T4LGXFIX3ASBPPXQOWL6HIYVWEYCOQSI5Y + mydog1: SLV_VSS_AFLAAJABAFLAACP4YIPAJV63DTDKCMPZHWIQRUJJJZRSA7KOO3VLDDOC7YAFKOD7AAOAKFQ4XLNIHUWEOT5DHVLZZBCT7ICBWWEKRTPCDODA5XCE3QFVSIIQWPBQZT4J2DIDKKF4U7BYNPA4IPIQCYNSXIWQDMNNYROW3BQNTEUXXSINVAQXLRGMUWLP3NM2I3OQZAD6MDWJAAZ6PY + mydog2: SLV_VSS_AFLAAJABAFLAACP4YIPAJV63DTDKCMPZHWIQRUJJJZRSA7KOO3VLDDOC7YAFKOD7AAHZYBHREC2Y4D6XZ5DUEZJPDBS2IMUPM6JDVMTV7BSXMZXAPX6WZTLZVHA6J6U6H4OZF2EDA7N66ESIN6TJTLWUDMUADBEC6JLH2KXDTDJNHMAQYCONVHET45S7EZMOXFZGHTST2O3XWFJYE4ZXK + mydog3: SLV_VSS_AFLAAJABAFLAACP4YIPAJV63DTDKCMPZHWIQRUJJJZRSA7KOO3VLDDOC7YAFKOD7AAVI3LGDLJ7FWYYXMPWQJ6MJ7TFJ7OFD6AJBDPOOZN7MOLRXAU3GIPQC55T3D5FGHYSUACLVDLLUMOTPEWV3MW5JAFMACGBTF5SKII7BRR6VHCIQRZSJBGUVSZWHFTOPV2TT7X4SNQ7XDR2I + mydog4: SLV_VSS_AFLAAJABAFLAACP4YIPAJV63DTDKCMPZHWIQRUJJJZRSA7KOO3VLDDOC7YAFKOD7AAFLP6ZUYHHB3UDJ2X6IGIOEIV43NICDSYEEKEXKM3LCEZVSTU5QWTZWMFD7BAPEWJV2POHBUGQYIBLMBHNQ6VBQZUSQDOFM3TTMZRWWZ4RA65IBPRNLJN5GCASVNOAWCQSEUGZ7S5O4TVUWR4 + mydog5: SLV_VSS_AFLAAJABAFLAACP4YIPAJV63DTDKCMPZHWIQRUJJJZRSA7KOO3VLDDOC7YAFKOD7ACAT2D2HBRDYWOCSWSSBRH2FKEVFWQHWWYXKXX2LUQY77PZETVHHHKTZ2VBIZBIBROYJ4VDFX2BWCKQTCIOXXP24B4ZQDWK26CWIGMW4NRZA7JYHQMPT4F2ULRFVYEYICZIU3DSWQIY3UILS734A + mydog6: SLV_VSS_AFLAAJABAFLAACP4YIPAJV63DTDKCMPZHWIQRUJJJZRSA7KOO3VLDDOC7YAFKOD7ACXD43QBPCXHV2N4NCK7NOCLKVBEOFP3DFVNUYMKKCUYHEMQHI6CIN27GNI3T7V4LHU3NMKPSW5KJTKQTANAA6J32MYACSTKMXXLQA2NEYCTYDJDJO2NNIOTCWLLLRFYYLJQL4RYOT3OFQ4G65KNO + mydog7: SLV_VSS_AFLAAJABAFLAACP4YIPAJV63DTDKCMPZHWIQRUJJJZRSA7KOO3VLDDOC7YAFKOD7AAGUZNYRO33NT3YZYFHZECLAC56X6QINFELQY74HIQRGSMENRWSDK4A7GB3RHNBTWHUK45Y5E3ZMGK7GPCDDFNPDS6SQDNXRQXVRSRBGERXTXGQAMNZLE2HD6O6JZJP5H6AQNFHBOCCL4KAQ + mydog8: SLV_VSS_AFLAAJABAFLAACP4YIPAJV63DTDKCMPZHWIQRUJJJZRSA7KOO3VLDDOC7YAFKOD7ABOECT65CLZU4ACQNX45VQTUT7N6EH2L3HNTHOOB3YHJHUSJLM2ARBJC4KUWU3GYGSJEZGBAIYJCAKYPRBYLP7QAFPEQCBGBV6DW52SL4GFKI644UTF35TDEVMJUA7GRCATJQOHH6FJED6ZG + slvConfig: + publicKey: SLV_VPK_AEAVMAAJ7TBB4BGX3MOMNIJR7E6ZCCGRFFHGGID5JZ3OVMMNYL7AAVJYP4 + wrappedKeys: + - SLV_EWK_AFCQAJABAFCQBC4M2C6WCF3L73OE6EDB5F77YNSWS54SV3PBR4VBAQ3JCP5ZJASZAAJGRI244SFFNTE5UH5OPB62AYYZU53KSRKVKGLUHXY2CXH6FNVUQVMAPD2EPYFVXWLILNXKVRK5TCNI3CTSZQVAPIOACDQPL4C2RV4MXDWAZECNGVNZOTR26KUVIAKLVQBPV5QW5NESXMLWVQOTPDNG5EEE4CIUVY3WJKWIVSLWQBUGWHY6RTWBCU3FODLFVIKLU