Add support for enabling ssl_ciphers per host (#2006)

* Add support for adding ssl_ciphers

* Add documentation
This commit is contained in:
Anish Ramasekar 2018-01-31 10:53:07 -06:00 committed by Manuel Alejandro de Brito Fontes
parent 8bdb5e42f2
commit d7ef6b3fc7
8 changed files with 131 additions and 1 deletions

View file

@ -59,6 +59,7 @@ The following annotations are supported:
|[nginx.ingress.kubernetes.io/upstream-vhost](#custom-nginx-upstream-vhost)|string| |[nginx.ingress.kubernetes.io/upstream-vhost](#custom-nginx-upstream-vhost)|string|
|[nginx.ingress.kubernetes.io/whitelist-source-range](#whitelist-source-range)|CIDR| |[nginx.ingress.kubernetes.io/whitelist-source-range](#whitelist-source-range)|CIDR|
|[nginx.ingress.kubernetes.io/proxy-buffering](#proxy-buffering)|string| |[nginx.ingress.kubernetes.io/proxy-buffering](#proxy-buffering)|string|
|[nginx.ingress.kubernetes.io/ssl-ciphers](#ssl-ciphers)|string|
**Note:** all the values must be a string. In case of booleans or number it must be quoted. **Note:** all the values must be a string. In case of booleans or number it must be quoted.
@ -419,3 +420,13 @@ To use custom values in an Ingress rule define these annotation:
```yaml ```yaml
nginx.ingress.kubernetes.io/proxy-buffering: "on" nginx.ingress.kubernetes.io/proxy-buffering: "on"
``` ```
### SSL ciphers
Specifies the [enabled ciphers](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_ciphers).
Using this annotation will set the `ssl_ciphers` directive at the server level. This configuration is active for all the paths in the host.
```yaml
nginx.ingress.kubernetes.io/ssl-ciphers: "ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP"
```

View file

@ -19,6 +19,7 @@ package annotations
import ( import (
"github.com/golang/glog" "github.com/golang/glog"
"github.com/imdario/mergo" "github.com/imdario/mergo"
"k8s.io/ingress-nginx/internal/ingress/annotations/sslcipher"
extensions "k8s.io/api/extensions/v1beta1" extensions "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -85,6 +86,7 @@ type Ingress struct {
VtsFilterKey string VtsFilterKey string
Whitelist ipwhitelist.SourceRange Whitelist ipwhitelist.SourceRange
XForwardedPrefix bool XForwardedPrefix bool
SSLCiphers string
} }
// Extractor defines the annotation parsers to be used in the extraction of annotations // Extractor defines the annotation parsers to be used in the extraction of annotations
@ -121,6 +123,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
"VtsFilterKey": vtsfilterkey.NewParser(cfg), "VtsFilterKey": vtsfilterkey.NewParser(cfg),
"Whitelist": ipwhitelist.NewParser(cfg), "Whitelist": ipwhitelist.NewParser(cfg),
"XForwardedPrefix": xforwardedprefix.NewParser(cfg), "XForwardedPrefix": xforwardedprefix.NewParser(cfg),
"SSLCiphers": sslcipher.NewParser(cfg),
}, },
} }
} }

View file

@ -0,0 +1,39 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sslcipher
import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
type sslCipher struct {
r resolver.Resolver
}
// NewParser creates a new sslCipher annotation parser
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return sslCipher{r}
}
// Parse parses the annotations contained in the ingress rule
// used to add ssl-ciphers to the server name
func (sc sslCipher) Parse(ing *extensions.Ingress) (interface{}, error) {
return parser.GetStringAnnotation("ssl-ciphers", ing)
}

View file

@ -0,0 +1,63 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sslcipher
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func TestParse(t *testing.T) {
annotation := parser.GetAnnotationWithPrefix("ssl-ciphers")
ap := NewParser(&resolver.Mock{})
if ap == nil {
t.Fatalf("expected a parser.IngressAnnotation but returned nil")
}
testCases := []struct {
annotations map[string]string
expected string
}{
{map[string]string{annotation: "ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP"}, "ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP"},
{map[string]string{annotation: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"},
"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"},
{map[string]string{annotation: ""}, ""},
{map[string]string{}, ""},
{nil, ""},
}
ing := &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
}
for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations)
result, _ := ap.Parse(ing)
if result != testCase.expected {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
}
}
}

View file

@ -913,6 +913,7 @@ func (n *NGINXController) createServers(data []*extensions.Ingress,
}, },
}, },
SSLPassthrough: anns.SSLPassthrough, SSLPassthrough: anns.SSLPassthrough,
SSLCiphers: anns.SSLCiphers,
} }
} }
} }
@ -954,6 +955,11 @@ func (n *NGINXController) createServers(data []*extensions.Ingress,
servers[host].ServerSnippet = anns.ServerSnippet servers[host].ServerSnippet = anns.ServerSnippet
} }
// only add ssl ciphers if the server does not have one previously configured
if servers[host].SSLCiphers == "" && anns.SSLCiphers != "" {
servers[host].SSLCiphers = anns.SSLCiphers
}
// only add a certificate if the server does not have one previously configured // only add a certificate if the server does not have one previously configured
if servers[host].SSLCertificate != "" { if servers[host].SSLCertificate != "" {
continue continue

View file

@ -156,10 +156,11 @@ type Server struct {
// CertificateAuth indicates the this server requires mutual authentication // CertificateAuth indicates the this server requires mutual authentication
// +optional // +optional
CertificateAuth authtls.Config `json:"certificateAuth"` CertificateAuth authtls.Config `json:"certificateAuth"`
// ServerSnippet returns the snippet of server // ServerSnippet returns the snippet of server
// +optional // +optional
ServerSnippet string `json:"serverSnippet"` ServerSnippet string `json:"serverSnippet"`
// SSLCiphers returns list of ciphers to be enabled
SSLCiphers string `json:"sslCiphers,omitempty"`
} }
// Location describes an URI inside a server. // Location describes an URI inside a server.

View file

@ -281,6 +281,9 @@ func (s1 *Server) Equal(s2 *Server) bool {
if len(s1.Locations) != len(s2.Locations) { if len(s1.Locations) != len(s2.Locations) {
return false return false
} }
if s1.SSLCiphers != s2.SSLCiphers {
return false
}
// Location are sorted // Location are sorted
for idx, s1l := range s1.Locations { for idx, s1l := range s1.Locations {

View file

@ -634,6 +634,10 @@ stream {
{{ end }} {{ end }}
{{ end }} {{ end }}
{{ if not (empty $server.SSLCiphers) }}
ssl_ciphers {{ $server.SSLCiphers }};
{{ end }}
{{ if not (empty $server.ServerSnippet) }} {{ if not (empty $server.ServerSnippet) }}
{{ $server.ServerSnippet }} {{ $server.ServerSnippet }}
{{ end }} {{ end }}