Skip to content

Commit 625e18f

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

File tree

3 files changed

+94
-11
lines changed

3 files changed

+94
-11
lines changed

pkg/tlsutil/error.go

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

pkg/tlsutil/tls.go

Lines changed: 60 additions & 8 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
@@ -153,19 +157,59 @@ func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service
153157
return nil, nil, nil, err
154158
}
155159
caSecretAndConfigMapName := ToCASecretAndConfigMapName(k, n)
156-
caSecret, caConfigMap, err := getCASecretAndConfigMapInCluster(scg.KubeClient, caSecretAndConfigMapName, ns)
160+
161+
var (
162+
caSecret *v1.Secret
163+
caConfigMap *v1.ConfigMap
164+
)
165+
166+
caSecret, caConfigMap, err = getCASecretAndConfigMapInCluster(scg.KubeClient, caSecretAndConfigMapName, ns)
157167
if err != nil {
158168
return nil, nil, nil, err
159169
}
160170

171+
if config.CAKey != "" && config.CACert != "" {
172+
// custom CA provided by the user.
173+
customCAKeyData, err := ioutil.ReadFile(config.CAKey)
174+
if err != nil {
175+
return nil, nil, nil, fmt.Errorf("error reading CA Key from the given file name: %v", err)
176+
}
177+
178+
customCACertData, err := ioutil.ReadFile(config.CACert)
179+
if err != nil {
180+
return nil, nil, nil, fmt.Errorf("error reading CA Cert from the given file name: %v", err)
181+
}
182+
183+
customCAKey, err := parsePEMEncodedPrivateKey(customCAKeyData)
184+
if err != nil {
185+
return nil, nil, nil, fmt.Errorf("error parsing CA Key from the given file name: %v", err)
186+
}
187+
188+
customCACert, err := parsePEMEncodedCert(customCACertData)
189+
if err != nil {
190+
return nil, nil, nil, fmt.Errorf("error parsing CA Cert from the given file name: %v", err)
191+
}
192+
caSecret, caConfigMap = toCASecretAndConfigmap(customCAKey, customCACert, caSecretAndConfigMapName)
193+
}
194+
195+
if config.CAKey != "" || config.CACert != "" {
196+
// if only one of the custom CA Key or Cert is provided
197+
return nil, nil, nil, ErrCAKeyAndCACertReq
198+
}
199+
161200
hasAppSecret := appSecret != nil
162201
hasCASecretAndConfigMap := caSecret != nil && caConfigMap != nil
163-
// TODO: handle passed in CA
164-
if hasAppSecret && hasCASecretAndConfigMap {
202+
203+
switch {
204+
case hasAppSecret && hasCASecretAndConfigMap:
165205
return appSecret, caConfigMap, caSecret, nil
166-
} else if hasAppSecret && !hasCASecretAndConfigMap {
206+
207+
case hasAppSecret && !hasCASecretAndConfigMap:
167208
return nil, nil, nil, ErrCANotFound
168-
} else if !hasAppSecret && hasCASecretAndConfigMap {
209+
210+
case !hasAppSecret && hasCASecretAndConfigMap:
211+
// Note: if a custom CA is passed in my the user it takes preference over an already
212+
// generated CA secret and CA configmap that might exist in the cluster
169213
caKey, err := parsePEMEncodedPrivateKey(caSecret.Data[TLSPrivateCAKeyKey])
170214
if err != nil {
171215
return nil, nil, nil, err
@@ -187,8 +231,9 @@ func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service
187231
return nil, nil, nil, err
188232
}
189233
return appSecret, caConfigMap, caSecret, nil
190-
} else {
191-
// case: both CA and Application TLS assets don't exist.
234+
235+
case !hasAppSecret && !hasCASecretAndConfigMap:
236+
// If no custom CAKey and CACert are provided we have to generate them
192237
caKey, err := newPrivateKey()
193238
if err != nil {
194239
return nil, nil, nil, err
@@ -197,6 +242,7 @@ func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service
197242
if err != nil {
198243
return nil, nil, nil, err
199244
}
245+
200246
caSecret, caConfigMap := toCASecretAndConfigmap(caKey, caCert, caSecretAndConfigMapName)
201247
caSecret, err = scg.KubeClient.CoreV1().Secrets(ns).Create(caSecret)
202248
if err != nil {
@@ -219,6 +265,8 @@ func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service
219265
return nil, nil, nil, err
220266
}
221267
return appSecret, caConfigMap, caSecret, nil
268+
default:
269+
return nil, nil, nil, ErrInternal
222270
}
223271
}
224272

@@ -252,7 +300,11 @@ func getAppSecretInCluster(kubeClient kubernetes.Interface, name, namespace stri
252300
}
253301

254302
// 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.
303+
// 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,
304+
// then we error out because we expect either both CA secret and configmap exit or not.
305+
//
306+
// NOTE: both the CA secret and configmap have the same name with template `<cr-kind>-<cr-name>-ca` which is what the
307+
// input parameter `name` refers to.
256308
func getCASecretAndConfigMapInCluster(kubeClient kubernetes.Interface, name, namespace string) (*v1.Secret, *v1.ConfigMap, error) {
257309
hasConfigMap := true
258310
cm, err := kubeClient.CoreV1().ConfigMaps(namespace).Get(name, metav1.GetOptions{})

test/e2e/tls_util_test.go

Lines changed: 31 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,32 @@ 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+
customConfig := &tlsutil.CertConfig{
217+
CertName: certName,
218+
CAKey: "testdata/ca.key",
219+
CACert: "testdata/ca.crt",
220+
}
221+
appSecret, caConfigMap, caSecret, err := cg.GenerateCert(newDummyCR(namespace), newAppSvc(namespace), customConfig)
222+
if err != nil {
223+
t.Fatal(err)
224+
}
225+
226+
verifyAppSecret(t, appSecret, namespace)
227+
}
228+
200229
func verifyCASecret(t *testing.T, caSecret *v1.Secret, namespace string) {
201230
// check if caConfigMap has the correct fields.
202231
if caConfigMapAndSecretName != caSecret.Name {

0 commit comments

Comments
 (0)