Skip to content

Commit a1b175f

Browse files
authored
feat: vaults v2 enhanced secret management (#246)
Complete overhaul of the vault system introducing named vaults, multiple encryption backends, and improved workflows for managing secrets across different vaults.
1 parent ed36313 commit a1b175f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+2409
-191
lines changed

.golangci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ linters:
9292
text: G115
9393
- linters:
9494
- staticcheck
95-
text: SA5011
95+
text: (SA5011|SA5001)
9696
- path: (.+)\.go$
9797
text: declaration of "(err|ctx)" shadows declaration at
9898
paths:

cmd/internal/config.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ func registerSetNamespaceCmd(ctx *context.Context, setCmd *cobra.Command) {
9292
Aliases: []string{"ns"},
9393
Short: "Change the current namespace.",
9494
Args: cobra.ExactArgs(1),
95-
PreRun: func(cmd *cobra.Command, args []string) { printContext(ctx, cmd) },
9695
Run: func(cmd *cobra.Command, args []string) { setNamespaceFunc(ctx, cmd, args) },
9796
}
9897
setCmd.AddCommand(namespaceCmd)
@@ -115,7 +114,6 @@ func registerSetWorkspaceModeCmd(ctx *context.Context, setCmd *cobra.Command) {
115114
Short: "Switch between fixed and dynamic workspace modes.",
116115
Args: cobra.ExactArgs(1),
117116
ValidArgs: []string{"fixed", "dynamic"},
118-
PreRun: func(cmd *cobra.Command, args []string) { printContext(ctx, cmd) },
119117
Run: func(cmd *cobra.Command, args []string) { setWorkspaceModeFunc(ctx, cmd, args) },
120118
}
121119
setCmd.AddCommand(workspaceModeCmd)
@@ -142,7 +140,6 @@ func registerSetLogModeCmd(ctx *context.Context, setCmd *cobra.Command) {
142140
Short: "Set the default log mode.",
143141
Args: cobra.ExactArgs(1),
144142
ValidArgs: []string{"logfmt", "json", "text", "hidden"},
145-
PreRun: func(cmd *cobra.Command, args []string) { printContext(ctx, cmd) },
146143
Run: func(cmd *cobra.Command, args []string) { setLogModeFunc(ctx, cmd, args) },
147144
}
148145
setCmd.AddCommand(logModeCmd)

cmd/internal/exec.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/jahvon/flow/internal/services/store"
2727
argUtils "github.com/jahvon/flow/internal/utils/args"
2828
"github.com/jahvon/flow/internal/vault"
29+
vaultV2 "github.com/jahvon/flow/internal/vault/v2"
2930
"github.com/jahvon/flow/types/executable"
3031
)
3132

@@ -156,7 +157,9 @@ func execFunc(ctx *context.Context, cmd *cobra.Command, verb executable.Verb, ar
156157
}
157158
}
158159

159-
setAuthEnv(ctx, cmd, e)
160+
if ctx.Config.CurrentVault == nil || *ctx.Config.CurrentVault == vaultV2.LegacyVaultReservedName {
161+
setAuthEnv(ctx, cmd, e, false)
162+
}
160163
startTime := time.Now()
161164
eng := engine.NewExecEngine()
162165
if err := runner.Exec(ctx, e, eng, envMap); err != nil {
@@ -215,8 +218,8 @@ func runByRef(ctx *context.Context, cmd *cobra.Command, argsStr string) error {
215218
return nil
216219
}
217220

218-
func setAuthEnv(ctx *context.Context, _ *cobra.Command, executable *executable.Executable) {
219-
if authRequired(ctx, executable) {
221+
func setAuthEnv(ctx *context.Context, _ *cobra.Command, executable *executable.Executable, force bool) {
222+
if authRequired(ctx, executable) || force {
220223
form, err := views.NewForm(
221224
io.Theme(ctx.Config.Theme.String()),
222225
ctx.StdIn(),

cmd/internal/flags/types.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,54 @@ var ParameterValueFlag = &Metadata{
183183
"This will override any existing parameter values defined for the executable.",
184184
Default: []string{},
185185
}
186+
187+
var VaultTypeFlag = &Metadata{
188+
Name: "type",
189+
Shorthand: "t",
190+
Usage: "Vault type. Either age or aes256",
191+
Default: "aes256",
192+
Required: false,
193+
}
194+
195+
var VaultPathFlag = &Metadata{
196+
Name: "path",
197+
Shorthand: "p",
198+
Usage: "Directory that the vault will use to store its data. If not set, the vault will be stored in the flow cache directory.",
199+
Default: "",
200+
Required: false,
201+
}
202+
203+
var VaultKeyEnvFlag = &Metadata{
204+
Name: "key-env",
205+
Usage: "Environment variable name for the vault encryption key. Only used for AES256 vaults.",
206+
Default: "",
207+
Required: false,
208+
}
209+
210+
var VaultKeyFileFlag = &Metadata{
211+
Name: "key-file",
212+
Usage: "File path for the vault encryption key. An absolute path is recommended. Only used for AES256 vaults.",
213+
Default: "",
214+
Required: false,
215+
}
216+
217+
var VaultRecipientsFlag = &Metadata{
218+
Name: "recipients",
219+
Usage: "Comma-separated list of recipient keys for the vault. Only used for Age vaults.",
220+
Default: "",
221+
Required: false,
222+
}
223+
224+
var VaultIdentityEnvFlag = &Metadata{
225+
Name: "identity-env",
226+
Usage: "Environment variable name for the Age vault identity. Only used for Age vaults.",
227+
Default: "",
228+
Required: false,
229+
}
230+
231+
var VaultIdentityFileFlag = &Metadata{
232+
Name: "identity-file",
233+
Usage: "File path for the Age vault identity. An absolute path is recommended. Only used for Age vaults.",
234+
Default: "",
235+
Required: false,
236+
}

cmd/internal/secret.go

Lines changed: 134 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import (
1313
"github.com/jahvon/flow/internal/context"
1414
"github.com/jahvon/flow/internal/io"
1515
"github.com/jahvon/flow/internal/io/secret"
16+
secretV2 "github.com/jahvon/flow/internal/io/secret/v2"
1617
"github.com/jahvon/flow/internal/vault"
18+
vaultV2 "github.com/jahvon/flow/internal/vault/v2"
19+
"github.com/jahvon/flow/types/config"
1720
)
1821

1922
func RegisterSecretCmd(ctx *context.Context, rootCmd *cobra.Command) {
@@ -65,10 +68,24 @@ func removeSecretFunc(ctx *context.Context, _ *cobra.Command, args []string) {
6568
return
6669
}
6770

68-
v := vault.NewVault(logger)
69-
if err = v.DeleteSecret(reference); err != nil {
70-
logger.FatalErr(err)
71+
if currentVault(ctx.Config) == vaultV2.LegacyVaultReservedName {
72+
logger.Warnf("Using deprecated vault. Consider creating a new vault with 'flow vault create' command.")
73+
v := vault.NewVault(logger)
74+
if err = v.DeleteSecret(reference); err != nil {
75+
logger.FatalErr(err)
76+
}
77+
} else {
78+
_, v, err := vaultV2.VaultFromName(currentVault(ctx.Config))
79+
defer v.Close()
80+
81+
if err != nil {
82+
logger.FatalErr(err)
83+
}
84+
if err = v.DeleteSecret(reference); err != nil {
85+
logger.FatalErr(err)
86+
}
7187
}
88+
7289
logger.PlainTextSuccess(fmt.Sprintf("Secret '%s' deleted from vault", reference))
7390
}
7491

@@ -78,7 +95,6 @@ func registerSetSecretCmd(ctx *context.Context, secretCmd *cobra.Command) {
7895
Aliases: []string{"new", "create", "update"},
7996
Short: "Set a secret in the current vault. If no value is provided, you will be prompted to enter one.",
8097
Args: cobra.MinimumNArgs(1),
81-
PreRun: func(cmd *cobra.Command, args []string) { printContext(ctx, cmd) },
8298
Run: func(cmd *cobra.Command, args []string) { setSecretFunc(ctx, cmd, args) },
8399
}
84100
secretCmd.AddCommand(setCmd)
@@ -115,11 +131,29 @@ func setSecretFunc(ctx *context.Context, _ *cobra.Command, args []string) {
115131
}
116132

117133
sv := vault.SecretValue(value)
118-
v := vault.NewVault(logger)
119-
err := v.SetSecret(reference, sv)
120-
if err != nil {
121-
logger.FatalErr(err)
134+
vaultName := currentVault(ctx.Config)
135+
if vaultName == vaultV2.LegacyVaultReservedName {
136+
logger.Warnf(
137+
"Using deprecated vault '%s'. Consider creating a new vault with 'flow vault create' command.",
138+
vaultName,
139+
)
140+
v := vault.NewVault(logger)
141+
err := v.SetSecret(reference, sv)
142+
if err != nil {
143+
logger.FatalErr(err)
144+
}
145+
} else {
146+
_, v, err := vaultV2.VaultFromName(vaultName)
147+
defer v.Close()
148+
149+
if err != nil {
150+
logger.FatalErr(err)
151+
}
152+
if err = v.SetSecret(reference, vaultV2.NewSecretValue([]byte(value))); err != nil {
153+
logger.FatalErr(err)
154+
}
122155
}
156+
123157
logger.PlainTextSuccess(fmt.Sprintf("Secret %s set in vault", reference))
124158
}
125159

@@ -143,17 +177,45 @@ func listSecretFunc(ctx *context.Context, cmd *cobra.Command, _ []string) {
143177
asPlainText := flags.ValueFor[bool](ctx, cmd, *flags.OutputSecretAsPlainTextFlag, false)
144178
outputFormat := flags.ValueFor[string](ctx, cmd, *flags.OutputFormatFlag, false)
145179

146-
v := vault.NewVault(logger)
147-
secrets, err := v.GetAllSecrets()
148-
if err != nil {
149-
logger.FatalErr(err)
150-
}
180+
//nolint:nestif
181+
if currentVault(ctx.Config) == vaultV2.LegacyVaultReservedName {
182+
v := vault.NewVault(logger)
183+
secrets, err := v.GetAllSecrets()
184+
if err != nil {
185+
logger.FatalErr(err)
186+
}
151187

152-
interactiveUI := TUIEnabled(ctx, cmd)
153-
if interactiveUI {
154-
secret.LoadSecretListView(ctx, asPlainText)
188+
interactiveUI := TUIEnabled(ctx, cmd)
189+
if interactiveUI {
190+
secret.LoadSecretListView(ctx, asPlainText)
191+
} else {
192+
secret.PrintSecrets(ctx, secrets, outputFormat, asPlainText)
193+
}
155194
} else {
156-
secret.PrintSecrets(ctx, secrets, outputFormat, asPlainText)
195+
name := currentVault(ctx.Config)
196+
interactiveUI := TUIEnabled(ctx, cmd)
197+
198+
_, v, err := vaultV2.VaultFromName(name)
199+
defer func() {
200+
// Don't close the vault prematurely if we're in interactive mode
201+
go func() {
202+
if interactiveUI {
203+
ctx.TUIContainer.WaitForExit()
204+
}
205+
_ = v.Close()
206+
}()
207+
}()
208+
209+
if err != nil {
210+
logger.FatalErr(err)
211+
}
212+
213+
if interactiveUI {
214+
view := secretV2.NewSecretListView(ctx, v, asPlainText)
215+
SetView(ctx, cmd, view)
216+
} else {
217+
secretV2.PrintSecrets(ctx, name, v, outputFormat, asPlainText)
218+
}
157219
}
158220
}
159221

@@ -176,23 +238,65 @@ func getSecretFunc(ctx *context.Context, cmd *cobra.Command, args []string) {
176238
asPlainText := flags.ValueFor[bool](ctx, cmd, *flags.OutputSecretAsPlainTextFlag, false)
177239
copyValue := flags.ValueFor[bool](ctx, cmd, *flags.CopyFlag, false)
178240

179-
v := vault.NewVault(logger)
180-
s, err := v.GetSecret(reference)
181-
if err != nil {
182-
logger.FatalErr(err)
183-
}
241+
//nolint:nestif
242+
if currentVault(ctx.Config) == vaultV2.LegacyVaultReservedName {
243+
logger.Warnf("Using deprecated vault. Consider creating a new vault with 'flow vault create' command.")
244+
v := vault.NewVault(logger)
245+
s, err := v.GetSecret(reference)
246+
if err != nil {
247+
logger.FatalErr(err)
248+
}
184249

185-
if asPlainText {
186-
logger.PlainTextInfo(s.PlainTextString())
250+
if asPlainText {
251+
logger.PlainTextInfo(s.PlainTextString())
252+
} else {
253+
logger.PlainTextInfo(s.String())
254+
}
255+
256+
if copyValue {
257+
if err := clipboard.WriteAll(s.PlainTextString()); err != nil {
258+
logger.Error(err, "\nunable to copy secret value to clipboard")
259+
} else {
260+
logger.PlainTextSuccess("\ncopied secret value to clipboard")
261+
}
262+
}
187263
} else {
188-
logger.PlainTextInfo(s.String())
189-
}
264+
rVault, key, err := vaultV2.RefToParts(vaultV2.SecretRef(reference))
265+
if err != nil {
266+
logger.FatalErr(err)
267+
}
268+
if rVault == "" {
269+
rVault = currentVault(ctx.Config)
270+
}
271+
_, v, err := vaultV2.VaultFromName(rVault)
272+
defer v.Close()
273+
274+
if err != nil {
275+
logger.FatalErr(err)
276+
}
277+
s, err := v.GetSecret(key)
278+
if err != nil {
279+
logger.FatalErr(err)
280+
}
190281

191-
if copyValue {
192-
if err := clipboard.WriteAll(s.PlainTextString()); err != nil {
193-
logger.Error(err, "\nunable to copy secret value to clipboard")
282+
if asPlainText {
283+
logger.PlainTextInfo(s.PlainTextString())
194284
} else {
195-
logger.PlainTextSuccess("\ncopied secret value to clipboard")
285+
logger.PlainTextInfo(s.String())
196286
}
287+
if copyValue {
288+
if err := clipboard.WriteAll(s.PlainTextString()); err != nil {
289+
logger.Error(err, "\nunable to copy secret value to clipboard")
290+
} else {
291+
logger.PlainTextSuccess("\ncopied secret value to clipboard")
292+
}
293+
}
294+
}
295+
}
296+
297+
func currentVault(cfg *config.Config) string {
298+
if cfg.CurrentVault == nil || *cfg.CurrentVault == "" {
299+
return vaultV2.LegacyVaultReservedName
197300
}
301+
return *cfg.CurrentVault
198302
}

0 commit comments

Comments
 (0)