Skip to content

Commit 9adb7f3

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

File tree

3 files changed

+105
-10
lines changed

3 files changed

+105
-10
lines changed

pkg/tlsutil/error.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ 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.")
26+
ErrInternal = errors.New("internal error while generating TLS assets.")
2127
// TODO: add other tls util errors.
2228
)

pkg/tlsutil/tls.go

Lines changed: 68 additions & 7 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
@@ -158,14 +162,49 @@ func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service
158162
return nil, nil, nil, err
159163
}
160164

165+
var customCAKey *rsa.PrivateKey
166+
var customCACert *x509.Certificate
167+
if config.CAKey != "" && config.CACert != "" {
168+
// custom CA provided by the user.
169+
customCAKeyData, err := ioutil.ReadFile(config.CAKey)
170+
if err != nil {
171+
return nil, nil, nil, ErrReadingCAKey
172+
}
173+
174+
customCACertData, err := ioutil.ReadFile(config.CACert)
175+
if err != nil {
176+
return nil, nil, nil, ErrReadingCACert
177+
}
178+
179+
customCAKey, err = parsePEMEncodedPrivateKey(customCAKeyData)
180+
if err != nil {
181+
return nil, nil, nil, ErrParsingCAKey
182+
}
183+
184+
customCACert, err = parsePEMEncodedCert(customCACertData)
185+
if err != nil {
186+
return nil, nil, nil, ErrParsingCACert
187+
}
188+
}
189+
190+
if config.CAKey != "" || config.CACert != "" {
191+
// if only one of the custom CA Key or Cert is provided
192+
return nil, nil, nil, ErrCAKeyAndCACertReq
193+
}
194+
161195
hasAppSecret := appSecret != nil
162196
hasCASecretAndConfigMap := caSecret != nil && caConfigMap != nil
163-
// TODO: handle passed in CA
164-
if hasAppSecret && hasCASecretAndConfigMap {
197+
hasCustomCA := customCAKey != nil && customCACert != nil
198+
199+
switch {
200+
case hasAppSecret && hasCASecretAndConfigMap:
165201
return appSecret, caConfigMap, caSecret, nil
166-
} else if hasAppSecret && !hasCASecretAndConfigMap {
202+
case hasAppSecret && hasCustomCA:
203+
return appSecret, nil, nil, nil
204+
case hasAppSecret && !hasCASecretAndConfigMap && !hasCustomCA:
167205
return nil, nil, nil, ErrCANotFound
168-
} else if !hasAppSecret && hasCASecretAndConfigMap {
206+
207+
case !hasAppSecret && hasCASecretAndConfigMap:
169208
caKey, err := parsePEMEncodedPrivateKey(caSecret.Data[TLSPrivateCAKeyKey])
170209
if err != nil {
171210
return nil, nil, nil, err
@@ -187,8 +226,23 @@ func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service
187226
return nil, nil, nil, err
188227
}
189228
return appSecret, caConfigMap, caSecret, nil
190-
} else {
191-
// case: both CA and Application TLS assets don't exist.
229+
case !hasAppSecret && hasCustomCA:
230+
key, err := newPrivateKey()
231+
if err != nil {
232+
return nil, nil, nil, err
233+
}
234+
cert, err := newSignedCertificate(config, service, key, customCACert, customCAKey)
235+
if err != nil {
236+
return nil, nil, nil, err
237+
}
238+
appSecret, err := scg.KubeClient.CoreV1().Secrets(ns).Create(toTLSSecret(key, cert, appSecretName))
239+
if err != nil {
240+
return nil, nil, nil, err
241+
}
242+
return appSecret, nil, nil, nil
243+
244+
case !hasAppSecret && !hasCASecretAndConfigMap && !hasCustomCA:
245+
// If no custom CAKey and CACert are provided we have to generate them
192246
caKey, err := newPrivateKey()
193247
if err != nil {
194248
return nil, nil, nil, err
@@ -197,6 +251,7 @@ func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service
197251
if err != nil {
198252
return nil, nil, nil, err
199253
}
254+
200255
caSecret, caConfigMap := toCASecretAndConfigmap(caKey, caCert, caSecretAndConfigMapName)
201256
caSecret, err = scg.KubeClient.CoreV1().Secrets(ns).Create(caSecret)
202257
if err != nil {
@@ -219,6 +274,8 @@ func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service
219274
return nil, nil, nil, err
220275
}
221276
return appSecret, caConfigMap, caSecret, nil
277+
default:
278+
return nil, nil, nil, ErrInternal
222279
}
223280
}
224281

@@ -252,7 +309,11 @@ func getAppSecretInCluster(kubeClient kubernetes.Interface, name, namespace stri
252309
}
253310

254311
// 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.
312+
// 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,
313+
// then we error out because we expect either both CA secret and configmap exit or not.
314+
//
315+
// NOTE: both the CA secret and configmap have the same name with template `<cr-kind>-<cr-name>-ca` which is what the
316+
// input parameter `name` refers to.
256317
func getCASecretAndConfigMapInCluster(kubeClient kubernetes.Interface, name, namespace string) (*v1.Secret, *v1.ConfigMap, error) {
257318
hasConfigMap := true
258319
cm, err := kubeClient.CoreV1().ConfigMaps(namespace).Get(name, metav1.GetOptions{})

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)