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
6 changes: 6 additions & 0 deletions deploy/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ spec:
env:
- name: INSECURE
value: "true"
- name: TLSMINVERSION
value: "1.3"
- name: TLSMAXVERSION
value: "1.3"
- name: TLSCIPHERSUITES
value: ""
volumeMounts:
- mountPath: "/etc/gitops/ssl"
name: backend-ssl
Expand Down
179 changes: 173 additions & 6 deletions pkg/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"log"
"net/http"
"strings"

argoV1aplha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/prometheus/client_golang/prometheus/promhttp"
Expand All @@ -22,14 +23,155 @@ import (
)

const (
portFlag = "port"
insecureFlag = "insecure"
tlsCertFlag = "tls-cert"
tlsKeyFlag = "tls-key"
noTLSFlag = "no-tls"
enableHTTP2 = "enable-http2"
portFlag = "port"
insecureFlag = "insecure"
tlsCertFlag = "tls-cert"
tlsKeyFlag = "tls-key"
noTLSFlag = "no-tls"
enableHTTP2 = "enable-http2"
tlsMinVersionFlag = "tlsminversion"
tlsMaxVersionFlag = "tlsmaxversion"
tlsCipherSuitesFlag = "tlsciphersuites"
)

type TLSConfig struct {
MinVersion string
MaxVersion string
Ciphers string
}

// tlsVersionMap maps version strings to tls version constants.
// TLS 1.0 is not supported as it is considered insecure.
var tlsVersionMap = map[string]uint16{
"1.1": tls.VersionTLS11,
"tls1.1": tls.VersionTLS11,
"1.2": tls.VersionTLS12,
"tls1.2": tls.VersionTLS12,
"1.3": tls.VersionTLS13,
"tls1.3": tls.VersionTLS13,
}

// TLSVersionName returns a human-readable name for a TLS version constant.
func TLSVersionName(version uint16) string {
for name, v := range tlsVersionMap {
if v == version {
return name
}
}
return fmt.Sprintf("unknown (%d)", version)
}

// ParseTLSVersion parses a TLS version string (e.g. "1.2", "1.3", "TLS1.2") into a tls version constant.
// Returns 0 if the string is empty (meaning "use default").
func ParseTLSVersion(version string) (uint16, error) {
if version == "" {
return 0, nil
}
v, ok := tlsVersionMap[strings.ToLower(strings.TrimSpace(version))]
if !ok {
return 0, fmt.Errorf("unsupported TLS version: %q (supported: 1.1, 1.2, 1.3)", version)
}
return v, nil
}

// ParseTLSCiphers parses a colon-separated list of cipher suite names into cipher suite IDs.
// Only secure cipher suites (from tls.CipherSuites()) are allowed.
// Returns nil if the input is empty.
func ParseTLSCiphers(ciphers string) ([]uint16, error) {
if ciphers == "" {
return nil, nil
}
// Build lookup map from Go's secure cipher suites only
cipherMap := make(map[string]uint16)
for _, cs := range tls.CipherSuites() {
cipherMap[cs.Name] = cs.ID
}
var result []uint16
for _, name := range strings.Split(ciphers, ":") {
name = strings.TrimSpace(name)
if name == "" {
continue
}
id, ok := cipherMap[name]
if !ok {
return nil, fmt.Errorf("unsupported TLS cipher suite: %q", name)
}
result = append(result, id)
}
return result, nil
}

// ValidateTLSConfig validates the TLS configuration parameters.
// It checks that:
// - The minimum TLS version is not greater than the maximum TLS version
// - All configured cipher suites are compatible with the minimum TLS version
func ValidateTLSConfig(minVersion, maxVersion uint16, cipherSuites []uint16) error {
if minVersion != 0 && maxVersion != 0 && minVersion > maxVersion {
return fmt.Errorf("minimum TLS version (%s) cannot be higher than maximum TLS version (%s)",
TLSVersionName(minVersion), TLSVersionName(maxVersion))
}
if len(cipherSuites) > 0 && minVersion != 0 {
availableCiphers := tls.CipherSuites()
for _, cipherID := range cipherSuites {
for _, cs := range availableCiphers {
if cs.ID == cipherID {
supported := false
for _, v := range cs.SupportedVersions {
if v == minVersion {
supported = true
break
}
}
if !supported {
return fmt.Errorf("cipher suite %s is not supported by minimum TLS version %s",
cs.Name, TLSVersionName(minVersion))
}
break
}
}
}
}

return nil
}

func buildTLSConfig() (*tls.Config, error) {
cfg := TLSConfig{
MinVersion: viper.GetString(tlsMinVersionFlag),
MaxVersion: viper.GetString(tlsMaxVersionFlag),
Ciphers: viper.GetString(tlsCipherSuitesFlag),
}

tlsCfg := &tls.Config{} //nolint:gosec
minVer, err := ParseTLSVersion(cfg.MinVersion)
if err != nil {
return nil, fmt.Errorf("invalid --%s: %w", tlsMinVersionFlag, err)
}
tlsCfg.MinVersion = minVer
maxVer, err := ParseTLSVersion(cfg.MaxVersion)
if err != nil {
return nil, fmt.Errorf("invalid --%s: %w", tlsMaxVersionFlag, err)
}
tlsCfg.MaxVersion = maxVer
ciphers, err := ParseTLSCiphers(cfg.Ciphers)
if err != nil {
return nil, fmt.Errorf("invalid --%s: %w", tlsCipherSuitesFlag, err)
}
if len(ciphers) > 0 && minVer == tls.VersionTLS13 {
log.Printf(
"%s has no effect when minimum TLS version is 1.3 because TLS 1.3 cipher suites are not configurable; ignoring",
tlsCipherSuitesFlag,
)
ciphers = nil
}
if err := ValidateTLSConfig(minVer, maxVer, ciphers); err != nil {
return nil, err
}
tlsCfg.CipherSuites = ciphers

return tlsCfg, nil
}

func init() {
cobra.OnInitialize(initConfig)
if err := argoV1aplha1.AddToScheme(scheme.Scheme); err != nil {
Expand Down Expand Up @@ -79,6 +221,11 @@ func makeHTTPCmd() *cobra.Command {
log.Println("TLS connections disabled")
return server.ListenAndServe()
}
tlsConfig, err := buildTLSConfig()
if err != nil {
return err
}
server.TLSConfig = tlsConfig
log.Printf("Using TLS from %q and %q", viper.GetString(tlsCertFlag), viper.GetString(tlsKeyFlag))
return server.ListenAndServeTLS(viper.GetString(tlsCertFlag), viper.GetString(tlsKeyFlag))
},
Expand Down Expand Up @@ -125,6 +272,26 @@ func makeHTTPCmd() *cobra.Command {
"enable HTTP/2 for the server",
)
logIfError(viper.BindPFlag(enableHTTP2, cmd.Flags().Lookup(enableHTTP2)))
cmd.Flags().String(
tlsMinVersionFlag,
"",
"minimum supported TLS version (1.2, 1.3)",
)
logIfError(viper.BindPFlag(tlsMinVersionFlag, cmd.Flags().Lookup(tlsMinVersionFlag)))

cmd.Flags().String(
tlsMaxVersionFlag,
"",
"maximum supported TLS version (1.2, 1.3)",
)
logIfError(viper.BindPFlag(tlsMaxVersionFlag, cmd.Flags().Lookup(tlsMaxVersionFlag)))

cmd.Flags().String(
tlsCipherSuitesFlag,
"",
"comma-separated list of TLS cipher suites",
)
logIfError(viper.BindPFlag(tlsCipherSuitesFlag, cmd.Flags().Lookup(tlsCipherSuitesFlag)))
return cmd
}

Expand Down
Loading