Skip to content

Commit b0e9a23

Browse files
authored
Merge pull request #5015 from laozc/populate-ca
Support adding untrusted root CA certificates (corp certs)
2 parents 91a76a8 + e329639 commit b0e9a23

File tree

4 files changed

+219
-1
lines changed

4 files changed

+219
-1
lines changed

deploy/iso/minikube-iso/configs/minikube_defconfig

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ BR2_PACKAGE_SOCAT=y
4747
BR2_PACKAGE_SUDO=y
4848
BR2_PACKAGE_ACL=y
4949
BR2_PACKAGE_COREUTILS=y
50+
BR2_PACKAGE_LIBRESSL=y
51+
BR2_PACKAGE_LIBRESSL_BIN=y
5052
BR2_PACKAGE_OPENVMTOOLS=y
5153
BR2_PACKAGE_OPENVMTOOLS_PROCPS=y
5254
BR2_PACKAGE_SYSTEMD_LOGIND=y

pkg/minikube/bootstrapper/certs.go

+146-1
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ limitations under the License.
1717
package bootstrapper
1818

1919
import (
20+
"encoding/pem"
2021
"fmt"
22+
"io/ioutil"
2123
"net"
24+
"os"
2225
"path"
2326
"path/filepath"
2427
"strings"
@@ -36,6 +39,11 @@ import (
3639
"k8s.io/minikube/pkg/util"
3740
)
3841

42+
const (
43+
CACertificatesDir = "/usr/share/ca-certificates"
44+
SSLCertStoreDir = "/etc/ssl/certs"
45+
)
46+
3947
var (
4048
certs = []string{
4149
"ca.crt", "ca.key", "apiserver.crt", "apiserver.key", "proxy-client-ca.crt",
@@ -67,6 +75,19 @@ func SetupCerts(cmd command.Runner, k8s config.KubernetesConfig) error {
6775
copyableFiles = append(copyableFiles, certFile)
6876
}
6977

78+
caCerts, err := collectCACerts()
79+
if err != nil {
80+
return err
81+
}
82+
for src, dst := range caCerts {
83+
certFile, err := assets.NewFileAsset(src, path.Dir(dst), path.Base(dst), "0644")
84+
if err != nil {
85+
return err
86+
}
87+
88+
copyableFiles = append(copyableFiles, certFile)
89+
}
90+
7091
kcs := &kubeconfig.Settings{
7192
ClusterName: k8s.NodeName,
7293
ClusterServerAddress: fmt.Sprintf("https://localhost:%d", k8s.NodePort),
@@ -77,7 +98,7 @@ func SetupCerts(cmd command.Runner, k8s config.KubernetesConfig) error {
7798
}
7899

79100
kubeCfg := api.NewConfig()
80-
err := kubeconfig.PopulateFromSettings(kcs, kubeCfg)
101+
err = kubeconfig.PopulateFromSettings(kcs, kubeCfg)
81102
if err != nil {
82103
return errors.Wrap(err, "populating kubeconfig")
83104
}
@@ -95,6 +116,11 @@ func SetupCerts(cmd command.Runner, k8s config.KubernetesConfig) error {
95116
return err
96117
}
97118
}
119+
120+
// configure CA certificates
121+
if err := configureCACerts(cmd, caCerts); err != nil {
122+
return errors.Wrapf(err, "error configuring CA certificates during provisioning %v", err)
123+
}
98124
return nil
99125
}
100126

@@ -198,3 +224,122 @@ func generateCerts(k8s config.KubernetesConfig) error {
198224

199225
return nil
200226
}
227+
228+
// isValidPEMCertificate checks whether the input file is a valid PEM certificate (with at least one CERTIFICATE block)
229+
func isValidPEMCertificate(filePath string) (bool, error) {
230+
fileBytes, err := ioutil.ReadFile(filePath)
231+
if err != nil {
232+
return false, err
233+
}
234+
235+
for {
236+
block, rest := pem.Decode(fileBytes)
237+
if block == nil {
238+
break
239+
}
240+
241+
if block.Type == "CERTIFICATE" {
242+
// certificate found
243+
return true, nil
244+
}
245+
fileBytes = rest
246+
}
247+
248+
return false, nil
249+
}
250+
251+
// collectCACerts looks up all PEM certificates with .crt or .pem extension in ~/.minikube/certs to copy to the host.
252+
// minikube root CA is also included but libmachine certificates (ca.pem/cert.pem) are excluded.
253+
func collectCACerts() (map[string]string, error) {
254+
localPath := constants.GetMinipath()
255+
certFiles := map[string]string{}
256+
257+
certsDir := filepath.Join(localPath, "certs")
258+
err := filepath.Walk(certsDir, func(hostpath string, info os.FileInfo, err error) error {
259+
if err != nil {
260+
return err
261+
}
262+
263+
if info != nil && !info.IsDir() {
264+
ext := strings.ToLower(filepath.Ext(hostpath))
265+
if ext == ".crt" || ext == ".pem" {
266+
validPem, err := isValidPEMCertificate(hostpath)
267+
if err != nil {
268+
return err
269+
}
270+
if validPem {
271+
filename := filepath.Base(hostpath)
272+
dst := fmt.Sprintf("%s.%s", strings.TrimSuffix(filename, ext), "pem")
273+
certFiles[hostpath] = path.Join(CACertificatesDir, dst)
274+
}
275+
}
276+
}
277+
return nil
278+
})
279+
if err != nil {
280+
return nil, errors.Wrapf(err, "provisioning: traversal certificates dir %s", certsDir)
281+
}
282+
283+
for _, excluded := range []string{"ca.pem", "cert.pem"} {
284+
certFiles[filepath.Join(certsDir, excluded)] = ""
285+
}
286+
287+
// populates minikube CA
288+
certFiles[filepath.Join(localPath, "ca.crt")] = path.Join(CACertificatesDir, "minikubeCA.pem")
289+
290+
filtered := map[string]string{}
291+
for k, v := range certFiles {
292+
if v != "" {
293+
filtered[k] = v
294+
}
295+
}
296+
return filtered, nil
297+
}
298+
299+
// getSubjectHash calculates Certificate Subject Hash for creating certificate symlinks
300+
func getSubjectHash(cmd command.Runner, filePath string) (string, error) {
301+
out, err := cmd.CombinedOutput(fmt.Sprintf("openssl x509 -hash -noout -in '%s'", filePath))
302+
if err != nil {
303+
return "", err
304+
}
305+
306+
stringHash := strings.TrimSpace(out)
307+
return stringHash, nil
308+
}
309+
310+
// configureCACerts looks up and installs all uploaded PEM certificates in /usr/share/ca-certificates to system-wide certificate store (/etc/ssl/certs).
311+
// OpenSSL binary required in minikube ISO
312+
func configureCACerts(cmd command.Runner, caCerts map[string]string) error {
313+
hasSSLBinary := true
314+
if err := cmd.Run("which openssl"); err != nil {
315+
hasSSLBinary = false
316+
}
317+
318+
if !hasSSLBinary && len(caCerts) > 0 {
319+
glog.Warning("OpenSSL not found. Please recreate the cluster with the latest minikube ISO.")
320+
}
321+
322+
for _, caCertFile := range caCerts {
323+
dstFilename := path.Base(caCertFile)
324+
certStorePath := path.Join(SSLCertStoreDir, dstFilename)
325+
if err := cmd.Run(fmt.Sprintf("sudo test -f '%s'", certStorePath)); err != nil {
326+
if err := cmd.Run(fmt.Sprintf("sudo ln -s '%s' '%s'", caCertFile, certStorePath)); err != nil {
327+
return errors.Wrapf(err, "error making symbol link for certificate %s", caCertFile)
328+
}
329+
}
330+
if hasSSLBinary {
331+
subjectHash, err := getSubjectHash(cmd, caCertFile)
332+
if err != nil {
333+
return errors.Wrapf(err, "error calculating subject hash for certificate %s", caCertFile)
334+
}
335+
subjectHashLink := path.Join(SSLCertStoreDir, fmt.Sprintf("%s.0", subjectHash))
336+
if err := cmd.Run(fmt.Sprintf("sudo test -f '%s'", subjectHashLink)); err != nil {
337+
if err := cmd.Run(fmt.Sprintf("sudo ln -s '%s' '%s'", certStorePath, subjectHashLink)); err != nil {
338+
return errors.Wrapf(err, "error making subject hash symbol link for certificate %s", caCertFile)
339+
}
340+
}
341+
}
342+
}
343+
344+
return nil
345+
}

pkg/minikube/bootstrapper/certs_test.go

+29
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ limitations under the License.
1717
package bootstrapper
1818

1919
import (
20+
"fmt"
2021
"os"
22+
"path"
2123
"path/filepath"
2224
"testing"
2325

@@ -39,10 +41,37 @@ func TestSetupCerts(t *testing.T) {
3941
ServiceCIDR: util.DefaultServiceCIDR,
4042
}
4143

44+
if err := os.Mkdir(filepath.Join(tempDir, "certs"), 0777); err != nil {
45+
t.Fatalf("error create certificate directory: %v", err)
46+
}
47+
48+
if err := util.GenerateCACert(
49+
filepath.Join(tempDir, "certs", "mycert.pem"),
50+
filepath.Join(tempDir, "certs", "mykey.pem"),
51+
"Test Certificate",
52+
); err != nil {
53+
t.Fatalf("error generating certificate: %v", err)
54+
}
55+
56+
cmdMap := map[string]string{}
57+
certFilenames := map[string]string{"ca.crt": "minikubeCA.pem", "mycert.pem": "mycert.pem"}
58+
for _, dst := range certFilenames {
59+
certFile := path.Join(CACertificatesDir, dst)
60+
certStorePath := path.Join(SSLCertStoreDir, dst)
61+
certNameHash := "abcdef"
62+
remoteCertHashLink := path.Join(SSLCertStoreDir, fmt.Sprintf("%s.0", certNameHash))
63+
cmdMap[fmt.Sprintf("sudo ln -s '%s' '%s'", certFile, certStorePath)] = "1"
64+
cmdMap[fmt.Sprintf("openssl x509 -hash -noout -in '%s'", certFile)] = certNameHash
65+
cmdMap[fmt.Sprintf("sudo ln -s '%s' '%s'", certStorePath, remoteCertHashLink)] = "1"
66+
}
67+
f.SetCommandToOutput(cmdMap)
68+
4269
var filesToBeTransferred []string
4370
for _, cert := range certs {
4471
filesToBeTransferred = append(filesToBeTransferred, filepath.Join(constants.GetMinipath(), cert))
4572
}
73+
filesToBeTransferred = append(filesToBeTransferred, filepath.Join(constants.GetMinipath(), "ca.crt"))
74+
filesToBeTransferred = append(filesToBeTransferred, filepath.Join(constants.GetMinipath(), "certs", "mycert.pem"))
4675

4776
if err := SetupCerts(f, k8s); err != nil {
4877
t.Fatalf("Error starting cluster: %v", err)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
title: "Untrusted Root Certificate"
3+
linkTitle: "Untrusted Root Certificate"
4+
weight: 1
5+
date: 2019-08-15
6+
description: >
7+
Using minikube with Untrusted Root Certificate
8+
---
9+
10+
## Overview
11+
12+
Most organizations deploy their own Root Certificate and CA service inside the corporate networks.
13+
Internal websites, image repositories and other resources may install SSL server certificates issued by this CA service for security and privacy concerns.
14+
You may install the Root Certificate into the minikube VM to access these corporate resources within the cluster.
15+
16+
## Prerequisites
17+
18+
- Corporate X.509 Root Certificate
19+
- Latest minikube binary and ISO
20+
21+
## Tutorial
22+
23+
* The certificate must be in PEM format. You may use `openssl` to convert from DER format.
24+
25+
```
26+
openssl x509 -inform der -in my_company.cer -out my_company.pem
27+
```
28+
29+
* You may need to delete existing minikube VM
30+
31+
```shell
32+
minikube delete
33+
```
34+
35+
* Copy the certificate before creating the minikube VM
36+
37+
```shell
38+
mkdir -p $HOME/.minikube/certs
39+
cp my_company.pem $HOME/.minikube/certs/my_company.pem
40+
41+
minikube start
42+
```

0 commit comments

Comments
 (0)