Skip to content
Draft
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
64 changes: 61 additions & 3 deletions pkg/cmd/ingresscmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/kernel/hypeman-go"
"github.com/kernel/hypeman-go/option"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
"github.com/urfave/cli/v3"
)

Expand Down Expand Up @@ -86,15 +87,25 @@ var ingressListCmd = cli.Command{
Name: "tag",
Usage: "Filter by tag key-value pair (KEY=VALUE, can be repeated)",
},
&cli.BoolFlag{
Name: "show-secrets",
Usage: "Show request header authorization values in structured output (default: hidden)",
},
},
Action: handleIngressList,
HideHelpCommand: true,
}

var ingressGetCmd = cli.Command{
Name: "get",
Usage: "Get ingress details",
ArgsUsage: "<id>",
Name: "get",
Usage: "Get ingress details",
ArgsUsage: "<id>",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "show-secrets",
Usage: "Show request header authorization values (default: hidden)",
},
},
Action: handleIngressGet,
HideHelpCommand: true,
}
Expand Down Expand Up @@ -220,6 +231,9 @@ func handleIngressList(ctx context.Context, cmd *cli.Command) error {
if err != nil {
return err
}
if !cmd.Bool("show-secrets") {
res = []byte(redactIngressAuthValues(string(res)))
}
obj := gjson.ParseBytes(res)
return ShowJSON(os.Stdout, "ingress list", obj, format, transform)
}
Expand Down Expand Up @@ -296,6 +310,9 @@ func handleIngressGet(ctx context.Context, cmd *cli.Command) error {
if err != nil {
return err
}
if !cmd.Bool("show-secrets") {
res = []byte(redactIngressAuthValues(string(res)))
}

format := cmd.Root().String("format")
transform := cmd.Root().String("transform")
Expand All @@ -304,6 +321,47 @@ func handleIngressGet(ctx context.Context, cmd *cli.Command) error {
return ShowJSON(os.Stdout, "ingress get", obj, format, transform)
}

func redactIngressAuthValues(raw string) string {
root := gjson.Parse(raw)
out := raw
redactRules := func(prefix string, rules gjson.Result) bool {
ruleIndex := 0
ok := true
rules.ForEach(func(_, rule gjson.Result) bool {
if rule.Get("request_header_auth.value").Exists() {
path := fmt.Sprintf("%s.%d.request_header_auth.value", prefix, ruleIndex)
updated, err := sjson.Set(out, path, "[hidden]")
if err != nil {
ok = false
return false
}
out = updated
}
ruleIndex++
return true
})
return ok
}

if root.IsArray() {
ingressIndex := 0
ok := true
root.ForEach(func(_, ingress gjson.Result) bool {
ok = redactRules(fmt.Sprintf("%d.rules", ingressIndex), ingress.Get("rules"))
ingressIndex++
return ok
})
if !ok {
return raw
}
return out
}
if !redactRules("rules", root.Get("rules")) {
return raw
}
return out
}

func handleIngressDelete(ctx context.Context, cmd *cli.Command) error {
args := cmd.Args().Slice()
if len(args) < 1 {
Expand Down
24 changes: 24 additions & 0 deletions pkg/cmd/ingresscmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,32 @@ import (

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

func TestRedactIngressAuthValues(t *testing.T) {
t.Run("redacts get response", func(t *testing.T) {
raw := `{"id":"ing-1","rules":[{"request_header_auth":{"header":"X-Origin-Verification","value":"secret-value"}},{"target":{"port":8080}}]}`
redacted := redactIngressAuthValues(raw)

assert.Equal(t, "[hidden]", gjson.Get(redacted, "rules.0.request_header_auth.value").String())
assert.Equal(t, "X-Origin-Verification", gjson.Get(redacted, "rules.0.request_header_auth.header").String())
})

t.Run("redacts list response", func(t *testing.T) {
raw := `[{"id":"ing-1","rules":[{"request_header_auth":{"value":"first-secret"}}]},{"id":"ing-2","rules":[{"request_header_auth":{"value":"second-secret"}}]}]`
redacted := redactIngressAuthValues(raw)

assert.Equal(t, "[hidden]", gjson.Get(redacted, "0.rules.0.request_header_auth.value").String())
assert.Equal(t, "[hidden]", gjson.Get(redacted, "1.rules.0.request_header_auth.value").String())
})

t.Run("preserves response without authorization values", func(t *testing.T) {
raw := `{"id":"ing-1","rules":[{"target":{"port":8080}}]}`
assert.Equal(t, raw, redactIngressAuthValues(raw))
})
}

func TestParseIngressRuleSpec(t *testing.T) {
t.Run("full spec with host port, tls, and redirect", func(t *testing.T) {
rule, err := parseIngressRuleSpec("api.example.com:443=web:8080,tls,redirect-http", "fallback")
Expand Down
Loading