Add support for multiple alias and remove duplication of SSL certificates (#4472)
This commit is contained in:
parent
4847bb02f0
commit
8def5ef7ca
19 changed files with 190 additions and 101 deletions
|
@ -27,6 +27,7 @@ resty \
|
||||||
-I ./rootfs/etc/nginx/lua \
|
-I ./rootfs/etc/nginx/lua \
|
||||||
--shdict "configuration_data 5M" \
|
--shdict "configuration_data 5M" \
|
||||||
--shdict "certificate_data 16M" \
|
--shdict "certificate_data 16M" \
|
||||||
|
--shdict "certificate_servers 1M" \
|
||||||
--shdict "balancer_ewma 1M" \
|
--shdict "balancer_ewma 1M" \
|
||||||
--shdict "balancer_ewma_last_touched_at 1M" \
|
--shdict "balancer_ewma_last_touched_at 1M" \
|
||||||
--shdict "balancer_ewma_locks 512k" \
|
--shdict "balancer_ewma_locks 512k" \
|
||||||
|
|
|
@ -331,13 +331,13 @@ Enables automatic conversion of preload links specified in the “Link” respon
|
||||||
|
|
||||||
### Server Alias
|
### Server Alias
|
||||||
|
|
||||||
To add Server Aliases to an Ingress rule add the annotation `nginx.ingress.kubernetes.io/server-alias: "<alias>"`.
|
Allows the definition of one or more aliases in the server definition of the NGINX configuration using the annotation `nginx.ingress.kubernetes.io/server-alias: "<alias 1>,<alias 2>"`.
|
||||||
This will create a server with the same configuration, but a different `server_name` as the provided host.
|
This will create a server with the same configuration, but adding new values to the `server_name` directive.
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
A server-alias name cannot conflict with the hostname of an existing server. If it does the server-alias annotation will be ignored.
|
A server-alias name cannot conflict with the hostname of an existing server. If it does, the server-alias annotation will be ignored.
|
||||||
If a server-alias is created and later a new server with the same hostname is created,
|
If a server-alias is created and later a new server with the same hostname is created, the new server configuration will take
|
||||||
the new server configuration will take place over the alias configuration.
|
place over the alias configuration.
|
||||||
|
|
||||||
For more information please see [the `server_name` documentation](http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name).
|
For more information please see [the `server_name` documentation](http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name).
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,11 @@ limitations under the License.
|
||||||
package alias
|
package alias
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
networking "k8s.io/api/networking/v1beta1"
|
networking "k8s.io/api/networking/v1beta1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||||
|
@ -35,5 +39,25 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
||||||
// Parse parses the annotations contained in the ingress rule
|
// Parse parses the annotations contained in the ingress rule
|
||||||
// used to add an alias to the provided hosts
|
// used to add an alias to the provided hosts
|
||||||
func (a alias) Parse(ing *networking.Ingress) (interface{}, error) {
|
func (a alias) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||||
return parser.GetStringAnnotation("server-alias", ing)
|
val, err := parser.GetStringAnnotation("server-alias", ing)
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
aliases := sets.NewString()
|
||||||
|
for _, alias := range strings.Split(val, ",") {
|
||||||
|
alias = strings.TrimSpace(alias)
|
||||||
|
if len(alias) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !aliases.Has(alias) {
|
||||||
|
aliases.Insert(alias)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l := aliases.List()
|
||||||
|
sort.Strings(l)
|
||||||
|
|
||||||
|
return l, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
package alias
|
package alias
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
api "k8s.io/api/core/v1"
|
api "k8s.io/api/core/v1"
|
||||||
|
@ -36,14 +37,15 @@ func TestParse(t *testing.T) {
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
annotations map[string]string
|
annotations map[string]string
|
||||||
expected string
|
expected []string
|
||||||
}{
|
}{
|
||||||
{map[string]string{annotation: "www.example.com"}, "www.example.com"},
|
{map[string]string{annotation: "a.com, b.com, , c.com"}, []string{"a.com", "b.com", "c.com"}},
|
||||||
{map[string]string{annotation: "*.example.com www.example.*"}, "*.example.com www.example.*"},
|
{map[string]string{annotation: "www.example.com"}, []string{"www.example.com"}},
|
||||||
{map[string]string{annotation: `~^www\d+\.example\.com$`}, `~^www\d+\.example\.com$`},
|
{map[string]string{annotation: "*.example.com,www.example.*"}, []string{"*.example.com", "www.example.*"}},
|
||||||
{map[string]string{annotation: ""}, ""},
|
{map[string]string{annotation: `~^www\d+\.example\.com$`}, []string{`~^www\d+\.example\.com$`}},
|
||||||
{map[string]string{}, ""},
|
{map[string]string{annotation: ""}, []string{}},
|
||||||
{nil, ""},
|
{map[string]string{}, []string{}},
|
||||||
|
{nil, []string{}},
|
||||||
}
|
}
|
||||||
|
|
||||||
ing := &networking.Ingress{
|
ing := &networking.Ingress{
|
||||||
|
@ -57,7 +59,7 @@ func TestParse(t *testing.T) {
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
ing.SetAnnotations(testCase.annotations)
|
ing.SetAnnotations(testCase.annotations)
|
||||||
result, _ := ap.Parse(ing)
|
result, _ := ap.Parse(ing)
|
||||||
if result != testCase.expected {
|
if !reflect.DeepEqual(result, testCase.expected) {
|
||||||
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
|
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ const DeniedKeyName = "Denied"
|
||||||
type Ingress struct {
|
type Ingress struct {
|
||||||
metav1.ObjectMeta
|
metav1.ObjectMeta
|
||||||
BackendProtocol string
|
BackendProtocol string
|
||||||
Alias string
|
Aliases []string
|
||||||
BasicDigestAuth auth.Config
|
BasicDigestAuth auth.Config
|
||||||
Canary canary.Config
|
Canary canary.Config
|
||||||
CertificateAuth authtls.Config
|
CertificateAuth authtls.Config
|
||||||
|
@ -124,7 +124,7 @@ type Extractor struct {
|
||||||
func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
|
func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
|
||||||
return Extractor{
|
return Extractor{
|
||||||
map[string]parser.IngressAnnotation{
|
map[string]parser.IngressAnnotation{
|
||||||
"Alias": alias.NewParser(cfg),
|
"Aliases": alias.NewParser(cfg),
|
||||||
"BasicDigestAuth": auth.NewParser(auth.AuthDirectory, cfg),
|
"BasicDigestAuth": auth.NewParser(auth.AuthDirectory, cfg),
|
||||||
"Canary": canary.NewParser(cfg),
|
"Canary": canary.NewParser(cfg),
|
||||||
"CertificateAuth": authtls.NewParser(cfg),
|
"CertificateAuth": authtls.NewParser(cfg),
|
||||||
|
|
|
@ -401,8 +401,11 @@ func (n *NGINXController) getConfiguration(ingresses []*ingress.Ingress) (sets.S
|
||||||
if !hosts.Has(server.Hostname) {
|
if !hosts.Has(server.Hostname) {
|
||||||
hosts.Insert(server.Hostname)
|
hosts.Insert(server.Hostname)
|
||||||
}
|
}
|
||||||
if server.Alias != "" && !hosts.Has(server.Alias) {
|
|
||||||
hosts.Insert(server.Alias)
|
for _, alias := range server.Aliases {
|
||||||
|
if !hosts.Has(alias) {
|
||||||
|
hosts.Insert(alias)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !server.SSLPassthrough {
|
if !server.SSLPassthrough {
|
||||||
|
@ -931,7 +934,7 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
|
||||||
du *ingress.Backend) map[string]*ingress.Server {
|
du *ingress.Backend) map[string]*ingress.Server {
|
||||||
|
|
||||||
servers := make(map[string]*ingress.Server, len(data))
|
servers := make(map[string]*ingress.Server, len(data))
|
||||||
aliases := make(map[string]string, len(data))
|
allAliases := make(map[string][]string, len(data))
|
||||||
|
|
||||||
bdef := n.store.GetDefaultBackend()
|
bdef := n.store.GetDefaultBackend()
|
||||||
ngxProxy := proxy.Config{
|
ngxProxy := proxy.Config{
|
||||||
|
@ -1061,16 +1064,13 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
|
||||||
host = defServerName
|
host = defServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
if anns.Alias != "" {
|
if len(servers[host].Aliases) == 0 {
|
||||||
if servers[host].Alias == "" {
|
servers[host].Aliases = anns.Aliases
|
||||||
servers[host].Alias = anns.Alias
|
if _, ok := allAliases[host]; !ok {
|
||||||
if _, ok := aliases["Alias"]; !ok {
|
allAliases[host] = anns.Aliases
|
||||||
aliases["Alias"] = host
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
klog.Warningf("Aliases already configured for server %q, skipping (Ingress %q)",
|
|
||||||
host, ingKey)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
klog.Warningf("Aliases already configured for server %q, skipping (Ingress %q)", host, ingKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if anns.ServerSnippet != "" {
|
if anns.ServerSnippet != "" {
|
||||||
|
@ -1133,10 +1133,12 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for alias, host := range aliases {
|
for host, hostAliases := range allAliases {
|
||||||
if _, ok := servers[alias]; ok {
|
for index, alias := range hostAliases {
|
||||||
klog.Warningf("Conflicting hostname (%v) and alias (%v). Removing alias to avoid conflicts.", host, alias)
|
if _, ok := servers[alias]; ok {
|
||||||
servers[host].Alias = ""
|
klog.Warningf("Conflicting hostname (%v) and alias (%v). Removing alias to avoid conflicts.", host, alias)
|
||||||
|
servers[host].Aliases = append(servers[host].Aliases[:index], servers[host].Aliases[index+1:]...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -991,30 +991,38 @@ func configureBackends(rawBackends []*ingress.Backend) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type sslConfiguration struct {
|
||||||
|
Certificates map[string]string `json:"certificates"`
|
||||||
|
Servers map[string]string `json:"servers"`
|
||||||
|
}
|
||||||
|
|
||||||
// configureCertificates JSON encodes certificates and POSTs it to an internal HTTP endpoint
|
// configureCertificates JSON encodes certificates and POSTs it to an internal HTTP endpoint
|
||||||
// that is handled by Lua
|
// that is handled by Lua
|
||||||
func configureCertificates(rawServers []*ingress.Server) error {
|
func configureCertificates(rawServers []*ingress.Server) error {
|
||||||
servers := make([]*ingress.Server, 0)
|
configuration := &sslConfiguration{
|
||||||
|
Certificates: map[string]string{},
|
||||||
|
Servers: map[string]string{},
|
||||||
|
}
|
||||||
|
|
||||||
for _, server := range rawServers {
|
for _, rawServer := range rawServers {
|
||||||
if server.SSLCert == nil {
|
if rawServer.SSLCert == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
servers = append(servers, &ingress.Server{
|
uid := rawServer.SSLCert.UID
|
||||||
Hostname: server.Hostname,
|
|
||||||
SSLCert: &ingress.SSLCert{
|
|
||||||
PemCertKey: server.SSLCert.PemCertKey,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if server.Alias != "" && ssl.IsValidHostname(server.Alias, server.SSLCert.CN) {
|
if _, ok := configuration.Certificates[uid]; !ok {
|
||||||
servers = append(servers, &ingress.Server{
|
configuration.Certificates[uid] = rawServer.SSLCert.PemCertKey
|
||||||
Hostname: server.Alias,
|
}
|
||||||
SSLCert: &ingress.SSLCert{
|
|
||||||
PemCertKey: server.SSLCert.PemCertKey,
|
configuration.Servers[rawServer.Hostname] = uid
|
||||||
},
|
|
||||||
})
|
for _, alias := range rawServer.Aliases {
|
||||||
|
if !ssl.IsValidHostname(alias, rawServer.SSLCert.CN) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration.Servers[alias] = uid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1024,15 +1032,14 @@ func configureCertificates(rawServers []*ingress.Server) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
servers = append(servers, &ingress.Server{
|
configuration.Servers[redirect.From] = redirect.SSLCert.UID
|
||||||
Hostname: redirect.From,
|
|
||||||
SSLCert: &ingress.SSLCert{
|
if _, ok := configuration.Certificates[redirect.SSLCert.UID]; !ok {
|
||||||
PemCertKey: redirect.SSLCert.PemCertKey,
|
configuration.Certificates[redirect.SSLCert.UID] = redirect.SSLCert.PemCertKey
|
||||||
},
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
statusCode, _, err := nginx.NewPostStatusRequest("/configuration/servers", "application/json", servers)
|
statusCode, _, err := nginx.NewPostStatusRequest("/configuration/servers", "application/json", configuration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,7 +185,7 @@ func TestConfigureDynamically(t *testing.T) {
|
||||||
}
|
}
|
||||||
body := string(b)
|
body := string(b)
|
||||||
|
|
||||||
endpointStats[r.URL.Path] += 1
|
endpointStats[r.URL.Path]++
|
||||||
|
|
||||||
switch r.URL.Path {
|
switch r.URL.Path {
|
||||||
case "/configuration/backends":
|
case "/configuration/backends":
|
||||||
|
@ -206,7 +206,7 @@ func TestConfigureDynamically(t *testing.T) {
|
||||||
}
|
}
|
||||||
case "/configuration/servers":
|
case "/configuration/servers":
|
||||||
{
|
{
|
||||||
if !strings.Contains(body, "[]") {
|
if !strings.Contains(body, `{"certificates":{},"servers":{}}`) {
|
||||||
t.Errorf("controllerPodsCount should be present in JSON content: %v", body)
|
t.Errorf("controllerPodsCount should be present in JSON content: %v", body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -337,6 +337,7 @@ func TestConfigureCertificates(t *testing.T) {
|
||||||
Hostname: "myapp.fake",
|
Hostname: "myapp.fake",
|
||||||
SSLCert: &ingress.SSLCert{
|
SSLCert: &ingress.SSLCert{
|
||||||
PemCertKey: "fake-cert",
|
PemCertKey: "fake-cert",
|
||||||
|
UID: "c89a5111-b2e9-4af8-be19-c2a4a924c256",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
@ -354,18 +355,18 @@ func TestConfigureCertificates(t *testing.T) {
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
var postedServers []ingress.Server
|
var conf sslConfiguration
|
||||||
err = jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(b, &postedServers)
|
err = jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(b, &conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(servers) != len(postedServers) {
|
if len(servers) != len(conf.Servers) {
|
||||||
t.Errorf("Expected servers to be the same length as the posted servers")
|
t.Errorf("Expected servers to be the same length as the posted servers")
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, server := range servers {
|
for _, server := range servers {
|
||||||
if !server.Equal(&postedServers[i]) {
|
if server.SSLCert.UID != conf.Servers[server.Hostname] {
|
||||||
t.Errorf("Expected servers and posted servers to be equal")
|
t.Errorf("Expected servers and posted servers to be equal")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,7 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
|
||||||
return nil, fmt.Errorf("key 'tls.key' missing from Secret %q", secretName)
|
return nil, fmt.Errorf("key 'tls.key' missing from Secret %q", secretName)
|
||||||
}
|
}
|
||||||
|
|
||||||
sslCert, err = ssl.CreateSSLCert(cert, key)
|
sslCert, err = ssl.CreateSSLCert(cert, key, string(secret.UID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unexpected error creating SSL Cert: %v", err)
|
return nil, fmt.Errorf("unexpected error creating SSL Cert: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,7 @@ var (
|
||||||
"balancer_ewma": 10,
|
"balancer_ewma": 10,
|
||||||
"balancer_ewma_last_touched_at": 10,
|
"balancer_ewma_last_touched_at": 10,
|
||||||
"balancer_ewma_locks": 1,
|
"balancer_ewma_locks": 1,
|
||||||
|
"certificate_servers": 5,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,9 @@ type SSLCert struct {
|
||||||
|
|
||||||
// Pem encoded certificate and key concatenated
|
// Pem encoded certificate and key concatenated
|
||||||
PemCertKey string `json:"pemCertKey,omitempty"`
|
PemCertKey string `json:"pemCertKey,omitempty"`
|
||||||
|
|
||||||
|
// UID unique identifier of the Kubernetes Secret
|
||||||
|
UID string `json:"uid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetObjectKind implements the ObjectKind interface as a noop
|
// GetObjectKind implements the ObjectKind interface as a noop
|
||||||
|
|
|
@ -184,8 +184,8 @@ type Server struct {
|
||||||
SSLCert *SSLCert `json:"sslCert"`
|
SSLCert *SSLCert `json:"sslCert"`
|
||||||
// Locations list of URIs configured in the server.
|
// Locations list of URIs configured in the server.
|
||||||
Locations []*Location `json:"locations,omitempty"`
|
Locations []*Location `json:"locations,omitempty"`
|
||||||
// Alias return the alias of the server name
|
// Aliases return the alias of the server name
|
||||||
Alias string `json:"alias,omitempty"`
|
Aliases []string `json:"aliases,omitempty"`
|
||||||
// RedirectFromToWWW returns if a redirect to/from prefix www is required
|
// RedirectFromToWWW returns if a redirect to/from prefix www is required
|
||||||
RedirectFromToWWW bool `json:"redirectFromToWWW,omitempty"`
|
RedirectFromToWWW bool `json:"redirectFromToWWW,omitempty"`
|
||||||
// CertificateAuth indicates the this server requires mutual authentication
|
// CertificateAuth indicates the this server requires mutual authentication
|
||||||
|
|
|
@ -269,9 +269,24 @@ func (s1 *Server) Equal(s2 *Server) bool {
|
||||||
if !(s1.SSLCert).Equal(s2.SSLCert) {
|
if !(s1.SSLCert).Equal(s2.SSLCert) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if s1.Alias != s2.Alias {
|
|
||||||
|
if len(s1.Aliases) != len(s2.Aliases) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, a1 := range s1.Aliases {
|
||||||
|
found := false
|
||||||
|
for _, a2 := range s2.Aliases {
|
||||||
|
if a1 == a2 {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if s1.RedirectFromToWWW != s2.RedirectFromToWWW {
|
if s1.RedirectFromToWWW != s2.RedirectFromToWWW {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -528,6 +543,9 @@ func (s1 *SSLCert) Equal(s2 *SSLCert) bool {
|
||||||
if s1.PemCertKey != s2.PemCertKey {
|
if s1.PemCertKey != s2.PemCertKey {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if s1.UID != s2.UID {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return sets.StringElementsMatch(s1.CN, s2.CN)
|
return sets.StringElementsMatch(s1.CN, s2.CN)
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,10 @@ import (
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// FakeSSLCertificateUID defines the default UID to use for the fake SSL
|
||||||
|
// certificate generated by the ingress controller
|
||||||
|
var FakeSSLCertificateUID = "00000000-0000-0000-0000-000000000000"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
oidExtensionSubjectAltName = asn1.ObjectIdentifier{2, 5, 29, 17}
|
oidExtensionSubjectAltName = asn1.ObjectIdentifier{2, 5, 29, 17}
|
||||||
)
|
)
|
||||||
|
@ -75,7 +79,7 @@ func verifyPemCertAgainstRootCA(pemCert *x509.Certificate, ca []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateSSLCert validates cert and key, extracts common names and returns corresponding SSLCert object
|
// CreateSSLCert validates cert and key, extracts common names and returns corresponding SSLCert object
|
||||||
func CreateSSLCert(cert, key []byte) (*ingress.SSLCert, error) {
|
func CreateSSLCert(cert, key []byte, uid string) (*ingress.SSLCert, error) {
|
||||||
var pemCertBuffer bytes.Buffer
|
var pemCertBuffer bytes.Buffer
|
||||||
pemCertBuffer.Write(cert)
|
pemCertBuffer.Write(cert)
|
||||||
|
|
||||||
|
@ -139,6 +143,7 @@ func CreateSSLCert(cert, key []byte) (*ingress.SSLCert, error) {
|
||||||
CN: cn.List(),
|
CN: cn.List(),
|
||||||
ExpireTime: pemCert.NotAfter,
|
ExpireTime: pemCert.NotAfter,
|
||||||
PemCertKey: pemCertBuffer.String(),
|
PemCertKey: pemCertBuffer.String(),
|
||||||
|
UID: uid,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,7 +346,7 @@ func AddOrUpdateDHParam(name string, dh []byte) (string, error) {
|
||||||
func GetFakeSSLCert() *ingress.SSLCert {
|
func GetFakeSSLCert() *ingress.SSLCert {
|
||||||
cert, key := getFakeHostSSLCert("ingress.local")
|
cert, key := getFakeHostSSLCert("ingress.local")
|
||||||
|
|
||||||
sslCert, err := CreateSSLCert(cert, key)
|
sslCert, err := CreateSSLCert(cert, key, FakeSSLCertificateUID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Fatalf("unexpected error creating fake SSL Cert: %v", err)
|
klog.Fatalf("unexpected error creating fake SSL Cert: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@ func TestStoreSSLCertOnDisk(t *testing.T) {
|
||||||
c := encodeCertPEM(cert.Cert)
|
c := encodeCertPEM(cert.Cert)
|
||||||
k := encodePrivateKeyPEM(cert.Key)
|
k := encodePrivateKeyPEM(cert.Key)
|
||||||
|
|
||||||
sslCert, err := CreateSSLCert(c, k)
|
sslCert, err := CreateSSLCert(c, k, FakeSSLCertificateUID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error creating SSL certificate: %v", err)
|
t.Fatalf("unexpected error creating SSL certificate: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ func TestCACert(t *testing.T) {
|
||||||
k := encodePrivateKeyPEM(cert.Key)
|
k := encodePrivateKeyPEM(cert.Key)
|
||||||
ca := encodeCertPEM(CA.Cert)
|
ca := encodeCertPEM(CA.Cert)
|
||||||
|
|
||||||
sslCert, err := CreateSSLCert(c, k)
|
sslCert, err := CreateSSLCert(c, k, FakeSSLCertificateUID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error creating SSL certificate: %v", err)
|
t.Fatalf("unexpected error creating SSL certificate: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -197,7 +197,7 @@ func TestCreateSSLCert(t *testing.T) {
|
||||||
c := encodeCertPEM(cert.Cert)
|
c := encodeCertPEM(cert.Cert)
|
||||||
k := encodePrivateKeyPEM(cert.Key)
|
k := encodePrivateKeyPEM(cert.Key)
|
||||||
|
|
||||||
sslCert, err := CreateSSLCert(c, k)
|
sslCert, err := CreateSSLCert(c, k, FakeSSLCertificateUID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error checking SSL certificate: %v", err)
|
t.Fatalf("unexpected error checking SSL certificate: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ local cjson = require("cjson.safe")
|
||||||
-- this is the Lua representation of Configuration struct in internal/ingress/types.go
|
-- this is the Lua representation of Configuration struct in internal/ingress/types.go
|
||||||
local configuration_data = ngx.shared.configuration_data
|
local configuration_data = ngx.shared.configuration_data
|
||||||
local certificate_data = ngx.shared.certificate_data
|
local certificate_data = ngx.shared.certificate_data
|
||||||
|
local certificate_servers = ngx.shared.certificate_servers
|
||||||
|
|
||||||
local _M = {}
|
local _M = {}
|
||||||
|
|
||||||
|
@ -35,7 +36,12 @@ local function fetch_request_body()
|
||||||
end
|
end
|
||||||
|
|
||||||
function _M.get_pem_cert_key(hostname)
|
function _M.get_pem_cert_key(hostname)
|
||||||
return certificate_data:get(hostname)
|
local uid = certificate_servers:get(hostname)
|
||||||
|
if not uid then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return certificate_data:get(uid)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function handle_servers()
|
local function handle_servers()
|
||||||
|
@ -45,30 +51,39 @@ local function handle_servers()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local raw_servers = fetch_request_body()
|
local raw_configuration = fetch_request_body()
|
||||||
|
|
||||||
local servers, err = cjson.decode(raw_servers)
|
local configuration, err = cjson.decode(raw_configuration)
|
||||||
if not servers then
|
if not configuration then
|
||||||
ngx.log(ngx.ERR, "could not parse servers: ", err)
|
ngx.log(ngx.ERR, "could not parse configuration: ", err)
|
||||||
ngx.status = ngx.HTTP_BAD_REQUEST
|
ngx.status = ngx.HTTP_BAD_REQUEST
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local err_buf = {}
|
local err_buf = {}
|
||||||
for _, server in ipairs(servers) do
|
|
||||||
if server.hostname and server.sslCert.pemCertKey then
|
for server, uid in pairs(configuration.servers) do
|
||||||
local success, set_err, forcible = certificate_data:set(server.hostname, server.sslCert.pemCertKey)
|
local success, set_err, forcible = certificate_servers:set(server, uid)
|
||||||
if not success then
|
if not success then
|
||||||
local err_msg = string.format("error setting certificate for %s: %s\n", server.hostname, tostring(set_err))
|
local err_msg = string.format("error setting certificate for %s: %s\n", server, tostring(set_err))
|
||||||
table.insert(err_buf, err_msg)
|
table.insert(err_buf, err_msg)
|
||||||
end
|
end
|
||||||
if forcible then
|
if forcible then
|
||||||
local msg = string.format("certificate_data dictionary is full, LRU entry has been removed to store %s",
|
local msg = string.format("certificate_servers dictionary is full, LRU entry has been removed to store %s",
|
||||||
server.hostname)
|
server)
|
||||||
ngx.log(ngx.WARN, msg)
|
ngx.log(ngx.WARN, msg)
|
||||||
end
|
end
|
||||||
else
|
end
|
||||||
ngx.log(ngx.WARN, "hostname or pemCertKey are not present")
|
|
||||||
|
for uid, cert in pairs(configuration.certificates) do
|
||||||
|
local success, set_err, forcible = certificate_data:set(uid, cert)
|
||||||
|
if not success then
|
||||||
|
local err_msg = string.format("error setting certificate for %s: %s\n", uid, tostring(set_err))
|
||||||
|
table.insert(err_buf, err_msg)
|
||||||
|
end
|
||||||
|
if forcible then
|
||||||
|
local msg = string.format("certificate_data dictionary is full, LRU entry has been removed to store %s", uid)
|
||||||
|
ngx.log(ngx.WARN, msg)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,8 @@ end
|
||||||
local EXAMPLE_CERT = read_file("rootfs/etc/nginx/lua/test/fixtures/example-com-cert.pem")
|
local EXAMPLE_CERT = read_file("rootfs/etc/nginx/lua/test/fixtures/example-com-cert.pem")
|
||||||
local DEFAULT_CERT = read_file("rootfs/etc/nginx/lua/test/fixtures/default-cert.pem")
|
local DEFAULT_CERT = read_file("rootfs/etc/nginx/lua/test/fixtures/default-cert.pem")
|
||||||
local DEFAULT_CERT_HOSTNAME = "_"
|
local DEFAULT_CERT_HOSTNAME = "_"
|
||||||
|
local UUID = "2ea8adb5-8ebb-4b14-a79b-0cdcd892e884"
|
||||||
|
local DEFAULT_UUID = "00000000-0000-0000-0000-000000000000"
|
||||||
|
|
||||||
local function assert_certificate_is_set(cert)
|
local function assert_certificate_is_set(cert)
|
||||||
spy.on(ngx, "log")
|
spy.on(ngx, "log")
|
||||||
|
@ -45,50 +47,57 @@ describe("Certificate", function()
|
||||||
ngx.exit = function(status) end
|
ngx.exit = function(status) end
|
||||||
|
|
||||||
|
|
||||||
ngx.shared.certificate_data:set(DEFAULT_CERT_HOSTNAME, DEFAULT_CERT)
|
ngx.shared.certificate_servers:set(DEFAULT_CERT_HOSTNAME, DEFAULT_UUID)
|
||||||
|
ngx.shared.certificate_data:set(DEFAULT_UUID, DEFAULT_CERT)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
after_each(function()
|
after_each(function()
|
||||||
ngx = unmocked_ngx
|
ngx = unmocked_ngx
|
||||||
ngx.shared.certificate_data:flush_all()
|
ngx.shared.certificate_data:flush_all()
|
||||||
|
ngx.shared.certificate_servers:flush_all()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("sets certificate and key when hostname is found in dictionary", function()
|
it("sets certificate and key when hostname is found in dictionary", function()
|
||||||
ngx.shared.certificate_data:set("hostname", EXAMPLE_CERT)
|
ngx.shared.certificate_servers:set("hostname", UUID)
|
||||||
|
ngx.shared.certificate_data:set(UUID, EXAMPLE_CERT)
|
||||||
|
|
||||||
assert_certificate_is_set(EXAMPLE_CERT)
|
assert_certificate_is_set(EXAMPLE_CERT)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("sets certificate and key for wildcard cert", function()
|
it("sets certificate and key for wildcard cert", function()
|
||||||
ssl.server_name = function() return "sub.hostname", nil end
|
ssl.server_name = function() return "sub.hostname", nil end
|
||||||
ngx.shared.certificate_data:set("*.hostname", EXAMPLE_CERT)
|
ngx.shared.certificate_servers:set("*.hostname", UUID)
|
||||||
|
ngx.shared.certificate_data:set(UUID, EXAMPLE_CERT)
|
||||||
|
|
||||||
assert_certificate_is_set(EXAMPLE_CERT)
|
assert_certificate_is_set(EXAMPLE_CERT)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("sets certificate and key for domain with trailing dot", function()
|
it("sets certificate and key for domain with trailing dot", function()
|
||||||
ssl.server_name = function() return "hostname.", nil end
|
ssl.server_name = function() return "hostname.", nil end
|
||||||
ngx.shared.certificate_data:set("hostname", EXAMPLE_CERT)
|
ngx.shared.certificate_servers:set("hostname", UUID)
|
||||||
|
ngx.shared.certificate_data:set(UUID, EXAMPLE_CERT)
|
||||||
|
|
||||||
assert_certificate_is_set(EXAMPLE_CERT)
|
assert_certificate_is_set(EXAMPLE_CERT)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("fallbacks to default certificate and key for domain with many trailing dots", function()
|
it("fallbacks to default certificate and key for domain with many trailing dots", function()
|
||||||
ssl.server_name = function() return "hostname..", nil end
|
ssl.server_name = function() return "hostname..", nil end
|
||||||
ngx.shared.certificate_data:set("hostname", EXAMPLE_CERT)
|
ngx.shared.certificate_servers:set("hostname", UUID)
|
||||||
|
ngx.shared.certificate_data:set(UUID, EXAMPLE_CERT)
|
||||||
|
|
||||||
assert_certificate_is_set(DEFAULT_CERT)
|
assert_certificate_is_set(DEFAULT_CERT)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("sets certificate and key for nested wildcard cert", function()
|
it("sets certificate and key for nested wildcard cert", function()
|
||||||
ssl.server_name = function() return "sub.nested.hostname", nil end
|
ssl.server_name = function() return "sub.nested.hostname", nil end
|
||||||
ngx.shared.certificate_data:set("*.nested.hostname", EXAMPLE_CERT)
|
ngx.shared.certificate_servers:set("*.nested.hostname", UUID)
|
||||||
|
ngx.shared.certificate_data:set(UUID, EXAMPLE_CERT)
|
||||||
|
|
||||||
assert_certificate_is_set(EXAMPLE_CERT)
|
assert_certificate_is_set(EXAMPLE_CERT)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("logs error message when certificate in dictionary is invalid", function()
|
it("logs error message when certificate in dictionary is invalid", function()
|
||||||
ngx.shared.certificate_data:set("hostname", "something invalid")
|
ngx.shared.certificate_servers:set("hostname", "something invalid")
|
||||||
|
|
||||||
spy.on(ngx, "log")
|
spy.on(ngx, "log")
|
||||||
|
|
||||||
|
@ -108,7 +117,8 @@ describe("Certificate", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("fails when hostname does not have certificate and default cert is invalid", function()
|
it("fails when hostname does not have certificate and default cert is invalid", function()
|
||||||
ngx.shared.certificate_data:set(DEFAULT_CERT_HOSTNAME, "invalid")
|
ngx.shared.certificate_servers:set(DEFAULT_CERT_HOSTNAME, UID)
|
||||||
|
ngx.shared.certificate_data:set(UID, "invalid")
|
||||||
|
|
||||||
spy.on(ngx, "log")
|
spy.on(ngx, "log")
|
||||||
|
|
||||||
|
|
|
@ -166,7 +166,7 @@ describe("Configuration", function()
|
||||||
describe("handle_servers()", function()
|
describe("handle_servers()", function()
|
||||||
it("should not accept non POST methods", function()
|
it("should not accept non POST methods", function()
|
||||||
ngx.var.request_method = "GET"
|
ngx.var.request_method = "GET"
|
||||||
|
|
||||||
local s = spy.on(ngx, "print")
|
local s = spy.on(ngx, "print")
|
||||||
assert.has_no.errors(configuration.handle_servers)
|
assert.has_no.errors(configuration.handle_servers)
|
||||||
assert.spy(s).was_called_with("Only POST requests are allowed!")
|
assert.spy(s).was_called_with("Only POST requests are allowed!")
|
||||||
|
@ -232,7 +232,7 @@ describe("Configuration", function()
|
||||||
|
|
||||||
local s = spy.on(ngx, "log")
|
local s = spy.on(ngx, "log")
|
||||||
assert.has_no.errors(configuration.handle_servers)
|
assert.has_no.errors(configuration.handle_servers)
|
||||||
assert.spy(s).was_called_with(ngx.ERR,
|
assert.spy(s).was_called_with(ngx.ERR,
|
||||||
"error setting certificate for hostname: error\nerror setting certificate for hostname2: error\n")
|
"error setting certificate for hostname: error\nerror setting certificate for hostname2: error\n")
|
||||||
assert.same(ngx.status, ngx.HTTP_INTERNAL_SERVER_ERROR)
|
assert.same(ngx.status, ngx.HTTP_INTERNAL_SERVER_ERROR)
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -506,7 +506,7 @@ http {
|
||||||
|
|
||||||
## start server {{ $server.Hostname }}
|
## start server {{ $server.Hostname }}
|
||||||
server {
|
server {
|
||||||
server_name {{ $server.Hostname }} {{ $server.Alias }};
|
server_name {{ $server.Hostname }} {{range $server.Aliases }}{{ . }} {{ end }};
|
||||||
|
|
||||||
{{ if gt (len $cfg.BlockUserAgents) 0 }}
|
{{ if gt (len $cfg.BlockUserAgents) 0 }}
|
||||||
if ($block_ua) {
|
if ($block_ua) {
|
||||||
|
|
Loading…
Reference in a new issue