mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 05:27:21 +00:00
Merge pull request #26976 from Clarifai/elb
Automatic merge from submit-queue AWS: support mixed plaintext/encrypted ports in ELBs via service.beta.kubernetes.io/aws-load-balancer-ssl-ports annotation Fixes #26268 Implements the second SSL ELB annotation, per #24978 `service.beta.kubernetes.io/aws-load-balancer-ssl-ports=*` (comma-separated list of port numbers or e.g. `https`) If not specified, all ports are secure (SSL or HTTPS).
This commit is contained in:
commit
a2703a3e63
@ -24,6 +24,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -48,6 +49,7 @@ import (
|
|||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"k8s.io/kubernetes/pkg/api/service"
|
"k8s.io/kubernetes/pkg/api/service"
|
||||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
|
"k8s.io/kubernetes/pkg/util/sets"
|
||||||
)
|
)
|
||||||
|
|
||||||
const ProviderName = "aws"
|
const ProviderName = "aws"
|
||||||
@ -79,6 +81,10 @@ const ServiceAnnotationLoadBalancerProxyProtocol = "service.beta.kubernetes.io/a
|
|||||||
// CertARN is an IAM or CM certificate ARN, e.g. arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012
|
// CertARN is an IAM or CM certificate ARN, e.g. arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012
|
||||||
const ServiceAnnotationLoadBalancerCertificate = "service.beta.kubernetes.io/aws-load-balancer-ssl-cert"
|
const ServiceAnnotationLoadBalancerCertificate = "service.beta.kubernetes.io/aws-load-balancer-ssl-cert"
|
||||||
|
|
||||||
|
// Service annotation specifying a comma-separated list of ports that will use SSL/HTTPS
|
||||||
|
// listeners. Defaults to '*' (all).
|
||||||
|
const ServiceAnnotationLoadBalancerSSLPorts = "service.beta.kubernetes.io/aws-load-balancer-ssl-ports"
|
||||||
|
|
||||||
// Service annotation specifying the protocol spoken by the backend (pod) behind a secure listener.
|
// Service annotation specifying the protocol spoken by the backend (pod) behind a secure listener.
|
||||||
// Only inspected when `aws-load-balancer-ssl-cert` is used.
|
// Only inspected when `aws-load-balancer-ssl-cert` is used.
|
||||||
// If `http` (default) or `https`, an HTTPS listener that terminates the connection and parses headers is created.
|
// If `http` (default) or `https`, an HTTPS listener that terminates the connection and parses headers is created.
|
||||||
@ -2095,10 +2101,38 @@ func isSubnetPublic(rt []*ec2.RouteTable, subnetID string) (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type portSets struct {
|
||||||
|
names sets.String
|
||||||
|
numbers sets.Int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPortSets returns a portSets structure representing port names and numbers
|
||||||
|
// that the comma-separated string describes. If the input is empty or equal to
|
||||||
|
// "*", a nil pointer is returned.
|
||||||
|
func getPortSets(annotation string) (ports *portSets) {
|
||||||
|
if annotation != "" && annotation != "*" {
|
||||||
|
ports = &portSets{
|
||||||
|
sets.NewString(),
|
||||||
|
sets.NewInt64(),
|
||||||
|
}
|
||||||
|
portStringSlice := strings.Split(annotation, ",")
|
||||||
|
for _, item := range portStringSlice {
|
||||||
|
port, err := strconv.Atoi(item)
|
||||||
|
if err != nil {
|
||||||
|
ports.names.Insert(item)
|
||||||
|
} else {
|
||||||
|
ports.numbers.Insert(int64(port))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// buildListener creates a new listener from the given port, adding an SSL certificate
|
// buildListener creates a new listener from the given port, adding an SSL certificate
|
||||||
// if indicated by the appropriate annotations.
|
// if indicated by the appropriate annotations.
|
||||||
func buildListener(port api.ServicePort, annotations map[string]string) (*elb.Listener, error) {
|
func buildListener(port api.ServicePort, annotations map[string]string, sslPorts *portSets) (*elb.Listener, error) {
|
||||||
loadBalancerPort := int64(port.Port)
|
loadBalancerPort := int64(port.Port)
|
||||||
|
portName := strings.ToLower(port.Name)
|
||||||
instancePort := int64(port.NodePort)
|
instancePort := int64(port.NodePort)
|
||||||
protocol := strings.ToLower(string(port.Protocol))
|
protocol := strings.ToLower(string(port.Protocol))
|
||||||
instanceProtocol := protocol
|
instanceProtocol := protocol
|
||||||
@ -2107,7 +2141,7 @@ func buildListener(port api.ServicePort, annotations map[string]string) (*elb.Li
|
|||||||
listener.InstancePort = &instancePort
|
listener.InstancePort = &instancePort
|
||||||
listener.LoadBalancerPort = &loadBalancerPort
|
listener.LoadBalancerPort = &loadBalancerPort
|
||||||
certID := annotations[ServiceAnnotationLoadBalancerCertificate]
|
certID := annotations[ServiceAnnotationLoadBalancerCertificate]
|
||||||
if certID != "" {
|
if certID != "" && (sslPorts == nil || sslPorts.numbers.Has(loadBalancerPort) || sslPorts.names.Has(portName)) {
|
||||||
instanceProtocol = annotations[ServiceAnnotationLoadBalancerBEProtocol]
|
instanceProtocol = annotations[ServiceAnnotationLoadBalancerBEProtocol]
|
||||||
if instanceProtocol == "" {
|
if instanceProtocol == "" {
|
||||||
protocol = "ssl"
|
protocol = "ssl"
|
||||||
@ -2128,8 +2162,9 @@ func buildListener(port api.ServicePort, annotations map[string]string) (*elb.Li
|
|||||||
|
|
||||||
// EnsureLoadBalancer implements LoadBalancer.EnsureLoadBalancer
|
// EnsureLoadBalancer implements LoadBalancer.EnsureLoadBalancer
|
||||||
func (s *AWSCloud) EnsureLoadBalancer(apiService *api.Service, hosts []string) (*api.LoadBalancerStatus, error) {
|
func (s *AWSCloud) EnsureLoadBalancer(apiService *api.Service, hosts []string) (*api.LoadBalancerStatus, error) {
|
||||||
|
annotations := apiService.Annotations
|
||||||
glog.V(2).Infof("EnsureLoadBalancer(%v, %v, %v, %v, %v, %v, %v)",
|
glog.V(2).Infof("EnsureLoadBalancer(%v, %v, %v, %v, %v, %v, %v)",
|
||||||
apiService.Namespace, apiService.Name, s.region, apiService.Spec.LoadBalancerIP, apiService.Spec.Ports, hosts, apiService.Annotations)
|
apiService.Namespace, apiService.Name, s.region, apiService.Spec.LoadBalancerIP, apiService.Spec.Ports, hosts, annotations)
|
||||||
|
|
||||||
if apiService.Spec.SessionAffinity != api.ServiceAffinityNone {
|
if apiService.Spec.SessionAffinity != api.ServiceAffinityNone {
|
||||||
// ELB supports sticky sessions, but only when configured for HTTP/HTTPS
|
// ELB supports sticky sessions, but only when configured for HTTP/HTTPS
|
||||||
@ -2142,6 +2177,7 @@ func (s *AWSCloud) EnsureLoadBalancer(apiService *api.Service, hosts []string) (
|
|||||||
|
|
||||||
// Figure out what mappings we want on the load balancer
|
// Figure out what mappings we want on the load balancer
|
||||||
listeners := []*elb.Listener{}
|
listeners := []*elb.Listener{}
|
||||||
|
portList := getPortSets(annotations[ServiceAnnotationLoadBalancerSSLPorts])
|
||||||
for _, port := range apiService.Spec.Ports {
|
for _, port := range apiService.Spec.Ports {
|
||||||
if port.Protocol != api.ProtocolTCP {
|
if port.Protocol != api.ProtocolTCP {
|
||||||
return nil, fmt.Errorf("Only TCP LoadBalancer is supported for AWS ELB")
|
return nil, fmt.Errorf("Only TCP LoadBalancer is supported for AWS ELB")
|
||||||
@ -2150,7 +2186,7 @@ func (s *AWSCloud) EnsureLoadBalancer(apiService *api.Service, hosts []string) (
|
|||||||
glog.Errorf("Ignoring port without NodePort defined: %v", port)
|
glog.Errorf("Ignoring port without NodePort defined: %v", port)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
listener, err := buildListener(port, apiService.Annotations)
|
listener, err := buildListener(port, annotations, portList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -1214,9 +1214,11 @@ func TestBuildListener(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
|
|
||||||
lbPort int64
|
lbPort int64
|
||||||
|
portName string
|
||||||
instancePort int64
|
instancePort int64
|
||||||
backendProtocolAnnotation string
|
backendProtocolAnnotation string
|
||||||
certAnnotation string
|
certAnnotation string
|
||||||
|
sslPortAnnotation string
|
||||||
|
|
||||||
expectError bool
|
expectError bool
|
||||||
lbProtocol string
|
lbProtocol string
|
||||||
@ -1225,49 +1227,69 @@ func TestBuildListener(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"No cert or BE protocol annotation, passthrough",
|
"No cert or BE protocol annotation, passthrough",
|
||||||
80, 7999, "", "",
|
80, "", 7999, "", "", "",
|
||||||
false, "tcp", "tcp", "",
|
false, "tcp", "tcp", "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Cert annotation without BE protocol specified, SSL->TCP",
|
"Cert annotation without BE protocol specified, SSL->TCP",
|
||||||
80, 8000, "", "cert",
|
80, "", 8000, "", "cert", "",
|
||||||
false, "ssl", "tcp", "cert",
|
false, "ssl", "tcp", "cert",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"BE protocol without cert annotation, passthrough",
|
"BE protocol without cert annotation, passthrough",
|
||||||
443, 8001, "https", "",
|
443, "", 8001, "https", "", "",
|
||||||
false, "tcp", "tcp", "",
|
false, "tcp", "tcp", "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Invalid cert annotation, bogus backend protocol",
|
"Invalid cert annotation, bogus backend protocol",
|
||||||
443, 8002, "bacon", "foo",
|
443, "", 8002, "bacon", "foo", "",
|
||||||
true, "tcp", "tcp", "cert",
|
true, "tcp", "tcp", "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Invalid cert annotation, protocol followed by equal sign",
|
"Invalid cert annotation, protocol followed by equal sign",
|
||||||
443, 8003, "http=", "=",
|
443, "", 8003, "http=", "=", "",
|
||||||
true, "tcp", "tcp", "cert",
|
true, "tcp", "tcp", "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"HTTPS->HTTPS",
|
"HTTPS->HTTPS",
|
||||||
443, 8004, "https", "cert",
|
443, "", 8004, "https", "cert", "",
|
||||||
false, "https", "https", "cert",
|
false, "https", "https", "cert",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"HTTPS->HTTP",
|
"HTTPS->HTTP",
|
||||||
443, 8005, "http", "cert",
|
443, "", 8005, "http", "cert", "",
|
||||||
false, "https", "http", "cert",
|
false, "https", "http", "cert",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"SSL->SSL",
|
"SSL->SSL",
|
||||||
443, 8006, "ssl", "cert",
|
443, "", 8006, "ssl", "cert", "",
|
||||||
false, "ssl", "ssl", "cert",
|
false, "ssl", "ssl", "cert",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"SSL->TCP",
|
"SSL->TCP",
|
||||||
443, 8007, "tcp", "cert",
|
443, "", 8007, "tcp", "cert", "",
|
||||||
false, "ssl", "tcp", "cert",
|
false, "ssl", "tcp", "cert",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Port in whitelist",
|
||||||
|
1234, "", 8008, "tcp", "cert", "1234,5678",
|
||||||
|
false, "ssl", "tcp", "cert",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Port not in whitelist, passthrough",
|
||||||
|
443, "", 8009, "tcp", "cert", "1234,5678",
|
||||||
|
false, "tcp", "tcp", "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Named port in whitelist",
|
||||||
|
1234, "bar", 8010, "tcp", "cert", "foo,bar",
|
||||||
|
false, "ssl", "tcp", "cert",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Named port not in whitelist, passthrough",
|
||||||
|
443, "", 8011, "tcp", "cert", "foo,bar",
|
||||||
|
false, "tcp", "tcp", "",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@ -1279,11 +1301,13 @@ func TestBuildListener(t *testing.T) {
|
|||||||
if test.certAnnotation != "" {
|
if test.certAnnotation != "" {
|
||||||
annotations[ServiceAnnotationLoadBalancerCertificate] = test.certAnnotation
|
annotations[ServiceAnnotationLoadBalancerCertificate] = test.certAnnotation
|
||||||
}
|
}
|
||||||
|
ports := getPortSets(test.sslPortAnnotation)
|
||||||
l, err := buildListener(api.ServicePort{
|
l, err := buildListener(api.ServicePort{
|
||||||
NodePort: int32(test.instancePort),
|
NodePort: int32(test.instancePort),
|
||||||
Port: int32(test.lbPort),
|
Port: int32(test.lbPort),
|
||||||
|
Name: test.portName,
|
||||||
Protocol: api.Protocol("tcp"),
|
Protocol: api.Protocol("tcp"),
|
||||||
}, annotations)
|
}, annotations, ports)
|
||||||
if test.expectError {
|
if test.expectError {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Should error for case %s", test.name)
|
t.Errorf("Should error for case %s", test.name)
|
||||||
|
Loading…
Reference in New Issue
Block a user