GCE/GKE "pre-shared" TLS cert (#291)
* add allow-named-tls annotation * works for setting tls * fix logs (mostly) * add ssl cert annotation * return an error when cert not found * use annotation if specified, otherwise use spec * add TODO on naming * use the annotation key from k8s * add unit test for HTTPS LB w/ cert annotation * refactor logic and check for error * move annotation to controller package * remove todo for function naming
This commit is contained in:
parent
648f899751
commit
e1d1445370
4 changed files with 97 additions and 8 deletions
|
@ -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)
|
glog.Warningf("Cannot get key for Ingress %v/%v: %v", ing.Namespace, ing.Name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
tls, err := lbc.tlsLoader.load(&ing)
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
glog.Warningf("Cannot get certs for Ingress %v/%v: %v", ing.Namespace, ing.Name, err)
|
glog.Warningf("Cannot get certs for Ingress %v/%v: %v", ing.Namespace, ing.Name, err)
|
||||||
}
|
}
|
||||||
annotations := ingAnnotations(ing.ObjectMeta.Annotations)
|
}
|
||||||
|
|
||||||
lbs = append(lbs, &loadbalancers.L7RuntimeInfo{
|
lbs = append(lbs, &loadbalancers.L7RuntimeInfo{
|
||||||
Name: k,
|
Name: k,
|
||||||
TLS: tls,
|
TLS: tls,
|
||||||
|
TLSName: annotations.useNamedTLS(),
|
||||||
AllowHTTP: annotations.allowHTTP(),
|
AllowHTTP: annotations.allowHTTP(),
|
||||||
StaticIPName: annotations.staticIPName(),
|
StaticIPName: annotations.staticIPName(),
|
||||||
})
|
})
|
||||||
|
|
|
@ -52,6 +52,13 @@ const (
|
||||||
// responsibility to create/delete it.
|
// responsibility to create/delete it.
|
||||||
staticIPNameKey = "kubernetes.io/ingress.global-static-ip-name"
|
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
|
// ingressClassKey picks a specific "class" for the Ingress. The controller
|
||||||
// only processes Ingresses with this annotation either unset, or set
|
// only processes Ingresses with this annotation either unset, or set
|
||||||
// to either gceIngessClass or the empty string.
|
// to either gceIngessClass or the empty string.
|
||||||
|
@ -79,6 +86,16 @@ func (ing ingAnnotations) allowHTTP() bool {
|
||||||
return v
|
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 {
|
func (ing ingAnnotations) staticIPName() string {
|
||||||
val, ok := ing[staticIPNameKey]
|
val, ok := ing[staticIPNameKey]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
@ -246,6 +246,8 @@ type L7RuntimeInfo struct {
|
||||||
IP string
|
IP string
|
||||||
// TLS are the tls certs to use in termination.
|
// TLS are the tls certs to use in termination.
|
||||||
TLS *TLSCerts
|
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,
|
// AllowHTTP will not setup :80, if TLS is nil and AllowHTTP is set,
|
||||||
// no loadbalancer is created.
|
// no loadbalancer is created.
|
||||||
AllowHTTP bool
|
AllowHTTP bool
|
||||||
|
@ -350,6 +352,29 @@ func (l *L7) deleteOldSSLCert() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *L7) checkSSLCert() (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
|
// TODO: Currently, GCE only supports a single certificate per static IP
|
||||||
// so we don't need to bother with disambiguation. Naming the cert after
|
// so we don't need to bother with disambiguation. Naming the cert after
|
||||||
// the loadbalancer is a simplification.
|
// the loadbalancer is a simplification.
|
||||||
|
@ -363,7 +388,7 @@ func (l *L7) checkSSLCert() (err error) {
|
||||||
// TODO: Clean this code up into a ring buffer.
|
// TODO: Clean this code up into a ring buffer.
|
||||||
primaryCertName := l.namer.Truncate(fmt.Sprintf("%v-%v", sslCertPrefix, l.Name))
|
primaryCertName := l.namer.Truncate(fmt.Sprintf("%v-%v", sslCertPrefix, l.Name))
|
||||||
secondaryCertName := l.namer.Truncate(fmt.Sprintf("%v-%d-%v", sslCertPrefix, 1, l.Name))
|
secondaryCertName := l.namer.Truncate(fmt.Sprintf("%v-%d-%v", sslCertPrefix, 1, l.Name))
|
||||||
certName := primaryCertName
|
certName = primaryCertName
|
||||||
if l.sslCert != nil {
|
if l.sslCert != nil {
|
||||||
certName = l.sslCert.Name
|
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.
|
// 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 {
|
if err := l.checkStaticIP(); err != nil {
|
||||||
return err
|
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)
|
glog.V(3).Infof("validating https for %v", l.Name)
|
||||||
if err := l.edgeHopHttps(); err != nil {
|
if err := l.edgeHopHttps(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -846,7 +871,8 @@ func (l *L7) Cleanup() error {
|
||||||
}
|
}
|
||||||
l.tps = nil
|
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)
|
glog.Infof("Deleting sslcert %v", l.sslCert.Name)
|
||||||
if err := l.cloud.DeleteSslCertificate(l.sslCert.Name); err != nil {
|
if err := l.cloud.DeleteSslCertificate(l.sslCert.Name); err != nil {
|
||||||
if !utils.IsHTTPErrorCode(err, http.StatusNotFound) {
|
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 {
|
if l7.ip != nil {
|
||||||
existing[fmt.Sprintf("%v/static-ip", utils.K8sAnnotationPrefix)] = l7.ip.Name
|
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.
|
// TODO: We really want to know *when* a backend flipped states.
|
||||||
existing[fmt.Sprintf("%v/backends", utils.K8sAnnotationPrefix)] = jsonBackendState
|
existing[fmt.Sprintf("%v/backends", utils.K8sAnnotationPrefix)] = jsonBackendState
|
||||||
return existing
|
return existing
|
||||||
|
|
|
@ -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) {
|
func TestCreateBothLoadBalancers(t *testing.T) {
|
||||||
// This should create 2 forwarding rules and target proxies
|
// This should create 2 forwarding rules and target proxies
|
||||||
// but they should use the same urlmap, and have the same
|
// but they should use the same urlmap, and have the same
|
||||||
|
|
Loading…
Reference in a new issue