Browse code

Allow extra trusted bundles when generating master certs, node config, or kubeconfig

Jordan Liggitt authored on 2016/03/08 07:28:00
Showing 15 changed files
... ...
@@ -2792,15 +2792,15 @@ _oadm_ca_create-master-certs()
2792 2792
     flags+=("--cert-dir=")
2793 2793
     flags_with_completion+=("--cert-dir")
2794 2794
     flags_completion+=("_filedir")
2795
+    flags+=("--certificate-authority=")
2796
+    flags_with_completion+=("--certificate-authority")
2797
+    flags_completion+=("_filedir")
2795 2798
     flags+=("--hostnames=")
2796 2799
     flags+=("--master=")
2797 2800
     flags+=("--overwrite")
2798 2801
     flags+=("--public-master=")
2799 2802
     flags+=("--signer-name=")
2800 2803
     flags+=("--api-version=")
2801
-    flags+=("--certificate-authority=")
2802
-    flags_with_completion+=("--certificate-authority")
2803
-    flags_completion+=("_filedir")
2804 2804
     flags+=("--client-certificate=")
2805 2805
     flags_with_completion+=("--client-certificate")
2806 2806
     flags_completion+=("_filedir")
... ...
@@ -5536,15 +5536,15 @@ _oc_adm_ca_create-master-certs()
5536 5536
     flags+=("--cert-dir=")
5537 5537
     flags_with_completion+=("--cert-dir")
5538 5538
     flags_completion+=("_filedir")
5539
+    flags+=("--certificate-authority=")
5540
+    flags_with_completion+=("--certificate-authority")
5541
+    flags_completion+=("_filedir")
5539 5542
     flags+=("--hostnames=")
5540 5543
     flags+=("--master=")
5541 5544
     flags+=("--overwrite")
5542 5545
     flags+=("--public-master=")
5543 5546
     flags+=("--signer-name=")
5544 5547
     flags+=("--api-version=")
5545
-    flags+=("--certificate-authority=")
5546
-    flags_with_completion+=("--certificate-authority")
5547
-    flags_completion+=("_filedir")
5548 5548
     flags+=("--client-certificate=")
5549 5549
     flags_with_completion+=("--client-certificate")
5550 5550
     flags_completion+=("_filedir")
... ...
@@ -228,6 +228,9 @@ _openshift_start_master()
228 228
     flags_with_completion=()
229 229
     flags_completion=()
230 230
 
231
+    flags+=("--certificate-authority=")
232
+    flags_with_completion+=("--certificate-authority")
233
+    flags_completion+=("_filedir")
231 234
     flags+=("--config=")
232 235
     flags_with_completion+=("--config")
233 236
     flags_completion+=("__handle_filename_extension_flag yaml|yml")
... ...
@@ -691,6 +694,9 @@ _openshift_start()
691 691
     flags_with_completion=()
692 692
     flags_completion=()
693 693
 
694
+    flags+=("--certificate-authority=")
695
+    flags_with_completion+=("--certificate-authority")
696
+    flags_completion+=("_filedir")
694 697
     flags+=("--cors-allowed-origins=")
695 698
     flags+=("--create-certs")
696 699
     flags+=("--dns=")
... ...
@@ -3347,15 +3353,15 @@ _openshift_admin_ca_create-master-certs()
3347 3347
     flags+=("--cert-dir=")
3348 3348
     flags_with_completion+=("--cert-dir")
3349 3349
     flags_completion+=("_filedir")
3350
+    flags+=("--certificate-authority=")
3351
+    flags_with_completion+=("--certificate-authority")
3352
+    flags_completion+=("_filedir")
3350 3353
     flags+=("--hostnames=")
3351 3354
     flags+=("--master=")
3352 3355
     flags+=("--overwrite")
3353 3356
     flags+=("--public-master=")
3354 3357
     flags+=("--signer-name=")
3355 3358
     flags+=("--api-version=")
3356
-    flags+=("--certificate-authority=")
3357
-    flags_with_completion+=("--certificate-authority")
3358
-    flags_completion+=("_filedir")
3359 3359
     flags+=("--client-certificate=")
3360 3360
     flags_with_completion+=("--client-certificate")
3361 3361
     flags_completion+=("_filedir")
... ...
@@ -9015,15 +9021,15 @@ _openshift_cli_adm_ca_create-master-certs()
9015 9015
     flags+=("--cert-dir=")
9016 9016
     flags_with_completion+=("--cert-dir")
9017 9017
     flags_completion+=("_filedir")
9018
+    flags+=("--certificate-authority=")
9019
+    flags_with_completion+=("--certificate-authority")
9020
+    flags_completion+=("_filedir")
9018 9021
     flags+=("--hostnames=")
9019 9022
     flags+=("--master=")
9020 9023
     flags+=("--overwrite")
9021 9024
     flags+=("--public-master=")
9022 9025
     flags+=("--signer-name=")
9023 9026
     flags+=("--api-version=")
9024
-    flags+=("--certificate-authority=")
9025
-    flags_with_completion+=("--certificate-authority")
9026
-    flags_completion+=("_filedir")
9027 9027
     flags+=("--client-certificate=")
9028 9028
     flags_with_completion+=("--client-certificate")
9029 9029
     flags_completion+=("_filedir")
... ...
@@ -777,9 +777,9 @@ os::util::host_platform() {
777 777
 
778 778
 os::util::sed() {
779 779
   if [[ "$(go env GOHOSTOS)" == "darwin" ]]; then
780
-  	sed -i '' $@
780
+  	sed -i '' "$@"
781 781
   else
782
-  	sed -i'' $@
782
+  	sed -i'' "$@"
783 783
   fi
784 784
 }
785 785
 
... ...
@@ -2,6 +2,7 @@ package admin
2 2
 
3 3
 import (
4 4
 	"errors"
5
+	"fmt"
5 6
 	"io"
6 7
 	"io/ioutil"
7 8
 
... ...
@@ -10,6 +11,7 @@ import (
10 10
 
11 11
 	kapi "k8s.io/kubernetes/pkg/api"
12 12
 	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
13
+	"k8s.io/kubernetes/pkg/util"
13 14
 )
14 15
 
15 16
 const CreateClientCommandName = "create-api-client-config"
... ...
@@ -23,7 +25,7 @@ type CreateClientOptions struct {
23 23
 	User   string
24 24
 	Groups []string
25 25
 
26
-	APIServerCAFile    string
26
+	APIServerCAFiles   []string
27 27
 	APIServerURL       string
28 28
 	PublicAPIServerURL string
29 29
 	Output             io.Writer
... ...
@@ -67,7 +69,7 @@ func NewCommandCreateClient(commandName string, fullName string, out io.Writer)
67 67
 
68 68
 	flags.StringVar(&options.APIServerURL, "master", "https://localhost:8443", "The API server's URL.")
69 69
 	flags.StringVar(&options.PublicAPIServerURL, "public-master", "", "The API public facing server's URL (if applicable).")
70
-	flags.StringVar(&options.APIServerCAFile, "certificate-authority", "openshift.local.config/master/ca.crt", "Path to the API server's CA file.")
70
+	flags.StringSliceVar(&options.APIServerCAFiles, "certificate-authority", []string{"openshift.local.config/master/ca.crt"}, "Files containing signing authorities to use to verify the API server's serving certificate.")
71 71
 
72 72
 	// autocompletion hints
73 73
 	cmd.MarkFlagFilename("client-dir")
... ...
@@ -89,8 +91,14 @@ func (o CreateClientOptions) Validate(args []string) error {
89 89
 	if len(o.APIServerURL) == 0 {
90 90
 		return errors.New("master must be provided")
91 91
 	}
92
-	if len(o.APIServerCAFile) == 0 {
92
+	if len(o.APIServerCAFiles) == 0 {
93 93
 		return errors.New("certificate-authority must be provided")
94
+	} else {
95
+		for _, caFile := range o.APIServerCAFiles {
96
+			if _, err := util.CertPoolFromFile(caFile); err != nil {
97
+				return fmt.Errorf("certificate-authority must be a valid certificate file: %v", err)
98
+			}
99
+		}
94 100
 	}
95 101
 
96 102
 	if o.SignerCertOptions == nil {
... ...
@@ -129,17 +137,17 @@ func (o CreateClientOptions) CreateClientFolder() error {
129 129
 		return err
130 130
 	}
131 131
 
132
-	// copy the CA file over
133
-	if caBytes, err := ioutil.ReadFile(o.APIServerCAFile); err != nil {
134
-		return err
135
-	} else if err := ioutil.WriteFile(clientCopyOfCAFile, caBytes, 0644); err != nil {
136
-		return nil
132
+	// copy the CA file(s) over
133
+	if caBytes, readErr := readFiles(o.APIServerCAFiles, []byte("\n")); readErr != nil {
134
+		return readErr
135
+	} else if writeErr := ioutil.WriteFile(clientCopyOfCAFile, caBytes, 0644); writeErr != nil {
136
+		return writeErr
137 137
 	}
138 138
 
139 139
 	createKubeConfigOptions := CreateKubeConfigOptions{
140 140
 		APIServerURL:       o.APIServerURL,
141 141
 		PublicAPIServerURL: o.PublicAPIServerURL,
142
-		APIServerCAFile:    clientCopyOfCAFile,
142
+		APIServerCAFiles:   []string{clientCopyOfCAFile},
143 143
 
144 144
 		CertFile: clientCertFile,
145 145
 		KeyFile:  clientKeyFile,
... ...
@@ -2,6 +2,7 @@ package admin
2 2
 
3 3
 import (
4 4
 	"errors"
5
+	"fmt"
5 6
 	"io"
6 7
 	"io/ioutil"
7 8
 	"os"
... ...
@@ -12,6 +13,7 @@ import (
12 12
 
13 13
 	kapi "k8s.io/kubernetes/pkg/api"
14 14
 	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
15
+	"k8s.io/kubernetes/pkg/util"
15 16
 
16 17
 	cliconfig "github.com/openshift/origin/pkg/cmd/cli/config"
17 18
 	"github.com/openshift/origin/pkg/cmd/server/crypto"
... ...
@@ -24,7 +26,7 @@ const CreateKubeConfigCommandName = "create-kubeconfig"
24 24
 type CreateKubeConfigOptions struct {
25 25
 	APIServerURL       string
26 26
 	PublicAPIServerURL string
27
-	APIServerCAFile    string
27
+	APIServerCAFiles   []string
28 28
 
29 29
 	CertFile string
30 30
 	KeyFile  string
... ...
@@ -87,7 +89,7 @@ users:
87 87
 
88 88
 	flags.StringVar(&options.APIServerURL, "master", "https://localhost:8443", "The API server's URL.")
89 89
 	flags.StringVar(&options.PublicAPIServerURL, "public-master", "", "The API public facing server's URL (if applicable).")
90
-	flags.StringVar(&options.APIServerCAFile, "certificate-authority", "openshift.local.config/master/ca.crt", "Path to the API server's CA file.")
90
+	flags.StringSliceVar(&options.APIServerCAFiles, "certificate-authority", []string{"openshift.local.config/master/ca.crt"}, "Files containing signing authorities to use to verify the API server's serving certificate.")
91 91
 	flags.StringVar(&options.CertFile, "client-certificate", "", "The client cert file.")
92 92
 	flags.StringVar(&options.KeyFile, "client-key", "", "The client key file.")
93 93
 	flags.StringVar(&options.ContextNamespace, "namespace", kapi.NamespaceDefault, "Namespace for this context in .kubeconfig.")
... ...
@@ -115,8 +117,14 @@ func (o CreateKubeConfigOptions) Validate(args []string) error {
115 115
 	if len(o.KeyFile) == 0 {
116 116
 		return errors.New("client-key must be provided")
117 117
 	}
118
-	if len(o.APIServerCAFile) == 0 {
118
+	if len(o.APIServerCAFiles) == 0 {
119 119
 		return errors.New("certificate-authority must be provided")
120
+	} else {
121
+		for _, caFile := range o.APIServerCAFiles {
122
+			if _, err := util.CertPoolFromFile(caFile); err != nil {
123
+				return fmt.Errorf("certificate-authority must be a valid certificate file: %v", err)
124
+			}
125
+		}
120 126
 	}
121 127
 	if len(o.ContextNamespace) == 0 {
122 128
 		return errors.New("namespace must be provided")
... ...
@@ -132,7 +140,7 @@ func (o CreateKubeConfigOptions) CreateKubeConfig() (*clientcmdapi.Config, error
132 132
 	glog.V(4).Infof("creating a .kubeconfig with: %#v", o)
133 133
 
134 134
 	// read all the referenced filenames
135
-	caData, err := ioutil.ReadFile(o.APIServerCAFile)
135
+	caData, err := readFiles(o.APIServerCAFiles, []byte("\n"))
136 136
 	if err != nil {
137 137
 		return nil, err
138 138
 	}
... ...
@@ -4,7 +4,9 @@ import (
4 4
 	"errors"
5 5
 	"fmt"
6 6
 	"io"
7
+	"io/ioutil"
7 8
 	"net/url"
9
+	"os"
8 10
 	"path/filepath"
9 11
 
10 12
 	"github.com/golang/glog"
... ...
@@ -12,6 +14,7 @@ import (
12 12
 
13 13
 	kapi "k8s.io/kubernetes/pkg/api"
14 14
 	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
15
+	"k8s.io/kubernetes/pkg/util"
15 16
 	utilerrors "k8s.io/kubernetes/pkg/util/errors"
16 17
 
17 18
 	"github.com/openshift/origin/pkg/util/parallel"
... ...
@@ -68,6 +71,9 @@ type CreateMasterCertsOptions struct {
68 68
 	CertDir    string
69 69
 	SignerName string
70 70
 
71
+	APIServerCAFiles []string
72
+	CABundleFile     string
73
+
71 74
 	Hostnames []string
72 75
 
73 76
 	APIServerURL       string
... ...
@@ -99,6 +105,7 @@ func NewCommandCreateMasterCerts(commandName string, fullName string, out io.Wri
99 99
 
100 100
 	flags.StringVar(&options.CertDir, "cert-dir", "openshift.local.config/master", "The certificate data directory.")
101 101
 	flags.StringVar(&options.SignerName, "signer-name", DefaultSignerName(), "The name to use for the generated signer.")
102
+	flags.StringSliceVar(&options.APIServerCAFiles, "certificate-authority", options.APIServerCAFiles, "Optional files containing signing authorities to use (in addition to the generated signer) to verify the API server's serving certificate.")
102 103
 
103 104
 	flags.StringVar(&options.APIServerURL, "master", "https://localhost:8443", "The API server's URL.")
104 105
 	flags.StringVar(&options.PublicAPIServerURL, "public-master", "", "The API public facing server's URL (if applicable).")
... ...
@@ -107,6 +114,7 @@ func NewCommandCreateMasterCerts(commandName string, fullName string, out io.Wri
107 107
 
108 108
 	// autocompletion hints
109 109
 	cmd.MarkFlagFilename("cert-dir")
110
+	cmd.MarkFlagFilename("certificate-authority")
110 111
 
111 112
 	return cmd
112 113
 }
... ...
@@ -140,6 +148,12 @@ func (o CreateMasterCertsOptions) Validate(args []string) error {
140 140
 		return errors.New("public master must be a valid URL (e.g. https://example.com:8443)")
141 141
 	}
142 142
 
143
+	for _, caFile := range o.APIServerCAFiles {
144
+		if _, err := util.CertPoolFromFile(caFile); err != nil {
145
+			return fmt.Errorf("certificate authority must be a valid certificate file: %v", err)
146
+		}
147
+	}
148
+
143 149
 	return nil
144 150
 }
145 151
 
... ...
@@ -168,6 +182,7 @@ func (o CreateMasterCertsOptions) CreateMasterCerts() error {
168 168
 	}
169 169
 
170 170
 	errs := parallel.Run(
171
+		func() error { return o.createCABundle(&getSignerCertOptions) },
171 172
 		func() error { return o.createServerCerts(&getSignerCertOptions) },
172 173
 		func() error { return o.createAPIClients(&getSignerCertOptions) },
173 174
 		func() error { return o.createEtcdClientCerts(&getSignerCertOptions) },
... ...
@@ -187,7 +202,7 @@ func (o CreateMasterCertsOptions) createAPIClients(getSignerCertOptions *SignerC
187 187
 		createKubeConfigOptions := CreateKubeConfigOptions{
188 188
 			APIServerURL:       o.APIServerURL,
189 189
 			PublicAPIServerURL: o.PublicAPIServerURL,
190
-			APIServerCAFile:    getSignerCertOptions.CertFile,
190
+			APIServerCAFiles:   append([]string{getSignerCertOptions.CertFile}, o.APIServerCAFiles...),
191 191
 
192 192
 			CertFile: clientCertInfo.CertLocation.CertFile,
193 193
 			KeyFile:  clientCertInfo.CertLocation.KeyFile,
... ...
@@ -252,6 +267,21 @@ func (o CreateMasterCertsOptions) createClientCert(clientCertInfo ClientCertInfo
252 252
 	return nil
253 253
 }
254 254
 
255
+func (o CreateMasterCertsOptions) createCABundle(getSignerCertOptions *SignerCertOptions) error {
256
+	caFiles := []string{getSignerCertOptions.CertFile}
257
+	caFiles = append(caFiles, o.APIServerCAFiles...)
258
+	caData, err := readFiles(caFiles, []byte("\n"))
259
+	if err != nil {
260
+		return err
261
+	}
262
+
263
+	// ensure parent dir
264
+	if err := os.MkdirAll(o.CertDir, os.FileMode(0755)); err != nil {
265
+		return err
266
+	}
267
+	return ioutil.WriteFile(DefaultCABundleFile(o.CertDir), caData, 0644)
268
+}
269
+
255 270
 func (o CreateMasterCertsOptions) createServerCerts(getSignerCertOptions *SignerCertOptions) error {
256 271
 	for _, serverCertInfo := range DefaultServerCerts(o.CertDir) {
257 272
 		serverCertOptions := CreateServerCertOptions{
... ...
@@ -18,6 +18,7 @@ import (
18 18
 	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
19 19
 	"k8s.io/kubernetes/pkg/master/ports"
20 20
 	"k8s.io/kubernetes/pkg/runtime"
21
+	"k8s.io/kubernetes/pkg/util"
21 22
 
22 23
 	"github.com/openshift/origin/pkg/cmd/flagtypes"
23 24
 	configapi "github.com/openshift/origin/pkg/cmd/server/api"
... ...
@@ -47,7 +48,7 @@ type CreateNodeConfigOptions struct {
47 47
 	ServerCertFile    string
48 48
 	ServerKeyFile     string
49 49
 	NodeClientCAFile  string
50
-	APIServerCAFile   string
50
+	APIServerCAFiles  []string
51 51
 	APIServerURL      string
52 52
 	Output            io.Writer
53 53
 	NetworkPluginName string
... ...
@@ -93,7 +94,7 @@ func NewCommandNodeConfig(commandName string, fullName string, out io.Writer) *c
93 93
 	flags.StringVar(&options.ServerKeyFile, "server-key", "", "The server key file for the node to serve secure traffic.")
94 94
 	flags.StringVar(&options.NodeClientCAFile, "node-client-certificate-authority", options.NodeClientCAFile, "The file containing signing authorities to use to verify requests to the node. If empty, all requests will be allowed.")
95 95
 	flags.StringVar(&options.APIServerURL, "master", options.APIServerURL, "The API server's URL.")
96
-	flags.StringVar(&options.APIServerCAFile, "certificate-authority", options.APIServerCAFile, "Path to the API server's CA file.")
96
+	flags.StringSliceVar(&options.APIServerCAFiles, "certificate-authority", options.APIServerCAFiles, "Files containing signing authorities to use to verify the API server's serving certificate.")
97 97
 	flags.StringVar(&options.NetworkPluginName, "network-plugin", options.NetworkPluginName, "Name of the network plugin to hook to for pod networking.")
98 98
 
99 99
 	// autocompletion hints
... ...
@@ -115,7 +116,7 @@ func NewDefaultCreateNodeConfigOptions() *CreateNodeConfigOptions {
115 115
 	// TODO: replace me with a proper round trip of config options through decode
116 116
 	options.DNSDomain = "cluster.local"
117 117
 	options.APIServerURL = "https://localhost:8443"
118
-	options.APIServerCAFile = "openshift.local.config/master/ca.crt"
118
+	options.APIServerCAFiles = []string{"openshift.local.config/master/ca.crt"}
119 119
 	options.NodeClientCAFile = "openshift.local.config/master/ca.crt"
120 120
 
121 121
 	options.ImageTemplate = variable.NewDefaultImageTemplate()
... ...
@@ -155,8 +156,14 @@ func (o CreateNodeConfigOptions) Validate(args []string) error {
155 155
 	if len(o.APIServerURL) == 0 {
156 156
 		return errors.New("--master must be provided")
157 157
 	}
158
-	if _, err := os.Stat(o.APIServerCAFile); len(o.APIServerCAFile) == 0 || err != nil {
159
-		return fmt.Errorf("--certificate-authority, %q must be a valid certificate file", cmdutil.GetDisplayFilename(o.APIServerCAFile))
158
+	if len(o.APIServerCAFiles) == 0 {
159
+		return fmt.Errorf("--certificate-authority must be a valid certificate file")
160
+	} else {
161
+		for _, caFile := range o.APIServerCAFiles {
162
+			if _, err := util.CertPoolFromFile(caFile); err != nil {
163
+				return fmt.Errorf("--certificate-authority must be a valid certificate file: %v", err)
164
+			}
165
+		}
160 166
 	}
161 167
 	if len(o.Hostnames) == 0 {
162 168
 		return errors.New("at least one hostname must be provided")
... ...
@@ -193,6 +200,23 @@ func (o CreateNodeConfigOptions) Validate(args []string) error {
193 193
 	return nil
194 194
 }
195 195
 
196
+// readFiles returns a byte array containing the contents of all the given filenames,
197
+// optionally separated by a delimiter, or an error if any of the files cannot be read
198
+func readFiles(srcFiles []string, separator []byte) ([]byte, error) {
199
+	data := []byte{}
200
+	for _, srcFile := range srcFiles {
201
+		fileData, err := ioutil.ReadFile(srcFile)
202
+		if err != nil {
203
+			return nil, err
204
+		}
205
+		if len(data) > 0 && len(separator) > 0 {
206
+			data = append(data, separator...)
207
+		}
208
+		data = append(data, fileData...)
209
+	}
210
+	return data, nil
211
+}
212
+
196 213
 func CopyFile(src, dest string, permissions os.FileMode) error {
197 214
 	// copy the cert and key over
198 215
 	if content, err := ioutil.ReadFile(src); err != nil {
... ...
@@ -317,11 +341,11 @@ func (o CreateNodeConfigOptions) MakeServerCert(serverCertFile, serverKeyFile st
317 317
 }
318 318
 
319 319
 func (o CreateNodeConfigOptions) MakeAPIServerCA(clientCopyOfCAFile string) error {
320
-	if err := CopyFile(o.APIServerCAFile, clientCopyOfCAFile, 0644); err != nil {
320
+	content, err := readFiles(o.APIServerCAFiles, []byte("\n"))
321
+	if err != nil {
321 322
 		return err
322 323
 	}
323
-
324
-	return nil
324
+	return ioutil.WriteFile(clientCopyOfCAFile, content, 0644)
325 325
 }
326 326
 
327 327
 func (o CreateNodeConfigOptions) MakeNodeClientCA(clientCopyOfCAFile string) error {
... ...
@@ -334,8 +358,8 @@ func (o CreateNodeConfigOptions) MakeNodeClientCA(clientCopyOfCAFile string) err
334 334
 
335 335
 func (o CreateNodeConfigOptions) MakeKubeConfig(clientCertFile, clientKeyFile, clientCopyOfCAFile, kubeConfigFile string) error {
336 336
 	createKubeConfigOptions := CreateKubeConfigOptions{
337
-		APIServerURL:    o.APIServerURL,
338
-		APIServerCAFile: clientCopyOfCAFile,
337
+		APIServerURL:     o.APIServerURL,
338
+		APIServerCAFiles: []string{clientCopyOfCAFile},
339 339
 
340 340
 		CertFile: clientCertFile,
341 341
 		KeyFile:  clientKeyFile,
... ...
@@ -39,14 +39,7 @@ func BindCreateSignerCertOptions(options *CreateSignerCertOptions, flags *pflag.
39 39
 }
40 40
 
41 41
 const createSignerLong = `
42
-Create a self-signed CA key/cert
43
-
44
-Create a self-signed CA key/cert for signing certificates used by server
45
-components.
46
-
47
-This is mainly intended for development/trial deployments as production
48
-deployments should utilize properly signed certificates (generated
49
-separately) or start with a properly signed CA.
42
+Create a self-signed CA key/cert for signing certificates used by server components.
50 43
 `
51 44
 
52 45
 func NewCommandCreateSignerCert(commandName string, fullName string, out io.Writer) *cobra.Command {
... ...
@@ -13,6 +13,7 @@ import (
13 13
 
14 14
 const (
15 15
 	CAFilePrefix     = "ca"
16
+	CABundlePrefix   = "ca-bundle"
16 17
 	MasterFilePrefix = "master"
17 18
 )
18 19
 
... ...
@@ -27,6 +28,10 @@ func DefaultSignerName() string {
27 27
 	return fmt.Sprintf("%s@%d", "openshift-signer", time.Now().Unix())
28 28
 }
29 29
 
30
+func DefaultCABundleFile(certDir string) string {
31
+	return DefaultCertFilename(certDir, CABundlePrefix)
32
+}
33
+
30 34
 func DefaultRootCAFile(certDir string) string {
31 35
 	return DefaultCertFilename(certDir, CAFilePrefix)
32 36
 }
... ...
@@ -63,6 +63,8 @@ type MasterArgs struct {
63 63
 	// CORS is enabled for localhost, 127.0.0.1, and the asset server by default.
64 64
 	CORSAllowedOrigins []string
65 65
 
66
+	APIServerCAFiles []string
67
+
66 68
 	ListenArg          *ListenArg
67 69
 	ImageFormatArgs    *ImageFormatArgs
68 70
 	KubeConnectionArgs *KubeConnectionArgs
... ...
@@ -84,6 +86,8 @@ func BindMasterArgs(args *MasterArgs, flags *pflag.FlagSet, prefix string) {
84 84
 
85 85
 	flags.StringVar(&args.EtcdDir, prefix+"etcd-dir", "openshift.local.etcd", "The etcd data directory.")
86 86
 
87
+	flags.StringSliceVar(&args.APIServerCAFiles, prefix+"certificate-authority", args.APIServerCAFiles, "Optional files containing signing authorities to use (in addition to the generated signer) to verify the API server's serving certificate.")
88
+
87 89
 	nodes := []string{}
88 90
 	flags.StringSliceVar(&nodes, prefix+"nodes", nodes, "DEPRECATED: nodes now register themselves")
89 91
 	flags.MarkDeprecated(prefix+"nodes", "Nodes register themselves at startup, and are no longer statically registered")
... ...
@@ -92,7 +96,7 @@ func BindMasterArgs(args *MasterArgs, flags *pflag.FlagSet, prefix string) {
92 92
 
93 93
 	// autocompletion hints
94 94
 	cobra.MarkFlagFilename(flags, prefix+"etcd-dir")
95
-
95
+	cobra.MarkFlagFilename(flags, prefix+"certificate-authority")
96 96
 }
97 97
 
98 98
 // NewDefaultMasterArgs creates MasterArgs with sub-objects created and default values set.
... ...
@@ -258,7 +262,7 @@ func (args MasterArgs) BuildSerializeableMasterConfig() (*configapi.MasterConfig
258 258
 		config.AssetConfig.ServingInfo.ServerCert = admin.DefaultAssetServingCertInfo(args.ConfigDir.Value())
259 259
 
260 260
 		if oauthConfig != nil {
261
-			s := admin.DefaultRootCAFile(args.ConfigDir.Value())
261
+			s := admin.DefaultCABundleFile(args.ConfigDir.Value())
262 262
 			oauthConfig.MasterCA = &s
263 263
 		}
264 264
 
... ...
@@ -266,7 +270,7 @@ func (args MasterArgs) BuildSerializeableMasterConfig() (*configapi.MasterConfig
266 266
 		if builtInKubernetes {
267 267
 			config.KubeletClientInfo.CA = admin.DefaultRootCAFile(args.ConfigDir.Value())
268 268
 			config.KubeletClientInfo.ClientCert = kubeletClientInfo.CertLocation
269
-			config.ServiceAccountConfig.MasterCA = admin.DefaultRootCAFile(args.ConfigDir.Value())
269
+			config.ServiceAccountConfig.MasterCA = admin.DefaultCABundleFile(args.ConfigDir.Value())
270 270
 		}
271 271
 
272 272
 		// Only set up ca/cert info for etcd connections if we're self-hosting etcd
... ...
@@ -312,6 +312,8 @@ func (o MasterOptions) CreateCerts() error {
312 312
 		SignerName:         signerName,
313 313
 		Hostnames:          hostnames.List(),
314 314
 		APIServerURL:       masterAddr.String(),
315
+		APIServerCAFiles:   o.MasterArgs.APIServerCAFiles,
316
+		CABundleFile:       admin.DefaultCABundleFile(o.MasterArgs.ConfigDir.Value()),
315 317
 		PublicAPIServerURL: publicMasterAddr.String(),
316 318
 		Output:             o.Output,
317 319
 	}
... ...
@@ -217,9 +217,9 @@ func (o NodeOptions) RunNode() error {
217 217
 
218 218
 func (o NodeOptions) CreateNodeConfig() error {
219 219
 	getSignerOptions := &admin.SignerCertOptions{
220
-		CertFile:   admin.DefaultCertFilename(o.NodeArgs.MasterCertDir, "ca"),
221
-		KeyFile:    admin.DefaultKeyFilename(o.NodeArgs.MasterCertDir, "ca"),
222
-		SerialFile: admin.DefaultSerialFilename(o.NodeArgs.MasterCertDir, "ca"),
220
+		CertFile:   admin.DefaultCertFilename(o.NodeArgs.MasterCertDir, admin.CAFilePrefix),
221
+		KeyFile:    admin.DefaultKeyFilename(o.NodeArgs.MasterCertDir, admin.CAFilePrefix),
222
+		SerialFile: admin.DefaultSerialFilename(o.NodeArgs.MasterCertDir, admin.CAFilePrefix),
223 223
 	}
224 224
 
225 225
 	var dnsIP string
... ...
@@ -253,8 +253,8 @@ func (o NodeOptions) CreateNodeConfig() error {
253 253
 		ListenAddr:          o.NodeArgs.ListenArg.ListenAddr,
254 254
 		NetworkPluginName:   o.NodeArgs.NetworkPluginName,
255 255
 
256
-		APIServerURL:    masterAddr.String(),
257
-		APIServerCAFile: getSignerOptions.CertFile,
256
+		APIServerURL:     masterAddr.String(),
257
+		APIServerCAFiles: []string{admin.DefaultCABundleFile(o.NodeArgs.MasterCertDir)},
258 258
 
259 259
 		NodeClientCAFile: getSignerOptions.CertFile,
260 260
 		Output:           o.Output,
261 261
new file mode 100755
... ...
@@ -0,0 +1,90 @@
0
+#!/bin/bash
1
+#
2
+# This scripts starts the OpenShift server with custom TLS certs, and verifies generated kubeconfig files can be used to talk to it.
3
+
4
+set -o errexit
5
+set -o nounset
6
+set -o pipefail
7
+
8
+OS_ROOT=$(dirname "${BASH_SOURCE}")/../..
9
+cd "${OS_ROOT}"
10
+source "${OS_ROOT}/hack/util.sh"
11
+source "${OS_ROOT}/hack/lib/log.sh"
12
+source "${OS_ROOT}/hack/lib/util/environment.sh"
13
+source "${OS_ROOT}/hack/cmd_util.sh"
14
+os::log::install_errexit
15
+
16
+os::util::environment::setup_all_server_vars "test-extended-alternate-certs/"
17
+reset_tmp_dir
18
+
19
+export EXTENDED_TEST_PATH="${OS_ROOT}/test/extended"
20
+
21
+function cleanup()
22
+{
23
+	out=$?
24
+    kill $OS_PID
25
+	echo "[INFO] Exiting"
26
+	exit $out
27
+}
28
+
29
+trap "exit" INT TERM
30
+trap "cleanup" EXIT
31
+
32
+
33
+echo "[INFO] Starting server as distinct processes"
34
+echo "[INFO] `openshift version`"
35
+echo "[INFO] Server logs will be at:    ${LOG_DIR}/openshift.log"
36
+echo "[INFO] Test artifacts will be in: ${ARTIFACT_DIR}"
37
+echo "[INFO] Config dir is:             ${SERVER_CONFIG_DIR}"
38
+
39
+mkdir -p ${LOG_DIR}
40
+
41
+echo "[INFO] Scan of OpenShift related processes already up via ps -ef	| grep openshift : "
42
+ps -ef | grep openshift
43
+
44
+mkdir -p "${SERVER_CONFIG_DIR}"
45
+pushd "${SERVER_CONFIG_DIR}"
46
+
47
+# Make custom CA and server cert
48
+os::cmd::expect_success 'oadm ca create-signer-cert --overwrite=true --cert=master/custom-ca.crt --key=master/custom-ca.key --serial=master/custom-ca.txt --name=my-custom-ca@`date +%s`'
49
+os::cmd::expect_success 'oadm ca create-server-cert --cert=master/custom.crt --key=master/custom.key --hostnames=localhost,customhost.com --signer-cert=master/custom-ca.crt --signer-key=master/custom-ca.key --signer-serial=master/custom-ca.txt'
50
+
51
+# Create master/node configs
52
+os::cmd::expect_success "openshift start --master=https://localhost:${API_PORT} --write-config=. --hostname=mynode --etcd-dir=./etcd --certificate-authority=master/custom-ca.crt"
53
+
54
+# Don't try this at home.  We don't have flags for setting etcd ports in the config, but we want deconflicted ones.  Use sed to replace defaults in a completely unsafe way
55
+os::util::sed "s/:4001$/:${ETCD_PORT}/g" master/master-config.yaml
56
+os::util::sed "s/:7001$/:${ETCD_PEER_PORT}/g" master/master-config.yaml
57
+# replace top-level namedCertificates config 
58
+os::util::sed 's#^  namedCertificates: null#  namedCertificates: [{"certFile":"custom.crt","keyFile":"custom.key","names":["localhost"]}]#' master/master-config.yaml 
59
+
60
+# Start master
61
+OPENSHIFT_PROFILE=web OPENSHIFT_ON_PANIC=crash openshift start master \
62
+ --config=master/master-config.yaml \
63
+ --loglevel=4 \
64
+&>"${LOG_DIR}/openshift.log" &
65
+OS_PID=$!
66
+
67
+# Wait for the server to be up
68
+os::cmd::try_until_success "oc whoami --config=master/admin.kubeconfig"
69
+
70
+# Verify the server is serving with the custom and internal CAs, and that the generated ca-bundle.crt works for both
71
+os::cmd::expect_success_and_text "curl -vvv https://localhost:${API_PORT} --cacert master/ca-bundle.crt -s 2>&1" 'my-custom-ca'
72
+os::cmd::expect_success_and_text "curl -vvv https://127.0.0.1:${API_PORT} --cacert master/ca-bundle.crt -s 2>&1" 'openshift-signer'
73
+
74
+# Verify kubeconfigs have connectivity to hosts serving with custom and generated certs
75
+os::cmd::expect_success_and_text "oc whoami --config=master/admin.kubeconfig"                                        'system:admin'
76
+os::cmd::expect_success_and_text "oc whoami --config=master/admin.kubeconfig --server=https://localhost:${API_PORT}" 'system:admin'
77
+os::cmd::expect_success_and_text "oc whoami --config=master/admin.kubeconfig --server=https://127.0.0.1:${API_PORT}" 'system:admin'
78
+
79
+os::cmd::expect_success_and_text "oc whoami --config=master/openshift-master.kubeconfig"                                        'system:openshift-master'
80
+os::cmd::expect_success_and_text "oc whoami --config=master/openshift-master.kubeconfig --server=https://localhost:${API_PORT}" 'system:openshift-master'
81
+os::cmd::expect_success_and_text "oc whoami --config=master/openshift-master.kubeconfig --server=https://127.0.0.1:${API_PORT}" 'system:openshift-master'
82
+
83
+os::cmd::expect_success_and_text "oc whoami --config=node-mynode/node.kubeconfig"                                        'system:node:mynode'
84
+os::cmd::expect_success_and_text "oc whoami --config=node-mynode/node.kubeconfig --server=https://localhost:${API_PORT}" 'system:node:mynode'
85
+os::cmd::expect_success_and_text "oc whoami --config=node-mynode/node.kubeconfig --server=https://127.0.0.1:${API_PORT}" 'system:node:mynode'
86
+
87
+kill $OS_PID
88
+
89
+popd
... ...
@@ -208,7 +208,7 @@ func CreateNodeCerts(nodeArgs *start.NodeArgs, masterURL string) error {
208 208
 	createNodeConfig.Hostnames = []string{nodeArgs.NodeName}
209 209
 	createNodeConfig.ListenAddr = nodeArgs.ListenArg.ListenAddr
210 210
 	createNodeConfig.APIServerURL = masterURL
211
-	createNodeConfig.APIServerCAFile = admin.DefaultCertFilename(nodeArgs.MasterCertDir, "ca")
211
+	createNodeConfig.APIServerCAFiles = []string{admin.DefaultCertFilename(nodeArgs.MasterCertDir, "ca")}
212 212
 	createNodeConfig.NodeClientCAFile = admin.DefaultCertFilename(nodeArgs.MasterCertDir, "ca")
213 213
 
214 214
 	if err := createNodeConfig.Validate(nil); err != nil {