1
0
Fork 0
mirror of https://github.com/prometheus-operator/prometheus-operator.git synced 2025-04-21 11:48:53 +00:00

pkg/prometheus: Enable users to configure bearer token from secret

To configure a bearer token users could only specify a file path in the
service monitor, pointing to a bearer token file in the Prometheus
container. This enables hostile users, being able to configure a service
monitor and controlling the scrape target, to retrieve arbitrary files
in the Prometheus container.

In cases where users can not be trusted, this patch adds an option to
disallow the above file path specification and replaces it by a secret
reference. This secret has to be in the same namespace as the service
monitor, shrinking the attack vector.

pkg/prometheus: Add option to deny file system access through service monitors

ArbitraryFSAccessThroughSMsConfig enables users to configure, whether
a service monitor selected by the Prometheus instance is allowed to use
arbitrary files on the file system of the Prometheus container. This is
the case when e.g. a service monitor specifies a BearerTokenFile in an
endpoint. A malicious user could create a service monitor
selecting arbitrary secret files in the Prometheus container. Those
secrets would then be send with a scrape request by Prometheus to a
malicious target. Denying the above would prevent the attack, users can
instead use the BearerTokenSecret field.

test/basic-auth-test-app: Add mTLS endpoint

pkg/prometheus: Enable users to configure tls from secret

pkg/prometheus/operator: Validate TLS configs before retrieving assets

Before retrieving TLS assets from Kubernetes secrets for a given service
monitor, make sure the user did not specify both file and secret
reference, e.g. both `CAFile` and `CASecret`.

test: Rename basic-auth-test-app to instrumented-sample-app

Given that the basic-auth-test-app not only supports basic auth, but
also bearer token as well as tls authentication, this patch renames the
app to a more generic name.

test/e2e/prometheus_test: Test ArbitraryFSAccessThroughSM option for tls

The Prometheus custom resource has the option to disable arbitrary
filesystem access configured through service monitors. This commit adds
an end-to-end test for this option in combination with the TLS
configuration via files or secret references in service monitors.

pkg/prometheus/operator: Move check for arbitrary fs access into func
This commit is contained in:
Max Leonard Inden 2019-03-18 15:56:38 +01:00 committed by paulfantom
parent 67bccfd45b
commit fd92cbfe94
No known key found for this signature in database
GPG key ID: 12AE0185401674E7
31 changed files with 1662 additions and 330 deletions

View file

@ -17,6 +17,7 @@ This Document documents the types introduced by the Prometheus Operator to be co
* [AlertmanagerList](#alertmanagerlist)
* [AlertmanagerSpec](#alertmanagerspec)
* [AlertmanagerStatus](#alertmanagerstatus)
* [ArbitraryFSAccessThroughSMsConfig](#arbitraryfsaccessthroughsmsconfig)
* [BasicAuth](#basicauth)
* [Endpoint](#endpoint)
* [NamespaceSelector](#namespaceselector)
@ -45,6 +46,7 @@ This Document documents the types introduced by the Prometheus Operator to be co
* [ServiceMonitorSpec](#servicemonitorspec)
* [StorageSpec](#storagespec)
* [TLSConfig](#tlsconfig)
* [TLSConfigValidationError](#tlsconfigvalidationerror)
* [ThanosSpec](#thanosspec)
## APIServerConfig
@ -164,14 +166,24 @@ AlertmanagerStatus is the most recent observed status of the Alertmanager cluste
[Back to TOC](#table-of-contents)
## ArbitraryFSAccessThroughSMsConfig
ArbitraryFSAccessThroughSMsConfig enables users to configure, whether a service monitor selected by the Prometheus instance is allowed to use arbitrary files on the file system of the Prometheus container. This is the case when e.g. a service monitor specifies a BearerTokenFile in an endpoint. A malicious user could create a service monitor selecting arbitrary secret files in the Prometheus container. Those secrets would then be send with a scrape request by Prometheus to a malicious target. Denying the above would prevent the attack, users can instead use the BearerTokenSecret field.
| Field | Description | Scheme | Required |
| ----- | ----------- | ------ | -------- |
| Deny | | bool | false |
[Back to TOC](#table-of-contents)
## BasicAuth
BasicAuth allow an endpoint to authenticate over basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints
| Field | Description | Scheme | Required |
| ----- | ----------- | ------ | -------- |
| username | The secret that contains the username for authenticate | [v1.SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#secretkeyselector-v1-core) | false |
| password | The secret that contains the password for authenticate | [v1.SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#secretkeyselector-v1-core) | false |
| username | The secret in the service monitor namespace that contains the username for authentication. | [v1.SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#secretkeyselector-v1-core) | false |
| password | The secret in the service monitor namespace that contains the password for authentication. | [v1.SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#secretkeyselector-v1-core) | false |
[Back to TOC](#table-of-contents)
@ -190,6 +202,7 @@ Endpoint defines a scrapeable endpoint serving Prometheus metrics.
| scrapeTimeout | Timeout after which the scrape is ended | string | false |
| tlsConfig | TLS configuration to use when scraping the endpoint | *[TLSConfig](#tlsconfig) | false |
| bearerTokenFile | File to read bearer token for scraping targets. | string | false |
| bearerTokenSecret | Secret to mount to read bearer token for scraping targets. The secret needs to be in the same namespace as the service monitor and accessible by the Prometheus Operator. | [v1.SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#secretkeyselector-v1-core) | false |
| honorLabels | HonorLabels chooses the metric's labels on collisions with target labels. | bool | false |
| basicAuth | BasicAuth allow an endpoint to authenticate over basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints | *[BasicAuth](#basicauth) | false |
| metricRelabelings | MetricRelabelConfigs to apply to samples before ingestion. | []*[RelabelConfig](#relabelconfig) | false |
@ -380,6 +393,7 @@ PrometheusSpec is a specification of the desired behavior of the Prometheus clus
| thanos | Thanos configuration allows configuring various aspects of a Prometheus server in a Thanos environment.\n\nThis section is experimental, it may change significantly without deprecation notice in any release.\n\nThis is experimental and may change significantly without backward compatibility in any release. | *[ThanosSpec](#thanosspec) | false |
| priorityClassName | Priority class assigned to the Pods | string | false |
| portName | Port name used for the pods and governing service. This defaults to web | string | false |
| arbitraryFSAccessThroughSMs | ArbitraryFSAccessThroughSMs configures whether configuration based on a service monitor can access arbitrary files on the file system of the Prometheus container e.g. bearer token files. | [ArbitraryFSAccessThroughSMsConfig](#arbitraryfsaccessthroughsmsconfig) | true |
[Back to TOC](#table-of-contents)
@ -583,14 +597,26 @@ TLSConfig specifies TLS configuration parameters.
| Field | Description | Scheme | Required |
| ----- | ----------- | ------ | -------- |
| caFile | The CA cert to use for the targets. | string | false |
| certFile | The client cert file for the targets. | string | false |
| keyFile | The client key file for the targets. | string | false |
| caFile | Path to the CA cert in the Prometheus container to use for the targets. | string | false |
| caSecret | Secret containing the CA cert to use for the targets. | *[v1.SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#secretkeyselector-v1-core) | false |
| certFile | Path to the client cert file in the Prometheus container for the targets. | string | false |
| certSecret | Secret containing the client cert file for the targets. | *[v1.SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#secretkeyselector-v1-core) | false |
| keyFile | Path to the client key file in the Prometheus container for the targets. | string | false |
| keySecret | Secret containing the client key file for the targets. | *[v1.SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#secretkeyselector-v1-core) | false |
| serverName | Used to verify the hostname for the targets. | string | false |
| insecureSkipVerify | Disable target certificate validation. | bool | false |
[Back to TOC](#table-of-contents)
## TLSConfigValidationError
TLSConfigValidationError is returned by TLSConfig.Validate() on semantically invalid tls configurations.
| Field | Description | Scheme | Required |
| ----- | ----------- | ------ | -------- |
[Back to TOC](#table-of-contents)
## ThanosSpec
ThanosSpec defines parameters for a Prometheus server within a Thanos deployment.

View file

@ -214,9 +214,12 @@ test: test-unit test-e2e
test-unit:
go test -race $(TEST_RUN_ARGS) -short $(pkgs) -count=1
test/instrumented-sample-app/certs/cert.pem test/instrumented-sample-app/certs/key.pem:
cd test/instrumented-sample-app && make generate-certs
.PHONY: test-e2e
test-e2e: KUBECONFIG?=$(HOME)/.kube/config
test-e2e:
test-e2e: test/instrumented-sample-app/certs/cert.pem test/instrumented-sample-app/certs/key.pem
go test -timeout 55m -v ./test/e2e/ $(TEST_RUN_ARGS) --kubeconfig=$(KUBECONFIG) --operator-image=$(REPO):$(TAG) -count=1
############

View file

@ -695,17 +695,71 @@ spec:
description: TLSConfig specifies TLS configuration parameters.
properties:
caFile:
description: The CA cert to use for the targets.
description: Path to the CA cert in the Prometheus container
to use for the targets.
type: string
caSecret:
description: SecretKeySelector selects a key of a Secret.
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'
type: string
optional:
description: Specify whether the Secret or its key
must be defined
type: boolean
required:
- key
type: object
certFile:
description: The client cert file for the targets.
description: Path to the client cert file in the Prometheus
container for the targets.
type: string
certSecret:
description: SecretKeySelector selects a key of a Secret.
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'
type: string
optional:
description: Specify whether the Secret or its key
must be defined
type: boolean
required:
- key
type: object
insecureSkipVerify:
description: Disable target certificate validation.
type: boolean
keyFile:
description: The client key file for the targets.
description: Path to the client key file in the Prometheus
container for the targets.
type: string
keySecret:
description: SecretKeySelector selects a key of a Secret.
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'
type: string
optional:
description: Specify whether the Secret or its key
must be defined
type: boolean
required:
- key
type: object
serverName:
description: Used to verify the hostname for the targets.
type: string
@ -776,17 +830,71 @@ spec:
description: TLSConfig specifies TLS configuration parameters.
properties:
caFile:
description: The CA cert to use for the targets.
description: Path to the CA cert in the Prometheus container
to use for the targets.
type: string
caSecret:
description: SecretKeySelector selects a key of a Secret.
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'
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
certFile:
description: The client cert file for the targets.
description: Path to the client cert file in the Prometheus
container for the targets.
type: string
certSecret:
description: SecretKeySelector selects a key of a Secret.
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'
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
insecureSkipVerify:
description: Disable target certificate validation.
type: boolean
keyFile:
description: The client key file for the targets.
description: Path to the client key file in the Prometheus container
for the targets.
type: string
keySecret:
description: SecretKeySelector selects a key of a Secret.
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'
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
serverName:
description: Used to verify the hostname for the targets.
type: string
@ -794,6 +902,7 @@ spec:
required:
- host
type: object
arbitraryFSAccessThroughSMs: {}
baseImage:
description: Base image to use for a Prometheus deployment.
type: string
@ -3164,17 +3273,71 @@ spec:
description: TLSConfig specifies TLS configuration parameters.
properties:
caFile:
description: The CA cert to use for the targets.
description: Path to the CA cert in the Prometheus container
to use for the targets.
type: string
caSecret:
description: SecretKeySelector selects a key of a Secret.
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'
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
certFile:
description: The client cert file for the targets.
description: Path to the client cert file in the Prometheus
container for the targets.
type: string
certSecret:
description: SecretKeySelector selects a key of a Secret.
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'
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
insecureSkipVerify:
description: Disable target certificate validation.
type: boolean
keyFile:
description: The client key file for the targets.
description: Path to the client key file in the Prometheus
container for the targets.
type: string
keySecret:
description: SecretKeySelector selects a key of a Secret.
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'
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
serverName:
description: Used to verify the hostname for the targets.
type: string
@ -3290,17 +3453,71 @@ spec:
description: TLSConfig specifies TLS configuration parameters.
properties:
caFile:
description: The CA cert to use for the targets.
description: Path to the CA cert in the Prometheus container
to use for the targets.
type: string
caSecret:
description: SecretKeySelector selects a key of a Secret.
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'
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
certFile:
description: The client cert file for the targets.
description: Path to the client cert file in the Prometheus
container for the targets.
type: string
certSecret:
description: SecretKeySelector selects a key of a Secret.
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'
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
insecureSkipVerify:
description: Disable target certificate validation.
type: boolean
keyFile:
description: The client key file for the targets.
description: Path to the client key file in the Prometheus
container for the targets.
type: string
keySecret:
description: SecretKeySelector selects a key of a Secret.
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'
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
serverName:
description: Used to verify the hostname for the targets.
type: string
@ -5392,6 +5609,8 @@ spec:
description: Enable compression of the write-ahead log using Snappy.
This flag is only available in versions of Prometheus >= 2.11.0.
type: boolean
required:
- arbitraryFSAccessThroughSMs
type: object
status:
description: 'PrometheusStatus is the most recent observed status of the

View file

@ -75,6 +75,23 @@ spec:
bearerTokenFile:
description: File to read bearer token for scraping targets.
type: string
bearerTokenSecret:
description: SecretKeySelector selects a key of a Secret.
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'
type: string
optional:
description: Specify whether the Secret or its key must be
defined
type: boolean
required:
- key
type: object
honorLabels:
description: HonorLabels chooses the metric's labels on collisions
with target labels.
@ -201,17 +218,71 @@ spec:
description: TLSConfig specifies TLS configuration parameters.
properties:
caFile:
description: The CA cert to use for the targets.
description: Path to the CA cert in the Prometheus container
to use for the targets.
type: string
caSecret:
description: SecretKeySelector selects a key of a Secret.
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'
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
certFile:
description: The client cert file for the targets.
description: Path to the client cert file in the Prometheus
container for the targets.
type: string
certSecret:
description: SecretKeySelector selects a key of a Secret.
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'
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
insecureSkipVerify:
description: Disable target certificate validation.
type: boolean
keyFile:
description: The client key file for the targets.
description: Path to the client key file in the Prometheus
container for the targets.
type: string
keySecret:
description: SecretKeySelector selects a key of a Secret.
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'
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
serverName:
description: Used to verify the hostname for the targets.
type: string

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -899,13 +899,13 @@ func schema_pkg_apis_monitoring_v1_BasicAuth(ref common.ReferenceCallback) commo
Properties: map[string]spec.Schema{
"username": {
SchemaProps: spec.SchemaProps{
Description: "The secret that contains the username for authenticate",
Description: "The secret in the service monitor namespace that contains the username for authentication.",
Ref: ref("k8s.io/api/core/v1.SecretKeySelector"),
},
},
"password": {
SchemaProps: spec.SchemaProps{
Description: "The secret that contains the password for authenticate",
Description: "The secret in the service monitor namespace that contains the password for authentication.",
Ref: ref("k8s.io/api/core/v1.SecretKeySelector"),
},
},
@ -1000,6 +1000,12 @@ func schema_pkg_apis_monitoring_v1_Endpoint(ref common.ReferenceCallback) common
Format: "",
},
},
"bearerTokenSecret": {
SchemaProps: spec.SchemaProps{
Description: "Secret to mount to read bearer token for scraping targets. The secret needs to be in the same namespace as the service monitor and accessible by the Prometheus Operator.",
Ref: ref("k8s.io/api/core/v1.SecretKeySelector"),
},
},
"honorLabels": {
SchemaProps: spec.SchemaProps{
Description: "HonorLabels chooses the metric's labels on collisions with target labels.",
@ -1050,7 +1056,7 @@ func schema_pkg_apis_monitoring_v1_Endpoint(ref common.ReferenceCallback) common
},
},
Dependencies: []string{
"github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.BasicAuth", "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.RelabelConfig", "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.TLSConfig", "k8s.io/apimachinery/pkg/util/intstr.IntOrString"},
"github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.BasicAuth", "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.RelabelConfig", "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.TLSConfig", "k8s.io/api/core/v1.SecretKeySelector", "k8s.io/apimachinery/pkg/util/intstr.IntOrString"},
}
}
@ -2001,11 +2007,22 @@ func schema_pkg_apis_monitoring_v1_PrometheusSpec(ref common.ReferenceCallback)
Format: "",
},
},
"arbitraryFSAccessThroughSMs": {
SchemaProps: spec.SchemaProps{
Description: "ArbitraryFSAccessThroughSMs configures whether configuration based on a service monitor can access arbitrary files on the file system of the Prometheus container e.g. bearer token files.",
Ref: ref("github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.ArbitraryFSAccessThroughSMsConfig"),
},
},
},
Required: []string{"arbitraryFSAccessThroughSMs"},
},
},
Dependencies: []string{
<<<<<<< HEAD
"github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.APIServerConfig", "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.AlertingSpec", "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.QuerySpec", "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.RemoteReadSpec", "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.RemoteWriteSpec", "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.Rules", "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.StorageSpec", "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.ThanosSpec", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.SecretKeySelector", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.Volume", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
=======
"github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.APIServerConfig", "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.AlertingSpec", "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.ArbitraryFSAccessThroughSMsConfig", "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.QuerySpec", "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.RemoteReadSpec", "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.RemoteWriteSpec", "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.Rules", "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.StorageSpec", "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.ThanosSpec", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.SecretKeySelector", "k8s.io/api/core/v1.Toleration", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
>>>>>>> pkg/prometheus: Enable users to configure bearer token from secret
}
}
@ -2769,25 +2786,43 @@ func schema_pkg_apis_monitoring_v1_TLSConfig(ref common.ReferenceCallback) commo
Properties: map[string]spec.Schema{
"caFile": {
SchemaProps: spec.SchemaProps{
Description: "The CA cert to use for the targets.",
Description: "Path to the CA cert in the Prometheus container to use for the targets.",
Type: []string{"string"},
Format: "",
},
},
"caSecret": {
SchemaProps: spec.SchemaProps{
Description: "Secret containing the CA cert to use for the targets.",
Ref: ref("k8s.io/api/core/v1.SecretKeySelector"),
},
},
"certFile": {
SchemaProps: spec.SchemaProps{
Description: "The client cert file for the targets.",
Description: "Path to the client cert file in the Prometheus container for the targets.",
Type: []string{"string"},
Format: "",
},
},
"certSecret": {
SchemaProps: spec.SchemaProps{
Description: "Secret containing the client cert file for the targets.",
Ref: ref("k8s.io/api/core/v1.SecretKeySelector"),
},
},
"keyFile": {
SchemaProps: spec.SchemaProps{
Description: "The client key file for the targets.",
Description: "Path to the client key file in the Prometheus container for the targets.",
Type: []string{"string"},
Format: "",
},
},
"keySecret": {
SchemaProps: spec.SchemaProps{
Description: "Secret containing the client key file for the targets.",
Ref: ref("k8s.io/api/core/v1.SecretKeySelector"),
},
},
"serverName": {
SchemaProps: spec.SchemaProps{
Description: "Used to verify the hostname for the targets.",
@ -2805,6 +2840,8 @@ func schema_pkg_apis_monitoring_v1_TLSConfig(ref common.ReferenceCallback) commo
},
},
},
Dependencies: []string{
"k8s.io/api/core/v1.SecretKeySelector"},
}
}

View file

@ -281,6 +281,24 @@ type PrometheusSpec struct {
// Port name used for the pods and governing service.
// This defaults to web
PortName string `json:"portName,omitempty"`
// ArbitraryFSAccessThroughSMs configures whether configuration
// based on a service monitor can access arbitrary files on the file system
// of the Prometheus container e.g. bearer token files.
ArbitraryFSAccessThroughSMs ArbitraryFSAccessThroughSMsConfig `json:"arbitraryFSAccessThroughSMs"`
}
// ArbitraryFSAccessThroughSMsConfig enables users to configure, whether
// a service monitor selected by the Prometheus instance is allowed to use
// arbitrary files on the file system of the Prometheus container. This is the case
// when e.g. a service monitor specifies a BearerTokenFile in an endpoint. A
// malicious user could create a service monitor selecting arbitrary secret files
// in the Prometheus container. Those secrets would then be send with a scrape
// request by Prometheus to a malicious target. Denying the above would prevent the
// attack, users can instead use the BearerTokenSecret field.
type ArbitraryFSAccessThroughSMsConfig struct {
Deny bool
// TODO: To be implemented.
// ServiceMonitorWhitelist
}
// PrometheusStatus is the most recent observed status of the Prometheus cluster. Read-only. Not
@ -552,6 +570,10 @@ type Endpoint struct {
TLSConfig *TLSConfig `json:"tlsConfig,omitempty"`
// File to read bearer token for scraping targets.
BearerTokenFile string `json:"bearerTokenFile,omitempty"`
// Secret to mount to read bearer token for scraping targets. The secret
// needs to be in the same namespace as the service monitor and accessible by
// the Prometheus Operator.
BearerTokenSecret v1.SecretKeySelector `json:"bearerTokenSecret,omitempty"`
// HonorLabels chooses the metric's labels on collisions with target labels.
HonorLabels bool `json:"honorLabels,omitempty"`
// BasicAuth allow an endpoint to authenticate over basic authentication
@ -628,27 +650,66 @@ type PodMetricsEndpoint struct {
// More info: https://prometheus.io/docs/operating/configuration/#endpoints
// +k8s:openapi-gen=true
type BasicAuth struct {
// The secret that contains the username for authenticate
// The secret in the service monitor namespace that contains the username
// for authentication.
Username v1.SecretKeySelector `json:"username,omitempty"`
// The secret that contains the password for authenticate
// The secret in the service monitor namespace that contains the password
// for authentication.
Password v1.SecretKeySelector `json:"password,omitempty"`
}
// TLSConfig specifies TLS configuration parameters.
// +k8s:openapi-gen=true
type TLSConfig struct {
// The CA cert to use for the targets.
// Path to the CA cert in the Prometheus container to use for the targets.
CAFile string `json:"caFile,omitempty"`
// The client cert file for the targets.
// Secret containing the CA cert to use for the targets.
CASecret *v1.SecretKeySelector `json:"caSecret,omitempty"`
// Path to the client cert file in the Prometheus container for the targets.
CertFile string `json:"certFile,omitempty"`
// The client key file for the targets.
// Secret containing the client cert file for the targets.
CertSecret *v1.SecretKeySelector `json:"certSecret,omitempty"`
// Path to the client key file in the Prometheus container for the targets.
KeyFile string `json:"keyFile,omitempty"`
// Secret containing the client key file for the targets.
KeySecret *v1.SecretKeySelector `json:"keySecret,omitempty"`
// Used to verify the hostname for the targets.
ServerName string `json:"serverName,omitempty"`
// Disable target certificate validation.
InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"`
}
// TLSConfigValidationError is returned by TLSConfig.Validate() on semantically
// invalid tls configurations.
// +k8s:openapi-gen=false
type TLSConfigValidationError struct {
err string
}
func (e *TLSConfigValidationError) Error() string {
return e.err
}
// Validate semantically validates the given TLSConfig.
func (c *TLSConfig) Validate() error {
if c.CAFile != "" && c.CASecret != nil {
return &TLSConfigValidationError{"tls config can not both specify CAFile and CASecret"}
}
if c.CertFile != "" && c.CertSecret != nil {
return &TLSConfigValidationError{"tls config can not both specify CertFile and CertSecret"}
}
if c.KeyFile != "" && c.KeySecret != nil {
return &TLSConfigValidationError{"tls config can not both specify KeyFile and KeySecret"}
}
return nil
}
// ServiceMonitorList is a list of ServiceMonitors.
// +k8s:openapi-gen=true
type ServiceMonitorList struct {

View file

@ -41,7 +41,7 @@ func TestMarshallServiceMonitor(t *testing.T) {
},
},
}
expected := `{"metadata":{"name":"test","namespace":"default","creationTimestamp":null,"labels":{"group":"group1"}},"spec":{"endpoints":[{"port":"metric"}],"selector":{},"namespaceSelector":{"matchNames":["test"]}}}`
expected := `{"metadata":{"name":"test","namespace":"default","creationTimestamp":null,"labels":{"group":"group1"}},"spec":{"endpoints":[{"port":"metric","bearerTokenSecret":{"key":""}}],"selector":{},"namespaceSelector":{"matchNames":["test"]}}}`
r, err := json.Marshal(sm)
if err != nil {

View file

@ -35,7 +35,7 @@ func (in *APIServerConfig) DeepCopyInto(out *APIServerConfig) {
if in.TLSConfig != nil {
in, out := &in.TLSConfig, &out.TLSConfig
*out = new(TLSConfig)
**out = **in
(*in).DeepCopyInto(*out)
}
return
}
@ -104,7 +104,7 @@ func (in *AlertmanagerEndpoints) DeepCopyInto(out *AlertmanagerEndpoints) {
if in.TLSConfig != nil {
in, out := &in.TLSConfig, &out.TLSConfig
*out = new(TLSConfig)
**out = **in
(*in).DeepCopyInto(*out)
}
return
}
@ -269,6 +269,22 @@ func (in *AlertmanagerStatus) DeepCopy() *AlertmanagerStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ArbitraryFSAccessThroughSMsConfig) DeepCopyInto(out *ArbitraryFSAccessThroughSMsConfig) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArbitraryFSAccessThroughSMsConfig.
func (in *ArbitraryFSAccessThroughSMsConfig) DeepCopy() *ArbitraryFSAccessThroughSMsConfig {
if in == nil {
return nil
}
out := new(ArbitraryFSAccessThroughSMsConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BasicAuth) DeepCopyInto(out *BasicAuth) {
*out = *in
@ -350,8 +366,9 @@ func (in *Endpoint) DeepCopyInto(out *Endpoint) {
if in.TLSConfig != nil {
in, out := &in.TLSConfig, &out.TLSConfig
*out = new(TLSConfig)
**out = **in
(*in).DeepCopyInto(*out)
}
in.BearerTokenSecret.DeepCopyInto(&out.BearerTokenSecret)
if in.BasicAuth != nil {
in, out := &in.BasicAuth, &out.BasicAuth
*out = new(BasicAuth)
@ -869,6 +886,7 @@ func (in *PrometheusSpec) DeepCopyInto(out *PrometheusSpec) {
*out = new(ThanosSpec)
(*in).DeepCopyInto(*out)
}
out.ArbitraryFSAccessThroughSMs = in.ArbitraryFSAccessThroughSMs
return
}
@ -989,7 +1007,7 @@ func (in *RemoteReadSpec) DeepCopyInto(out *RemoteReadSpec) {
if in.TLSConfig != nil {
in, out := &in.TLSConfig, &out.TLSConfig
*out = new(TLSConfig)
**out = **in
(*in).DeepCopyInto(*out)
}
return
}
@ -1022,7 +1040,7 @@ func (in *RemoteWriteSpec) DeepCopyInto(out *RemoteWriteSpec) {
if in.TLSConfig != nil {
in, out := &in.TLSConfig, &out.TLSConfig
*out = new(TLSConfig)
**out = **in
(*in).DeepCopyInto(*out)
}
if in.QueueConfig != nil {
in, out := &in.QueueConfig, &out.QueueConfig
@ -1237,6 +1255,21 @@ func (in *StorageSpec) DeepCopy() *StorageSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLSConfig) DeepCopyInto(out *TLSConfig) {
*out = *in
if in.CASecret != nil {
in, out := &in.CASecret, &out.CASecret
*out = new(corev1.SecretKeySelector)
(*in).DeepCopyInto(*out)
}
if in.CertSecret != nil {
in, out := &in.CertSecret, &out.CertSecret
*out = new(corev1.SecretKeySelector)
(*in).DeepCopyInto(*out)
}
if in.KeySecret != nil {
in, out := &in.KeySecret, &out.KeySecret
*out = new(corev1.SecretKeySelector)
(*in).DeepCopyInto(*out)
}
return
}
@ -1250,6 +1283,22 @@ func (in *TLSConfig) DeepCopy() *TLSConfig {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLSConfigValidationError) DeepCopyInto(out *TLSConfigValidationError) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSConfigValidationError.
func (in *TLSConfigValidationError) DeepCopy() *TLSConfigValidationError {
if in == nil {
return nil
}
out := new(TLSConfigValidationError)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ThanosSpec) DeepCopyInto(out *ThanosSpec) {
*out = *in

View file

@ -164,11 +164,21 @@ type Namespaces struct {
PrometheusAllowList, AlertmanagerAllowList []string
}
// BasicAuthCredentials represents a username password pair to be used with
// basic http authentication, see https://tools.ietf.org/html/rfc7617.
type BasicAuthCredentials struct {
username string
password string
}
// BearerToken represents a bearer token, see
// https://tools.ietf.org/html/rfc6750.
type BearerToken string
// TLSAsset represents any TLS related opaque string, e.g. CA files, client
// certificates.
type TLSAsset string
// New creates a new controller.
func New(conf Config, logger log.Logger) (*Operator, error) {
cfg, err := k8sutil.NewClusterConfig(conf.Host, conf.TLSInsecure, &conf.TLSConfig)
@ -1091,6 +1101,10 @@ func (c *Operator) sync(key string) error {
return err
}
if err := c.createOrUpdateTLSAssetSecret(p); err != nil {
return errors.Wrap(err, "creating tls asset secret failed")
}
// Create governing service if it doesn't exist.
svcClient := c.kclient.CoreV1().Services(p.Namespace)
if err := k8sutil.CreateOrUpdateService(svcClient, makeStatefulSetService(p, c.config)); err != nil {
@ -1351,6 +1365,7 @@ func (c *Operator) loadBasicAuthSecrets(
}
secrets[fmt.Sprintf("serviceMonitor/%s/%s/%d", mon.Namespace, mon.Name, i)] = credentials
}
}
}
@ -1387,6 +1402,94 @@ func (c *Operator) loadBasicAuthSecrets(
}
func (c *Operator) loadBearerTokensFromSecrets(mons map[string]*monitoringv1.ServiceMonitor) (map[string]BearerToken, error) {
tokens := map[string]BearerToken{}
nsSecretCache := make(map[string]*v1.Secret)
for _, mon := range mons {
for i, ep := range mon.Spec.Endpoints {
if ep.BearerTokenSecret.Name == "" {
continue
}
sClient := c.kclient.CoreV1().Secrets(mon.Namespace)
token, err := getCredFromSecret(
sClient,
ep.BearerTokenSecret,
"bearertoken",
mon.Namespace+"/"+ep.BearerTokenSecret.Name,
nsSecretCache,
)
if err != nil {
return nil, fmt.Errorf(
"failed to extract endpoint bearertoken for servicemonitor %v from secret %v in namespace %v",
mon.Name, ep.BearerTokenSecret.Name, mon.Namespace,
)
}
tokens[fmt.Sprintf("serviceMonitor/%s/%s/%d", mon.Namespace, mon.Name, i)] = BearerToken(token)
}
}
return tokens, nil
}
func (c *Operator) loadTLSAssetsFromSecrets(mons map[string]*monitoringv1.ServiceMonitor) (map[string]TLSAsset, error) {
assets := map[string]TLSAsset{}
nsSecretCache := make(map[string]*v1.Secret)
for _, mon := range mons {
for _, ep := range mon.Spec.Endpoints {
if ep.TLSConfig == nil {
continue
}
prefix := mon.Namespace + "/"
secretSelectors := map[string]*v1.SecretKeySelector{}
if ep.TLSConfig.CASecret != nil {
secretSelectors[prefix+ep.TLSConfig.CASecret.Name+"/"+ep.TLSConfig.CASecret.Key] = ep.TLSConfig.CASecret
}
if ep.TLSConfig.CertSecret != nil {
secretSelectors[prefix+ep.TLSConfig.CertSecret.Name+"/"+ep.TLSConfig.CertSecret.Key] = ep.TLSConfig.CertSecret
}
if ep.TLSConfig.KeySecret != nil {
secretSelectors[prefix+ep.TLSConfig.KeySecret.Name+"/"+ep.TLSConfig.KeySecret.Key] = ep.TLSConfig.KeySecret
}
for key, selector := range secretSelectors {
sClient := c.kclient.CoreV1().Secrets(mon.Namespace)
asset, err := getCredFromSecret(
sClient,
*selector,
"tls config",
// TODO: Is the cache key really necessary? Can't this be part of `getCredFromSecret`?
key,
nsSecretCache,
)
if err != nil {
return nil, fmt.Errorf(
"failed to extract endpoint tls asset for servicemonitor %v from secret %v and key %v in namespace %v",
mon.Name, selector.Name, selector.Key, mon.Namespace,
)
}
// TODO: Namespacing via underscores seems rather hacky.
assets[fmt.Sprintf(
"%v_%v_%v",
mon.Namespace,
selector.Name,
selector.Key,
)] = TLSAsset(asset)
}
}
}
return assets, nil
}
func (c *Operator) createOrUpdateConfigurationSecret(p *monitoringv1.Prometheus, ruleConfigMapNames []string) error {
smons, err := c.selectServiceMonitors(p)
if err != nil {
@ -1409,6 +1512,11 @@ func (c *Operator) createOrUpdateConfigurationSecret(p *monitoringv1.Prometheus,
return err
}
bearerTokens, err := c.loadBearerTokensFromSecrets(smons)
if err != nil {
return err
}
additionalScrapeConfigs, err := c.loadAdditionalScrapeConfigsSecret(p.Spec.AdditionalScrapeConfigs, SecretsInPromNS)
if err != nil {
return errors.Wrap(err, "loading additional scrape configs from Secret failed")
@ -1428,6 +1536,7 @@ func (c *Operator) createOrUpdateConfigurationSecret(p *monitoringv1.Prometheus,
smons,
pmons,
basicAuthSecrets,
bearerTokens,
additionalScrapeConfigs,
additionalAlertRelabelConfigs,
additionalAlertManagerConfigs,
@ -1475,9 +1584,73 @@ func (c *Operator) createOrUpdateConfigurationSecret(p *monitoringv1.Prometheus,
return err
}
func (c *Operator) createOrUpdateTLSAssetSecret(p *monitoringv1.Prometheus) error {
boolTrue := true
sClient := c.kclient.CoreV1().Secrets(p.Namespace)
smons, err := c.selectServiceMonitors(p)
if err != nil {
return errors.Wrap(err, "selecting ServiceMonitors failed")
}
tlsAssets, err := c.loadTLSAssetsFromSecrets(smons)
if err != nil {
return err
}
tlsAssetsSecret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: tlsAssetsSecretName(p.Name),
Labels: c.config.Labels.Merge(managedByOperatorLabels),
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: p.APIVersion,
BlockOwnerDeletion: &boolTrue,
Controller: &boolTrue,
Kind: p.Kind,
Name: p.Name,
UID: p.UID,
},
},
},
Data: map[string][]byte{},
}
for key, asset := range tlsAssets {
tlsAssetsSecret.Data[key] = []byte(asset)
}
oldSecretExists := true
_, err = sClient.Get(tlsAssetsSecret.Name, metav1.GetOptions{})
if err != nil {
if !apierrors.IsNotFound(err) {
return errors.Wrapf(
err,
"failed to check whether tls assets secret already exists for Prometheus %v in namespace %v",
p.Name,
p.Namespace,
)
}
oldSecretExists = false
}
if oldSecretExists {
_, err = sClient.Update(tlsAssetsSecret)
} else {
_, err = sClient.Create(tlsAssetsSecret)
}
if err != nil {
return errors.Wrapf(err, "failed to create tls assets secret for Prometheus %v in namespace %v", p.Name, p.Namespace)
}
return nil
}
func (c *Operator) selectServiceMonitors(p *monitoringv1.Prometheus) (map[string]*monitoringv1.ServiceMonitor, error) {
namespaces := []string{}
// Selectors might overlap. Deduplicate them along the keyFunc.
// Selectors (<namespace>/<name>) might overlap. Deduplicate them along the keyFunc.
res := make(map[string]*monitoringv1.ServiceMonitor)
servMonSelector, err := metav1.LabelSelectorAsSelector(p.Spec.ServiceMonitorSelector)
@ -1511,6 +1684,25 @@ func (c *Operator) selectServiceMonitors(p *monitoringv1.Prometheus) (map[string
})
}
// If denied by Prometheus spec, filter out all service monitors that access
// the file system.
if p.Spec.ArbitraryFSAccessThroughSMs.Deny {
for namespaceAndName, sm := range res {
for _, endpoint := range sm.Spec.Endpoints {
if err := testForArbitraryFSAccess(endpoint); err != nil {
delete(res, namespaceAndName)
level.Warn(c.logger).Log(
"msg", "skipping servicemonitor",
"error", err.Error(),
"servicemonitor", namespaceAndName,
"namespace", p.Namespace,
"prometheus", p.Name,
)
}
}
}
}
serviceMonitors := []string{}
for k := range res {
serviceMonitors = append(serviceMonitors, k)
@ -1563,6 +1755,27 @@ func (c *Operator) selectPodMonitors(p *monitoringv1.Prometheus) (map[string]*mo
return res, nil
}
func testForArbitraryFSAccess(e monitoringv1.Endpoint) error {
if e.BearerTokenFile != "" {
return errors.New("it accesses file system via bearer token file which Prometheus specification prohibits")
}
tlsConf := e.TLSConfig
if tlsConf == nil {
return nil
}
if err := e.TLSConfig.Validate(); err != nil {
return err
}
if tlsConf.CAFile != "" || tlsConf.CertFile != "" || tlsConf.KeyFile != "" {
return errors.New("it accesses file system via tls config which Prometheus specification prohibits")
}
return nil
}
// listMatchingNamespaces lists all the namespaces that match the provided
// selector.
func (c *Operator) listMatchingNamespaces(selector labels.Selector) ([]string, error) {

View file

@ -16,6 +16,7 @@ package prometheus
import (
"fmt"
"path"
"regexp"
"sort"
"strings"
@ -70,20 +71,30 @@ func stringMapToMapSlice(m map[string]string) yaml.MapSlice {
return res
}
func addTLStoYaml(cfg yaml.MapSlice, tls *v1.TLSConfig) yaml.MapSlice {
func addTLStoYaml(cfg yaml.MapSlice, namespace string, tls *v1.TLSConfig) yaml.MapSlice {
if tls != nil {
pathPrefix := path.Join(tlsAssetsDir, namespace)
tlsConfig := yaml.MapSlice{
{Key: "insecure_skip_verify", Value: tls.InsecureSkipVerify},
}
if tls.CAFile != "" {
tlsConfig = append(tlsConfig, yaml.MapItem{Key: "ca_file", Value: tls.CAFile})
}
if tls.CASecret != nil {
tlsConfig = append(tlsConfig, yaml.MapItem{Key: "ca_file", Value: pathPrefix + "_" + tls.CASecret.Name + "_" + tls.CASecret.Key})
}
if tls.CertFile != "" {
tlsConfig = append(tlsConfig, yaml.MapItem{Key: "cert_file", Value: tls.CertFile})
}
if tls.CertSecret != nil {
tlsConfig = append(tlsConfig, yaml.MapItem{Key: "cert_file", Value: pathPrefix + "_" + tls.CertSecret.Name + "_" + tls.CertSecret.Key})
}
if tls.KeyFile != "" {
tlsConfig = append(tlsConfig, yaml.MapItem{Key: "key_file", Value: tls.KeyFile})
}
if tls.KeySecret != nil {
tlsConfig = append(tlsConfig, yaml.MapItem{Key: "key_file", Value: pathPrefix + "_" + tls.KeySecret.Name + "_" + tls.KeySecret.Key})
}
if tls.ServerName != "" {
tlsConfig = append(tlsConfig, yaml.MapItem{Key: "server_name", Value: tls.ServerName})
}
@ -136,6 +147,7 @@ func (cg *configGenerator) generateConfig(
sMons map[string]*v1.ServiceMonitor,
pMons map[string]*v1.PodMonitor,
basicAuthSecrets map[string]BasicAuthCredentials,
bearerTokens map[string]BearerToken,
additionalScrapeConfigs []byte,
additionalAlertRelabelConfigs []byte,
additionalAlertManagerConfigs []byte,
@ -206,7 +218,7 @@ func (cg *configGenerator) generateConfig(
var scrapeConfigs []yaml.MapSlice
for _, identifier := range sMonIdentifiers {
for i, ep := range sMons[identifier].Spec.Endpoints {
scrapeConfigs = append(scrapeConfigs, cg.generateServiceMonitorConfig(version, sMons[identifier], ep, i, apiserverConfig, basicAuthSecrets))
scrapeConfigs = append(scrapeConfigs, cg.generateServiceMonitorConfig(version, sMons[identifier], ep, i, apiserverConfig, basicAuthSecrets, bearerTokens))
}
}
for _, identifier := range pMonIdentifiers {
@ -523,7 +535,15 @@ func (cg *configGenerator) generatePodMonitorConfig(version semver.Version, m *v
return cfg
}
func (cg *configGenerator) generateServiceMonitorConfig(version semver.Version, m *v1.ServiceMonitor, ep v1.Endpoint, i int, apiserverConfig *v1.APIServerConfig, basicAuthSecrets map[string]BasicAuthCredentials) yaml.MapSlice {
func (cg *configGenerator) generateServiceMonitorConfig(
version semver.Version,
m *v1.ServiceMonitor,
ep v1.Endpoint,
i int,
apiserverConfig *v1.APIServerConfig,
basicAuthSecrets map[string]BasicAuthCredentials,
bearerTokens map[string]BearerToken,
) yaml.MapSlice {
cfg := yaml.MapSlice{
{
Key: "job_name",
@ -568,12 +588,18 @@ func (cg *configGenerator) generateServiceMonitorConfig(version semver.Version,
cfg = append(cfg, yaml.MapItem{Key: "scheme", Value: ep.Scheme})
}
cfg = addTLStoYaml(cfg, ep.TLSConfig)
cfg = addTLStoYaml(cfg, m.Namespace, ep.TLSConfig)
if ep.BearerTokenFile != "" {
cfg = append(cfg, yaml.MapItem{Key: "bearer_token_file", Value: ep.BearerTokenFile})
}
if ep.BearerTokenSecret.Name != "" {
if s, ok := bearerTokens[fmt.Sprintf("serviceMonitor/%s/%s/%d", m.Namespace, m.Name, i)]; ok {
cfg = append(cfg, yaml.MapItem{Key: "bearer_token", Value: s})
}
}
if ep.BasicAuth != nil {
if s, ok := basicAuthSecrets[fmt.Sprintf("serviceMonitor/%s/%s/%d", m.Namespace, m.Name, i)]; ok {
cfg = append(cfg, yaml.MapItem{
@ -899,7 +925,9 @@ func (cg *configGenerator) generateK8SSDConfig(namespaces []string, apiserverCon
k8sSDConfig = append(k8sSDConfig, yaml.MapItem{Key: "bearer_token_file", Value: apiserverConfig.BearerTokenFile})
}
k8sSDConfig = addTLStoYaml(k8sSDConfig, apiserverConfig.TLSConfig)
// TODO: If we want to support secret refs for k8s service discovery tls
// config as well, make sure to path the right namespace here.
k8sSDConfig = addTLStoYaml(k8sSDConfig, "", apiserverConfig.TLSConfig)
}
return yaml.MapItem{
@ -924,7 +952,9 @@ func (cg *configGenerator) generateAlertmanagerConfig(version semver.Version, am
{Key: "scheme", Value: am.Scheme},
}
cfg = addTLStoYaml(cfg, am.TLSConfig)
// TODO: If we want to support secret refs for alertmanager config tls
// config as well, make sure to path the right namespace here.
cfg = addTLStoYaml(cfg, "", am.TLSConfig)
switch version.Major {
case 1:
@ -1021,7 +1051,9 @@ func (cg *configGenerator) generateRemoteReadConfig(version semver.Version, spec
cfg = append(cfg, yaml.MapItem{Key: "bearer_token_file", Value: spec.BearerTokenFile})
}
cfg = addTLStoYaml(cfg, spec.TLSConfig)
// TODO: If we want to support secret refs for remote read tls
// config as well, make sure to path the right namespace here.
cfg = addTLStoYaml(cfg, "", spec.TLSConfig)
if spec.ProxyURL != "" {
cfg = append(cfg, yaml.MapItem{Key: "proxy_url", Value: spec.ProxyURL})
@ -1110,7 +1142,9 @@ func (cg *configGenerator) generateRemoteWriteConfig(version semver.Version, spe
cfg = append(cfg, yaml.MapItem{Key: "bearer_token_file", Value: spec.BearerTokenFile})
}
cfg = addTLStoYaml(cfg, spec.TLSConfig)
// TODO: If we want to support secret refs for remote write tls
// config as well, make sure to path the right namespace here.
cfg = addTLStoYaml(cfg, "", spec.TLSConfig)
if spec.ProxyURL != "" {
cfg = append(cfg, yaml.MapItem{Key: "proxy_url", Value: spec.ProxyURL})

View file

@ -254,6 +254,7 @@ func TestAlertmanagerBearerToken(t *testing.T) {
nil,
nil,
map[string]BasicAuthCredentials{},
map[string]BearerToken{},
nil,
nil,
nil,
@ -329,6 +330,7 @@ func TestAdditionalAlertRelabelConfigs(t *testing.T) {
nil,
nil,
map[string]BasicAuthCredentials{},
map[string]BearerToken{},
nil,
[]byte(`- action: drop
source_labels: [__meta_kubernetes_node_name]
@ -408,6 +410,7 @@ func TestAdditionalAlertmanagers(t *testing.T) {
nil,
nil,
map[string]BasicAuthCredentials{},
map[string]BearerToken{},
nil,
nil,
[]byte(`- static_configs:
@ -500,6 +503,7 @@ func TestTargetLabels(t *testing.T) {
},
nil,
map[string]BasicAuthCredentials{},
map[string]BearerToken{},
nil,
nil,
nil,
@ -622,6 +626,7 @@ func TestPodTargetLabels(t *testing.T) {
},
nil,
map[string]BasicAuthCredentials{},
map[string]BearerToken{},
nil,
nil,
nil,
@ -842,6 +847,7 @@ func TestEmptyEndointPorts(t *testing.T) {
},
nil,
map[string]BasicAuthCredentials{},
map[string]BearerToken{},
nil,
nil,
nil,
@ -971,6 +977,7 @@ func generateTestConfig(version string) ([]byte, error) {
makeServiceMonitors(),
makePodMonitors(),
map[string]BasicAuthCredentials{},
map[string]BearerToken{},
nil,
nil,
nil,

View file

@ -41,6 +41,7 @@ const (
storageDir = "/prometheus"
confDir = "/etc/prometheus/config"
confOutDir = "/etc/prometheus/config_out"
tlsAssetsDir = "/etc/prometheus/certs"
rulesDir = "/etc/prometheus/rules"
secretsDir = "/etc/prometheus/secrets/"
configmapsDir = "/etc/prometheus/configmaps/"
@ -496,6 +497,14 @@ func makeStatefulSetSpec(p monitoringv1.Prometheus, c *Config, ruleConfigMapName
},
},
},
{
Name: "tls-assets",
VolumeSource: v1.VolumeSource{
Secret: &v1.SecretVolumeSource{
SecretName: tlsAssetsSecretName(p.Name),
},
},
},
{
Name: "config-out",
VolumeSource: v1.VolumeSource{
@ -530,6 +539,11 @@ func makeStatefulSetSpec(p monitoringv1.Prometheus, c *Config, ruleConfigMapName
ReadOnly: true,
MountPath: confOutDir,
},
{
Name: "tls-assets",
ReadOnly: true,
MountPath: tlsAssetsDir,
},
{
Name: volName,
MountPath: storageDir,
@ -894,7 +908,11 @@ func makeStatefulSetSpec(p monitoringv1.Prometheus, c *Config, ruleConfigMapName
}
func configSecretName(name string) string {
return prefixedName(name)
return fmt.Sprintf("%s-config", prefixedName(name))
}
func tlsAssetsSecretName(name string) string {
return fmt.Sprintf("%s-tls-assets", prefixedName(name))
}
func volumeName(name string) string {

View file

@ -185,6 +185,14 @@ func TestStatefulSetVolumeInitial(t *testing.T) {
MountPath: "/etc/prometheus/config_out",
SubPath: "",
},
{
Name: "tls-assets",
ReadOnly: true,
MountPath: "/etc/prometheus/certs",
SubPath: "",
MountPropagation: nil,
SubPathExpr: "",
},
{
Name: "prometheus-volume-init-test-db",
ReadOnly: false,
@ -215,6 +223,14 @@ func TestStatefulSetVolumeInitial(t *testing.T) {
},
},
},
{
Name: "tls-assets",
VolumeSource: v1.VolumeSource{
Secret: &v1.SecretVolumeSource{
SecretName: tlsAssetsSecretName("volume-init-test"),
},
},
},
{
Name: "config-out",
VolumeSource: v1.VolumeSource{

View file

@ -1,13 +0,0 @@
REG := quay.io/coreos
APP := basic-auth-test-app
VERSION ?= $(shell cat VERSION)
all: build push
build:
@GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -mod=vendor -o basic-auth-test-app main.go
@docker build --build-arg VERSION=$(VERSION) -t $(REG)/$(APP):$(VERSION) --file $(APP).dockerfile .
@rm $(APP)
push:
@docker push $(REG)/$(APP):$(VERSION)

View file

@ -1 +0,0 @@
0.1.0

View file

@ -1,8 +0,0 @@
FROM alpine:3.7
ARG VERSION="$VERSION"
ENV VERSION="$VERSION"
COPY basic-auth-test-app /
ENTRYPOINT ["/basic-auth-test-app"]

View file

@ -1,65 +0,0 @@
// Copyright 2016 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 main
import (
"encoding/base64"
"fmt"
"github.com/prometheus/client_golang/prometheus/promhttp"
"net/http"
"os"
"strings"
"time"
)
func main() {
http.HandleFunc("/", handler)
http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
if checkAuth(w, r) {
promhttp.Handler().ServeHTTP(w, r)
return
}
w.Header().Set("WWW-Authenticate", `Basic realm="MY REALM"`)
w.WriteHeader(401)
w.Write([]byte("401 Unauthorized\n"))
})
http.ListenAndServe(":8080", nil)
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, time.Now().String())
fmt.Fprintf(w, "\nAppVersion:"+os.Getenv("VERSION"))
}
func checkAuth(w http.ResponseWriter, r *http.Request) bool {
s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
if len(s) != 2 {
return false
}
b, err := base64.StdEncoding.DecodeString(s[1])
if err != nil {
return false
}
pair := strings.SplitN(string(b), ":", 2)
if len(pair) != 2 {
return false
}
return pair[0] == "user" && pair[1] == "pass"
}

View file

@ -149,7 +149,9 @@ func testAllNS(t *testing.T) {
"PromExposingWithKubernetesAPI": testPromExposingWithKubernetesAPI,
"PromDiscoverTargetPort": testPromDiscoverTargetPort,
"PromOpMatchPromAndServMonInDiffNSs": testPromOpMatchPromAndServMonInDiffNSs,
"PromGetBasicAuthSecret": testPromGetBasicAuthSecret,
"PromGetAuthSecret": testPromGetAuthSecret,
"PromArbitraryFSAcc": testPromArbitraryFSAcc,
"PromTLSConfigViaSecret": testPromTLSConfigViaSecret,
"Thanos": testThanos,
}

View file

@ -20,6 +20,7 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"reflect"
"sort"
@ -299,7 +300,7 @@ scrape_configs:
cfg := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("prometheus-%s", name),
Name: fmt.Sprintf("prometheus-%s-config", name),
},
Data: map[string][]byte{
"prometheus.yaml.gz": firstConfigCompressed,
@ -477,7 +478,7 @@ func testPromAdditionalAlertManagerConfig(t *testing.T) {
}
err = wait.Poll(time.Second, 5*time.Minute, func() (done bool, err error) {
response, err := framework.QueryPrometheusSVC(ns, svc.Name, "/api/v1/alertmanagers", map[string]string{})
response, err := framework.PrometheusSVCGetRequest(ns, svc.Name, "/api/v1/alertmanagers", map[string]string{})
if err != nil {
return true, err
}
@ -862,7 +863,18 @@ func testPromOnlyUpdatedOnRelevantChanges(t *testing.T) {
KubeClient.
CoreV1().
Secrets(ns).
Get("prometheus-"+prometheusName, metav1.GetOptions{})
Get("prometheus-"+prometheusName+"-config", metav1.GetOptions{})
},
MaxExpectedChanges: 2,
},
{
Name: "tlsAssetSecret",
Getter: func(prometheusName string) (versionedResource, error) {
return framework.
KubeClient.
CoreV1().
Secrets(ns).
Get("prometheus-"+prometheusName+"-tls-assets", metav1.GetOptions{})
},
MaxExpectedChanges: 2,
},
@ -958,7 +970,7 @@ func testPromOnlyUpdatedOnRelevantChanges(t *testing.T) {
t.Fatal(err)
}
err = isDiscoveryWorking(ns, pSVC.Name, prometheus.Name)
err = framework.WaitForDiscoveryWorking(ns, pSVC.Name, prometheus.Name)
if err != nil {
t.Fatal(errors.Wrap(err, "validating Prometheus target discovery failed"))
}
@ -1055,12 +1067,12 @@ func testPromDiscovery(t *testing.T) {
ctx.AddFinalizerFn(finalizerFn)
}
_, err = framework.KubeClient.CoreV1().Secrets(ns).Get(fmt.Sprintf("prometheus-%s", prometheusName), metav1.GetOptions{})
_, err = framework.KubeClient.CoreV1().Secrets(ns).Get(fmt.Sprintf("prometheus-%s-config", prometheusName), metav1.GetOptions{})
if err != nil {
t.Fatal("Generated Secret could not be retrieved: ", err)
}
err = isDiscoveryWorking(ns, svc.Name, prometheusName)
err = framework.WaitForDiscoveryWorking(ns, svc.Name, prometheusName)
if err != nil {
t.Fatal(errors.Wrap(err, "validating Prometheus target discovery failed"))
}
@ -1098,7 +1110,7 @@ func testPromAlertmanagerDiscovery(t *testing.T) {
t.Fatalf("Creating ServiceMonitor failed: %v", err)
}
_, err = framework.KubeClient.CoreV1().Secrets(ns).Get(fmt.Sprintf("prometheus-%s", prometheusName), metav1.GetOptions{})
_, err = framework.KubeClient.CoreV1().Secrets(ns).Get(fmt.Sprintf("prometheus-%s-config", prometheusName), metav1.GetOptions{})
if err != nil {
t.Fatalf("Generated Secret could not be retrieved: %v", err)
}
@ -1192,12 +1204,12 @@ func testPromDiscoverTargetPort(t *testing.T) {
ctx.AddFinalizerFn(finalizerFn)
}
_, err := framework.KubeClient.CoreV1().Secrets(ns).Get(fmt.Sprintf("prometheus-%s", prometheusName), metav1.GetOptions{})
_, err := framework.KubeClient.CoreV1().Secrets(ns).Get(fmt.Sprintf("prometheus-%s-config", prometheusName), metav1.GetOptions{})
if err != nil {
t.Fatal("Generated Secret could not be retrieved: ", err)
}
err = isDiscoveryWorking(ns, svc.Name, prometheusName)
err = framework.WaitForDiscoveryWorking(ns, svc.Name, prometheusName)
if err != nil {
t.Fatal(errors.Wrap(err, "validating Prometheus target discovery failed"))
}
@ -1249,7 +1261,7 @@ func testPromOpMatchPromAndServMonInDiffNSs(t *testing.T) {
ctx.AddFinalizerFn(finalizerFn)
}
resp, err := framework.QueryPrometheusSVC(prometheusNSName, svc.Name, "/api/v1/status/config", map[string]string{})
resp, err := framework.PrometheusSVCGetRequest(prometheusNSName, svc.Name, "/api/v1/status/config", map[string]string{})
if err != nil {
t.Fatal(err)
}
@ -1353,105 +1365,146 @@ func testThanos(t *testing.T) {
}
}
func testPromGetBasicAuthSecret(t *testing.T) {
func testPromGetAuthSecret(t *testing.T) {
t.Parallel()
ctx := framework.NewTestCtx(t)
defer ctx.Cleanup(t)
ns := ctx.CreateNamespace(t, framework.KubeClient)
ctx.SetupPrometheusRBACGlobal(t, ns, framework.KubeClient)
name := "test"
maptest := make(map[string]string)
maptest["tc"] = ns
prometheusCRD := framework.MakeBasicPrometheus(ns, name, name, 1)
prometheusCRD.Spec.ServiceMonitorNamespaceSelector = &metav1.LabelSelector{
MatchLabels: maptest,
}
if _, err := framework.CreatePrometheusAndWaitUntilReady(ns, prometheusCRD); err != nil {
t.Fatal(err)
}
testNamespace := ctx.CreateNamespace(t, framework.KubeClient)
err := testFramework.AddLabelsToNamespace(framework.KubeClient, testNamespace, maptest)
if err != nil {
t.Fatal(err)
}
simple, err := testFramework.MakeDeployment("../../test/framework/ressources/basic-auth-app-deployment.yaml")
if err != nil {
t.Fatal(err)
}
if err := testFramework.CreateDeployment(framework.KubeClient, testNamespace, simple); err != nil {
t.Fatal("Creating simple basic auth app failed: ", err)
}
authSecret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string][]byte{
"user": []byte("user"),
"password": []byte("pass"),
},
}
if _, err := framework.KubeClient.CoreV1().Secrets(testNamespace).Create(authSecret); err != nil {
t.Fatal(err)
}
svc := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: map[string]string{
"group": name,
},
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
Ports: []v1.ServicePort{
v1.ServicePort{
Name: "web",
Port: 8080,
tests := []struct {
name string
secret *v1.Secret
serviceMonitor func() *monitoringv1.ServiceMonitor
}{
{
name: "basic-auth",
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string][]byte{
"user": []byte("user"),
"password": []byte("pass"),
},
},
Selector: map[string]string{
"group": name,
serviceMonitor: func() *monitoringv1.ServiceMonitor {
sm := framework.MakeBasicServiceMonitor(name)
sm.Spec.Endpoints[0].BasicAuth = &monitoringv1.BasicAuth{
Username: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "user",
},
Password: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "password",
},
}
return sm
},
},
{
name: "bearer-token",
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string][]byte{
"bearertoken": []byte("abc"),
},
},
serviceMonitor: func() *monitoringv1.ServiceMonitor {
sm := framework.MakeBasicServiceMonitor(name)
sm.Spec.Endpoints[0].BearerTokenSecret = v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "bearertoken",
}
sm.Spec.Endpoints[0].Path = "/bearer-token"
return sm
},
},
}
sm := framework.MakeBasicServiceMonitor(name)
sm.Spec.Endpoints[0].BasicAuth = &monitoringv1.BasicAuth{
Username: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "user",
},
Password: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "password",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
ctx := framework.NewTestCtx(t)
defer ctx.Cleanup(t)
ns := ctx.CreateNamespace(t, framework.KubeClient)
ctx.SetupPrometheusRBACGlobal(t, ns, framework.KubeClient)
if finalizerFn, err := testFramework.CreateServiceAndWaitUntilReady(framework.KubeClient, testNamespace, svc); err != nil {
t.Fatal(err)
} else {
ctx.AddFinalizerFn(finalizerFn)
}
maptest := make(map[string]string)
maptest["tc"] = ns
prometheusCRD := framework.MakeBasicPrometheus(ns, name, name, 1)
prometheusCRD.Spec.ServiceMonitorNamespaceSelector = &metav1.LabelSelector{
MatchLabels: maptest,
}
prometheusCRD.Spec.ScrapeInterval = "1s"
if _, err := framework.MonClientV1.ServiceMonitors(testNamespace).Create(sm); err != nil {
t.Fatal("Creating ServiceMonitor failed: ", err)
}
if _, err := framework.CreatePrometheusAndWaitUntilReady(ns, prometheusCRD); err != nil {
t.Fatal(err)
}
testNamespace := ctx.CreateNamespace(t, framework.KubeClient)
if err := framework.WaitForTargets(ns, "prometheus-operated", 1); err != nil {
t.Fatal(err)
err := testFramework.AddLabelsToNamespace(framework.KubeClient, testNamespace, maptest)
if err != nil {
t.Fatal(err)
}
simple, err := testFramework.MakeDeployment("../../test/framework/ressources/basic-auth-app-deployment.yaml")
if err != nil {
t.Fatal(err)
}
if err := testFramework.CreateDeployment(framework.KubeClient, testNamespace, simple); err != nil {
t.Fatal("Creating simple basic auth app failed: ", err)
}
authSecret := test.secret
if _, err := framework.KubeClient.CoreV1().Secrets(testNamespace).Create(authSecret); err != nil {
t.Fatal(err)
}
svc := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: map[string]string{
"group": name,
},
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
Ports: []v1.ServicePort{
v1.ServicePort{
Name: "web",
Port: 8080,
},
},
Selector: map[string]string{
"group": name,
},
},
}
sm := test.serviceMonitor()
if finalizerFn, err := testFramework.CreateServiceAndWaitUntilReady(framework.KubeClient, testNamespace, svc); err != nil {
t.Fatal(err)
} else {
ctx.AddFinalizerFn(finalizerFn)
}
if _, err := framework.MonClientV1.ServiceMonitors(testNamespace).Create(sm); err != nil {
t.Fatal("Creating ServiceMonitor failed: ", err)
}
if err := framework.WaitForTargets(ns, "prometheus-operated", 1); err != nil {
t.Fatal(err)
}
})
}
}
@ -1607,79 +1660,430 @@ func testOperatorNSScope(t *testing.T) {
})
}
func isDiscoveryWorking(ns, svcName, prometheusName string) error {
var loopErr error
// testPromArbitraryFSAcc tests the
// github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1.PrometheusSpec.ArbitraryFSAccessThroughSMs
// configuration with the service monitor bearer token and tls assets option.
func testPromArbitraryFSAcc(t *testing.T) {
t.Parallel()
err := wait.Poll(time.Second, 5*framework.DefaultTimeout, func() (bool, error) {
pods, loopErr := framework.KubeClient.CoreV1().Pods(ns).List(prometheus.ListOptions(prometheusName))
if loopErr != nil {
return false, loopErr
}
if 1 != len(pods.Items) {
return false, nil
}
podIP := pods.Items[0].Status.PodIP
expectedTargets := []string{fmt.Sprintf("http://%s:9090/metrics", podIP)}
name := "test"
activeTargets, loopErr := framework.GetActiveTargets(ns, svcName)
if loopErr != nil {
return false, loopErr
}
tests := []struct {
name string
arbitraryFSAccessThroughSMsConfig monitoringv1.ArbitraryFSAccessThroughSMsConfig
endpoint monitoringv1.Endpoint
expectTargets bool
}{
//
// Bearer tokens:
//
{
name: "allowed-bearer-file",
arbitraryFSAccessThroughSMsConfig: monitoringv1.ArbitraryFSAccessThroughSMsConfig{
Deny: false,
},
endpoint: monitoringv1.Endpoint{
Port: "web",
BearerTokenFile: "abc",
},
expectTargets: true,
},
{
name: "denied-bearer-file",
arbitraryFSAccessThroughSMsConfig: monitoringv1.ArbitraryFSAccessThroughSMsConfig{
Deny: true,
},
endpoint: monitoringv1.Endpoint{
Port: "web",
BearerTokenFile: "abc",
},
expectTargets: false,
},
{
name: "allowed-bearer-secret",
arbitraryFSAccessThroughSMsConfig: monitoringv1.ArbitraryFSAccessThroughSMsConfig{
Deny: false,
},
endpoint: monitoringv1.Endpoint{
Port: "web",
BearerTokenSecret: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "bearer-token",
},
},
expectTargets: true,
},
{
name: "denied-bearer-secret",
arbitraryFSAccessThroughSMsConfig: monitoringv1.ArbitraryFSAccessThroughSMsConfig{
Deny: true,
},
endpoint: monitoringv1.Endpoint{
Port: "web",
BearerTokenSecret: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "bearer-token",
},
},
expectTargets: true,
},
//
// TLS assets:
//
{
name: "allowed-tls-file",
arbitraryFSAccessThroughSMsConfig: monitoringv1.ArbitraryFSAccessThroughSMsConfig{
Deny: false,
},
endpoint: monitoringv1.Endpoint{
Port: "web",
TLSConfig: &monitoringv1.TLSConfig{
CAFile: "/etc/ca-certificates/example-ca.pem",
CertFile: "/etc/ca-certificates/example-cert.pem",
KeyFile: "/etc/ca-certificates/example-key.pem",
},
},
expectTargets: true,
},
{
name: "denied-tls-file",
arbitraryFSAccessThroughSMsConfig: monitoringv1.ArbitraryFSAccessThroughSMsConfig{
Deny: true,
},
endpoint: monitoringv1.Endpoint{
Port: "web",
TLSConfig: &monitoringv1.TLSConfig{
CAFile: "/etc/ca-certificates/example-ca.pem",
CertFile: "/etc/ca-certificates/example-cert.pem",
KeyFile: "/etc/ca-certificates/example-key.pem",
},
},
expectTargets: false,
},
{
name: "allowed-tls-secret",
arbitraryFSAccessThroughSMsConfig: monitoringv1.ArbitraryFSAccessThroughSMsConfig{
Deny: false,
},
endpoint: monitoringv1.Endpoint{
Port: "web",
TLSConfig: &monitoringv1.TLSConfig{
InsecureSkipVerify: true,
CASecret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "cert.pem",
},
CertSecret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "cert.pem",
},
KeySecret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "key.pem",
},
},
},
expectTargets: true,
},
{
name: "denied-tls-secret",
arbitraryFSAccessThroughSMsConfig: monitoringv1.ArbitraryFSAccessThroughSMsConfig{
Deny: true,
},
endpoint: monitoringv1.Endpoint{
Port: "web",
TLSConfig: &monitoringv1.TLSConfig{
InsecureSkipVerify: true,
CASecret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "cert.pem",
},
CertSecret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "cert.pem",
},
KeySecret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "key.pem",
},
},
},
expectTargets: true,
},
}
if loopErr = assertExpectedTargets(activeTargets, expectedTargets); loopErr != nil {
return false, nil
}
for _, test := range tests {
test := test
working, loopErr := basicQueryWorking(ns, svcName)
if loopErr != nil {
return false, loopErr
}
if !working {
return false, nil
}
t.Run(test.name, func(t *testing.T) {
t.Parallel()
return true, nil
})
ctx := framework.NewTestCtx(t)
defer ctx.Cleanup(t)
ns := ctx.CreateNamespace(t, framework.KubeClient)
ctx.SetupPrometheusRBAC(t, ns, framework.KubeClient)
// Create secret either used by bearer token secret key ref, or tls
// asset key ref.
cert, err := ioutil.ReadFile("../../test/instrumented-sample-app/certs/cert.pem")
if err != nil {
t.Fatalf("failed to load cert.pem: %v", err)
}
key, err := ioutil.ReadFile("../../test/instrumented-sample-app/certs/key.pem")
if err != nil {
t.Fatalf("failed to load key.pem: %v", err)
}
tlsCertsSecret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string][]byte{
"cert.pem": cert,
"key.pem": key,
"bearer-token": []byte("abc"),
},
}
if _, err := framework.KubeClient.CoreV1().Secrets(ns).Create(tlsCertsSecret); err != nil {
t.Fatal(err)
}
prometheusCRD := framework.MakeBasicPrometheus(ns, name, name, 1)
prometheusCRD.Namespace = ns
prometheusCRD.Spec.ArbitraryFSAccessThroughSMs = test.arbitraryFSAccessThroughSMsConfig
if _, err := framework.CreatePrometheusAndWaitUntilReady(ns, prometheusCRD); err != nil {
t.Fatal(err)
}
svc := framework.MakePrometheusService(prometheusCRD.Name, name, v1.ServiceTypeClusterIP)
if _, err := testFramework.CreateServiceAndWaitUntilReady(framework.KubeClient, ns, svc); err != nil {
t.Fatal(err)
}
s := framework.MakeBasicServiceMonitor(name)
s.Spec.Endpoints[0] = test.endpoint
if _, err := framework.MonClientV1.ServiceMonitors(ns).Create(s); err != nil {
t.Fatal("creating ServiceMonitor failed: ", err)
}
if test.expectTargets {
if err := framework.WaitForTargets(ns, svc.Name, 1); err != nil {
t.Fatal(err)
}
return
}
// Make sure Prometheus has enough time to reload.
time.Sleep(2 * time.Minute)
if err := framework.WaitForTargets(ns, svc.Name, 0); err != nil {
t.Fatal(err)
}
})
}
}
// testPromTLSConfigViaSecret tests the service monitor endpoint option to load
// certificate assets via Kubernetes secrets into the Prometheus container.
func testPromTLSConfigViaSecret(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)
name := "test"
//
// Setup sample app.
//
cert, err := ioutil.ReadFile("../../test/instrumented-sample-app/certs/cert.pem")
if err != nil {
return fmt.Errorf("waiting for Prometheus to discover targets failed: %v: %v", err, loopErr)
t.Fatalf("failed to load cert.pem: %v", err)
}
return nil
}
type resultVector struct {
Metric map[string]string `json:"metric"`
Value []interface{} `json:"value"`
}
type queryResult struct {
ResultType string `json:"resultType"`
Result []*resultVector `json:"result"`
}
type prometheusQueryAPIResponse struct {
Status string `json:"status"`
Data *queryResult `json:"data"`
}
func basicQueryWorking(ns, svcName string) (bool, error) {
response, err := framework.QueryPrometheusSVC(ns, svcName, "/api/v1/query", map[string]string{"query": "up"})
key, err := ioutil.ReadFile("../../test/instrumented-sample-app/certs/key.pem")
if err != nil {
return false, err
t.Fatalf("failed to load key.pem: %v", err)
}
rq := prometheusQueryAPIResponse{}
if err := json.NewDecoder(bytes.NewBuffer(response)).Decode(&rq); err != nil {
return false, err
tlsCertsSecret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string][]byte{
"cert.pem": cert,
"key.pem": key,
},
}
if rq.Status != "success" && rq.Data.Result[0].Value[1] == "1" {
log.Printf("Query Response not successful.")
return false, nil
if _, err := framework.KubeClient.CoreV1().Secrets(ns).Create(tlsCertsSecret); err != nil {
t.Fatal(err)
}
return true, nil
simple, err := testFramework.MakeDeployment("../../test/framework/ressources/basic-auth-app-deployment.yaml")
if err != nil {
t.Fatal(err)
}
simple.Spec.Template.Spec.Containers[0].Args = []string{"--cert-path=/etc/certs"}
simple.Spec.Template.Spec.Volumes = []v1.Volume{
v1.Volume{
Name: "tls-certs",
VolumeSource: v1.VolumeSource{
Secret: &v1.SecretVolumeSource{
SecretName: tlsCertsSecret.Name,
},
},
},
}
simple.Spec.Template.Spec.Containers[0].VolumeMounts = []v1.VolumeMount{
{
Name: simple.Spec.Template.Spec.Volumes[0].Name,
MountPath: "/etc/certs",
},
}
if err := testFramework.CreateDeployment(framework.KubeClient, ns, simple); err != nil {
t.Fatal("Creating simple basic auth app failed: ", err)
}
svc := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: map[string]string{
"group": name,
},
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
Ports: []v1.ServicePort{
v1.ServicePort{
Name: "web",
Port: 8080,
},
v1.ServicePort{
Name: "mtls",
Port: 8081,
},
},
Selector: map[string]string{
"group": name,
},
},
}
if _, err := testFramework.CreateServiceAndWaitUntilReady(framework.KubeClient, ns, svc); err != nil {
t.Fatal(err)
}
//
// Setup monitoring.
//
sm := framework.MakeBasicServiceMonitor(name)
sm.Spec.Endpoints = []monitoringv1.Endpoint{
{
Port: "mtls",
Interval: "30s",
Scheme: "https",
TLSConfig: &monitoringv1.TLSConfig{
InsecureSkipVerify: true,
CASecret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: tlsCertsSecret.Name,
},
Key: "cert.pem",
},
CertSecret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: tlsCertsSecret.Name,
},
Key: "cert.pem",
},
KeySecret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: tlsCertsSecret.Name,
},
Key: "key.pem",
},
},
},
}
if _, err := framework.MonClientV1.ServiceMonitors(ns).Create(sm); err != nil {
t.Fatal("creating ServiceMonitor failed: ", err)
}
prometheusCRD := framework.MakeBasicPrometheus(ns, name, name, 1)
if _, err := framework.CreatePrometheusAndWaitUntilReady(ns, prometheusCRD); err != nil {
t.Fatal(err)
}
promSVC := framework.MakePrometheusService(prometheusCRD.Name, name, v1.ServiceTypeClusterIP)
if _, err := testFramework.CreateServiceAndWaitUntilReady(framework.KubeClient, ns, promSVC); err != nil {
t.Fatal(err)
}
//
// Check for proper scraping.
//
if err := framework.WaitForTargets(ns, promSVC.Name, 1); err != nil {
t.Fatal(err)
}
// TODO: Do a poll instead, should speed up things.
time.Sleep(30 * time.Second)
response, err := framework.PrometheusSVCGetRequest(
ns,
promSVC.Name,
"/api/v1/query",
map[string]string{"query": fmt.Sprintf(`up{job="%v",endpoint="%v"}`, name, sm.Spec.Endpoints[0].Port)},
)
if err != nil {
t.Fatal(err)
}
q := testFramework.PrometheusQueryAPIResponse{}
if err := json.NewDecoder(bytes.NewBuffer(response)).Decode(&q); err != nil {
t.Fatal(err)
}
if q.Status != "success" {
t.Fatalf("expected query status to be 'success' but got %v", q.Status)
}
if q.Data.Result[0].Value[1] != "1" {
t.Fatalf("expected query result to be '1' but got %v", q.Data.Result[0].Value[1])
}
}
func isAlertmanagerDiscoveryWorking(ns, promSVCName, alertmanagerName string) func() (bool, error) {
@ -1696,7 +2100,7 @@ func isAlertmanagerDiscoveryWorking(ns, promSVCName, alertmanagerName string) fu
expectedAlertmanagerTargets = append(expectedAlertmanagerTargets, fmt.Sprintf("http://%s:9093/api/v1/alerts", p.Status.PodIP))
}
response, err := framework.QueryPrometheusSVC(ns, promSVCName, "/api/v1/alertmanagers", map[string]string{})
response, err := framework.PrometheusSVCGetRequest(ns, promSVCName, "/api/v1/alertmanagers", map[string]string{})
if err != nil {
return false, err
}
@ -1714,26 +2118,6 @@ func isAlertmanagerDiscoveryWorking(ns, promSVCName, alertmanagerName string) fu
}
}
func assertExpectedTargets(targets []*testFramework.Target, expectedTargets []string) error {
existingTargets := []string{}
for _, t := range targets {
existingTargets = append(existingTargets, t.ScrapeURL)
}
sort.Strings(expectedTargets)
sort.Strings(existingTargets)
if !reflect.DeepEqual(expectedTargets, existingTargets) {
return fmt.Errorf(
"expected targets %q but got %q", strings.Join(expectedTargets, ","),
strings.Join(existingTargets, ","),
)
}
return nil
}
func assertExpectedAlertmanagerTargets(ams []*alertmanagerTarget, expectedTargets []string) bool {
log.Printf("Expected Alertmanager Targets: %#+v\n", expectedTargets)

View file

@ -22,12 +22,16 @@ import (
)
func CreateClusterRoleBinding(kubeClient kubernetes.Interface, ns string, relativePath string) (finalizerFn, error) {
finalizerFn := func() error { return DeleteClusterRoleBinding(kubeClient, relativePath) }
finalizerFn := func() error { return DeleteClusterRoleBinding(kubeClient, ns, relativePath) }
clusterRoleBinding, err := parseClusterRoleBindingYaml(relativePath)
if err != nil {
return finalizerFn, err
}
// Make sure to create a new cluster role binding for each namespace to
// prevent concurrent tests to delete each others bindings.
clusterRoleBinding.Name = ns + "-" + clusterRoleBinding.Name
clusterRoleBinding.Subjects[0].Namespace = ns
_, err = kubeClient.RbacV1().ClusterRoleBindings().Get(clusterRoleBinding.Name, metav1.GetOptions{})
@ -49,12 +53,16 @@ func CreateClusterRoleBinding(kubeClient kubernetes.Interface, ns string, relati
return finalizerFn, err
}
func DeleteClusterRoleBinding(kubeClient kubernetes.Interface, relativePath string) error {
func DeleteClusterRoleBinding(kubeClient kubernetes.Interface, ns string, relativePath string) error {
clusterRoleBinding, err := parseClusterRoleYaml(relativePath)
if err != nil {
return err
}
// Make sure to delete the specific cluster role binding for the namespace
// it was created preventing concurrent tests to delete each others bindings.
clusterRoleBinding.Name = ns + "-" + clusterRoleBinding.Name
return kubeClient.RbacV1().ClusterRoleBindings().Delete(clusterRoleBinding.Name, &metav1.DeleteOptions{})
}

View file

@ -40,12 +40,12 @@ func MakeDeployment(pathToYaml string) (*appsv1.Deployment, error) {
if err != nil {
return nil, err
}
tectonicPromOp := appsv1.Deployment{}
if err := yaml.NewYAMLOrJSONDecoder(manifest, 100).Decode(&tectonicPromOp); err != nil {
deployment := appsv1.Deployment{}
if err := yaml.NewYAMLOrJSONDecoder(manifest, 100).Decode(&deployment); err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("failed to decode file %s", pathToYaml))
}
return &tectonicPromOp, nil
return &deployment, nil
}
func CreateDeployment(kubeClient kubernetes.Interface, namespace string, d *appsv1.Deployment) error {

View file

@ -364,7 +364,7 @@ func (ctx *TestCtx) SetupPrometheusRBACGlobal(t *testing.T, ns string, kubeClien
ctx.AddFinalizerFn(finalizerFn)
}
if finalizerFn, err := CreateClusterRoleBinding(kubeClient, ns, "../../example/rbac/prometheus/prometheus-cluster-role-binding.yaml"); err != nil {
if finalizerFn, err := CreateClusterRoleBinding(kubeClient, ns, "../../example/rbac/prometheus/prometheus-cluster-role-binding.yaml"); err != nil && !apierrors.IsAlreadyExists(err) {
t.Fatal(errors.Wrap(err, "failed to create prometheus cluster role binding"))
} else {
ctx.AddFinalizerFn(finalizerFn)

View file

@ -18,6 +18,9 @@ import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"sort"
"strings"
"time"
v1 "k8s.io/api/core/v1"
@ -252,14 +255,94 @@ func (f *Framework) WaitForTargets(ns, svcName string, amount int) error {
return nil
}
func (f *Framework) QueryPrometheusSVC(ns, svcName, endpoint string, query map[string]string) ([]byte, error) {
func (f *Framework) WaitForDiscoveryWorking(ns, svcName, prometheusName string) error {
var loopErr error
err := wait.Poll(time.Second, 5*f.DefaultTimeout, func() (bool, error) {
pods, loopErr := f.KubeClient.CoreV1().Pods(ns).List(prometheus.ListOptions(prometheusName))
if loopErr != nil {
return false, loopErr
}
if 1 != len(pods.Items) {
return false, nil
}
podIP := pods.Items[0].Status.PodIP
expectedTargets := []string{fmt.Sprintf("http://%s:9090/metrics", podIP)}
activeTargets, loopErr := f.GetActiveTargets(ns, svcName)
if loopErr != nil {
return false, loopErr
}
if loopErr = assertExpectedTargets(activeTargets, expectedTargets); loopErr != nil {
return false, nil
}
working, loopErr := f.basicQueryWorking(ns, svcName)
if loopErr != nil {
return false, loopErr
}
if !working {
return false, nil
}
return true, nil
})
if err != nil {
return fmt.Errorf("waiting for Prometheus to discover targets failed: %v: %v", err, loopErr)
}
return nil
}
func (f *Framework) basicQueryWorking(ns, svcName string) (bool, error) {
response, err := f.PrometheusSVCGetRequest(ns, svcName, "/api/v1/query", map[string]string{"query": "up"})
if err != nil {
return false, err
}
rq := PrometheusQueryAPIResponse{}
if err := json.NewDecoder(bytes.NewBuffer(response)).Decode(&rq); err != nil {
return false, err
}
if rq.Status != "success" && rq.Data.Result[0].Value[1] == "1" {
fmt.Printf("Query Response not successful.")
return false, nil
}
return true, nil
}
func assertExpectedTargets(targets []*Target, expectedTargets []string) error {
existingTargets := []string{}
for _, t := range targets {
existingTargets = append(existingTargets, t.ScrapeURL)
}
sort.Strings(expectedTargets)
sort.Strings(existingTargets)
if !reflect.DeepEqual(expectedTargets, existingTargets) {
return fmt.Errorf(
"expected targets %q but got %q", strings.Join(expectedTargets, ","),
strings.Join(existingTargets, ","),
)
}
return nil
}
func (f *Framework) PrometheusSVCGetRequest(ns, svcName, endpoint string, query map[string]string) ([]byte, error) {
ProxyGet := f.KubeClient.CoreV1().Services(ns).ProxyGet
request := ProxyGet("", svcName, "web", endpoint, query)
return request.DoRaw()
}
func (f *Framework) GetActiveTargets(ns, svcName string) ([]*Target, error) {
response, err := f.QueryPrometheusSVC(ns, svcName, "/api/v1/targets", map[string]string{})
response, err := f.PrometheusSVCGetRequest(ns, svcName, "/api/v1/targets", map[string]string{})
if err != nil {
return nil, err
}
@ -273,7 +356,7 @@ func (f *Framework) GetActiveTargets(ns, svcName string) ([]*Target, error) {
}
func (f *Framework) CheckPrometheusFiringAlert(ns, svcName, alertName string) (bool, error) {
response, err := f.QueryPrometheusSVC(
response, err := f.PrometheusSVCGetRequest(
ns,
svcName,
"/api/v1/query",
@ -283,7 +366,7 @@ func (f *Framework) CheckPrometheusFiringAlert(ns, svcName, alertName string) (b
return false, err
}
q := prometheusQueryAPIResponse{}
q := PrometheusQueryAPIResponse{}
if err := json.NewDecoder(bytes.NewBuffer(response)).Decode(&q); err != nil {
return false, err
}
@ -334,15 +417,17 @@ type prometheusTargetAPIResponse struct {
Data *targetDiscovery `json:"data"`
}
type prometheusQueryResult struct {
type PrometheusQueryResult struct {
Metric map[string]string `json:"metric"`
Value []interface{} `json:"value"`
}
type prometheusQueryData struct {
Result []prometheusQueryResult `json:"result"`
type PrometheusQueryData struct {
ResultType string `json:"resultType"`
Result []PrometheusQueryResult `json:"result"`
}
type prometheusQueryAPIResponse struct {
type PrometheusQueryAPIResponse struct {
Status string `json:"status"`
Data *prometheusQueryData `json:"data"`
Data *PrometheusQueryData `json:"data"`
}

View file

@ -1,7 +1,7 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: basic-auth-test-app
name: instrumented-sample-app
labels:
group: test
spec:
@ -16,8 +16,10 @@ spec:
spec:
containers:
- name: example-app
image: quay.io/coreos/basic-auth-test-app:0.1.0
image: quay.io/mxinden/instrumented-sample-app:0.2.0-bearer-mtls-1
imagePullPolicy: IfNotPresent
ports:
- name: web
containerPort: 8080
- name: mtls
containerPort: 8081

View file

@ -0,0 +1 @@
certs

View file

@ -0,0 +1,22 @@
REG := quay.io/mxinden
APP := instrumented-sample-app
VERSION ?= $(shell cat VERSION)
all: build push
build:
@GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -mod=vendor -o instrumented-sample-app main.go
@docker build --build-arg VERSION=$(VERSION) -t $(REG)/$(APP):$(VERSION) --file $(APP).dockerfile .
@rm $(APP)
push:
@docker push $(REG)/$(APP):$(VERSION)
generate-certs:
mkdir -p certs && \
openssl req -newkey rsa:2048 \
-new -nodes -x509 \
-days 3650 \
-out certs/cert.pem \
-keyout certs/key.pem \
-subj "/C=US/ST=California/L=Mountain View/O=Your Organization/OU=Your Unit/CN=localhost"

View file

@ -0,0 +1 @@
0.2.0-bearer-mtls-1

View file

@ -0,0 +1,8 @@
FROM alpine:3.7
ARG VERSION="$VERSION"
ENV VERSION="$VERSION"
COPY instrumented-sample-app /
ENTRYPOINT ["/instrumented-sample-app"]

View file

@ -0,0 +1,140 @@
// Copyright 2016 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 main
import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"path"
"strings"
"time"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
certPath = flag.String("cert-path", "", "path to tls certificates for mutual TLS endpoint")
)
func main() {
flag.Parse()
if *certPath != "" {
go func() {
log.Fatal(mTLSEndpoint())
}()
}
http.HandleFunc("/", handler)
http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
if checkBasicAuth(w, r) {
promhttp.Handler().ServeHTTP(w, r)
return
}
w.Header().Set("WWW-Authenticate", `Basic realm="MY REALM"`)
w.WriteHeader(401)
w.Write([]byte("401 Unauthorized\n"))
})
http.HandleFunc("/bearer-metrics", func(w http.ResponseWriter, r *http.Request) {
if checkBearerAuth(w, r) {
promhttp.Handler().ServeHTTP(w, r)
return
}
w.Header().Set("WWW-Authenticate", `Bearer realm="MY REALM"`)
w.WriteHeader(401)
w.Write([]byte("401 Unauthorized\n"))
})
address := ":8080"
fmt.Printf("listening for metric requests on '%v' protected via basic auth or bearer token\n", address)
http.ListenAndServe(address, nil)
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, time.Now().String())
fmt.Fprintf(w, "\nAppVersion:"+os.Getenv("VERSION"))
}
func checkBasicAuth(w http.ResponseWriter, r *http.Request) bool {
s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
if len(s) != 2 {
return false
}
b, err := base64.StdEncoding.DecodeString(s[1])
if err != nil {
return false
}
pair := strings.SplitN(string(b), ":", 2)
if len(pair) != 2 {
return false
}
return pair[0] == "user" && pair[1] == "pass"
}
func checkBearerAuth(w http.ResponseWriter, r *http.Request) bool {
s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
if len(s) != 2 {
return false
}
fmt.Println(s[1])
return s[1] == "abc"
}
func mTLSEndpoint() error {
certPool := x509.NewCertPool()
pem, err := ioutil.ReadFile(path.Join(*certPath, "cert.pem"))
if err != nil {
return fmt.Errorf("failed to load certificate authority")
}
ok := certPool.AppendCertsFromPEM(pem)
if !ok {
return fmt.Errorf("failed to add certificate authority to certificate pool")
}
tlsConfig := &tls.Config{
ClientCAs: certPool,
ClientAuth: tls.RequireAndVerifyClientCert,
}
tlsConfig.BuildNameToCertificate()
address := ":8081"
server := &http.Server{
Addr: address,
TLSConfig: tlsConfig,
Handler: promhttp.Handler(),
}
fmt.Printf("listening for metric requests on '%v' protected via mutual tls\n", address)
return server.ListenAndServeTLS(path.Join(*certPath, "cert.pem"), path.Join(*certPath, "key.pem"))
}