ocirepo: adopt Kubernetes style TLS secrets for .spec.certSecretRef
Adopt Kubernetes TLS secrets API to check for TLS data in the Secret referred to by `.spec.certSecretRef`, i.e. check for keys `tls.crt` and `tls.key` for the certificate and private key. Use `ca.crt` for the CA certificate. Deprecate the usage of `caFile`, `certFile` and `keyFile` keys. Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
This commit is contained in:
parent
4bd6bcc9e9
commit
6fe3c96311
|
@ -97,17 +97,21 @@ type OCIRepositorySpec struct {
|
||||||
// +optional
|
// +optional
|
||||||
ServiceAccountName string `json:"serviceAccountName,omitempty"`
|
ServiceAccountName string `json:"serviceAccountName,omitempty"`
|
||||||
|
|
||||||
// CertSecretRef can be given the name of a secret containing
|
// CertSecretRef can be given the name of a Secret containing
|
||||||
// either or both of
|
// either or both of
|
||||||
//
|
//
|
||||||
// - a PEM-encoded client certificate (`certFile`) and private
|
// - a PEM-encoded client certificate (`tls.crt`) and private
|
||||||
// key (`keyFile`);
|
// key (`tls.key`);
|
||||||
// - a PEM-encoded CA certificate (`caFile`)
|
// - a PEM-encoded CA certificate (`ca.crt`)
|
||||||
//
|
//
|
||||||
// and whichever are supplied, will be used for connecting to the
|
// and whichever are supplied, will be used for connecting to the
|
||||||
// registry. The client cert and key are useful if you are
|
// registry. The client cert and key are useful if you are
|
||||||
// authenticating with a certificate; the CA cert is useful if
|
// authenticating with a certificate; the CA cert is useful if
|
||||||
// you are using a self-signed server certificate.
|
// you are using a self-signed server certificate. The Secret must
|
||||||
|
// be of type `Opaque` or `kubernetes.io/tls`.
|
||||||
|
//
|
||||||
|
// Note: Support for the `caFile`, `certFile` and `keyFile` keys have
|
||||||
|
// been deprecated.
|
||||||
// +optional
|
// +optional
|
||||||
CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`
|
CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`
|
||||||
|
|
||||||
|
|
|
@ -50,13 +50,15 @@ spec:
|
||||||
description: OCIRepositorySpec defines the desired state of OCIRepository
|
description: OCIRepositorySpec defines the desired state of OCIRepository
|
||||||
properties:
|
properties:
|
||||||
certSecretRef:
|
certSecretRef:
|
||||||
description: "CertSecretRef can be given the name of a secret containing
|
description: "CertSecretRef can be given the name of a Secret containing
|
||||||
either or both of \n - a PEM-encoded client certificate (`certFile`)
|
either or both of \n - a PEM-encoded client certificate (`tls.crt`)
|
||||||
and private key (`keyFile`); - a PEM-encoded CA certificate (`caFile`)
|
and private key (`tls.key`); - a PEM-encoded CA certificate (`ca.crt`)
|
||||||
\n and whichever are supplied, will be used for connecting to the
|
\n and whichever are supplied, will be used for connecting to the
|
||||||
registry. The client cert and key are useful if you are authenticating
|
registry. The client cert and key are useful if you are authenticating
|
||||||
with a certificate; the CA cert is useful if you are using a self-signed
|
with a certificate; the CA cert is useful if you are using a self-signed
|
||||||
server certificate."
|
server certificate. The Secret must be of type `Opaque` or `kubernetes.io/tls`.
|
||||||
|
\n Note: Support for the `caFile`, `certFile` and `keyFile` keys
|
||||||
|
have been deprecated."
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
description: Name of the referent.
|
description: Name of the referent.
|
||||||
|
|
|
@ -1119,17 +1119,20 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<em>(Optional)</em>
|
<em>(Optional)</em>
|
||||||
<p>CertSecretRef can be given the name of a secret containing
|
<p>CertSecretRef can be given the name of a Secret containing
|
||||||
either or both of</p>
|
either or both of</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>a PEM-encoded client certificate (<code>certFile</code>) and private
|
<li>a PEM-encoded client certificate (<code>tls.crt</code>) and private
|
||||||
key (<code>keyFile</code>);</li>
|
key (<code>tls.key</code>);</li>
|
||||||
<li>a PEM-encoded CA certificate (<code>caFile</code>)</li>
|
<li>a PEM-encoded CA certificate (<code>ca.crt</code>)</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>and whichever are supplied, will be used for connecting to the
|
<p>and whichever are supplied, will be used for connecting to the
|
||||||
registry. The client cert and key are useful if you are
|
registry. The client cert and key are useful if you are
|
||||||
authenticating with a certificate; the CA cert is useful if
|
authenticating with a certificate; the CA cert is useful if
|
||||||
you are using a self-signed server certificate.</p>
|
you are using a self-signed server certificate. The Secret must
|
||||||
|
be of type <code>Opaque</code> or <code>kubernetes.io/tls</code>.</p>
|
||||||
|
<p>Note: Support for the <code>caFile</code>, <code>certFile</code> and <code>keyFile</code> keys have
|
||||||
|
been deprecated.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -3024,17 +3027,20 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<em>(Optional)</em>
|
<em>(Optional)</em>
|
||||||
<p>CertSecretRef can be given the name of a secret containing
|
<p>CertSecretRef can be given the name of a Secret containing
|
||||||
either or both of</p>
|
either or both of</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>a PEM-encoded client certificate (<code>certFile</code>) and private
|
<li>a PEM-encoded client certificate (<code>tls.crt</code>) and private
|
||||||
key (<code>keyFile</code>);</li>
|
key (<code>tls.key</code>);</li>
|
||||||
<li>a PEM-encoded CA certificate (<code>caFile</code>)</li>
|
<li>a PEM-encoded CA certificate (<code>ca.crt</code>)</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>and whichever are supplied, will be used for connecting to the
|
<p>and whichever are supplied, will be used for connecting to the
|
||||||
registry. The client cert and key are useful if you are
|
registry. The client cert and key are useful if you are
|
||||||
authenticating with a certificate; the CA cert is useful if
|
authenticating with a certificate; the CA cert is useful if
|
||||||
you are using a self-signed server certificate.</p>
|
you are using a self-signed server certificate. The Secret must
|
||||||
|
be of type <code>Opaque</code> or <code>kubernetes.io/tls</code>.</p>
|
||||||
|
<p>Note: Support for the <code>caFile</code>, <code>certFile</code> and <code>keyFile</code> keys have
|
||||||
|
been deprecated.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
@ -310,42 +310,62 @@ fetch the image pull secrets attached to the service account and use them for au
|
||||||
**Note:** that for a publicly accessible image repository, you don't need to provide a `secretRef`
|
**Note:** that for a publicly accessible image repository, you don't need to provide a `secretRef`
|
||||||
nor `serviceAccountName`.
|
nor `serviceAccountName`.
|
||||||
|
|
||||||
### TLS Certificates
|
### Cert secret reference
|
||||||
|
|
||||||
`.spec.certSecretRef` field names a secret with TLS certificate data. This is for two separate
|
`.spec.certSecretRef.name` is an optional field to specify a secret containing
|
||||||
purposes:
|
TLS certificate data. The secret can contain the following keys:
|
||||||
|
|
||||||
- to provide a client certificate and private key, if you use a certificate to authenticate with
|
* `tls.crt` and `tls.key`, to specify the client certificate and private key used
|
||||||
the container registry; and,
|
for TLS client authentication. These must be used in conjunction, i.e.
|
||||||
- to provide a CA certificate, if the registry uses a self-signed certificate.
|
specifying one without the other will lead to an error.
|
||||||
|
* `ca.crt`, to specify the CA certificate used to verify the server, which is
|
||||||
|
required if the server is using a self-signed certificate.
|
||||||
|
|
||||||
These will often go together, if you are hosting a container registry yourself. All the files in the
|
If the server is using a self-signed certificate and has TLS client
|
||||||
secret are expected to be [PEM-encoded][pem-encoding]. This is an ASCII format for certificates and
|
authentication enabled, all three values are required.
|
||||||
keys; `openssl` and such tools will typically give you an option of PEM output.
|
|
||||||
|
|
||||||
Assuming you have obtained a certificate file and private key and put them in the files `client.crt`
|
The Secret should be of type `Opaque` or `kubernetes.io/tls`. All the files in
|
||||||
and `client.key` respectively, you can create a secret with `kubectl` like this:
|
the Secret are expected to be [PEM-encoded][pem-encoding]. Assuming you have
|
||||||
|
three files; `client.key`, `client.crt` and `ca.crt` for the client private key,
|
||||||
|
client certificate and the CA certificate respectively, you can generate the
|
||||||
|
required Secret using the `flux create secret tls` command:
|
||||||
|
|
||||||
```bash
|
```sh
|
||||||
kubectl create secret generic tls-certs \
|
flux create secret tls --tls-key-file=client.key --tls-crt-file=client.crt --ca-crt-file=ca.crt
|
||||||
--from-file=certFile=client.crt \
|
|
||||||
--from-file=keyFile=client.key
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You could also [prepare a secret and encrypt it][sops-guide]; the important bit is that the data
|
Example usage:
|
||||||
keys in the secret are `certFile` and `keyFile`.
|
|
||||||
|
|
||||||
If you have a CA certificate for the client to use, the data key for that is `caFile`. Adapting the
|
```yaml
|
||||||
previous example, if you have the certificate in the file `ca.crt`, and the client certificate and
|
---
|
||||||
key as before, the whole command would be:
|
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||||
|
kind: OCIRepository
|
||||||
```bash
|
metadata:
|
||||||
kubectl create secret generic tls-certs \
|
name: example
|
||||||
--from-file=certFile=client.crt \
|
namespace: default
|
||||||
--from-file=keyFile=client.key \
|
spec:
|
||||||
--from-file=caFile=ca.crt
|
interval: 5m0s
|
||||||
|
url: oci://example.com
|
||||||
|
certSecretRef:
|
||||||
|
name: example-tls
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: example-tls
|
||||||
|
namespace: default
|
||||||
|
type: kubernetes.io/tls # or Opaque
|
||||||
|
data:
|
||||||
|
tls.crt: <BASE64>
|
||||||
|
tls.key: <BASE64>
|
||||||
|
# NOTE: Can be supplied without the above values
|
||||||
|
ca.crt: <BASE64>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Warning:** Support for the `caFile`, `certFile` and `keyFile` keys have been
|
||||||
|
deprecated. If you have any Secrets using these keys and specified in an
|
||||||
|
OCIRepository, the controller will log a deprecation warning.
|
||||||
|
|
||||||
### Insecure
|
### Insecure
|
||||||
|
|
||||||
`.spec.insecure` is an optional field to allow connecting to an insecure (HTTP)
|
`.spec.insecure` is an optional field to allow connecting to an insecure (HTTP)
|
||||||
|
|
|
@ -18,8 +18,6 @@ package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -71,6 +69,7 @@ import (
|
||||||
soci "github.com/fluxcd/source-controller/internal/oci"
|
soci "github.com/fluxcd/source-controller/internal/oci"
|
||||||
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
|
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
|
||||||
"github.com/fluxcd/source-controller/internal/reconcile/summarize"
|
"github.com/fluxcd/source-controller/internal/reconcile/summarize"
|
||||||
|
"github.com/fluxcd/source-controller/internal/tls"
|
||||||
"github.com/fluxcd/source-controller/internal/util"
|
"github.com/fluxcd/source-controller/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -841,29 +840,22 @@ func (r *OCIRepositoryReconciler) transport(ctx context.Context, obj *ociv1.OCIR
|
||||||
}
|
}
|
||||||
|
|
||||||
transport := remote.DefaultTransport.(*http.Transport).Clone()
|
transport := remote.DefaultTransport.(*http.Transport).Clone()
|
||||||
tlsConfig := transport.TLSClientConfig
|
tlsConfig, _, err := tls.KubeTLSClientConfigFromSecret(certSecret, "")
|
||||||
|
if err != nil {
|
||||||
if clientCert, ok := certSecret.Data[oci.ClientCert]; ok {
|
return nil, err
|
||||||
// parse and set client cert and secret
|
|
||||||
if clientKey, ok := certSecret.Data[oci.ClientKey]; ok {
|
|
||||||
cert, err := tls.X509KeyPair(clientCert, clientKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("'%s' found in secret, but no %s", oci.ClientCert, oci.ClientKey)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if tlsConfig == nil {
|
||||||
if caCert, ok := certSecret.Data[oci.CACert]; ok {
|
tlsConfig, _, err = tls.TLSClientConfigFromSecret(certSecret, "")
|
||||||
syscerts, err := x509.SystemCertPool()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
syscerts.AppendCertsFromPEM(caCert)
|
if tlsConfig != nil {
|
||||||
tlsConfig.RootCAs = syscerts
|
ctrl.LoggerFrom(ctx).
|
||||||
|
Info("warning: specifying TLS auth data via `certFile`/`keyFile`/`caFile` is deprecated, please use `tls.crt`/`tls.key`/`ca.crt` instead")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
transport.TLSClientConfig = tlsConfig
|
||||||
|
|
||||||
return transport, nil
|
return transport, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -557,6 +557,31 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
tlsCertSecret: &corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "ca-file",
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"ca.crt": tlsCA,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
assertConditions: []metav1.Condition{
|
||||||
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"),
|
||||||
|
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "HTTPS with valid certfile using deprecated keys",
|
||||||
|
want: sreconcile.ResultSuccess,
|
||||||
|
registryOpts: registryOptions{
|
||||||
|
withTLS: true,
|
||||||
|
},
|
||||||
|
craneOpts: []crane.Option{crane.WithTransport(&http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
RootCAs: pool,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
tlsCertSecret: &corev1.Secret{
|
tlsCertSecret: &corev1.Secret{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "ca-file",
|
Name: "ca-file",
|
||||||
|
@ -605,11 +630,37 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
|
||||||
Name: "ca-file",
|
Name: "ca-file",
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
|
"ca.crt": []byte("invalid"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
assertConditions: []metav1.Condition{
|
||||||
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, ociv1.AuthenticationFailedReason, "cannot append certificate into certificate pool: invalid CA certificate"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "HTTPS with certfile using both caFile and ca.crt ignores caFile",
|
||||||
|
want: sreconcile.ResultSuccess,
|
||||||
|
registryOpts: registryOptions{
|
||||||
|
withTLS: true,
|
||||||
|
},
|
||||||
|
craneOpts: []crane.Option{crane.WithTransport(&http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
RootCAs: pool,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
tlsCertSecret: &corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "ca-file",
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"ca.crt": tlsCA,
|
||||||
"caFile": []byte("invalid"),
|
"caFile": []byte("invalid"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, ociv1.OCIPullFailedReason, "failed to determine artifact digest"),
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"),
|
||||||
|
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1257,7 +1308,7 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) {
|
||||||
Generation: 1,
|
Generation: 1,
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"caFile": tlsCA,
|
"ca.crt": tlsCA,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue