Skip to content

Commit 97c4be3

Browse files
committed
pkg/tlsutil: add support for custom CA
1 parent bc557b1 commit 97c4be3

File tree

3 files changed

+105
-11
lines changed

3 files changed

+105
-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: 58 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,57 @@ 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+
} else if config.CAKey != "" || config.CACert != "" {
194+
// if only one of the custom CA Key or Cert is provided
195+
return nil, nil, nil, ErrCAKeyAndCACertReq
196+
}
197+
161198
hasAppSecret := appSecret != nil
162199
hasCASecretAndConfigMap := caSecret != nil && caConfigMap != nil
163-
// TODO: handle passed in CA
164-
if hasAppSecret && hasCASecretAndConfigMap {
200+
201+
switch {
202+
case hasAppSecret && hasCASecretAndConfigMap:
165203
return appSecret, caConfigMap, caSecret, nil
166-
} else if hasAppSecret && !hasCASecretAndConfigMap {
204+
205+
case hasAppSecret && !hasCASecretAndConfigMap:
167206
return nil, nil, nil, ErrCANotFound
168-
} else if !hasAppSecret && hasCASecretAndConfigMap {
207+
208+
case !hasAppSecret && hasCASecretAndConfigMap:
209+
// Note: if a custom CA is passed in my the user it takes preference over an already
210+
// generated CA secret and CA configmap that might exist in the cluster
169211
caKey, err := parsePEMEncodedPrivateKey(caSecret.Data[TLSPrivateCAKeyKey])
170212
if err != nil {
171213
return nil, nil, nil, err
@@ -187,8 +229,9 @@ func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service
187229
return nil, nil, nil, err
188230
}
189231
return appSecret, caConfigMap, caSecret, nil
190-
} else {
191-
// case: both CA and Application TLS assets don't exist.
232+
233+
case !hasAppSecret && !hasCASecretAndConfigMap:
234+
// If no custom CAKey and CACert are provided we have to generate them
192235
caKey, err := newPrivateKey()
193236
if err != nil {
194237
return nil, nil, nil, err
@@ -197,6 +240,7 @@ func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service
197240
if err != nil {
198241
return nil, nil, nil, err
199242
}
243+
200244
caSecret, caConfigMap := toCASecretAndConfigmap(caKey, caCert, caSecretAndConfigMapName)
201245
caSecret, err = scg.KubeClient.CoreV1().Secrets(ns).Create(caSecret)
202246
if err != nil {
@@ -219,6 +263,8 @@ func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service
219263
return nil, nil, nil, err
220264
}
221265
return appSecret, caConfigMap, caSecret, nil
266+
default:
267+
return nil, nil, nil, ErrInternal
222268
}
223269
}
224270

@@ -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{})

test/e2e/tls_util_test.go

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package e2e
1717
import (
1818
"io/ioutil"
1919
"reflect"
20+
"strings"
2021
"testing"
2122

2223
"github.com/operator-framework/operator-sdk/pkg/tlsutil"
@@ -121,7 +122,10 @@ func TestBothAppAndCATLSAssetsExist(t *testing.T) {
121122
}
122123
}
123124

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.
125+
// TestOnlyAppSecretExist tests a case where the application TLS asset exists but its
126+
// correspoding CA asset doesn't. In this case, CertGenerator can't genereate a new CA because
127+
// it won't verify the existing application TLS cert. Therefore, CertGenerator can't proceed
128+
// and returns an error to the caller.
125129
func TestOnlyAppSecretExist(t *testing.T) {
126130
f := framework.Global
127131
ctx := f.NewTestCtx(t)
@@ -146,7 +150,7 @@ func TestOnlyAppSecretExist(t *testing.T) {
146150
}
147151
}
148152

149-
// TestOnlyCAExist ensures that at the case where only the CA exists in the cluster;
153+
// TestOnlyCAExist tests the case where only the CA exists in the cluster;
150154
// GenerateCert can retrieve the CA and uses it to create a new application secret.
151155
func TestOnlyCAExist(t *testing.T) {
152156
f := framework.Global
@@ -197,6 +201,44 @@ func TestNoneOfCaAndAppSecretExist(t *testing.T) {
197201
verifyCASecret(t, caSecret, namespace)
198202
}
199203

204+
// TestCustomCA ensures that if a user provides a custom Key and Cert and the CA and Application TLS assets
205+
// do not exist, the GenerateCert method can use the custom CA to generate the TLS assest.
206+
func TestCustomCA(t *testing.T) {
207+
f := framework.Global
208+
ctx := f.NewTestCtx(t)
209+
defer ctx.Cleanup(t)
210+
namespace, err := ctx.GetNamespace()
211+
if err != nil {
212+
t.Fatal(err)
213+
}
214+
215+
cg := tlsutil.NewSDKCertGenerator(f.KubeClient)
216+
217+
customConfig := &tlsutil.CertConfig{
218+
CertName: certName,
219+
CAKey: "testdata/ca.key",
220+
CACert: "testdata/ca.crt",
221+
}
222+
appSecret, _, _, err := cg.GenerateCert(newDummyCR(namespace), newAppSvc(namespace), customConfig)
223+
if err != nil {
224+
t.Fatal(err)
225+
}
226+
227+
verifyAppSecret(t, appSecret, namespace)
228+
229+
// ensure caConfigMap does not exist in k8s cluster.
230+
_, err = framework.Global.KubeClient.CoreV1().Secrets(namespace).Get(caConfigMapAndSecretName, metav1.GetOptions{})
231+
if !strings.Contains(err.Error(), "not found") {
232+
t.Fatal(err)
233+
}
234+
235+
// ensure caConfigMap does not exist in k8s cluster.
236+
_, err = framework.Global.KubeClient.CoreV1().Secrets(namespace).Get(caConfigMapAndSecretName, metav1.GetOptions{})
237+
if !strings.Contains(err.Error(), "not found") {
238+
t.Fatal(err)
239+
}
240+
}
241+
200242
func verifyCASecret(t *testing.T, caSecret *v1.Secret, namespace string) {
201243
// check if caConfigMap has the correct fields.
202244
if caConfigMapAndSecretName != caSecret.Name {

0 commit comments

Comments
 (0)