1
0
Fork 0
mirror of https://github.com/prometheus-operator/prometheus-operator.git synced 2025-04-21 03:38:43 +00:00

Merge pull request from fpetkovski/web-config-support

pkg/prometheus: add support for web TLS configuration
This commit is contained in:
Simon Pasquier 2021-06-09 13:27:14 +02:00 committed by GitHub
commit fa3edac84f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 1802 additions and 18 deletions

View file

@ -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)

View file

@ -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:

View file

@ -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:

477
go.sum

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -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.

View file

@ -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
}

View file

@ -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.

View file

@ -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))
}

View file

@ -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
View 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,
}
}

View 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)
}
}
}

View 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)
}

View file

@ -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 {

View file

@ -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))

View file

@ -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
}