mirror of
https://github.com/prometheus-operator/prometheus-operator.git
synced 2025-04-21 03:38:43 +00:00
Merge pull request #4025 from fpetkovski/web-config-support
pkg/prometheus: add support for web TLS configuration
This commit is contained in:
commit
fa3edac84f
16 changed files with 1802 additions and 18 deletions
Documentation
bundle.yamlexample/prometheus-operator-crd
go.sumjsonnet/prometheus-operator
pkg
apis/monitoring/v1
prometheus
webconfig
test
|
@ -70,6 +70,7 @@ This Document documents the types introduced by the Prometheus Operator to be co
|
|||
* [TLSConfig](#tlsconfig)
|
||||
* [ThanosSpec](#thanosspec)
|
||||
* [WebSpec](#webspec)
|
||||
* [WebTLSConfig](#webtlsconfig)
|
||||
* [ThanosRuler](#thanosruler)
|
||||
* [ThanosRulerList](#thanosrulerlist)
|
||||
* [ThanosRulerSpec](#thanosrulerspec)
|
||||
|
@ -914,6 +915,25 @@ WebSpec defines the query command line flags when starting Prometheus.
|
|||
| Field | Description | Scheme | Required |
|
||||
| ----- | ----------- | ------ | -------- |
|
||||
| pageTitle | The prometheus web page title | *string | false |
|
||||
| tlsConfig | | *[WebTLSConfig](#webtlsconfig) | false |
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
## WebTLSConfig
|
||||
|
||||
WebTLSConfig defines the TLS parameters for HTTPS.
|
||||
|
||||
| Field | Description | Scheme | Required |
|
||||
| ----- | ----------- | ------ | -------- |
|
||||
| keySecret | Secret containing the TLS key for the server. | [v1.SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#secretkeyselector-v1-core) | true |
|
||||
| cert | Contains the TLS certificate for the server. | [SecretOrConfigMap](#secretorconfigmap) | true |
|
||||
| clientAuthType | Server policy for client authentication. Maps to ClientAuth Policies. For more detail on clientAuth options: https://golang.org/pkg/crypto/tls/#ClientAuthType | string | false |
|
||||
| client_ca | Contains the CA certificate for client certificate authentication to the server. | [SecretOrConfigMap](#secretorconfigmap) | false |
|
||||
| minVersion | Minimum TLS version that is acceptable. Defaults to TLS12. | string | false |
|
||||
| maxVersion | Maximum TLS version that is acceptable. Defaults to TLS13. | string | false |
|
||||
| cipherSuites | List of supported cipher suites for TLS versions up to TLS 1.2. If empty, Go default cipher suites are used. Available cipher suites are documented in the go documentation: https://golang.org/pkg/crypto/tls/#pkg-constants | []string | false |
|
||||
| preferServerCipherSuites | Controls whether the server selects the client's most preferred cipher suite, or the server's most preferred cipher suite. If true then the server's preference, as expressed in the order of elements in cipherSuites, is used. | *bool | false |
|
||||
| curvePreferences | Elliptic curves that will be used in an ECDHE handshake, in preference order. Available curves are documented in the go documentation: https://golang.org/pkg/crypto/tls/#CurveID | []string | false |
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
|
|
112
bundle.yaml
112
bundle.yaml
|
@ -10190,6 +10190,118 @@ spec:
|
|||
pageTitle:
|
||||
description: The prometheus web page title
|
||||
type: string
|
||||
tlsConfig:
|
||||
description: WebTLSConfig defines the TLS parameters for HTTPS.
|
||||
properties:
|
||||
cert:
|
||||
description: Contains the TLS certificate for the server.
|
||||
properties:
|
||||
configMap:
|
||||
description: ConfigMap containing data to use for the targets.
|
||||
properties:
|
||||
key:
|
||||
description: The key to select.
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
optional:
|
||||
description: Specify whether the ConfigMap or its key must be defined
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
secret:
|
||||
description: Secret containing data to use for the targets.
|
||||
properties:
|
||||
key:
|
||||
description: The key of the secret to select from. Must be a valid secret key.
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
optional:
|
||||
description: Specify whether the Secret or its key must be defined
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
type: object
|
||||
cipherSuites:
|
||||
description: 'List of supported cipher suites for TLS versions up to TLS 1.2. If empty, Go default cipher suites are used. Available cipher suites are documented in the go documentation: https://golang.org/pkg/crypto/tls/#pkg-constants'
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
client_ca:
|
||||
description: Contains the CA certificate for client certificate authentication to the server.
|
||||
properties:
|
||||
configMap:
|
||||
description: ConfigMap containing data to use for the targets.
|
||||
properties:
|
||||
key:
|
||||
description: The key to select.
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
optional:
|
||||
description: Specify whether the ConfigMap or its key must be defined
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
secret:
|
||||
description: Secret containing data to use for the targets.
|
||||
properties:
|
||||
key:
|
||||
description: The key of the secret to select from. Must be a valid secret key.
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
optional:
|
||||
description: Specify whether the Secret or its key must be defined
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
type: object
|
||||
clientAuthType:
|
||||
description: 'Server policy for client authentication. Maps to ClientAuth Policies. For more detail on clientAuth options: https://golang.org/pkg/crypto/tls/#ClientAuthType'
|
||||
type: string
|
||||
curvePreferences:
|
||||
description: 'Elliptic curves that will be used in an ECDHE handshake, in preference order. Available curves are documented in the go documentation: https://golang.org/pkg/crypto/tls/#CurveID'
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
keySecret:
|
||||
description: Secret containing the TLS key for the server.
|
||||
properties:
|
||||
key:
|
||||
description: The key of the secret to select from. Must be a valid secret key.
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
optional:
|
||||
description: Specify whether the Secret or its key must be defined
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
maxVersion:
|
||||
description: Maximum TLS version that is acceptable. Defaults to TLS13.
|
||||
type: string
|
||||
minVersion:
|
||||
description: Minimum TLS version that is acceptable. Defaults to TLS12.
|
||||
type: string
|
||||
preferServerCipherSuites:
|
||||
description: Controls whether the server selects the client's most preferred cipher suite, or the server's most preferred cipher suite. If true then the server's preference, as expressed in the order of elements in cipherSuites, is used.
|
||||
type: boolean
|
||||
required:
|
||||
- cert
|
||||
- keySecret
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
status:
|
||||
|
|
|
@ -4401,6 +4401,118 @@ spec:
|
|||
pageTitle:
|
||||
description: The prometheus web page title
|
||||
type: string
|
||||
tlsConfig:
|
||||
description: WebTLSConfig defines the TLS parameters for HTTPS.
|
||||
properties:
|
||||
cert:
|
||||
description: Contains the TLS certificate for the server.
|
||||
properties:
|
||||
configMap:
|
||||
description: ConfigMap containing data to use for the targets.
|
||||
properties:
|
||||
key:
|
||||
description: The key to select.
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
optional:
|
||||
description: Specify whether the ConfigMap or its key must be defined
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
secret:
|
||||
description: Secret containing data to use for the targets.
|
||||
properties:
|
||||
key:
|
||||
description: The key of the secret to select from. Must be a valid secret key.
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
optional:
|
||||
description: Specify whether the Secret or its key must be defined
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
type: object
|
||||
cipherSuites:
|
||||
description: 'List of supported cipher suites for TLS versions up to TLS 1.2. If empty, Go default cipher suites are used. Available cipher suites are documented in the go documentation: https://golang.org/pkg/crypto/tls/#pkg-constants'
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
client_ca:
|
||||
description: Contains the CA certificate for client certificate authentication to the server.
|
||||
properties:
|
||||
configMap:
|
||||
description: ConfigMap containing data to use for the targets.
|
||||
properties:
|
||||
key:
|
||||
description: The key to select.
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
optional:
|
||||
description: Specify whether the ConfigMap or its key must be defined
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
secret:
|
||||
description: Secret containing data to use for the targets.
|
||||
properties:
|
||||
key:
|
||||
description: The key of the secret to select from. Must be a valid secret key.
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
optional:
|
||||
description: Specify whether the Secret or its key must be defined
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
type: object
|
||||
clientAuthType:
|
||||
description: 'Server policy for client authentication. Maps to ClientAuth Policies. For more detail on clientAuth options: https://golang.org/pkg/crypto/tls/#ClientAuthType'
|
||||
type: string
|
||||
curvePreferences:
|
||||
description: 'Elliptic curves that will be used in an ECDHE handshake, in preference order. Available curves are documented in the go documentation: https://golang.org/pkg/crypto/tls/#CurveID'
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
keySecret:
|
||||
description: Secret containing the TLS key for the server.
|
||||
properties:
|
||||
key:
|
||||
description: The key of the secret to select from. Must be a valid secret key.
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
optional:
|
||||
description: Specify whether the Secret or its key must be defined
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
maxVersion:
|
||||
description: Maximum TLS version that is acceptable. Defaults to TLS13.
|
||||
type: string
|
||||
minVersion:
|
||||
description: Minimum TLS version that is acceptable. Defaults to TLS12.
|
||||
type: string
|
||||
preferServerCipherSuites:
|
||||
description: Controls whether the server selects the client's most preferred cipher suite, or the server's most preferred cipher suite. If true then the server's preference, as expressed in the order of elements in cipherSuites, is used.
|
||||
type: boolean
|
||||
required:
|
||||
- cert
|
||||
- keySecret
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
status:
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -15,6 +15,7 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
@ -509,7 +510,77 @@ type QuerySpec struct {
|
|||
// +k8s:openapi-gen=true
|
||||
type WebSpec struct {
|
||||
// The prometheus web page title
|
||||
PageTitle *string `json:"pageTitle,omitempty"`
|
||||
PageTitle *string `json:"pageTitle,omitempty"`
|
||||
TLSConfig *WebTLSConfig `json:"tlsConfig,omitempty"`
|
||||
}
|
||||
|
||||
// WebTLSConfig defines the TLS parameters for HTTPS.
|
||||
// +k8s:openapi-gen=true
|
||||
type WebTLSConfig struct {
|
||||
// Secret containing the TLS key for the server.
|
||||
KeySecret v1.SecretKeySelector `json:"keySecret"`
|
||||
// Contains the TLS certificate for the server.
|
||||
Cert SecretOrConfigMap `json:"cert"`
|
||||
// Server policy for client authentication. Maps to ClientAuth Policies.
|
||||
// For more detail on clientAuth options:
|
||||
// https://golang.org/pkg/crypto/tls/#ClientAuthType
|
||||
ClientAuthType string `json:"clientAuthType,omitempty"`
|
||||
// Contains the CA certificate for client certificate authentication to the server.
|
||||
ClientCA SecretOrConfigMap `json:"client_ca,omitempty"`
|
||||
// Minimum TLS version that is acceptable. Defaults to TLS12.
|
||||
MinVersion string `json:"minVersion,omitempty"`
|
||||
// Maximum TLS version that is acceptable. Defaults to TLS13.
|
||||
MaxVersion string `json:"maxVersion,omitempty"`
|
||||
// List of supported cipher suites for TLS versions up to TLS 1.2. If empty,
|
||||
// Go default cipher suites are used. Available cipher suites are documented
|
||||
// in the go documentation: https://golang.org/pkg/crypto/tls/#pkg-constants
|
||||
CipherSuites []string `json:"cipherSuites,omitempty"`
|
||||
// Controls whether the server selects the
|
||||
// client's most preferred cipher suite, or the server's most preferred
|
||||
// cipher suite. If true then the server's preference, as expressed in
|
||||
// the order of elements in cipherSuites, is used.
|
||||
PreferServerCipherSuites *bool `json:"preferServerCipherSuites,omitempty"`
|
||||
// Elliptic curves that will be used in an ECDHE handshake, in preference
|
||||
// order. Available curves are documented in the go documentation:
|
||||
// https://golang.org/pkg/crypto/tls/#CurveID
|
||||
CurvePreferences []string `json:"curvePreferences,omitempty"`
|
||||
}
|
||||
|
||||
// WebTLSConfigError is returned by WebTLSConfig.Validate() on
|
||||
// semantically invalid configurations.
|
||||
// +k8s:openapi-gen=false
|
||||
type WebTLSConfigError struct {
|
||||
err string
|
||||
}
|
||||
|
||||
func (e *WebTLSConfigError) Error() string {
|
||||
return e.err
|
||||
}
|
||||
|
||||
func (c *WebTLSConfig) Validate() error {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.ClientCA != (SecretOrConfigMap{}) {
|
||||
if err := c.ClientCA.Validate(); err != nil {
|
||||
msg := fmt.Sprintf("invalid web tls config: %s", err.Error())
|
||||
return &WebTLSConfigError{msg}
|
||||
}
|
||||
}
|
||||
|
||||
if c.Cert == (SecretOrConfigMap{}) {
|
||||
return &WebTLSConfigError{"invalid web tls config: cert must be defined"}
|
||||
} else if err := c.Cert.Validate(); err != nil {
|
||||
msg := fmt.Sprintf("invalid web tls config: %s", err.Error())
|
||||
return &WebTLSConfigError{msg}
|
||||
}
|
||||
|
||||
if c.KeySecret == (v1.SecretKeySelector{}) {
|
||||
return &WebTLSConfigError{"invalid web tls config: key must be defined"}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ThanosSpec defines parameters for a Prometheus server within a Thanos deployment.
|
||||
|
|
|
@ -1962,6 +1962,11 @@ func (in *WebSpec) DeepCopyInto(out *WebSpec) {
|
|||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.TLSConfig != nil {
|
||||
in, out := &in.TLSConfig, &out.TLSConfig
|
||||
*out = new(WebTLSConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebSpec.
|
||||
|
@ -1973,3 +1978,51 @@ func (in *WebSpec) DeepCopy() *WebSpec {
|
|||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WebTLSConfig) DeepCopyInto(out *WebTLSConfig) {
|
||||
*out = *in
|
||||
in.KeySecret.DeepCopyInto(&out.KeySecret)
|
||||
in.Cert.DeepCopyInto(&out.Cert)
|
||||
in.ClientCA.DeepCopyInto(&out.ClientCA)
|
||||
if in.CipherSuites != nil {
|
||||
in, out := &in.CipherSuites, &out.CipherSuites
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.PreferServerCipherSuites != nil {
|
||||
in, out := &in.PreferServerCipherSuites, &out.PreferServerCipherSuites
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.CurvePreferences != nil {
|
||||
in, out := &in.CurvePreferences, &out.CurvePreferences
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebTLSConfig.
|
||||
func (in *WebTLSConfig) DeepCopy() *WebTLSConfig {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WebTLSConfig)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WebTLSConfigError) DeepCopyInto(out *WebTLSConfigError) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebTLSConfigError.
|
||||
func (in *WebTLSConfigError) DeepCopy() *WebTLSConfigError {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WebTLSConfigError)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"compress/gzip"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/prometheus-operator/prometheus-operator/pkg/webconfig"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
@ -1237,6 +1238,10 @@ func (c *Operator) sync(ctx context.Context, key string) error {
|
|||
return errors.Wrap(err, "creating tls asset secret failed")
|
||||
}
|
||||
|
||||
if err := c.createOrUpdateWebConfigSecret(ctx, p); err != nil {
|
||||
return errors.Wrap(err, "synchronizing web config secret failed")
|
||||
}
|
||||
|
||||
// Create governing service if it doesn't exist.
|
||||
svcClient := c.kclient.CoreV1().Services(p.Namespace)
|
||||
if err := k8sutil.CreateOrUpdateService(ctx, svcClient, makeStatefulSetService(p, c.config)); err != nil {
|
||||
|
@ -1628,6 +1633,47 @@ func (c *Operator) createOrUpdateTLSAssetSecret(ctx context.Context, p *monitori
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Operator) createOrUpdateWebConfigSecret(ctx context.Context, p *monitoringv1.Prometheus) error {
|
||||
boolTrue := true
|
||||
client := c.kclient.CoreV1().Secrets(p.Namespace)
|
||||
|
||||
var tlsConfig *monitoringv1.WebTLSConfig
|
||||
if p.Spec.Web != nil {
|
||||
tlsConfig = p.Spec.Web.TLSConfig
|
||||
}
|
||||
|
||||
webConfig, err := webconfig.New(
|
||||
webConfigDir,
|
||||
WebConfigSecretName(p.Name),
|
||||
tlsConfig,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ownerReference := metav1.OwnerReference{
|
||||
APIVersion: p.APIVersion,
|
||||
BlockOwnerDeletion: &boolTrue,
|
||||
Controller: &boolTrue,
|
||||
Kind: p.Kind,
|
||||
Name: p.Name,
|
||||
UID: p.UID,
|
||||
}
|
||||
|
||||
secretLabels := c.config.Labels.Merge(managedByOperatorLabels)
|
||||
secret, err := webConfig.MakeConfigFileSecret(secretLabels, ownerReference)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = k8sutil.CreateOrUpdateSecret(ctx, client, secret)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create web config for Prometheus")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Operator) selectServiceMonitors(ctx context.Context, p *monitoringv1.Prometheus, store *assets.Store) (map[string]*monitoringv1.ServiceMonitor, error) {
|
||||
namespaces := []string{}
|
||||
// Selectors (<namespace>/<name>) might overlap. Deduplicate them along the keyFunc.
|
||||
|
|
|
@ -16,6 +16,7 @@ package prometheus
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/prometheus-operator/prometheus-operator/pkg/webconfig"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
@ -40,6 +41,7 @@ const (
|
|||
storageDir = "/prometheus"
|
||||
confDir = "/etc/prometheus/config"
|
||||
confOutDir = "/etc/prometheus/config_out"
|
||||
webConfigDir = "/etc/prometheus/web_config"
|
||||
tlsAssetsDir = "/etc/prometheus/certs"
|
||||
rulesDir = "/etc/prometheus/rules"
|
||||
secretsDir = "/etc/prometheus/secrets/"
|
||||
|
@ -526,6 +528,29 @@ func makeStatefulSetSpec(p monitoringv1.Prometheus, c *operator.Config, shard in
|
|||
})
|
||||
}
|
||||
|
||||
// Mount web config and web TLS credentials as volumes.
|
||||
// We always mount the web config file for versions greater than 2.24.0.
|
||||
// With this we avoid redeploying prometheus when reconfiguring between
|
||||
// HTTP and HTTPS and vice-versa.
|
||||
if version.GTE(semver.MustParse("2.24.0")) {
|
||||
var webTLSConfig *monitoringv1.WebTLSConfig
|
||||
if p.Spec.Web != nil {
|
||||
webTLSConfig = p.Spec.Web.TLSConfig
|
||||
}
|
||||
|
||||
webConfig, err := webconfig.New(webConfigDir, WebConfigSecretName(p.Name), webTLSConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
confArg, configVol, configMount := webConfig.GetMountParameters()
|
||||
promArgs = append(promArgs, confArg)
|
||||
volumes = append(volumes, configVol...)
|
||||
promVolumeMounts = append(promVolumeMounts, configMount...)
|
||||
}
|
||||
|
||||
// Mount related secrets
|
||||
|
||||
for _, s := range p.Spec.Secrets {
|
||||
volumes = append(volumes, v1.Volume{
|
||||
Name: k8sutil.SanitizeVolumeName("secret-" + s),
|
||||
|
@ -574,12 +599,14 @@ func makeStatefulSetSpec(p monitoringv1.Prometheus, c *operator.Config, shard in
|
|||
fmt.Sprintf(localProbe, localReadyPath, localReadyPath),
|
||||
},
|
||||
}
|
||||
|
||||
} else {
|
||||
readinessProbeHandler.HTTPGet = &v1.HTTPGetAction{
|
||||
Path: readyPath,
|
||||
Port: intstr.FromString(p.Spec.PortName),
|
||||
}
|
||||
if p.Spec.Web != nil && p.Spec.Web.TLSConfig != nil && version.GTE(semver.MustParse("2.24.0")) {
|
||||
readinessProbeHandler.HTTPGet.Scheme = v1.URISchemeHTTPS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -770,6 +797,15 @@ func makeStatefulSetSpec(p monitoringv1.Prometheus, c *operator.Config, shard in
|
|||
MountPath: confOutDir,
|
||||
},
|
||||
}
|
||||
|
||||
if version.GTE(semver.MustParse("2.24.0")) {
|
||||
configReloaderVolumeMounts = append(configReloaderVolumeMounts, v1.VolumeMount{
|
||||
Name: "web-config",
|
||||
MountPath: webConfigDir,
|
||||
})
|
||||
configReloaderArgs = append(configReloaderArgs, fmt.Sprintf("--watched-dir=%s", webConfigDir))
|
||||
}
|
||||
|
||||
if len(ruleConfigMapNames) != 0 {
|
||||
for _, name := range ruleConfigMapNames {
|
||||
mountPath := rulesDir + "/" + name
|
||||
|
@ -855,6 +891,10 @@ func tlsAssetsSecretName(name string) string {
|
|||
return fmt.Sprintf("%s-tls-assets", prefixedName(name))
|
||||
}
|
||||
|
||||
func WebConfigSecretName(name string) string {
|
||||
return fmt.Sprintf("%s-web-config", prefixedName(name))
|
||||
}
|
||||
|
||||
func volumeName(name string) string {
|
||||
return fmt.Sprintf("%s-db", prefixedName(name))
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ package prometheus
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -252,6 +253,12 @@ func TestStatefulSetVolumeInitial(t *testing.T) {
|
|||
MountPath: "/etc/prometheus/rules/rules-configmap-one",
|
||||
SubPath: "",
|
||||
},
|
||||
{
|
||||
Name: "web-config",
|
||||
ReadOnly: true,
|
||||
MountPath: "/etc/prometheus/web_config/web-config.yaml",
|
||||
SubPath: "web-config.yaml",
|
||||
},
|
||||
{
|
||||
Name: "secret-test-secret1",
|
||||
ReadOnly: true,
|
||||
|
@ -294,6 +301,14 @@ func TestStatefulSetVolumeInitial(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "web-config",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
Secret: &v1.SecretVolumeSource{
|
||||
SecretName: "prometheus-volume-init-test-web-config",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "secret-test-secret1",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
|
@ -336,7 +351,6 @@ func TestStatefulSetVolumeInitial(t *testing.T) {
|
|||
fmt.Println(pretty.Compare(expected.Spec.Template.Spec.Containers[0].VolumeMounts, sset.Spec.Template.Spec.Containers[0].VolumeMounts))
|
||||
t.Fatal("expected volume mounts to match")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAdditionalConfigMap(t *testing.T) {
|
||||
|
@ -415,6 +429,49 @@ func TestListenLocal(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestListenTLS(t *testing.T) {
|
||||
sset, err := makeStatefulSet("test", monitoringv1.Prometheus{
|
||||
Spec: monitoringv1.PrometheusSpec{
|
||||
Web: &monitoringv1.WebSpec{
|
||||
TLSConfig: &monitoringv1.WebTLSConfig{
|
||||
KeySecret: v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "some-secret",
|
||||
},
|
||||
},
|
||||
Cert: monitoringv1.SecretOrConfigMap{
|
||||
ConfigMap: &v1.ConfigMapKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "some-configmap",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, defaultTestConfig, nil, "", 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error while making StatefulSet: %v", err)
|
||||
}
|
||||
|
||||
actualReadinessProbe := sset.Spec.Template.Spec.Containers[0].ReadinessProbe
|
||||
expectedReadinessProbe := &v1.Probe{
|
||||
Handler: v1.Handler{
|
||||
HTTPGet: &v1.HTTPGetAction{
|
||||
Path: "/-/ready",
|
||||
Port: intstr.FromString("web"),
|
||||
Scheme: "HTTPS",
|
||||
},
|
||||
},
|
||||
TimeoutSeconds: 3,
|
||||
PeriodSeconds: 5,
|
||||
FailureThreshold: 120,
|
||||
}
|
||||
if !reflect.DeepEqual(actualReadinessProbe, expectedReadinessProbe) {
|
||||
t.Fatalf("Readiness probe doesn't match expected. \n\nExpected: %+v\n\nGot: %+v", expectedReadinessProbe, actualReadinessProbe)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTagAndShaAndVersion(t *testing.T) {
|
||||
{
|
||||
sset, err := makeStatefulSet("test", monitoringv1.Prometheus{
|
||||
|
|
202
pkg/webconfig/config.go
Normal file
202
pkg/webconfig/config.go
Normal file
|
@ -0,0 +1,202 @@
|
|||
// Copyright 2021 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 webconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
|
||||
"gopkg.in/yaml.v2"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"path"
|
||||
)
|
||||
|
||||
var (
|
||||
volumeName = "web-config"
|
||||
configFile = "web-config.yaml"
|
||||
)
|
||||
|
||||
// Config is the web configuration for a prometheus 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 {
|
||||
tlsConfig *monitoringv1.WebTLSConfig
|
||||
tlsCredentials *tlsCredentials
|
||||
mountingDir string
|
||||
secretName string
|
||||
}
|
||||
|
||||
// New creates a new Config.
|
||||
func New(mountingDir string, secretName string, tlsConfig *monitoringv1.WebTLSConfig) (*Config, error) {
|
||||
if err := tlsConfig.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tlsCreds *tlsCredentials
|
||||
if tlsConfig != nil {
|
||||
tlsCreds = newTLSCredentials(mountingDir, tlsConfig.KeySecret, tlsConfig.Cert, tlsConfig.ClientCA)
|
||||
}
|
||||
|
||||
return &Config{
|
||||
tlsConfig: tlsConfig,
|
||||
tlsCredentials: tlsCreds,
|
||||
mountingDir: mountingDir,
|
||||
secretName: secretName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetMountParameters returns volumes and volume mounts referencing the config file
|
||||
// and the associated TLS credentials.
|
||||
// In addition, GetMountParameters returns a web.config.file command line option pointing
|
||||
// to the file in the volume mount.
|
||||
func (c Config) GetMountParameters() (string, []v1.Volume, []v1.VolumeMount) {
|
||||
destinationPath := path.Join(c.mountingDir, configFile)
|
||||
|
||||
var volumes []v1.Volume
|
||||
var mounts []v1.VolumeMount
|
||||
|
||||
arg := c.makeArg(destinationPath)
|
||||
cfgVolume := c.makeVolume()
|
||||
volumes = append(volumes, cfgVolume)
|
||||
|
||||
cfgMount := c.makeVolumeMount(destinationPath)
|
||||
mounts = append(mounts, cfgMount)
|
||||
|
||||
if c.tlsCredentials != nil {
|
||||
tlsVolumes, tlsMounts := c.tlsCredentials.getMountParameters()
|
||||
volumes = append(volumes, tlsVolumes...)
|
||||
mounts = append(mounts, tlsMounts...)
|
||||
}
|
||||
|
||||
return arg, volumes, mounts
|
||||
}
|
||||
|
||||
// MakeConfigFileSecret returns a Kubernetes secret with the data for the web config file.
|
||||
// The format of the web config file is available in the official prometheus documentation:
|
||||
// https://prometheus.io/docs/prometheus/latest/configuration/https/#https-and-authentication
|
||||
func (c *Config) MakeConfigFileSecret(labels map[string]string, ownerReference metav1.OwnerReference) (*v1.Secret, error) {
|
||||
data, err := c.generateConfigFileContents()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: c.secretName,
|
||||
Labels: labels,
|
||||
OwnerReferences: []metav1.OwnerReference{ownerReference},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
configFile: data,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c Config) generateConfigFileContents() ([]byte, error) {
|
||||
tls := c.tlsConfig
|
||||
if tls == nil {
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
tlsServerConfig := yaml.MapSlice{}
|
||||
if certPath := c.tlsCredentials.getCertMountPath(); certPath != "" {
|
||||
tlsServerConfig = append(tlsServerConfig, yaml.MapItem{Key: "cert_file", Value: certPath})
|
||||
}
|
||||
|
||||
if keyPath := c.tlsCredentials.getKeyMountPath(); keyPath != "" {
|
||||
tlsServerConfig = append(tlsServerConfig, yaml.MapItem{Key: "key_file", Value: keyPath})
|
||||
}
|
||||
|
||||
if tls.ClientAuthType != "" {
|
||||
tlsServerConfig = append(tlsServerConfig, yaml.MapItem{
|
||||
Key: "client_auth_type",
|
||||
Value: tls.ClientAuthType,
|
||||
})
|
||||
}
|
||||
|
||||
if caPath := c.tlsCredentials.getCAMountPath(); caPath != "" {
|
||||
tlsServerConfig = append(tlsServerConfig, yaml.MapItem{Key: "client_ca_file", Value: caPath})
|
||||
}
|
||||
|
||||
if tls.MinVersion != "" {
|
||||
tlsServerConfig = append(tlsServerConfig, yaml.MapItem{
|
||||
Key: "min_version",
|
||||
Value: tls.MinVersion,
|
||||
})
|
||||
}
|
||||
|
||||
if tls.MaxVersion != "" {
|
||||
tlsServerConfig = append(tlsServerConfig, yaml.MapItem{
|
||||
Key: "max_version",
|
||||
Value: tls.MaxVersion,
|
||||
})
|
||||
}
|
||||
|
||||
if len(tls.CipherSuites) != 0 {
|
||||
tlsServerConfig = append(tlsServerConfig, yaml.MapItem{
|
||||
Key: "cipher_suites",
|
||||
Value: tls.CipherSuites,
|
||||
})
|
||||
}
|
||||
|
||||
if tls.PreferServerCipherSuites != nil {
|
||||
tlsServerConfig = append(tlsServerConfig, yaml.MapItem{
|
||||
Key: "prefer_server_cipher_suites",
|
||||
Value: tls.PreferServerCipherSuites,
|
||||
})
|
||||
}
|
||||
|
||||
if len(tls.CurvePreferences) != 0 {
|
||||
tlsServerConfig = append(tlsServerConfig, yaml.MapItem{
|
||||
Key: "curve_preferences",
|
||||
Value: tls.CurvePreferences,
|
||||
})
|
||||
}
|
||||
|
||||
cfg := yaml.MapSlice{
|
||||
{
|
||||
Key: "tls_server_config",
|
||||
Value: tlsServerConfig,
|
||||
},
|
||||
}
|
||||
|
||||
return yaml.Marshal(cfg)
|
||||
}
|
||||
|
||||
func (c Config) makeArg(filePath string) string {
|
||||
return fmt.Sprintf("--web.config.file=%s", filePath)
|
||||
}
|
||||
|
||||
func (c Config) makeVolume() v1.Volume {
|
||||
return v1.Volume{
|
||||
Name: volumeName,
|
||||
VolumeSource: v1.VolumeSource{
|
||||
Secret: &v1.SecretVolumeSource{
|
||||
SecretName: c.secretName,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c Config) makeVolumeMount(filePath string) v1.VolumeMount {
|
||||
return v1.VolumeMount{
|
||||
Name: volumeName,
|
||||
SubPath: configFile,
|
||||
ReadOnly: true,
|
||||
MountPath: filePath,
|
||||
}
|
||||
}
|
325
pkg/webconfig/config_test.go
Normal file
325
pkg/webconfig/config_test.go
Normal file
|
@ -0,0 +1,325 @@
|
|||
// Copyright 2021 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 webconfig_test
|
||||
|
||||
import (
|
||||
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
|
||||
"github.com/prometheus-operator/prometheus-operator/pkg/webconfig"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var falseVal = false
|
||||
|
||||
func TestMakeSecret(t *testing.T) {
|
||||
tc := []struct {
|
||||
name string
|
||||
webTLSConfig *monitoringv1.WebTLSConfig
|
||||
expectedData string
|
||||
}{
|
||||
{
|
||||
name: "tls config not defined",
|
||||
webTLSConfig: nil,
|
||||
expectedData: "",
|
||||
},
|
||||
{
|
||||
name: "minimal TLS config with certificate from secret",
|
||||
webTLSConfig: &monitoringv1.WebTLSConfig{
|
||||
Cert: monitoringv1.SecretOrConfigMap{
|
||||
Secret: &v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "test-secret",
|
||||
},
|
||||
Key: "tls.crt",
|
||||
},
|
||||
},
|
||||
KeySecret: v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "test-secret",
|
||||
},
|
||||
Key: "tls.key",
|
||||
},
|
||||
},
|
||||
expectedData: `tls_server_config:
|
||||
cert_file: /web_certs_path_prefix/secret_test-secret_tls.crt
|
||||
key_file: /web_certs_path_prefix/secret_test-secret_tls.key
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "minimal TLS config with certificate from configmap",
|
||||
webTLSConfig: &monitoringv1.WebTLSConfig{
|
||||
Cert: monitoringv1.SecretOrConfigMap{
|
||||
ConfigMap: &v1.ConfigMapKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "test-configmap",
|
||||
},
|
||||
Key: "tls.crt",
|
||||
},
|
||||
},
|
||||
KeySecret: v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "test-secret",
|
||||
},
|
||||
Key: "tls.key",
|
||||
},
|
||||
},
|
||||
expectedData: `tls_server_config:
|
||||
cert_file: /web_certs_path_prefix/configmap_test-configmap_tls.crt
|
||||
key_file: /web_certs_path_prefix/secret_test-secret_tls.key
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "minimal TLS config with client CA from configmap",
|
||||
webTLSConfig: &monitoringv1.WebTLSConfig{
|
||||
Cert: monitoringv1.SecretOrConfigMap{
|
||||
ConfigMap: &v1.ConfigMapKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "test-configmap",
|
||||
},
|
||||
Key: "tls.crt",
|
||||
},
|
||||
},
|
||||
KeySecret: v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "test-secret",
|
||||
},
|
||||
Key: "tls.key",
|
||||
},
|
||||
ClientCA: monitoringv1.SecretOrConfigMap{
|
||||
ConfigMap: &v1.ConfigMapKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "test-configmap",
|
||||
},
|
||||
Key: "tls.client_ca",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedData: `tls_server_config:
|
||||
cert_file: /web_certs_path_prefix/configmap_test-configmap_tls.crt
|
||||
key_file: /web_certs_path_prefix/secret_test-secret_tls.key
|
||||
client_ca_file: /web_certs_path_prefix/configmap_test-configmap_tls.client_ca
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "TLS config with all parameters from secrets",
|
||||
webTLSConfig: &monitoringv1.WebTLSConfig{
|
||||
ClientCA: monitoringv1.SecretOrConfigMap{
|
||||
Secret: &v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "test-secret",
|
||||
},
|
||||
Key: "tls.ca",
|
||||
},
|
||||
},
|
||||
Cert: monitoringv1.SecretOrConfigMap{
|
||||
Secret: &v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "test-secret",
|
||||
},
|
||||
Key: "tls.crt",
|
||||
},
|
||||
},
|
||||
KeySecret: v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "test-secret",
|
||||
},
|
||||
Key: "tls.keySecret",
|
||||
},
|
||||
ClientAuthType: "RequireAnyClientCert",
|
||||
MinVersion: "TLS11",
|
||||
MaxVersion: "TLS13",
|
||||
CipherSuites: []string{"cipher-1", "cipher-2"},
|
||||
PreferServerCipherSuites: &falseVal,
|
||||
CurvePreferences: []string{"curve-1", "curve-2"},
|
||||
},
|
||||
expectedData: `tls_server_config:
|
||||
cert_file: /web_certs_path_prefix/secret_test-secret_tls.crt
|
||||
key_file: /web_certs_path_prefix/secret_test-secret_tls.keySecret
|
||||
client_auth_type: RequireAnyClientCert
|
||||
client_ca_file: /web_certs_path_prefix/secret_test-secret_tls.ca
|
||||
min_version: TLS11
|
||||
max_version: TLS13
|
||||
cipher_suites:
|
||||
- cipher-1
|
||||
- cipher-2
|
||||
prefer_server_cipher_suites: false
|
||||
curve_preferences:
|
||||
- curve-1
|
||||
- curve-2
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tc {
|
||||
config, err := webconfig.New("/web_certs_path_prefix", "test-secret", tt.webTLSConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
secret, err := config.MakeConfigFileSecret(nil, metav1.OwnerReference{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if tt.expectedData != string(secret.Data["web-config.yaml"]) {
|
||||
t.Fatalf("%s failed.\n\nGot %s\nwant %s\n", tt.name, secret.Data["web-config.yaml"], tt.expectedData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMountParameters(t *testing.T) {
|
||||
ts := []struct {
|
||||
tlsConfig *monitoringv1.WebTLSConfig
|
||||
expectedVolumes []v1.Volume
|
||||
expectedMounts []v1.VolumeMount
|
||||
}{
|
||||
{
|
||||
tlsConfig: nil,
|
||||
expectedVolumes: []v1.Volume{
|
||||
{
|
||||
Name: "web-config",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
Secret: &v1.SecretVolumeSource{
|
||||
SecretName: "web-config",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedMounts: []v1.VolumeMount{
|
||||
{
|
||||
Name: "web-config",
|
||||
ReadOnly: true,
|
||||
MountPath: "/etc/prometheus/web_config/web-config.yaml",
|
||||
SubPath: "web-config.yaml",
|
||||
MountPropagation: nil,
|
||||
SubPathExpr: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
tlsConfig: &monitoringv1.WebTLSConfig{
|
||||
KeySecret: v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "some-secret",
|
||||
},
|
||||
Key: "tls.key",
|
||||
},
|
||||
Cert: monitoringv1.SecretOrConfigMap{
|
||||
Secret: &v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "some-secret",
|
||||
},
|
||||
Key: "tls.crt",
|
||||
},
|
||||
},
|
||||
ClientCA: monitoringv1.SecretOrConfigMap{
|
||||
Secret: &v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "some-secret",
|
||||
},
|
||||
Key: "tls.client_ca",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVolumes: []v1.Volume{
|
||||
{
|
||||
Name: "web-config",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
Secret: &v1.SecretVolumeSource{
|
||||
SecretName: "web-config",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "web-config-tls-secret-key-some-secret",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
Secret: &v1.SecretVolumeSource{
|
||||
SecretName: "some-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "web-config-tls-secret-cert-some-secret",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
Secret: &v1.SecretVolumeSource{
|
||||
SecretName: "some-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "web-config-tls-secret-client-ca-some-secret",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
Secret: &v1.SecretVolumeSource{
|
||||
SecretName: "some-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedMounts: []v1.VolumeMount{
|
||||
{
|
||||
Name: "web-config",
|
||||
ReadOnly: true,
|
||||
MountPath: "/etc/prometheus/web_config/web-config.yaml",
|
||||
SubPath: "web-config.yaml",
|
||||
MountPropagation: nil,
|
||||
SubPathExpr: "",
|
||||
},
|
||||
{
|
||||
Name: "web-config-tls-secret-key-some-secret",
|
||||
ReadOnly: true,
|
||||
MountPath: "/etc/prometheus/web_config/secret_some-secret_tls.key",
|
||||
SubPath: "tls.key",
|
||||
MountPropagation: nil,
|
||||
SubPathExpr: "",
|
||||
},
|
||||
{
|
||||
Name: "web-config-tls-secret-cert-some-secret",
|
||||
ReadOnly: true,
|
||||
MountPath: "/etc/prometheus/web_config/secret_some-secret_tls.crt",
|
||||
SubPath: "tls.crt",
|
||||
MountPropagation: nil,
|
||||
SubPathExpr: "",
|
||||
},
|
||||
{
|
||||
Name: "web-config-tls-secret-client-ca-some-secret",
|
||||
ReadOnly: true,
|
||||
MountPath: "/etc/prometheus/web_config/secret_some-secret_tls.client_ca",
|
||||
SubPath: "tls.client_ca",
|
||||
MountPropagation: nil,
|
||||
SubPathExpr: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range ts {
|
||||
tlsAssets, err := webconfig.New("/etc/prometheus/web_config", "web-config", tt.tlsConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, volumes, mounts := tlsAssets.GetMountParameters()
|
||||
|
||||
if !reflect.DeepEqual(volumes, tt.expectedVolumes) {
|
||||
t.Errorf("invalid volumes,\ngot %v,\nwant %v", volumes, tt.expectedVolumes)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(mounts, tt.expectedMounts) {
|
||||
t.Errorf("invalid mounts,\ngot %v,\nwant %v", mounts, tt.expectedMounts)
|
||||
}
|
||||
}
|
||||
}
|
172
pkg/webconfig/tls_credentials.go
Normal file
172
pkg/webconfig/tls_credentials.go
Normal file
|
@ -0,0 +1,172 @@
|
|||
// Copyright 2021 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 webconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"path"
|
||||
)
|
||||
|
||||
var (
|
||||
volumePrefix = "web-config-tls-"
|
||||
)
|
||||
|
||||
// tlsCredentials are the credentials used for web TLS.
|
||||
type tlsCredentials struct {
|
||||
// mountPath is the directory where TLS credentials are intended to be mounted
|
||||
mountPath string
|
||||
|
||||
// keySecret is the kubernetes secret containing the TLS key
|
||||
keySecret corev1.SecretKeySelector
|
||||
// cert is the kubernetes secret or configmap containing the TLS certificate
|
||||
cert monitoringv1.SecretOrConfigMap
|
||||
// clientCA is the kubernetes secret or configmap containing the client CA certificate
|
||||
clientCA monitoringv1.SecretOrConfigMap
|
||||
}
|
||||
|
||||
// newTLSCredentials creates new tlsCredentials from secrets of configmaps.
|
||||
func newTLSCredentials(
|
||||
mountPath string,
|
||||
key corev1.SecretKeySelector,
|
||||
cert monitoringv1.SecretOrConfigMap,
|
||||
clientCA monitoringv1.SecretOrConfigMap,
|
||||
) *tlsCredentials {
|
||||
return &tlsCredentials{
|
||||
mountPath: mountPath,
|
||||
keySecret: key,
|
||||
cert: cert,
|
||||
clientCA: clientCA,
|
||||
}
|
||||
}
|
||||
|
||||
// getMountParameters creates volumes and volume mounts referencing the TLS credentials.
|
||||
func (a tlsCredentials) getMountParameters() ([]corev1.Volume, []corev1.VolumeMount) {
|
||||
var volumes []corev1.Volume
|
||||
var mounts []corev1.VolumeMount
|
||||
|
||||
prefix := volumePrefix + "secret-key-"
|
||||
volumes, mounts = a.mountParamsForSecret(volumes, mounts, &a.keySecret, prefix, a.getKeyMountPath())
|
||||
|
||||
if a.cert.Secret != nil {
|
||||
prefix := volumePrefix + "secret-cert-"
|
||||
volumes, mounts = a.mountParamsForSecret(volumes, mounts, a.cert.Secret, prefix, a.getCertMountPath())
|
||||
} else if a.cert.ConfigMap != nil {
|
||||
prefix := volumePrefix + "configmap-cert-"
|
||||
volumes, mounts = a.mountParamsForConfigmap(volumes, mounts, a.cert.ConfigMap, prefix, a.getCertMountPath())
|
||||
}
|
||||
|
||||
if a.clientCA.Secret != nil {
|
||||
prefix := volumePrefix + "secret-client-ca-"
|
||||
volumes, mounts = a.mountParamsForSecret(volumes, mounts, a.clientCA.Secret, prefix, a.getCAMountPath())
|
||||
} else if a.clientCA.ConfigMap != nil {
|
||||
prefix := volumePrefix + "configmap-client-ca-"
|
||||
volumes, mounts = a.mountParamsForConfigmap(volumes, mounts, a.clientCA.ConfigMap, prefix, a.getCAMountPath())
|
||||
}
|
||||
|
||||
return volumes, mounts
|
||||
}
|
||||
|
||||
func (a tlsCredentials) mountParamsForSecret(
|
||||
volumes []corev1.Volume,
|
||||
mounts []corev1.VolumeMount,
|
||||
secret *corev1.SecretKeySelector,
|
||||
volumePrefix string,
|
||||
mountPath string,
|
||||
) ([]corev1.Volume, []corev1.VolumeMount) {
|
||||
volumeName := volumePrefix + secret.Name
|
||||
volumes = append(volumes, corev1.Volume{
|
||||
Name: volumeName,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: secret.Name,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
mounts = append(mounts, corev1.VolumeMount{
|
||||
Name: volumeName,
|
||||
ReadOnly: true,
|
||||
MountPath: mountPath,
|
||||
SubPath: secret.Key,
|
||||
})
|
||||
|
||||
return volumes, mounts
|
||||
}
|
||||
|
||||
func (a tlsCredentials) mountParamsForConfigmap(
|
||||
volumes []corev1.Volume,
|
||||
mounts []corev1.VolumeMount,
|
||||
configMap *corev1.ConfigMapKeySelector,
|
||||
volumePrefix string,
|
||||
mountPath string,
|
||||
) ([]corev1.Volume, []corev1.VolumeMount) {
|
||||
volumeName := volumePrefix + configMap.Name
|
||||
volumes = append(volumes, corev1.Volume{
|
||||
Name: volumeName,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: configMap.Name,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
mounts = append(mounts, corev1.VolumeMount{
|
||||
Name: volumeName,
|
||||
ReadOnly: true,
|
||||
MountPath: mountPath,
|
||||
SubPath: configMap.Key,
|
||||
})
|
||||
|
||||
return volumes, mounts
|
||||
}
|
||||
|
||||
// getKeyMountPath is the mount path of the TLS key inside a prometheus container.
|
||||
func (a tlsCredentials) getKeyMountPath() string {
|
||||
secret := monitoringv1.SecretOrConfigMap{Secret: &a.keySecret}
|
||||
return a.tlsPathForSelector(secret)
|
||||
}
|
||||
|
||||
// getCertMountPath is the mount path of the TLS certificate inside a prometheus container,
|
||||
func (a tlsCredentials) getCertMountPath() string {
|
||||
if a.cert.ConfigMap != nil || a.cert.Secret != nil {
|
||||
return a.tlsPathForSelector(a.cert)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// getCAMountPath is the mount path of the client CA certificate inside a prometheus container.
|
||||
func (a tlsCredentials) getCAMountPath() string {
|
||||
if a.clientCA.ConfigMap != nil || a.clientCA.Secret != nil {
|
||||
return a.tlsPathForSelector(a.clientCA)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a *tlsCredentials) tlsPathForSelector(sel monitoringv1.SecretOrConfigMap) string {
|
||||
var filename string
|
||||
if sel.Secret != nil {
|
||||
filename = fmt.Sprintf("secret_%s_%s", sel.Secret.Name, sel.Secret.Key)
|
||||
} else {
|
||||
filename = fmt.Sprintf("configmap_%s_%s", sel.ConfigMap.Name, sel.ConfigMap.Key)
|
||||
}
|
||||
|
||||
return path.Join(a.mountPath, filename)
|
||||
}
|
|
@ -17,12 +17,11 @@ package e2e
|
|||
import (
|
||||
"context"
|
||||
"flag"
|
||||
operatorFramework "github.com/prometheus-operator/prometheus-operator/test/framework"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
operatorFramework "github.com/prometheus-operator/prometheus-operator/test/framework"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
)
|
||||
|
@ -196,6 +195,7 @@ func testAllNSPrometheus(t *testing.T) {
|
|||
"PromSecurePodMonitor": testPromSecurePodMonitor,
|
||||
"PromSharedResourcesReconciliation": testPromSharedResourcesReconciliation,
|
||||
"PromPreserveUserAddedMetadata": testPromPreserveUserAddedMetadata,
|
||||
"PromWebTLS": testPromWebTLS,
|
||||
}
|
||||
|
||||
for name, f := range testFuncs {
|
||||
|
|
|
@ -18,10 +18,13 @@ import (
|
|||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
@ -3651,6 +3654,90 @@ func testPromSecurePodMonitor(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func testPromWebTLS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := framework.NewTestCtx(t)
|
||||
defer ctx.Cleanup(t)
|
||||
ns := ctx.CreateNamespace(t, framework.KubeClient)
|
||||
ctx.SetupPrometheusRBAC(t, ns, framework.KubeClient)
|
||||
|
||||
host := fmt.Sprintf("%s.%s.svc", "basic-prometheus", ns)
|
||||
certBytes, keyBytes, err := certutil.GenerateSelfSignedCertKey(host, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
kubeClient := framework.KubeClient
|
||||
if err := testFramework.CreateSecretWithCert(kubeClient, certBytes, keyBytes, ns, "web-tls"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
prom := framework.MakeBasicPrometheus(ns, "basic-prometheus", "test-group", 1)
|
||||
prom.Spec.Web = &monitoringv1.WebSpec{
|
||||
TLSConfig: &monitoringv1.WebTLSConfig{
|
||||
KeySecret: v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "web-tls",
|
||||
},
|
||||
Key: "tls.key",
|
||||
},
|
||||
Cert: monitoringv1.SecretOrConfigMap{
|
||||
Secret: &v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "web-tls",
|
||||
},
|
||||
Key: "tls.crt",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if _, err := framework.CreatePrometheusAndWaitUntilReady(ns, prom); err != nil {
|
||||
t.Fatal("Creating prometheus failed: ", err)
|
||||
}
|
||||
|
||||
promPods, err := kubeClient.CoreV1().Pods(ns).List(context.TODO(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(promPods.Items) == 0 {
|
||||
t.Fatalf("No prometheus pods found in namespace %s", ns)
|
||||
}
|
||||
|
||||
cfg := framework.RestConfig
|
||||
podName := promPods.Items[0].Name
|
||||
if err := testFramework.StartPortForward(cfg, "https", podName, ns, "9090"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// The prometheus certificate is issued to <pod>.<namespace>.svc,
|
||||
// but port-forwarding is done through localhost.
|
||||
// This is why we use an http client which skips the TLS verification.
|
||||
// In the test we will verify the TLS certificate manually to make sure
|
||||
// the prometheus instance is configured properly.
|
||||
httpClient := http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
resp, err := httpClient.Get("https://localhost:9090")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
receivedCertBytes, err := certutil.EncodeCertificates(resp.TLS.PeerCertificates...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(receivedCertBytes, certBytes) {
|
||||
t.Fatal("Certificate received from prometheus instance does not match the one which is configured")
|
||||
}
|
||||
}
|
||||
|
||||
func isAlertmanagerDiscoveryWorking(ns, promSVCName, alertmanagerName string) func() (bool, error) {
|
||||
return func() (bool, error) {
|
||||
pods, err := framework.KubeClient.CoreV1().Pods(ns).List(context.TODO(), alertmanager.ListOptions(alertmanagerName))
|
||||
|
|
|
@ -19,6 +19,9 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"k8s.io/client-go/tools/portforward"
|
||||
"k8s.io/client-go/transport/spdy"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
|
@ -125,3 +128,34 @@ func execute(method string, url *url.URL, config *rest.Config, stdin io.Reader,
|
|||
Tty: tty,
|
||||
})
|
||||
}
|
||||
|
||||
// StartPortForward initiates a port forwarding connection to a pod on the localhost interface.
|
||||
//
|
||||
// StartPortForward blocks until the port forwarding proxy server is ready to receive connections.
|
||||
func StartPortForward(config *rest.Config, scheme string, name string, ns string, port string) error {
|
||||
roundTripper, upgrader, err := spdy.RoundTripperFor(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/portforward", ns, name)
|
||||
hostIP := strings.TrimLeft(config.Host, "htps:/")
|
||||
serverURL := url.URL{Scheme: scheme, Path: path, Host: hostIP}
|
||||
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: roundTripper}, http.MethodPost, &serverURL)
|
||||
|
||||
stopChan, readyChan := make(chan struct{}, 1), make(chan struct{}, 1)
|
||||
out, errOut := new(bytes.Buffer), new(bytes.Buffer)
|
||||
forwarder, err := portforward.New(dialer, []string{port}, stopChan, readyChan, out, errOut)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := forwarder.ForwardPorts(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
<-readyChan
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue