Skip to content

Commit 372f4a8

Browse files
authored
Add TLS configuration to Recommender http client (#252)
* Add TLS configuration to Recommender http client This allows to set client certificate along with server CA allowing the recommender server to implement mTLS and validate client certificate. Note that this will work only with a default client implementing an http.Transport. Any other RoundTripper will need to implement their own TLS implementation. The implementation here reloads the certificate and private key for every request to a given recommender endpoint. This is to support systems where certificate can be rotated from time to time. * Implement certificate caching To prevent reloading the certificate and key from disk at every new HTTPs connection we cache the certificate in a central cache (up until it is either expired or too old). The cache is trimmed every 10 minutes to remove stale entries (not accessed after 10 min). * Supports setting wpa default TLS config for the recommender API For environment where certificates can be the same for all WPA resources, it might be cumbersome to specify the same client certificate in all the recommender specs. By using the `--tls-*` option it is possible to set default CA, certificate and key files (along with a few other TLS options) that will be used when the recommender specs don't contain such information. * Retry if certificate & key loaded don't match For environments where client certificate loaded from disks are renewed it is very possible that the certificate generator can't write both files atomically. More care should be used when reading so that if the certificate and the key don't match, reading is retried until there's either a match or a failure. * Declare the recommender tls_config in the CRD * Refactor tests for a bit more readability * Review fixes * Remove LRU mentions as the cache is not an LRU cache * Cache errors for 1 min to prevent busy looping reading invalid certs * Review fix: reorder TLS functions So that types and methods are close together. * Regenerate all bundle and manifests
1 parent 8b765f5 commit 372f4a8

File tree

11 files changed

+900
-14
lines changed

11 files changed

+900
-14
lines changed

apis/datadoghq/v1alpha1/watermarkpodautoscaler_types.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,38 @@ type WatermarkPodAutoscalerSpec struct {
132132
ReadinessDelaySeconds int32 `json:"readinessDelaySeconds,omitempty"`
133133
}
134134

135+
// TLSConfig specifies the Recommender http client TLS configuration
136+
// +k8s:openapi-gen=true
137+
type TLSConfig struct {
138+
// CAFile is a path to a CA certificate
139+
// +optional
140+
CAFile string `json:"caFile,omitempty"`
141+
142+
// CertFile is a path to a client Cert
143+
// +optional
144+
CertFile string `json:"certFile,omitempty"`
145+
146+
// Keyfile is a path to the client certificate key (mandatory if CertFile is set)
147+
// +optional
148+
KeyFile string `json:"keyFile,omitempty"`
149+
150+
// ServerName is a settings to activate TLS SNI
151+
ServerName string `json:"serverName,omitempty"`
152+
153+
// InsecureSkipVerify when true disable verifying server certificate
154+
InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"`
155+
156+
// MinVersion, mininum TLS version to accept for the connection. If not set will default to Go default (TLS1.2)
157+
// +optional
158+
// +kubebuilder:validation:Pattern:=`^TLS1[0-3]$`
159+
MinVersion string `json:"minVersion,omitempty"`
160+
161+
// MaxVersion, maximum TLS version to accept for the connection. If not set will default to Go default (TLS1.2)
162+
// +optional
163+
// +kubebuilder:validation:Pattern:=`^TLS1[0-3]$`
164+
MaxVersion string `json:"maxVersion,omitempty"`
165+
}
166+
135167
// RecommenderSpec indicates which recommender service to use to calculate the desired replica count
136168
//
137169
// See https://github.com/DataDog/agent-payload/pull/348 for details about the API.
@@ -141,6 +173,10 @@ type RecommenderSpec struct {
141173
// URL of the recommender service to use
142174
URL string `json:"url"`
143175

176+
// TLS Configuration for the http client allowing to set up a client certificate or server CA certificate
177+
// +optional
178+
TLSConfig *TLSConfig `json:"tlsConfig,omitempty"`
179+
144180
// Settings to pass to the recommender service
145181
// +optional
146182
Settings map[string]string `json:"settings,omitempty"`

apis/datadoghq/v1alpha1/zz_generated.deepcopy.go

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apis/datadoghq/v1alpha1/zz_generated.openapi.go

Lines changed: 70 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bundle/manifests/datadoghq.com_watermarkpodautoscalers.yaml

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ spec:
2929
- jsonPath: .status.currentMetrics[*].external.currentValue..
3030
name: value
3131
type: string
32-
- jsonPath: .spec.metrics[*].external.highWatermark..
32+
- jsonPath: .spec..highWatermark
3333
name: high watermark
3434
type: string
35-
- jsonPath: .spec.metrics[*].external.lowWatermark..
35+
- jsonPath: .spec..lowWatermark
3636
name: low watermark
3737
type: string
3838
- jsonPath: .metadata.creationTimestamp
@@ -313,6 +313,38 @@ spec:
313313
description: TargetType is the type of target the recommender
314314
service should use.
315315
type: string
316+
tlsConfig:
317+
description: TLS Configuration for the http client allowing to
318+
set up a client certificate or server CA certificate
319+
properties:
320+
caFile:
321+
description: CAFile is a path to a CA certificate
322+
type: string
323+
certFile:
324+
description: CertFile is a path to a client Cert
325+
type: string
326+
insecureSkipVerify:
327+
description: InsecureSkipVerify when true disable verifying
328+
server certificate
329+
type: boolean
330+
keyFile:
331+
description: Keyfile is a path to the client certificate key
332+
(mandatory if CertFile is set)
333+
type: string
334+
maxVersion:
335+
description: MaxVersion, maximum TLS version to accept for
336+
the connection. If not set will default to Go default (TLS1.2)
337+
pattern: ^TLS1[0-3]$
338+
type: string
339+
minVersion:
340+
description: MinVersion, mininum TLS version to accept for
341+
the connection. If not set will default to Go default (TLS1.2)
342+
pattern: ^TLS1[0-3]$
343+
type: string
344+
serverName:
345+
description: ServerName is a settings to activate TLS SNI
346+
type: string
347+
type: object
316348
url:
317349
description: URL of the recommender service to use
318350
type: string

config/crd/bases/v1/datadoghq.com_watermarkpodautoscalers.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,38 @@ spec:
313313
description: TargetType is the type of target the recommender
314314
service should use.
315315
type: string
316+
tlsConfig:
317+
description: TLS Configuration for the http client allowing to
318+
set up a client certificate or server CA certificate
319+
properties:
320+
caFile:
321+
description: CAFile is a path to a CA certificate
322+
type: string
323+
certFile:
324+
description: CertFile is a path to a client Cert
325+
type: string
326+
insecureSkipVerify:
327+
description: InsecureSkipVerify when true disable verifying
328+
server certificate
329+
type: boolean
330+
keyFile:
331+
description: Keyfile is a path to the client certificate key
332+
(mandatory if CertFile is set)
333+
type: string
334+
maxVersion:
335+
description: MaxVersion, maximum TLS version to accept for
336+
the connection. If not set will default to Go default (TLS1.2)
337+
pattern: ^TLS1[0-3]$
338+
type: string
339+
minVersion:
340+
description: MinVersion, mininum TLS version to accept for
341+
the connection. If not set will default to Go default (TLS1.2)
342+
pattern: ^TLS1[0-3]$
343+
type: string
344+
serverName:
345+
description: ServerName is a settings to activate TLS SNI
346+
type: string
347+
type: object
316348
url:
317349
description: URL of the recommender service to use
318350
type: string

config/crd/bases/v1beta1/datadoghq.com_watermarkpodautoscalers.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,33 @@ spec:
303303
targetType:
304304
description: TargetType is the type of target the recommender service should use.
305305
type: string
306+
tlsConfig:
307+
description: TLS Configuration for the http client allowing to set up a client certificate or server CA certificate
308+
properties:
309+
caFile:
310+
description: CAFile is a path to a CA certificate
311+
type: string
312+
certFile:
313+
description: CertFile is a path to a client Cert
314+
type: string
315+
insecureSkipVerify:
316+
description: InsecureSkipVerify when true disable verifying server certificate
317+
type: boolean
318+
keyFile:
319+
description: Keyfile is a path to the client certificate key (mandatory if CertFile is set)
320+
type: string
321+
maxVersion:
322+
description: MaxVersion, maximum TLS version to accept for the connection. If not set will default to Go default (TLS1.2)
323+
pattern: ^TLS1[0-3]$
324+
type: string
325+
minVersion:
326+
description: MinVersion, mininum TLS version to accept for the connection. If not set will default to Go default (TLS1.2)
327+
pattern: ^TLS1[0-3]$
328+
type: string
329+
serverName:
330+
description: ServerName is a settings to activate TLS SNI
331+
type: string
332+
type: object
306333
url:
307334
description: URL of the recommender service to use
308335
type: string

controllers/datadoghq/recommender.go

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,22 @@ type RecommenderClient interface {
4747
}
4848

4949
type RecommenderClientImpl struct {
50-
client *http.Client
50+
client *http.Client
51+
certificateCache *tlsCertificateCache
52+
options *RecommenderOptions
53+
}
54+
55+
type RecommenderOption func(*RecommenderOptions)
56+
57+
type RecommenderOptions struct {
58+
tlsConfig *v1alpha1.TLSConfig
59+
}
60+
61+
// WithTLSConfig sets a custom default TLS Config for all Recommender API requests
62+
func WithTLSConfig(tlsConfig *v1alpha1.TLSConfig) RecommenderOption {
63+
return func(option *RecommenderOptions) {
64+
option.tlsConfig = tlsConfig
65+
}
5166
}
5267

5368
type ReplicaRecommendationRequest struct {
@@ -72,23 +87,38 @@ type ReplicaRecommendationResponse struct {
7287
}
7388

7489
// NewRecommenderClient returns a new RecommenderClient with the given http.Client.
75-
func NewRecommenderClient(client *http.Client) RecommenderClient {
90+
func NewRecommenderClient(client *http.Client, options ...RecommenderOption) RecommenderClient {
7691
if client.Transport == nil {
7792
client.Transport = http.DefaultTransport
7893
}
94+
95+
recommenderSettings := &RecommenderOptions{}
96+
for _, opt := range options {
97+
opt(recommenderSettings)
98+
}
99+
79100
return &RecommenderClientImpl{
80-
client: client,
101+
client: client,
102+
certificateCache: newTLSCertificateCache(),
103+
options: recommenderSettings,
81104
}
82105
}
83106

84107
// instrumentedClient returns a copy of the client with an instrumented Transport for this recommender.
85108
//
86109
// The returned client is a shallow copy of the original client, with the Transport field replaced
87110
// with an instrumented RoundTripper (which just wraps the original Transport).
88-
func (r *RecommenderClientImpl) instrumentedClient(recommender string) *http.Client {
111+
func (r *RecommenderClientImpl) instrumentedClient(recommender string, tlsConfig *v1alpha1.TLSConfig) (*http.Client, error) {
89112
client := *r.client
113+
if transport, ok := client.Transport.(*http.Transport); ok && tlsConfig != nil {
114+
tlsTransport, err := NewCertificateReloadingTransport(tlsConfig, r.certificateCache, transport)
115+
if err != nil {
116+
return nil, fmt.Errorf("impossible to setup TLS config: %w", err)
117+
}
118+
client.Transport = tlsTransport
119+
}
90120
client.Transport = instrumentRoundTripper(recommender, client.Transport)
91-
return &client
121+
return &client, nil
92122
}
93123

94124
func instrumentRoundTripper(recommender string, transport http.RoundTripper) http.RoundTripper {
@@ -139,7 +169,10 @@ func (r *RecommenderClientImpl) GetReplicaRecommendation(request *ReplicaRecomme
139169
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
140170
defer cancel()
141171

142-
client := r.instrumentedClient(request.Recommender.URL)
172+
client, err := r.instrumentedClient(request.Recommender.URL, mergeTLSConfig(r.options.tlsConfig, request.Recommender.TLSConfig))
173+
if err != nil {
174+
return nil, fmt.Errorf("error creating http client: %w", err)
175+
}
143176

144177
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), bytes.NewReader(payload))
145178
httpReq.Header.Set("Content-Type", "application/json")

0 commit comments

Comments
 (0)