// Copyright 2024 The prometheus-operator Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package clustertlsconfig import ( "fmt" "path" "path/filepath" "gopkg.in/yaml.v2" v1 "k8s.io/api/core/v1" "k8s.io/utils/ptr" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" webconfig "github.com/prometheus-operator/prometheus-operator/pkg/webconfig" ) const ( cmdflag = "cluster.tls-config" volumeName = "cluster-tls-config" serverVolumePrefix = "cluster-tls-server-config-" clientVolumePrefix = "cluster-tls-client-config-" serverTLSCredDir = "server_tls" clientTLSCredDir = "client_tls" ConfigFileKey = "cluster-tls-config.yaml" ) // Config is the web configuration for prometheus and alertmanager instance. // // Config can make a secret which holds the web config contents, as well as // volumes and volume mounts for referencing the secret and the // necessary TLS credentials. type Config struct { clusterTLSConfig *monitoringv1.ClusterTLSConfig serverTLSReferences *webconfig.TLSReferences clientTLSReferences *webconfig.TLSReferences mountingDir string secretName string } // New creates a new ClusterTLSConfig. // All volumes related to the cluster TLS config will be mounted via the `mountingDir`. // The Secret where the cluster TLS config will be stored will be named `secretName`. // All volumes containing TLS credentials related to cluster TLS configuration will be prefixed with "cluster-tls-server-config-" // or "cluster-tls-client-config-" respectively, for server and client credentials. func New(mountingDir string, secretName string, clusterTLSConfig *monitoringv1.ClusterTLSConfig) (*Config, error) { if clusterTLSConfig == nil { return &Config{ mountingDir: mountingDir, secretName: secretName, }, nil } var ( clientTLSCreds *webconfig.TLSReferences serverTLSCreds *webconfig.TLSReferences ) serverTLSConfig := clusterTLSConfig.ServerTLS if err := serverTLSConfig.Validate(); err != nil { return nil, err } clientTLSConfig := clusterTLSConfig.ClientTLS if err := clientTLSConfig.Validate(); err != nil { return nil, err } serverTLSCreds = webconfig.NewTLSReferences(path.Join(mountingDir, serverTLSCredDir), serverTLSConfig.KeySecret, serverTLSConfig.Cert, serverTLSConfig.ClientCA) clientTLSCreds = webconfig.NewTLSReferences(path.Join(mountingDir, clientTLSCredDir), *clientTLSConfig.KeySecret, clientTLSConfig.Cert, clientTLSConfig.CA) return &Config{ clusterTLSConfig: clusterTLSConfig, serverTLSReferences: serverTLSCreds, clientTLSReferences: clientTLSCreds, mountingDir: mountingDir, secretName: secretName, }, nil } // GetMountParameters returns volumes and volume mounts referencing the cluster TLS config file // and the associated TLS credentials. // In addition, GetMountParameters returns a cluster.tls-config command line option pointing // to the cluster TLS config file in the volume mount. // All TLS credentials related to cluster TLS configuration will be prefixed with "cluster-tls-server-config-" // or "cluster-tls-client-config-" respectively, for server and client credentials. // The server and client TLS credentials are mounted in different paths: ~/{mountingDir}/server-tls/ // and ~/{mountingDir}/client-tls/ respectively. func (c Config) GetMountParameters() (*monitoringv1.Argument, []v1.Volume, []v1.VolumeMount, error) { destinationPath := path.Join(c.mountingDir, ConfigFileKey) var volumes []v1.Volume var mounts []v1.VolumeMount var arg *monitoringv1.Argument // Only return an argument if the cluster TLS config and it's server component are defined. if c.clusterTLSConfig != nil { arg = c.makeArg(destinationPath) } cfgVolume := c.makeVolume() volumes = append(volumes, cfgVolume) cfgMount := c.makeVolumeMount(destinationPath) mounts = append(mounts, cfgMount) if c.serverTLSReferences != nil { servertlsVolumes, servertlsMounts, err := c.serverTLSReferences.GetMountParameters(serverVolumePrefix) if err != nil { return &monitoringv1.Argument{}, nil, nil, err } volumes = append(volumes, servertlsVolumes...) mounts = append(mounts, servertlsMounts...) } if c.clientTLSReferences != nil { clienttlsVolumes, clienttlsMounts, err := c.clientTLSReferences.GetMountParameters(clientVolumePrefix) if err != nil { return &monitoringv1.Argument{}, nil, nil, err } volumes = append(volumes, clienttlsVolumes...) mounts = append(mounts, clienttlsMounts...) } return arg, volumes, mounts, nil } // CreateOrUpdateConfigSecret create or update a Kubernetes secret with the data for the cluster TLS config file. // The format of the cluster TLS config file is available in the official prometheus documentation: // https://github.com/prometheus/alertmanager/blob/main/docs/https.md#gossip-traffic/ func (c Config) ClusterTLSConfiguration() ([]byte, error) { if c.clusterTLSConfig == nil { return []byte{}, nil } data, err := c.generateConfigFileContents() if err != nil { return nil, err } return data, nil } // generateConfigFileContents() generates the contents of cluster-tls-config.yaml // from the Config in the form of an array of bytes. func (c Config) generateConfigFileContents() ([]byte, error) { cfg := yaml.MapSlice{} cfg = c.addServerTLSConfigToYaml(cfg) cfg = c.addClientTLSConfigToYaml(cfg) return yaml.Marshal(cfg) } // makeArg() returns an argument with the name "cluster.tls-config" with the filePath // as its value. func (c Config) makeArg(filePath string) *monitoringv1.Argument { return &monitoringv1.Argument{Name: cmdflag, Value: filePath} } // makeVolume() creates a Volume with volumeName = "cluster-tls-config" which stores // the secret which contains the cluster TLS config. func (c Config) makeVolume() v1.Volume { return v1.Volume{ Name: volumeName, VolumeSource: v1.VolumeSource{ Secret: &v1.SecretVolumeSource{ SecretName: c.secretName, }, }, } } // makeVolumeMount() creates a VolumeMount, mounting the cluster_tls_config.yaml SubPath // to the given filePath. func (c Config) makeVolumeMount(filePath string) v1.VolumeMount { return v1.VolumeMount{ Name: volumeName, SubPath: ConfigFileKey, ReadOnly: true, MountPath: filePath, } } func (c Config) GetSecretName() string { return c.secretName } func (c Config) addServerTLSConfigToYaml(cfg yaml.MapSlice) yaml.MapSlice { tls := c.clusterTLSConfig.ServerTLS mtlsServerConfig := yaml.MapSlice{} tlsRefs := c.serverTLSReferences switch { case ptr.Deref(tls.KeyFile, "") != "": mtlsServerConfig = append(mtlsServerConfig, yaml.MapItem{Key: "key_file", Value: *tls.KeyFile}) case tlsRefs.GetKeyMountPath() != "": mtlsServerConfig = append(mtlsServerConfig, yaml.MapItem{Key: "key_file", Value: filepath.Join(tlsRefs.GetKeyMountPath(), tlsRefs.GetKeyFilename())}) } switch { case ptr.Deref(tls.CertFile, "") != "": mtlsServerConfig = append(mtlsServerConfig, yaml.MapItem{Key: "cert_file", Value: *tls.CertFile}) case tlsRefs.GetCertMountPath() != "": mtlsServerConfig = append(mtlsServerConfig, yaml.MapItem{Key: "cert_file", Value: filepath.Join(tlsRefs.GetCertMountPath(), tlsRefs.GetCertFilename())}) } if ptr.Deref(tls.ClientAuthType, "") != "" { mtlsServerConfig = append(mtlsServerConfig, yaml.MapItem{ Key: "client_auth_type", Value: *tls.ClientAuthType, }) } switch { case ptr.Deref(tls.ClientCAFile, "") != "": mtlsServerConfig = append(mtlsServerConfig, yaml.MapItem{Key: "client_ca_file", Value: *tls.ClientCAFile}) case tlsRefs.GetCAMountPath() != "": mtlsServerConfig = append(mtlsServerConfig, yaml.MapItem{Key: "client_ca_file", Value: filepath.Join(tlsRefs.GetCAMountPath(), tlsRefs.GetCAFilename())}) } if ptr.Deref(tls.MinVersion, "") != "" { mtlsServerConfig = append(mtlsServerConfig, yaml.MapItem{ Key: "min_version", Value: *tls.MinVersion, }) } if ptr.Deref(tls.MaxVersion, "") != "" { mtlsServerConfig = append(mtlsServerConfig, yaml.MapItem{ Key: "max_version", Value: *tls.MaxVersion, }) } if len(tls.CipherSuites) != 0 { mtlsServerConfig = append(mtlsServerConfig, yaml.MapItem{ Key: "cipher_suites", Value: tls.CipherSuites, }) } if tls.PreferServerCipherSuites != nil { mtlsServerConfig = append(mtlsServerConfig, yaml.MapItem{ Key: "prefer_server_cipher_suites", Value: tls.PreferServerCipherSuites, }) } if len(tls.CurvePreferences) != 0 { mtlsServerConfig = append(mtlsServerConfig, yaml.MapItem{ Key: "curve_preferences", Value: tls.CurvePreferences, }) } return append(cfg, yaml.MapItem{Key: "tls_server_config", Value: mtlsServerConfig}) } func (c Config) addClientTLSConfigToYaml(cfg yaml.MapSlice) yaml.MapSlice { tls := c.clusterTLSConfig.ClientTLS mtlsClientConfig := yaml.MapSlice{} tlsRefs := c.clientTLSReferences if keyPath := tlsRefs.GetKeyMountPath(); keyPath != "" { mtlsClientConfig = append(mtlsClientConfig, yaml.MapItem{Key: "key_file", Value: fmt.Sprintf("%s/%s", keyPath, tlsRefs.GetKeyFilename())}) } if certPath := tlsRefs.GetCertMountPath(); certPath != "" { mtlsClientConfig = append(mtlsClientConfig, yaml.MapItem{Key: "cert_file", Value: fmt.Sprintf("%s/%s", certPath, tlsRefs.GetCertFilename())}) } if caPath := tlsRefs.GetCAMountPath(); caPath != "" { mtlsClientConfig = append(mtlsClientConfig, yaml.MapItem{Key: "ca_file", Value: fmt.Sprintf("%s/%s", caPath, tlsRefs.GetCAFilename())}) } if serverName := tls.ServerName; serverName != nil { mtlsClientConfig = append(mtlsClientConfig, yaml.MapItem{Key: "server_name", Value: serverName}) } if tls.InsecureSkipVerify != nil { mtlsClientConfig = append(mtlsClientConfig, yaml.MapItem{ Key: "insecure_skip_verify", Value: tls.InsecureSkipVerify, }) } return append(cfg, yaml.MapItem{Key: "tls_client_config", Value: mtlsClientConfig}) }