Skip to content
Open
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
83 changes: 68 additions & 15 deletions cmd/plugin-backend.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
package main

import (
"crypto/tls"
"flag"
"fmt"
"os"
"strconv"
"strings"

server "github.com/openshift/troubleshooting-panel-console-plugin/pkg"
"github.com/sirupsen/logrus"
k8sapiflag "k8s.io/component-base/cli/flag"
)

var (
portArg = flag.Int("port", 0, "server port to listen on (default: 9443)")
certArg = flag.String("cert", "", "cert file path to enable TLS (disabled by default)")
keyArg = flag.String("key", "", "private key file path to enable TLS (disabled by default)")
featuresArg = flag.String("features", "", "enabled features, comma separated")
staticPathArg = flag.String("static-path", "", "static files path to serve frontend (default: './web/dist')")
configPathArg = flag.String("config-path", "", "config files path (default: './config')")
pluginConfigArg = flag.String("plugin-config-path", "", "plugin yaml configuration")
logLevelArg = flag.String("log-level", logrus.InfoLevel.String(), "verbosity of logs\noptions: ['panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace']\n'trace' level will log all incoming requests\n(default 'error')")
log = logrus.WithField("module", "main")
portArg = flag.Int("port", 0, "server port to listen on (default: 9443)")
certArg = flag.String("cert", "", "cert file path to enable TLS (disabled by default)")
keyArg = flag.String("key", "", "private key file path to enable TLS (disabled by default)")
tlsMinVersionArg = flag.String("tls-min-version", "", "minimum TLS version (e.g., VersionTLS12, VersionTLS13)")
tlsCipherSuitesArg = flag.String("tls-cipher-suites", "", "comma-separated list of cipher suites for TLS 1.0-1.2 (ignored for TLS 1.3)")
featuresArg = flag.String("features", "", "enabled features, comma separated")
staticPathArg = flag.String("static-path", "", "static files path to serve frontend (default: './web/dist')")
configPathArg = flag.String("config-path", "", "config files path (default: './config')")
pluginConfigArg = flag.String("plugin-config-path", "", "plugin yaml configuration")
logLevelArg = flag.String("log-level", logrus.InfoLevel.String(), "verbosity of logs\noptions: ['panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace']\n'trace' level will log all incoming requests\n(default 'error')")
log = logrus.WithField("module", "main")
)

func main() {
Expand All @@ -28,6 +33,8 @@ func main() {
port := mergeEnvValueInt("PORT", *portArg, 9443)
cert := mergeEnvValue("CERT_FILE_PATH", *certArg, "")
key := mergeEnvValue("PRIVATE_KEY_FILE_PATH", *keyArg, "")
tlsMinVersion := mergeEnvValue("TLS_MIN_VERSION", *tlsMinVersionArg, "")
tlsCipherSuites := mergeEnvValue("TLS_CIPHER_SUITES", *tlsCipherSuitesArg, "")
features := mergeEnvValue("TROUBLESHOOTING_PANEL_CONSOLE_PLUGIN_FEATURES", *featuresArg, "")
staticPath := mergeEnvValue("TROUBLESHOOTING_PANEL_CONSOLE_PLUGIN_STATIC_PATH", *staticPathArg, "opt/app-root/web/dist")
configPath := mergeEnvValue("TROUBLESHOOTING_PANEL_CONSOLE_PLUGIN_MANIFEST_CONFIG_PATH", *configPathArg, "opt/app-root/web/dist")
Expand All @@ -49,13 +56,25 @@ func main() {

log.Infof("enabled features: %+q\n", featuresList)

tlsMinVer, err := parseTLSVersion(tlsMinVersion)
Comment thread
shwetaap marked this conversation as resolved.
if err != nil {
log.WithError(err).Fatal("Invalid TLS minimum version")
}

tlsCipherSuitesList, err := parseCipherSuites(tlsCipherSuites)
if err != nil {
log.WithError(err).Fatal("Invalid TLS cipher suites")
}

server.Start(&server.Config{
Port: port,
CertFile: cert,
PrivateKeyFile: key,
Features: featuresSet,
StaticPath: staticPath,
ConfigPath: configPath,
Port: port,
CertFile: cert,
PrivateKeyFile: key,
TLSMinVersion: tlsMinVer,
TLSCipherSuites: tlsCipherSuitesList,
Features: featuresSet,
StaticPath: staticPath,
ConfigPath: configPath,
PluginConfigPath: pluginConfigPath,
})
}
Expand Down Expand Up @@ -88,3 +107,37 @@ func mergeEnvValueInt(key string, arg int, defaultValue int) int {

return defaultValue
}

func parseTLSVersion(version string) (uint16, error) {
if version == "" {
return 0, nil
}
tlsVersion, err := k8sapiflag.TLSVersion(version)
if err != nil {
return 0, err
}
// Reject TLS 1.0 and 1.1 as they are deprecated per RFC 8996
if tlsVersion != 0 && tlsVersion < tls.VersionTLS12 {
return 0, fmt.Errorf("TLS versions below 1.2 are not supported (got %s); minimum allowed is VersionTLS12", version)
}
return tlsVersion, nil
}

func parseCipherSuites(cipherSuitesStr string) ([]uint16, error) {
if cipherSuitesStr == "" {
return nil, nil
}

cipherSuiteNames := strings.Split(cipherSuitesStr, ",")
// Trim whitespace from each cipher suite name
trimmed := make([]string, 0, len(cipherSuiteNames))
for _, name := range cipherSuiteNames {
if t := strings.TrimSpace(name); t != "" {
trimmed = append(trimmed, t)
}
}
if len(trimmed) == 0 {
return nil, nil
}
return k8sapiflag.TLSCipherSuites(trimmed)
}
130 changes: 130 additions & 0 deletions cmd/plugin-backend_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package main

import (
"crypto/tls"
"testing"

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

func TestParseTLSVersion(t *testing.T) {
tests := []struct {
name string
version string
expected uint16
expectError bool
}{
{
name: "Empty string returns 0",
version: "",
expected: 0,
expectError: false,
},
{
name: "Valid TLS 1.2",
version: "VersionTLS12",
expected: tls.VersionTLS12,
expectError: false,
},
{
name: "Valid TLS 1.3",
version: "VersionTLS13",
expected: tls.VersionTLS13,
expectError: false,
},
{
name: "Invalid version",
version: "InvalidVersion",
expected: 0,
expectError: true,
},
{
name: "TLS 1.0 rejected",
version: "VersionTLS10",
expected: 0,
expectError: true,
},
{
name: "TLS 1.1 rejected",
version: "VersionTLS11",
expected: 0,
expectError: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := parseTLSVersion(tt.version)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expected, result)
}
})
}
}

func TestParseCipherSuites(t *testing.T) {
tests := []struct {
name string
input string
expected []uint16
expectError bool
}{
{
name: "Empty string returns nil",
input: "",
expected: nil,
expectError: false,
},
{
name: "Single valid cipher suite",
input: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
expected: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
expectError: false,
},
{
name: "Multiple valid cipher suites",
input: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
expected: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
},
expectError: false,
},
{
name: "Valid cipher suites with spaces",
input: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_AES_128_GCM_SHA256",
expected: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_AES_128_GCM_SHA256,
},
expectError: false,
},
{
name: "Invalid cipher suite",
input: "INVALID_CIPHER",
expected: nil,
expectError: true,
},
{
name: "Mixed valid and invalid",
input: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,INVALID_CIPHER",
expected: nil,
expectError: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := parseCipherSuites(tt.input)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expected, result)
}
})
}
}
7 changes: 6 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ module github.com/openshift/troubleshooting-panel-console-plugin
go 1.24.0

require (
github.com/evanphx/json-patch v0.5.2
github.com/evanphx/json-patch v4.12.0+incompatible
github.com/gorilla/handlers v1.5.2
github.com/gorilla/mux v1.8.1
github.com/openshift/library-go v0.0.0-20240412173449-eb2f24c36528
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.9.0
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.31.1
k8s.io/apiserver v0.31.1
k8s.io/client-go v0.31.1
k8s.io/component-base v0.31.1
)

require (
Expand All @@ -31,6 +33,7 @@ require (
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
Expand All @@ -39,6 +42,8 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/spf13/cobra v1.8.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/oauth2 v0.25.0 // indirect
Expand Down
19 changes: 14 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
Expand Down Expand Up @@ -46,7 +47,8 @@ github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyE
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
Expand All @@ -71,17 +73,22 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/openshift/library-go v0.0.0-20240412173449-eb2f24c36528 h1:vnLKZUSW1aPv7Pd6+QYjDUU+/8z2MSBacU38cAlNMPA=
github.com/openshift/library-go v0.0.0-20240412173449-eb2f24c36528/go.mod h1:m/HsttSi90vSixwoy5mPUBHcZid2YRw/QbsLErLxF9s=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down Expand Up @@ -163,6 +170,8 @@ k8s.io/apiserver v0.31.1 h1:Sars5ejQDCRBY5f7R3QFHdqN3s61nhkpaX8/k1iEw1c=
k8s.io/apiserver v0.31.1/go.mod h1:lzDhpeToamVZJmmFlaLwdYZwd7zB+WYRYIboqA1kGxM=
k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0=
k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg=
k8s.io/component-base v0.31.1 h1:UpOepcrX3rQ3ab5NB6g5iP0tvsgJWzxTyAo20sgYSy8=
k8s.io/component-base v0.31.1/go.mod h1:WGeaw7t/kTsqpVTaCoVEtillbqAhF2/JgvO0LDOMa0w=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
Expand Down
18 changes: 16 additions & 2 deletions pkg/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/gorilla/handlers"
"github.com/gorilla/mux"
oscrypto "github.com/openshift/library-go/pkg/crypto"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
v1 "k8s.io/api/core/v1"
Expand All @@ -26,6 +27,8 @@ type Config struct {
Port int
CertFile string
PrivateKeyFile string
TLSMinVersion uint16
TLSCipherSuites []uint16
Features map[string]bool
StaticPath string
ConfigPath string
Expand All @@ -51,8 +54,19 @@ func Start(cfg *Config) {
router, pluginConfig := setupRoutes(cfg)
router.Use(corsHeaderMiddleware())

tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
tlsConfig := oscrypto.SecureTLSConfig(&tls.Config{})

if cfg.TLSMinVersion != 0 {
tlsConfig.MinVersion = cfg.TLSMinVersion
}
// Note: CipherSuites only applies to TLS 1.0-1.2. TLS 1.3 cipher suites are
// non-configurable in Go's crypto/tls package and will use secure defaults.
if len(cfg.TLSCipherSuites) > 0 {
tlsConfig.CipherSuites = cfg.TLSCipherSuites
Comment thread
coderabbitai[bot] marked this conversation as resolved.
// Warn if cipher suites are configured with TLS 1.3 minimum, as they won't apply
if cfg.TLSMinVersion >= tls.VersionTLS13 {
log.Warn("TLS cipher suites are configured but will be ignored with TLS 1.3; TLS 1.3 uses non-configurable cipher suites")
}
}

timeout := 30 * time.Second
Expand Down