Add secure-verify-ca-secret annotation

This commit is contained in:
Joao Morais 2017-05-14 19:14:27 -03:00
parent 07647fd313
commit 8b5a6e7661
6 changed files with 144 additions and 17 deletions

View file

@ -17,25 +17,61 @@ limitations under the License.
package secureupstream
import (
"fmt"
"github.com/pkg/errors"
extensions "k8s.io/client-go/pkg/apis/extensions/v1beta1"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/ingress/core/pkg/ingress/resolver"
)
const (
secureUpstream = "ingress.kubernetes.io/secure-backends"
secureUpstream = "ingress.kubernetes.io/secure-backends"
secureVerifyCASecret = "ingress.kubernetes.io/secure-verify-ca-secret"
)
// Secure describes SSL backend configuration
type Secure struct {
Secure bool
CACert resolver.AuthSSLCert
}
type su struct {
certResolver resolver.AuthCertificate
}
// NewParser creates a new secure upstream annotation parser
func NewParser() parser.IngressAnnotation {
return su{}
func NewParser(resolver resolver.AuthCertificate) parser.IngressAnnotation {
return su{
certResolver: resolver,
}
}
// Parse parses the annotations contained in the ingress
// rule used to indicate if the upstream servers should use SSL
func (a su) Parse(ing *extensions.Ingress) (interface{}, error) {
return parser.GetBoolAnnotation(secureUpstream, ing)
s, _ := parser.GetBoolAnnotation(secureUpstream, ing)
ca, _ := parser.GetStringAnnotation(secureVerifyCASecret, ing)
secure := &Secure{
Secure: s,
CACert: resolver.AuthSSLCert{},
}
if !s && ca != "" {
return secure,
errors.Errorf("trying to use CA from secret %v/%v on a non secure backend", ing.Namespace, ca)
}
if ca == "" {
return secure, nil
}
caCert, err := a.certResolver.GetAuthCertificate(fmt.Sprintf("%v/%v", ing.Namespace, ca))
if err != nil {
return secure, errors.Wrap(err, "error obtaining certificate")
}
if caCert == nil {
return secure, nil
}
return &Secure{
Secure: s,
CACert: *caCert,
}, nil
}

View file

@ -23,7 +23,9 @@ import (
api "k8s.io/client-go/pkg/api/v1"
extensions "k8s.io/client-go/pkg/apis/extensions/v1beta1"
"fmt"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress/core/pkg/ingress/resolver"
)
func buildIngress() *extensions.Ingress {
@ -61,22 +63,58 @@ func buildIngress() *extensions.Ingress {
}
}
type mockCfg struct {
certs map[string]resolver.AuthSSLCert
}
func (cfg mockCfg) GetAuthCertificate(secret string) (*resolver.AuthSSLCert, error) {
if cert, ok := cfg.certs[secret]; ok {
return &cert, nil
}
return nil, fmt.Errorf("secret not found: %v", secret)
}
func TestAnnotations(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[secureUpstream] = "true"
data[secureVerifyCASecret] = "secure-verify-ca"
ing.SetAnnotations(data)
_, err := NewParser().Parse(ing)
_, err := NewParser(mockCfg{
certs: map[string]resolver.AuthSSLCert{
"default/secure-verify-ca": resolver.AuthSSLCert{},
},
}).Parse(ing)
if err != nil {
t.Error("Expected error with ingress without annotations")
t.Errorf("Unexpected error on ingress: %v", err)
}
}
func TestWithoutAnnotations(t *testing.T) {
func TestSecretNotFound(t *testing.T) {
ing := buildIngress()
_, err := NewParser().Parse(ing)
data := map[string]string{}
data[secureUpstream] = "true"
data[secureVerifyCASecret] = "secure-verify-ca"
ing.SetAnnotations(data)
_, err := NewParser(mockCfg{}).Parse(ing)
if err == nil {
t.Error("Expected error with ingress without annotations")
t.Error("Expected secret not found error on ingress")
}
}
func TestSecretOnNonSecure(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[secureUpstream] = "false"
data[secureVerifyCASecret] = "secure-verify-ca"
ing.SetAnnotations(data)
_, err := NewParser(mockCfg{
certs: map[string]resolver.AuthSSLCert{
"default/secure-verify-ca": resolver.AuthSSLCert{},
},
}).Parse(ing)
if err == nil {
t.Error("Expected CA secret on non secure backend error on ingress")
}
}

View file

@ -68,7 +68,7 @@ func newAnnotationExtractor(cfg extractorConfig) annotationExtractor {
"Proxy": proxy.NewParser(cfg),
"RateLimit": ratelimit.NewParser(),
"Redirect": rewrite.NewParser(cfg),
"SecureUpstream": secureupstream.NewParser(),
"SecureUpstream": secureupstream.NewParser(cfg),
"SessionAffinity": sessionaffinity.NewParser(),
"SSLPassthrough": sslpassthrough.NewParser(),
"ConfigurationSnippet": snippet.NewParser(),
@ -111,9 +111,13 @@ const (
sessionAffinity = "SessionAffinity"
)
func (e *annotationExtractor) SecureUpstream(ing *extensions.Ingress) bool {
val, _ := e.annotations[secureUpstream].Parse(ing)
return val.(bool)
func (e *annotationExtractor) SecureUpstream(ing *extensions.Ingress) *secureupstream.Secure {
val, err := e.annotations[secureUpstream].Parse(ing)
if err != nil {
glog.Errorf("error parsing secure upstream: %v", err)
}
secure := val.(*secureupstream.Secure)
return secure
}
func (e *annotationExtractor) HealthCheck(ing *extensions.Ingress) *healthcheck.Upstream {

View file

@ -30,6 +30,7 @@ import (
const (
annotationSecureUpstream = "ingress.kubernetes.io/secure-backends"
annotationSecureVerifyCACert = "ingress.kubernetes.io/secure-verify-ca-secret"
annotationUpsMaxFails = "ingress.kubernetes.io/upstream-max-fails"
annotationUpsFailTimeout = "ingress.kubernetes.io/upstream-fail-timeout"
annotationPassthrough = "ingress.kubernetes.io/ssl-passthrough"
@ -51,7 +52,14 @@ func (m mockCfg) GetSecret(name string) (*api.Secret, error) {
return m.MockSecrets[name], nil
}
func (m mockCfg) GetAuthCertificate(string) (*resolver.AuthSSLCert, error) {
func (m mockCfg) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error) {
if secret, _ := m.GetSecret(name); secret != nil {
return &resolver.AuthSSLCert{
Secret: name,
CAFileName: "/opt/ca.pem",
PemSHA: "123",
}, nil
}
return nil, nil
}
@ -122,12 +130,47 @@ func TestSecureUpstream(t *testing.T) {
for _, foo := range fooAnns {
ing.SetAnnotations(foo.annotations)
r := ec.SecureUpstream(ing)
if r != foo.er {
if r.Secure != foo.er {
t.Errorf("Returned %v but expected %v", r, foo.er)
}
}
}
func TestSecureVerifyCACert(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{
MockSecrets: map[string]*api.Secret{
"default/secure-verify-ca": {
ObjectMeta: meta_v1.ObjectMeta{
Name: "secure-verify-ca",
},
},
},
})
anns := []struct {
it int
annotations map[string]string
exists bool
}{
{1, map[string]string{annotationSecureUpstream: "true", annotationSecureVerifyCACert: "not"}, false},
{2, map[string]string{annotationSecureUpstream: "false", annotationSecureVerifyCACert: "secure-verify-ca"}, false},
{3, map[string]string{annotationSecureUpstream: "true", annotationSecureVerifyCACert: "secure-verify-ca"}, true},
{4, map[string]string{annotationSecureUpstream: "true", annotationSecureVerifyCACert + "_not": "secure-verify-ca"}, false},
{5, map[string]string{annotationSecureUpstream: "true"}, false},
{6, map[string]string{}, false},
{7, nil, false},
}
for _, ann := range anns {
ing := buildIngress()
ing.SetAnnotations(ann.annotations)
res := ec.SecureUpstream(ing)
if (res.CACert.CAFileName != "") != ann.exists {
t.Errorf("Expected exists was %v on iteration %v", ann.exists, ann.it)
}
}
}
func TestHealthCheck(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{})
ing := buildIngress()

View file

@ -783,7 +783,10 @@ func (ic *GenericController) createUpstreams(data []interface{}) map[string]*ing
glog.V(3).Infof("creating upstream %v", name)
upstreams[name] = newUpstream(name)
if !upstreams[name].Secure {
upstreams[name].Secure = secUpstream
upstreams[name].Secure = secUpstream.Secure
}
if upstreams[name].SecureCACert.Secret == "" {
upstreams[name].SecureCACert = secUpstream.CACert
}
if upstreams[name].SessionAffinity.AffinityType == "" {
upstreams[name].SessionAffinity.AffinityType = affinity.AffinityType

View file

@ -31,6 +31,7 @@ import (
"k8s.io/ingress/core/pkg/ingress/annotations/ratelimit"
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
"k8s.io/ingress/core/pkg/ingress/defaults"
"k8s.io/ingress/core/pkg/ingress/resolver"
"k8s.io/ingress/core/pkg/ingress/store"
)
@ -154,8 +155,10 @@ type Backend struct {
// Allowing the use of HTTPS
// The endpoint/s must provide a TLS connection.
// The certificate used in the endpoint cannot be a self signed certificate
// TODO: add annotation to allow the load of ca certificate
Secure bool `json:"secure"`
// SecureCACert has the filename and SHA1 of the certificate authorities used to validate
// a secured connection to the backend
SecureCACert resolver.AuthSSLCert `json:"secureCert"`
// SSLPassthrough indicates that Ingress controller will delegate TLS termination to the endpoints.
SSLPassthrough bool `json:"sslPassthrough"`
// Endpoints contains the list of endpoints currently running