-
Notifications
You must be signed in to change notification settings - Fork 93
Support for SSL BackendSet certificate #243
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
pkg/oci/load_balancer.go
Outdated
secretString, ok := svc.Annotations[ServiceAnnotationLoadBalancerTLSSecret] | ||
if !ok { | ||
return "", "", errors.Errorf("no %q annotation found", ServiceAnnotationLoadBalancerTLSSecret) | ||
func (cp *CloudProvider) readSSLSecret(secretType string, svc *v1.Service) (string, string, string, string, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return type here is pretty hard to understand and might be prone to assignment error. Suggest refactoring into a struct or similar.
func (cp *CloudProvider) readSSLSecret(secretType string, svc *v1.Service) (*TLSCertificate, error)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, thanks!
Minor typo in commit message: Add Backendset SSL cerrtificates => Add Backendset SSL certificates |
b2ffd66
to
079b329
Compare
cdf2d44
to
30084bb
Compare
pkg/oci/load_balancer.go
Outdated
if !exists { | ||
return cp.createLoadBalancer(ctx, spec) | ||
} | ||
|
||
// for _, v := range spec.BackendSets { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
429affc
to
f14a7e8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
docs/tutorial-ssl-backendset.md
Outdated
targetPort: 80 | ||
``` | ||
|
||
First the required secret needs to be created in Kubernetes. For the purposes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/secret/Secret/
production you would most likely use a public certificate signed by a | ||
certificate authority. | ||
|
||
Below is an example of a secret configuration file required to be uploaded as a Kubernetes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/secret/Secret/
docs/tutorial-ssl-backendset.md
Outdated
certificate authority. | ||
|
||
Below is an example of a secret configuration file required to be uploaded as a Kubernetes | ||
generic secret. The CA certificate, the public certificate and the private key need to be base64 encoded : |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/secret/Secret/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Space before :
docs/tutorial-ssl-backendset.md
Outdated
tls.key: LS0tLS1CRUdJTi(...) | ||
``` | ||
|
||
Self-signed certificates can be generated using the following GitHub repository: https://github.com/square/certstrap |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe cfssl (or god forbid even openssl) would be a more standard recommendation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha, true, I'll just remove it, Google can help if needed
pkg/oci/load_balancer.go
Outdated
// ServiceAnnotationLoadBalancerTLSBackendSecret is a Service annotation for | ||
// specifying the TLS secret to install on the load balancer listeners which | ||
// have SSL enabled. | ||
// See: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This probably needs updating to reflect that we aren't using TLS Secrets.
pkg/oci/load_balancer.go
Outdated
@@ -230,7 +249,8 @@ func (cp *CloudProvider) ensureSSLCertificate(ctx context.Context, lb *loadbalan | |||
} | |||
|
|||
// Although we iterate here only one certificate is supported at the moment. | |||
certs, err := spec.Certificates() | |||
certs := make(map[string]loadbalancer.CertificateDetails) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a strange pattern. Any reason you're not allocating the map in the function and returning?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's because I wanted to hold all certificates (both for listeners and backend sets) within the same map. Therefore, I am creating a map of certificates and I add to it step by step. I guess this saves me from having to make a union of the map :) for which go doesn't have a nice implementation of (unlike python)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, that makes sense. I think perhaps then that extending SSLConfig to include both BackendSet and Listener SSL configuration would likely result in cleaner code. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you're right, Or maybe have a separate struct to hold both
pkg/oci/load_balancer.go
Outdated
// specifying the TLS secret to install on the load balancer listeners which | ||
// have SSL enabled. | ||
// See: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls | ||
ServiceAnnotationLoadBalancerTLSBackendSecret = "service.beta.kubernetes.io/oci-load-balancer-tls-backend-secret" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we also need to support something along the lines of ServiceAnnotationLoadBalancerSSLPorts
but for backends at all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought of this too, but @owainlewis was mentioning that we can assume that both the Listener certificate and the Backend set certificates can be uniformly applied for the same ports. The closest thing to what OCI offers would be to have a one-to-one mapping between one port and one certificate, which is not the case now, even with Listeners. Maybe we should have a discussion about this and address this separately
pkg/oci/load_balancer.go
Outdated
|
||
return string(cert), string(key), nil | ||
if key, ok = secret.Data[SSLPrivateKeyFileName]; !ok { | ||
return &SSLK8SSecret{}, errors.Errorf("%s not found in secret %s/%s", SSLPrivateKeyFileName, ns, name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return nil, errors.Errorf("...
pkg/oci/load_balancer_util.go
Outdated
@@ -29,8 +29,14 @@ import ( | |||
) | |||
|
|||
const ( | |||
sslCertificateFileName = "tls.crt" | |||
sslPrivateKeyFileName = "tls.key" | |||
// SSLCAFileName - Key name for ca data in the secrets config |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docstring comments should read as a sentence:
// SSLCAFileName is the key name for ca data in the secrets config.
952a2c1
to
b7efcb9
Compare
test/e2e/framework/util.go
Outdated
@@ -349,6 +434,23 @@ func GetReadySchedulableNodesOrDie(c clientset.Interface) (nodes *v1.NodeList) { | |||
return nodes | |||
} | |||
|
|||
// NewSecretData returns the default v1.Secret template for this jig, but | |||
// does not actually create the Secret. | |||
func NewSecretData(ns, generateName string, caCert, cert, privateKey, passphrase []byte) *v1.Secret { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Appears unused.
pkg/oci/load_balancer_spec.go
Outdated
path, port := apiservice.GetServiceHealthCheckPathPort(svc) | ||
if path != "" { | ||
func getHealthChecker(cfg *SSLConfig, port int, svc *v1.Service) *loadbalancer.HealthCheckerDetails { | ||
var protocol string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// If the health-check has SSL enabled use TCP rather than HTTP.
protocol := lbNodesHealthCheckProtoHTTP
if cfg != nil && cfg.Ports.Has(port) {
protocol = lbNodesHealthCheckProtoTCP
}
pkg/oci/load_balancer_util.go
Outdated
@@ -29,8 +29,14 @@ import ( | |||
) | |||
|
|||
const ( | |||
sslCertificateFileName = "tls.crt" | |||
sslPrivateKeyFileName = "tls.key" | |||
// SSLCAFileName is a key name for ca data in the secrets config |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As docstrings are expected to be sentences (for nice godocs) they should end with a full-stop.
pkg/oci/load_balancer_util.go
Outdated
SSLCAFileName = "ca.crt" | ||
// SSLCertificateFileName is a key name for certificate data in the secrets config | ||
SSLCertificateFileName = "tls.crt" | ||
// SSLPrivateKeyFileName - is a key name for cartificate private key in the secrets config |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/-//
(sentences)
|
||
ipPort := net.JoinHostPort(ip, strconv.Itoa(port)) | ||
url := fmt.Sprintf("http://%s%s", ipPort, request) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
url := fmt.Sprintf("http://%s%s", ipPort, request)
if secure {
url = fmt.Sprintf("https://%s%s", ipPort, request)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of nits and queries but otherwise looking awesome 👍
test/e2e/load_balancer.go
Outdated
ns := f.Namespace.Name | ||
|
||
jig := framework.NewServiceTestJig(f.ClientSet, serviceName) | ||
//nodeIP := framework.PickNodeIP(jig.Client) // for later |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should probably clean up commented out code?
|
||
ipPort := net.JoinHostPort(ip, strconv.Itoa(port)) | ||
url := fmt.Sprintf("http://%s%s", ipPort, request) | ||
var url string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
url := fmt.Sprintf("http://%s%s", ipPort, request)
(no need for separate declare and define)
if !c.rateLimiter.Writer.TryAccept() { | ||
return "", RateLimitError(true, "CreateCertificate") | ||
} | ||
|
||
// TODO(apryde): We currently don't have a mechanism for supplying | ||
// CreateCertificateDetails.CaCertificate. | ||
resp, err := c.loadbalancer.CreateCertificate(ctx, loadbalancer.CreateCertificateRequest{ | ||
LoadBalancerId: &lbID, | ||
CreateCertificateDetails: loadbalancer.CreateCertificateDetails{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you not just do CreateCertificateDetails: cert,
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, one is CreateCertificateDetails type and one is CertificateDetails. Unless there's some magic to make that happen? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please say there is to make go cooler for me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤦♂️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that a facepalm because it should have been obvious for me on how to cast it from one type to the other?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Goodness no! Facepalm that I didn't notice they were different types 😄
pkg/oci/load_balancer.go
Outdated
return "", "", errors.Errorf("%s not found in secret %s/%s", sslCertificateFileName, ns, name) | ||
var ok bool | ||
var cacert, cert, key, pass []byte | ||
cacert, _ = secret.Data[SSLCAFileName] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cacert = secret.Data[SSLCAFileName]
pkg/oci/load_balancer.go
Outdated
if requiresCertificate(service) { | ||
ports, err := getSSLEnabledPorts(service) | ||
if err != nil { | ||
return nil, err | ||
} | ||
ssl = NewSSLConfig(lbName, ports, cp) | ||
secretListenerString, _ := service.Annotations[ServiceAnnotationLoadBalancerTLSSecret] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/, _//
pkg/oci/load_balancer.go
Outdated
if requiresCertificate(service) { | ||
ports, err := getSSLEnabledPorts(service) | ||
if err != nil { | ||
return nil, err | ||
} | ||
ssl = NewSSLConfig(lbName, ports, cp) | ||
secretListenerString, _ := service.Annotations[ServiceAnnotationLoadBalancerTLSSecret] | ||
secretBackendSetString, _ := service.Annotations[ServiceAnnotationLoadBalancerBackendSetSecret] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/, _//
pkg/oci/load_balancer_spec.go
Outdated
} | ||
if cert == "" || key == "" { | ||
if len(sslSecret.PublicCert) == 0 || len(sslSecret.PrivateKey) == 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not clear on how this works in the case that there's BackendSet certs but not Listener?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LMAATCFT (let me add a test case for that) good spot
pkg/oci/load_balancer_spec.go
Outdated
secretName = sslCfg.BackendSetSSLSecretName | ||
} | ||
sslConfig := getSSLConfiguration(sslCfg, secretName, port) | ||
if sslConfig != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for if statement. loadbalancer.BackendSetDetails{SslConfiguration: sslConfig}
when sslConfig == nil
is equivalent to not specifying the property as the default value for pointers is nil.
7adec2e
to
8b07710
Compare
pkg/oci/load_balancer_spec.go
Outdated
if len(sslSecret.PublicCert) == 0 || len(sslSecret.PrivateKey) == 0 { | ||
return certs, nil | ||
} | ||
if len(s.SSLConfig.ListenerSSLSecretName) != 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can support ca certificates and passphrases on listener certs and implement as follows I believe:
secrets := make([]string, 0, 2)
if s.SSLConfig.ListenerSSLSecretName != "" {
secrets = append(secrets, s.SSLConfig.ListenerSSLSecretName)
}
if s.SSLConfig.BackendSetSSLSecretName != "" {
secrets = append(secrets, s.SSLConfig.BackendSetSSLSecretName)
}
for _, name := range secrets {
cert, err := s.SSLConfig.readSSLSecret(s.service.Namespace, name)
if err != nil {
return nil, errors.Wrap(err, "reading SSL BackendSet Secret")
}
certs[name] = loadbalancer.CertificateDetails{
CertificateName: &s.SSLConfig.BackendSetSSLSecretName,
CaCertificate: common.String(string(cert.CACert)),
PublicCertificate: common.String(string(cert.PublicCert)),
PrivateKey: common.String(string(cert.PrivateKey)),
Passphrase: common.String(string(cert.Passphrase)),
}
}
8b07710
to
71a0110
Compare
ef38a27
to
5d97908
Compare
5d97908
to
22bb891
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of minor comments to address from previous reviews. Overall looks good to me.
docs/tutorial-ssl-backendset.md
Outdated
annotations: | ||
service.beta.kubernetes.io/oci-load-balancer-ssl-ports: "443" | ||
service.beta.kubernetes.io/oci-load-balancer-tls-secret: ssl-certificate-secret | ||
service.beta.kubernetes.io/oci-load-balancer-backendset-secret: ssl-certificate-secret |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be consistent let's change to service.beta.kubernetes.io/oci-load-balancer-tls-backendset-secret
test/e2e/load_balancer.go
Outdated
tcpIngressIP := framework.GetIngressPoint(&tcpService.Status.LoadBalancer.Ingress[0]) | ||
framework.Logf("TCP load balancer: %s", tcpIngressIP) | ||
|
||
// By("hitting the TCP service's NodePort") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove unused code
pkg/oci/load_balancer.go
Outdated
// specifying the generic secret to install on the load balancer listeners which | ||
// have SSL enabled. | ||
// See: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls | ||
ServiceAnnotationLoadBalancerBackendSetSecret = "service.beta.kubernetes.io/oci-load-balancer-backendset-secret" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
service.beta.kubernetes.io/oci-load-balancer-backendset-secret => service.beta.kubernetes.io/oci-load-balancer-backendset-tls-secret to be consistent?
var cert, key []byte | ||
if cert, ok = secret.Data[sslCertificateFileName]; !ok { | ||
return "", "", errors.Errorf("%s not found in secret %s/%s", sslCertificateFileName, ns, name) | ||
var ok bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this (var ok bool) needed?
if cert, ok := secret.Data[SSLCertificateFileName]; !ok {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, I think it is. It's because we need to declare cert and key beforehand.
pkg/oci/load_balancer.go
Outdated
certs, err := spec.Certificates() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var ok bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this needed in outer scope vs if _, ok := lb.Certificates[*cert.CertificateName]; !ok {
test/e2e/load_balancer.go
Outdated
ns := f.Namespace.Name | ||
|
||
jig := framework.NewServiceTestJig(f.ClientSet, serviceName) | ||
//nodeIP := framework.PickNodeIP(jig.Client) // for later |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove unused code
test/e2e/load_balancer.go
Outdated
jig := framework.NewServiceTestJig(f.ClientSet, serviceName) | ||
//nodeIP := framework.PickNodeIP(jig.Client) // for later | ||
|
||
sslSecretNameL := "ssl-certificate-secret-listener" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: Would generally prefer a more readable name like sslListenerSecretName
test/e2e/load_balancer.go
Outdated
cloudprovider.SSLPassphrase: []byte(framework.SSLPassphrase), | ||
}, | ||
}) | ||
sslSecretNameB := "ssl-certificate-secret-backendset" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: Would prefer sslBackendSetSecretName
vs sslSecretNameB
test/e2e/load_balancer.go
Outdated
loadBalancerCreateTimeout = framework.LoadBalancerCreateTimeoutLarge | ||
} | ||
|
||
// TODO(apryde): Test that LoadBalancers can receive static IP addresses |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove me
test/e2e/load_balancer.go
Outdated
loadBalancerCreateTimeout = framework.LoadBalancerCreateTimeoutLarge | ||
} | ||
|
||
// TODO(apryde): Test that LoadBalancers can receive static IP addresses |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copy paste from previous test. Probably not worth duplicating again.
8c9c044
to
3167fd9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Once additional reviews are +1 we can merge.
8e313ba
to
b64268e
Compare
b64268e
to
0d3625f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome work! 🎉
Added support for supplying SSL certificates for LoadBalancers backend sets:
Modified Health Check to support both TCP and HTTP protocols. The choice between protocols is driven by whether there exists an SSL certificate associated to the port for which the health check is being done. Both the SSL certificates and the SSL enabled ports are specified in the annotations of the Load Balancer configuration file
Added Ginkgo test to check connectivity for SSL enabled backend sets