Skip to content

Commit c04ad84

Browse files
authored
Merge pull request #597 from fluxcd/kube-tls-secret
Adopt Kubernetes style TLS Secret
2 parents 1ce7d99 + 23e733b commit c04ad84

File tree

14 files changed

+216
-14
lines changed

14 files changed

+216
-14
lines changed

api/v1beta2/provider_types.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,11 @@ type ProviderSpec struct {
103103
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
104104

105105
// CertSecretRef specifies the Secret containing
106-
// a PEM-encoded CA certificate (`caFile`).
106+
// a PEM-encoded CA certificate (in the `ca.crt` key).
107107
// +optional
108+
//
109+
// Note: Support for the `caFile` key has
110+
// been deprecated.
108111
CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`
109112

110113
// Suspend tells the controller to suspend subsequent

config/crd/bases/notification.toolkit.fluxcd.io_providers.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,9 @@ spec:
236236
maxLength: 2048
237237
type: string
238238
certSecretRef:
239-
description: CertSecretRef specifies the Secret containing a PEM-encoded
240-
CA certificate (`caFile`).
239+
description: "CertSecretRef specifies the Secret containing a PEM-encoded
240+
CA certificate (in the `ca.crt` key). \n Note: Support for the `caFile`
241+
key has been deprecated."
241242
properties:
242243
name:
243244
description: Name of the referent.

docs/api/v1beta2/notification.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,9 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
372372
<td>
373373
<em>(Optional)</em>
374374
<p>CertSecretRef specifies the Secret containing
375-
a PEM-encoded CA certificate (<code>caFile</code>).</p>
375+
a PEM-encoded CA certificate (in the <code>ca.crt</code> key).</p>
376+
<p>Note: Support for the <code>caFile</code> key has
377+
been deprecated.</p>
376378
</td>
377379
</tr>
378380
<tr>
@@ -964,7 +966,9 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
964966
<td>
965967
<em>(Optional)</em>
966968
<p>CertSecretRef specifies the Secret containing
967-
a PEM-encoded CA certificate (<code>caFile</code>).</p>
969+
a PEM-encoded CA certificate (in the <code>ca.crt</code> key).</p>
970+
<p>Note: Support for the <code>caFile</code> key has
971+
been deprecated.</p>
968972
</td>
969973
</tr>
970974
<tr>

docs/spec/v1beta2/providers.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,11 +1095,12 @@ stringData:
10951095

10961096
`.spec.certSecretRef` is an optional field to specify a name reference to a
10971097
Secret in the same namespace as the Provider, containing the TLS CA certificate.
1098+
The secret must be of type `kubernetes.io/tls` or `Opaque`.
10981099

10991100
#### Example
11001101

11011102
To enable notification-controller to communicate with a provider API over HTTPS
1102-
using a self-signed TLS certificate, set the `caFile` like so:
1103+
using a self-signed TLS certificate, set the `ca.crt` like so:
11031104

11041105
```yaml
11051106
---
@@ -1119,11 +1120,16 @@ kind: Secret
11191120
metadata:
11201121
name: my-ca-crt
11211122
namespace: default
1123+
type: kubernetes.io/tls # or Opaque
11221124
stringData:
1123-
caFile: |
1125+
ca.crt: |
11241126
<--- CA Key --->
11251127
```
11261128

1129+
**Warning:** Support for the `caFile` key has been
1130+
deprecated. If you have any Secrets using this key,
1131+
the controller will log a deprecation warning.
1132+
11271133
### HTTP/S proxy
11281134

11291135
`.spec.proxy` is an optional field to specify an HTTP/S proxy address.

internal/controller/alert_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ func (r *AlertReconciler) isProviderReady(ctx context.Context, alert *apiv1beta2
178178
providerName := types.NamespacedName{Namespace: alert.Namespace, Name: alert.Spec.ProviderRef.Name}
179179
if err := r.Get(ctx, providerName, provider); err != nil {
180180
// log not found errors since they get filtered out
181-
ctrl.LoggerFrom(ctx).Error(err, "failed to get provider %s", providerName.String())
181+
ctrl.LoggerFrom(ctx).Error(err, "failed to get provider", "provider", providerName.String())
182182
return fmt.Errorf("failed to get provider '%s': %w", providerName.String(), err)
183183
}
184184

internal/controller/provider_controller.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ func (r *ProviderReconciler) validateURLs(provider *apiv1beta2.Provider) error {
190190
}
191191

192192
func (r *ProviderReconciler) validateCredentials(ctx context.Context, provider *apiv1beta2.Provider) error {
193+
log := ctrl.LoggerFrom(ctx)
194+
193195
address := provider.Spec.Address
194196
proxy := provider.Spec.Proxy
195197
username := provider.Spec.Username
@@ -245,9 +247,19 @@ func (r *ProviderReconciler) validateCredentials(ctx context.Context, provider *
245247
return fmt.Errorf("failed to read secret, error: %w", err)
246248
}
247249

248-
caFile, ok := secret.Data["caFile"]
250+
switch secret.Type {
251+
case corev1.SecretTypeOpaque, corev1.SecretTypeTLS, "":
252+
default:
253+
return fmt.Errorf("cannot use secret '%s' to get TLS certificate: invalid secret type: '%s'", secret.Name, secret.Type)
254+
}
255+
256+
caFile, ok := secret.Data["ca.crt"]
249257
if !ok {
250-
return fmt.Errorf("no caFile found in secret %s", provider.Spec.CertSecretRef.Name)
258+
caFile, ok = secret.Data["caFile"]
259+
if !ok {
260+
return fmt.Errorf("no 'ca.crt' key found in secret '%s'", provider.Spec.CertSecretRef.Name)
261+
}
262+
log.Info("warning: specifying CA cert via 'caFile' is deprecated, please use 'ca.crt' instead")
251263
}
252264

253265
certPool = x509.NewCertPool()

internal/controller/provider_controller_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package controller
1919
import (
2020
"context"
2121
"fmt"
22+
"os"
2223
"testing"
2324
"time"
2425

@@ -237,3 +238,99 @@ func TestProviderReconciler_Reconcile(t *testing.T) {
237238
}, timeout, time.Second).Should(BeTrue())
238239
})
239240
}
241+
242+
func TestProviderReconciler_Reconcile_cacert(t *testing.T) {
243+
g := NewWithT(t)
244+
namespaceName := "provider-" + randStringRunes(5)
245+
secretName := "ca-secret-" + randStringRunes(5)
246+
247+
caCrt, err := os.ReadFile("./testdata/certs/ca.pem")
248+
g.Expect(err).To(Not(HaveOccurred()))
249+
250+
g.Expect(createNamespace(namespaceName)).NotTo(HaveOccurred(), "failed to create test namespace")
251+
252+
providerKey := types.NamespacedName{
253+
Name: fmt.Sprintf("provider-%s", randStringRunes(5)),
254+
Namespace: namespaceName,
255+
}
256+
257+
provider := &apiv1beta2.Provider{
258+
ObjectMeta: metav1.ObjectMeta{
259+
Name: providerKey.Name,
260+
Namespace: providerKey.Namespace,
261+
},
262+
Spec: apiv1beta2.ProviderSpec{
263+
Type: "generic",
264+
Address: "https://webhook.internal",
265+
CertSecretRef: &meta.LocalObjectReference{Name: secretName},
266+
},
267+
}
268+
g.Expect(k8sClient.Create(context.Background(), provider)).To(Succeed())
269+
270+
certSecret := &corev1.Secret{
271+
ObjectMeta: metav1.ObjectMeta{
272+
Name: secretName,
273+
Namespace: providerKey.Namespace,
274+
},
275+
Data: map[string][]byte{
276+
"caFile": []byte("invalid byte"),
277+
"ca.crt": caCrt,
278+
},
279+
}
280+
g.Expect(k8sClient.Create(context.Background(), certSecret)).To(Succeed())
281+
282+
r := &ProviderReconciler{
283+
Client: k8sClient,
284+
EventRecorder: record.NewFakeRecorder(32),
285+
}
286+
287+
t.Run("uses `ca.crt` instead of deprecated `caFile`", func(t *testing.T) {
288+
g := NewWithT(t)
289+
_, err = r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(provider)})
290+
g.Expect(err).NotTo(HaveOccurred())
291+
})
292+
293+
t.Run("works if only deprecated `caFile` is specified", func(t *testing.T) {
294+
g := NewWithT(t)
295+
296+
clusterCertSecret := &corev1.Secret{}
297+
g.Expect(k8sClient.Get(context.Background(), client.ObjectKeyFromObject(certSecret), clusterCertSecret)).To(Succeed())
298+
299+
patchHelper, err := patch.NewHelper(clusterCertSecret, k8sClient)
300+
g.Expect(err).ToNot(HaveOccurred())
301+
clusterCertSecret.Data = map[string][]byte{
302+
"caFile": caCrt,
303+
}
304+
g.Expect(patchHelper.Patch(context.Background(), clusterCertSecret)).ToNot(HaveOccurred())
305+
306+
_, err = r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(provider)})
307+
g.Expect(err).NotTo(HaveOccurred())
308+
})
309+
310+
t.Run("returns error with certSecretRef of the wrong type", func(t *testing.T) {
311+
g := NewWithT(t)
312+
313+
dockerSecret := &corev1.Secret{
314+
ObjectMeta: metav1.ObjectMeta{
315+
Name: "docker-secret",
316+
Namespace: providerKey.Namespace,
317+
},
318+
Type: corev1.DockerConfigJsonKey,
319+
}
320+
g.Expect(k8sClient.Create(context.Background(), dockerSecret)).To(Succeed())
321+
322+
clusterProvider := &apiv1beta2.Provider{}
323+
g.Expect(k8sClient.Get(context.Background(), client.ObjectKeyFromObject(provider), clusterProvider)).To(Succeed())
324+
325+
patchHelper, err := patch.NewHelper(clusterProvider, k8sClient)
326+
g.Expect(err).ToNot(HaveOccurred())
327+
clusterProvider.Spec.CertSecretRef = &meta.LocalObjectReference{
328+
Name: dockerSecret.Name,
329+
}
330+
g.Expect(patchHelper.Patch(context.Background(), clusterProvider)).ToNot(HaveOccurred())
331+
332+
_, err = r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(provider)})
333+
g.Expect(err).To(HaveOccurred())
334+
g.Expect(err.Error()).To(ContainSubstring("invalid secret type"))
335+
})
336+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright 2023 The Flux authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
all: ca-key.pem
16+
17+
ca-key.pem: ca-csr.json
18+
cfssl gencert -initca ca-csr.json | cfssljson -bare ca –
19+
ca.pem: ca-key.pem
20+
ca.csr: ca-key.pem
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"signing": {
3+
"default": {
4+
"expiry": "8760h"
5+
},
6+
"profiles": {
7+
"kubernetes": {
8+
"usages": ["signing", "key encipherment", "server auth", "client auth"],
9+
"expiry": "8760h"
10+
}
11+
}
12+
}
13+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"CN": "example.com CA",
3+
"hosts": [
4+
"127.0.0.1",
5+
"localhost",
6+
"example.com",
7+
"www.example.com"
8+
]
9+
}

0 commit comments

Comments
 (0)