Merge pull request #597 from fluxcd/kube-tls-secret

Adopt Kubernetes style TLS Secret
This commit is contained in:
Stefan Prodan 2023-08-23 16:38:09 +03:00 committed by GitHub
commit c04ad842fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 216 additions and 14 deletions

View File

@ -103,8 +103,11 @@ type ProviderSpec struct {
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"` SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
// CertSecretRef specifies the Secret containing // CertSecretRef specifies the Secret containing
// a PEM-encoded CA certificate (`caFile`). // a PEM-encoded CA certificate (in the `ca.crt` key).
// +optional // +optional
//
// Note: Support for the `caFile` key has
// been deprecated.
CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"` CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`
// Suspend tells the controller to suspend subsequent // Suspend tells the controller to suspend subsequent

View File

@ -236,8 +236,9 @@ spec:
maxLength: 2048 maxLength: 2048
type: string type: string
certSecretRef: certSecretRef:
description: CertSecretRef specifies the Secret containing a PEM-encoded description: "CertSecretRef specifies the Secret containing a PEM-encoded
CA certificate (`caFile`). CA certificate (in the `ca.crt` key). \n Note: Support for the `caFile`
key has been deprecated."
properties: properties:
name: name:
description: Name of the referent. description: Name of the referent.

View File

@ -372,7 +372,9 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
<td> <td>
<em>(Optional)</em> <em>(Optional)</em>
<p>CertSecretRef specifies the Secret containing <p>CertSecretRef specifies the Secret containing
a PEM-encoded CA certificate (<code>caFile</code>).</p> a PEM-encoded CA certificate (in the <code>ca.crt</code> key).</p>
<p>Note: Support for the <code>caFile</code> key has
been deprecated.</p>
</td> </td>
</tr> </tr>
<tr> <tr>
@ -964,7 +966,9 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
<td> <td>
<em>(Optional)</em> <em>(Optional)</em>
<p>CertSecretRef specifies the Secret containing <p>CertSecretRef specifies the Secret containing
a PEM-encoded CA certificate (<code>caFile</code>).</p> a PEM-encoded CA certificate (in the <code>ca.crt</code> key).</p>
<p>Note: Support for the <code>caFile</code> key has
been deprecated.</p>
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@ -1095,11 +1095,12 @@ stringData:
`.spec.certSecretRef` is an optional field to specify a name reference to a `.spec.certSecretRef` is an optional field to specify a name reference to a
Secret in the same namespace as the Provider, containing the TLS CA certificate. Secret in the same namespace as the Provider, containing the TLS CA certificate.
The secret must be of type `kubernetes.io/tls` or `Opaque`.
#### Example #### Example
To enable notification-controller to communicate with a provider API over HTTPS To enable notification-controller to communicate with a provider API over HTTPS
using a self-signed TLS certificate, set the `caFile` like so: using a self-signed TLS certificate, set the `ca.crt` like so:
```yaml ```yaml
--- ---
@ -1119,11 +1120,16 @@ kind: Secret
metadata: metadata:
name: my-ca-crt name: my-ca-crt
namespace: default namespace: default
type: kubernetes.io/tls # or Opaque
stringData: stringData:
caFile: | ca.crt: |
<--- CA Key ---> <--- CA Key --->
``` ```
**Warning:** Support for the `caFile` key has been
deprecated. If you have any Secrets using this key,
the controller will log a deprecation warning.
### HTTP/S proxy ### HTTP/S proxy
`.spec.proxy` is an optional field to specify an HTTP/S proxy address. `.spec.proxy` is an optional field to specify an HTTP/S proxy address.

View File

@ -178,7 +178,7 @@ func (r *AlertReconciler) isProviderReady(ctx context.Context, alert *apiv1beta2
providerName := types.NamespacedName{Namespace: alert.Namespace, Name: alert.Spec.ProviderRef.Name} providerName := types.NamespacedName{Namespace: alert.Namespace, Name: alert.Spec.ProviderRef.Name}
if err := r.Get(ctx, providerName, provider); err != nil { if err := r.Get(ctx, providerName, provider); err != nil {
// log not found errors since they get filtered out // log not found errors since they get filtered out
ctrl.LoggerFrom(ctx).Error(err, "failed to get provider %s", providerName.String()) ctrl.LoggerFrom(ctx).Error(err, "failed to get provider", "provider", providerName.String())
return fmt.Errorf("failed to get provider '%s': %w", providerName.String(), err) return fmt.Errorf("failed to get provider '%s': %w", providerName.String(), err)
} }

View File

@ -190,6 +190,8 @@ func (r *ProviderReconciler) validateURLs(provider *apiv1beta2.Provider) error {
} }
func (r *ProviderReconciler) validateCredentials(ctx context.Context, provider *apiv1beta2.Provider) error { func (r *ProviderReconciler) validateCredentials(ctx context.Context, provider *apiv1beta2.Provider) error {
log := ctrl.LoggerFrom(ctx)
address := provider.Spec.Address address := provider.Spec.Address
proxy := provider.Spec.Proxy proxy := provider.Spec.Proxy
username := provider.Spec.Username username := provider.Spec.Username
@ -245,9 +247,19 @@ func (r *ProviderReconciler) validateCredentials(ctx context.Context, provider *
return fmt.Errorf("failed to read secret, error: %w", err) return fmt.Errorf("failed to read secret, error: %w", err)
} }
caFile, ok := secret.Data["caFile"] switch secret.Type {
case corev1.SecretTypeOpaque, corev1.SecretTypeTLS, "":
default:
return fmt.Errorf("cannot use secret '%s' to get TLS certificate: invalid secret type: '%s'", secret.Name, secret.Type)
}
caFile, ok := secret.Data["ca.crt"]
if !ok { if !ok {
return fmt.Errorf("no caFile found in secret %s", provider.Spec.CertSecretRef.Name) caFile, ok = secret.Data["caFile"]
if !ok {
return fmt.Errorf("no 'ca.crt' key found in secret '%s'", provider.Spec.CertSecretRef.Name)
}
log.Info("warning: specifying CA cert via 'caFile' is deprecated, please use 'ca.crt' instead")
} }
certPool = x509.NewCertPool() certPool = x509.NewCertPool()

View File

@ -19,6 +19,7 @@ package controller
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"testing" "testing"
"time" "time"
@ -237,3 +238,99 @@ func TestProviderReconciler_Reconcile(t *testing.T) {
}, timeout, time.Second).Should(BeTrue()) }, timeout, time.Second).Should(BeTrue())
}) })
} }
func TestProviderReconciler_Reconcile_cacert(t *testing.T) {
g := NewWithT(t)
namespaceName := "provider-" + randStringRunes(5)
secretName := "ca-secret-" + randStringRunes(5)
caCrt, err := os.ReadFile("./testdata/certs/ca.pem")
g.Expect(err).To(Not(HaveOccurred()))
g.Expect(createNamespace(namespaceName)).NotTo(HaveOccurred(), "failed to create test namespace")
providerKey := types.NamespacedName{
Name: fmt.Sprintf("provider-%s", randStringRunes(5)),
Namespace: namespaceName,
}
provider := &apiv1beta2.Provider{
ObjectMeta: metav1.ObjectMeta{
Name: providerKey.Name,
Namespace: providerKey.Namespace,
},
Spec: apiv1beta2.ProviderSpec{
Type: "generic",
Address: "https://webhook.internal",
CertSecretRef: &meta.LocalObjectReference{Name: secretName},
},
}
g.Expect(k8sClient.Create(context.Background(), provider)).To(Succeed())
certSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: providerKey.Namespace,
},
Data: map[string][]byte{
"caFile": []byte("invalid byte"),
"ca.crt": caCrt,
},
}
g.Expect(k8sClient.Create(context.Background(), certSecret)).To(Succeed())
r := &ProviderReconciler{
Client: k8sClient,
EventRecorder: record.NewFakeRecorder(32),
}
t.Run("uses `ca.crt` instead of deprecated `caFile`", func(t *testing.T) {
g := NewWithT(t)
_, err = r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(provider)})
g.Expect(err).NotTo(HaveOccurred())
})
t.Run("works if only deprecated `caFile` is specified", func(t *testing.T) {
g := NewWithT(t)
clusterCertSecret := &corev1.Secret{}
g.Expect(k8sClient.Get(context.Background(), client.ObjectKeyFromObject(certSecret), clusterCertSecret)).To(Succeed())
patchHelper, err := patch.NewHelper(clusterCertSecret, k8sClient)
g.Expect(err).ToNot(HaveOccurred())
clusterCertSecret.Data = map[string][]byte{
"caFile": caCrt,
}
g.Expect(patchHelper.Patch(context.Background(), clusterCertSecret)).ToNot(HaveOccurred())
_, err = r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(provider)})
g.Expect(err).NotTo(HaveOccurred())
})
t.Run("returns error with certSecretRef of the wrong type", func(t *testing.T) {
g := NewWithT(t)
dockerSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "docker-secret",
Namespace: providerKey.Namespace,
},
Type: corev1.DockerConfigJsonKey,
}
g.Expect(k8sClient.Create(context.Background(), dockerSecret)).To(Succeed())
clusterProvider := &apiv1beta2.Provider{}
g.Expect(k8sClient.Get(context.Background(), client.ObjectKeyFromObject(provider), clusterProvider)).To(Succeed())
patchHelper, err := patch.NewHelper(clusterProvider, k8sClient)
g.Expect(err).ToNot(HaveOccurred())
clusterProvider.Spec.CertSecretRef = &meta.LocalObjectReference{
Name: dockerSecret.Name,
}
g.Expect(patchHelper.Patch(context.Background(), clusterProvider)).ToNot(HaveOccurred())
_, err = r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(provider)})
g.Expect(err).To(HaveOccurred())
g.Expect(err.Error()).To(ContainSubstring("invalid secret type"))
})
}

View File

@ -0,0 +1,20 @@
# Copyright 2023 The Flux 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.
all: ca-key.pem
ca-key.pem: ca-csr.json
cfssl gencert -initca ca-csr.json | cfssljson -bare ca
ca.pem: ca-key.pem
ca.csr: ca-key.pem

View File

@ -0,0 +1,13 @@
{
"signing": {
"default": {
"expiry": "8760h"
},
"profiles": {
"kubernetes": {
"usages": ["signing", "key encipherment", "server auth", "client auth"],
"expiry": "8760h"
}
}
}
}

View File

@ -0,0 +1,9 @@
{
"CN": "example.com CA",
"hosts": [
"127.0.0.1",
"localhost",
"example.com",
"www.example.com"
]
}

View File

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIEY+R5pZqRQUvD2wrbL4HFl5IKFY84FEGHG5GHW1EojroAoGCCqGSM49
AwEHoUQDQgAEDqdWaSYf2JT7L90ywnuhz2BS4zhy+v5juPLpBI0Indo8mHpOw9m+
LCnMzN2WkcUYt+XLQwhaNNt0RBlfnXRzhw==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,9 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBHzCBxgIBADAZMRcwFQYDVQQDEw5leGFtcGxlLmNvbSBDQTBZMBMGByqGSM49
AgEGCCqGSM49AwEHA0IABA6nVmkmH9iU+y/dMsJ7oc9gUuM4cvr+Y7jy6QSNCJ3a
PJh6TsPZviwpzMzdlpHFGLfly0MIWjTbdEQZX510c4egSzBJBgkqhkiG9w0BCQ4x
PDA6MDgGA1UdEQQxMC+CCWxvY2FsaG9zdIILZXhhbXBsZS5jb22CD3d3dy5leGFt
cGxlLmNvbYcEfwAAATAKBggqhkjOPQQDAgNIADBFAiEAl0UiOdxlEcuKrNAGh/Pv
CFxMyX5shaeAsdGvq/gyXckCIFlTwheOJZZVRRQl9b0l5LUVeJyIH6mnvitFGyQ7
NRk5
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE-----
MIIBhzCCAS2gAwIBAgIUXe+UdziaK3dAdwRgwpWa0XPkKLIwCgYIKoZIzj0EAwIw
GTEXMBUGA1UEAxMOZXhhbXBsZS5jb20gQ0EwHhcNMjMwODE2MTcwMDAwWhcNMjgw
ODE0MTcwMDAwWjAZMRcwFQYDVQQDEw5leGFtcGxlLmNvbSBDQTBZMBMGByqGSM49
AgEGCCqGSM49AwEHA0IABA6nVmkmH9iU+y/dMsJ7oc9gUuM4cvr+Y7jy6QSNCJ3a
PJh6TsPZviwpzMzdlpHFGLfly0MIWjTbdEQZX510c4ejUzBRMA4GA1UdDwEB/wQE
AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRSCe96Yb+o/q3kuqBp5IMs
uD9N/DAPBgNVHREECDAGhwR/AAABMAoGCCqGSM49BAMCA0gAMEUCIBw52JoEQ/0Z
Bnz4gXlXLXtVX0C4LvcNohdlwSRGHPlYAiEAnVqcT2kxBs2+E3SqJPU3DUM7ZFOO
n3zfiIVinQNlXPY=
-----END CERTIFICATE-----

View File

@ -205,16 +205,28 @@ func (s *EventServer) handleEvent() func(w http.ResponseWriter, r *http.Request)
continue continue
} }
caFile, ok := secret.Data["caFile"] switch secret.Type {
if !ok { case corev1.SecretTypeOpaque, corev1.SecretTypeTLS, "":
alertLogger.Error(err, "failed to read secret key caFile") default:
alertLogger.Error(nil, "cannot use secret '%s' to get TLS certificate: invalid secret type: '%s'",
secret.Name, secret.Type)
continue continue
} }
caFile, ok := secret.Data["ca.crt"]
if !ok {
caFile, ok = secret.Data["caFile"]
if !ok {
alertLogger.Error(nil, "no 'ca.crt' key found in secret '%s'", provider.Spec.CertSecretRef.Name)
continue
}
alertLogger.Info("warning: specifying CA cert via 'caFile' is deprecated, please use 'ca.crt' instead")
}
certPool = x509.NewCertPool() certPool = x509.NewCertPool()
ok = certPool.AppendCertsFromPEM(caFile) ok = certPool.AppendCertsFromPEM(caFile)
if !ok { if !ok {
alertLogger.Error(err, "could not append to cert pool") alertLogger.Error(nil, "could not append to cert pool")
continue continue
} }
} }