Merge pull request #1194 from fluxcd/tls-secret

Adopt Kubernetes style TLS Secrets
This commit is contained in:
Sanskar Jaiswal 2023-08-22 18:35:18 +05:30 committed by GitHub
commit a302c71c57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 662 additions and 298 deletions

View File

@ -56,10 +56,21 @@ type HelmRepositorySpec struct {
// +optional
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
// CertSecretRef specifies the Secret containing the TLS authentication
// data. The secret must contain a 'certFile' and 'keyFile', and/or 'caFile'
// fields. It takes precedence over the values specified in the Secret
// referred to by `.spec.secretRef`.
// CertSecretRef can be given the name of a Secret containing
// either or both of
//
// - a PEM-encoded client certificate (`tls.crt`) and private
// key (`tls.key`);
// - a PEM-encoded CA certificate (`ca.crt`)
//
// and whichever are supplied, will be used for connecting to the
// 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 server certificate. The Secret must
// be of type `Opaque` or `kubernetes.io/tls`.
//
// It takes precedence over the values specified in the Secret referred
// to by `.spec.secretRef`.
// +optional
CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`

View File

@ -97,17 +97,21 @@ type OCIRepositorySpec struct {
// +optional
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
//
// - a PEM-encoded client certificate (`certFile`) and private
// key (`keyFile`);
// - a PEM-encoded CA certificate (`caFile`)
// - a PEM-encoded client certificate (`tls.crt`) and private
// key (`tls.key`);
// - a PEM-encoded CA certificate (`ca.crt`)
//
// and whichever are supplied, will be used for connecting to the
// 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 server certificate.
// and whichever are supplied, will be used for connecting to the
// 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 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
CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`

View File

@ -297,10 +297,15 @@ spec:
- namespaceSelectors
type: object
certSecretRef:
description: CertSecretRef specifies the Secret containing the TLS
authentication data. The secret must contain a 'certFile' and 'keyFile',
and/or 'caFile' fields. It takes precedence over the values specified
in the Secret referred to by `.spec.secretRef`.
description: "CertSecretRef can be given the name of a Secret containing
either or both of \n - a PEM-encoded client certificate (`tls.crt`)
and private key (`tls.key`); - a PEM-encoded CA certificate (`ca.crt`)
\n and whichever are supplied, will be used for connecting to the
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
server certificate. The Secret must be of type `Opaque` or `kubernetes.io/tls`.
\n It takes precedence over the values specified in the Secret referred
to by `.spec.secretRef`."
properties:
name:
description: Name of the referent.

View File

@ -50,13 +50,15 @@ spec:
description: OCIRepositorySpec defines the desired state of OCIRepository
properties:
certSecretRef:
description: "CertSecretRef can be given the name of a secret containing
either or both of \n - a PEM-encoded client certificate (`certFile`)
and private key (`keyFile`); - a PEM-encoded CA certificate (`caFile`)
description: "CertSecretRef can be given the name of a Secret containing
either or both of \n - a PEM-encoded client certificate (`tls.crt`)
and private key (`tls.key`); - a PEM-encoded CA certificate (`ca.crt`)
\n and whichever are supplied, will be used for connecting to the
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
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:
name:
description: Name of the referent.

View File

@ -811,10 +811,20 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
</td>
<td>
<em>(Optional)</em>
<p>CertSecretRef specifies the Secret containing the TLS authentication
data. The secret must contain a &lsquo;certFile&rsquo; and &lsquo;keyFile&rsquo;, and/or &lsquo;caFile&rsquo;
fields. It takes precedence over the values specified in the Secret
referred to by <code>.spec.secretRef</code>.</p>
<p>CertSecretRef can be given the name of a Secret containing
either or both of</p>
<ul>
<li>a PEM-encoded client certificate (<code>tls.crt</code>) and private
key (<code>tls.key</code>);</li>
<li>a PEM-encoded CA certificate (<code>ca.crt</code>)</li>
</ul>
<p>and whichever are supplied, will be used for connecting to the
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 server certificate. The Secret must
be of type <code>Opaque</code> or <code>kubernetes.io/tls</code>.</p>
<p>It takes precedence over the values specified in the Secret referred
to by <code>.spec.secretRef</code>.</p>
</td>
</tr>
<tr>
@ -1109,17 +1119,20 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
</td>
<td>
<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>
<ul>
<li>a PEM-encoded client certificate (<code>certFile</code>) and private
key (<code>keyFile</code>);</li>
<li>a PEM-encoded CA certificate (<code>caFile</code>)</li>
<li>a PEM-encoded client certificate (<code>tls.crt</code>) and private
key (<code>tls.key</code>);</li>
<li>a PEM-encoded CA certificate (<code>ca.crt</code>)</li>
</ul>
<p>and whichever are supplied, will be used for connecting to the
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 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>
</tr>
<tr>
@ -2503,10 +2516,20 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
</td>
<td>
<em>(Optional)</em>
<p>CertSecretRef specifies the Secret containing the TLS authentication
data. The secret must contain a &lsquo;certFile&rsquo; and &lsquo;keyFile&rsquo;, and/or &lsquo;caFile&rsquo;
fields. It takes precedence over the values specified in the Secret
referred to by <code>.spec.secretRef</code>.</p>
<p>CertSecretRef can be given the name of a Secret containing
either or both of</p>
<ul>
<li>a PEM-encoded client certificate (<code>tls.crt</code>) and private
key (<code>tls.key</code>);</li>
<li>a PEM-encoded CA certificate (<code>ca.crt</code>)</li>
</ul>
<p>and whichever are supplied, will be used for connecting to the
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 server certificate. The Secret must
be of type <code>Opaque</code> or <code>kubernetes.io/tls</code>.</p>
<p>It takes precedence over the values specified in the Secret referred
to by <code>.spec.secretRef</code>.</p>
</td>
</tr>
<tr>
@ -3004,17 +3027,20 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
</td>
<td>
<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>
<ul>
<li>a PEM-encoded client certificate (<code>certFile</code>) and private
key (<code>keyFile</code>);</li>
<li>a PEM-encoded CA certificate (<code>caFile</code>)</li>
<li>a PEM-encoded client certificate (<code>tls.crt</code>) and private
key (<code>tls.key</code>);</li>
<li>a PEM-encoded CA certificate (<code>ca.crt</code>)</li>
</ul>
<p>and whichever are supplied, will be used for connecting to the
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 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>
</tr>
<tr>

View File

@ -161,8 +161,9 @@ data:
#### HTTPS Certificate Authority
To provide a Certificate Authority to trust while connecting with a Git
repository over HTTPS, the referenced Secret can contain a `.data.caFile`
value.
repository over HTTPS, the referenced Secret's `.data` can contain a `ca.crt`
or `caFile` key. `ca.crt` takes precedence over `caFile`, i.e. if both keys
are present, the value of `ca.crt` will be taken into consideration.
```yaml
---
@ -173,7 +174,7 @@ metadata:
namespace: default
type: Opaque
data:
caFile: <BASE64>
ca.crt: <BASE64>
```
#### SSH authentication

View File

@ -467,32 +467,33 @@ flux create secret oci ghcr-auth \
--password=${GITHUB_PAT}
```
**Note:** Support for specifying TLS authentication data using this API has been
**Warning:** Support for specifying TLS authentication data using this API has been
deprecated. Please use [`.spec.certSecretRef`](#cert-secret-reference) instead.
If the controller uses the secret specfied by this field to configure TLS, then
a deprecation warning will be logged.
### Cert secret reference
`.spec.certSecretRef.name` is an optional field to specify a secret containing TLS
certificate data. The secret can contain the following keys:
`.spec.certSecretRef.name` is an optional field to specify a secret containing
TLS certificate data. The secret can contain the following keys:
* `certFile` and `keyFile`, to specify the client certificate and private key used for
TLS client authentication. These must be used in conjunction, i.e. specifying one without
the other will lead to an error.
* `caFile`, to specify the CA certificate used to verify the server, which is required
if the server is using a self-signed certificate.
* `tls.crt` and `tls.key`, to specify the client certificate and private key used
for TLS client authentication. These must be used in conjunction, i.e.
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.
If the server is using a self-signed certificate and has TLS client authentication enabled,
all three values are required.
If the server is using a self-signed certificate and has TLS client
authentication enabled, all three values are required.
All the files in 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 creat secret helm` command:
The Secret should be of type `Opaque` or `kubernetes.io/tls`. All the files in
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:
```sh
flux create secret helm tls --key-file=client.key --cert-file=client.crt --ca-file=ca.crt
flux create secret tls --tls-key-file=client.key --tls-crt-file=client.crt --ca-crt-file=ca.crt
```
Example usage:
@ -515,11 +516,12 @@ kind: Secret
metadata:
name: example-tls
namespace: default
type: kubernetes.io/tls # or Opaque
data:
certFile: <BASE64>
keyFile: <BASE64>
tls.crt: <BASE64>
tls.key: <BASE64>
# NOTE: Can be supplied without the above values
caFile: <BASE64>
ca.crt: <BASE64>
```
### Pass credentials

View File

@ -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`
nor `serviceAccountName`.
### TLS Certificates
### Cert secret reference
`.spec.certSecretRef` field names a secret with TLS certificate data. This is for two separate
purposes:
`.spec.certSecretRef.name` is an optional field to specify a secret containing
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
the container registry; and,
- to provide a CA certificate, if the registry uses a self-signed certificate.
* `tls.crt` and `tls.key`, to specify the client certificate and private key used
for TLS client authentication. These must be used in conjunction, i.e.
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
secret are expected to be [PEM-encoded][pem-encoding]. This is an ASCII format for certificates and
keys; `openssl` and such tools will typically give you an option of PEM output.
If the server is using a self-signed certificate and has TLS client
authentication enabled, all three values are required.
Assuming you have obtained a certificate file and private key and put them in the files `client.crt`
and `client.key` respectively, you can create a secret with `kubectl` like this:
The Secret should be of type `Opaque` or `kubernetes.io/tls`. All the files in
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
kubectl create secret generic tls-certs \
--from-file=certFile=client.crt \
--from-file=keyFile=client.key
```sh
flux create secret tls --tls-key-file=client.key --tls-crt-file=client.crt --ca-crt-file=ca.crt
```
You could also [prepare a secret and encrypt it][sops-guide]; the important bit is that the data
keys in the secret are `certFile` and `keyFile`.
Example usage:
If you have a CA certificate for the client to use, the data key for that is `caFile`. Adapting the
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:
```bash
kubectl create secret generic tls-certs \
--from-file=certFile=client.crt \
--from-file=keyFile=client.key \
--from-file=caFile=ca.crt
```yaml
---
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata:
name: example
namespace: default
spec:
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
`.spec.insecure` is an optional field to allow connecting to an insecure (HTTP)

View File

@ -410,6 +410,32 @@ func TestGitRepositoryReconciler_reconcileSource_authStrategy(t *testing.T) {
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new upstream revision 'master@sha1:<commit>'"),
},
},
{
name: "HTTPS with CAFile secret with both ca.crt and caFile keys makes Reconciling=True and ignores caFile",
protocol: "https",
server: options{
publicKey: tlsPublicKey,
privateKey: tlsPrivateKey,
ca: tlsCA,
},
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "ca-file",
},
Data: map[string][]byte{
"ca.crt": tlsCA,
"caFile": []byte("invalid"),
},
},
beforeFunc: func(obj *sourcev1.GitRepository) {
obj.Spec.SecretRef = &meta.LocalObjectReference{Name: "ca-file"}
},
want: sreconcile.ResultSuccess,
assertConditions: []metav1.Condition{
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: new upstream revision 'master@sha1:<commit>'"),
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new upstream revision 'master@sha1:<commit>'"),
},
},
{
name: "HTTPS with invalid CAFile secret makes CheckoutFailed=True and returns error",
protocol: "https",

View File

@ -2248,7 +2248,7 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) {
registryOpts registryOptions
secretOpts secretOptions
secret *corev1.Secret
certsecret *corev1.Secret
certSecret *corev1.Secret
insecure bool
provider string
providerImg string
@ -2363,16 +2363,16 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) {
Type: corev1.SecretTypeDockerConfigJson,
Data: map[string][]byte{},
},
certsecret: &corev1.Secret{
certSecret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "certs-secretref",
},
Data: map[string][]byte{
"caFile": []byte("invalid caFile"),
"ca.crt": []byte("invalid caFile"),
},
},
assertConditions: []metav1.Condition{
*conditions.TrueCondition(sourcev1.FetchFailedCondition, "Unknown", "unknown build error: failed to construct Helm client's TLS config: cannot append certificate into certificate pool: invalid caFile"),
*conditions.TrueCondition(sourcev1.FetchFailedCondition, "Unknown", "unknown build error: failed to construct Helm client's TLS config: cannot append certificate into certificate pool: invalid CA certificate"),
},
},
{
@ -2393,14 +2393,14 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) {
Type: corev1.SecretTypeDockerConfigJson,
Data: map[string][]byte{},
},
certsecret: &corev1.Secret{
certSecret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "certs-secretref",
},
Data: map[string][]byte{
"caFile": tlsCA,
"certFile": clientPublicKey,
"keyFile": clientPrivateKey,
"ca.crt": tlsCA,
"tls.crt": clientPublicKey,
"tls.key": clientPrivateKey,
},
},
assertConditions: []metav1.Condition{
@ -2472,11 +2472,11 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) {
clientBuilder.WithObjects(tt.secret)
}
if tt.certsecret != nil {
if tt.certSecret != nil {
repo.Spec.CertSecretRef = &meta.LocalObjectReference{
Name: tt.certsecret.Name,
Name: tt.certSecret.Name,
}
clientBuilder.WithObjects(tt.certsecret)
clientBuilder.WithObjects(tt.certSecret)
}
clientBuilder.WithObjects(repo)

View File

@ -325,12 +325,12 @@ func TestHelmRepositoryOCIReconciler_authStrategy(t *testing.T) {
Name: "certs-secretref",
},
Data: map[string][]byte{
"caFile": []byte("invalid caFile"),
"ca.crt": []byte("invalid caFile"),
},
},
assertConditions: []metav1.Condition{
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingWithRetryReason, "processing object: new generation 0 -> 1"),
*conditions.FalseCondition(meta.ReadyCondition, sourcev1.AuthenticationFailedReason, "cannot append certificate into certificate pool: invalid caFile"),
*conditions.FalseCondition(meta.ReadyCondition, sourcev1.AuthenticationFailedReason, "cannot append certificate into certificate pool: invalid CA certificate"),
},
},
{
@ -356,9 +356,9 @@ func TestHelmRepositoryOCIReconciler_authStrategy(t *testing.T) {
Name: "certs-secretref",
},
Data: map[string][]byte{
"caFile": tlsCA,
"certFile": clientPublicKey,
"keyFile": clientPrivateKey,
"ca.crt": tlsCA,
"tls.crt": clientPublicKey,
"tls.key": clientPrivateKey,
},
},
assertConditions: []metav1.Condition{

View File

@ -56,6 +56,7 @@ import (
"github.com/fluxcd/source-controller/internal/helm/repository"
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
"github.com/fluxcd/source-controller/internal/reconcile/summarize"
stls "github.com/fluxcd/source-controller/internal/tls"
)
func TestHelmRepositoryReconciler_deleteBeforeFinalizer(t *testing.T) {
@ -434,7 +435,7 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) {
Name: "ca-file",
},
Data: map[string][]byte{
"caFile": tlsCA,
"ca.crt": tlsCA,
},
},
beforeFunc: func(t *WithT, obj *helmv1.HelmRepository, rev digest.Digest) {
@ -445,6 +446,66 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) {
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new index revision"),
},
},
{
name: "HTTPS with certSecretRef makes ArtifactOutdated=True",
protocol: "https",
server: options{
publicKey: tlsPublicKey,
privateKey: tlsPrivateKey,
ca: tlsCA,
},
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "ca-file",
},
Data: map[string][]byte{
"ca.crt": tlsCA,
},
},
beforeFunc: func(t *WithT, obj *helmv1.HelmRepository, rev digest.Digest) {
obj.Spec.CertSecretRef = &meta.LocalObjectReference{Name: "ca-file"}
},
want: sreconcile.ResultSuccess,
assertConditions: []metav1.Condition{
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: new index revision"),
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new index revision"),
},
afterFunc: func(t *WithT, obj *helmv1.HelmRepository, artifact sourcev1.Artifact, chartRepo *repository.ChartRepository) {
t.Expect(chartRepo.Path).ToNot(BeEmpty())
t.Expect(chartRepo.Index).ToNot(BeNil())
t.Expect(artifact.Revision).ToNot(BeEmpty())
},
},
{
name: "HTTPS with secretRef and caFile key makes ArtifactOutdated=True",
protocol: "https",
server: options{
publicKey: tlsPublicKey,
privateKey: tlsPrivateKey,
ca: tlsCA,
},
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "ca-file",
},
Data: map[string][]byte{
"caFile": tlsCA,
},
},
beforeFunc: func(t *WithT, obj *helmv1.HelmRepository, rev digest.Digest) {
obj.Spec.SecretRef = &meta.LocalObjectReference{Name: "ca-file"}
},
want: sreconcile.ResultSuccess,
assertConditions: []metav1.Condition{
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: new index revision"),
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new index revision"),
},
afterFunc: func(t *WithT, obj *helmv1.HelmRepository, artifact sourcev1.Artifact, chartRepo *repository.ChartRepository) {
t.Expect(chartRepo.Path).ToNot(BeEmpty())
t.Expect(chartRepo.Index).ToNot(BeNil())
t.Expect(artifact.Revision).ToNot(BeEmpty())
},
},
{
name: "HTTP without secretRef makes ArtifactOutdated=True",
protocol: "http",
@ -502,7 +563,7 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) {
Name: "invalid-ca",
},
Data: map[string][]byte{
"caFile": []byte("invalid"),
"ca.crt": []byte("invalid"),
},
},
beforeFunc: func(t *WithT, obj *helmv1.HelmRepository, rev digest.Digest) {
@ -512,7 +573,7 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) {
},
wantErr: true,
assertConditions: []metav1.Condition{
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "cannot append certificate into certificate pool: invalid caFile"),
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "cannot append certificate into certificate pool: invalid CA certificate"),
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
},
@ -769,10 +830,16 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) {
if tt.url != "" {
repoURL = tt.url
}
tlsConf, _, serr = getter.TLSClientConfigFromSecret(*secret, repoURL)
tlsConf, _, serr = stls.KubeTLSClientConfigFromSecret(*secret, repoURL)
if serr != nil {
validSecret = false
}
if tlsConf == nil {
tlsConf, _, serr = stls.TLSClientConfigFromSecret(*secret, repoURL)
if serr != nil {
validSecret = false
}
}
newChartRepo, err = repository.NewChartRepository(obj.Spec.URL, "", testGetters, tlsConf, getterOpts...)
} else {
newChartRepo, err = repository.NewChartRepository(obj.Spec.URL, "", testGetters, nil)

View File

@ -18,8 +18,6 @@ package controller
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io"
@ -71,6 +69,7 @@ import (
soci "github.com/fluxcd/source-controller/internal/oci"
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
"github.com/fluxcd/source-controller/internal/reconcile/summarize"
"github.com/fluxcd/source-controller/internal/tls"
"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()
tlsConfig := transport.TLSClientConfig
if clientCert, ok := certSecret.Data[oci.ClientCert]; ok {
// 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)
}
tlsConfig, _, err := tls.KubeTLSClientConfigFromSecret(certSecret, "")
if err != nil {
return nil, err
}
if caCert, ok := certSecret.Data[oci.CACert]; ok {
syscerts, err := x509.SystemCertPool()
if tlsConfig == nil {
tlsConfig, _, err = tls.TLSClientConfigFromSecret(certSecret, "")
if err != nil {
return nil, err
}
syscerts.AppendCertsFromPEM(caCert)
tlsConfig.RootCAs = syscerts
if tlsConfig != nil {
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
}

View File

@ -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{
ObjectMeta: metav1.ObjectMeta{
Name: "ca-file",
@ -605,11 +630,37 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
Name: "ca-file",
},
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"),
},
},
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,
},
Data: map[string][]byte{
"caFile": tlsCA,
"ca.crt": tlsCA,
},
}

View File

@ -19,10 +19,8 @@ package getter
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net/url"
"os"
"path"
@ -37,6 +35,7 @@ import (
helmv1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/source-controller/internal/helm/registry"
soci "github.com/fluxcd/source-controller/internal/oci"
stls "github.com/fluxcd/source-controller/internal/tls"
)
const (
@ -47,16 +46,6 @@ const (
var ErrDeprecatedTLSConfig = errors.New("TLS configured in a deprecated manner")
// TLSBytes contains the bytes of the TLS files.
type TLSBytes struct {
// CertBytes is the bytes of the certificate file.
CertBytes []byte
// KeyBytes is the bytes of the key file.
KeyBytes []byte
// CABytes is the bytes of the CA file.
CABytes []byte
}
// ClientOpts contains the various options to use while constructing
// a Helm repository client.
type ClientOpts struct {
@ -91,7 +80,7 @@ func GetClientOpts(ctx context.Context, c client.Client, obj *helmv1.HelmReposit
var (
certSecret *corev1.Secret
tlsBytes *TLSBytes
tlsBytes *stls.TLSBytes
certFile string
keyFile string
caFile string
@ -105,7 +94,7 @@ func GetClientOpts(ctx context.Context, c client.Client, obj *helmv1.HelmReposit
return nil, "", fmt.Errorf("failed to get TLS authentication secret '%s/%s': %w", obj.GetNamespace(), obj.Spec.CertSecretRef.Name, err)
}
hrOpts.TlsConfig, tlsBytes, err = TLSClientConfigFromSecret(*certSecret, url)
hrOpts.TlsConfig, tlsBytes, err = stls.KubeTLSClientConfigFromSecret(*certSecret, url)
if err != nil {
return nil, "", fmt.Errorf("failed to construct Helm client's TLS config: %w", err)
}
@ -128,8 +117,8 @@ func GetClientOpts(ctx context.Context, c client.Client, obj *helmv1.HelmReposit
// If the TLS config is nil, i.e. one couldn't be constructed using `.spec.certSecretRef`
// then try to use `.spec.secretRef`.
if hrOpts.TlsConfig == nil {
hrOpts.TlsConfig, tlsBytes, err = TLSClientConfigFromSecret(*authSecret, url)
if hrOpts.TlsConfig == nil && !ociRepo {
hrOpts.TlsConfig, tlsBytes, err = stls.TLSClientConfigFromSecret(*authSecret, url)
if err != nil {
return nil, "", fmt.Errorf("failed to construct Helm client's TLS config: %w", err)
}
@ -162,7 +151,7 @@ func GetClientOpts(ctx context.Context, c client.Client, obj *helmv1.HelmReposit
if err != nil {
return nil, "", fmt.Errorf("cannot create temporary directory: %w", err)
}
certFile, keyFile, caFile, err = StoreTLSCertificateFiles(tlsBytes, dir)
certFile, keyFile, caFile, err = storeTLSCertificateFiles(tlsBytes, dir)
if err != nil {
return nil, "", fmt.Errorf("cannot write certs files to path: %w", err)
}
@ -198,60 +187,8 @@ func fetchSecret(ctx context.Context, c client.Client, name, namespace string) (
return &secret, nil
}
// TLSClientConfigFromSecret attempts to construct a TLS client config
// for the given v1.Secret. It returns the TLS client config or an error.
//
// Secrets with no certFile, keyFile, AND caFile are ignored, if only a
// certBytes OR keyBytes is defined it returns an error.
func TLSClientConfigFromSecret(secret corev1.Secret, repositoryUrl string) (*tls.Config, *TLSBytes, error) {
certBytes, keyBytes, caBytes := secret.Data["certFile"], secret.Data["keyFile"], secret.Data["caFile"]
switch {
case len(certBytes)+len(keyBytes)+len(caBytes) == 0:
return nil, nil, nil
case (len(certBytes) > 0 && len(keyBytes) == 0) || (len(keyBytes) > 0 && len(certBytes) == 0):
return nil, nil, fmt.Errorf("invalid '%s' secret data: fields 'certFile' and 'keyFile' require each other's presence",
secret.Name)
}
tlsConf := &tls.Config{}
if len(certBytes) > 0 && len(keyBytes) > 0 {
cert, err := tls.X509KeyPair(certBytes, keyBytes)
if err != nil {
return nil, nil, err
}
tlsConf.Certificates = append(tlsConf.Certificates, cert)
}
if len(caBytes) > 0 {
cp, err := x509.SystemCertPool()
if err != nil {
return nil, nil, fmt.Errorf("cannot retrieve system certificate pool: %w", err)
}
if !cp.AppendCertsFromPEM(caBytes) {
return nil, nil, fmt.Errorf("cannot append certificate into certificate pool: invalid caFile")
}
tlsConf.RootCAs = cp
}
tlsConf.BuildNameToCertificate()
u, err := url.Parse(repositoryUrl)
if err != nil {
return nil, nil, fmt.Errorf("cannot parse repository URL: %w", err)
}
tlsConf.ServerName = u.Hostname()
return tlsConf, &TLSBytes{
CertBytes: certBytes,
KeyBytes: keyBytes,
CABytes: caBytes,
}, nil
}
// StoreTLSCertificateFiles writes the certs files to the given path and returns the files paths.
func StoreTLSCertificateFiles(tlsBytes *TLSBytes, path string) (string, string, string, error) {
// storeTLSCertificateFiles writes the certs files to the given path and returns the files paths.
func storeTLSCertificateFiles(tlsBytes *stls.TLSBytes, path string) (string, string, string, error) {
var (
certFile string
keyFile string

View File

@ -18,11 +18,6 @@ package getter
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"math/big"
"os"
"testing"
"time"
@ -58,7 +53,7 @@ func TestGetClientOpts(t *testing.T) {
Name: "ca-file",
},
Data: map[string][]byte{
"caFile": tlsCA,
"ca.crt": tlsCA,
},
},
authSecret: &corev1.Secret{
@ -160,42 +155,6 @@ func TestGetClientOpts(t *testing.T) {
}
}
func Test_tlsClientConfigFromSecret(t *testing.T) {
tlsSecretFixture := validTlsSecret(t)
tests := []struct {
name string
secret corev1.Secret
modify func(secret *corev1.Secret)
wantErr bool
wantNil bool
}{
{"certFile, keyFile and caFile", tlsSecretFixture, nil, false, false},
{"without certFile", tlsSecretFixture, func(s *corev1.Secret) { delete(s.Data, "certFile") }, true, true},
{"without keyFile", tlsSecretFixture, func(s *corev1.Secret) { delete(s.Data, "keyFile") }, true, true},
{"without caFile", tlsSecretFixture, func(s *corev1.Secret) { delete(s.Data, "caFile") }, false, false},
{"empty", corev1.Secret{}, nil, false, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
secret := tt.secret.DeepCopy()
if tt.modify != nil {
tt.modify(secret)
}
got, _, err := TLSClientConfigFromSecret(*secret, "")
if (err != nil) != tt.wantErr {
t.Errorf("TLSClientConfigFromSecret() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantNil && got != nil {
t.Error("TLSClientConfigFromSecret() != nil")
return
}
})
}
}
func TestGetClientOpts_registryTLSLoginOption(t *testing.T) {
tlsCA, err := os.ReadFile("../../controller/testdata/certs/ca.pem")
if err != nil {
@ -215,7 +174,7 @@ func TestGetClientOpts_registryTLSLoginOption(t *testing.T) {
Name: "ca-file",
},
Data: map[string][]byte{
"caFile": tlsCA,
"ca.crt": tlsCA,
},
},
authSecret: &corev1.Secret{
@ -307,60 +266,3 @@ func TestGetClientOpts_registryTLSLoginOption(t *testing.T) {
})
}
}
// validTlsSecret creates a secret containing key pair and CA certificate that are
// valid from a syntax (minimum requirements) perspective.
func validTlsSecret(t *testing.T) corev1.Secret {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal("Private key cannot be created.", err.Error())
}
certTemplate := x509.Certificate{
SerialNumber: big.NewInt(1337),
}
cert, err := x509.CreateCertificate(rand.Reader, &certTemplate, &certTemplate, &key.PublicKey, key)
if err != nil {
t.Fatal("Certificate cannot be created.", err.Error())
}
ca := &x509.Certificate{
SerialNumber: big.NewInt(7331),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
}
caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
t.Fatal("CA private key cannot be created.", err.Error())
}
caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey)
if err != nil {
t.Fatal("CA certificate cannot be created.", err.Error())
}
keyPem := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
})
certPem := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: cert,
})
caPem := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: caBytes,
})
return corev1.Secret{
Data: map[string][]byte{
"certFile": []byte(certPem),
"keyFile": []byte(keyPem),
"caFile": []byte(caPem),
},
}
}

140
internal/tls/config.go Normal file
View File

@ -0,0 +1,140 @@
/*
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.
*/
package tls
import (
"crypto/tls"
"crypto/x509"
"fmt"
neturl "net/url"
corev1 "k8s.io/api/core/v1"
)
const CACrtKey = "ca.crt"
// TLSBytes contains the bytes of the TLS files.
type TLSBytes struct {
// CertBytes is the bytes of the certificate file.
CertBytes []byte
// KeyBytes is the bytes of the key file.
KeyBytes []byte
// CABytes is the bytes of the CA file.
CABytes []byte
}
// KubeTLSClientConfigFromSecret returns a TLS client config as a `tls.Config`
// object and in its bytes representation. The secret is expected to have the
// following keys:
// - tls.key, for the private key
// - tls.crt, for the certificate
// - ca.crt, for the CA certificate
//
// Secrets with no certificate, private key, AND CA cert are ignored. If only a
// certificate OR private key is found, an error is returned.
func KubeTLSClientConfigFromSecret(secret corev1.Secret, url string) (*tls.Config, *TLSBytes, error) {
return tlsClientConfigFromSecret(secret, url, true)
}
// TLSClientConfigFromSecret returns a TLS client config as a `tls.Config`
// object and in its bytes representation. The secret is expected to have the
// following keys:
// - keyFile, for the private key
// - certFile, for the certificate
// - caFile, for the CA certificate
//
// Secrets with no certificate, private key, AND CA cert are ignored. If only a
// certificate OR private key is found, an error is returned.
func TLSClientConfigFromSecret(secret corev1.Secret, url string) (*tls.Config, *TLSBytes, error) {
return tlsClientConfigFromSecret(secret, url, false)
}
// tlsClientConfigFromSecret attempts to construct and return a TLS client
// config from the given Secret. If the Secret does not contain any TLS
// data, it returns nil.
//
// kubernetesTLSKeys is a boolean indicating whether to check the Secret
// for keys expected to be present in a Kubernetes TLS Secret. Based on its
// value, the Secret is checked for the following keys:
// - tls.key/keyFile for the private key
// - tls.crt/certFile for the certificate
// - ca.crt/caFile for the CA certificate
// The keys should adhere to a single convention, i.e. a Secret with tls.key
// and certFile is invalid.
func tlsClientConfigFromSecret(secret corev1.Secret, url string, kubernetesTLSKeys bool) (*tls.Config, *TLSBytes, error) {
// Only Secrets of type Opaque and TLS are allowed. We also allow Secrets with a blank
// type, to avoid having to specify the type of the Secret for every test case.
// Since a real Kubernetes Secret is of type Opaque by default, its safe to allow this.
switch secret.Type {
case corev1.SecretTypeOpaque, corev1.SecretTypeTLS, "":
default:
return nil, nil, fmt.Errorf("cannot use secret '%s' to construct TLS config: invalid secret type: '%s'", secret.Name, secret.Type)
}
var certBytes, keyBytes, caBytes []byte
if kubernetesTLSKeys {
certBytes, keyBytes, caBytes = secret.Data[corev1.TLSCertKey], secret.Data[corev1.TLSPrivateKeyKey], secret.Data[CACrtKey]
} else {
certBytes, keyBytes, caBytes = secret.Data["certFile"], secret.Data["keyFile"], secret.Data["caFile"]
}
switch {
case len(certBytes)+len(keyBytes)+len(caBytes) == 0:
return nil, nil, nil
case (len(certBytes) > 0 && len(keyBytes) == 0) || (len(keyBytes) > 0 && len(certBytes) == 0):
return nil, nil, fmt.Errorf("invalid '%s' secret data: both certificate and private key need to be provided",
secret.Name)
}
tlsConf := &tls.Config{
MinVersion: tls.VersionTLS12,
}
if len(certBytes) > 0 && len(keyBytes) > 0 {
cert, err := tls.X509KeyPair(certBytes, keyBytes)
if err != nil {
return nil, nil, err
}
tlsConf.Certificates = append(tlsConf.Certificates, cert)
}
if len(caBytes) > 0 {
cp, err := x509.SystemCertPool()
if err != nil {
return nil, nil, fmt.Errorf("cannot retrieve system certificate pool: %w", err)
}
if !cp.AppendCertsFromPEM(caBytes) {
return nil, nil, fmt.Errorf("cannot append certificate into certificate pool: invalid CA certificate")
}
tlsConf.RootCAs = cp
}
if url != "" {
u, err := neturl.Parse(url)
if err != nil {
return nil, nil, fmt.Errorf("cannot parse repository URL: %w", err)
}
tlsConf.ServerName = u.Hostname()
}
return tlsConf, &TLSBytes{
CertBytes: certBytes,
KeyBytes: keyBytes,
CABytes: caBytes,
}, nil
}

178
internal/tls/config_test.go Normal file
View File

@ -0,0 +1,178 @@
/*
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.
*/
package tls
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"math/big"
"net/url"
"testing"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
)
func Test_tlsClientConfigFromSecret(t *testing.T) {
kubernetesTlsSecretFixture := validTlsSecret(t, true)
tlsSecretFixture := validTlsSecret(t, false)
tests := []struct {
name string
secret corev1.Secret
modify func(secret *corev1.Secret)
tlsKeys bool
url string
wantErr bool
wantNil bool
}{
{
name: "tls.crt, tls.key and ca.crt",
secret: kubernetesTlsSecretFixture,
modify: nil,
tlsKeys: true,
url: "https://example.com",
},
{
name: "certFile, keyFile and caFile",
secret: tlsSecretFixture,
modify: nil,
tlsKeys: false,
url: "https://example.com",
},
{
name: "without tls.crt",
secret: kubernetesTlsSecretFixture,
modify: func(s *corev1.Secret) { delete(s.Data, "tls.crt") },
tlsKeys: true,
wantErr: true,
wantNil: true,
},
{
name: "without tls.key",
secret: kubernetesTlsSecretFixture,
modify: func(s *corev1.Secret) { delete(s.Data, "tls.key") },
tlsKeys: true,
wantErr: true,
wantNil: true,
},
{
name: "without ca.crt",
secret: kubernetesTlsSecretFixture,
modify: func(s *corev1.Secret) { delete(s.Data, "ca.crt") },
tlsKeys: true,
},
{
name: "empty secret",
secret: corev1.Secret{},
tlsKeys: true,
wantNil: true,
},
{
name: "invalid secret type",
secret: corev1.Secret{Type: corev1.SecretTypeDockerConfigJson},
wantErr: true,
wantNil: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
secret := tt.secret.DeepCopy()
if tt.modify != nil {
tt.modify(secret)
}
tlsConfig, _, err := tlsClientConfigFromSecret(*secret, tt.url, tt.tlsKeys)
g.Expect(err != nil).To(Equal(tt.wantErr), fmt.Sprintf("expected error: %v, got: %v", tt.wantErr, err))
g.Expect(tlsConfig == nil).To(Equal(tt.wantNil))
if tt.url != "" {
u, _ := url.Parse(tt.url)
g.Expect(u.Hostname()).To(Equal(tlsConfig.ServerName))
}
})
}
}
// validTlsSecret creates a secret containing key pair and CA certificate that are
// valid from a syntax (minimum requirements) perspective.
func validTlsSecret(t *testing.T, kubernetesTlsKeys bool) corev1.Secret {
t.Helper()
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal("Private key cannot be created.", err.Error())
}
certTemplate := x509.Certificate{
SerialNumber: big.NewInt(1337),
}
cert, err := x509.CreateCertificate(rand.Reader, &certTemplate, &certTemplate, &key.PublicKey, key)
if err != nil {
t.Fatal("Certificate cannot be created.", err.Error())
}
ca := &x509.Certificate{
SerialNumber: big.NewInt(7331),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
}
caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
t.Fatal("CA private key cannot be created.", err.Error())
}
caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey)
if err != nil {
t.Fatal("CA certificate cannot be created.", err.Error())
}
keyPem := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
})
certPem := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: cert,
})
caPem := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: caBytes,
})
crtKey := corev1.TLSCertKey
pkKey := corev1.TLSPrivateKeyKey
caKey := CACrtKey
if !kubernetesTlsKeys {
crtKey = "certFile"
pkKey = "keyFile"
caKey = "caFile"
}
return corev1.Secret{
Data: map[string][]byte{
crtKey: []byte(certPem),
pkKey: []byte(keyPem),
caKey: []byte(caPem),
},
}
}