Skip to content

Commit cad07d3

Browse files
committed
pkg/tlsutil: add support for custom CA
1 parent 384f13c commit cad07d3

File tree

3 files changed

+99
-16
lines changed

3 files changed

+99
-16
lines changed

pkg/tlsutil/error.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ package tlsutil
1717
import "errors"
1818

1919
var (
20-
ErrCANotFound = errors.New("ca secret and configMap are not found")
20+
ErrCANotFound = errors.New("ca secret and configMap are not found")
21+
ErrCAKeyAndCACertReq = errors.New("CA key and CA cert need to be provided when requesting a custom CA.")
22+
ErrReadingCAKey = errors.New("error reading CA Key from the given file name.")
23+
ErrReadingCACert = errors.New("error reading CA Cert from the given file name.")
24+
ErrParsingCAKey = errors.New("error parsing CA Cert from the given file name.")
25+
ErrParsingCACert = errors.New("error parsing CA Cert from the given file name.")
2126
// TODO: add other tls util errors.
2227
)

pkg/tlsutil/tls.go

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"crypto/x509"
2020
"errors"
2121
"fmt"
22+
"io/ioutil"
2223
"strings"
2324

2425
"k8s.io/api/core/v1"
@@ -138,6 +139,9 @@ type SDKCertGenerator struct {
138139
KubeClient kubernetes.Interface
139140
}
140141

142+
// GenerateCert returns a secret containing the TLS encryption key and cert,
143+
// a ConfigMap containing the CA Certificate and a Secret containing the CA key or it
144+
// returns a error incase something goes wrong.
141145
func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service, config *CertConfig) (*v1.Secret, *v1.ConfigMap, *v1.Secret, error) {
142146
if err := verifyConfig(config); err != nil {
143147
return nil, nil, nil, err
@@ -161,11 +165,15 @@ func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service
161165
hasAppSecret := appSecret != nil
162166
hasCASecretAndConfigMap := caSecret != nil && caConfigMap != nil
163167
// TODO: handle passed in CA
168+
164169
if hasAppSecret && hasCASecretAndConfigMap {
165170
return appSecret, caConfigMap, caSecret, nil
166-
} else if hasAppSecret && !hasCASecretAndConfigMap {
171+
}
172+
if hasAppSecret && !hasCASecretAndConfigMap {
167173
return nil, nil, nil, ErrCANotFound
168-
} else if !hasAppSecret && hasCASecretAndConfigMap {
174+
}
175+
176+
if !hasAppSecret && hasCASecretAndConfigMap {
169177
caKey, err := parsePEMEncodedPrivateKey(caSecret.Data[TLSPrivateCAKeyKey])
170178
if err != nil {
171179
return nil, nil, nil, err
@@ -188,16 +196,54 @@ func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service
188196
}
189197
return appSecret, caConfigMap, caSecret, nil
190198
} else {
199+
var caKeyData, caCertData []byte
200+
var err error
201+
var caKey *rsa.PrivateKey
202+
var caCert *x509.Certificate
191203
// case: both CA and Application TLS assets don't exist.
192-
caKey, err := newPrivateKey()
193-
if err != nil {
194-
return nil, nil, nil, err
204+
if config.CAKey != "" && config.CACert != "" {
205+
// custom CA provided by the user.
206+
caKeyData, err = ioutil.ReadFile(config.CAKey)
207+
if err != nil {
208+
return nil, nil, nil, ErrReadingCAKey
209+
}
210+
211+
caCertData, err = ioutil.ReadFile(config.CACert)
212+
if err != nil {
213+
return nil, nil, nil, ErrReadingCACert
214+
}
215+
216+
caKey, err = parsePEMEncodedPrivateKey(caKeyData)
217+
if err != nil {
218+
return nil, nil, nil, ErrParsingCAKey
219+
}
220+
221+
caCert, err = parsePEMEncodedCert(caCertData)
222+
if err != nil {
223+
return nil, nil, nil, ErrParsingCACert
224+
}
225+
195226
}
196-
caCert, err := newSelfSignedCACertificate(caKey)
197-
if err != nil {
198-
return nil, nil, nil, err
227+
228+
if config.CAKey != "" || config.CACert != "" {
229+
// if only one of the custom CA Key or Cert is provided
230+
return nil, nil, nil, ErrCAKeyAndCACertReq
231+
} else {
232+
// If no custom CAKey and CACert are provided we have to generate them
233+
caKey, err := newPrivateKey()
234+
if err != nil {
235+
return nil, nil, nil, err
236+
}
237+
caCert, err := newSelfSignedCACertificate(caKey)
238+
if err != nil {
239+
return nil, nil, nil, err
240+
}
241+
242+
caKeyData = encodePrivateKeyPEM(caKey)
243+
caCertData = encodeCertificatePEM(caCert)
199244
}
200-
caSecret, caConfigMap := toCASecretAndConfigmap(caKey, caCert, caSecretAndConfigMapName)
245+
246+
caSecret, caConfigMap := toCASecretAndConfigmap(caKeyData, caCertData, caSecretAndConfigMapName)
201247
caSecret, err = scg.KubeClient.CoreV1().Secrets(ns).Create(caSecret)
202248
if err != nil {
203249
return nil, nil, nil, err
@@ -252,7 +298,11 @@ func getAppSecretInCluster(kubeClient kubernetes.Interface, name, namespace stri
252298
}
253299

254300
// getCASecretAndConfigMapInCluster gets CA secret and configmap of the given name and namespace.
255-
// it only returns both if they are found and nil if both are not found. In the case if only one of them is found, then we error out because we expect either both CA secret and configmap exit or not.
301+
// it only returns both if they are found and nil if both are not found. In the case if only one of them is found,
302+
// then we error out because we expect either both CA secret and configmap exit or not.
303+
//
304+
// NOTE: both the CA secret and configmap have the same name with template `<cr-kind>-<cr-name>-ca` which is what the
305+
// input parameter `name` refers to.
256306
func getCASecretAndConfigMapInCluster(kubeClient kubernetes.Interface, name, namespace string) (*v1.Secret, *v1.ConfigMap, error) {
257307
hasConfigMap := true
258308
cm, err := kubeClient.CoreV1().ConfigMaps(namespace).Get(name, metav1.GetOptions{})
@@ -315,20 +365,20 @@ func toTLSSecret(key *rsa.PrivateKey, cert *x509.Certificate, name string) *v1.S
315365
}
316366

317367
// TODO: add owner ref.
318-
func toCASecretAndConfigmap(key *rsa.PrivateKey, cert *x509.Certificate, name string) (*v1.Secret, *v1.ConfigMap) {
368+
func toCASecretAndConfigmap(key, cert []byte, name string) (*v1.Secret, *v1.ConfigMap) {
319369
return &v1.Secret{
320370
ObjectMeta: metav1.ObjectMeta{
321371
Name: name,
322372
},
323373
Data: map[string][]byte{
324-
TLSPrivateCAKeyKey: encodePrivateKeyPEM(key),
374+
TLSPrivateCAKeyKey: key,
325375
},
326376
}, &v1.ConfigMap{
327377
ObjectMeta: metav1.ObjectMeta{
328378
Name: name,
329379
},
330380
Data: map[string]string{
331-
TLSCACertKey: string(encodeCertificatePEM(cert)),
381+
TLSCACertKey: string(cert),
332382
},
333383
}
334384
}

test/e2e/tls_util_test.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,10 @@ func TestBothAppAndCATLSAssetsExist(t *testing.T) {
121121
}
122122
}
123123

124-
// TestOnlyAppSecretExist tests a case where the application TLS asset exists but its correspoding CA asset doesn't. In this case, CertGenerator can't genereate a new CA because it won't verify the existing application TLS cert. Therefore, CertGenerator can't proceed and returns an error to the caller.
124+
// TestOnlyAppSecretExist tests a case where the application TLS asset exists but its
125+
// correspoding CA asset doesn't. In this case, CertGenerator can't genereate a new CA because
126+
// it won't verify the existing application TLS cert. Therefore, CertGenerator can't proceed
127+
// and returns an error to the caller.
125128
func TestOnlyAppSecretExist(t *testing.T) {
126129
f := framework.Global
127130
ctx := f.NewTestCtx(t)
@@ -146,7 +149,7 @@ func TestOnlyAppSecretExist(t *testing.T) {
146149
}
147150
}
148151

149-
// TestOnlyCAExist ensures that at the case where only the CA exists in the cluster;
152+
// TestOnlyCAExist tests the case where only the CA exists in the cluster;
150153
// GenerateCert can retrieve the CA and uses it to create a new application secret.
151154
func TestOnlyCAExist(t *testing.T) {
152155
f := framework.Global
@@ -197,6 +200,31 @@ func TestNoneOfCaAndAppSecretExist(t *testing.T) {
197200
verifyCASecret(t, caSecret, namespace)
198201
}
199202

203+
// TestCustomCA ensures that if a user provides a custom Key and Cert and the CA and Application TLS assets
204+
// do not exist, the GenerateCert method can use the custom CA to generate the TLS assest.
205+
func TestCustomCA(t *testing.T) {
206+
f := framework.Global
207+
ctx := f.NewTestCtx(t)
208+
defer ctx.Cleanup(t)
209+
namespace, err := ctx.GetNamespace()
210+
if err != nil {
211+
t.Fatal(err)
212+
}
213+
214+
cg := tlsutil.NewSDKCertGenerator(f.KubeClient)
215+
216+
ccfg.CAKey = "./testdata/ca.key"
217+
ccfg.CACert = "./testdata/ca.crt"
218+
appSecret, caConfigMap, caSecret, err := cg.GenerateCert(newDummyCR(namespace), newAppSvc(namespace), ccfg)
219+
if err != nil {
220+
t.Fatal(err)
221+
}
222+
223+
verifyAppSecret(t, appSecret, namespace)
224+
verifyCaConfigMap(t, caConfigMap, namespace)
225+
verifyCASecret(t, caSecret, namespace)
226+
}
227+
200228
func verifyCASecret(t *testing.T, caSecret *v1.Secret, namespace string) {
201229
// check if caConfigMap has the correct fields.
202230
if caConfigMapAndSecretName != caSecret.Name {

0 commit comments

Comments
 (0)