Skip to content

Commit

Permalink
Support SNI in TCP mode (#805)
Browse files Browse the repository at this point in the history
  • Loading branch information
tamalsaha committed May 9, 2018
1 parent e95998a commit a5481ee
Show file tree
Hide file tree
Showing 4 changed files with 645 additions and 33 deletions.
10 changes: 10 additions & 0 deletions apis/voyager/v1beta1/diff.go
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/x509"
"net"
"reflect"
"sort"
"strings"
"time"

Expand Down Expand Up @@ -266,3 +267,12 @@ func (r IngressRule) GetHost() string {
}
return host
}

func (r TCPIngressRuleValue) ParseALPNOptions() string {
opt := append([]string{}, r.ALPN...) // copy slice, don't modify the input
if len(opt) <= 0 {
return ""
}
sort.Strings(opt)
return "alpn " + strings.Join(opt, ",")
}
118 changes: 87 additions & 31 deletions apis/voyager/v1beta1/validator.go
Expand Up @@ -55,13 +55,13 @@ func (r *Ingress) Migrate() {
func (r Ingress) IsValid(cloudProvider string) error {
for key, fn := range get {
if _, err := fn(r.Annotations); err != nil && err != kutil.ErrNotFound {
return errors.Errorf("can not parse annotaion %s. Reason: %s", key, err)
return errors.Errorf("can not parse annotation %s. Reason: %s", key, err)
}
}

timeouts, _ := get[DefaultsTimeOut](r.Annotations)
if err := checkMapKeys(timeouts.(map[string]string), sets.StringKeySet(timeoutDefaults)); err != nil {
return errors.Errorf("invalid value for annotaion %s. Reason: %s", DefaultsTimeOut, err)
return errors.Errorf("invalid value for annotation %s. Reason: %s", DefaultsTimeOut, err)
}

for ri, rule := range r.Spec.FrontendRules {
Expand All @@ -87,6 +87,8 @@ func (r Ingress) IsValid(cloudProvider string) error {
addrs := make(map[string]*address)
nodePorts := make(map[int]int)
usesHTTPRule := false
sslPassthrough := r.SSLPassthrough()

for ri, rule := range r.Spec.Rules {
if rule.HTTP != nil && rule.TCP == nil {
usesHTTPRule = true
Expand All @@ -107,8 +109,8 @@ func (r Ingress) IsValid(cloudProvider string) error {
if nodePort, err = checkOptionalPort(rule.HTTP.NodePort); err != nil {
return errors.Errorf("spec.rule[%d].http.nodePort %s is invalid. Reason: %s", ri, rule.HTTP.NodePort, err)
} else if nodePort > 0 {
if r.LBType() == LBTypeHostPort {
return errors.Errorf("spec.rule[%d].http.nodePort %s may not be specified when `LBType` is `HostPort`", ri, rule.HTTP.NodePort)
if r.LBType() == LBTypeHostPort || r.LBType() == LBTypeInternal {
return errors.Errorf("spec.rule[%d].http.nodePort %s may not be specified when `LBType` is %s", ri, rule.HTTP.NodePort, r.LBType())
}
}
bindAddress, err := checkOptionalAddress(rule.HTTP.Address)
Expand Down Expand Up @@ -185,43 +187,97 @@ func (r Ingress) IsValid(cloudProvider string) error {
}
}
} else if rule.TCP != nil && rule.HTTP == nil {
var a *address
var err error
var podPort, nodePort int

if podPort, err := checkRequiredPort(rule.TCP.Port); err != nil {
if podPort, err = checkRequiredPort(rule.TCP.Port); err != nil {
return errors.Errorf("spec.rule[%d].tcp.port %s is invalid. Reason: %s", ri, rule.TCP.Port, err)
} else {
bindAddress, err := checkOptionalAddress(rule.TCP.Address)
if err != nil {
return errors.Errorf("spec.rule[%d].tcp.address %s is invalid. Reason: %s", ri, rule.TCP.Address, err)
} else if err = checkExclusiveWildcard(bindAddress, podPort, addrs); err != nil {
return errors.Errorf("spec.rule[%d].tcp.address %s is invalid. Reason: %s", ri, rule.TCP.Address, err)
}
if nodePort, err = checkOptionalPort(rule.TCP.NodePort); err != nil {
return errors.Errorf("spec.rule[%d].tcp.nodePort %s is invalid. Reason: %s", ri, rule.TCP.NodePort, err)
} else if nodePort > 0 {
if r.LBType() == LBTypeHostPort || r.LBType() == LBTypeInternal {
return errors.Errorf("spec.rule[%d].tcp.nodePort %s may not be specified when `LBType` is %s", ri, rule.TCP.NodePort, r.LBType())
}
}
bindAddress, err := checkOptionalAddress(rule.TCP.Address)
if err != nil {
return errors.Errorf("spec.rule[%d].tcp.address %s is invalid. Reason: %s", ri, rule.TCP.Address, err)
} else if err = checkExclusiveWildcard(bindAddress, podPort, addrs); err != nil {
return errors.Errorf("spec.rule[%d].tcp.address %s is invalid. Reason: %s", ri, rule.TCP.Address, err)
}

// should not use TLS in passthrough mode
_, foundTLS := r.FindTLSSecret(rule.Host)
useTLS := foundTLS && !rule.TCP.NoTLS
if sslPassthrough && useTLS {
return errors.Errorf("TLS defined for spec.rule[%d].tcp in SSLPassthrough mode", ri)
}

var a *address
var addrKey = fmt.Sprintf("%s:%d", bindAddress, podPort)

var addrKey = fmt.Sprintf("%s:%d", bindAddress, podPort)
if ea, found := addrs[addrKey]; found {
return errors.Errorf("spec.rule[%d].tcp is reusing addr %s, also used in spec.rule[%d]", ri, ea, ea.FirstRuleIndex)
if ea, found := addrs[addrKey]; found {
if ea.Protocol != "tcp" {
return errors.Errorf("spec.rule[%d].tcp is reusing port %d, also used in spec.rule[%d].http", ri, ea.PodPort, ea.FirstRuleIndex)
}
if nodePort != ea.NodePort {
return errors.Errorf("spec.rule[%d].tcp.nodePort %d does not match with nodePort %d", ri, nodePort, ea.NodePort)
} else {
nodePorts[nodePort] = ri
}

// for empty-host and wildcard-host, there can not be more than one rules under same address-binder
if rule.Host == "" || rule.Host == "*" { // current host is wildcard-host but previously found one/more rules
return errors.Errorf("multiple rules with one/more wildcard/empty host found for address %s", addrKey)
} else { // current host is not wildcard-host but previously found rules with with wildcard-host
if _, found := ea.Hosts[""]; found {
return errors.Errorf("multiple rules with one/more wildcard/empty host found for address %s", addrKey)
}
if _, found := ea.Hosts["*"]; found {
return errors.Errorf("multiple rules with one/more wildcard/empty host found for address %s", addrKey)
}
}

// check for conflicting TLS
_, foundTLS1 := r.FindTLSSecret(r.Spec.Rules[ea.FirstRuleIndex].Host)
useTLS1 := foundTLS1 && !r.Spec.Rules[ea.FirstRuleIndex].TCP.NoTLS
if useTLS != useTLS1 {
return errors.Errorf("spec.rule[%d].TCP has conflicting TLS spec with spec.rule[%d].TCP", ri, ea.FirstRuleIndex)
}

// check for conflicting ALPN
if rule.TCP.ParseALPNOptions() != r.Spec.Rules[ea.FirstRuleIndex].TCP.ParseALPNOptions() {
return errors.Errorf("spec.rule[%d].TCP has conflicting ALPN spec with spec.rule[%d].TCP", ri, ea.FirstRuleIndex)
}

a = ea
} else {
a = &address{
Protocol: "tcp",
Address: bindAddress,
PodPort: podPort,
FirstRuleIndex: ri,
Hosts: map[string]Paths{rule.GetHost(): {}},
Hosts: map[string]Paths{},
}
if nodePort > 0 {
if ei, found := nodePorts[nodePort]; found {
return errors.Errorf("spec.rule[%d].tcp is reusing nodePort %d for addr %s, also used in spec.rule[%d]", ri, nodePort, a, ei)
} else {
a.NodePort = nodePort
nodePorts[nodePort] = ri
}
}
addrs[addrKey] = a
}
if np, err := checkOptionalPort(rule.TCP.NodePort); err != nil {
return errors.Errorf("spec.rule[%d].tcp.nodePort %s is invalid. Reason: %s", ri, rule.TCP.NodePort, err)
} else if np > 0 {
if r.LBType() == LBTypeHostPort {
return errors.Errorf("spec.rule[%d].tcp.nodePort %s may not be specified when `LBType` is `HostPort`", ri, rule.TCP.NodePort)
}
if ei, found := nodePorts[np]; found {
return errors.Errorf("spec.rule[%d].tcp is reusing nodePort %d for addr %s, also used in spec.rule[%d]", ri, np, a, ei)
} else {
a.NodePort = np
nodePorts[np] = ri

if _, found := a.Hosts[rule.GetHost()]; !found {
a.Hosts[rule.GetHost()] = Paths{
"": indices{RuleIndex: ri}, // for tcp no paths, just store indices in empty-path
}
} else { // same host under same address-binder
ei := a.Hosts[rule.GetHost()][""]
return errors.Errorf("spec.rule[%d].tcp is reusing host %s for addr %s, also used in spec.rule[%d]", ri, rule.Host, a, ei.RuleIndex)
}

if !checkBackendServiceName(rule.TCP.Backend.ServiceName) {
Expand Down Expand Up @@ -313,7 +369,7 @@ func checkBackendServiceName(name string) bool {
func checkRequiredPort(port intstr.IntOrString) (int, error) {
if port.Type == intstr.Int {
if port.IntVal <= 0 {
return 0, errors.Errorf("port %s must a +ve interger", port)
return 0, errors.Errorf("port %s must a +ve integer", port)
}
return int(port.IntVal), nil
} else if port.Type == intstr.String {
Expand All @@ -325,7 +381,7 @@ func checkRequiredPort(port intstr.IntOrString) (int, error) {
func checkOptionalPort(port intstr.IntOrString) (int, error) {
if port.Type == intstr.Int {
if port.IntVal < 0 {
return 0, errors.Errorf("port %s can't be -ve interger", port)
return 0, errors.Errorf("port %s can't be -ve integer", port)
}
return int(port.IntVal), nil
} else if port.Type == intstr.String {
Expand Down Expand Up @@ -371,11 +427,11 @@ func checkExclusiveWildcard(address string, port int, defined map[string]*addres

func (c Certificate) IsValid(cloudProvider string) error {
if len(c.Spec.Domains) == 0 {
return errors.Errorf("doamin list is empty")
return errors.Errorf("domain list is empty")
}

if c.Spec.ChallengeProvider.HTTP == nil && c.Spec.ChallengeProvider.DNS == nil {
return errors.Errorf("certificate has no valid challange provider")
return errors.Errorf("certificate has no valid challenge provider")
}

if c.Spec.ChallengeProvider.HTTP != nil && c.Spec.ChallengeProvider.DNS != nil {
Expand Down

0 comments on commit a5481ee

Please sign in to comment.