Skip to content

Commit 7aaabef

Browse files
jgbernalpopenshift-cherrypick-robot
authored andcommitted
OCPBUGS-62276: allow additional TLS config
Signed-off-by: Gabriel Bernal <gbernal@redhat.com>
1 parent 9a8dd0e commit 7aaabef

4 files changed

Lines changed: 463 additions & 43 deletions

File tree

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ build-backend:
4848
start-backend:
4949
go run ./cmd/plugin-backend.go -port='9001' -config-path='./config' -static-path='./web/dist'
5050

51+
.PHONY: test-backend
52+
test-backend:
53+
go test ./pkg/... -v
54+
5155
.PHONY: build-image
5256
build-image:
5357
./scripts/build-image.sh

cmd/plugin-backend.go

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package main
22

33
import (
4+
"context"
5+
"crypto/tls"
46
"flag"
57
"os"
68
"strconv"
@@ -21,6 +23,9 @@ var (
2123
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')")
2224
alertmanagerUrlArg = flag.String("alertmanager", "", "alertmanager url to proxy to for acm mode")
2325
thanosQuerierUrlArg = flag.String("thanos-querier", "", "thanos querier url to proxy to for acm mode")
26+
tlsMinVersionArg = flag.String("tls-min-version", "", "minimum TLS version\noptions: ['VersionTLS10', 'VersionTLS11', 'VersionTLS12', 'VersionTLS13']\n(default 'VersionTLS12')")
27+
tlsMaxVersionArg = flag.String("tls-max-version", "", "maximum TLS version\noptions: ['VersionTLS10', 'VersionTLS11', 'VersionTLS12', 'VersionTLS13']\n(default is the highest supported by Go)")
28+
tlsCipherSuitesArg = flag.String("tls-cipher-suites", "", "comma-separated list of cipher suites for the server\nvalues are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants)")
2429
log = logrus.WithField("module", "main")
2530
)
2631

@@ -37,6 +42,9 @@ func main() {
3742
logLevel := mergeEnvValue("MONITORING_PLUGIN_LOG_LEVEL", *logLevelArg, logrus.InfoLevel.String())
3843
alertmanagerUrl := mergeEnvValue("MONITORING_PLUGIN_ALERTMANAGER", *alertmanagerUrlArg, "")
3944
thanosQuerierUrl := mergeEnvValue("MONITORING_PLUGIN_THANOS_QUERIER", *thanosQuerierUrlArg, "")
45+
tlsMinVersion := mergeEnvValue("TLS_MIN_VERSION", *tlsMinVersionArg, "")
46+
tlsMaxVersion := mergeEnvValue("TLS_MAX_VERSION", *tlsMaxVersionArg, "")
47+
tlsCipherSuites := mergeEnvValue("TLS_CIPHER_SUITES", *tlsCipherSuitesArg, "")
4048

4149
featuresList := strings.Fields(strings.Join(strings.Split(strings.ToLower(features), ","), " "))
4250

@@ -54,7 +62,12 @@ func main() {
5462

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

57-
server.Start(&server.Config{
65+
// Parse TLS configuration
66+
tlsMinVer := parseTLSVersion(tlsMinVersion)
67+
tlsMaxVer := parseTLSVersion(tlsMaxVersion)
68+
tlsCiphers := parseCipherSuites(tlsCipherSuites)
69+
70+
srv, err := server.CreateServer(context.Background(), &server.Config{
5871
Port: port,
5972
CertFile: cert,
6073
PrivateKeyFile: key,
@@ -64,7 +77,18 @@ func main() {
6477
PluginConfigPath: pluginConfigPath,
6578
AlertmanagerUrl: alertmanagerUrl,
6679
ThanosQuerierUrl: thanosQuerierUrl,
80+
TLSMinVersion: tlsMinVer,
81+
TLSMaxVersion: tlsMaxVer,
82+
TLSCipherSuites: tlsCiphers,
6783
})
84+
85+
if err != nil {
86+
panic(err)
87+
}
88+
89+
if err = srv.StartHTTPServer(); err != nil {
90+
panic(err)
91+
}
6892
}
6993

7094
func mergeEnvValue(key string, arg string, defaultValue string) string {
@@ -95,3 +119,66 @@ func mergeEnvValueInt(key string, arg int, defaultValue int) int {
95119

96120
return defaultValue
97121
}
122+
123+
func getCipherSuitesMap() map[string]uint16 {
124+
result := make(map[string]uint16)
125+
126+
for _, suite := range tls.CipherSuites() {
127+
result[suite.Name] = suite.ID
128+
}
129+
130+
return result
131+
}
132+
133+
func getTLSVersionsMap() map[string]uint16 {
134+
versions := make(map[string]uint16)
135+
136+
versions["VersionTLS12"] = tls.VersionTLS12
137+
versions["VersionTLS13"] = tls.VersionTLS13
138+
139+
return versions
140+
}
141+
142+
func parseTLSVersion(version string) uint16 {
143+
if version == "" {
144+
return tls.VersionTLS12
145+
}
146+
147+
tlsVersions := getTLSVersionsMap()
148+
149+
if v, ok := tlsVersions[version]; ok {
150+
return v
151+
}
152+
153+
log.Warnf("Invalid TLS version %q, using default VersionTLS12", version)
154+
return tls.VersionTLS12
155+
}
156+
157+
func parseCipherSuites(ciphers string) []uint16 {
158+
if ciphers == "" {
159+
return nil
160+
}
161+
162+
cipherMap := getCipherSuitesMap()
163+
164+
cipherNames := strings.Split(strings.ReplaceAll(ciphers, " ", ""), ",")
165+
var result []uint16
166+
167+
for _, name := range cipherNames {
168+
if name == "" {
169+
continue
170+
}
171+
if cipher, ok := cipherMap[name]; ok {
172+
result = append(result, cipher)
173+
} else {
174+
log.Warnf("Unknown cipher suite %q, skipping", name)
175+
}
176+
}
177+
178+
if len(result) == 0 {
179+
log.Warn("No valid cipher suites provided, using Go defaults")
180+
return nil
181+
}
182+
183+
return result
184+
}

pkg/server.go

Lines changed: 92 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ type Config struct {
3535
PluginConfigPath string
3636
AlertmanagerUrl string
3737
ThanosQuerierUrl string
38+
TLSMinVersion uint16
39+
TLSMaxVersion uint16
40+
TLSCipherSuites []uint16
41+
}
42+
43+
func (c *Config) IsTLSEnabled() bool {
44+
return c.CertFile != "" && c.PrivateKeyFile != ""
45+
}
46+
47+
type PluginServer struct {
48+
*http.Server
49+
Config *Config
3850
}
3951

4052
type PluginConfig struct {
@@ -61,19 +73,47 @@ func (pluginConfig *PluginConfig) MarshalJSON() ([]byte, error) {
6173
})
6274
}
6375

64-
func Start(cfg *Config) {
76+
func CreateServer(ctx context.Context, cfg *Config) (*PluginServer, error) {
77+
httpServer, err := createHTTPServer(ctx, cfg)
78+
if err != nil {
79+
return nil, err
80+
}
81+
82+
return &PluginServer{
83+
Config: cfg,
84+
Server: httpServer,
85+
}, nil
86+
}
87+
88+
func (s *PluginServer) StartHTTPServer() error {
89+
if s.Config.IsTLSEnabled() {
90+
log.Infof("listening for https on %s", s.Server.Addr)
91+
return s.Server.ListenAndServeTLS(s.Config.CertFile, s.Config.PrivateKeyFile)
92+
}
93+
log.Infof("listening for http on %s", s.Server.Addr)
94+
return s.Server.ListenAndServe()
95+
}
96+
97+
func (s *PluginServer) Shutdown(ctx context.Context) error {
98+
if s.Server != nil {
99+
return s.Server.Shutdown(ctx)
100+
}
101+
return nil
102+
}
103+
104+
func createHTTPServer(ctx context.Context, cfg *Config) (*http.Server, error) {
65105
acmMode := cfg.Features[AcmAlerting]
66106
acmLocationsLength := len(cfg.AlertmanagerUrl) + len(cfg.ThanosQuerierUrl)
67107

68108
if acmLocationsLength > 0 && !acmMode {
69-
log.Panic("alertmanager and thanos-querier cannot be set without the 'acm-alerting' feature flag")
109+
return nil, fmt.Errorf("alertmanager and thanos-querier cannot be set without the 'acm-alerting' feature flag")
70110
}
71111
if acmLocationsLength == 0 && acmMode {
72-
log.Panic("alertmanager and thanos-querier must be set to use the 'acm-alerting' feature flag")
112+
return nil, fmt.Errorf("alertmanager and thanos-querier must be set to use the 'acm-alerting' feature flag")
73113
}
74114

75115
if cfg.Port == int(proxy.AlertmanagerPort) || cfg.Port == int(proxy.ThanosQuerierPort) {
76-
log.Panic(fmt.Printf("Cannot set default port to reserved port %d", cfg.Port))
116+
return nil, fmt.Errorf("cannot set default port to reserved port %d", cfg.Port)
77117
}
78118

79119
// Uncomment the following line for local development:
@@ -86,12 +126,12 @@ func Start(cfg *Config) {
86126
k8sconfig, err := rest.InClusterConfig()
87127

88128
if err != nil {
89-
panic(fmt.Errorf("cannot get in cluster config: %w", err))
129+
return nil, fmt.Errorf("cannot get in cluster config: %w", err)
90130
}
91131

92132
k8sclient, err = dynamic.NewForConfig(k8sconfig)
93133
if err != nil {
94-
panic(fmt.Errorf("error creating dynamicClient: %w", err))
134+
return nil, fmt.Errorf("error creating dynamicClient: %w", err)
95135
}
96136
} else {
97137
k8sclient = nil
@@ -100,15 +140,27 @@ func Start(cfg *Config) {
100140
router, pluginConfig := setupRoutes(cfg)
101141
router.Use(corsHeaderMiddleware())
102142

103-
tlsConfig := &tls.Config{
104-
MinVersion: tls.VersionTLS12,
105-
}
106-
tlsEnabled := cfg.CertFile != "" && cfg.PrivateKeyFile != ""
143+
tlsConfig := &tls.Config{}
144+
145+
tlsEnabled := cfg.IsTLSEnabled()
107146
if tlsEnabled {
147+
// Set MinVersion - default to TLS 1.2 if not specified
148+
if cfg.TLSMinVersion != 0 {
149+
tlsConfig.MinVersion = cfg.TLSMinVersion
150+
} else {
151+
tlsConfig.MinVersion = tls.VersionTLS12
152+
}
153+
154+
if cfg.TLSMaxVersion != 0 {
155+
tlsConfig.MaxVersion = cfg.TLSMaxVersion
156+
}
157+
158+
if len(cfg.TLSCipherSuites) > 0 {
159+
tlsConfig.CipherSuites = cfg.TLSCipherSuites
160+
}
161+
108162
// Build and run the controller which reloads the certificate and key
109163
// files whenever they change.
110-
ctx := context.Background()
111-
112164
certKeyPair, err := dynamiccertificates.NewDynamicServingContentFromFiles("serving-cert", cfg.CertFile, cfg.PrivateKeyFile)
113165
if err != nil {
114166
log.WithError(err).Fatal("unable to create TLS controller")
@@ -138,6 +190,7 @@ func Start(cfg *Config) {
138190
// Notify cert/key file changes to the controller.
139191
certKeyPair.AddListener(ctrl)
140192

193+
// Start certificate controllers in background
141194
go ctrl.Run(1, ctx.Done())
142195
go certKeyPair.Run(ctx, 1)
143196
}
@@ -160,18 +213,13 @@ func Start(cfg *Config) {
160213
httpServer.Handler = loggedRouter
161214
}
162215

163-
if tlsEnabled {
164-
if acmMode {
165-
startProxy(cfg, k8sclient, tlsConfig, timeout, proxy.AlertManagerKind, proxy.AlertmanagerPort)
166-
startProxy(cfg, k8sclient, tlsConfig, timeout, proxy.ThanosQuerierKind, proxy.ThanosQuerierPort)
167-
}
168-
169-
log.Infof("listening for https on %s", httpServer.Addr)
170-
panic(httpServer.ListenAndServeTLS(cfg.CertFile, cfg.PrivateKeyFile))
171-
} else {
172-
log.Infof("listening for http on %s", httpServer.Addr)
173-
panic(httpServer.ListenAndServe())
216+
// Start proxy servers if in ACM mode
217+
if tlsEnabled && acmMode {
218+
startProxy(cfg, k8sclient, tlsConfig, timeout, proxy.AlertManagerKind, proxy.AlertmanagerPort)
219+
startProxy(cfg, k8sclient, tlsConfig, timeout, proxy.ThanosQuerierKind, proxy.ThanosQuerierPort)
174220
}
221+
222+
return httpServer, nil
175223
}
176224

177225
func setupRoutes(cfg *Config) (*mux.Router, *PluginConfig) {
@@ -210,17 +258,32 @@ func setupProxyRoutes(cfg *Config, k8sclient *dynamic.DynamicClient, kind proxy.
210258
return router
211259
}
212260

213-
func filesHandler(root http.FileSystem) http.Handler {
214-
fileServer := http.FileServer(root)
215-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
216-
filePath := r.URL.Path
261+
type headerPreservingWriter struct {
262+
http.ResponseWriter
263+
wroteHeader bool
264+
}
217265

218-
// disable caching for plugin entry point
219-
if strings.HasPrefix(filePath, "/plugin-entry.js") {
266+
func (w *headerPreservingWriter) WriteHeader(statusCode int) {
267+
if !w.wroteHeader {
268+
if w.Header().Get("Cache-Control") == "" {
220269
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
270+
}
271+
if w.Header().Get("Expires") == "" {
221272
w.Header().Set("Expires", "0")
222273
}
274+
w.wroteHeader = true
275+
}
276+
w.ResponseWriter.WriteHeader(statusCode)
277+
}
223278

279+
func filesHandler(root http.FileSystem) http.Handler {
280+
fileServer := http.FileServer(root)
281+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
282+
// disable caching for plugin entry point
283+
if strings.HasPrefix(r.URL.Path, "/plugin-entry.js") {
284+
fileServer.ServeHTTP(&headerPreservingWriter{ResponseWriter: w}, r)
285+
return
286+
}
224287
fileServer.ServeHTTP(w, r)
225288
})
226289
}

0 commit comments

Comments
 (0)