Skip to content

Commit d314923

Browse files
authored
Merge pull request #412 from fanminshi/tls_impl
pkg/tlsutil: implement the case ca and key are in the cluster
2 parents b08081a + 3d12890 commit d314923

File tree

2 files changed

+234
-0
lines changed

2 files changed

+234
-0
lines changed

pkg/tlsutil/primitives.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"crypto/x509"
2121
"crypto/x509/pkix"
2222
"encoding/pem"
23+
"errors"
2324
"fmt"
2425
"math"
2526
"math/big"
@@ -54,6 +55,24 @@ func encodeCertificatePEM(cert *x509.Certificate) []byte {
5455
})
5556
}
5657

58+
// parsePEMEncodedCert parses a certificate from the given pemdata
59+
func parsePEMEncodedCert(pemdata []byte) (*x509.Certificate, error) {
60+
decoded, _ := pem.Decode(pemdata)
61+
if decoded == nil {
62+
return nil, errors.New("no PEM data found")
63+
}
64+
return x509.ParseCertificate(decoded.Bytes)
65+
}
66+
67+
// parsePEMEncodedPrivateKey parses a private key from given pemdata
68+
func parsePEMEncodedPrivateKey(pemdata []byte) (*rsa.PrivateKey, error) {
69+
decoded, _ := pem.Decode(pemdata)
70+
if decoded == nil {
71+
return nil, errors.New("no PEM data found")
72+
}
73+
return x509.ParsePKCS1PrivateKey(decoded.Bytes)
74+
}
75+
5776
// newSelfSignedCACertificate returns a self-signed CA certificate based on given configuration and private key.
5877
// The certificate has one-year lease.
5978
func newSelfSignedCACertificate(key *rsa.PrivateKey) (*x509.Certificate, error) {

pkg/tlsutil/tls.go

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,18 @@
1515
package tlsutil
1616

1717
import (
18+
"crypto/rsa"
19+
"crypto/x509"
20+
"errors"
21+
"fmt"
22+
"strings"
23+
24+
"github.com/operator-framework/operator-sdk/pkg/sdk"
25+
1826
"k8s.io/api/core/v1"
27+
apiErrors "k8s.io/apimachinery/pkg/api/errors"
28+
"k8s.io/apimachinery/pkg/api/meta"
29+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1930
"k8s.io/apimachinery/pkg/runtime"
2031
)
2132

@@ -111,3 +122,207 @@ type CertGenerator interface {
111122
// ca.key: ..
112123
GenerateCert(cr runtime.Object, service *v1.Service, config *CertConfig) (*v1.Secret, *v1.ConfigMap, *v1.Secret, error)
113124
}
125+
126+
const (
127+
// TLSPrivateCAKeyKey is the key for the private CA key field.
128+
TLSPrivateCAKeyKey = "ca.key"
129+
// TLSCertKey is the key for tls CA certificates.
130+
TLSCACertKey = "ca.crt"
131+
)
132+
133+
type SDKCertGenerator struct {
134+
}
135+
136+
type keyAndCert struct {
137+
key *rsa.PrivateKey
138+
cert *x509.Certificate
139+
}
140+
141+
func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service, config *CertConfig) (*v1.Secret, *v1.ConfigMap, *v1.Secret, error) {
142+
if err := verifyConfig(config); err != nil {
143+
return nil, nil, nil, err
144+
}
145+
146+
k, n, ns, err := toKindNameNamespace(cr)
147+
if err != nil {
148+
return nil, nil, nil, err
149+
}
150+
appSecret, err := getAppSecretInCluster(toAppSecretName(k, n, config.CertName), ns)
151+
if err != nil {
152+
return nil, nil, nil, err
153+
}
154+
caSecret, caConfigMap, err := getCASecretAndConfigMapInCluster(toCASecretAndConfigMapName(k, n), ns)
155+
if err != nil {
156+
return nil, nil, nil, err
157+
}
158+
159+
hasAppSecret := appSecret != nil
160+
hasCASecretAndConfigMap := caSecret != nil && caConfigMap != nil
161+
// TODO: handle passed in CA
162+
if hasAppSecret && hasCASecretAndConfigMap {
163+
return appSecret, caConfigMap, caSecret, nil
164+
} else if hasAppSecret && !hasCASecretAndConfigMap {
165+
// TODO
166+
} else if !hasAppSecret && hasCASecretAndConfigMap {
167+
// TODO
168+
} else {
169+
// TODO
170+
}
171+
return nil, nil, nil, nil
172+
}
173+
174+
func verifyConfig(config *CertConfig) error {
175+
if config == nil {
176+
return errors.New("nil CertConfig not allowed")
177+
}
178+
if config.CertName == "" {
179+
return errors.New("empty CertConfig.CertName not allowed")
180+
}
181+
return nil
182+
}
183+
184+
func toAppSecretName(kind, name, certName string) string {
185+
return strings.ToLower(kind) + "-" + name + "-" + certName
186+
}
187+
188+
func toCASecretAndConfigMapName(kind, name string) string {
189+
return strings.ToLower(kind) + "-" + name + "-ca"
190+
}
191+
192+
func getAppSecretInCluster(name, namespace string) (*v1.Secret, error) {
193+
se := &v1.Secret{
194+
TypeMeta: metav1.TypeMeta{
195+
Kind: "Secret",
196+
APIVersion: "v1",
197+
},
198+
ObjectMeta: metav1.ObjectMeta{
199+
Name: name,
200+
Namespace: namespace,
201+
},
202+
}
203+
err := sdk.Get(se)
204+
if apiErrors.IsNotFound(err) {
205+
return nil, nil
206+
}
207+
if err != nil {
208+
return nil, err
209+
}
210+
return se, nil
211+
}
212+
213+
// getCASecretAndConfigMapInCluster gets CA secret and configmap of the given name and namespace.
214+
// 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.
215+
func getCASecretAndConfigMapInCluster(name, namespace string) (*v1.Secret, *v1.ConfigMap, error) {
216+
cm := &v1.ConfigMap{
217+
TypeMeta: metav1.TypeMeta{
218+
Kind: "ConfigMap",
219+
APIVersion: "v1",
220+
},
221+
ObjectMeta: metav1.ObjectMeta{
222+
Name: name,
223+
Namespace: namespace,
224+
},
225+
}
226+
hasConfigMap := true
227+
err := sdk.Get(cm)
228+
if apiErrors.IsNotFound(err) {
229+
hasConfigMap = false
230+
}
231+
if err != nil {
232+
return nil, nil, err
233+
}
234+
235+
se := &v1.Secret{
236+
TypeMeta: metav1.TypeMeta{
237+
Kind: "Secret",
238+
APIVersion: "v1",
239+
},
240+
ObjectMeta: metav1.ObjectMeta{
241+
Name: name,
242+
Namespace: namespace,
243+
},
244+
}
245+
hasSecret := true
246+
err = sdk.Get(se)
247+
if apiErrors.IsNotFound(err) {
248+
hasSecret = false
249+
}
250+
if err != nil {
251+
return nil, nil, err
252+
}
253+
254+
if hasConfigMap != hasSecret {
255+
// TODO: this case can happen if creating CA configmap succeeds and creating CA secret failed. We need to handle this case properly.
256+
return nil, nil, fmt.Errorf("expect either both ca configmap and secret both exist or not exist, but got hasCAConfigmap==%v and hasCASecret==%v", hasConfigMap, hasSecret)
257+
}
258+
if hasConfigMap == false {
259+
return nil, nil, nil
260+
}
261+
return se, cm, nil
262+
}
263+
264+
func toKindNameNamespace(cr runtime.Object) (string, string, string, error) {
265+
a := meta.NewAccessor()
266+
k, err := a.Kind(cr)
267+
if err != nil {
268+
return "", "", "", err
269+
}
270+
n, err := a.Name(cr)
271+
if err != nil {
272+
return "", "", "", err
273+
}
274+
ns, err := a.Namespace(cr)
275+
if err != nil {
276+
return "", "", "", err
277+
}
278+
return k, n, ns, nil
279+
}
280+
281+
// toTLSSecret returns a client/server "kubernetes.io/tls" secret.
282+
// TODO: add owner ref.
283+
func toTLSSecret(key *rsa.PrivateKey, cert *x509.Certificate, name, namespace string) *v1.Secret {
284+
return &v1.Secret{
285+
TypeMeta: metav1.TypeMeta{
286+
Kind: "Secret",
287+
APIVersion: "v1",
288+
},
289+
ObjectMeta: metav1.ObjectMeta{
290+
Name: name,
291+
Namespace: namespace,
292+
},
293+
Data: map[string][]byte{
294+
v1.TLSPrivateKeyKey: encodePrivateKeyPEM(key),
295+
v1.TLSCertKey: encodeCertificatePEM(cert),
296+
},
297+
Type: v1.SecretTypeTLS,
298+
}
299+
}
300+
301+
// TODO: add owner ref.
302+
func toCASecretAndConfigmap(key *rsa.PrivateKey, cert *x509.Certificate, name, namespace string) (*v1.ConfigMap, *v1.Secret) {
303+
return &v1.ConfigMap{
304+
TypeMeta: metav1.TypeMeta{
305+
Kind: "ConfigMap",
306+
APIVersion: "v1",
307+
},
308+
ObjectMeta: metav1.ObjectMeta{
309+
Name: name,
310+
Namespace: namespace,
311+
},
312+
Data: map[string]string{
313+
TLSCACertKey: string(encodeCertificatePEM(cert)),
314+
},
315+
}, &v1.Secret{
316+
TypeMeta: metav1.TypeMeta{
317+
Kind: "Secret",
318+
APIVersion: "v1",
319+
},
320+
ObjectMeta: metav1.ObjectMeta{
321+
Name: name,
322+
Namespace: namespace,
323+
},
324+
Data: map[string][]byte{
325+
TLSPrivateCAKeyKey: encodePrivateKeyPEM(key),
326+
},
327+
}
328+
}

0 commit comments

Comments
 (0)