Skip to content

Commit d3c2d8f

Browse files
authored
Add secret auth flow flag (#323)
* Add fetch for tenant config and write context file * Update based on new api changes * lint * Adjust for latest api changes * add support for api key * Clean up * Extract orgID from claim * review feedback * tenant -> user context * Add secret auth flow flag * refactor tests
1 parent 3446baf commit d3c2d8f

File tree

8 files changed

+123
-19
lines changed

8 files changed

+123
-19
lines changed

cmd/secrets/common/handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ func (h *Handler) Execute(
351351
inputs UpsertSecretsInputs,
352352
method string,
353353
duration time.Duration,
354-
ownerType string,
354+
secretsAuth string,
355355
) error {
356356
ui.Dim("Verifying ownership...")
357357
if err := h.EnsureOwnerLinkedOrFail(); err != nil {

cmd/secrets/common/validate.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package common
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
8+
const (
9+
SecretsAuthOwnerKeySigning = "owner-key-signing"
10+
SecretsAuthBrowser = "browser"
11+
)
12+
13+
// ValidateSecretsAuthFlow checks that the chosen auth flow is valid and
14+
// allowed in the current environment. Browser flow is blocked in production.
15+
func ValidateSecretsAuthFlow(flow, envName string) error {
16+
switch flow {
17+
case SecretsAuthOwnerKeySigning:
18+
return nil
19+
case SecretsAuthBrowser:
20+
if strings.EqualFold(envName, "PRODUCTION") || envName == "" {
21+
return fmt.Errorf("browser auth flow is not yet available in production; use owner-key-signing")
22+
}
23+
return nil
24+
default:
25+
return fmt.Errorf("unknown --secrets-auth value %q; expected %q or %q", flow, SecretsAuthOwnerKeySigning, SecretsAuthBrowser)
26+
}
27+
}
28+
29+
// IsBrowserFlow returns true when the browser (JWT) auth flow is selected.
30+
func IsBrowserFlow(flow string) bool {
31+
return flow == SecretsAuthBrowser
32+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package common
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestValidateSecretsAuthFlow(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
flow string
14+
env string
15+
wantErr bool
16+
errMsg string
17+
}{
18+
{"owner-key-signing in production", SecretsAuthOwnerKeySigning, "PRODUCTION", false, ""},
19+
{"owner-key-signing in staging", SecretsAuthOwnerKeySigning, "STAGING", false, ""},
20+
{"owner-key-signing in dev", SecretsAuthOwnerKeySigning, "DEVELOPMENT", false, ""},
21+
{"owner-key-signing empty env defaults safe", SecretsAuthOwnerKeySigning, "", false, ""},
22+
{"browser in staging", SecretsAuthBrowser, "STAGING", false, ""},
23+
{"browser in dev", SecretsAuthBrowser, "DEVELOPMENT", false, ""},
24+
{"browser in production blocked", SecretsAuthBrowser, "PRODUCTION", true, "not yet available in production"},
25+
{"browser in production lowercase", SecretsAuthBrowser, "production", true, "not yet available in production"},
26+
{"browser empty env treated as production", SecretsAuthBrowser, "", true, "not yet available in production"},
27+
{"unknown value rejected", "magic", "STAGING", true, "unknown --secrets-auth value"},
28+
}
29+
30+
for _, tt := range tests {
31+
t.Run(tt.name, func(t *testing.T) {
32+
err := ValidateSecretsAuthFlow(tt.flow, tt.env)
33+
if tt.wantErr {
34+
require.Error(t, err)
35+
if tt.errMsg != "" {
36+
require.Contains(t, err.Error(), tt.errMsg)
37+
}
38+
} else {
39+
require.NoError(t, err)
40+
}
41+
})
42+
}
43+
}
44+
45+
func TestIsBrowserFlow(t *testing.T) {
46+
assert.False(t, IsBrowserFlow(SecretsAuthOwnerKeySigning), "owner-key-signing should not be browser flow")
47+
assert.True(t, IsBrowserFlow(SecretsAuthBrowser), "browser should be browser flow")
48+
assert.False(t, IsBrowserFlow("unknown"), "unknown should not be browser flow")
49+
}

cmd/secrets/create/create.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ func New(ctx *runtime.Context) *cobra.Command {
2424
RunE: func(cmd *cobra.Command, args []string) error {
2525
secretsFilePath := args[0]
2626

27+
secretsAuth, err := cmd.Flags().GetString("secrets-auth")
28+
if err != nil {
29+
return err
30+
}
31+
if err := common.ValidateSecretsAuthFlow(secretsAuth, ctx.EnvironmentSet.EnvName); err != nil {
32+
return err
33+
}
34+
2735
h, err := common.NewHandler(ctx, secretsFilePath)
2836
if err != nil {
2937
return err
@@ -54,7 +62,7 @@ func New(ctx *runtime.Context) *cobra.Command {
5462
return err
5563
}
5664

57-
return h.Execute(inputs, vaulttypes.MethodSecretsCreate, duration, ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType)
65+
return h.Execute(inputs, vaulttypes.MethodSecretsCreate, duration, secretsAuth)
5866
},
5967
}
6068

cmd/secrets/delete/delete.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ func New(ctx *runtime.Context) *cobra.Command {
5757
RunE: func(cmd *cobra.Command, args []string) error {
5858
secretsFilePath := args[0]
5959

60+
secretsAuth, err := cmd.Flags().GetString("secrets-auth")
61+
if err != nil {
62+
return err
63+
}
64+
if err := common.ValidateSecretsAuthFlow(secretsAuth, ctx.EnvironmentSet.EnvName); err != nil {
65+
return err
66+
}
67+
6068
h, err := common.NewHandler(ctx, secretsFilePath)
6169
if err != nil {
6270
return err
@@ -78,7 +86,6 @@ func New(ctx *runtime.Context) *cobra.Command {
7886
return fmt.Errorf("invalid --timeout: must be greater than 0 and less than %dh (%dd)", maxHours, maxDays)
7987
}
8088

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

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

@@ -101,7 +107,7 @@ func New(ctx *runtime.Context) *cobra.Command {
101107
// Two paths:
102108
// - MSIG step 1: build request, compute digest, write bundle, print steps
103109
// - EOA: allowlist if needed, then POST to gateway
104-
func Execute(h *common.Handler, inputs DeleteSecretsInputs, duration time.Duration, ownerType string) error {
110+
func Execute(h *common.Handler, inputs DeleteSecretsInputs, duration time.Duration, secretsAuth string) error {
105111
spinner := ui.NewSpinner()
106112
spinner.Start("Verifying ownership...")
107113
if err := h.EnsureOwnerLinkedOrFail(); err != nil {

cmd/secrets/list/list.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ func New(ctx *runtime.Context) *cobra.Command {
3636
Use: "list",
3737
Short: "Lists secret identifiers for the current owner address in the given namespace.",
3838
RunE: func(cmd *cobra.Command, args []string) error {
39+
secretsAuth, err := cmd.Flags().GetString("secrets-auth")
40+
if err != nil {
41+
return err
42+
}
43+
if err := common.ValidateSecretsAuthFlow(secretsAuth, ctx.EnvironmentSet.EnvName); err != nil {
44+
return err
45+
}
46+
3947
h, err := common.NewHandler(ctx, "")
4048
if err != nil {
4149
return err
@@ -58,12 +66,7 @@ func New(ctx *runtime.Context) *cobra.Command {
5866
return fmt.Errorf("invalid --timeout: must be greater than 0 and less than %dh (%dd)", maxHours, maxDays)
5967
}
6068

61-
return Execute(
62-
h,
63-
namespace,
64-
duration,
65-
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType,
66-
)
69+
return Execute(h, namespace, duration, secretsAuth)
6770
},
6871
}
6972

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

7780
// Execute performs: build request → (MSIG step 1 bundle OR EOA allowlist+post) → parse.
78-
func Execute(h *common.Handler, namespace string, duration time.Duration, ownerType string) error {
81+
func Execute(h *common.Handler, namespace string, duration time.Duration, secretsAuth string) error {
7982
spinner := ui.NewSpinner()
8083
spinner.Start("Verifying ownership...")
8184
if err := h.EnsureOwnerLinkedOrFail(); err != nil {

cmd/secrets/secrets.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ func New(runtimeContext *runtime.Context) *cobra.Command {
3232
"Timeout for secrets operations (e.g. 30m, 2h, 48h).",
3333
)
3434

35+
secretsCmd.PersistentFlags().String("secrets-auth", "owner-key-signing", "Auth flow for secrets operations (owner-key-signing, browser).")
36+
_ = secretsCmd.PersistentFlags().MarkHidden("secrets-auth")
37+
3538
secretsCmd.AddCommand(create.New(runtimeContext))
3639
secretsCmd.AddCommand(update.New(runtimeContext))
3740
secretsCmd.AddCommand(delete.New(runtimeContext))

cmd/secrets/update/update.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ func New(ctx *runtime.Context) *cobra.Command {
2424
RunE: func(cmd *cobra.Command, args []string) error {
2525
secretsFilePath := args[0]
2626

27+
secretsAuth, err := cmd.Flags().GetString("secrets-auth")
28+
if err != nil {
29+
return err
30+
}
31+
if err := common.ValidateSecretsAuthFlow(secretsAuth, ctx.EnvironmentSet.EnvName); err != nil {
32+
return err
33+
}
34+
2735
h, err := common.NewHandler(ctx, secretsFilePath)
2836
if err != nil {
2937
return err
@@ -55,12 +63,7 @@ func New(ctx *runtime.Context) *cobra.Command {
5563
return err
5664
}
5765

58-
return h.Execute(
59-
inputs,
60-
vaulttypes.MethodSecretsUpdate,
61-
duration,
62-
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType,
63-
)
66+
return h.Execute(inputs, vaulttypes.MethodSecretsUpdate, duration, secretsAuth)
6467
},
6568
}
6669

0 commit comments

Comments
 (0)