Browse code

add service serving cert controller

deads2k authored on 2016/02/19 06:35:27
Showing 20 changed files
... ...
@@ -189,6 +189,7 @@ func (o CreateMasterCertsOptions) CreateMasterCerts() error {
189 189
 		func() error { return o.createKubeletClientCerts(&getSignerCertOptions) },
190 190
 		func() error { return o.createProxyClientCerts(&getSignerCertOptions) },
191 191
 		func() error { return o.createServiceAccountKeys() },
192
+		func() error { return o.createServiceSigningCA(&getSignerCertOptions) },
192 193
 	)
193 194
 	return utilerrors.NewAggregate(errs)
194 195
 }
... ...
@@ -320,3 +321,24 @@ func (o CreateMasterCertsOptions) createServiceAccountKeys() error {
320 320
 	}
321 321
 	return nil
322 322
 }
323
+
324
+func (o CreateMasterCertsOptions) createServiceSigningCA(getSignerCertOptions *SignerCertOptions) error {
325
+	caInfo := DefaultServiceSignerCAInfo(o.CertDir)
326
+
327
+	caOptions := CreateSignerCertOptions{
328
+		CertFile:   caInfo.CertFile,
329
+		KeyFile:    caInfo.KeyFile,
330
+		SerialFile: "", // we want the random cert serial for this one
331
+		Name:       DefaultServiceServingCertSignerName(),
332
+		Output:     o.Output,
333
+
334
+		Overwrite: o.Overwrite,
335
+	}
336
+	if err := caOptions.Validate(nil); err != nil {
337
+		return err
338
+	}
339
+	if _, err := caOptions.CreateSignerCert(); err != nil {
340
+		return err
341
+	}
342
+	return nil
343
+}
... ...
@@ -250,7 +250,7 @@ func (o CreateNodeConfigOptions) CreateNodeFolder() error {
250 250
 		return err
251 251
 	}
252 252
 	if o.UseTLS() {
253
-		if err := o.MakeServerCert(serverCertFile, serverKeyFile); err != nil {
253
+		if err := o.MakeAndWriteServerCert(serverCertFile, serverKeyFile); err != nil {
254 254
 			return err
255 255
 		}
256 256
 		if o.UseNodeClientCA() {
... ...
@@ -309,7 +309,7 @@ func (o CreateNodeConfigOptions) MakeClientCert(clientCertFile, clientKeyFile st
309 309
 	return nil
310 310
 }
311 311
 
312
-func (o CreateNodeConfigOptions) MakeServerCert(serverCertFile, serverKeyFile string) error {
312
+func (o CreateNodeConfigOptions) MakeAndWriteServerCert(serverCertFile, serverKeyFile string) error {
313 313
 	if o.IsCreateServerCertificate() {
314 314
 		nodeServerCertOptions := CreateServerCertOptions{
315 315
 			SignerCertOptions: o.SignerCertOptions,
... ...
@@ -113,7 +113,7 @@ func (o CreateServerCertOptions) CreateServerCert() (*crypto.TLSCertificateConfi
113 113
 	var ca *crypto.TLSCertificateConfig
114 114
 	written := true
115 115
 	if o.Overwrite {
116
-		ca, err = signerCert.MakeServerCert(o.CertFile, o.KeyFile, sets.NewString([]string(o.Hostnames)...))
116
+		ca, err = signerCert.MakeAndWriteServerCert(o.CertFile, o.KeyFile, sets.NewString([]string(o.Hostnames)...))
117 117
 	} else {
118 118
 		ca, written, err = signerCert.EnsureServerCert(o.CertFile, o.KeyFile, sets.NewString([]string(o.Hostnames)...))
119 119
 	}
... ...
@@ -32,6 +32,10 @@ func DefaultCABundleFile(certDir string) string {
32 32
 	return DefaultCertFilename(certDir, CABundlePrefix)
33 33
 }
34 34
 
35
+func DefaultServiceServingCertSignerName() string {
36
+	return fmt.Sprintf("%s@%d", "openshift-service-serving-signer", time.Now().Unix())
37
+}
38
+
35 39
 func DefaultRootCAFile(certDir string) string {
36 40
 	return DefaultCertFilename(certDir, CAFilePrefix)
37 41
 }
... ...
@@ -207,6 +211,13 @@ func DefaultNodeKubeConfigFile(nodeDir string) string {
207 207
 	return path.Join(nodeDir, "node.kubeconfig")
208 208
 }
209 209
 
210
+func DefaultServiceSignerCAInfo(certDir string) configapi.CertInfo {
211
+	caInfo := configapi.CertInfo{}
212
+	caInfo.CertFile = DefaultCAFilename(certDir, "service-signer")
213
+	caInfo.KeyFile = DefaultKeyFilename(certDir, "service-signer")
214
+	return caInfo
215
+}
216
+
210 217
 func DefaultCAFilename(certDir, prefix string) string {
211 218
 	return path.Join(certDir, prefix+".crt")
212 219
 }
... ...
@@ -251,6 +251,11 @@ func GetMasterFileReferences(config *MasterConfig) []*string {
251 251
 
252 252
 	refs = append(refs, &config.PolicyConfig.BootstrapPolicyFile)
253 253
 
254
+	if config.ControllerConfig.ServiceServingCert.Signer != nil {
255
+		refs = append(refs, &config.ControllerConfig.ServiceServingCert.Signer.CertFile)
256
+		refs = append(refs, &config.ControllerConfig.ServiceServingCert.Signer.KeyFile)
257
+	}
258
+
254 259
 	return refs
255 260
 }
256 261
 
... ...
@@ -237,6 +237,8 @@ type MasterConfig struct {
237 237
 	// AdmissionConfig contains admission control plugin configuration.
238 238
 	AdmissionConfig AdmissionConfig
239 239
 
240
+	ControllerConfig ControllerConfig
241
+
240 242
 	// Allow to disable OpenShift components
241 243
 	DisabledFeatures FeatureList
242 244
 
... ...
@@ -1166,3 +1168,18 @@ type AdmissionConfig struct {
1166 1166
 	// on the master. Order is significant. If empty, a default list of plugins is used.
1167 1167
 	PluginOrderOverride []string
1168 1168
 }
1169
+
1170
+// ControllerConfig holds configuration values for controllers
1171
+type ControllerConfig struct {
1172
+	// ServiceServingCert holds configuration for service serving cert signer which creates cert/key pairs for
1173
+	// pods fullfilling a service to serve with.
1174
+	ServiceServingCert ServiceServingCert
1175
+}
1176
+
1177
+// ServiceServingCert holds configuration for service serving cert signer which creates cert/key pairs for
1178
+// pods fullfilling a service to serve with.
1179
+type ServiceServingCert struct {
1180
+	// Signer holds the signing information used to automatically sign serving certificates.
1181
+	// If this value is nil, then certs are not signed automatically.
1182
+	Signer *CertInfo
1183
+}
... ...
@@ -105,6 +105,15 @@ func (CertInfo) SwaggerDoc() map[string]string {
105 105
 	return map_CertInfo
106 106
 }
107 107
 
108
+var map_ControllerConfig = map[string]string{
109
+	"":                   "ControllerConfig holds configuration values for controllers",
110
+	"serviceServingCert": "ServiceServingCert holds configuration for service serving cert signer which creates cert/key pairs for pods fullfilling a service to serve with.",
111
+}
112
+
113
+func (ControllerConfig) SwaggerDoc() map[string]string {
114
+	return map_ControllerConfig
115
+}
116
+
108 117
 var map_DNSConfig = map[string]string{
109 118
 	"":                      "DNSConfig holds the necessary configuration options for DNS",
110 119
 	"bindAddress":           "BindAddress is the ip:port to serve DNS on",
... ...
@@ -404,6 +413,7 @@ var map_MasterConfig = map[string]string{
404 404
 	"pauseControllers":       "PauseControllers instructs the master to not automatically start controllers, but instead to wait until a notification to the server is received before launching them.",
405 405
 	"controllerLeaseTTL":     "ControllerLeaseTTL enables controller election, instructing the master to attempt to acquire a lease before controllers start and renewing it within a number of seconds defined by this value. Setting this value non-negative forces pauseControllers=true. This value defaults off (0, or omitted) and controller election can be disabled with -1.",
406 406
 	"admissionConfig":        "AdmissionConfig contains admission control plugin configuration.",
407
+	"controllerConfig":       "ControllerConfig holds configuration values for controllers",
407 408
 	"disabledFeatures":       "DisabledFeatures is a list of features that should not be started.  We omitempty here because its very unlikely that anyone will want to manually disable features and we don't want to encourage it.",
408 409
 	"etcdStorageConfig":      "EtcdStorageConfig contains information about how API resources are stored in Etcd. These values are only relevant when etcd is the backing store for the cluster.",
409 410
 	"etcdClientInfo":         "EtcdClientInfo contains information about how to connect to etcd",
... ...
@@ -694,6 +704,15 @@ func (ServiceAccountConfig) SwaggerDoc() map[string]string {
694 694
 	return map_ServiceAccountConfig
695 695
 }
696 696
 
697
+var map_ServiceServingCert = map[string]string{
698
+	"":       "ServiceServingCert holds configuration for service serving cert signer which creates cert/key pairs for pods fullfilling a service to serve with.",
699
+	"signer": "Signer holds the signing information used to automatically sign serving certificates. If this value is nil, then certs are not signed automatically.",
700
+}
701
+
702
+func (ServiceServingCert) SwaggerDoc() map[string]string {
703
+	return map_ServiceServingCert
704
+}
705
+
697 706
 var map_ServingInfo = map[string]string{
698 707
 	"":                  "ServingInfo holds information about serving web pages",
699 708
 	"bindAddress":       "BindAddress is the ip:port to serve on",
... ...
@@ -176,6 +176,9 @@ type MasterConfig struct {
176 176
 	// AdmissionConfig contains admission control plugin configuration.
177 177
 	AdmissionConfig AdmissionConfig `json:"admissionConfig"`
178 178
 
179
+	// ControllerConfig holds configuration values for controllers
180
+	ControllerConfig ControllerConfig `json:"controllerConfig"`
181
+
179 182
 	// DisabledFeatures is a list of features that should not be started.  We
180 183
 	// omitempty here because its very unlikely that anyone will want to
181 184
 	// manually disable features and we don't want to encourage it.
... ...
@@ -1171,3 +1174,18 @@ type AdmissionConfig struct {
1171 1171
 	// on the master. Order is significant. If empty, a default list of plugins is used.
1172 1172
 	PluginOrderOverride []string `json:"pluginOrderOverride,omitempty"`
1173 1173
 }
1174
+
1175
+// ControllerConfig holds configuration values for controllers
1176
+type ControllerConfig struct {
1177
+	// ServiceServingCert holds configuration for service serving cert signer which creates cert/key pairs for
1178
+	// pods fullfilling a service to serve with.
1179
+	ServiceServingCert ServiceServingCert `json:"serviceServingCert"`
1180
+}
1181
+
1182
+// ServiceServingCert holds configuration for service serving cert signer which creates cert/key pairs for
1183
+// pods fullfilling a service to serve with.
1184
+type ServiceServingCert struct {
1185
+	// Signer holds the signing information used to automatically sign serving certificates.
1186
+	// If this value is nil, then certs are not signed automatically.
1187
+	Signer *CertInfo `json:"signer"`
1188
+}
... ...
@@ -102,6 +102,9 @@ assetConfig:
102 102
     maxRequestsInFlight: 0
103 103
     namedCertificates: null
104 104
     requestTimeoutSeconds: 0
105
+controllerConfig:
106
+  serviceServingCert:
107
+    signer: null
105 108
 controllerLeaseTTL: 0
106 109
 controllers: ""
107 110
 corsAllowedOrigins: null
... ...
@@ -183,6 +183,18 @@ func ValidateMasterConfig(config *api.MasterConfig, fldPath *field.Path) Validat
183 183
 		validationResults.AddErrors(ValidateAdmissionPluginConfig(config.AdmissionConfig.PluginConfig, fldPath.Child("admissionConfig", "pluginConfig"))...)
184 184
 	}
185 185
 
186
+	validationResults.Append(ValidateControllerConfig(config.ControllerConfig, fldPath.Child("controllerConfig")))
187
+
188
+	return validationResults
189
+}
190
+
191
+func ValidateControllerConfig(config api.ControllerConfig, fldPath *field.Path) ValidationResults {
192
+	validationResults := ValidationResults{}
193
+
194
+	if config.ServiceServingCert.Signer != nil {
195
+		validationResults.AddErrors(ValidateCertInfo(*config.ServiceServingCert.Signer, true, fldPath.Child("serviceServingCert.signer"))...)
196
+	}
197
+
186 198
 	return validationResults
187 199
 }
188 200
 
... ...
@@ -48,6 +48,9 @@ const (
48 48
 
49 49
 	InfraServiceLoadBalancerControllerServiceAccountName = "service-load-balancer-controller"
50 50
 	ServiceLoadBalancerControllerRoleName                = "system:service-load-balancer-controller"
51
+
52
+	ServiceServingCertServiceAccountName = "service-serving-cert-controller"
53
+	ServiceServingCertControllerRoleName = "system:service-serving-cert-controller"
51 54
 )
52 55
 
53 56
 type InfraServiceAccounts struct {
... ...
@@ -611,4 +614,29 @@ func init() {
611 611
 	if err != nil {
612 612
 		panic(err)
613 613
 	}
614
+
615
+	err = InfraSAs.addServiceAccount(
616
+		ServiceServingCertServiceAccountName,
617
+		authorizationapi.ClusterRole{
618
+			ObjectMeta: kapi.ObjectMeta{
619
+				Name: ServiceServingCertControllerRoleName,
620
+			},
621
+			Rules: []authorizationapi.PolicyRule{
622
+				{
623
+					APIGroups: []string{kapi.GroupName},
624
+					Verbs:     sets.NewString("list", "watch", "update"),
625
+					Resources: sets.NewString("services"),
626
+				},
627
+				{
628
+					APIGroups: []string{kapi.GroupName},
629
+					Verbs:     sets.NewString("get", "create"),
630
+					Resources: sets.NewString("secrets"),
631
+				},
632
+			},
633
+		},
634
+	)
635
+	if err != nil {
636
+		panic(err)
637
+	}
638
+
614 639
 }
... ...
@@ -77,6 +77,19 @@ func (c *TLSCertificateConfig) writeCertConfig(certFile, keyFile string) error {
77 77
 	}
78 78
 	return nil
79 79
 }
80
+func (c *TLSCertificateConfig) GetPEMBytes() ([]byte, []byte, error) {
81
+	certBytes, err := encodeCertificates(c.Certs...)
82
+	if err != nil {
83
+		return nil, nil, err
84
+	}
85
+	keyBytes, err := encodeKey(c.Key)
86
+	if err != nil {
87
+		return nil, nil, err
88
+	}
89
+
90
+	return certBytes, keyBytes, nil
91
+}
92
+
80 93
 func (c *TLSCARoots) writeCARoots(rootFile string) error {
81 94
 	if err := writeCertificates(rootFile, c.Roots...); err != nil {
82 95
 		return err
... ...
@@ -295,7 +308,7 @@ func MakeCA(certFile, keyFile, serialFile, name string) (*CA, error) {
295 295
 func (ca *CA) EnsureServerCert(certFile, keyFile string, hostnames sets.String) (*TLSCertificateConfig, bool, error) {
296 296
 	certConfig, err := GetServerCert(certFile, keyFile, hostnames)
297 297
 	if err != nil {
298
-		certConfig, err = ca.MakeServerCert(certFile, keyFile, hostnames)
298
+		certConfig, err = ca.MakeAndWriteServerCert(certFile, keyFile, hostnames)
299 299
 		return certConfig, true, err
300 300
 	}
301 301
 
... ...
@@ -320,9 +333,20 @@ func GetServerCert(certFile, keyFile string, hostnames sets.String) (*TLSCertifi
320 320
 	return nil, fmt.Errorf("Existing server certificate in %s was missing some hostnames (%v) or IP addresses (%v).", certFile, missingDns, missingIps)
321 321
 }
322 322
 
323
-func (ca *CA) MakeServerCert(certFile, keyFile string, hostnames sets.String) (*TLSCertificateConfig, error) {
323
+func (ca *CA) MakeAndWriteServerCert(certFile, keyFile string, hostnames sets.String) (*TLSCertificateConfig, error) {
324 324
 	glog.V(4).Infof("Generating server certificate in %s, key in %s", certFile, keyFile)
325 325
 
326
+	server, err := ca.MakeServerCert(hostnames)
327
+	if err != nil {
328
+		return nil, err
329
+	}
330
+	if err := server.writeCertConfig(certFile, keyFile); err != nil {
331
+		return server, err
332
+	}
333
+	return server, nil
334
+}
335
+
336
+func (ca *CA) MakeServerCert(hostnames sets.String) (*TLSCertificateConfig, error) {
326 337
 	serverPublicKey, serverPrivateKey, _ := NewKeyPair()
327 338
 	serverTemplate, _ := newServerCertificateTemplate(pkix.Name{CommonName: hostnames.List()[0]}, hostnames.List())
328 339
 	serverCrt, err := ca.signCertificate(serverTemplate, serverPublicKey)
... ...
@@ -333,9 +357,6 @@ func (ca *CA) MakeServerCert(certFile, keyFile string, hostnames sets.String) (*
333 333
 		Certs: append([]*x509.Certificate{serverCrt}, ca.Config.Certs...),
334 334
 		Key:   serverPrivateKey,
335 335
 	}
336
-	if err := server.writeCertConfig(certFile, keyFile); err != nil {
337
-		return server, err
338
-	}
339 336
 	return server, nil
340 337
 }
341 338
 
... ...
@@ -13,6 +13,7 @@ import (
13 13
 	"k8s.io/kubernetes/pkg/admission"
14 14
 	kapi "k8s.io/kubernetes/pkg/api"
15 15
 	"k8s.io/kubernetes/pkg/api/unversioned"
16
+	kclient "k8s.io/kubernetes/pkg/client/unversioned"
16 17
 	clientadapter "k8s.io/kubernetes/pkg/client/unversioned/adapters/internalclientset"
17 18
 	"k8s.io/kubernetes/pkg/controller"
18 19
 	kresourcequota "k8s.io/kubernetes/pkg/controller/resourcequota"
... ...
@@ -28,6 +29,7 @@ import (
28 28
 	buildclient "github.com/openshift/origin/pkg/build/client"
29 29
 	buildcontrollerfactory "github.com/openshift/origin/pkg/build/controller/factory"
30 30
 	buildstrategy "github.com/openshift/origin/pkg/build/controller/strategy"
31
+	"github.com/openshift/origin/pkg/cmd/server/crypto"
31 32
 	"github.com/openshift/origin/pkg/cmd/server/etcd"
32 33
 	cmdutil "github.com/openshift/origin/pkg/cmd/util"
33 34
 	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
... ...
@@ -43,6 +45,7 @@ import (
43 43
 	"github.com/openshift/origin/pkg/security/mcs"
44 44
 	"github.com/openshift/origin/pkg/security/uid"
45 45
 	"github.com/openshift/origin/pkg/security/uidallocator"
46
+	servingcertcontroller "github.com/openshift/origin/pkg/service/controller/servingcert"
46 47
 
47 48
 	"github.com/openshift/openshift-sdn/plugins/osdn/factory"
48 49
 	configapi "github.com/openshift/origin/pkg/cmd/server/api"
... ...
@@ -371,6 +374,19 @@ func (c *MasterConfig) RunSDNController() {
371 371
 	}
372 372
 }
373 373
 
374
+func (c *MasterConfig) RunServiceServingCertController(client *kclient.Client) {
375
+	if c.Options.ControllerConfig.ServiceServingCert.Signer == nil {
376
+		return
377
+	}
378
+	ca, err := crypto.GetCA(c.Options.ControllerConfig.ServiceServingCert.Signer.CertFile, c.Options.ControllerConfig.ServiceServingCert.Signer.KeyFile, "")
379
+	if err != nil {
380
+		glog.Fatalf("service serving cert controller failed: %v", err)
381
+	}
382
+
383
+	servingCertController := servingcertcontroller.NewServiceServingCertController(client, client, ca, "cluster.local", 2*time.Minute)
384
+	go servingCertController.Run(1, make(chan struct{}))
385
+}
386
+
374 387
 // RunImageImportController starts the image import trigger controller process.
375 388
 func (c *MasterConfig) RunImageImportController() {
376 389
 	osclient := c.ImageImportControllerClient()
... ...
@@ -180,6 +180,8 @@ func (args MasterArgs) BuildSerializeableMasterConfig() (*configapi.MasterConfig
180 180
 
181 181
 	etcdClientInfo := admin.DefaultMasterEtcdClientCertInfo(args.ConfigDir.Value())
182 182
 
183
+	serviceServingCertSigner := admin.DefaultServiceSignerCAInfo(args.ConfigDir.Value())
184
+
183 185
 	dnsServingInfo := servingInfoForAddr(&dnsBindAddr)
184 186
 
185 187
 	config := &configapi.MasterConfig{
... ...
@@ -255,6 +257,12 @@ func (args MasterArgs) BuildSerializeableMasterConfig() (*configapi.MasterConfig
255 255
 		VolumeConfig: configapi.MasterVolumeConfig{
256 256
 			DynamicProvisioningEnabled: true,
257 257
 		},
258
+
259
+		ControllerConfig: configapi.ControllerConfig{
260
+			ServiceServingCert: configapi.ServiceServingCert{
261
+				Signer: &serviceServingCertSigner,
262
+			},
263
+		},
258 264
 	}
259 265
 
260 266
 	if args.ListenArg.UseTLS() {
... ...
@@ -641,6 +641,12 @@ func startControllers(oc *origin.MasterConfig, kc *kubernetes.MasterConfig) erro
641 641
 	oc.RunOriginNamespaceController()
642 642
 	oc.RunSDNController()
643 643
 
644
+	_, _, serviceServingCertClient, err := oc.GetServiceAccountClients(bootstrappolicy.ServiceServingCertServiceAccountName)
645
+	if err != nil {
646
+		glog.Fatalf("Could not get client: %v", err)
647
+	}
648
+	oc.RunServiceServingCertController(serviceServingCertClient)
649
+
644 650
 	glog.Infof("Started Origin Controllers")
645 651
 
646 652
 	return nil
647 653
new file mode 100644
... ...
@@ -0,0 +1,293 @@
0
+package servingcert
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"strconv"
6
+	"time"
7
+
8
+	"github.com/golang/glog"
9
+
10
+	kapi "k8s.io/kubernetes/pkg/api"
11
+	kapierrors "k8s.io/kubernetes/pkg/api/errors"
12
+	"k8s.io/kubernetes/pkg/client/cache"
13
+	kclient "k8s.io/kubernetes/pkg/client/unversioned"
14
+	"k8s.io/kubernetes/pkg/controller"
15
+	"k8s.io/kubernetes/pkg/controller/framework"
16
+	"k8s.io/kubernetes/pkg/runtime"
17
+	utilruntime "k8s.io/kubernetes/pkg/util/runtime"
18
+	"k8s.io/kubernetes/pkg/util/sets"
19
+	"k8s.io/kubernetes/pkg/util/wait"
20
+	"k8s.io/kubernetes/pkg/util/workqueue"
21
+	"k8s.io/kubernetes/pkg/watch"
22
+
23
+	"github.com/openshift/origin/pkg/cmd/server/crypto"
24
+)
25
+
26
+const (
27
+	// ServingCertSecretAnnotation stores the name of the secret to generate into.
28
+	ServingCertSecretAnnotation = "service.alpha.openshift.io/serving-cert-secret-name"
29
+	// ServingCertCreatedByAnnotation stores the of the signer common name.  This could be used later to see if the
30
+	// services need to have the the serving certs regenerated.  The presence and matching of this annotation prevents
31
+	// regeneration
32
+	ServingCertCreatedByAnnotation = "service.alpha.openshift.io/serving-cert-signed-by"
33
+	// ServingCertErrorAnnotation stores the error that caused cert generation failures.
34
+	ServingCertErrorAnnotation = "service.alpha.openshift.io/serving-cert-generation-error"
35
+	// ServingCertErrorNumAnnotation stores how many consecutive errors we've hit.  A value of the maxRetries will prevent
36
+	// the controller from reattempting until it is cleared.
37
+	ServingCertErrorNumAnnotation = "service.alpha.openshift.io/serving-cert-generation-error-num"
38
+	// ServiceUIDAnnotation is an annotation on a secret that indicates which service created it, by UID
39
+	ServiceUIDAnnotation = "service.alpha.openshift.io/originating-service-uid"
40
+	// ServiceNameAnnotation is an annotation on a secret that indicates which service created it, by Name to allow reverse lookups on services
41
+	// for comparison against UIDs
42
+	ServiceNameAnnotation = "service.alpha.openshift.io/originating-service-name"
43
+)
44
+
45
+// ServiceServingCertController is responsible for synchronizing Service objects stored
46
+// in the system with actual running replica sets and pods.
47
+type ServiceServingCertController struct {
48
+	serviceClient kclient.ServicesNamespacer
49
+	secretClient  kclient.SecretsNamespacer
50
+
51
+	// Services that need to be checked
52
+	queue      workqueue.RateLimitingInterface
53
+	maxRetries int
54
+
55
+	serviceCache      cache.Store
56
+	serviceController *framework.Controller
57
+
58
+	ca         *crypto.CA
59
+	publicCert string
60
+	dnsSuffix  string
61
+
62
+	// syncHandler does the work. It's factored out for unit testing
63
+	syncHandler func(serviceKey string) error
64
+}
65
+
66
+// NewServiceServingCertController creates a new ServiceServingCertController.
67
+// TODO this should accept a shared informer
68
+func NewServiceServingCertController(serviceClient kclient.ServicesNamespacer, secretClient kclient.SecretsNamespacer, ca *crypto.CA, dnsSuffix string, resyncInterval time.Duration) *ServiceServingCertController {
69
+	sc := &ServiceServingCertController{
70
+		serviceClient: serviceClient,
71
+		secretClient:  secretClient,
72
+
73
+		queue:      workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
74
+		maxRetries: 10,
75
+
76
+		ca:        ca,
77
+		dnsSuffix: dnsSuffix,
78
+	}
79
+
80
+	sc.serviceCache, sc.serviceController = framework.NewInformer(
81
+		&cache.ListWatch{
82
+			ListFunc: func(options kapi.ListOptions) (runtime.Object, error) {
83
+				return sc.serviceClient.Services(kapi.NamespaceAll).List(options)
84
+			},
85
+			WatchFunc: func(options kapi.ListOptions) (watch.Interface, error) {
86
+				return sc.serviceClient.Services(kapi.NamespaceAll).Watch(options)
87
+			},
88
+		},
89
+		&kapi.Service{},
90
+		resyncInterval,
91
+		framework.ResourceEventHandlerFuncs{
92
+			AddFunc: func(obj interface{}) {
93
+				service := obj.(*kapi.Service)
94
+				glog.V(4).Infof("Adding service %s", service.Name)
95
+				sc.enqueueService(obj)
96
+			},
97
+			UpdateFunc: func(old, cur interface{}) {
98
+				service := cur.(*kapi.Service)
99
+				glog.V(4).Infof("Updating service %s", service.Name)
100
+				// Resync on service object relist.
101
+				sc.enqueueService(cur)
102
+			},
103
+		},
104
+	)
105
+
106
+	sc.syncHandler = sc.syncService
107
+
108
+	return sc
109
+}
110
+
111
+// Run begins watching and syncing.
112
+func (sc *ServiceServingCertController) Run(workers int, stopCh <-chan struct{}) {
113
+	defer utilruntime.HandleCrash()
114
+	go sc.serviceController.Run(stopCh)
115
+	for i := 0; i < workers; i++ {
116
+		go wait.Until(sc.worker, time.Second, stopCh)
117
+	}
118
+
119
+	<-stopCh
120
+	glog.Infof("Shutting down service signing cert controller")
121
+	sc.queue.ShutDown()
122
+}
123
+
124
+func (sc *ServiceServingCertController) enqueueService(obj interface{}) {
125
+	key, err := controller.KeyFunc(obj)
126
+	if err != nil {
127
+		glog.Errorf("Couldn't get key for object %+v: %v", obj, err)
128
+		return
129
+	}
130
+
131
+	sc.queue.Add(key)
132
+}
133
+
134
+// worker runs a worker thread that just dequeues items, processes them, and marks them done.
135
+// It enforces that the syncHandler is never invoked concurrently with the same key.
136
+func (sc *ServiceServingCertController) worker() {
137
+	for {
138
+		if !sc.worker_inner() {
139
+			return
140
+		}
141
+	}
142
+}
143
+
144
+// worker_inner returns true if the worker thread should continue
145
+func (sc *ServiceServingCertController) worker_inner() bool {
146
+	key, quit := sc.queue.Get()
147
+	if quit {
148
+		return false
149
+	}
150
+	defer sc.queue.Done(key)
151
+
152
+	if err := sc.syncHandler(key.(string)); err == nil {
153
+		// this means the request was successfully handled.  We should "forget" the item so that any retry
154
+		// later on is reset
155
+		sc.queue.Forget(key)
156
+
157
+	} else {
158
+		// if we had an error it means that we didn't handle it, which means that we want to requeue the work
159
+		utilruntime.HandleError(fmt.Errorf("error syncing service, it will be retried: %v", err))
160
+		sc.queue.AddRateLimited(key)
161
+	}
162
+
163
+	return true
164
+}
165
+
166
+// syncService will sync the service with the given key.
167
+// This function is not meant to be invoked concurrently with the same key.
168
+func (sc *ServiceServingCertController) syncService(key string) error {
169
+	obj, exists, err := sc.serviceCache.GetByKey(key)
170
+	if err != nil {
171
+		glog.V(4).Infof("Unable to retrieve service %v from store: %v", key, err)
172
+		return err
173
+	}
174
+	if !exists {
175
+		glog.V(4).Infof("Service has been deleted %v", key)
176
+		return nil
177
+	}
178
+
179
+	// make a copy to avoid mutating cache state
180
+	t := *obj.(*kapi.Service)
181
+	service := &t
182
+	if !sc.requiresCertGeneration(service) {
183
+		return nil
184
+	}
185
+	if service.Annotations == nil {
186
+		service.Annotations = map[string]string{}
187
+	}
188
+
189
+	dnsName := service.Name + "." + service.Namespace + ".svc"
190
+	fqDNSName := dnsName + "." + sc.dnsSuffix
191
+	servingCert, err := sc.ca.MakeServerCert(sets.NewString(dnsName, fqDNSName))
192
+	if err != nil {
193
+		return err
194
+	}
195
+	certBytes, keyBytes, err := servingCert.GetPEMBytes()
196
+	if err != nil {
197
+		return err
198
+	}
199
+
200
+	secret := &kapi.Secret{
201
+		ObjectMeta: kapi.ObjectMeta{
202
+			Namespace: service.Namespace,
203
+			Name:      service.Annotations[ServingCertSecretAnnotation],
204
+			Annotations: map[string]string{
205
+				ServiceUIDAnnotation:  string(service.UID),
206
+				ServiceNameAnnotation: service.Name,
207
+			},
208
+		},
209
+		Type: kapi.SecretTypeTLS,
210
+		Data: map[string][]byte{
211
+			kapi.TLSCertKey:       certBytes,
212
+			kapi.TLSPrivateKeyKey: keyBytes,
213
+		},
214
+	}
215
+
216
+	_, err = sc.secretClient.Secrets(service.Namespace).Create(secret)
217
+	if err != nil && !kapierrors.IsAlreadyExists(err) {
218
+		// if we have an error creating the secret, then try to update the service with that information.  If it fails,
219
+		// then we'll just try again later on  re-list or because the service had already been updated and we'll get triggered again.
220
+		service.Annotations[ServingCertErrorAnnotation] = err.Error()
221
+		service.Annotations[ServingCertErrorNumAnnotation] = strconv.Itoa(getNumFailures(service) + 1)
222
+		_, updateErr := sc.serviceClient.Services(service.Namespace).Update(service)
223
+
224
+		// if we're past the max retries and we successfully updated, then the sync loop successfully handled this service and we want to forget it
225
+		if updateErr == nil && getNumFailures(service) >= sc.maxRetries {
226
+			return nil
227
+		}
228
+		return err
229
+	}
230
+	if kapierrors.IsAlreadyExists(err) {
231
+		actualSecret, err := sc.secretClient.Secrets(service.Namespace).Get(secret.Name)
232
+		if err != nil {
233
+			// if we have an error creating the secret, then try to update the service with that information.  If it fails,
234
+			// then we'll just try again later on  re-list or because the service had already been updated and we'll get triggered again.
235
+			service.Annotations[ServingCertErrorAnnotation] = err.Error()
236
+			service.Annotations[ServingCertErrorNumAnnotation] = strconv.Itoa(getNumFailures(service) + 1)
237
+			_, updateErr := sc.serviceClient.Services(service.Namespace).Update(service)
238
+
239
+			// if we're past the max retries and we successfully updated, then the sync loop successfully handled this service and we want to forget it
240
+			if updateErr == nil && getNumFailures(service) >= sc.maxRetries {
241
+				return nil
242
+			}
243
+			return err
244
+		}
245
+
246
+		if actualSecret.Annotations[ServiceUIDAnnotation] != string(service.UID) {
247
+			service.Annotations[ServingCertErrorAnnotation] = fmt.Sprintf("secret/%v references serviceUID %v, which does not match %v", actualSecret.Name, actualSecret.Annotations[ServiceUIDAnnotation], service.UID)
248
+			service.Annotations[ServingCertErrorNumAnnotation] = strconv.Itoa(getNumFailures(service) + 1)
249
+			_, updateErr := sc.serviceClient.Services(service.Namespace).Update(service)
250
+
251
+			// if we're past the max retries and we successfully updated, then the sync loop successfully handled this service and we want to forget it
252
+			if updateErr == nil && getNumFailures(service) >= sc.maxRetries {
253
+				return nil
254
+			}
255
+			return errors.New(service.Annotations[ServingCertErrorAnnotation])
256
+		}
257
+	}
258
+
259
+	service.Annotations[ServingCertCreatedByAnnotation] = sc.ca.Config.Certs[0].Subject.CommonName
260
+	delete(service.Annotations, ServingCertErrorAnnotation)
261
+	delete(service.Annotations, ServingCertErrorNumAnnotation)
262
+	_, err = sc.serviceClient.Services(service.Namespace).Update(service)
263
+
264
+	return err
265
+}
266
+
267
+func getNumFailures(service *kapi.Service) int {
268
+	numFailuresString := service.Annotations[ServingCertErrorNumAnnotation]
269
+	if len(numFailuresString) == 0 {
270
+		return 0
271
+	}
272
+
273
+	numFailures, err := strconv.Atoi(numFailuresString)
274
+	if err != nil {
275
+		return 0
276
+	}
277
+	return numFailures
278
+}
279
+
280
+func (sc *ServiceServingCertController) requiresCertGeneration(service *kapi.Service) bool {
281
+	if secretName := service.Annotations[ServingCertSecretAnnotation]; len(secretName) == 0 {
282
+		return false
283
+	}
284
+	if getNumFailures(service) >= sc.maxRetries {
285
+		return false
286
+	}
287
+	if service.Annotations[ServingCertCreatedByAnnotation] == sc.ca.Config.Certs[0].Subject.CommonName {
288
+		return false
289
+	}
290
+
291
+	return true
292
+}
0 293
new file mode 100644
... ...
@@ -0,0 +1,419 @@
0
+package servingcert
1
+
2
+import (
3
+	"fmt"
4
+	"io/ioutil"
5
+	"reflect"
6
+	"testing"
7
+	"time"
8
+
9
+	kapi "k8s.io/kubernetes/pkg/api"
10
+	kapierrors "k8s.io/kubernetes/pkg/api/errors"
11
+	ktestclient "k8s.io/kubernetes/pkg/client/unversioned/testclient"
12
+	"k8s.io/kubernetes/pkg/runtime"
13
+	"k8s.io/kubernetes/pkg/types"
14
+	"k8s.io/kubernetes/pkg/watch"
15
+
16
+	"github.com/openshift/origin/pkg/cmd/server/admin"
17
+)
18
+
19
+func controllerSetup(startingObjects []runtime.Object, stopChannel chan struct{}, t *testing.T) ( /*caName*/ string, *ktestclient.Fake, *watch.FakeWatcher, *ServiceServingCertController) {
20
+	certDir, err := ioutil.TempDir("", "serving-cert-unit-")
21
+	if err != nil {
22
+		t.Fatalf("unexpected error: %v", err)
23
+	}
24
+	caInfo := admin.DefaultServiceSignerCAInfo(certDir)
25
+
26
+	caOptions := admin.CreateSignerCertOptions{
27
+		CertFile: caInfo.CertFile,
28
+		KeyFile:  caInfo.KeyFile,
29
+		Name:     admin.DefaultServiceServingCertSignerName(),
30
+		Output:   ioutil.Discard,
31
+	}
32
+	ca, err := caOptions.CreateSignerCert()
33
+	if err != nil {
34
+		t.Fatalf("unexpected error: %v", err)
35
+	}
36
+
37
+	kubeclient := ktestclient.NewSimpleFake(startingObjects...)
38
+	fakeWatch := watch.NewFake()
39
+	kubeclient.PrependReactor("create", "*", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
40
+		return true, action.(ktestclient.CreateAction).GetObject(), nil
41
+	})
42
+	kubeclient.PrependReactor("update", "*", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
43
+		return true, action.(ktestclient.UpdateAction).GetObject(), nil
44
+	})
45
+	kubeclient.PrependWatchReactor("*", ktestclient.DefaultWatchReactor(fakeWatch, nil))
46
+
47
+	controller := NewServiceServingCertController(kubeclient, kubeclient, ca, "cluster.local", 10*time.Minute)
48
+
49
+	return caOptions.Name, kubeclient, fakeWatch, controller
50
+}
51
+
52
+func TestBasicControllerFlow(t *testing.T) {
53
+	stopChannel := make(chan struct{})
54
+	defer close(stopChannel)
55
+	received := make(chan bool)
56
+
57
+	caName, kubeclient, fakeWatch, controller := controllerSetup([]runtime.Object{}, stopChannel, t)
58
+	controller.syncHandler = func(serviceKey string) error {
59
+		defer func() { received <- true }()
60
+
61
+		err := controller.syncService(serviceKey)
62
+		if err != nil {
63
+			t.Errorf("unexpected error: %v", err)
64
+		}
65
+
66
+		return err
67
+	}
68
+	go controller.Run(1, stopChannel)
69
+
70
+	expectedSecretName := "new-secret"
71
+	serviceName := "svc-name"
72
+	serviceUID := "some-uid"
73
+	expectedServiceAnnotations := map[string]string{ServingCertSecretAnnotation: expectedSecretName, ServingCertCreatedByAnnotation: caName}
74
+	expectedSecretAnnotations := map[string]string{ServiceUIDAnnotation: serviceUID, ServiceNameAnnotation: serviceName}
75
+	namespace := "ns"
76
+
77
+	serviceToAdd := &kapi.Service{}
78
+	serviceToAdd.Name = serviceName
79
+	serviceToAdd.Namespace = namespace
80
+	serviceToAdd.UID = types.UID(serviceUID)
81
+	serviceToAdd.Annotations = map[string]string{ServingCertSecretAnnotation: expectedSecretName}
82
+	fakeWatch.Add(serviceToAdd)
83
+
84
+	t.Log("waiting to reach syncHandler")
85
+	select {
86
+	case <-received:
87
+	case <-time.After(time.Duration(10 * time.Second)):
88
+		t.Fatalf("failed to call into syncService")
89
+	}
90
+
91
+	foundSecret := false
92
+	foundServiceUpdate := false
93
+	for _, action := range kubeclient.Actions() {
94
+		switch {
95
+		case action.Matches("create", "secrets"):
96
+			createSecret := action.(ktestclient.CreateAction)
97
+			newSecret := createSecret.GetObject().(*kapi.Secret)
98
+			if newSecret.Name != expectedSecretName {
99
+				t.Errorf("expected %v, got %v", expectedSecretName, newSecret.Name)
100
+				continue
101
+			}
102
+			if newSecret.Namespace != namespace {
103
+				t.Errorf("expected %v, got %v", namespace, newSecret.Namespace)
104
+				continue
105
+			}
106
+			if !reflect.DeepEqual(newSecret.Annotations, expectedSecretAnnotations) {
107
+				t.Errorf("expected %v, got %v", expectedSecretAnnotations, newSecret.Annotations)
108
+				continue
109
+			}
110
+			foundSecret = true
111
+
112
+		case action.Matches("update", "services"):
113
+			updateService := action.(ktestclient.UpdateAction)
114
+			service := updateService.GetObject().(*kapi.Service)
115
+			if !reflect.DeepEqual(service.Annotations, expectedServiceAnnotations) {
116
+				t.Errorf("expected %v, got %v", expectedServiceAnnotations, service.Annotations)
117
+				continue
118
+			}
119
+			foundServiceUpdate = true
120
+
121
+		}
122
+	}
123
+
124
+	if !foundSecret {
125
+		t.Errorf("secret wasn't created.  Got %v\n", kubeclient.Actions())
126
+	}
127
+	if !foundServiceUpdate {
128
+		t.Errorf("service wasn't updated.  Got %v\n", kubeclient.Actions())
129
+	}
130
+}
131
+
132
+func TestAlreadyExistingSecretControllerFlow(t *testing.T) {
133
+	stopChannel := make(chan struct{})
134
+	defer close(stopChannel)
135
+	received := make(chan bool)
136
+
137
+	expectedSecretName := "new-secret"
138
+	serviceName := "svc-name"
139
+	serviceUID := "some-uid"
140
+	expectedSecretAnnotations := map[string]string{ServiceUIDAnnotation: serviceUID, ServiceNameAnnotation: serviceName}
141
+	namespace := "ns"
142
+
143
+	existingSecret := &kapi.Secret{}
144
+	existingSecret.Name = expectedSecretName
145
+	existingSecret.Namespace = namespace
146
+	existingSecret.Type = kapi.SecretTypeTLS
147
+	existingSecret.Annotations = expectedSecretAnnotations
148
+
149
+	caName, kubeclient, fakeWatch, controller := controllerSetup([]runtime.Object{existingSecret}, stopChannel, t)
150
+	kubeclient.PrependReactor("create", "secrets", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
151
+		return true, &kapi.Secret{}, kapierrors.NewAlreadyExists(kapi.Resource("secrets"), "new-secret")
152
+	})
153
+	controller.syncHandler = func(serviceKey string) error {
154
+		defer func() { received <- true }()
155
+
156
+		err := controller.syncService(serviceKey)
157
+		if err != nil {
158
+			t.Errorf("unexpected error: %v", err)
159
+		}
160
+
161
+		return err
162
+	}
163
+	go controller.Run(1, stopChannel)
164
+
165
+	expectedServiceAnnotations := map[string]string{ServingCertSecretAnnotation: expectedSecretName, ServingCertCreatedByAnnotation: caName}
166
+
167
+	serviceToAdd := &kapi.Service{}
168
+	serviceToAdd.Name = serviceName
169
+	serviceToAdd.Namespace = namespace
170
+	serviceToAdd.UID = types.UID(serviceUID)
171
+	serviceToAdd.Annotations = map[string]string{ServingCertSecretAnnotation: expectedSecretName}
172
+	fakeWatch.Add(serviceToAdd)
173
+
174
+	t.Log("waiting to reach syncHandler")
175
+	select {
176
+	case <-received:
177
+	case <-time.After(time.Duration(10 * time.Second)):
178
+		t.Fatalf("failed to call into syncService")
179
+	}
180
+
181
+	foundSecret := false
182
+	foundServiceUpdate := false
183
+	for _, action := range kubeclient.Actions() {
184
+		switch {
185
+		case action.Matches("get", "secrets"):
186
+			foundSecret = true
187
+
188
+		case action.Matches("update", "services"):
189
+			updateService := action.(ktestclient.UpdateAction)
190
+			service := updateService.GetObject().(*kapi.Service)
191
+			if !reflect.DeepEqual(service.Annotations, expectedServiceAnnotations) {
192
+				t.Errorf("expected %v, got %v", expectedServiceAnnotations, service.Annotations)
193
+				continue
194
+			}
195
+			foundServiceUpdate = true
196
+
197
+		}
198
+	}
199
+
200
+	if !foundSecret {
201
+		t.Errorf("secret wasn't retrieved.  Got %v\n", kubeclient.Actions())
202
+	}
203
+	if !foundServiceUpdate {
204
+		t.Errorf("service wasn't updated.  Got %v\n", kubeclient.Actions())
205
+	}
206
+
207
+}
208
+
209
+func TestAlreadyExistingSecretForDifferentUIDControllerFlow(t *testing.T) {
210
+	stopChannel := make(chan struct{})
211
+	defer close(stopChannel)
212
+	received := make(chan bool)
213
+
214
+	expectedError := "secret/new-secret references serviceUID wrong-uid, which does not match some-uid"
215
+	expectedSecretName := "new-secret"
216
+	serviceName := "svc-name"
217
+	serviceUID := "some-uid"
218
+	namespace := "ns"
219
+
220
+	existingSecret := &kapi.Secret{}
221
+	existingSecret.Name = expectedSecretName
222
+	existingSecret.Namespace = namespace
223
+	existingSecret.Type = kapi.SecretTypeTLS
224
+	existingSecret.Annotations = map[string]string{ServiceUIDAnnotation: "wrong-uid", ServiceNameAnnotation: serviceName}
225
+
226
+	_, kubeclient, fakeWatch, controller := controllerSetup([]runtime.Object{existingSecret}, stopChannel, t)
227
+	kubeclient.PrependReactor("create", "secrets", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
228
+		return true, &kapi.Secret{}, kapierrors.NewAlreadyExists(kapi.Resource("secrets"), "new-secret")
229
+	})
230
+	controller.syncHandler = func(serviceKey string) error {
231
+		defer func() { received <- true }()
232
+
233
+		err := controller.syncService(serviceKey)
234
+		if err != nil && err.Error() != expectedError {
235
+			t.Errorf("unexpected error: %v", err)
236
+		}
237
+
238
+		return err
239
+	}
240
+	go controller.Run(1, stopChannel)
241
+
242
+	expectedServiceAnnotations := map[string]string{ServingCertSecretAnnotation: expectedSecretName, ServingCertErrorAnnotation: expectedError, ServingCertErrorNumAnnotation: "1"}
243
+
244
+	serviceToAdd := &kapi.Service{}
245
+	serviceToAdd.Name = serviceName
246
+	serviceToAdd.Namespace = namespace
247
+	serviceToAdd.UID = types.UID(serviceUID)
248
+	serviceToAdd.Annotations = map[string]string{ServingCertSecretAnnotation: expectedSecretName}
249
+	fakeWatch.Add(serviceToAdd)
250
+
251
+	t.Log("waiting to reach syncHandler")
252
+	select {
253
+	case <-received:
254
+	case <-time.After(time.Duration(10 * time.Second)):
255
+		t.Fatalf("failed to call into syncService")
256
+	}
257
+
258
+	foundSecret := false
259
+	foundServiceUpdate := false
260
+	for _, action := range kubeclient.Actions() {
261
+		switch {
262
+		case action.Matches("get", "secrets"):
263
+			foundSecret = true
264
+
265
+		case action.Matches("update", "services"):
266
+			updateService := action.(ktestclient.UpdateAction)
267
+			service := updateService.GetObject().(*kapi.Service)
268
+			if !reflect.DeepEqual(service.Annotations, expectedServiceAnnotations) {
269
+				t.Errorf("expected %v, got %v", expectedServiceAnnotations, service.Annotations)
270
+				continue
271
+			}
272
+			foundServiceUpdate = true
273
+
274
+		}
275
+	}
276
+
277
+	if !foundSecret {
278
+		t.Errorf("secret wasn't retrieved.  Got %v\n", kubeclient.Actions())
279
+	}
280
+	if !foundServiceUpdate {
281
+		t.Errorf("service wasn't updated.  Got %v\n", kubeclient.Actions())
282
+	}
283
+}
284
+
285
+func TestSecretCreationErrorControllerFlow(t *testing.T) {
286
+	stopChannel := make(chan struct{})
287
+	defer close(stopChannel)
288
+	received := make(chan bool)
289
+
290
+	expectedError := `secrets "new-secret" is forbidden: any reason`
291
+	expectedSecretName := "new-secret"
292
+	serviceName := "svc-name"
293
+	serviceUID := "some-uid"
294
+	namespace := "ns"
295
+
296
+	_, kubeclient, fakeWatch, controller := controllerSetup([]runtime.Object{}, stopChannel, t)
297
+	kubeclient.PrependReactor("create", "secrets", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
298
+		return true, &kapi.Secret{}, kapierrors.NewForbidden(kapi.Resource("secrets"), "new-secret", fmt.Errorf("any reason"))
299
+	})
300
+	controller.syncHandler = func(serviceKey string) error {
301
+		defer func() { received <- true }()
302
+
303
+		err := controller.syncService(serviceKey)
304
+		if err != nil && err.Error() != expectedError {
305
+			t.Errorf("unexpected error: %v", err)
306
+		}
307
+
308
+		return err
309
+	}
310
+	go controller.Run(1, stopChannel)
311
+
312
+	expectedServiceAnnotations := map[string]string{ServingCertSecretAnnotation: expectedSecretName, ServingCertErrorAnnotation: expectedError, ServingCertErrorNumAnnotation: "1"}
313
+
314
+	serviceToAdd := &kapi.Service{}
315
+	serviceToAdd.Name = serviceName
316
+	serviceToAdd.Namespace = namespace
317
+	serviceToAdd.UID = types.UID(serviceUID)
318
+	serviceToAdd.Annotations = map[string]string{ServingCertSecretAnnotation: expectedSecretName}
319
+	fakeWatch.Add(serviceToAdd)
320
+
321
+	t.Log("waiting to reach syncHandler")
322
+	select {
323
+	case <-received:
324
+	case <-time.After(time.Duration(10 * time.Second)):
325
+		t.Fatalf("failed to call into syncService")
326
+	}
327
+
328
+	foundServiceUpdate := false
329
+	for _, action := range kubeclient.Actions() {
330
+		switch {
331
+		case action.Matches("update", "services"):
332
+			updateService := action.(ktestclient.UpdateAction)
333
+			service := updateService.GetObject().(*kapi.Service)
334
+			if !reflect.DeepEqual(service.Annotations, expectedServiceAnnotations) {
335
+				t.Errorf("expected %v, got %v", expectedServiceAnnotations, service.Annotations)
336
+				continue
337
+			}
338
+			foundServiceUpdate = true
339
+
340
+		}
341
+	}
342
+
343
+	if !foundServiceUpdate {
344
+		t.Errorf("service wasn't updated.  Got %v\n", kubeclient.Actions())
345
+	}
346
+}
347
+
348
+func TestSkipGenerationControllerFlow(t *testing.T) {
349
+	stopChannel := make(chan struct{})
350
+	defer close(stopChannel)
351
+	received := make(chan bool)
352
+
353
+	expectedSecretName := "new-secret"
354
+	serviceName := "svc-name"
355
+	serviceUID := "some-uid"
356
+	namespace := "ns"
357
+
358
+	caName, kubeclient, fakeWatch, controller := controllerSetup([]runtime.Object{}, stopChannel, t)
359
+	kubeclient.PrependReactor("update", "service", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
360
+		return true, &kapi.Service{}, kapierrors.NewForbidden(kapi.Resource("fdsa"), "new-service", fmt.Errorf("any service reason"))
361
+	})
362
+	kubeclient.PrependReactor("create", "secret", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
363
+		return true, &kapi.Secret{}, kapierrors.NewForbidden(kapi.Resource("asdf"), "new-secret", fmt.Errorf("any reason"))
364
+	})
365
+	kubeclient.PrependReactor("update", "secret", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
366
+		return true, &kapi.Secret{}, kapierrors.NewForbidden(kapi.Resource("asdf"), "new-secret", fmt.Errorf("any reason"))
367
+	})
368
+	controller.syncHandler = func(serviceKey string) error {
369
+		defer func() { received <- true }()
370
+
371
+		err := controller.syncService(serviceKey)
372
+		if err != nil {
373
+			t.Errorf("unexpected error: %v", err)
374
+		}
375
+
376
+		return err
377
+	}
378
+	go controller.Run(1, stopChannel)
379
+
380
+	serviceToAdd := &kapi.Service{}
381
+	serviceToAdd.Name = serviceName
382
+	serviceToAdd.Namespace = namespace
383
+	serviceToAdd.UID = types.UID(serviceUID)
384
+	serviceToAdd.Annotations = map[string]string{ServingCertSecretAnnotation: expectedSecretName, ServingCertErrorAnnotation: "any-error", ServingCertErrorNumAnnotation: "11"}
385
+	fakeWatch.Add(serviceToAdd)
386
+
387
+	t.Log("waiting to reach syncHandler")
388
+	select {
389
+	case <-received:
390
+	case <-time.After(time.Duration(10 * time.Second)):
391
+		t.Fatalf("failed to call into syncService")
392
+	}
393
+
394
+	for _, action := range kubeclient.Actions() {
395
+		switch action.GetVerb() {
396
+		case "update", "create":
397
+			t.Errorf("no mutation expected, but we got %v", action)
398
+		}
399
+	}
400
+
401
+	kubeclient.ClearActions()
402
+	serviceToAdd.Annotations = map[string]string{ServingCertSecretAnnotation: expectedSecretName, ServingCertCreatedByAnnotation: caName}
403
+	fakeWatch.Add(serviceToAdd)
404
+
405
+	t.Log("waiting to reach syncHandler")
406
+	select {
407
+	case <-received:
408
+	case <-time.After(time.Duration(10 * time.Second)):
409
+		t.Fatalf("failed to call into syncService")
410
+	}
411
+
412
+	for _, action := range kubeclient.Actions() {
413
+		switch action.GetVerb() {
414
+		case "update", "create":
415
+			t.Errorf("no mutation expected, but we got %v", action)
416
+		}
417
+	}
418
+}
... ...
@@ -11,6 +11,7 @@ set -o pipefail
11 11
 
12 12
 OS_ROOT=$(dirname "${BASH_SOURCE}")/../..
13 13
 source "${OS_ROOT}/hack/util.sh"
14
+source "${OS_ROOT}/hack/cmd_util.sh"
14 15
 source "${OS_ROOT}/hack/common.sh"
15 16
 source "${OS_ROOT}/hack/lib/log.sh"
16 17
 source "${OS_ROOT}/hack/cmd_util.sh"
... ...
@@ -56,7 +57,9 @@ install_registry
56 56
 wait_for_registry
57 57
 docker_registry="$( oc get service/docker-registry -n default -o jsonpath='{.spec.clusterIP}:{.spec.ports[0].port}' )"
58 58
 
59
+os::test::junit::declare_suite_start "extended/cmd"
59 60
 
61
+os::test::junit::declare_suite_start "extended/cmd/new-app"
60 62
 echo "[INFO] Running newapp extended tests"
61 63
 oc login "${MASTER_ADDR}" -u new-app -p password --certificate-authority="${MASTER_CONFIG_DIR}/ca.crt"
62 64
 oc new-project new-app
... ...
@@ -74,6 +77,7 @@ docker build -t test/scratchimage .
74 74
 popd
75 75
 rm -rf "${tmp}"
76 76
 
77
+
77 78
 # ensure a local-only image gets a docker image(not imagestream) reference created.
78 79
 VERBOSE=true os::cmd::expect_success "oc new-project test-scratchimage"
79 80
 os::cmd::expect_success "oc new-app test/scratchimage~https://github.com/openshift/ruby-hello-world.git --strategy=docker"
... ...
@@ -86,7 +90,9 @@ os::cmd::expect_failure_and_text "oc new-app test/scratchimage2 -o yaml" "partia
86 86
 # success with exact match	
87 87
 os::cmd::expect_success "oc new-app test/scratchimage"
88 88
 echo "[INFO] newapp: ok"
89
+os::test::junit::declare_suite_end
89 90
 
91
+os::test::junit::declare_suite_start "extended/cmd/variable-expansion"
90 92
 echo "[INFO] Running env variable expansion tests"
91 93
 VERBOSE=true os::cmd::expect_success "oc new-project envtest"
92 94
 os::cmd::expect_success "oc create -f test/extended/fixtures/test-env-pod.json"
... ...
@@ -97,7 +103,9 @@ os::cmd::expect_success_and_text "oc exec test-pod env" "var1=value1"
97 97
 os::cmd::expect_success_and_text "oc exec test-pod env" "var2=value1"
98 98
 os::cmd::expect_success_and_text "oc exec test-pod ps ax" "sleep 120"
99 99
 echo "[INFO] variable-expansion: ok"
100
+os::test::junit::declare_suite_end
100 101
 
102
+os::test::junit::declare_suite_start "extended/cmd/image-pull-secrets"
101 103
 echo "[INFO] Running image pull secrets tests"
102 104
 VERBOSE=true os::cmd::expect_success "oc login '${MASTER_ADDR}' -u pull-secrets-user -p password --certificate-authority='${MASTER_CONFIG_DIR}/ca.crt'"
103 105
 
... ...
@@ -143,3 +151,28 @@ os::cmd::expect_success "oc process -f test/extended/fixtures/image-pull-secrets
143 143
 os::cmd::try_until_text "oc get pods/my-dc-1-hook-pre -o jsonpath='{.status.containerStatuses[0].imageID}'" "docker"
144 144
 os::cmd::expect_success "oc delete all --all"
145 145
 os::cmd::expect_success "docker rmi -f ${docker_registry}/image-ns/busybox:latest"
146
+os::test::junit::declare_suite_end
147
+
148
+os::test::junit::declare_suite_start "extended/cmd/service-signer"
149
+# check to make sure that service serving cert signing works correctly
150
+# nginx currently needs to run as root
151
+os::cmd::expect_success "oc login -u system:admin -n default"
152
+os::cmd::expect_success "oadm policy add-scc-to-user anyuid system:serviceaccount:service-serving-cert-generation:default"
153
+
154
+os::cmd::expect_success "oc login -u serving-cert -p asdf"
155
+VERBOSE=true os::cmd::expect_success "oc new-project service-serving-cert-generation"
156
+
157
+os::cmd::expect_success 'oc create dc nginx --image=nginx -- sh -c "nginx -c /etc/nginx/nginx.conf && sleep 86400"'
158
+os::cmd::expect_success "oc expose dc/nginx --port=443"
159
+os::cmd::expect_success "oc annotate svc/nginx service.alpha.openshift.io/serving-cert-secret-name=nginx-ssl-key"
160
+os::cmd::expect_success "oc volumes dc/nginx --add --secret-name=nginx-ssl-key  --mount-path=/etc/serving-cert"
161
+os::cmd::expect_success "oc create configmap default-conf --from-file=test/extended/fixtures/service-serving-cert/nginx-serving-cert.conf"
162
+os::cmd::expect_success "oc set volumes dc/nginx --add --configmap-name=default-conf --mount-path=/etc/nginx/conf.d"
163
+os::cmd::try_until_text "oc get pods -l deployment-config.name=nginx" 'Running'
164
+
165
+# break mac os
166
+service_ip=$(oc get service/nginx -o=jsonpath={.spec.clusterIP})
167
+os::cmd::try_until_success "curl --cacert ${MASTER_CONFIG_DIR}/service-signer.crt --resolve nginx.service-serving-cert-generation.svc:443:${service_ip} https://nginx.service-serving-cert-generation.svc:443"
168
+os::test::junit::declare_suite_end
169
+
170
+os::test::junit::declare_suite_end
146 171
new file mode 100644
... ...
@@ -0,0 +1,48 @@
0
+server {
1
+    listen   443;
2
+
3
+    ssl    on;
4
+    ssl_certificate     /etc/serving-cert/tls.crt;
5
+    ssl_certificate_key    /etc/serving-cert/tls.key;
6
+    server_name  localhost;
7
+
8
+    #charset koi8-r;
9
+    #access_log  /var/log/nginx/log/host.access.log  main;
10
+
11
+    location / {
12
+        root   /usr/share/nginx/html;
13
+        index  index.html index.htm;
14
+    }
15
+
16
+    #error_page  404              /404.html;
17
+
18
+    # redirect server error pages to the static page /50x.html
19
+    #
20
+    error_page   500 502 503 504  /50x.html;
21
+    location = /50x.html {
22
+        root   /usr/share/nginx/html;
23
+    }
24
+
25
+    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
26
+    #
27
+    #location ~ \.php$ {
28
+    #    proxy_pass   http://127.0.0.1;
29
+    #}
30
+
31
+    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
32
+    #
33
+    #location ~ \.php$ {
34
+    #    root           html;
35
+    #    fastcgi_pass   127.0.0.1:9000;
36
+    #    fastcgi_index  index.php;
37
+    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
38
+    #    include        fastcgi_params;
39
+    #}
40
+
41
+    # deny access to .htaccess files, if Apache's document root
42
+    # concurs with nginx's one
43
+    #
44
+    #location ~ /\.ht {
45
+    #    deny  all;
46
+    #}
47
+}
... ...
@@ -1992,5 +1992,28 @@ items:
1992 1992
     - create
1993 1993
     - patch
1994 1994
     - update
1995
+- apiVersion: v1
1996
+  kind: ClusterRole
1997
+  metadata:
1998
+    creationTimestamp: null
1999
+    name: system:service-serving-cert-controller
2000
+  rules:
2001
+  - apiGroups:
2002
+    - ""
2003
+    attributeRestrictions: null
2004
+    resources:
2005
+    - services
2006
+    verbs:
2007
+    - list
2008
+    - update
2009
+    - watch
2010
+  - apiGroups:
2011
+    - ""
2012
+    attributeRestrictions: null
2013
+    resources:
2014
+    - secrets
2015
+    verbs:
2016
+    - create
2017
+    - get
1995 2018
 kind: List
1996 2019
 metadata: {}