Merge pull request #597 from fluxcd/kube-tls-secret
Adopt Kubernetes style TLS Secret
This commit is contained in:
commit
c04ad842fc
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"signing": {
|
||||||
|
"default": {
|
||||||
|
"expiry": "8760h"
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"kubernetes": {
|
||||||
|
"usages": ["signing", "key encipherment", "server auth", "client auth"],
|
||||||
|
"expiry": "8760h"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"CN": "example.com CA",
|
||||||
|
"hosts": [
|
||||||
|
"127.0.0.1",
|
||||||
|
"localhost",
|
||||||
|
"example.com",
|
||||||
|
"www.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
MHcCAQEEIEY+R5pZqRQUvD2wrbL4HFl5IKFY84FEGHG5GHW1EojroAoGCCqGSM49
|
||||||
|
AwEHoUQDQgAEDqdWaSYf2JT7L90ywnuhz2BS4zhy+v5juPLpBI0Indo8mHpOw9m+
|
||||||
|
LCnMzN2WkcUYt+XLQwhaNNt0RBlfnXRzhw==
|
||||||
|
-----END EC PRIVATE KEY-----
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIIBHzCBxgIBADAZMRcwFQYDVQQDEw5leGFtcGxlLmNvbSBDQTBZMBMGByqGSM49
|
||||||
|
AgEGCCqGSM49AwEHA0IABA6nVmkmH9iU+y/dMsJ7oc9gUuM4cvr+Y7jy6QSNCJ3a
|
||||||
|
PJh6TsPZviwpzMzdlpHFGLfly0MIWjTbdEQZX510c4egSzBJBgkqhkiG9w0BCQ4x
|
||||||
|
PDA6MDgGA1UdEQQxMC+CCWxvY2FsaG9zdIILZXhhbXBsZS5jb22CD3d3dy5leGFt
|
||||||
|
cGxlLmNvbYcEfwAAATAKBggqhkjOPQQDAgNIADBFAiEAl0UiOdxlEcuKrNAGh/Pv
|
||||||
|
CFxMyX5shaeAsdGvq/gyXckCIFlTwheOJZZVRRQl9b0l5LUVeJyIH6mnvitFGyQ7
|
||||||
|
NRk5
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
||||||
|
|
@ -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-----
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue