Skip to content

Commit 7361943

Browse files
authored
[OCCM] add support for TLS terminated loadbalancers (kubernetes#1474)
* add support for TLS terminated loadbalancers * update docs
1 parent 37ba02c commit 7361943

File tree

5 files changed

+87
-12
lines changed

5 files changed

+87
-12
lines changed

docs/openstack-cloud-controller-manager/expose-applications-using-loadbalancer-type-service.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,9 @@ Request Body:
132132

133133
- `loadbalancer.openstack.org/x-forwarded-for`
134134

135-
If 'true', `X-Forwarded-For` is inserted into the HTTP headers which contains the original client IP address so that the backend HTTP service is able to get the real source IP of the request. Only applies when using Octavia.
135+
If 'true', `X-Forwarded-For` is inserted into the HTTP headers which contains the original client IP address so that the backend HTTP service is able to get the real source IP of the request. Please note that the cloud provider will force the creation of an Octavia listener of type `HTTP` if this option is set. Only applies when using Octavia.
136+
137+
This annotation also works in conjunction with the `loadbalancer.openstack.org/default-tls-container-ref` annotation. In this case the cloud provider will create an Octavia listener of type `TERMINATED_HTTPS` instead of an `HTTP` listener.
136138

137139
- `loadbalancer.openstack.org/timeout-client-data`
138140

@@ -166,6 +168,11 @@ Request Body:
166168

167169
The name of the loadbalancer availability zone to use. It is ignored if the Octavia version doesn't support availability zones yet.
168170

171+
- `loadbalancer.openstack.org/default-tls-container-ref`
172+
173+
Reference to a tls container. This option works with Octavia, when this option is set then the cloud provider will create an Octavia Listener of type `TERMINATED_HTTPS` for a TLS Terminated loadbalancer.
174+
Format for tls container ref: `https://{keymanager_host}/v1/containers/{uuid}`
175+
169176
### Switching between Floating Subnets by using preconfigured Classes
170177

171178
If you have multiple `FloatingIPPools` and/or `FloatingIPSubnets` it might be desirable to offer the user logical meanings for `LoadBalancers` like `internetFacing` or `DMZ` instead of requiring the user to select a dedicated network or subnet ID at the service object level as an annotation.

docs/openstack-cloud-controller-manager/using-openstack-cloud-controller-manager.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,11 @@ Although the openstack-cloud-controller-manager was initially implemented with N
244244
245245
This option is currently a workaround for the issue https://github.com/kubernetes/ingress-nginx/issues/3996, should be removed or refactored after the Kubernetes [KEP-1860](https://github.com/kubernetes/enhancements/tree/master/keps/sig-network/1860-kube-proxy-IP-node-binding) is implemented.
246246
247+
* `default-tls-container-ref`
248+
Reference to a tls container. This option works with Octavia, when this option is set then the cloud provider will create an Octavia Listener of type TERMINATED_HTTPS for a TLS Terminated loadbalancer.
249+
250+
Format for tls container ref: `https://{keymanager_host}/v1/containers/{uuid}`
251+
247252
### Metadata
248253
249254
* `search-order`

pkg/client/service.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,12 @@ func NewLoadBalancerV2(provider *gophercloud.ProviderClient, eo *gophercloud.End
6464
}
6565
return lb, nil
6666
}
67+
68+
// NewKeyManagerV1 creates a ServiceClient that can be used with KeyManager v1 API
69+
func NewKeyManagerV1(provider *gophercloud.ProviderClient, eo *gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
70+
secret, err := openstack.NewKeyManagerV1(provider, *eo)
71+
if err != nil {
72+
return nil, fmt.Errorf("unable to initialize keymanager client for region %s: %v", eo.Region, err)
73+
}
74+
return secret, nil
75+
}

pkg/openstack/loadbalancer.go

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"gopkg.in/godo.v2/glob"
3131

3232
"github.com/gophercloud/gophercloud"
33+
"github.com/gophercloud/gophercloud/openstack/keymanager/v1/containers"
3334
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners"
3435
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers"
3536
v2monitors "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors"
@@ -107,7 +108,7 @@ const (
107108
// ServiceAnnotationLoadBalancerEnableHealthMonitor defines whether or not to create health monitor for the load balancer
108109
// pool, if not specified, use 'create-monitor' config. The health monitor can be created or deleted dynamically.
109110
ServiceAnnotationLoadBalancerEnableHealthMonitor = "loadbalancer.openstack.org/enable-health-monitor"
110-
111+
ServiceAnnotationTlsContainerRef = "loadbalancer.openstack.org/default-tls-container-ref"
111112
// See https://nip.io
112113
defaultProxyHostnameSuffix = "nip.io"
113114
)
@@ -345,6 +346,7 @@ type serviceConfig struct {
345346
enableMonitor bool
346347
flavorID string
347348
availabilityZone string
349+
tlsContainerRef string
348350
}
349351

350352
type listenerKey struct {
@@ -1388,8 +1390,14 @@ func (lbaas *LbaasV2) buildPoolCreateOpt(listenerProtocol string, service *corev
13881390
poolProto := v2pools.Protocol(listenerProtocol)
13891391
if svcConf.enableProxyProtocol {
13901392
poolProto = v2pools.ProtocolPROXY
1391-
} else if svcConf.keepClientIP && poolProto != v2pools.ProtocolHTTP {
1392-
klog.V(4).Infof("Forcing to use %q protocol for pool because annotation %q is set", v2pools.ProtocolHTTP, ServiceAnnotationLoadBalancerXForwardedFor)
1393+
} else if (svcConf.keepClientIP || svcConf.tlsContainerRef != "") && poolProto != v2pools.ProtocolHTTP {
1394+
if svcConf.keepClientIP && svcConf.tlsContainerRef != "" {
1395+
klog.V(4).Infof("Forcing to use %q protocol for pool because annotations %q %q are set", v2pools.ProtocolHTTP, ServiceAnnotationLoadBalancerXForwardedFor, ServiceAnnotationTlsContainerRef)
1396+
} else if svcConf.keepClientIP {
1397+
klog.V(4).Infof("Forcing to use %q protocol for pool because annotation %q is set", v2pools.ProtocolHTTP, ServiceAnnotationLoadBalancerXForwardedFor)
1398+
} else {
1399+
klog.V(4).Infof("Forcing to use %q protocol for pool because annotations %q is set", v2pools.ProtocolHTTP, ServiceAnnotationTlsContainerRef)
1400+
}
13931401
poolProto = v2pools.ProtocolHTTP
13941402
}
13951403

@@ -1449,7 +1457,9 @@ func (lbaas *LbaasV2) ensureOctaviaListener(lbID string, oldListeners []listener
14491457
}
14501458

14511459
proto := toListenersProtocol(port.Protocol)
1452-
if svcConf.keepClientIP {
1460+
if svcConf.tlsContainerRef != "" {
1461+
proto = listeners.ProtocolTerminatedHTTPS
1462+
} else if svcConf.keepClientIP {
14531463
proto = listeners.ProtocolHTTP
14541464
}
14551465
listener, ok := lbListeners[listenerKey{
@@ -1478,16 +1488,24 @@ func (lbaas *LbaasV2) ensureOctaviaListener(lbID string, oldListeners []listener
14781488
updateOpts.ConnLimit = &svcConf.connLimit
14791489
listenerChanged = true
14801490
}
1481-
updateOpts.InsertHeaders = &listener.InsertHeaders
1491+
14821492
listenerKeepClientIP := listener.InsertHeaders[annotationXForwardedFor] == "true"
14831493
if svcConf.keepClientIP != listenerKeepClientIP {
1494+
updateOpts.InsertHeaders = &listener.InsertHeaders
14841495
if svcConf.keepClientIP {
1496+
if *updateOpts.InsertHeaders == nil {
1497+
*updateOpts.InsertHeaders = make(map[string]string)
1498+
}
14851499
(*updateOpts.InsertHeaders)[annotationXForwardedFor] = "true"
14861500
} else {
14871501
delete(*updateOpts.InsertHeaders, annotationXForwardedFor)
14881502
}
14891503
listenerChanged = true
14901504
}
1505+
if svcConf.tlsContainerRef != listener.DefaultTlsContainerRef {
1506+
updateOpts.DefaultTlsContainerRef = &svcConf.tlsContainerRef
1507+
listenerChanged = true
1508+
}
14911509
if openstackutil.IsOctaviaFeatureSupported(lbaas.lb, openstackutil.OctaviaFeatureTimeout) {
14921510
if svcConf.timeoutClientData != listener.TimeoutClientData {
14931511
updateOpts.TimeoutClientData = &svcConf.timeoutClientData
@@ -1542,13 +1560,22 @@ func (lbaas *LbaasV2) buildListenerCreateOpt(port corev1.ServicePort, svcConf *s
15421560
}
15431561

15441562
if svcConf.keepClientIP {
1545-
if listenerCreateOpt.Protocol != listeners.ProtocolHTTP {
1546-
klog.V(4).Infof("Forcing to use %q protocol for listener because %q annotation is set", listeners.ProtocolHTTP, ServiceAnnotationLoadBalancerXForwardedFor)
1547-
listenerCreateOpt.Protocol = listeners.ProtocolHTTP
1548-
}
15491563
listenerCreateOpt.InsertHeaders = map[string]string{annotationXForwardedFor: "true"}
15501564
}
15511565

1566+
if svcConf.tlsContainerRef != "" {
1567+
listenerCreateOpt.DefaultTlsContainerRef = svcConf.tlsContainerRef
1568+
}
1569+
1570+
// protocol selection
1571+
if svcConf.tlsContainerRef != "" && listenerCreateOpt.Protocol != listeners.ProtocolTerminatedHTTPS {
1572+
klog.V(4).Infof("Forcing to use %q protocol for listener because %q annotation is set", listeners.ProtocolTerminatedHTTPS, ServiceAnnotationTlsContainerRef)
1573+
listenerCreateOpt.Protocol = listeners.ProtocolTerminatedHTTPS
1574+
} else if svcConf.keepClientIP && listenerCreateOpt.Protocol != listeners.ProtocolHTTP {
1575+
klog.V(4).Infof("Forcing to use %q protocol for listener because %q annotation is set", listeners.ProtocolHTTP, ServiceAnnotationLoadBalancerXForwardedFor)
1576+
listenerCreateOpt.Protocol = listeners.ProtocolHTTP
1577+
}
1578+
15521579
if len(svcConf.allowedCIDR) > 0 {
15531580
listenerCreateOpt.AllowedCIDRs = svcConf.allowedCIDR
15541581
}
@@ -1577,6 +1604,24 @@ func (lbaas *LbaasV2) checkService(service *corev1.Service, nodes []*corev1.Node
15771604
svcConf.internal = internal
15781605
}
15791606

1607+
svcConf.tlsContainerRef = getStringFromServiceAnnotation(service, ServiceAnnotationTlsContainerRef, lbaas.opts.TlsContainerRef)
1608+
if svcConf.tlsContainerRef != "" {
1609+
if lbaas.secret == nil {
1610+
return fmt.Errorf("failed to create a TLS Terminated loadbalancer because openstack keymanager client is not "+
1611+
"initialized and default-tls-container-ref %q is set", svcConf.tlsContainerRef)
1612+
}
1613+
1614+
// check if container exists
1615+
// tls container ref has format: https://{keymanager_host}/v1/containers/{uuid}
1616+
slice := strings.Split(svcConf.tlsContainerRef, "/")
1617+
containerID := slice[len(slice)-1]
1618+
container, err := containers.Get(lbaas.secret, containerID).Extract()
1619+
if err != nil {
1620+
return fmt.Errorf("failed to get tls container %q: %v", svcConf.tlsContainerRef, err)
1621+
}
1622+
klog.V(4).Infof("Default TLS container %q found", container.ContainerRef)
1623+
}
1624+
15801625
svcConf.connLimit = getIntFromServiceAnnotation(service, ServiceAnnotationLoadBalancerConnLimit, -1)
15811626

15821627
svcConf.lbNetworkID = getStringFromServiceAnnotation(service, ServiceAnnotationLoadBalancerNetworkID, lbaas.opts.NetworkID)

pkg/openstack/openstack.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ func AddExtraFlags(fs *pflag.FlagSet) {
8484

8585
// LoadBalancer is used for creating and maintaining load balancers
8686
type LoadBalancer struct {
87+
secret *gophercloud.ServiceClient
8788
network *gophercloud.ServiceClient
8889
compute *gophercloud.ServiceClient
8990
lb *gophercloud.ServiceClient
@@ -113,7 +114,8 @@ type LoadBalancerOpts struct {
113114
CascadeDelete bool `gcfg:"cascade-delete"` // applicable only if use-octavia is set to True
114115
FlavorID string `gcfg:"flavor-id"`
115116
AvailabilityZone string `gcfg:"availability-zone"`
116-
EnableIngressHostname bool `gcfg:"enable-ingress-hostname"` // Used with proxy protocol by adding a dns suffix to the load balancer IP address. Default false.
117+
EnableIngressHostname bool `gcfg:"enable-ingress-hostname"` // Used with proxy protocol by adding a dns suffix to the load balancer IP address. Default false.
118+
TlsContainerRef string `gcfg:"default-tls-container-ref"` // reference to a tls container
117119
}
118120

119121
// LBClass defines the corresponding floating network, floating subnet or internal subnet ID
@@ -210,6 +212,7 @@ func ReadConfig(config io.Reader) (Config, error) {
210212
cfg.LoadBalancer.MonitorMaxRetries = 1
211213
cfg.LoadBalancer.CascadeDelete = true
212214
cfg.LoadBalancer.EnableIngressHostname = false
215+
cfg.LoadBalancer.TlsContainerRef = ""
213216

214217
err := gcfg.FatalOnly(gcfg.ReadInto(&cfg, config))
215218
if err != nil {
@@ -621,6 +624,12 @@ func (os *OpenStack) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
621624
return nil, false
622625
}
623626

627+
// keymanager client is optional
628+
secret, err := client.NewKeyManagerV1(os.provider, os.epOpts)
629+
if err != nil {
630+
klog.Warningf("Failed to create an OpenStack Secret client: %v", err)
631+
}
632+
624633
// LBaaS v1 is deprecated in the OpenStack Liberty release.
625634
// Currently kubernetes OpenStack cloud provider just support LBaaS v2.
626635
lbVersion := os.lbOpts.LBVersion
@@ -631,7 +640,7 @@ func (os *OpenStack) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
631640

632641
klog.V(1).Info("Claiming to support LoadBalancer")
633642

634-
return &LbaasV2{LoadBalancer{network, compute, lb, os.lbOpts}}, true
643+
return &LbaasV2{LoadBalancer{secret, network, compute, lb, os.lbOpts}}, true
635644
}
636645

637646
// Zones indicates that we support zones

0 commit comments

Comments
 (0)