Repository navigation
fix(cmd)!: validate port is numeric and valid uint16 #2107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,8 @@ package cmd | |||||||||||||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||||||||||||||||||||||
| "log" | ||||||||||||||||||||||||||||||||||||||||||||||
| "math" | ||||||||||||||||||||||||||||||||||||||||||||||
| "strconv" | ||||||||||||||||||||||||||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||||||||||||||||||||||||||
| "time" | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -11,6 +13,7 @@ import ( | |||||||||||||||||||||||||||||||||||||||||||||
| "github.com/open-feature/flagd/core/pkg/sync" | ||||||||||||||||||||||||||||||||||||||||||||||
| syncbuilder "github.com/open-feature/flagd/core/pkg/sync/builder" | ||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/open-feature/flagd/flagd/pkg/runtime" | ||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/spf13/cast" | ||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/spf13/cobra" | ||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/spf13/pflag" | ||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/spf13/viper" | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -220,12 +223,29 @@ var startCmd = &cobra.Command{ | |||||||||||||||||||||||||||||||||||||||||||||
| rtLogger.Info(fmt.Sprintf("request header limit set to %d bytes", maxRequestHeaderBytes)) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| managementPort, err := getPort(managementPortFlagName) | ||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||
| log.Fatal(err) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| ofrepPort, err := getPort(ofrepPortFlagName) | ||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||
| log.Fatal(err) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| servicePort, err := getPort(portFlagName) | ||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||
| log.Fatal(err) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| syncPort, err := getPort(syncPortFlagName) | ||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||
| log.Fatal(err) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Build Runtime ----------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||
| rt, err := runtime.FromConfig(logger, Version, runtime.Config{ | ||||||||||||||||||||||||||||||||||||||||||||||
| CORS: viper.GetStringSlice(corsFlagName), | ||||||||||||||||||||||||||||||||||||||||||||||
| MetricExporter: viper.GetString(metricsExporter), | ||||||||||||||||||||||||||||||||||||||||||||||
| ManagementPort: viper.GetUint16(managementPortFlagName), | ||||||||||||||||||||||||||||||||||||||||||||||
| OfrepServicePort: viper.GetUint16(ofrepPortFlagName), | ||||||||||||||||||||||||||||||||||||||||||||||
| ManagementPort: managementPort, | ||||||||||||||||||||||||||||||||||||||||||||||
| OfrepServicePort: ofrepPort, | ||||||||||||||||||||||||||||||||||||||||||||||
| OfrepSSEEnabled: viper.GetBool(ofrepSSEEnabledFlagName), | ||||||||||||||||||||||||||||||||||||||||||||||
| OfrepSSEInactivityDel: viper.GetInt(ofrepSSEInactivityFlagName), | ||||||||||||||||||||||||||||||||||||||||||||||
| OfrepSSEPublicURL: viper.GetString(ofrepSSEPublicURLFlagName), | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -236,9 +256,9 @@ var startCmd = &cobra.Command{ | |||||||||||||||||||||||||||||||||||||||||||||
| OtelCAPath: viper.GetString(otelCAPathFlagName), | ||||||||||||||||||||||||||||||||||||||||||||||
| ServiceCertPath: viper.GetString(serverCertPathFlagName), | ||||||||||||||||||||||||||||||||||||||||||||||
| ServiceKeyPath: viper.GetString(serverKeyPathFlagName), | ||||||||||||||||||||||||||||||||||||||||||||||
| ServicePort: viper.GetUint16(portFlagName), | ||||||||||||||||||||||||||||||||||||||||||||||
| ServicePort: servicePort, | ||||||||||||||||||||||||||||||||||||||||||||||
| ServiceSocketPath: viper.GetString(socketPathFlagName), | ||||||||||||||||||||||||||||||||||||||||||||||
| SyncServicePort: viper.GetUint16(syncPortFlagName), | ||||||||||||||||||||||||||||||||||||||||||||||
| SyncServicePort: syncPort, | ||||||||||||||||||||||||||||||||||||||||||||||
| SyncServiceHTTPEnabled: viper.GetBool(syncHTTPFlagName), | ||||||||||||||||||||||||||||||||||||||||||||||
| SyncServiceSocketPath: viper.GetString(syncSocketPathFlagName), | ||||||||||||||||||||||||||||||||||||||||||||||
| StreamDeadline: viper.GetDuration(streamDeadlineFlagName), | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -263,3 +283,40 @@ var startCmd = &cobra.Command{ | |||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| func getPort(flagName string) (uint16, error) { | ||||||||||||||||||||||||||||||||||||||||||||||
| val := viper.Get(flagName) | ||||||||||||||||||||||||||||||||||||||||||||||
| if val == nil { | ||||||||||||||||||||||||||||||||||||||||||||||
| return 0, nil | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| if str, ok := val.(string); ok { | ||||||||||||||||||||||||||||||||||||||||||||||
| if strings.HasPrefix(str, "tcp://") { | ||||||||||||||||||||||||||||||||||||||||||||||
| return 0, fmt.Errorf("invalid port value for '%s': %q (this can be caused by Kubernetes service links, set enableServiceLinks: false on the pod)", flagName, str) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| p, err := strconv.ParseUint(str, 10, 16) | ||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||
| return 0, fmt.Errorf("invalid port value for '%s': %w", flagName, err) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| return uint16(p), nil | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| switch v := val.(type) { | ||||||||||||||||||||||||||||||||||||||||||||||
| case bool: | ||||||||||||||||||||||||||||||||||||||||||||||
| return 0, fmt.Errorf("invalid port value for '%s': must be an integer", flagName) | ||||||||||||||||||||||||||||||||||||||||||||||
| case float32: | ||||||||||||||||||||||||||||||||||||||||||||||
| if math.Trunc(float64(v)) != float64(v) { | ||||||||||||||||||||||||||||||||||||||||||||||
| return 0, fmt.Errorf("invalid port value for '%s': must be an integer", flagName) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| case float64: | ||||||||||||||||||||||||||||||||||||||||||||||
| if math.Trunc(v) != v { | ||||||||||||||||||||||||||||||||||||||||||||||
| return 0, fmt.Errorf("invalid port value for '%s': must be an integer", flagName) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| p, err := cast.ToUint16E(val) | ||||||||||||||||||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||
| return 0, fmt.Errorf("invalid port value for '%s': %w", flagName, err) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| if i, err := cast.ToInt64E(val); err == nil && (i < 0 || i > math.MaxUint16) { | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+314
to
+318
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔎 Supported by static analysis
Reject booleans and fractional floats before conversion.
The This is a narrow validation gap, not a major issue. Explicit port Suggested fix
Committable suggestion
🧰 Tools
|
||||||||||||||||||||||||||||||||||||||||||||||
| return 0, fmt.Errorf("invalid port value for '%s': value out of range", flagName) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| return p, nil | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| package cmd | ||
|
|
||
| import ( | ||
| "strings" | ||
| "testing" | ||
|
|
||
| "github.com/spf13/viper" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func Test_getPort(t *testing.T) { | ||
| viper.AutomaticEnv() | ||
| t.Cleanup(func() { | ||
| viper.Reset() | ||
| viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) | ||
| viper.SetEnvPrefix("FLAGD") | ||
| bindFlags(startCmd.Flags()) | ||
| }) | ||
|
|
||
| tests := []struct { | ||
| name string | ||
| flagName string | ||
| envVar string | ||
| envVal string | ||
| expected uint16 | ||
| expectedErr string | ||
| expectError bool | ||
| }{ | ||
| { | ||
| name: "default port value when env var not set", | ||
| flagName: portFlagName, | ||
| expected: 8013, | ||
| expectError: false, | ||
| }, | ||
| { | ||
| name: "default management port value when env var not set", | ||
| flagName: managementPortFlagName, | ||
| expected: 8014, | ||
| expectError: false, | ||
| }, | ||
| { | ||
| name: "default sync port value when env var not set", | ||
| flagName: syncPortFlagName, | ||
| expected: 8015, | ||
| expectError: false, | ||
| }, | ||
| { | ||
| name: "default ofrep port value when env var not set", | ||
| flagName: ofrepPortFlagName, | ||
| expected: 8016, | ||
| expectError: false, | ||
| }, | ||
| { | ||
| name: "valid numeric port via env var", | ||
| flagName: portFlagName, | ||
| envVar: "FLAGD_PORT", | ||
| envVal: "9000", | ||
| expected: 9000, | ||
| expectError: false, | ||
| }, | ||
| { | ||
| name: "leading zero decimal port resolves as base 10", | ||
| flagName: portFlagName, | ||
| envVar: "FLAGD_PORT", | ||
| envVal: "010000", | ||
| expected: 10000, | ||
| expectError: false, | ||
| }, | ||
| { | ||
| name: "non-numeric port via env var returns error", | ||
| flagName: portFlagName, | ||
| envVar: "FLAGD_PORT", | ||
| envVal: "abc", | ||
| expectError: true, | ||
| }, | ||
| { | ||
| name: "k8s service link url via env var returns error", | ||
| flagName: portFlagName, | ||
| envVar: "FLAGD_PORT", | ||
| envVal: "tcp://10.0.0.1:8015", | ||
| expectedErr: `invalid port value for 'port': "tcp://10.0.0.1:8015" (this can be caused by Kubernetes service links, set enableServiceLinks: false on the pod)`, | ||
| expectError: true, | ||
| }, | ||
| { | ||
| name: "out of range port via env var returns error", | ||
| flagName: portFlagName, | ||
| envVar: "FLAGD_PORT", | ||
| envVal: "70000", | ||
| expectError: true, | ||
| }, | ||
| { | ||
| name: "negative port via env var returns error", | ||
| flagName: portFlagName, | ||
| envVar: "FLAGD_PORT", | ||
| envVal: "-1", | ||
| expectError: true, | ||
| }, | ||
| { | ||
| name: "non-numeric management port via env var returns error", | ||
| flagName: managementPortFlagName, | ||
| envVar: "FLAGD_MANAGEMENT_PORT", | ||
| envVal: "invalid-port", | ||
| expectError: true, | ||
| }, | ||
| { | ||
| name: "non-numeric sync port via env var returns error", | ||
| flagName: syncPortFlagName, | ||
| envVar: "FLAGD_SYNC_PORT", | ||
| envVal: "invalid-port", | ||
| expectError: true, | ||
| }, | ||
| { | ||
| name: "non-numeric ofrep port via env var returns error", | ||
| flagName: ofrepPortFlagName, | ||
| envVar: "FLAGD_OFREP_PORT", | ||
| envVal: "invalid-port", | ||
| expectError: true, | ||
| }, | ||
| { | ||
| name: "nil flag returns 0 and nil error", | ||
| flagName: "non_existent_flag", | ||
| expected: 0, | ||
| expectError: false, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| if tt.envVar != "" { | ||
| t.Setenv(tt.envVar, tt.envVal) | ||
| } | ||
| got, err := getPort(tt.flagName) | ||
| if tt.expectError { | ||
| require.Error(t, err) | ||
| assert.Contains(t, err.Error(), "invalid port value for '"+tt.flagName+"'") | ||
| if tt.expectedErr != "" { | ||
| assert.EqualError(t, err, tt.expectedErr) | ||
| } | ||
| } else { | ||
| require.NoError(t, err) | ||
| assert.Equal(t, tt.expected, got) | ||
| } | ||
| }) | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be simpler: one
cast.ToInt64E, then a0..65535check, instead of the type switch.