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
2 changes: 1 addition & 1 deletion cmd/secrets/common/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ func (h *Handler) Execute(
inputs UpsertSecretsInputs,
method string,
duration time.Duration,
ownerType string,
secretsAuth string,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ownerType param wasn't used anywhere in secret commands?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, it was pulled directly from the runtime context rather than passed as a param. I think this was leftover from a previous change

) error {
ui.Dim("Verifying ownership...")
if err := h.EnsureOwnerLinkedOrFail(); err != nil {
Expand Down
32 changes: 32 additions & 0 deletions cmd/secrets/common/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package common

import (
"fmt"
"strings"
)

const (
SecretsAuthOwnerKeySigning = "owner-key-signing"
SecretsAuthBrowser = "browser"
)

// ValidateSecretsAuthFlow checks that the chosen auth flow is valid and
// allowed in the current environment. Browser flow is blocked in production.
func ValidateSecretsAuthFlow(flow, envName string) error {
switch flow {
case SecretsAuthOwnerKeySigning:
return nil
case SecretsAuthBrowser:
if strings.EqualFold(envName, "PRODUCTION") || envName == "" {
return fmt.Errorf("browser auth flow is not yet available in production; use owner-key-signing")
}
return nil
default:
return fmt.Errorf("unknown --secrets-auth value %q; expected %q or %q", flow, SecretsAuthOwnerKeySigning, SecretsAuthBrowser)
}
}

// IsBrowserFlow returns true when the browser (JWT) auth flow is selected.
func IsBrowserFlow(flow string) bool {
return flow == SecretsAuthBrowser
}
49 changes: 49 additions & 0 deletions cmd/secrets/common/validate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package common

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestValidateSecretsAuthFlow(t *testing.T) {
tests := []struct {
name string
flow string
env string
wantErr bool
errMsg string
}{
{"owner-key-signing in production", SecretsAuthOwnerKeySigning, "PRODUCTION", false, ""},
{"owner-key-signing in staging", SecretsAuthOwnerKeySigning, "STAGING", false, ""},
{"owner-key-signing in dev", SecretsAuthOwnerKeySigning, "DEVELOPMENT", false, ""},
{"owner-key-signing empty env defaults safe", SecretsAuthOwnerKeySigning, "", false, ""},
{"browser in staging", SecretsAuthBrowser, "STAGING", false, ""},
{"browser in dev", SecretsAuthBrowser, "DEVELOPMENT", false, ""},
{"browser in production blocked", SecretsAuthBrowser, "PRODUCTION", true, "not yet available in production"},
{"browser in production lowercase", SecretsAuthBrowser, "production", true, "not yet available in production"},
{"browser empty env treated as production", SecretsAuthBrowser, "", true, "not yet available in production"},
{"unknown value rejected", "magic", "STAGING", true, "unknown --secrets-auth value"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateSecretsAuthFlow(tt.flow, tt.env)
if tt.wantErr {
require.Error(t, err)
if tt.errMsg != "" {
require.Contains(t, err.Error(), tt.errMsg)
}
} else {
require.NoError(t, err)
}
})
}
}

func TestIsBrowserFlow(t *testing.T) {
assert.False(t, IsBrowserFlow(SecretsAuthOwnerKeySigning), "owner-key-signing should not be browser flow")
assert.True(t, IsBrowserFlow(SecretsAuthBrowser), "browser should be browser flow")
assert.False(t, IsBrowserFlow("unknown"), "unknown should not be browser flow")
}
10 changes: 9 additions & 1 deletion cmd/secrets/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ func New(ctx *runtime.Context) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
secretsFilePath := args[0]

secretsAuth, err := cmd.Flags().GetString("secrets-auth")
if err != nil {
return err
}
if err := common.ValidateSecretsAuthFlow(secretsAuth, ctx.EnvironmentSet.EnvName); err != nil {
return err
}

h, err := common.NewHandler(ctx, secretsFilePath)
if err != nil {
return err
Expand Down Expand Up @@ -54,7 +62,7 @@ func New(ctx *runtime.Context) *cobra.Command {
return err
}

return h.Execute(inputs, vaulttypes.MethodSecretsCreate, duration, ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType)
return h.Execute(inputs, vaulttypes.MethodSecretsCreate, duration, secretsAuth)
},
}

Expand Down
14 changes: 10 additions & 4 deletions cmd/secrets/delete/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ func New(ctx *runtime.Context) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
secretsFilePath := args[0]

secretsAuth, err := cmd.Flags().GetString("secrets-auth")
if err != nil {
return err
}
if err := common.ValidateSecretsAuthFlow(secretsAuth, ctx.EnvironmentSet.EnvName); err != nil {
return err
}

h, err := common.NewHandler(ctx, secretsFilePath)
if err != nil {
return err
Expand All @@ -78,7 +86,6 @@ func New(ctx *runtime.Context) *cobra.Command {
return fmt.Errorf("invalid --timeout: must be greater than 0 and less than %dh (%dd)", maxHours, maxDays)
}

// Parse & validate YAML input
inputs, err := ResolveDeleteInputs(secretsFilePath)
if err != nil {
return err
Expand All @@ -87,8 +94,7 @@ func New(ctx *runtime.Context) *cobra.Command {
return err
}

// Two-path logic: MSIG step 1 (bundle) or EOA (allowlist + post)
return Execute(h, inputs, duration, ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType)
return Execute(h, inputs, duration, secretsAuth)
},
}

Expand All @@ -101,7 +107,7 @@ func New(ctx *runtime.Context) *cobra.Command {
// Two paths:
// - MSIG step 1: build request, compute digest, write bundle, print steps
// - EOA: allowlist if needed, then POST to gateway
func Execute(h *common.Handler, inputs DeleteSecretsInputs, duration time.Duration, ownerType string) error {
func Execute(h *common.Handler, inputs DeleteSecretsInputs, duration time.Duration, secretsAuth string) error {
spinner := ui.NewSpinner()
spinner.Start("Verifying ownership...")
if err := h.EnsureOwnerLinkedOrFail(); err != nil {
Expand Down
17 changes: 10 additions & 7 deletions cmd/secrets/list/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ func New(ctx *runtime.Context) *cobra.Command {
Use: "list",
Short: "Lists secret identifiers for the current owner address in the given namespace.",
RunE: func(cmd *cobra.Command, args []string) error {
secretsAuth, err := cmd.Flags().GetString("secrets-auth")
if err != nil {
return err
}
if err := common.ValidateSecretsAuthFlow(secretsAuth, ctx.EnvironmentSet.EnvName); err != nil {
return err
}

h, err := common.NewHandler(ctx, "")
if err != nil {
return err
Expand All @@ -58,12 +66,7 @@ func New(ctx *runtime.Context) *cobra.Command {
return fmt.Errorf("invalid --timeout: must be greater than 0 and less than %dh (%dd)", maxHours, maxDays)
}

return Execute(
h,
namespace,
duration,
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType,
)
return Execute(h, namespace, duration, secretsAuth)
},
}

Expand All @@ -75,7 +78,7 @@ func New(ctx *runtime.Context) *cobra.Command {
}

// Execute performs: build request → (MSIG step 1 bundle OR EOA allowlist+post) → parse.
func Execute(h *common.Handler, namespace string, duration time.Duration, ownerType string) error {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Note: ownerType was previously unused

func Execute(h *common.Handler, namespace string, duration time.Duration, secretsAuth string) error {
spinner := ui.NewSpinner()
spinner.Start("Verifying ownership...")
if err := h.EnsureOwnerLinkedOrFail(); err != nil {
Expand Down
3 changes: 3 additions & 0 deletions cmd/secrets/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ func New(runtimeContext *runtime.Context) *cobra.Command {
"Timeout for secrets operations (e.g. 30m, 2h, 48h).",
)

secretsCmd.PersistentFlags().String("secrets-auth", "owner-key-signing", "Auth flow for secrets operations (owner-key-signing, browser).")
_ = secretsCmd.PersistentFlags().MarkHidden("secrets-auth")

secretsCmd.AddCommand(create.New(runtimeContext))
secretsCmd.AddCommand(update.New(runtimeContext))
secretsCmd.AddCommand(delete.New(runtimeContext))
Expand Down
15 changes: 9 additions & 6 deletions cmd/secrets/update/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ func New(ctx *runtime.Context) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
secretsFilePath := args[0]

secretsAuth, err := cmd.Flags().GetString("secrets-auth")
if err != nil {
return err
}
if err := common.ValidateSecretsAuthFlow(secretsAuth, ctx.EnvironmentSet.EnvName); err != nil {
return err
}

h, err := common.NewHandler(ctx, secretsFilePath)
if err != nil {
return err
Expand Down Expand Up @@ -55,12 +63,7 @@ func New(ctx *runtime.Context) *cobra.Command {
return err
}

return h.Execute(
inputs,
vaulttypes.MethodSecretsUpdate,
duration,
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType,
)
return h.Execute(inputs, vaulttypes.MethodSecretsUpdate, duration, secretsAuth)
},
}

Expand Down
Loading