diff --git a/controllers/gce/controller/controller.go b/controllers/gce/controller/controller.go index cb125fbc0..69f6a2533 100644 --- a/controllers/gce/controller/controller.go +++ b/controllers/gce/controller/controller.go @@ -427,14 +427,23 @@ func (lbc *LoadBalancerController) ListRuntimeInfo() (lbs []*loadbalancers.L7Run glog.Warningf("Cannot get key for Ingress %v/%v: %v", ing.Namespace, ing.Name, err) continue } - tls, err := lbc.tlsLoader.load(&ing) - if err != nil { - glog.Warningf("Cannot get certs for Ingress %v/%v: %v", ing.Namespace, ing.Name, err) - } + + var tls *loadbalancers.TLSCerts + annotations := ingAnnotations(ing.ObjectMeta.Annotations) + // Load the TLS cert from the API Spec if it is not specified in the annotation. + // TODO: enforce this with validation. + if annotations.useNamedTLS() == "" { + tls, err = lbc.tlsLoader.load(&ing) + if err != nil { + glog.Warningf("Cannot get certs for Ingress %v/%v: %v", ing.Namespace, ing.Name, err) + } + } + lbs = append(lbs, &loadbalancers.L7RuntimeInfo{ Name: k, TLS: tls, + TLSName: annotations.useNamedTLS(), AllowHTTP: annotations.allowHTTP(), StaticIPName: annotations.staticIPName(), }) diff --git a/controllers/gce/controller/utils.go b/controllers/gce/controller/utils.go index 617ce5fad..b57020cc3 100644 --- a/controllers/gce/controller/utils.go +++ b/controllers/gce/controller/utils.go @@ -52,6 +52,13 @@ const ( // responsibility to create/delete it. staticIPNameKey = "kubernetes.io/ingress.global-static-ip-name" + // preSharedCertKey represents the specific pre-shared SSL + // certicate for the Ingress controller to use. The controller *does not* + // manage this certificate, it is the users responsibility to create/delete it. + // In GCP, the Ingress controller assigns the SSL certificate with this name + // to the target proxies of the Ingress. + preSharedCertKey = "ingress.gcp.kubernetes.io/pre-shared-cert" + // ingressClassKey picks a specific "class" for the Ingress. The controller // only processes Ingresses with this annotation either unset, or set // to either gceIngessClass or the empty string. @@ -79,6 +86,16 @@ func (ing ingAnnotations) allowHTTP() bool { return v } +// useNamedTLS returns the name of the GCE SSL certificate. Empty by default. +func (ing ingAnnotations) useNamedTLS() string { + val, ok := ing[preSharedCertKey] + if !ok { + return "" + } + + return val +} + func (ing ingAnnotations) staticIPName() string { val, ok := ing[staticIPNameKey] if !ok { diff --git a/controllers/gce/loadbalancers/loadbalancers.go b/controllers/gce/loadbalancers/loadbalancers.go index 8e4ba99e4..3d3b2d529 100644 --- a/controllers/gce/loadbalancers/loadbalancers.go +++ b/controllers/gce/loadbalancers/loadbalancers.go @@ -246,6 +246,8 @@ type L7RuntimeInfo struct { IP string // TLS are the tls certs to use in termination. TLS *TLSCerts + // TLSName is the name of/for the tls cert to use. + TLSName string // AllowHTTP will not setup :80, if TLS is nil and AllowHTTP is set, // no loadbalancer is created. AllowHTTP bool @@ -350,6 +352,29 @@ func (l *L7) deleteOldSSLCert() (err error) { } func (l *L7) checkSSLCert() (err error) { + certName := l.runtimeInfo.TLSName + + // Use the named GCE cert when it is specified by the annotation. + if certName != "" { + // Use the targetHTTPSProxy's cert name if it already has one set. + if l.sslCert != nil { + certName = l.sslCert.Name + } + + // Ask GCE for the cert, checking for problems and existence. + cert, err := l.cloud.GetSslCertificate(certName) + if err != nil { + return err + } + if cert == nil { + return fmt.Errorf("Cannot find existing sslCertificate %v for %v", certName, l.Name) + } + + glog.Infof("Using existing sslCertificate %v for %v", certName, l.Name) + l.sslCert = cert + return nil + } + // TODO: Currently, GCE only supports a single certificate per static IP // so we don't need to bother with disambiguation. Naming the cert after // the loadbalancer is a simplification. @@ -363,7 +388,7 @@ func (l *L7) checkSSLCert() (err error) { // TODO: Clean this code up into a ring buffer. primaryCertName := l.namer.Truncate(fmt.Sprintf("%v-%v", sslCertPrefix, l.Name)) secondaryCertName := l.namer.Truncate(fmt.Sprintf("%v-%d-%v", sslCertPrefix, 1, l.Name)) - certName := primaryCertName + certName = primaryCertName if l.sslCert != nil { certName = l.sslCert.Name } @@ -581,12 +606,12 @@ func (l *L7) edgeHop() error { } } // Defer promoting an emphemral to a static IP till it's really needed. - if l.runtimeInfo.AllowHTTP && l.runtimeInfo.TLS != nil { + if l.runtimeInfo.AllowHTTP && (l.runtimeInfo.TLS != nil || l.runtimeInfo.TLSName != "") { if err := l.checkStaticIP(); err != nil { return err } } - if l.runtimeInfo.TLS != nil { + if l.runtimeInfo.TLS != nil || l.runtimeInfo.TLSName != "" { glog.V(3).Infof("validating https for %v", l.Name) if err := l.edgeHopHttps(); err != nil { return err @@ -846,7 +871,8 @@ func (l *L7) Cleanup() error { } l.tps = nil } - if l.sslCert != nil { + // Delete the SSL cert if it is not a pre-created GCE cert. + if l.sslCert != nil && l.sslCert.Name != l.runtimeInfo.TLSName { glog.Infof("Deleting sslcert %v", l.sslCert.Name) if err := l.cloud.DeleteSslCertificate(l.sslCert.Name); err != nil { if !utils.IsHTTPErrorCode(err, http.StatusNotFound) { @@ -936,6 +962,9 @@ func GetLBAnnotations(l7 *L7, existing map[string]string, backendPool backends.B if l7.ip != nil { existing[fmt.Sprintf("%v/static-ip", utils.K8sAnnotationPrefix)] = l7.ip.Name } + if l7.sslCert != nil { + existing[fmt.Sprintf("%v/ssl-cert", utils.K8sAnnotationPrefix)] = l7.sslCert.Name + } // TODO: We really want to know *when* a backend flipped states. existing[fmt.Sprintf("%v/backends", utils.K8sAnnotationPrefix)] = jsonBackendState return existing diff --git a/controllers/gce/loadbalancers/loadbalancers_test.go b/controllers/gce/loadbalancers/loadbalancers_test.go index 581f7010a..6ed940f14 100644 --- a/controllers/gce/loadbalancers/loadbalancers_test.go +++ b/controllers/gce/loadbalancers/loadbalancers_test.go @@ -103,6 +103,40 @@ func TestCreateHTTPSLoadBalancer(t *testing.T) { } } +func TestCreateHTTPSLoadBalancerAnnotationCert(t *testing.T) { + // This should NOT create the forwarding rule and target proxy + // associated with the HTTP branch of this loadbalancer. + tlsName := "external-cert-name" + lbInfo := &L7RuntimeInfo{ + Name: "test", + AllowHTTP: false, + TLSName: tlsName, + } + f := NewFakeLoadBalancers(lbInfo.Name) + f.CreateSslCertificate(&compute.SslCertificate{ + Name: tlsName, + }) + pool := newFakeLoadBalancerPool(f, t) + pool.Sync([]*L7RuntimeInfo{lbInfo}) + l7, err := pool.Get(lbInfo.Name) + if err != nil || l7 == nil { + t.Fatalf("Expected l7 not created") + } + um, err := f.GetUrlMap(f.umName()) + if err != nil || + um.DefaultService != pool.(*L7s).glbcDefaultBackend.SelfLink { + t.Fatalf("%v", err) + } + tps, err := f.GetTargetHttpsProxy(f.tpName(true)) + if err != nil || tps.UrlMap != um.SelfLink { + t.Fatalf("%v", err) + } + fws, err := f.GetGlobalForwardingRule(f.fwName(true)) + if err != nil || fws.Target != tps.SelfLink { + t.Fatalf("%v", err) + } +} + func TestCreateBothLoadBalancers(t *testing.T) { // This should create 2 forwarding rules and target proxies // but they should use the same urlmap, and have the same