Skip to content

Commit ce46f5b

Browse files
Merge pull request #488 from tmshort/backport
OCPBUGS-13321: OCPBUGS-1684: Optimize certificate generation
2 parents 3174a8a + 6d6f3f2 commit ce46f5b

File tree

1 file changed

+74
-48
lines changed

1 file changed

+74
-48
lines changed

cmd/collect-profiles/main.go

Lines changed: 74 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import (
3232
const (
3333
profileConfigMapLabelKey = "olm.openshift.io/pprof"
3434
pprofSecretName = "pprof-cert"
35+
validity = 24 * time.Hour
36+
minValidity = 15 * time.Minute
3537
)
3638

3739
var (
@@ -74,7 +76,7 @@ func newCmd() *cobra.Command {
7476
Short: "Retrieves the pprof data from a URL and stores it in a configMap.",
7577
Long: `The collect-profiles command makes https requests against pprof URLs
7678
provided as arguments and stores that information in immutable configMaps.
77-
79+
7880
# Example command with multiple arguments
7981
./collect-profiles -n - openshift-operator-lifecycle-manager \
8082
- --config-mount-path \
@@ -143,15 +145,23 @@ func newCmd() *cobra.Command {
143145
certPath := filepath.Join(certMountPath, corev1.TLSCertKey)
144146
keyPath := filepath.Join(certMountPath, corev1.TLSPrivateKeyKey)
145147

146-
if err := verifyCertAndKeyExist(certPath, keyPath); err != nil {
148+
var tlsCert *tls.Certificate
149+
if tlsCert, err = verifyCertAndKey(certPath, keyPath); err != nil {
147150
logrus.Infof("error verifying provided cert and key: %v", err)
148151
logrus.Info("generating a new cert and key")
149-
return populateServingCert(cmd.Context(), cfg.Client)
152+
if tlsCert, err = populateServingCert(cmd.Context(), cfg.Client); err != nil {
153+
return err
154+
}
155+
// Continue with new certificate/keypair
150156
}
151157

152-
httpClient, err := getHttpClient(certPath, keyPath)
153-
if err != nil {
154-
return err
158+
httpClient := &http.Client{
159+
Transport: &http.Transport{
160+
TLSClientConfig: &tls.Config{
161+
InsecureSkipVerify: true,
162+
Certificates: []tls.Certificate{*tlsCert},
163+
},
164+
},
155165
}
156166

157167
// Track successfully created configMaps by generateName for each endpoint being scrapped.
@@ -205,29 +215,49 @@ func newCmd() *cobra.Command {
205215
return fmt.Errorf("error deleting existing pprof configMaps: %v", errs)
206216
}
207217

208-
// Update serving cert after a successful run
209-
return populateServingCert(cmd.Context(), cfg.Client)
218+
return nil
210219
},
211220
}
212221
}
213222

214-
func verifyCertAndKeyExist(certPath, keyPath string) error {
223+
func verifyCertAndKey(certPath, keyPath string) (*tls.Certificate, error) {
215224
fi, err := os.Stat(certPath)
216225
if err != nil {
217-
return err
226+
return nil, err
218227
}
219228
if fi.Size() == 0 {
220-
return fmt.Errorf("cert file should not be empty")
229+
return nil, fmt.Errorf("cert file should not be empty")
221230
}
222231

223232
fi, err = os.Stat(keyPath)
224233
if err != nil {
225-
return err
234+
return nil, err
226235
}
227236
if fi.Size() == 0 {
228-
return fmt.Errorf("key file should not be empty")
237+
return nil, fmt.Errorf("key file should not be empty")
238+
}
239+
240+
tlsCert, err := tls.LoadX509KeyPair(certPath, keyPath)
241+
if err != nil {
242+
return nil, err
243+
}
244+
245+
x509Cert, err := x509.ParseCertificate(tlsCert.Certificate[0])
246+
if err != nil {
247+
return nil, err
248+
}
249+
250+
now := time.Now()
251+
if x509Cert.NotAfter.Before(now) {
252+
return nil, fmt.Errorf("certificate has expired")
253+
}
254+
// Since this cron runs every 15 minutes, we assume that the job takes less
255+
// than 15 minutes, so make sure there's still 15 minutes of validity left
256+
if x509Cert.NotAfter.Before(now.Add(minValidity)) {
257+
return nil, fmt.Errorf("certificate expires in less than 15 minutes")
229258
}
230-
return nil
259+
260+
return &tlsCert, nil
231261
}
232262

233263
func separateConfigMapsIntoNewestAndExpired(configMaps []corev1.ConfigMap) (newestCMs []corev1.ConfigMap, expiredCMs []corev1.ConfigMap) {
@@ -279,21 +309,6 @@ func newArgument(s string) (*argument, error) {
279309
return arg, nil
280310
}
281311

282-
func getHttpClient(certPath, keyPath string) (*http.Client, error) {
283-
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
284-
if err != nil {
285-
return nil, err
286-
}
287-
return &http.Client{
288-
Transport: &http.Transport{
289-
TLSClientConfig: &tls.Config{
290-
InsecureSkipVerify: true,
291-
Certificates: []tls.Certificate{cert},
292-
},
293-
},
294-
}, nil
295-
}
296-
297312
func requestURLBody(httpClient *http.Client, u *url.URL) ([]byte, error) {
298313
response, err := httpClient.Do(&http.Request{
299314
Method: http.MethodGet,
@@ -315,56 +330,67 @@ func requestURLBody(httpClient *http.Client, u *url.URL) ([]byte, error) {
315330
return b.Bytes(), nil
316331
}
317332

318-
func populateServingCert(ctx context.Context, client client.Client) error {
333+
func populateServingCert(ctx context.Context, client client.Client) (*tls.Certificate, error) {
319334
secret := &corev1.Secret{}
320335
err := client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: pprofSecretName}, secret)
321336
if err != nil {
322-
return err
337+
return nil, err
323338
}
324339

325-
cert, privateKey, err := getCertAndKey()
340+
certPEMBytes, privKeyPEMBytes, err := generateCertAndKey()
326341
if err != nil {
327-
return err
342+
return nil, err
328343
}
329344

330-
secret.Data[corev1.TLSCertKey] = cert
331-
secret.Data[corev1.TLSPrivateKeyKey] = privateKey
332-
return client.Update(ctx, secret)
345+
secret.Data[corev1.TLSCertKey] = certPEMBytes
346+
secret.Data[corev1.TLSPrivateKeyKey] = privKeyPEMBytes
347+
348+
if err = client.Update(ctx, secret); err != nil {
349+
return nil, err
350+
}
351+
// Create tlsCert for client use
352+
tlsCert, err := tls.X509KeyPair(certPEMBytes, privKeyPEMBytes)
353+
if err != nil {
354+
return nil, err
355+
}
356+
return &tlsCert, nil
333357
}
334358

335-
func getCertAndKey() ([]byte, []byte, error) {
359+
func generateCertAndKey() ([]byte, []byte, error) {
360+
now := time.Now()
336361
cert := &x509.Certificate{
337362
SerialNumber: big.NewInt(1658),
338363
Subject: pkix.Name{
339364
Organization: []string{"Red Hat, Inc."},
340365
},
341-
NotBefore: time.Now(),
342-
NotAfter: time.Now().Add(time.Hour),
366+
NotBefore: now,
367+
NotAfter: now.Add(validity),
343368
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
344369
KeyUsage: x509.KeyUsageDigitalSignature,
345370
}
346371

347-
caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
372+
keyPair, err := rsa.GenerateKey(rand.Reader, 4096)
348373
if err != nil {
349374
return nil, nil, err
350375
}
351376

352-
caBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, &caPrivKey.PublicKey, caPrivKey)
377+
// Generate a DER-encoded self-signed certificate
378+
certDER, err := x509.CreateCertificate(rand.Reader, cert, cert, &keyPair.PublicKey, keyPair)
353379
if err != nil {
354380
return nil, nil, err
355381
}
356382

357-
caPEM := new(bytes.Buffer)
358-
pem.Encode(caPEM, &pem.Block{
383+
certPEM := new(bytes.Buffer)
384+
pem.Encode(certPEM, &pem.Block{
359385
Type: "CERTIFICATE",
360-
Bytes: caBytes,
386+
Bytes: certDER,
361387
})
362388

363-
caPrivKeyPEM := new(bytes.Buffer)
364-
pem.Encode(caPrivKeyPEM, &pem.Block{
389+
privKeyPEM := new(bytes.Buffer)
390+
pem.Encode(privKeyPEM, &pem.Block{
365391
Type: "RSA PRIVATE KEY",
366-
Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey),
392+
Bytes: x509.MarshalPKCS1PrivateKey(keyPair),
367393
})
368394

369-
return caPEM.Bytes(), caPrivKeyPEM.Bytes(), nil
395+
return certPEM.Bytes(), privKeyPEM.Bytes(), nil
370396
}

0 commit comments

Comments
 (0)