Merge branch 'master' of https://github.com/kubernetes/ingress-nginx into proxyssl
This commit is contained in:
commit
65b9e2c574
391 changed files with 23957 additions and 20447 deletions
|
|
@ -16,92 +16,5 @@ limitations under the License.
|
|||
|
||||
package file
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubernetes/pkg/util/filesystem"
|
||||
)
|
||||
|
||||
// ReadWriteByUser defines linux permission to read and write files for the owner user
|
||||
const ReadWriteByUser = 0660
|
||||
|
||||
// ReadByUserGroup defines linux permission to read files by the user and group owner/s
|
||||
const ReadByUserGroup = 0640
|
||||
|
||||
// Filesystem is an interface that we can use to mock various filesystem operations
|
||||
type Filesystem interface {
|
||||
filesystem.Filesystem
|
||||
}
|
||||
|
||||
// NewLocalFS implements Filesystem using same-named functions from "os" and "io/ioutil".
|
||||
func NewLocalFS() (Filesystem, error) {
|
||||
fs := filesystem.DefaultFs{}
|
||||
|
||||
for _, directory := range directories {
|
||||
err := fs.MkdirAll(directory, ReadWriteByUser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return fs, nil
|
||||
}
|
||||
|
||||
// NewFakeFS creates an in-memory filesystem with all the required
|
||||
// paths used by the ingress controller.
|
||||
// This allows running test without polluting the local machine.
|
||||
func NewFakeFS() (Filesystem, error) {
|
||||
osFs := filesystem.DefaultFs{}
|
||||
fakeFs := filesystem.NewFakeFs()
|
||||
|
||||
//TODO: find another way to do this
|
||||
rootFS := filepath.Clean(fmt.Sprintf("%v/%v", os.Getenv("GOPATH"), "src/k8s.io/ingress-nginx/rootfs"))
|
||||
|
||||
var fileList []string
|
||||
err := filepath.Walk(rootFS, func(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
file := strings.TrimPrefix(path, rootFS)
|
||||
if file == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
fileList = append(fileList, file)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, file := range fileList {
|
||||
realPath := fmt.Sprintf("%v%v", rootFS, file)
|
||||
|
||||
data, err := osFs.ReadFile(realPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fakeFile, err := fakeFs.Create(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = fakeFile.Write(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return fakeFs, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
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 file
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewFakeFS(t *testing.T) {
|
||||
fs, err := NewFakeFS()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating filesystem abstraction: %v", err)
|
||||
}
|
||||
|
||||
if fs == nil {
|
||||
t.Fatal("expected a filesystem but none returned")
|
||||
}
|
||||
|
||||
_, err = fs.Stat("/etc/nginx/nginx.conf")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error reading default nginx.conf file: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -39,12 +39,14 @@ import (
|
|||
"k8s.io/ingress-nginx/internal/ingress/annotations/cors"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/customhttperrors"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/defaultbackend"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/fastcgi"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/http2pushpreload"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/influxdb"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/loadbalancing"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/log"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/luarestywaf"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/mirror"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/portinredirect"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/proxy"
|
||||
|
|
@ -83,6 +85,7 @@ type Ingress struct {
|
|||
CustomHTTPErrors []int
|
||||
DefaultBackend *apiv1.Service
|
||||
//TODO: Change this back into an error when https://github.com/imdario/mergo/issues/100 is resolved
|
||||
FastCGI fastcgi.Config
|
||||
Denied *string
|
||||
ExternalAuth authreq.Config
|
||||
EnableGlobalAuth bool
|
||||
|
|
@ -109,6 +112,7 @@ type Ingress struct {
|
|||
LuaRestyWAF luarestywaf.Config
|
||||
InfluxDB influxdb.Config
|
||||
ModSecurity modsecurity.Config
|
||||
Mirror mirror.Config
|
||||
}
|
||||
|
||||
// Extractor defines the annotation parsers to be used in the extraction of annotations
|
||||
|
|
@ -130,6 +134,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
|
|||
"CorsConfig": cors.NewParser(cfg),
|
||||
"CustomHTTPErrors": customhttperrors.NewParser(cfg),
|
||||
"DefaultBackend": defaultbackend.NewParser(cfg),
|
||||
"FastCGI": fastcgi.NewParser(cfg),
|
||||
"ExternalAuth": authreq.NewParser(cfg),
|
||||
"EnableGlobalAuth": authreqglobal.NewParser(cfg),
|
||||
"HTTP2PushPreload": http2pushpreload.NewParser(cfg),
|
||||
|
|
@ -156,6 +161,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
|
|||
"InfluxDB": influxdb.NewParser(cfg),
|
||||
"BackendProtocol": backendprotocol.NewParser(cfg),
|
||||
"ModSecurity": modsecurity.NewParser(cfg),
|
||||
"Mirror": mirror.NewParser(cfg),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ func (m mockCfg) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error)
|
|||
return &resolver.AuthSSLCert{
|
||||
Secret: name,
|
||||
CAFileName: "/opt/ca.pem",
|
||||
PemSHA: "123",
|
||||
CASHA: "123",
|
||||
}, nil
|
||||
}
|
||||
return nil, nil
|
||||
|
|
|
|||
|
|
@ -86,12 +86,7 @@ func (e1 *Config) Equal(e2 *Config) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
match = sets.StringElementsMatch(e1.AuthCacheDuration, e2.AuthCacheDuration)
|
||||
if !match {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
return sets.StringElementsMatch(e1.AuthCacheDuration, e2.AuthCacheDuration)
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ func TestHeaderAnnotations(t *testing.T) {
|
|||
i, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
if test.expErr {
|
||||
if err == nil {
|
||||
t.Errorf("%v: expected error but retuned nil", err.Error())
|
||||
t.Error("expected error but retuned nil")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
|
@ -216,7 +216,7 @@ func TestCacheDurationAnnotations(t *testing.T) {
|
|||
i, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
if test.expErr {
|
||||
if err == nil {
|
||||
t.Errorf("%v: expected error but retuned nil", err.Error())
|
||||
t.Errorf("expected error but retuned nil")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ func (m mockSecret) GetAuthCertificate(name string) (*resolver.AuthSSLCert, erro
|
|||
return &resolver.AuthSSLCert{
|
||||
Secret: "default/demo-secret",
|
||||
CAFileName: "/ssl/ca.crt",
|
||||
PemSHA: "abc",
|
||||
CASHA: "abc",
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
|
@ -202,12 +202,12 @@ func TestEquals(t *testing.T) {
|
|||
sslCert1 := resolver.AuthSSLCert{
|
||||
Secret: "default/demo-secret",
|
||||
CAFileName: "/ssl/ca.crt",
|
||||
PemSHA: "abc",
|
||||
CASHA: "abc",
|
||||
}
|
||||
sslCert2 := resolver.AuthSSLCert{
|
||||
Secret: "default/other-demo-secret",
|
||||
CAFileName: "/ssl/ca.crt",
|
||||
PemSHA: "abc",
|
||||
CASHA: "abc",
|
||||
}
|
||||
cfg1.AuthSSLCert = sslCert1
|
||||
cfg2.AuthSSLCert = sslCert2
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import (
|
|||
const HTTP = "HTTP"
|
||||
|
||||
var (
|
||||
validProtocols = regexp.MustCompile(`^(HTTP|HTTPS|AJP|GRPC|GRPCS)$`)
|
||||
validProtocols = regexp.MustCompile(`^(HTTP|HTTPS|AJP|GRPC|GRPCS|FCGI)$`)
|
||||
)
|
||||
|
||||
type backendProtocol struct {
|
||||
|
|
|
|||
107
internal/ingress/annotations/fastcgi/main.go
Normal file
107
internal/ingress/annotations/fastcgi/main.go
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
Copyright 2018 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 fastcgi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
networking "k8s.io/api/networking/v1beta1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
|
||||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
)
|
||||
|
||||
type fastcgi struct {
|
||||
r resolver.Resolver
|
||||
}
|
||||
|
||||
// Config describes the per location fastcgi config
|
||||
type Config struct {
|
||||
Index string `json:"index"`
|
||||
Params map[string]string `json:"params"`
|
||||
}
|
||||
|
||||
// Equal tests for equality between two Configuration types
|
||||
func (l1 *Config) Equal(l2 *Config) bool {
|
||||
if l1 == l2 {
|
||||
return true
|
||||
}
|
||||
|
||||
if l1 == nil || l2 == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if l1.Index != l2.Index {
|
||||
return false
|
||||
}
|
||||
|
||||
return reflect.DeepEqual(l1.Params, l2.Params)
|
||||
}
|
||||
|
||||
// NewParser creates a new fastcgiConfig protocol annotation parser
|
||||
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
||||
return fastcgi{r}
|
||||
}
|
||||
|
||||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to indicate the fastcgiConfig.
|
||||
func (a fastcgi) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||
|
||||
fcgiConfig := Config{}
|
||||
|
||||
if ing.GetAnnotations() == nil {
|
||||
return fcgiConfig, nil
|
||||
}
|
||||
|
||||
index, err := parser.GetStringAnnotation("fastcgi-index", ing)
|
||||
if err != nil {
|
||||
index = ""
|
||||
}
|
||||
fcgiConfig.Index = index
|
||||
|
||||
cm, err := parser.GetStringAnnotation("fastcgi-params-configmap", ing)
|
||||
if err != nil {
|
||||
return fcgiConfig, nil
|
||||
}
|
||||
|
||||
cmns, cmn, err := cache.SplitMetaNamespaceKey(cm)
|
||||
if err != nil {
|
||||
return fcgiConfig, ing_errors.LocationDenied{
|
||||
Reason: errors.Wrap(err, "error reading configmap name from annotation"),
|
||||
}
|
||||
}
|
||||
|
||||
if cmns == "" {
|
||||
cmns = ing.Namespace
|
||||
}
|
||||
|
||||
cm = fmt.Sprintf("%v/%v", cmns, cmn)
|
||||
cmap, err := a.r.GetConfigMap(cm)
|
||||
if err != nil {
|
||||
return fcgiConfig, ing_errors.LocationDenied{
|
||||
Reason: errors.Wrapf(err, "unexpected error reading configmap %v", cm),
|
||||
}
|
||||
}
|
||||
|
||||
fcgiConfig.Params = cmap.Data
|
||||
|
||||
return fcgiConfig, nil
|
||||
}
|
||||
263
internal/ingress/annotations/fastcgi/main_test.go
Normal file
263
internal/ingress/annotations/fastcgi/main_test.go
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
/*
|
||||
Copyright 2018 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 fastcgi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
api "k8s.io/api/core/v1"
|
||||
networking "k8s.io/api/networking/v1beta1"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
"k8s.io/ingress-nginx/internal/ingress/errors"
|
||||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
func buildIngress() *networking.Ingress {
|
||||
return &networking.Ingress{
|
||||
ObjectMeta: meta_v1.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: api.NamespaceDefault,
|
||||
},
|
||||
Spec: networking.IngressSpec{
|
||||
Backend: &networking.IngressBackend{
|
||||
ServiceName: "fastcgi",
|
||||
ServicePort: intstr.FromInt(80),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type mockConfigMap struct {
|
||||
resolver.Mock
|
||||
}
|
||||
|
||||
func (m mockConfigMap) GetConfigMap(name string) (*api.ConfigMap, error) {
|
||||
if name != "default/demo-configmap" {
|
||||
return nil, errors.Errorf("there is no configmap with name %v", name)
|
||||
}
|
||||
|
||||
return &api.ConfigMap{
|
||||
ObjectMeta: meta_v1.ObjectMeta{
|
||||
Namespace: api.NamespaceDefault,
|
||||
Name: "demo-secret",
|
||||
},
|
||||
Data: map[string]string{"REDIRECT_STATUS": "200", "SERVER_NAME": "$server_name"},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestParseEmptyFastCGIAnnotations(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
i, err := NewParser(&mockConfigMap{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error parsing ingress without fastcgi")
|
||||
}
|
||||
|
||||
config, ok := i.(Config)
|
||||
if !ok {
|
||||
t.Errorf("Parse do not return a Config object")
|
||||
}
|
||||
|
||||
if config.Index != "" {
|
||||
t.Errorf("Index should be an empty string")
|
||||
}
|
||||
|
||||
if 0 != len(config.Params) {
|
||||
t.Errorf("Params should be an empty slice")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFastCGIIndexAnnotation(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
const expectedAnnotation = "index.php"
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix("fastcgi-index")] = expectedAnnotation
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, err := NewParser(&mockConfigMap{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error parsing ingress without fastcgi")
|
||||
}
|
||||
|
||||
config, ok := i.(Config)
|
||||
if !ok {
|
||||
t.Errorf("Parse do not return a Config object")
|
||||
}
|
||||
|
||||
if config.Index != "index.php" {
|
||||
t.Errorf("expected %s but %v returned", expectedAnnotation, config.Index)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseEmptyFastCGIParamsConfigMapAnnotation(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = ""
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, err := NewParser(&mockConfigMap{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error parsing ingress without fastcgi")
|
||||
}
|
||||
|
||||
config, ok := i.(Config)
|
||||
if !ok {
|
||||
t.Errorf("Parse do not return a Config object")
|
||||
}
|
||||
|
||||
if 0 != len(config.Params) {
|
||||
t.Errorf("Params should be an empty slice")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFastCGIInvalidParamsConfigMapAnnotation(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
invalidConfigMapList := []string{"unknown/configMap", "unknown/config/map"}
|
||||
for _, configmap := range invalidConfigMapList {
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = configmap
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, err := NewParser(&mockConfigMap{}).Parse(ing)
|
||||
if err == nil {
|
||||
t.Errorf("Reading an unexisting configmap should return an error")
|
||||
}
|
||||
|
||||
config, ok := i.(Config)
|
||||
if !ok {
|
||||
t.Errorf("Parse do not return a Config object")
|
||||
}
|
||||
|
||||
if 0 != len(config.Params) {
|
||||
t.Errorf("Params should be an empty slice")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFastCGIParamsConfigMapAnnotationWithoutNS(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = "demo-configmap"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, err := NewParser(&mockConfigMap{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error parsing ingress without fastcgi")
|
||||
}
|
||||
|
||||
config, ok := i.(Config)
|
||||
if !ok {
|
||||
t.Errorf("Parse do not return a Config object")
|
||||
}
|
||||
|
||||
if 2 != len(config.Params) {
|
||||
t.Errorf("Params should have a length of 2")
|
||||
}
|
||||
|
||||
if "200" != config.Params["REDIRECT_STATUS"] || "$server_name" != config.Params["SERVER_NAME"] {
|
||||
t.Errorf("Params value is not the one expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFastCGIParamsConfigMapAnnotationWithNS(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = "default/demo-configmap"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, err := NewParser(&mockConfigMap{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error parsing ingress without fastcgi")
|
||||
}
|
||||
|
||||
config, ok := i.(Config)
|
||||
if !ok {
|
||||
t.Errorf("Parse do not return a Config object")
|
||||
}
|
||||
|
||||
if 2 != len(config.Params) {
|
||||
t.Errorf("Params should have a length of 2")
|
||||
}
|
||||
|
||||
if "200" != config.Params["REDIRECT_STATUS"] || "$server_name" != config.Params["SERVER_NAME"] {
|
||||
t.Errorf("Params value is not the one expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigEquality(t *testing.T) {
|
||||
|
||||
var nilConfig *Config
|
||||
|
||||
config := Config{
|
||||
Index: "index.php",
|
||||
Params: map[string]string{"REDIRECT_STATUS": "200", "SERVER_NAME": "$server_name"},
|
||||
}
|
||||
|
||||
configCopy := Config{
|
||||
Index: "index.php",
|
||||
Params: map[string]string{"REDIRECT_STATUS": "200", "SERVER_NAME": "$server_name"},
|
||||
}
|
||||
|
||||
config2 := Config{
|
||||
Index: "index.php",
|
||||
Params: map[string]string{"REDIRECT_STATUS": "200"},
|
||||
}
|
||||
|
||||
config3 := Config{
|
||||
Index: "index.py",
|
||||
Params: map[string]string{"SERVER_NAME": "$server_name", "REDIRECT_STATUS": "200"},
|
||||
}
|
||||
|
||||
config4 := Config{
|
||||
Index: "index.php",
|
||||
Params: map[string]string{"SERVER_NAME": "$server_name", "REDIRECT_STATUS": "200"},
|
||||
}
|
||||
|
||||
if !config.Equal(&config) {
|
||||
t.Errorf("config should be equal to itself")
|
||||
}
|
||||
|
||||
if nilConfig.Equal(&config) {
|
||||
t.Errorf("Foo")
|
||||
}
|
||||
|
||||
if !config.Equal(&configCopy) {
|
||||
t.Errorf("config should be equal to configCopy")
|
||||
}
|
||||
|
||||
if config.Equal(&config2) {
|
||||
t.Errorf("config2 should not be equal to config")
|
||||
}
|
||||
|
||||
if config.Equal(&config3) {
|
||||
t.Errorf("config3 should not be equal to config")
|
||||
}
|
||||
|
||||
if !config.Equal(&config4) {
|
||||
t.Errorf("config4 should be equal to config")
|
||||
}
|
||||
}
|
||||
58
internal/ingress/annotations/mirror/main.go
Normal file
58
internal/ingress/annotations/mirror/main.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
Copyright 2019 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 mirror
|
||||
|
||||
import (
|
||||
networking "k8s.io/api/networking/v1beta1"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
)
|
||||
|
||||
// Config returns the mirror to use in a given location
|
||||
type Config struct {
|
||||
URI string `json:"uri"`
|
||||
RequestBody string `json:"requestBody"`
|
||||
}
|
||||
|
||||
type mirror struct {
|
||||
r resolver.Resolver
|
||||
}
|
||||
|
||||
// NewParser creates a new mirror configuration annotation parser
|
||||
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
||||
return mirror{r}
|
||||
}
|
||||
|
||||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to configure mirror
|
||||
func (a mirror) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||
config := &Config{}
|
||||
var err error
|
||||
|
||||
config.URI, err = parser.GetStringAnnotation("mirror-uri", ing)
|
||||
if err != nil {
|
||||
config.URI = ""
|
||||
}
|
||||
|
||||
config.RequestBody, err = parser.GetStringAnnotation("mirror-request-body", ing)
|
||||
if err != nil || config.RequestBody != "off" {
|
||||
config.RequestBody = "on"
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
86
internal/ingress/annotations/mirror/main_test.go
Normal file
86
internal/ingress/annotations/mirror/main_test.go
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
Copyright 2019 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 mirror
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
api "k8s.io/api/core/v1"
|
||||
networking "k8s.io/api/networking/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) {
|
||||
uri := parser.GetAnnotationWithPrefix("mirror-uri")
|
||||
requestBody := parser.GetAnnotationWithPrefix("mirror-request-body")
|
||||
|
||||
ap := NewParser(&resolver.Mock{})
|
||||
if ap == nil {
|
||||
t.Fatalf("expected a parser.IngressAnnotation but returned nil")
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
annotations map[string]string
|
||||
expected *Config
|
||||
}{
|
||||
{map[string]string{uri: "/mirror", requestBody: ""}, &Config{
|
||||
URI: "/mirror",
|
||||
RequestBody: "on",
|
||||
}},
|
||||
{map[string]string{uri: "/mirror", requestBody: "off"}, &Config{
|
||||
URI: "/mirror",
|
||||
RequestBody: "off",
|
||||
}},
|
||||
{map[string]string{uri: "", requestBody: "ahh"}, &Config{
|
||||
URI: "",
|
||||
RequestBody: "on",
|
||||
}},
|
||||
{map[string]string{uri: "", requestBody: ""}, &Config{
|
||||
URI: "",
|
||||
RequestBody: "on",
|
||||
}},
|
||||
{map[string]string{}, &Config{
|
||||
URI: "",
|
||||
RequestBody: "on",
|
||||
}},
|
||||
{nil, &Config{
|
||||
URI: "",
|
||||
RequestBody: "on",
|
||||
}},
|
||||
}
|
||||
|
||||
ing := &networking.Ingress{
|
||||
ObjectMeta: meta_v1.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: api.NamespaceDefault,
|
||||
},
|
||||
Spec: networking.IngressSpec{},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
ing.SetAnnotations(testCase.annotations)
|
||||
result, _ := ap.Parse(ing)
|
||||
fmt.Printf("%t", result)
|
||||
if !reflect.DeepEqual(result, testCase.expected) {
|
||||
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,22 +25,23 @@ import (
|
|||
|
||||
// Config returns the proxy timeout to use in the upstream server/s
|
||||
type Config struct {
|
||||
BodySize string `json:"bodySize"`
|
||||
ConnectTimeout int `json:"connectTimeout"`
|
||||
SendTimeout int `json:"sendTimeout"`
|
||||
ReadTimeout int `json:"readTimeout"`
|
||||
BuffersNumber int `json:"buffersNumber"`
|
||||
BufferSize string `json:"bufferSize"`
|
||||
CookieDomain string `json:"cookieDomain"`
|
||||
CookiePath string `json:"cookiePath"`
|
||||
NextUpstream string `json:"nextUpstream"`
|
||||
NextUpstreamTimeout int `json:"nextUpstreamTimeout"`
|
||||
NextUpstreamTries int `json:"nextUpstreamTries"`
|
||||
ProxyRedirectFrom string `json:"proxyRedirectFrom"`
|
||||
ProxyRedirectTo string `json:"proxyRedirectTo"`
|
||||
RequestBuffering string `json:"requestBuffering"`
|
||||
ProxyBuffering string `json:"proxyBuffering"`
|
||||
ProxyHTTPVersion string `json:"proxyHTTPVersion"`
|
||||
BodySize string `json:"bodySize"`
|
||||
ConnectTimeout int `json:"connectTimeout"`
|
||||
SendTimeout int `json:"sendTimeout"`
|
||||
ReadTimeout int `json:"readTimeout"`
|
||||
BuffersNumber int `json:"buffersNumber"`
|
||||
BufferSize string `json:"bufferSize"`
|
||||
CookieDomain string `json:"cookieDomain"`
|
||||
CookiePath string `json:"cookiePath"`
|
||||
NextUpstream string `json:"nextUpstream"`
|
||||
NextUpstreamTimeout int `json:"nextUpstreamTimeout"`
|
||||
NextUpstreamTries int `json:"nextUpstreamTries"`
|
||||
ProxyRedirectFrom string `json:"proxyRedirectFrom"`
|
||||
ProxyRedirectTo string `json:"proxyRedirectTo"`
|
||||
RequestBuffering string `json:"requestBuffering"`
|
||||
ProxyBuffering string `json:"proxyBuffering"`
|
||||
ProxyHTTPVersion string `json:"proxyHTTPVersion"`
|
||||
ProxyMaxTempFileSize string `json:"proxyMaxTempFileSize"`
|
||||
}
|
||||
|
||||
// Equal tests for equality between two Configuration types
|
||||
|
|
@ -100,6 +101,10 @@ func (l1 *Config) Equal(l2 *Config) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
if l1.ProxyMaxTempFileSize != l2.ProxyMaxTempFileSize {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -200,5 +205,10 @@ func (a proxy) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
config.ProxyHTTPVersion = defBackend.ProxyHTTPVersion
|
||||
}
|
||||
|
||||
config.ProxyMaxTempFileSize, err = parser.GetStringAnnotation("proxy-max-temp-file-size", ing)
|
||||
if err != nil {
|
||||
config.ProxyMaxTempFileSize = defBackend.ProxyMaxTempFileSize
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
|||
ProxyRequestBuffering: "on",
|
||||
ProxyBuffering: "off",
|
||||
ProxyHTTPVersion: "1.1",
|
||||
ProxyMaxTempFileSize: "1024m",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -101,6 +102,7 @@ func TestProxy(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix("proxy-request-buffering")] = "off"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-buffering")] = "on"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-http-version")] = "1.0"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-max-temp-file-size")] = "128k"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, err := NewParser(mockBackend{}).Parse(ing)
|
||||
|
|
@ -147,6 +149,9 @@ func TestProxy(t *testing.T) {
|
|||
if p.ProxyHTTPVersion != "1.0" {
|
||||
t.Errorf("expected 1.0 as proxy-http-version but returned %v", p.ProxyHTTPVersion)
|
||||
}
|
||||
if p.ProxyMaxTempFileSize != "128k" {
|
||||
t.Errorf("expected 128k as proxy-max-temp-file-size but returned %v", p.ProxyMaxTempFileSize)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyWithNoAnnotation(t *testing.T) {
|
||||
|
|
@ -196,4 +201,7 @@ func TestProxyWithNoAnnotation(t *testing.T) {
|
|||
if p.ProxyHTTPVersion != "1.1" {
|
||||
t.Errorf("expected 1.1 as proxy-http-version but returned %v", p.ProxyHTTPVersion)
|
||||
}
|
||||
if p.ProxyMaxTempFileSize != "1024m" {
|
||||
t.Errorf("expected 1024m as proxy-max-temp-file-size but returned %v", p.ProxyMaxTempFileSize)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ func TestIngressAffinityCookieConfig(t *testing.T) {
|
|||
}
|
||||
|
||||
if nginxAffinity.Cookie.Name != "INGRESSCOOKIE" {
|
||||
t.Errorf("expected route as session-cookie-name but returned %v", nginxAffinity.Cookie.Name)
|
||||
t.Errorf("expected INGRESSCOOKIE as session-cookie-name but returned %v", nginxAffinity.Cookie.Name)
|
||||
}
|
||||
|
||||
if nginxAffinity.Cookie.Expires != "4500" {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package controller
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -63,7 +64,7 @@ func (n *NGINXController) Check(_ *http.Request) error {
|
|||
if err != nil {
|
||||
return errors.Wrap(err, "unexpected error reading /proc directory")
|
||||
}
|
||||
f, err := n.fileSystem.ReadFile(nginx.PID)
|
||||
f, err := ioutil.ReadFile(nginx.PID)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unexpected error reading %v", nginx.PID)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"k8s.io/apiserver/pkg/server/healthz"
|
||||
"k8s.io/kubernetes/pkg/util/filesystem"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/file"
|
||||
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
|
||||
|
|
@ -55,14 +54,10 @@ func TestNginxCheck(t *testing.T) {
|
|||
defer server.Close()
|
||||
server.Start()
|
||||
|
||||
// mock filesystem
|
||||
fs := filesystem.DefaultFs{}
|
||||
|
||||
n := &NGINXController{
|
||||
cfg: &Configuration{
|
||||
ListenPorts: &ngx_config.ListenPorts{},
|
||||
},
|
||||
fileSystem: fs,
|
||||
}
|
||||
|
||||
t.Run("no pid or process", func(t *testing.T) {
|
||||
|
|
@ -72,8 +67,8 @@ func TestNginxCheck(t *testing.T) {
|
|||
})
|
||||
|
||||
// create pid file
|
||||
fs.MkdirAll("/tmp", file.ReadWriteByUser)
|
||||
pidFile, err := fs.Create(nginx.PID)
|
||||
os.MkdirAll("/tmp", file.ReadWriteByUser)
|
||||
pidFile, err := os.Create(nginx.PID)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,8 +33,6 @@ import (
|
|||
var (
|
||||
// EnableSSLChainCompletion Autocomplete SSL certificate chains with missing intermediate CA certificates.
|
||||
EnableSSLChainCompletion = false
|
||||
// EnableDynamicCertificates Dynamically update SSL certificates instead of reloading NGINX
|
||||
EnableDynamicCertificates = true
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -75,6 +73,10 @@ const (
|
|||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols
|
||||
sslProtocols = "TLSv1.2"
|
||||
|
||||
// Disable TLS 1.3 early data
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_early_data
|
||||
sslEarlyData = false
|
||||
|
||||
// Time during which a client may reuse the session parameters stored in a cache.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_timeout
|
||||
sslSessionTimeout = "10m"
|
||||
|
|
@ -124,11 +126,6 @@ type Configuration struct {
|
|||
// By default error logs go to /var/log/nginx/error.log
|
||||
ErrorLogPath string `json:"error-log-path,omitempty"`
|
||||
|
||||
// EnableDynamicTLSRecords enables dynamic TLS record sizes
|
||||
// https://blog.cloudflare.com/optimizing-tls-over-tcp-to-reduce-latency
|
||||
// By default this is enabled
|
||||
EnableDynamicTLSRecords bool `json:"enable-dynamic-tls-records"`
|
||||
|
||||
// EnableModsecurity enables the modsecurity module for NGINX
|
||||
// By default this is disabled
|
||||
EnableModsecurity bool `json:"enable-modsecurity"`
|
||||
|
|
@ -317,6 +314,10 @@ type Configuration struct {
|
|||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols
|
||||
SSLProtocols string `json:"ssl-protocols,omitempty"`
|
||||
|
||||
// Enables or disable TLS 1.3 early data.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_early_data
|
||||
SSLEarlyData bool `json:"ssl-early-data,omitempty"`
|
||||
|
||||
// Enables or disables the use of shared SSL cache among worker processes.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache
|
||||
SSLSessionCache bool `json:"ssl-session-cache,omitempty"`
|
||||
|
|
@ -607,6 +608,13 @@ type Configuration struct {
|
|||
|
||||
// Block all requests with given Referer headers
|
||||
BlockReferers []string `json:"block-referers"`
|
||||
|
||||
// Lua shared dict configuration data / certificate data
|
||||
LuaSharedDicts map[string]int `json:"lua-shared-dicts"`
|
||||
|
||||
// DefaultSSLCertificate holds the default SSL certificate to use in the configuration
|
||||
// It can be the fake certificate or the one behind the flag --default-ssl-certificate
|
||||
DefaultSSLCertificate *ingress.SSLCert `json:"-"`
|
||||
}
|
||||
|
||||
// NewDefault returns the default nginx configuration
|
||||
|
|
@ -640,7 +648,6 @@ func NewDefault() Configuration {
|
|||
ClientHeaderTimeout: 60,
|
||||
ClientBodyBufferSize: "8k",
|
||||
ClientBodyTimeout: 60,
|
||||
EnableDynamicTLSRecords: true,
|
||||
EnableUnderscoresInHeaders: false,
|
||||
ErrorLogLevel: errorLevel,
|
||||
UseForwardedHeaders: false,
|
||||
|
|
@ -683,6 +690,7 @@ func NewDefault() Configuration {
|
|||
SSLCiphers: sslCiphers,
|
||||
SSLECDHCurve: "auto",
|
||||
SSLProtocols: sslProtocols,
|
||||
SSLEarlyData: sslEarlyData,
|
||||
SSLSessionCache: true,
|
||||
SSLSessionCacheSize: sslSessionCacheSize,
|
||||
SSLSessionTickets: true,
|
||||
|
|
@ -720,6 +728,7 @@ func NewDefault() Configuration {
|
|||
LimitRateAfter: 0,
|
||||
ProxyBuffering: "off",
|
||||
ProxyHTTPVersion: "1.1",
|
||||
ProxyMaxTempFileSize: "1024m",
|
||||
},
|
||||
UpstreamKeepaliveConnections: 32,
|
||||
UpstreamKeepaliveTimeout: 60,
|
||||
|
|
@ -767,25 +776,24 @@ func (cfg Configuration) BuildLogFormatUpstream() string {
|
|||
|
||||
// TemplateConfig contains the nginx configuration to render the file nginx.conf
|
||||
type TemplateConfig struct {
|
||||
ProxySetHeaders map[string]string
|
||||
AddHeaders map[string]string
|
||||
BacklogSize int
|
||||
Backends []*ingress.Backend
|
||||
PassthroughBackends []*ingress.SSLPassthroughBackend
|
||||
Servers []*ingress.Server
|
||||
TCPBackends []ingress.L4Service
|
||||
UDPBackends []ingress.L4Service
|
||||
HealthzURI string
|
||||
Cfg Configuration
|
||||
IsIPV6Enabled bool
|
||||
IsSSLPassthroughEnabled bool
|
||||
NginxStatusIpv4Whitelist []string
|
||||
NginxStatusIpv6Whitelist []string
|
||||
RedirectServers interface{}
|
||||
ListenPorts *ListenPorts
|
||||
PublishService *apiv1.Service
|
||||
EnableDynamicCertificates bool
|
||||
EnableMetrics bool
|
||||
ProxySetHeaders map[string]string
|
||||
AddHeaders map[string]string
|
||||
BacklogSize int
|
||||
Backends []*ingress.Backend
|
||||
PassthroughBackends []*ingress.SSLPassthroughBackend
|
||||
Servers []*ingress.Server
|
||||
TCPBackends []ingress.L4Service
|
||||
UDPBackends []ingress.L4Service
|
||||
HealthzURI string
|
||||
Cfg Configuration
|
||||
IsIPV6Enabled bool
|
||||
IsSSLPassthroughEnabled bool
|
||||
NginxStatusIpv4Whitelist []string
|
||||
NginxStatusIpv6Whitelist []string
|
||||
RedirectServers interface{}
|
||||
ListenPorts *ListenPorts
|
||||
PublishService *apiv1.Service
|
||||
EnableMetrics bool
|
||||
|
||||
PID string
|
||||
StatusSocket string
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ func (n *NGINXController) syncIngress(interface{}) error {
|
|||
}
|
||||
|
||||
err := wait.ExponentialBackoff(retry, func() (bool, error) {
|
||||
err := configureDynamically(pcfg)
|
||||
err := n.configureDynamically(pcfg)
|
||||
if err == nil {
|
||||
klog.V(2).Infof("Dynamic reconfiguration succeeded.")
|
||||
return true, nil
|
||||
|
|
@ -846,9 +846,9 @@ func (n *NGINXController) getServiceClusterEndpoint(svcKey string, backend *netw
|
|||
|
||||
// serviceEndpoints returns the upstream servers (Endpoints) associated with a Service.
|
||||
func (n *NGINXController) serviceEndpoints(svcKey, backendPort string) ([]ingress.Endpoint, error) {
|
||||
svc, err := n.store.GetService(svcKey)
|
||||
|
||||
var upstreams []ingress.Endpoint
|
||||
|
||||
svc, err := n.store.GetService(svcKey)
|
||||
if err != nil {
|
||||
return upstreams, err
|
||||
}
|
||||
|
|
@ -859,14 +859,26 @@ func (n *NGINXController) serviceEndpoints(svcKey, backendPort string) ([]ingres
|
|||
if svc.Spec.Type == apiv1.ServiceTypeExternalName {
|
||||
externalPort, err := strconv.Atoi(backendPort)
|
||||
if err != nil {
|
||||
klog.Warningf("Only numeric ports are allowed in ExternalName Services: %q is not a valid port number.", backendPort)
|
||||
return upstreams, nil
|
||||
// check if the service ports have one with backendPort as name
|
||||
found := false
|
||||
for _, port := range svc.Spec.Ports {
|
||||
if port.Name == backendPort {
|
||||
externalPort = int(port.Port)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
klog.Errorf("service %v/%v does not have a port with name %v", svc.Namespace, svc.Namespace, backendPort)
|
||||
return upstreams, nil
|
||||
}
|
||||
}
|
||||
|
||||
servicePort := apiv1.ServicePort{
|
||||
Protocol: "TCP",
|
||||
Port: int32(externalPort),
|
||||
TargetPort: intstr.FromString(backendPort),
|
||||
TargetPort: intstr.FromInt(externalPort),
|
||||
}
|
||||
endps := getEndpoints(svc, &servicePort, apiv1.ProtocolTCP, n.store.GetServiceEndpoints)
|
||||
if len(endps) == 0 {
|
||||
|
|
@ -897,19 +909,18 @@ func (n *NGINXController) serviceEndpoints(svcKey, backendPort string) ([]ingres
|
|||
return upstreams, nil
|
||||
}
|
||||
|
||||
// overridePemFileNameAndPemSHA should only be called when EnableDynamicCertificates
|
||||
// ideally this function should not exist, the only reason why we use it is that
|
||||
// we rely on PemFileName in nginx.tmpl to configure SSL directives
|
||||
// and PemSHA to force reload
|
||||
func (n *NGINXController) overridePemFileNameAndPemSHA(cert *ingress.SSLCert) {
|
||||
// TODO(elvinefendi): It is not great but we currently use PemFileName to decide whether SSL needs to be configured
|
||||
// in nginx configuration or not. The whole thing needs to be refactored, we should rely on a proper
|
||||
// signal to configure SSL, not PemFileName.
|
||||
cert.PemFileName = n.cfg.FakeCertificate.PemFileName
|
||||
func (n *NGINXController) getDefaultSSLCertificate() *ingress.SSLCert {
|
||||
// read custom default SSL certificate, fall back to generated default certificate
|
||||
if n.cfg.DefaultSSLCertificate != "" {
|
||||
certificate, err := n.store.GetLocalSSLCert(n.cfg.DefaultSSLCertificate)
|
||||
if err == nil {
|
||||
return certificate
|
||||
}
|
||||
|
||||
// TODO(elvinefendi): This is again another hacky way of avoiding Nginx reload when certificate
|
||||
// changes in dynamic SSL mode since FakeCertificate never changes.
|
||||
cert.PemSHA = n.cfg.FakeCertificate.PemSHA
|
||||
klog.Warningf("Error loading custom default certificate, falling back to generated default:\n%v", err)
|
||||
}
|
||||
|
||||
return n.cfg.FakeCertificate
|
||||
}
|
||||
|
||||
// createServers builds a map of host name to Server structs from a map of
|
||||
|
|
@ -924,42 +935,28 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
|
|||
|
||||
bdef := n.store.GetDefaultBackend()
|
||||
ngxProxy := proxy.Config{
|
||||
BodySize: bdef.ProxyBodySize,
|
||||
ConnectTimeout: bdef.ProxyConnectTimeout,
|
||||
SendTimeout: bdef.ProxySendTimeout,
|
||||
ReadTimeout: bdef.ProxyReadTimeout,
|
||||
BuffersNumber: bdef.ProxyBuffersNumber,
|
||||
BufferSize: bdef.ProxyBufferSize,
|
||||
CookieDomain: bdef.ProxyCookieDomain,
|
||||
CookiePath: bdef.ProxyCookiePath,
|
||||
NextUpstream: bdef.ProxyNextUpstream,
|
||||
NextUpstreamTimeout: bdef.ProxyNextUpstreamTimeout,
|
||||
NextUpstreamTries: bdef.ProxyNextUpstreamTries,
|
||||
RequestBuffering: bdef.ProxyRequestBuffering,
|
||||
ProxyRedirectFrom: bdef.ProxyRedirectFrom,
|
||||
ProxyBuffering: bdef.ProxyBuffering,
|
||||
ProxyHTTPVersion: bdef.ProxyHTTPVersion,
|
||||
}
|
||||
|
||||
defaultCertificate := n.cfg.FakeCertificate
|
||||
|
||||
// read custom default SSL certificate, fall back to generated default certificate
|
||||
if n.cfg.DefaultSSLCertificate != "" {
|
||||
certificate, err := n.store.GetLocalSSLCert(n.cfg.DefaultSSLCertificate)
|
||||
if err == nil {
|
||||
defaultCertificate = certificate
|
||||
if ngx_config.EnableDynamicCertificates {
|
||||
n.overridePemFileNameAndPemSHA(defaultCertificate)
|
||||
}
|
||||
} else {
|
||||
klog.Warningf("Error loading custom default certificate, falling back to generated default:\n%v", err)
|
||||
}
|
||||
BodySize: bdef.ProxyBodySize,
|
||||
ConnectTimeout: bdef.ProxyConnectTimeout,
|
||||
SendTimeout: bdef.ProxySendTimeout,
|
||||
ReadTimeout: bdef.ProxyReadTimeout,
|
||||
BuffersNumber: bdef.ProxyBuffersNumber,
|
||||
BufferSize: bdef.ProxyBufferSize,
|
||||
CookieDomain: bdef.ProxyCookieDomain,
|
||||
CookiePath: bdef.ProxyCookiePath,
|
||||
NextUpstream: bdef.ProxyNextUpstream,
|
||||
NextUpstreamTimeout: bdef.ProxyNextUpstreamTimeout,
|
||||
NextUpstreamTries: bdef.ProxyNextUpstreamTries,
|
||||
RequestBuffering: bdef.ProxyRequestBuffering,
|
||||
ProxyRedirectFrom: bdef.ProxyRedirectFrom,
|
||||
ProxyBuffering: bdef.ProxyBuffering,
|
||||
ProxyHTTPVersion: bdef.ProxyHTTPVersion,
|
||||
ProxyMaxTempFileSize: bdef.ProxyMaxTempFileSize,
|
||||
}
|
||||
|
||||
// initialize default server and root location
|
||||
servers[defServerName] = &ingress.Server{
|
||||
Hostname: defServerName,
|
||||
SSLCert: *defaultCertificate,
|
||||
SSLCert: n.getDefaultSSLCertificate(),
|
||||
Locations: []*ingress.Location{
|
||||
{
|
||||
Path: rootLocation,
|
||||
|
|
@ -1023,6 +1020,7 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
|
|||
if host == "" {
|
||||
host = defServerName
|
||||
}
|
||||
|
||||
if _, ok := servers[host]; ok {
|
||||
// server already configured
|
||||
continue
|
||||
|
|
@ -1090,7 +1088,7 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
|
|||
}
|
||||
|
||||
// only add a certificate if the server does not have one previously configured
|
||||
if servers[host].SSLCert.PemFileName != "" {
|
||||
if servers[host].SSLCert != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -1100,10 +1098,8 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
|
|||
}
|
||||
|
||||
tlsSecretName := extractTLSSecretName(host, ing, n.store.GetLocalSSLCert)
|
||||
|
||||
if tlsSecretName == "" {
|
||||
klog.V(3).Infof("Host %q is listed in the TLS section but secretName is empty. Using default certificate.", host)
|
||||
servers[host].SSLCert = *defaultCertificate
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -1111,7 +1107,6 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
|
|||
cert, err := n.store.GetLocalSSLCert(secrKey)
|
||||
if err != nil {
|
||||
klog.Warningf("Error getting SSL certificate %q: %v. Using default certificate", secrKey, err)
|
||||
servers[host].SSLCert = *defaultCertificate
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -1126,16 +1121,11 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
|
|||
klog.Warningf("SSL certificate %q does not contain a Common Name or Subject Alternative Name for server %q: %v",
|
||||
secrKey, host, err)
|
||||
klog.Warningf("Using default certificate")
|
||||
servers[host].SSLCert = *defaultCertificate
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if ngx_config.EnableDynamicCertificates {
|
||||
n.overridePemFileNameAndPemSHA(cert)
|
||||
}
|
||||
|
||||
servers[host].SSLCert = *cert
|
||||
servers[host].SSLCert = cert
|
||||
|
||||
if cert.ExpireTime.Before(time.Now().Add(240 * time.Hour)) {
|
||||
klog.Warningf("SSL certificate for server %q is about to expire (%v)", host, cert.ExpireTime)
|
||||
|
|
@ -1176,9 +1166,11 @@ func locationApplyAnnotations(loc *ingress.Location, anns *annotations.Ingress)
|
|||
loc.InfluxDB = anns.InfluxDB
|
||||
loc.DefaultBackend = anns.DefaultBackend
|
||||
loc.BackendProtocol = anns.BackendProtocol
|
||||
loc.FastCGI = anns.FastCGI
|
||||
loc.CustomHTTPErrors = anns.CustomHTTPErrors
|
||||
loc.ModSecurity = anns.ModSecurity
|
||||
loc.Satisfy = anns.Satisfy
|
||||
loc.Mirror = anns.Mirror
|
||||
}
|
||||
|
||||
// OK to merge canary ingresses iff there exists one or more ingresses to potentially merge into
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ import (
|
|||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/file"
|
||||
"k8s.io/ingress-nginx/internal/ingress"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/canary"
|
||||
|
|
@ -1109,11 +1108,6 @@ func newNGINXController(t *testing.T) *NGINXController {
|
|||
t.Fatalf("error creating the configuration map: %v", err)
|
||||
}
|
||||
|
||||
fs, err := file.NewFakeFS()
|
||||
if err != nil {
|
||||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
storer := store.New(
|
||||
ns,
|
||||
fmt.Sprintf("%v/config", ns),
|
||||
|
|
@ -1122,12 +1116,11 @@ func newNGINXController(t *testing.T) *NGINXController {
|
|||
"",
|
||||
10*time.Minute,
|
||||
clientSet,
|
||||
fs,
|
||||
channels.NewRingChannel(10),
|
||||
pod,
|
||||
false)
|
||||
|
||||
sslCert := ssl.GetFakeSSLCert(fs)
|
||||
sslCert := ssl.GetFakeSSLCert()
|
||||
config := &Configuration{
|
||||
FakeCertificate: sslCert,
|
||||
ListenPorts: &ngx_config.ListenPorts{
|
||||
|
|
@ -1136,10 +1129,9 @@ func newNGINXController(t *testing.T) *NGINXController {
|
|||
}
|
||||
|
||||
return &NGINXController{
|
||||
store: storer,
|
||||
cfg: config,
|
||||
command: NewNginxCommand(),
|
||||
fileSystem: fs,
|
||||
store: storer,
|
||||
cfg: config,
|
||||
command: NewNginxCommand(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
|
@ -44,7 +45,6 @@ import (
|
|||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/flowcontrol"
|
||||
"k8s.io/klog"
|
||||
"k8s.io/kubernetes/pkg/util/filesystem"
|
||||
|
||||
adm_controler "k8s.io/ingress-nginx/internal/admission/controller"
|
||||
"k8s.io/ingress-nginx/internal/file"
|
||||
|
|
@ -69,12 +69,8 @@ const (
|
|||
tempNginxPattern = "nginx-cfg"
|
||||
)
|
||||
|
||||
var (
|
||||
tmplPath = "/etc/nginx/template/nginx.tmpl"
|
||||
)
|
||||
|
||||
// NewNGINXController creates a new NGINX Ingress controller.
|
||||
func NewNGINXController(config *Configuration, mc metric.Collector, fs file.Filesystem) *NGINXController {
|
||||
func NewNGINXController(config *Configuration, mc metric.Collector) *NGINXController {
|
||||
eventBroadcaster := record.NewBroadcaster()
|
||||
eventBroadcaster.StartLogging(klog.Infof)
|
||||
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{
|
||||
|
|
@ -102,8 +98,6 @@ func NewNGINXController(config *Configuration, mc metric.Collector, fs file.File
|
|||
|
||||
stopLock: &sync.Mutex{},
|
||||
|
||||
fileSystem: fs,
|
||||
|
||||
runningConfig: new(ingress.Configuration),
|
||||
|
||||
Proxy: &TCPProxy{},
|
||||
|
|
@ -135,7 +129,6 @@ func NewNGINXController(config *Configuration, mc metric.Collector, fs file.File
|
|||
config.DefaultSSLCertificate,
|
||||
config.ResyncPeriod,
|
||||
config.Client,
|
||||
fs,
|
||||
n.updateCh,
|
||||
pod,
|
||||
config.DisableCatchAll)
|
||||
|
|
@ -156,7 +149,7 @@ func NewNGINXController(config *Configuration, mc metric.Collector, fs file.File
|
|||
}
|
||||
|
||||
onTemplateChange := func() {
|
||||
template, err := ngx_template.NewTemplate(tmplPath, fs)
|
||||
template, err := ngx_template.NewTemplate(nginx.TemplatePath)
|
||||
if err != nil {
|
||||
// this error is different from the rest because it must be clear why nginx is not working
|
||||
klog.Errorf(`
|
||||
|
|
@ -172,21 +165,16 @@ Error loading new template: %v
|
|||
n.syncQueue.EnqueueTask(task.GetDummyObject("template-change"))
|
||||
}
|
||||
|
||||
ngxTpl, err := ngx_template.NewTemplate(tmplPath, fs)
|
||||
ngxTpl, err := ngx_template.NewTemplate(nginx.TemplatePath)
|
||||
if err != nil {
|
||||
klog.Fatalf("Invalid NGINX configuration template: %v", err)
|
||||
}
|
||||
|
||||
n.t = ngxTpl
|
||||
|
||||
if _, ok := fs.(filesystem.DefaultFs); !ok {
|
||||
// do not setup watchers on tests
|
||||
return n
|
||||
}
|
||||
|
||||
_, err = watch.NewFileWatcher(tmplPath, onTemplateChange)
|
||||
_, err = watch.NewFileWatcher(nginx.TemplatePath, onTemplateChange)
|
||||
if err != nil {
|
||||
klog.Fatalf("Error creating file watcher for %v: %v", tmplPath, err)
|
||||
klog.Fatalf("Error creating file watcher for %v: %v", nginx.TemplatePath, err)
|
||||
}
|
||||
|
||||
filesToWatch := []string{}
|
||||
|
|
@ -260,8 +248,6 @@ type NGINXController struct {
|
|||
|
||||
store store.Storer
|
||||
|
||||
fileSystem filesystem.Filesystem
|
||||
|
||||
metricCollector metric.Collector
|
||||
|
||||
validationWebhookServer *http.Server
|
||||
|
|
@ -582,7 +568,7 @@ func (n NGINXController) generateTemplate(cfg ngx_config.Configuration, ingressC
|
|||
nsSecName := strings.Replace(secretName, "/", "-", -1)
|
||||
dh, ok := secret.Data["dhparam.pem"]
|
||||
if ok {
|
||||
pemFileName, err := ssl.AddOrUpdateDHParam(nsSecName, dh, n.fileSystem)
|
||||
pemFileName, err := ssl.AddOrUpdateDHParam(nsSecName, dh)
|
||||
if err != nil {
|
||||
klog.Warningf("Error adding or updating dhparam file %v: %v", nsSecName, err)
|
||||
} else {
|
||||
|
|
@ -594,25 +580,26 @@ func (n NGINXController) generateTemplate(cfg ngx_config.Configuration, ingressC
|
|||
|
||||
cfg.SSLDHParam = sslDHParam
|
||||
|
||||
cfg.DefaultSSLCertificate = n.getDefaultSSLCertificate()
|
||||
|
||||
tc := ngx_config.TemplateConfig{
|
||||
ProxySetHeaders: setHeaders,
|
||||
AddHeaders: addHeaders,
|
||||
BacklogSize: sysctlSomaxconn(),
|
||||
Backends: ingressCfg.Backends,
|
||||
PassthroughBackends: ingressCfg.PassthroughBackends,
|
||||
Servers: ingressCfg.Servers,
|
||||
TCPBackends: ingressCfg.TCPEndpoints,
|
||||
UDPBackends: ingressCfg.UDPEndpoints,
|
||||
Cfg: cfg,
|
||||
IsIPV6Enabled: n.isIPV6Enabled && !cfg.DisableIpv6,
|
||||
NginxStatusIpv4Whitelist: cfg.NginxStatusIpv4Whitelist,
|
||||
NginxStatusIpv6Whitelist: cfg.NginxStatusIpv6Whitelist,
|
||||
RedirectServers: buildRedirects(ingressCfg.Servers),
|
||||
IsSSLPassthroughEnabled: n.cfg.EnableSSLPassthrough,
|
||||
ListenPorts: n.cfg.ListenPorts,
|
||||
PublishService: n.GetPublishService(),
|
||||
EnableDynamicCertificates: ngx_config.EnableDynamicCertificates,
|
||||
EnableMetrics: n.cfg.EnableMetrics,
|
||||
ProxySetHeaders: setHeaders,
|
||||
AddHeaders: addHeaders,
|
||||
BacklogSize: sysctlSomaxconn(),
|
||||
Backends: ingressCfg.Backends,
|
||||
PassthroughBackends: ingressCfg.PassthroughBackends,
|
||||
Servers: ingressCfg.Servers,
|
||||
TCPBackends: ingressCfg.TCPEndpoints,
|
||||
UDPBackends: ingressCfg.UDPEndpoints,
|
||||
Cfg: cfg,
|
||||
IsIPV6Enabled: n.isIPV6Enabled && !cfg.DisableIpv6,
|
||||
NginxStatusIpv4Whitelist: cfg.NginxStatusIpv4Whitelist,
|
||||
NginxStatusIpv6Whitelist: cfg.NginxStatusIpv6Whitelist,
|
||||
RedirectServers: buildRedirects(ingressCfg.Servers),
|
||||
IsSSLPassthroughEnabled: n.cfg.EnableSSLPassthrough,
|
||||
ListenPorts: n.cfg.ListenPorts,
|
||||
PublishService: n.GetPublishService(),
|
||||
EnableMetrics: n.cfg.EnableMetrics,
|
||||
|
||||
HealthzURI: nginx.HealthPath,
|
||||
PID: nginx.PID,
|
||||
|
|
@ -698,7 +685,12 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
|
|||
|
||||
diffOutput, err := exec.Command("diff", "-u", cfgPath, tmpfile.Name()).CombinedOutput()
|
||||
if err != nil {
|
||||
klog.Warningf("Failed to executing diff command: %v", err)
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
ws := exitError.Sys().(syscall.WaitStatus)
|
||||
if ws.ExitStatus() == 2 {
|
||||
klog.Warningf("Failed to executing diff command: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
klog.Infof("NGINX configuration diff:\n%v", string(diffOutput))
|
||||
|
|
@ -800,7 +792,9 @@ func clearCertificates(config *ingress.Configuration) {
|
|||
var clearedServers []*ingress.Server
|
||||
for _, server := range config.Servers {
|
||||
copyOfServer := *server
|
||||
copyOfServer.SSLCert = ingress.SSLCert{PemFileName: copyOfServer.SSLCert.PemFileName}
|
||||
if copyOfServer.SSLCert != nil {
|
||||
copyOfServer.SSLCert = &ingress.SSLCert{PemFileName: copyOfServer.SSLCert.PemFileName}
|
||||
}
|
||||
clearedServers = append(clearedServers, ©OfServer)
|
||||
}
|
||||
config.Servers = clearedServers
|
||||
|
|
@ -848,20 +842,112 @@ func (n *NGINXController) IsDynamicConfigurationEnough(pcfg *ingress.Configurati
|
|||
copyOfRunningConfig.ControllerPodsCount = 0
|
||||
copyOfPcfg.ControllerPodsCount = 0
|
||||
|
||||
if ngx_config.EnableDynamicCertificates {
|
||||
clearCertificates(©OfRunningConfig)
|
||||
clearCertificates(©OfPcfg)
|
||||
}
|
||||
clearCertificates(©OfRunningConfig)
|
||||
clearCertificates(©OfPcfg)
|
||||
|
||||
return copyOfRunningConfig.Equal(©OfPcfg)
|
||||
}
|
||||
|
||||
// configureDynamically encodes new Backends in JSON format and POSTs the
|
||||
// payload to an internal HTTP endpoint handled by Lua.
|
||||
func configureDynamically(pcfg *ingress.Configuration) error {
|
||||
backends := make([]*ingress.Backend, len(pcfg.Backends))
|
||||
func (n *NGINXController) configureDynamically(pcfg *ingress.Configuration) error {
|
||||
backendsChanged := !reflect.DeepEqual(n.runningConfig.Backends, pcfg.Backends)
|
||||
if backendsChanged {
|
||||
err := configureBackends(pcfg.Backends)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for i, backend := range pcfg.Backends {
|
||||
streamConfigurationChanged := !reflect.DeepEqual(n.runningConfig.TCPEndpoints, pcfg.TCPEndpoints) || !reflect.DeepEqual(n.runningConfig.UDPEndpoints, pcfg.UDPEndpoints)
|
||||
if streamConfigurationChanged {
|
||||
err := updateStreamConfiguration(pcfg.TCPEndpoints, pcfg.UDPEndpoints)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if n.runningConfig.ControllerPodsCount != pcfg.ControllerPodsCount {
|
||||
statusCode, _, err := nginx.NewPostStatusRequest("/configuration/general", "application/json", ingress.GeneralConfig{
|
||||
ControllerPodsCount: pcfg.ControllerPodsCount,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if statusCode != http.StatusCreated {
|
||||
return fmt.Errorf("unexpected error code: %d", statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
serversChanged := !reflect.DeepEqual(n.runningConfig.Servers, pcfg.Servers)
|
||||
if serversChanged {
|
||||
err := configureCertificates(pcfg.Servers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateStreamConfiguration(TCPEndpoints []ingress.L4Service, UDPEndpoints []ingress.L4Service) error {
|
||||
streams := make([]ingress.Backend, 0)
|
||||
for _, ep := range TCPEndpoints {
|
||||
var service *apiv1.Service
|
||||
if ep.Service != nil {
|
||||
service = &apiv1.Service{Spec: ep.Service.Spec}
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("tcp-%v-%v-%v", ep.Backend.Namespace, ep.Backend.Name, ep.Backend.Port.String())
|
||||
streams = append(streams, ingress.Backend{
|
||||
Name: key,
|
||||
Endpoints: ep.Endpoints,
|
||||
Port: intstr.FromInt(ep.Port),
|
||||
Service: service,
|
||||
})
|
||||
}
|
||||
for _, ep := range UDPEndpoints {
|
||||
var service *apiv1.Service
|
||||
if ep.Service != nil {
|
||||
service = &apiv1.Service{Spec: ep.Service.Spec}
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("udp-%v-%v-%v", ep.Backend.Namespace, ep.Backend.Name, ep.Backend.Port.String())
|
||||
streams = append(streams, ingress.Backend{
|
||||
Name: key,
|
||||
Endpoints: ep.Endpoints,
|
||||
Port: intstr.FromInt(ep.Port),
|
||||
Service: service,
|
||||
})
|
||||
}
|
||||
|
||||
conn, err := net.Dial("unix", nginx.StreamSocket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
buf, err := json.Marshal(streams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Write(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintf(conn, "\r\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func configureBackends(rawBackends []*ingress.Backend) error {
|
||||
backends := make([]*ingress.Backend, len(rawBackends))
|
||||
|
||||
for i, backend := range rawBackends {
|
||||
var service *apiv1.Service
|
||||
if backend.Service != nil {
|
||||
service = &apiv1.Service{Spec: backend.Service.Spec}
|
||||
|
|
@ -900,99 +986,22 @@ func configureDynamically(pcfg *ingress.Configuration) error {
|
|||
return fmt.Errorf("unexpected error code: %d", statusCode)
|
||||
}
|
||||
|
||||
streams := make([]ingress.Backend, 0)
|
||||
for _, ep := range pcfg.TCPEndpoints {
|
||||
var service *apiv1.Service
|
||||
if ep.Service != nil {
|
||||
service = &apiv1.Service{Spec: ep.Service.Spec}
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("tcp-%v-%v-%v", ep.Backend.Namespace, ep.Backend.Name, ep.Backend.Port.String())
|
||||
streams = append(streams, ingress.Backend{
|
||||
Name: key,
|
||||
Endpoints: ep.Endpoints,
|
||||
Port: intstr.FromInt(ep.Port),
|
||||
Service: service,
|
||||
})
|
||||
}
|
||||
for _, ep := range pcfg.UDPEndpoints {
|
||||
var service *apiv1.Service
|
||||
if ep.Service != nil {
|
||||
service = &apiv1.Service{Spec: ep.Service.Spec}
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("udp-%v-%v-%v", ep.Backend.Namespace, ep.Backend.Name, ep.Backend.Port.String())
|
||||
streams = append(streams, ingress.Backend{
|
||||
Name: key,
|
||||
Endpoints: ep.Endpoints,
|
||||
Port: intstr.FromInt(ep.Port),
|
||||
Service: service,
|
||||
})
|
||||
}
|
||||
|
||||
err = updateStreamConfiguration(streams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
statusCode, _, err = nginx.NewPostStatusRequest("/configuration/general", "application/json", ingress.GeneralConfig{
|
||||
ControllerPodsCount: pcfg.ControllerPodsCount,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if statusCode != http.StatusCreated {
|
||||
return fmt.Errorf("unexpected error code: %d", statusCode)
|
||||
}
|
||||
|
||||
if ngx_config.EnableDynamicCertificates {
|
||||
err = configureCertificates(pcfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateStreamConfiguration(streams []ingress.Backend) error {
|
||||
conn, err := net.Dial("unix", nginx.StreamSocket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
buf, err := json.Marshal(streams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Write(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintf(conn, "\r\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// configureCertificates JSON encodes certificates and POSTs it to an internal HTTP endpoint
|
||||
// that is handled by Lua
|
||||
func configureCertificates(pcfg *ingress.Configuration) error {
|
||||
var servers []*ingress.Server
|
||||
func configureCertificates(rawServers []*ingress.Server) error {
|
||||
servers := make([]*ingress.Server, 0)
|
||||
|
||||
for _, server := range pcfg.Servers {
|
||||
if server.SSLCert.PemCertKey == "" {
|
||||
for _, server := range rawServers {
|
||||
if server.SSLCert == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
servers = append(servers, &ingress.Server{
|
||||
Hostname: server.Hostname,
|
||||
SSLCert: ingress.SSLCert{
|
||||
SSLCert: &ingress.SSLCert{
|
||||
PemCertKey: server.SSLCert.PemCertKey,
|
||||
},
|
||||
})
|
||||
|
|
@ -1000,22 +1009,22 @@ func configureCertificates(pcfg *ingress.Configuration) error {
|
|||
if server.Alias != "" && ssl.IsValidHostname(server.Alias, server.SSLCert.CN) {
|
||||
servers = append(servers, &ingress.Server{
|
||||
Hostname: server.Alias,
|
||||
SSLCert: ingress.SSLCert{
|
||||
SSLCert: &ingress.SSLCert{
|
||||
PemCertKey: server.SSLCert.PemCertKey,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
redirects := buildRedirects(pcfg.Servers)
|
||||
redirects := buildRedirects(rawServers)
|
||||
for _, redirect := range redirects {
|
||||
if redirect.SSLCert.PemCertKey == "" {
|
||||
if redirect.SSLCert == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
servers = append(servers, &ingress.Server{
|
||||
Hostname: redirect.From,
|
||||
SSLCert: ingress.SSLCert{
|
||||
SSLCert: &ingress.SSLCert{
|
||||
PemCertKey: redirect.SSLCert.PemCertKey,
|
||||
},
|
||||
})
|
||||
|
|
@ -1126,7 +1135,7 @@ func cleanTempNginxCfg() error {
|
|||
type redirect struct {
|
||||
From string
|
||||
To string
|
||||
SSLCert ingress.SSLCert
|
||||
SSLCert *ingress.SSLCert
|
||||
}
|
||||
|
||||
func buildRedirects(servers []*ingress.Server) []*redirect {
|
||||
|
|
@ -1170,7 +1179,7 @@ func buildRedirects(servers []*ingress.Server) []*redirect {
|
|||
To: to,
|
||||
}
|
||||
|
||||
if srv.SSLCert.PemSHA != "" {
|
||||
if srv.SSLCert != nil {
|
||||
if ssl.IsValidHostname(from, srv.SSLCert.CN) {
|
||||
r.SSLCert = srv.SSLCert
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -32,14 +32,10 @@ import (
|
|||
apiv1 "k8s.io/api/core/v1"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/ingress"
|
||||
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
|
||||
"k8s.io/ingress-nginx/internal/nginx"
|
||||
)
|
||||
|
||||
func TestIsDynamicConfigurationEnough(t *testing.T) {
|
||||
ngx_config.EnableDynamicCertificates = false
|
||||
defer func() { ngx_config.EnableDynamicCertificates = true }()
|
||||
|
||||
backends := []*ingress.Backend{{
|
||||
Name: "fakenamespace-myapp-80",
|
||||
Endpoints: []ingress.Endpoint{
|
||||
|
|
@ -62,7 +58,7 @@ func TestIsDynamicConfigurationEnough(t *testing.T) {
|
|||
Backend: "fakenamespace-myapp-80",
|
||||
},
|
||||
},
|
||||
SSLCert: ingress.SSLCert{
|
||||
SSLCert: &ingress.SSLCert{
|
||||
PemCertKey: "fake-certificate",
|
||||
},
|
||||
}}
|
||||
|
|
@ -98,8 +94,6 @@ func TestIsDynamicConfigurationEnough(t *testing.T) {
|
|||
Servers: servers,
|
||||
}
|
||||
|
||||
ngx_config.EnableDynamicCertificates = true
|
||||
|
||||
if !n.IsDynamicConfigurationEnough(newConfig) {
|
||||
t.Errorf("Expected to be dynamically configurable when only backends change")
|
||||
}
|
||||
|
|
@ -112,7 +106,7 @@ func TestIsDynamicConfigurationEnough(t *testing.T) {
|
|||
Backend: "fakenamespace-myapp-80",
|
||||
},
|
||||
},
|
||||
SSLCert: ingress.SSLCert{
|
||||
SSLCert: &ingress.SSLCert{
|
||||
PemCertKey: "fake-certificate",
|
||||
},
|
||||
}}
|
||||
|
|
@ -168,6 +162,13 @@ func TestConfigureDynamically(t *testing.T) {
|
|||
defer streamListener.Close()
|
||||
defer os.Remove(nginx.StreamSocket)
|
||||
|
||||
endpointStats := map[string]int{"/configuration/backends": 0, "/configuration/general": 0, "/configuration/servers": 0}
|
||||
resetEndpointStats := func() {
|
||||
for k := range endpointStats {
|
||||
endpointStats[k] = 0
|
||||
}
|
||||
}
|
||||
|
||||
server := &httptest.Server{
|
||||
Listener: listener,
|
||||
Config: &http.Server{
|
||||
|
|
@ -184,6 +185,8 @@ func TestConfigureDynamically(t *testing.T) {
|
|||
}
|
||||
body := string(b)
|
||||
|
||||
endpointStats[r.URL.Path] += 1
|
||||
|
||||
switch r.URL.Path {
|
||||
case "/configuration/backends":
|
||||
{
|
||||
|
|
@ -201,6 +204,12 @@ func TestConfigureDynamically(t *testing.T) {
|
|||
t.Errorf("controllerPodsCount should be present in JSON content: %v", body)
|
||||
}
|
||||
}
|
||||
case "/configuration/servers":
|
||||
{
|
||||
if !strings.Contains(body, "[]") {
|
||||
t.Errorf("controllerPodsCount should be present in JSON content: %v", body)
|
||||
}
|
||||
}
|
||||
default:
|
||||
t.Errorf("unknown request to %s", r.URL.Path)
|
||||
}
|
||||
|
|
@ -246,17 +255,67 @@ func TestConfigureDynamically(t *testing.T) {
|
|||
ControllerPodsCount: 2,
|
||||
}
|
||||
|
||||
ngx_config.EnableDynamicCertificates = false
|
||||
defer func() { ngx_config.EnableDynamicCertificates = true }()
|
||||
n := &NGINXController{
|
||||
runningConfig: &ingress.Configuration{},
|
||||
cfg: &Configuration{},
|
||||
}
|
||||
|
||||
err = configureDynamically(commonConfig)
|
||||
err = n.configureDynamically(commonConfig)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error posting dynamic configuration: %v", err)
|
||||
}
|
||||
|
||||
if commonConfig.Backends[0].Endpoints[0].Target != target {
|
||||
t.Errorf("unexpected change in the configuration object after configureDynamically invocation")
|
||||
}
|
||||
for endpoint, count := range endpointStats {
|
||||
if count != 1 {
|
||||
t.Errorf("Expected %v to receive %d requests but received %d.", endpoint, 1, count)
|
||||
}
|
||||
}
|
||||
|
||||
resetEndpointStats()
|
||||
n.runningConfig.Backends = backends
|
||||
err = n.configureDynamically(commonConfig)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error posting dynamic configuration: %v", err)
|
||||
}
|
||||
for endpoint, count := range endpointStats {
|
||||
if endpoint == "/configuration/backends" {
|
||||
if count != 0 {
|
||||
t.Errorf("Expected %v to receive %d requests but received %d.", endpoint, 0, count)
|
||||
}
|
||||
} else if count != 1 {
|
||||
t.Errorf("Expected %v to receive %d requests but received %d.", endpoint, 1, count)
|
||||
}
|
||||
}
|
||||
|
||||
resetEndpointStats()
|
||||
n.runningConfig.Servers = servers
|
||||
err = n.configureDynamically(commonConfig)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error posting dynamic configuration: %v", err)
|
||||
}
|
||||
if count, _ := endpointStats["/configuration/backends"]; count != 0 {
|
||||
t.Errorf("Expected %v to receive %d requests but received %d.", "/configuration/backends", 0, count)
|
||||
}
|
||||
if count, _ := endpointStats["/configuration/servers"]; count != 0 {
|
||||
t.Errorf("Expected %v to receive %d requests but received %d.", "/configuration/servers", 0, count)
|
||||
}
|
||||
if count, _ := endpointStats["/configuration/general"]; count != 1 {
|
||||
t.Errorf("Expected %v to receive %d requests but received %d.", "/configuration/general", 0, count)
|
||||
}
|
||||
|
||||
resetEndpointStats()
|
||||
n.runningConfig.ControllerPodsCount = commonConfig.ControllerPodsCount
|
||||
err = n.configureDynamically(commonConfig)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error posting dynamic configuration: %v", err)
|
||||
}
|
||||
for endpoint, count := range endpointStats {
|
||||
if count != 0 {
|
||||
t.Errorf("Expected %v to receive %d requests but received %d.", endpoint, 0, count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigureCertificates(t *testing.T) {
|
||||
|
|
@ -276,7 +335,7 @@ func TestConfigureCertificates(t *testing.T) {
|
|||
|
||||
servers := []*ingress.Server{{
|
||||
Hostname: "myapp.fake",
|
||||
SSLCert: ingress.SSLCert{
|
||||
SSLCert: &ingress.SSLCert{
|
||||
PemCertKey: "fake-cert",
|
||||
},
|
||||
}}
|
||||
|
|
@ -316,11 +375,7 @@ func TestConfigureCertificates(t *testing.T) {
|
|||
defer server.Close()
|
||||
server.Start()
|
||||
|
||||
commonConfig := &ingress.Configuration{
|
||||
Servers: servers,
|
||||
}
|
||||
|
||||
err = configureCertificates(commonConfig)
|
||||
err = configureCertificates(servers)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error posting dynamic certificate configuration: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,17 +17,20 @@ limitations under the License.
|
|||
package store
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/klog"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
networking "k8s.io/api/networking/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/file"
|
||||
"k8s.io/ingress-nginx/internal/ingress"
|
||||
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
|
||||
"k8s.io/ingress-nginx/internal/net/ssl"
|
||||
)
|
||||
|
||||
|
|
@ -39,7 +42,6 @@ func (s *k8sStore) syncSecret(key string) {
|
|||
|
||||
klog.V(3).Infof("Syncing Secret %q", key)
|
||||
|
||||
// TODO: getPemCertificate should not write to disk to avoid unnecessary overhead
|
||||
cert, err := s.getPemCertificate(key)
|
||||
if err != nil {
|
||||
if !isErrSecretForAuth(err) {
|
||||
|
|
@ -92,6 +94,7 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
|
|||
if cert == nil {
|
||||
return nil, fmt.Errorf("key 'tls.crt' missing from Secret %q", secretName)
|
||||
}
|
||||
|
||||
if key == nil {
|
||||
return nil, fmt.Errorf("key 'tls.key' missing from Secret %q", secretName)
|
||||
}
|
||||
|
|
@ -101,15 +104,16 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
|
|||
return nil, fmt.Errorf("unexpected error creating SSL Cert: %v", err)
|
||||
}
|
||||
|
||||
if !ngx_config.EnableDynamicCertificates || len(ca) > 0 {
|
||||
err = ssl.StoreSSLCertOnDisk(s.filesystem, nsSecName, sslCert)
|
||||
if len(ca) > 0 {
|
||||
path, err := ssl.StoreSSLCertOnDisk(nsSecName, sslCert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while storing certificate and key: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(ca) > 0 {
|
||||
err = ssl.ConfigureCACertWithCertAndKey(s.filesystem, nsSecName, ca, sslCert)
|
||||
sslCert.CAFileName = path
|
||||
sslCert.CASHA = file.SHA1(path)
|
||||
|
||||
err = ssl.ConfigureCACertWithCertAndKey(nsSecName, ca, sslCert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error configuring CA certificate: %v", err)
|
||||
}
|
||||
|
|
@ -120,14 +124,13 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
|
|||
msg += " and authentication"
|
||||
}
|
||||
klog.V(3).Info(msg)
|
||||
|
||||
} else if len(ca) > 0 {
|
||||
sslCert, err = ssl.CreateCACert(ca)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unexpected error creating SSL Cert: %v", err)
|
||||
}
|
||||
|
||||
err = ssl.ConfigureCACert(s.filesystem, nsSecName, ca, sslCert)
|
||||
err = ssl.ConfigureCACert(nsSecName, ca, sslCert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error configuring CA certificate: %v", err)
|
||||
}
|
||||
|
|
@ -135,7 +138,6 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
|
|||
// makes this secret in 'syncSecret' to be used for Certificate Authentication
|
||||
// this does not enable Certificate Authentication
|
||||
klog.V(3).Infof("Configuring Secret %q for TLS authentication", secretName)
|
||||
|
||||
} else {
|
||||
if auth != nil {
|
||||
return nil, ErrSecretForAuth
|
||||
|
|
@ -147,6 +149,21 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
|
|||
sslCert.Name = secret.Name
|
||||
sslCert.Namespace = secret.Namespace
|
||||
|
||||
hasher := sha1.New()
|
||||
hasher.Write(sslCert.Certificate.Raw)
|
||||
|
||||
sslCert.PemSHA = hex.EncodeToString(hasher.Sum(nil))
|
||||
|
||||
// the default SSL certificate needs to be present on disk
|
||||
if secretName == s.defaultSSLCertificate {
|
||||
path, err := ssl.StoreSSLCertOnDisk(nsSecName, sslCert)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "storing default SSL Certificate")
|
||||
}
|
||||
|
||||
sslCert.PemFileName = path
|
||||
}
|
||||
|
||||
return sslCert, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -152,9 +152,9 @@ func (e NotExistsError) Error() string {
|
|||
|
||||
// Run initiates the synchronization of the informers against the API server.
|
||||
func (i *Informer) Run(stopCh chan struct{}) {
|
||||
go i.Secret.Run(stopCh)
|
||||
go i.Endpoint.Run(stopCh)
|
||||
go i.Service.Run(stopCh)
|
||||
go i.Secret.Run(stopCh)
|
||||
go i.ConfigMap.Run(stopCh)
|
||||
go i.Pod.Run(stopCh)
|
||||
|
||||
|
|
@ -165,6 +165,7 @@ func (i *Informer) Run(stopCh chan struct{}) {
|
|||
i.Service.HasSynced,
|
||||
i.Secret.HasSynced,
|
||||
i.ConfigMap.HasSynced,
|
||||
i.Pod.HasSynced,
|
||||
) {
|
||||
runtime.HandleError(fmt.Errorf("timed out waiting for caches to sync"))
|
||||
}
|
||||
|
|
@ -208,8 +209,6 @@ type k8sStore struct {
|
|||
// secret in the annotations.
|
||||
secretIngressMap ObjectRefMap
|
||||
|
||||
filesystem file.Filesystem
|
||||
|
||||
// updateCh
|
||||
updateCh *channels.RingChannel
|
||||
|
||||
|
|
@ -229,7 +228,6 @@ func New(
|
|||
namespace, configmap, tcp, udp, defaultSSLCertificate string,
|
||||
resyncPeriod time.Duration,
|
||||
client clientset.Interface,
|
||||
fs file.Filesystem,
|
||||
updateCh *channels.RingChannel,
|
||||
pod *k8s.PodInfo,
|
||||
disableCatchAll bool) Storer {
|
||||
|
|
@ -238,7 +236,6 @@ func New(
|
|||
informers: &Informer{},
|
||||
listers: &Lister{},
|
||||
sslStore: NewSSLCertTracker(),
|
||||
filesystem: fs,
|
||||
updateCh: updateCh,
|
||||
backendConfig: ngx_config.NewDefault(),
|
||||
syncSecretMu: &sync.Mutex{},
|
||||
|
|
@ -494,6 +491,7 @@ func New(
|
|||
}
|
||||
store.syncIngress(ing)
|
||||
}
|
||||
|
||||
updateCh.In() <- Event{
|
||||
Type: DeleteEvent,
|
||||
Obj: obj,
|
||||
|
|
@ -814,7 +812,7 @@ func (s *k8sStore) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error
|
|||
return &resolver.AuthSSLCert{
|
||||
Secret: name,
|
||||
CAFileName: cert.CAFileName,
|
||||
PemSHA: cert.PemSHA,
|
||||
CASHA: cert.CASHA,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,10 +38,8 @@ import (
|
|||
"k8s.io/client-go/tools/cache"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/file"
|
||||
"k8s.io/ingress-nginx/internal/ingress"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
|
||||
"k8s.io/ingress-nginx/internal/k8s"
|
||||
"k8s.io/ingress-nginx/test/e2e/framework"
|
||||
)
|
||||
|
|
@ -87,7 +85,6 @@ func TestStore(t *testing.T) {
|
|||
}
|
||||
}(updateCh)
|
||||
|
||||
fs := newFS(t)
|
||||
storer := New(
|
||||
ns,
|
||||
fmt.Sprintf("%v/config", ns),
|
||||
|
|
@ -96,7 +93,6 @@ func TestStore(t *testing.T) {
|
|||
"",
|
||||
10*time.Minute,
|
||||
clientSet,
|
||||
fs,
|
||||
updateCh,
|
||||
pod,
|
||||
false)
|
||||
|
|
@ -167,7 +163,6 @@ func TestStore(t *testing.T) {
|
|||
}
|
||||
}(updateCh)
|
||||
|
||||
fs := newFS(t)
|
||||
storer := New(
|
||||
ns,
|
||||
fmt.Sprintf("%v/config", ns),
|
||||
|
|
@ -176,7 +171,6 @@ func TestStore(t *testing.T) {
|
|||
"",
|
||||
10*time.Minute,
|
||||
clientSet,
|
||||
fs,
|
||||
updateCh,
|
||||
pod,
|
||||
false)
|
||||
|
|
@ -317,7 +311,6 @@ func TestStore(t *testing.T) {
|
|||
}
|
||||
}(updateCh)
|
||||
|
||||
fs := newFS(t)
|
||||
storer := New(
|
||||
ns,
|
||||
fmt.Sprintf("%v/config", ns),
|
||||
|
|
@ -326,7 +319,6 @@ func TestStore(t *testing.T) {
|
|||
"",
|
||||
10*time.Minute,
|
||||
clientSet,
|
||||
fs,
|
||||
updateCh,
|
||||
pod,
|
||||
false)
|
||||
|
|
@ -423,7 +415,6 @@ func TestStore(t *testing.T) {
|
|||
}
|
||||
}(updateCh)
|
||||
|
||||
fs := newFS(t)
|
||||
storer := New(
|
||||
ns,
|
||||
fmt.Sprintf("%v/config", ns),
|
||||
|
|
@ -432,7 +423,6 @@ func TestStore(t *testing.T) {
|
|||
"",
|
||||
10*time.Minute,
|
||||
clientSet,
|
||||
fs,
|
||||
updateCh,
|
||||
pod,
|
||||
false)
|
||||
|
|
@ -512,7 +502,6 @@ func TestStore(t *testing.T) {
|
|||
}
|
||||
}(updateCh)
|
||||
|
||||
fs := newFS(t)
|
||||
storer := New(
|
||||
ns,
|
||||
fmt.Sprintf("%v/config", ns),
|
||||
|
|
@ -521,7 +510,6 @@ func TestStore(t *testing.T) {
|
|||
"",
|
||||
10*time.Minute,
|
||||
clientSet,
|
||||
fs,
|
||||
updateCh,
|
||||
pod,
|
||||
false)
|
||||
|
|
@ -623,7 +611,6 @@ func TestStore(t *testing.T) {
|
|||
}
|
||||
}(updateCh)
|
||||
|
||||
fs := newFS(t)
|
||||
storer := New(
|
||||
ns,
|
||||
fmt.Sprintf("%v/config", ns),
|
||||
|
|
@ -632,7 +619,6 @@ func TestStore(t *testing.T) {
|
|||
"",
|
||||
10*time.Minute,
|
||||
clientSet,
|
||||
fs,
|
||||
updateCh,
|
||||
pod,
|
||||
false)
|
||||
|
|
@ -701,39 +687,6 @@ func TestStore(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Errorf("error creating secret: %v", err)
|
||||
}
|
||||
|
||||
t.Run("should exists a secret in the local store and filesystem", func(t *testing.T) {
|
||||
ngx_config.EnableDynamicCertificates = false
|
||||
defer func() { ngx_config.EnableDynamicCertificates = true }()
|
||||
|
||||
err := framework.WaitForSecretInNamespace(clientSet, ns, name)
|
||||
if err != nil {
|
||||
t.Errorf("error waiting for secret: %v", err)
|
||||
}
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
pemFile := fmt.Sprintf("%v/%v-%v.pem", file.DefaultSSLDirectory, ns, name)
|
||||
err = framework.WaitForFileInFS(pemFile, fs)
|
||||
if err != nil {
|
||||
t.Errorf("error waiting for file to exist on the file system: %v", err)
|
||||
}
|
||||
|
||||
secretName := fmt.Sprintf("%v/%v", ns, name)
|
||||
sslCert, err := storer.GetLocalSSLCert(secretName)
|
||||
if err != nil {
|
||||
t.Errorf("error reading local secret %v: %v", secretName, err)
|
||||
}
|
||||
|
||||
if sslCert == nil {
|
||||
t.Errorf("expected a secret but none returned")
|
||||
}
|
||||
|
||||
pemSHA := file.SHA1(pemFile)
|
||||
if sslCert.PemSHA != pemSHA {
|
||||
t.Errorf("SHA of secret on disk differs from local secret store (%v != %v)", pemSHA, sslCert.PemSHA)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// test add ingress with secret it doesn't exists and then add secret
|
||||
|
|
@ -821,22 +774,9 @@ func deleteIngress(ingress *networking.Ingress, clientSet kubernetes.Interface,
|
|||
t.Logf("Ingress %+v deleted", ingress)
|
||||
}
|
||||
|
||||
func newFS(t *testing.T) file.Filesystem {
|
||||
fs, err := file.NewFakeFS()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating filesystem: %v", err)
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
// newStore creates a new mock object store for tests which do not require the
|
||||
// use of Informers.
|
||||
func newStore(t *testing.T) *k8sStore {
|
||||
fs, err := file.NewFakeFS()
|
||||
if err != nil {
|
||||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
pod := &k8s.PodInfo{
|
||||
Name: "ingress-1",
|
||||
Namespace: v1.NamespaceDefault,
|
||||
|
|
@ -853,7 +793,6 @@ func newStore(t *testing.T) *k8sStore {
|
|||
Pod: PodLister{cache.NewStore(cache.MetaNamespaceKeyFunc)},
|
||||
},
|
||||
sslStore: NewSSLCertTracker(),
|
||||
filesystem: fs,
|
||||
updateCh: channels.NewRingChannel(10),
|
||||
syncSecretMu: new(sync.Mutex),
|
||||
backendConfigMu: new(sync.RWMutex),
|
||||
|
|
|
|||
|
|
@ -59,10 +59,23 @@ const (
|
|||
globalAuthSnippet = "global-auth-snippet"
|
||||
globalAuthCacheKey = "global-auth-cache-key"
|
||||
globalAuthCacheDuration = "global-auth-cache-duration"
|
||||
luaSharedDictsKey = "lua-shared-dicts"
|
||||
)
|
||||
|
||||
var (
|
||||
validRedirectCodes = sets.NewInt([]int{301, 302, 307, 308}...)
|
||||
validRedirectCodes = sets.NewInt([]int{301, 302, 307, 308}...)
|
||||
defaultLuaSharedDicts = map[string]int{
|
||||
"configuration_data": 20,
|
||||
"certificate_data": 20,
|
||||
"balancer_ewma": 10,
|
||||
"balancer_ewma_last_touched_at": 10,
|
||||
"balancer_ewma_locks": 1,
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
maxAllowedLuaDictSize = 200
|
||||
maxNumberOfLuaDicts = 100
|
||||
)
|
||||
|
||||
// ReadConfig obtains the configuration defined by the user merged with the defaults.
|
||||
|
|
@ -87,7 +100,40 @@ func ReadConfig(src map[string]string) config.Configuration {
|
|||
blockUserAgentList := make([]string, 0)
|
||||
blockRefererList := make([]string, 0)
|
||||
responseHeaders := make([]string, 0)
|
||||
luaSharedDicts := make(map[string]int)
|
||||
|
||||
//parse lua shared dict values
|
||||
if val, ok := conf[luaSharedDictsKey]; ok {
|
||||
delete(conf, luaSharedDictsKey)
|
||||
lsd := strings.Split(val, ",")
|
||||
for _, v := range lsd {
|
||||
v = strings.Replace(v, " ", "", -1)
|
||||
results := strings.SplitN(v, ":", 2)
|
||||
dictName := results[0]
|
||||
size, err := strconv.Atoi(results[1])
|
||||
if err != nil {
|
||||
klog.Errorf("Ignoring non integer value %v for Lua dictionary %v: %v.", results[1], dictName, err)
|
||||
continue
|
||||
}
|
||||
if size > maxAllowedLuaDictSize {
|
||||
klog.Errorf("Ignoring %v for Lua dictionary %v: maximum size is %v.", size, dictName, maxAllowedLuaDictSize)
|
||||
continue
|
||||
}
|
||||
if len(luaSharedDicts)+1 > maxNumberOfLuaDicts {
|
||||
klog.Errorf("Ignoring %v for Lua dictionary %v: can not configure more than %v dictionaries.",
|
||||
size, dictName, maxNumberOfLuaDicts)
|
||||
continue
|
||||
}
|
||||
|
||||
luaSharedDicts[dictName] = size
|
||||
}
|
||||
}
|
||||
// set default Lua shared dicts
|
||||
for k, v := range defaultLuaSharedDicts {
|
||||
if _, ok := luaSharedDicts[k]; !ok {
|
||||
luaSharedDicts[k] = v
|
||||
}
|
||||
}
|
||||
if val, ok := conf[customHTTPErrors]; ok {
|
||||
delete(conf, customHTTPErrors)
|
||||
for _, i := range strings.Split(val, ",") {
|
||||
|
|
@ -305,6 +351,7 @@ func ReadConfig(src map[string]string) config.Configuration {
|
|||
to.HideHeaders = hideHeadersList
|
||||
to.ProxyStreamResponses = streamResponses
|
||||
to.DisableIpv6DNS = !ing_net.IsIPv6Enabled()
|
||||
to.LuaSharedDicts = luaSharedDicts
|
||||
|
||||
config := &mapstructure.DecoderConfig{
|
||||
Metadata: nil,
|
||||
|
|
|
|||
|
|
@ -64,7 +64,6 @@ func TestMergeConfigMapToStruct(t *testing.T) {
|
|||
"access-log-path": "/var/log/test/access.log",
|
||||
"error-log-path": "/var/log/test/error.log",
|
||||
"use-gzip": "true",
|
||||
"enable-dynamic-tls-records": "false",
|
||||
"gzip-level": "9",
|
||||
"gzip-types": "text/html",
|
||||
"proxy-real-ip-cidr": "1.1.1.1/8,2.2.2.2/24",
|
||||
|
|
@ -84,7 +83,6 @@ func TestMergeConfigMapToStruct(t *testing.T) {
|
|||
def.SkipAccessLogURLs = []string{"/log", "/demo", "/test"}
|
||||
def.ProxyReadTimeout = 1
|
||||
def.ProxySendTimeout = 2
|
||||
def.EnableDynamicTLSRecords = false
|
||||
def.UseProxyProtocol = true
|
||||
def.GzipLevel = 9
|
||||
def.GzipTypes = "text/html"
|
||||
|
|
@ -95,7 +93,7 @@ func TestMergeConfigMapToStruct(t *testing.T) {
|
|||
def.NginxStatusIpv4Whitelist = []string{"127.0.0.1", "10.0.0.0/24"}
|
||||
def.NginxStatusIpv6Whitelist = []string{"::1", "2001::/16"}
|
||||
def.ProxyAddOriginalURIHeader = false
|
||||
|
||||
def.LuaSharedDicts = defaultLuaSharedDicts
|
||||
def.DisableIpv6DNS = true
|
||||
|
||||
hash, err := hashstructure.Hash(def, &hashstructure.HashOptions{
|
||||
|
|
@ -125,6 +123,7 @@ func TestMergeConfigMapToStruct(t *testing.T) {
|
|||
}
|
||||
|
||||
def = config.NewDefault()
|
||||
def.LuaSharedDicts = defaultLuaSharedDicts
|
||||
def.DisableIpv6DNS = true
|
||||
|
||||
hash, err = hashstructure.Hash(def, &hashstructure.HashOptions{
|
||||
|
|
@ -143,6 +142,7 @@ func TestMergeConfigMapToStruct(t *testing.T) {
|
|||
}
|
||||
|
||||
def = config.NewDefault()
|
||||
def.LuaSharedDicts = defaultLuaSharedDicts
|
||||
def.WhitelistSourceRange = []string{"1.1.1.1/32"}
|
||||
def.DisableIpv6DNS = true
|
||||
|
||||
|
|
@ -303,3 +303,56 @@ func TestGlobalExternalAuthCacheDurationParsing(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLuaSharedDictsParsing(t *testing.T) {
|
||||
testsCases := []struct {
|
||||
name string
|
||||
entry map[string]string
|
||||
expect map[string]int
|
||||
}{
|
||||
{
|
||||
name: "default dicts configured when lua-shared-dicts is not set",
|
||||
entry: make(map[string]string),
|
||||
expect: defaultLuaSharedDicts,
|
||||
},
|
||||
{
|
||||
name: "configuration_data only",
|
||||
entry: map[string]string{"lua-shared-dicts": "configuration_data:5"},
|
||||
expect: map[string]int{"configuration_data": 5},
|
||||
},
|
||||
{
|
||||
name: "certificate_data only",
|
||||
entry: map[string]string{"lua-shared-dicts": "certificate_data: 4"},
|
||||
expect: map[string]int{"certificate_data": 4},
|
||||
},
|
||||
{
|
||||
name: "custom dicts",
|
||||
entry: map[string]string{"lua-shared-dicts": "configuration_data: 10, my_random_dict:15 , another_example:2"},
|
||||
expect: map[string]int{"configuration_data": 10, "my_random_dict": 15, "another_example": 2},
|
||||
},
|
||||
{
|
||||
name: "invalid size value should be ignored",
|
||||
entry: map[string]string{"lua-shared-dicts": "mydict: 10, invalid_dict: 1a"},
|
||||
expect: map[string]int{"mydict": 10},
|
||||
},
|
||||
{
|
||||
name: "dictionary size can not be larger than 200",
|
||||
entry: map[string]string{"lua-shared-dicts": "mydict: 10, invalid_dict: 201"},
|
||||
expect: map[string]int{"mydict": 10},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testsCases {
|
||||
// dynamically insert default dicts in the expected output
|
||||
for dictName, dictSize := range defaultLuaSharedDicts {
|
||||
if _, ok := tc.expect[dictName]; !ok {
|
||||
tc.expect[dictName] = dictSize
|
||||
}
|
||||
}
|
||||
|
||||
cfg := ReadConfig(tc.entry)
|
||||
if !reflect.DeepEqual(cfg.LuaSharedDicts, tc.expect) {
|
||||
t.Errorf("Testing %v. Expected \"%v\" but \"%v\" was returned", tc.name, tc.expect, cfg.LuaSharedDicts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/url"
|
||||
|
|
@ -37,13 +38,13 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/ingress-nginx/internal/file"
|
||||
"k8s.io/klog"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/ingress"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/influxdb"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/ratelimit"
|
||||
"k8s.io/ingress-nginx/internal/ingress/controller/config"
|
||||
ing_net "k8s.io/ingress-nginx/internal/net"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -66,8 +67,8 @@ type Template struct {
|
|||
|
||||
//NewTemplate returns a new Template instance or an
|
||||
//error if the specified template file contains errors
|
||||
func NewTemplate(file string, fs file.Filesystem) (*Template, error) {
|
||||
data, err := fs.ReadFile(file)
|
||||
func NewTemplate(file string) (*Template, error) {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unexpected error reading template %v", file)
|
||||
}
|
||||
|
|
@ -132,36 +133,37 @@ var (
|
|||
}
|
||||
return true
|
||||
},
|
||||
"escapeLiteralDollar": escapeLiteralDollar,
|
||||
"shouldConfigureLuaRestyWAF": shouldConfigureLuaRestyWAF,
|
||||
"buildLuaSharedDictionaries": buildLuaSharedDictionaries,
|
||||
"buildLocation": buildLocation,
|
||||
"buildAuthLocation": buildAuthLocation,
|
||||
"shouldApplyGlobalAuth": shouldApplyGlobalAuth,
|
||||
"buildAuthResponseHeaders": buildAuthResponseHeaders,
|
||||
"buildProxyPass": buildProxyPass,
|
||||
"filterRateLimits": filterRateLimits,
|
||||
"buildRateLimitZones": buildRateLimitZones,
|
||||
"buildRateLimit": buildRateLimit,
|
||||
"buildResolversForLua": buildResolversForLua,
|
||||
"configForLua": configForLua,
|
||||
"locationConfigForLua": locationConfigForLua,
|
||||
"buildResolvers": buildResolvers,
|
||||
"buildUpstreamName": buildUpstreamName,
|
||||
"isLocationInLocationList": isLocationInLocationList,
|
||||
"isLocationAllowed": isLocationAllowed,
|
||||
"buildLogFormatUpstream": buildLogFormatUpstream,
|
||||
"buildDenyVariable": buildDenyVariable,
|
||||
"getenv": os.Getenv,
|
||||
"contains": strings.Contains,
|
||||
"hasPrefix": strings.HasPrefix,
|
||||
"hasSuffix": strings.HasSuffix,
|
||||
"trimSpace": strings.TrimSpace,
|
||||
"toUpper": strings.ToUpper,
|
||||
"toLower": strings.ToLower,
|
||||
"formatIP": formatIP,
|
||||
"buildNextUpstream": buildNextUpstream,
|
||||
"getIngressInformation": getIngressInformation,
|
||||
"escapeLiteralDollar": escapeLiteralDollar,
|
||||
"shouldConfigureLuaRestyWAF": shouldConfigureLuaRestyWAF,
|
||||
"buildLuaSharedDictionaries": buildLuaSharedDictionaries,
|
||||
"luaConfigurationRequestBodySize": luaConfigurationRequestBodySize,
|
||||
"buildLocation": buildLocation,
|
||||
"buildAuthLocation": buildAuthLocation,
|
||||
"shouldApplyGlobalAuth": shouldApplyGlobalAuth,
|
||||
"buildAuthResponseHeaders": buildAuthResponseHeaders,
|
||||
"buildProxyPass": buildProxyPass,
|
||||
"filterRateLimits": filterRateLimits,
|
||||
"buildRateLimitZones": buildRateLimitZones,
|
||||
"buildRateLimit": buildRateLimit,
|
||||
"configForLua": configForLua,
|
||||
"locationConfigForLua": locationConfigForLua,
|
||||
"buildResolvers": buildResolvers,
|
||||
"buildUpstreamName": buildUpstreamName,
|
||||
"isLocationInLocationList": isLocationInLocationList,
|
||||
"isLocationAllowed": isLocationAllowed,
|
||||
"buildLogFormatUpstream": buildLogFormatUpstream,
|
||||
"buildDenyVariable": buildDenyVariable,
|
||||
"getenv": os.Getenv,
|
||||
"contains": strings.Contains,
|
||||
"hasPrefix": strings.HasPrefix,
|
||||
"hasSuffix": strings.HasSuffix,
|
||||
"trimSpace": strings.TrimSpace,
|
||||
"toUpper": strings.ToUpper,
|
||||
"toLower": strings.ToLower,
|
||||
"formatIP": formatIP,
|
||||
"quote": quote,
|
||||
"buildNextUpstream": buildNextUpstream,
|
||||
"getIngressInformation": getIngressInformation,
|
||||
"serverConfig": func(all config.TemplateConfig, server *ingress.Server) interface{} {
|
||||
return struct{ First, Second interface{} }{all, server}
|
||||
},
|
||||
|
|
@ -177,6 +179,8 @@ var (
|
|||
"opentracingPropagateContext": opentracingPropagateContext,
|
||||
"buildCustomErrorLocationsPerServer": buildCustomErrorLocationsPerServer,
|
||||
"shouldLoadModSecurityModule": shouldLoadModSecurityModule,
|
||||
"buildHTTPListener": buildHTTPListener,
|
||||
"buildHTTPSListener": buildHTTPSListener,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -208,6 +212,21 @@ func formatIP(input string) string {
|
|||
return fmt.Sprintf("[%s]", input)
|
||||
}
|
||||
|
||||
func quote(input interface{}) string {
|
||||
var inputStr string
|
||||
switch input := input.(type) {
|
||||
case string:
|
||||
inputStr = input
|
||||
break
|
||||
case fmt.Stringer:
|
||||
inputStr = input.String()
|
||||
break
|
||||
default:
|
||||
inputStr = fmt.Sprintf("%v", input)
|
||||
}
|
||||
return fmt.Sprintf("%q", inputStr)
|
||||
}
|
||||
|
||||
func shouldConfigureLuaRestyWAF(disableLuaRestyWAF bool, mode string) bool {
|
||||
if !disableLuaRestyWAF && len(mode) > 0 {
|
||||
return true
|
||||
|
|
@ -216,19 +235,26 @@ func shouldConfigureLuaRestyWAF(disableLuaRestyWAF bool, mode string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func buildLuaSharedDictionaries(s interface{}, disableLuaRestyWAF bool) string {
|
||||
func buildLuaSharedDictionaries(c interface{}, s interface{}, disableLuaRestyWAF bool) string {
|
||||
var out []string
|
||||
|
||||
cfg, ok := c.(config.Configuration)
|
||||
if !ok {
|
||||
klog.Errorf("expected a 'config.Configuration' type but %T was returned", c)
|
||||
return ""
|
||||
}
|
||||
servers, ok := s.([]*ingress.Server)
|
||||
if !ok {
|
||||
klog.Errorf("expected an '[]*ingress.Server' type but %T was returned", s)
|
||||
return ""
|
||||
}
|
||||
|
||||
out := []string{
|
||||
"lua_shared_dict configuration_data 15M",
|
||||
"lua_shared_dict certificate_data 16M",
|
||||
for name, size := range cfg.LuaSharedDicts {
|
||||
out = append(out, fmt.Sprintf("lua_shared_dict %s %dM", name, size))
|
||||
}
|
||||
|
||||
if !disableLuaRestyWAF {
|
||||
// TODO: there must be a better place for this
|
||||
if _, ok := cfg.LuaSharedDicts["waf_storage"]; !ok && !disableLuaRestyWAF {
|
||||
luaRestyWAFEnabled := func() bool {
|
||||
for _, server := range servers {
|
||||
for _, location := range server.Locations {
|
||||
|
|
@ -244,38 +270,23 @@ func buildLuaSharedDictionaries(s interface{}, disableLuaRestyWAF bool) string {
|
|||
}
|
||||
}
|
||||
|
||||
return strings.Join(out, ";\n\r") + ";"
|
||||
return strings.Join(out, ";\n") + ";\n"
|
||||
}
|
||||
|
||||
func buildResolversForLua(res interface{}, disableIpv6 interface{}) string {
|
||||
nss, ok := res.([]net.IP)
|
||||
func luaConfigurationRequestBodySize(c interface{}) string {
|
||||
cfg, ok := c.(config.Configuration)
|
||||
if !ok {
|
||||
klog.Errorf("expected a '[]net.IP' type but %T was returned", res)
|
||||
return ""
|
||||
}
|
||||
no6, ok := disableIpv6.(bool)
|
||||
if !ok {
|
||||
klog.Errorf("expected a 'bool' type but %T was returned", disableIpv6)
|
||||
return ""
|
||||
klog.Errorf("expected a 'config.Configuration' type but %T was returned", c)
|
||||
return "100" // just a default number
|
||||
}
|
||||
|
||||
if len(nss) == 0 {
|
||||
return ""
|
||||
size := cfg.LuaSharedDicts["configuration_data"]
|
||||
if size < cfg.LuaSharedDicts["certificate_data"] {
|
||||
size = cfg.LuaSharedDicts["certificate_data"]
|
||||
}
|
||||
size = size + 1
|
||||
|
||||
r := []string{}
|
||||
for _, ns := range nss {
|
||||
if ing_net.IsIPV6(ns) {
|
||||
if no6 {
|
||||
continue
|
||||
}
|
||||
r = append(r, fmt.Sprintf("\"[%v]\"", ns))
|
||||
} else {
|
||||
r = append(r, fmt.Sprintf("\"%v\"", ns))
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(r, ", ")
|
||||
return fmt.Sprintf("%d", size)
|
||||
}
|
||||
|
||||
// configForLua returns some general configuration as Lua table represented as string
|
||||
|
|
@ -314,7 +325,7 @@ func locationConfigForLua(l interface{}, s interface{}, a interface{}) string {
|
|||
return "{}"
|
||||
}
|
||||
|
||||
forceSSLRedirect := location.Rewrite.ForceSSLRedirect || (len(server.SSLCert.PemFileName) > 0 && location.Rewrite.SSLRedirect)
|
||||
forceSSLRedirect := location.Rewrite.ForceSSLRedirect || (server.SSLCert != nil && location.Rewrite.SSLRedirect)
|
||||
forceSSLRedirect = forceSSLRedirect && !isLocationInLocationList(l, all.Cfg.NoTLSRedirectLocations)
|
||||
|
||||
return fmt.Sprintf(`{
|
||||
|
|
@ -497,6 +508,9 @@ func buildProxyPass(host string, b interface{}, loc interface{}) string {
|
|||
case "AJP":
|
||||
proto = ""
|
||||
proxyPass = "ajp_pass"
|
||||
case "FCGI":
|
||||
proto = ""
|
||||
proxyPass = "fastcgi_pass"
|
||||
}
|
||||
|
||||
upstreamName := "upstream_balancer"
|
||||
|
|
@ -1088,3 +1102,169 @@ func shouldLoadModSecurityModule(c interface{}, s interface{}) bool {
|
|||
// Not enabled globally nor via annotation on a location, no need to load the module.
|
||||
return false
|
||||
}
|
||||
|
||||
func buildHTTPListener(t interface{}, s interface{}) string {
|
||||
var out []string
|
||||
|
||||
tc, ok := t.(config.TemplateConfig)
|
||||
if !ok {
|
||||
klog.Errorf("expected a 'config.TemplateConfig' type but %T was returned", t)
|
||||
return ""
|
||||
}
|
||||
|
||||
hostname, ok := s.(string)
|
||||
if !ok {
|
||||
klog.Errorf("expected a 'string' type but %T was returned", s)
|
||||
return ""
|
||||
}
|
||||
|
||||
addrV4 := []string{""}
|
||||
if len(tc.Cfg.BindAddressIpv4) > 0 {
|
||||
addrV4 = tc.Cfg.BindAddressIpv4
|
||||
}
|
||||
|
||||
co := commonListenOptions(tc, hostname)
|
||||
|
||||
out = append(out, httpListener(addrV4, co, tc)...)
|
||||
|
||||
if !tc.IsIPV6Enabled {
|
||||
return strings.Join(out, "\n")
|
||||
}
|
||||
|
||||
addrV6 := []string{"[::]"}
|
||||
if len(tc.Cfg.BindAddressIpv6) > 0 {
|
||||
addrV6 = tc.Cfg.BindAddressIpv6
|
||||
}
|
||||
|
||||
out = append(out, httpListener(addrV6, co, tc)...)
|
||||
|
||||
return strings.Join(out, "\n")
|
||||
}
|
||||
|
||||
func buildHTTPSListener(t interface{}, s interface{}) string {
|
||||
var out []string
|
||||
|
||||
tc, ok := t.(config.TemplateConfig)
|
||||
if !ok {
|
||||
klog.Errorf("expected a 'config.TemplateConfig' type but %T was returned", t)
|
||||
return ""
|
||||
}
|
||||
|
||||
hostname, ok := s.(string)
|
||||
if !ok {
|
||||
klog.Errorf("expected a 'string' type but %T was returned", s)
|
||||
return ""
|
||||
}
|
||||
|
||||
/*
|
||||
if server.SSLCert == nil && server.Hostname != "_" {
|
||||
return ""
|
||||
}
|
||||
*/
|
||||
|
||||
co := commonListenOptions(tc, hostname)
|
||||
|
||||
addrV4 := []string{""}
|
||||
if len(tc.Cfg.BindAddressIpv4) > 0 {
|
||||
addrV4 = tc.Cfg.BindAddressIpv4
|
||||
}
|
||||
|
||||
out = append(out, httpsListener(addrV4, co, tc)...)
|
||||
|
||||
if !tc.IsIPV6Enabled {
|
||||
return strings.Join(out, "\n")
|
||||
}
|
||||
|
||||
addrV6 := []string{"[::]"}
|
||||
if len(tc.Cfg.BindAddressIpv6) > 0 {
|
||||
addrV6 = tc.Cfg.BindAddressIpv6
|
||||
}
|
||||
|
||||
out = append(out, httpsListener(addrV6, co, tc)...)
|
||||
|
||||
return strings.Join(out, "\n")
|
||||
}
|
||||
|
||||
func commonListenOptions(template config.TemplateConfig, hostname string) string {
|
||||
var out []string
|
||||
|
||||
if template.Cfg.UseProxyProtocol {
|
||||
out = append(out, "proxy_protocol")
|
||||
}
|
||||
|
||||
if hostname != "_" {
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
// setup options that are valid only once per port
|
||||
|
||||
out = append(out, "default_server")
|
||||
|
||||
if template.Cfg.ReusePort {
|
||||
out = append(out, "reuseport")
|
||||
}
|
||||
|
||||
out = append(out, fmt.Sprintf("backlog=%v", template.BacklogSize))
|
||||
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
func httpListener(addresses []string, co string, tc config.TemplateConfig) []string {
|
||||
out := make([]string, 0)
|
||||
for _, address := range addresses {
|
||||
l := make([]string, 0)
|
||||
l = append(l, "listen")
|
||||
|
||||
if address == "" {
|
||||
l = append(l, fmt.Sprintf("%v", tc.ListenPorts.HTTP))
|
||||
} else {
|
||||
l = append(l, fmt.Sprintf("%v:%v", address, tc.ListenPorts.HTTP))
|
||||
}
|
||||
|
||||
l = append(l, co)
|
||||
l = append(l, ";")
|
||||
out = append(out, strings.Join(l, " "))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func httpsListener(addresses []string, co string, tc config.TemplateConfig) []string {
|
||||
out := make([]string, 0)
|
||||
for _, address := range addresses {
|
||||
l := make([]string, 0)
|
||||
l = append(l, "listen")
|
||||
|
||||
if tc.IsSSLPassthroughEnabled {
|
||||
if address == "" {
|
||||
l = append(l, fmt.Sprintf("%v", tc.ListenPorts.SSLProxy))
|
||||
} else {
|
||||
l = append(l, fmt.Sprintf("%v:%v", address, tc.ListenPorts.SSLProxy))
|
||||
}
|
||||
|
||||
l = append(l, "proxy_protocol")
|
||||
} else {
|
||||
if address == "" {
|
||||
l = append(l, fmt.Sprintf("%v", tc.ListenPorts.HTTPS))
|
||||
} else {
|
||||
l = append(l, fmt.Sprintf("%v:%v", address, tc.ListenPorts.HTTPS))
|
||||
}
|
||||
|
||||
if tc.Cfg.UseProxyProtocol {
|
||||
l = append(l, "proxy_protocol")
|
||||
}
|
||||
}
|
||||
|
||||
l = append(l, co)
|
||||
l = append(l, "ssl")
|
||||
|
||||
if tc.Cfg.UseHTTP2 {
|
||||
l = append(l, "http2")
|
||||
}
|
||||
|
||||
l = append(l, ";")
|
||||
out = append(out, strings.Join(l, " "))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,20 +17,20 @@ limitations under the License.
|
|||
package template
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
networking "k8s.io/api/networking/v1beta1"
|
||||
"k8s.io/ingress-nginx/internal/file"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/ingress"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/authreq"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/influxdb"
|
||||
|
|
@ -39,8 +39,18 @@ import (
|
|||
"k8s.io/ingress-nginx/internal/ingress/annotations/ratelimit"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/rewrite"
|
||||
"k8s.io/ingress-nginx/internal/ingress/controller/config"
|
||||
"k8s.io/ingress-nginx/internal/nginx"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// the default value of nginx.TemplatePath assumes the template exists in
|
||||
// the root filesystem and not in the rootfs directory
|
||||
path, err := filepath.Abs(filepath.Join("../../../../rootfs/", nginx.TemplatePath))
|
||||
if err == nil {
|
||||
nginx.TemplatePath = path
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// TODO: add tests for SSLPassthrough
|
||||
tmplFuncTestcases = map[string]struct {
|
||||
|
|
@ -168,7 +178,14 @@ proxy_pass http://upstream_balancer;`,
|
|||
func TestBuildLuaSharedDictionaries(t *testing.T) {
|
||||
invalidType := &ingress.Ingress{}
|
||||
expected := ""
|
||||
actual := buildLuaSharedDictionaries(invalidType, true)
|
||||
|
||||
// config lua dict
|
||||
cfg := config.Configuration{
|
||||
LuaSharedDicts: map[string]int{
|
||||
"configuration_data": 10, "certificate_data": 20,
|
||||
},
|
||||
}
|
||||
actual := buildLuaSharedDictionaries(cfg, invalidType, true)
|
||||
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Errorf("Expected '%v' but returned '%v'", expected, actual)
|
||||
|
|
@ -184,20 +201,41 @@ func TestBuildLuaSharedDictionaries(t *testing.T) {
|
|||
Locations: []*ingress.Location{{Path: "/", LuaRestyWAF: luarestywaf.Config{}}},
|
||||
},
|
||||
}
|
||||
|
||||
configuration := buildLuaSharedDictionaries(servers, false)
|
||||
if !strings.Contains(configuration, "lua_shared_dict configuration_data") {
|
||||
// returns value from config
|
||||
configuration := buildLuaSharedDictionaries(cfg, servers, false)
|
||||
if !strings.Contains(configuration, "lua_shared_dict configuration_data 10M;\n") {
|
||||
t.Errorf("expected to include 'configuration_data' but got %s", configuration)
|
||||
}
|
||||
if !strings.Contains(configuration, "lua_shared_dict certificate_data 20M;\n") {
|
||||
t.Errorf("expected to include 'certificate_data' but got %s", configuration)
|
||||
}
|
||||
if strings.Contains(configuration, "waf_storage") {
|
||||
t.Errorf("expected to not include 'waf_storage' but got %s", configuration)
|
||||
}
|
||||
|
||||
servers[1].Locations[0].LuaRestyWAF = luarestywaf.Config{Mode: "ACTIVE"}
|
||||
configuration = buildLuaSharedDictionaries(servers, false)
|
||||
configuration = buildLuaSharedDictionaries(cfg, servers, false)
|
||||
if !strings.Contains(configuration, "lua_shared_dict waf_storage") {
|
||||
t.Errorf("expected to configure 'waf_storage', but got %s", configuration)
|
||||
}
|
||||
// test invalid config
|
||||
configuration = buildLuaSharedDictionaries(invalidType, servers, false)
|
||||
if expected != actual {
|
||||
t.Errorf("Expected '%v' but returned '%v' ", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLuaConfigurationRequestBodySize(t *testing.T) {
|
||||
cfg := config.Configuration{
|
||||
LuaSharedDicts: map[string]int{
|
||||
"configuration_data": 10, "certificate_data": 20,
|
||||
},
|
||||
}
|
||||
|
||||
size := luaConfigurationRequestBodySize(cfg)
|
||||
if "21" != size {
|
||||
t.Errorf("expected the size to be 20 but got: %v", size)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatIP(t *testing.T) {
|
||||
|
|
@ -219,6 +257,21 @@ func TestFormatIP(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestQuote(t *testing.T) {
|
||||
cases := map[interface{}]string{
|
||||
"foo": `"foo"`,
|
||||
"\"foo\"": `"\"foo\""`,
|
||||
"foo\nbar": `"foo\nbar"`,
|
||||
10: `"10"`,
|
||||
}
|
||||
for input, output := range cases {
|
||||
actual := quote(input)
|
||||
if actual != output {
|
||||
t.Errorf("quote('%s'): expected '%v' but returned '%v'", input, output, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildLocation(t *testing.T) {
|
||||
invalidType := &ingress.Ingress{}
|
||||
expected := "/"
|
||||
|
|
@ -408,16 +461,13 @@ func TestTemplateWithData(t *testing.T) {
|
|||
dat.ListenPorts = &config.ListenPorts{}
|
||||
}
|
||||
|
||||
fs, err := file.NewFakeFS()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
ngxTpl, err := NewTemplate("/etc/nginx/template/nginx.tmpl", fs)
|
||||
ngxTpl, err := NewTemplate(nginx.TemplatePath)
|
||||
if err != nil {
|
||||
t.Errorf("invalid NGINX template: %v", err)
|
||||
}
|
||||
|
||||
dat.Cfg.DefaultSSLCertificate = &ingress.SSLCert{}
|
||||
|
||||
rt, err := ngxTpl.Write(dat)
|
||||
if err != nil {
|
||||
t.Errorf("invalid NGINX template: %v", err)
|
||||
|
|
@ -452,12 +502,7 @@ func BenchmarkTemplateWithData(b *testing.B) {
|
|||
b.Errorf("unexpected error unmarshalling json: %v", err)
|
||||
}
|
||||
|
||||
fs, err := file.NewFakeFS()
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
ngxTpl, err := NewTemplate("/etc/nginx/template/nginx.tmpl", fs)
|
||||
ngxTpl, err := NewTemplate(nginx.TemplatePath)
|
||||
if err != nil {
|
||||
b.Errorf("invalid NGINX template: %v", err)
|
||||
}
|
||||
|
|
@ -550,43 +595,6 @@ func TestBuildForwardedFor(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBuildResolversForLua(t *testing.T) {
|
||||
|
||||
ipOne := net.ParseIP("192.0.0.1")
|
||||
ipTwo := net.ParseIP("2001:db8:1234:0000:0000:0000:0000:0000")
|
||||
ipList := []net.IP{ipOne, ipTwo}
|
||||
|
||||
invalidType := &ingress.Ingress{}
|
||||
expected := ""
|
||||
actual := buildResolversForLua(invalidType, false)
|
||||
|
||||
// Invalid Type for []net.IP
|
||||
if expected != actual {
|
||||
t.Errorf("Expected '%v' but returned '%v'", expected, actual)
|
||||
}
|
||||
|
||||
actual = buildResolversForLua(ipList, invalidType)
|
||||
|
||||
// Invalid Type for bool
|
||||
if expected != actual {
|
||||
t.Errorf("Expected '%v' but returned '%v'", expected, actual)
|
||||
}
|
||||
|
||||
expected = "\"192.0.0.1\", \"[2001:db8:1234::]\""
|
||||
actual = buildResolversForLua(ipList, false)
|
||||
|
||||
if expected != actual {
|
||||
t.Errorf("Expected '%v' but returned '%v'", expected, actual)
|
||||
}
|
||||
|
||||
expected = "\"192.0.0.1\""
|
||||
actual = buildResolversForLua(ipList, true)
|
||||
|
||||
if expected != actual {
|
||||
t.Errorf("Expected '%v' but returned '%v'", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildResolvers(t *testing.T) {
|
||||
ipOne := net.ParseIP("192.0.0.1")
|
||||
ipTwo := net.ParseIP("2001:db8:1234:0000:0000:0000:0000:0000")
|
||||
|
|
@ -874,6 +882,7 @@ func TestOpentracingPropagateContext(t *testing.T) {
|
|||
&ingress.Location{BackendProtocol: "GRPC"}: "opentracing_grpc_propagate_context",
|
||||
&ingress.Location{BackendProtocol: "GRPCS"}: "opentracing_grpc_propagate_context",
|
||||
&ingress.Location{BackendProtocol: "AJP"}: "opentracing_propagate_context",
|
||||
&ingress.Location{BackendProtocol: "FCGI"}: "opentracing_propagate_context",
|
||||
"not a location": "opentracing_propagate_context",
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -154,4 +154,8 @@ type Backend struct {
|
|||
// Modifies the HTTP version the proxy uses to interact with the backend.
|
||||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_http_version
|
||||
ProxyHTTPVersion string `json:"proxy-http-version"`
|
||||
|
||||
// Sets the maximum temp file size when proxy-buffers capacity is exceeded.
|
||||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_max_temp_file_size
|
||||
ProxyMaxTempFileSize string `json:"proxy-max-temp-file-size"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ func (cm Controller) Collect(ch chan<- prometheus.Metric) {
|
|||
// SetSSLExpireTime sets the expiration time of SSL Certificates
|
||||
func (cm *Controller) SetSSLExpireTime(servers []*ingress.Server) {
|
||||
for _, s := range servers {
|
||||
if s.Hostname != "" && s.SSLCert.ExpireTime.Unix() > 0 {
|
||||
if s.Hostname != "" && s.SSLCert != nil && s.SSLCert.ExpireTime.Unix() > 0 {
|
||||
labels := make(prometheus.Labels, len(cm.labels)+1)
|
||||
for k, v := range cm.labels {
|
||||
labels[k] = v
|
||||
|
|
|
|||
|
|
@ -80,13 +80,13 @@ func TestControllerCounters(t *testing.T) {
|
|||
servers := []*ingress.Server{
|
||||
{
|
||||
Hostname: "demo",
|
||||
SSLCert: ingress.SSLCert{
|
||||
SSLCert: &ingress.SSLCert{
|
||||
ExpireTime: t1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Hostname: "invalid",
|
||||
SSLCert: ingress.SSLCert{
|
||||
SSLCert: &ingress.SSLCert{
|
||||
ExpireTime: time.Unix(0, 0),
|
||||
},
|
||||
},
|
||||
|
|
@ -135,13 +135,13 @@ func TestRemoveMetrics(t *testing.T) {
|
|||
servers := []*ingress.Server{
|
||||
{
|
||||
Hostname: "demo",
|
||||
SSLCert: ingress.SSLCert{
|
||||
SSLCert: &ingress.SSLCert{
|
||||
ExpireTime: t1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Hostname: "invalid",
|
||||
SSLCert: ingress.SSLCert{
|
||||
SSLCert: &ingress.SSLCert{
|
||||
ExpireTime: time.Unix(0, 0),
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ type Resolver interface {
|
|||
// GetDefaultBackend returns the backend that must be used as default
|
||||
GetDefaultBackend() defaults.Backend
|
||||
|
||||
// GetConfigMap searches for configmap containing the namespace and name usting the character /
|
||||
GetConfigMap(string) (*apiv1.ConfigMap, error)
|
||||
|
||||
// GetSecret searches for secrets containing the namespace and name using a the character /
|
||||
GetSecret(string) (*apiv1.Secret, error)
|
||||
|
||||
|
|
@ -48,8 +51,8 @@ type AuthSSLCert struct {
|
|||
Secret string `json:"secret"`
|
||||
// CAFileName contains the path to the secrets 'ca.crt'
|
||||
CAFileName string `json:"caFilename"`
|
||||
// PemSHA contains the SHA1 hash of the 'ca.crt' or combinations of (tls.crt, tls.key, tls.crt) depending on certs in secret
|
||||
PemSHA string `json:"pemSha"`
|
||||
// CASHA contains the SHA1 hash of the 'ca.crt' or combinations of (tls.crt, tls.key, tls.crt) depending on certs in secret
|
||||
CASHA string `json:"caSha"`
|
||||
}
|
||||
|
||||
// Equal tests for equality between two AuthSSLCert types
|
||||
|
|
@ -67,7 +70,7 @@ func (asslc1 *AuthSSLCert) Equal(assl2 *AuthSSLCert) bool {
|
|||
if asslc1.CAFileName != assl2.CAFileName {
|
||||
return false
|
||||
}
|
||||
if asslc1.PemSHA != assl2.PemSHA {
|
||||
if asslc1.CASHA != assl2.CASHA {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,11 @@ func (m Mock) GetDefaultBackend() defaults.Backend {
|
|||
return defaults.Backend{}
|
||||
}
|
||||
|
||||
// GetConfigMap searches for configmap containing the namespace and name usting the character /
|
||||
func (m Mock) GetConfigMap(string) (*apiv1.ConfigMap, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetSecret searches for secrets contenating the namespace and name using a the character /
|
||||
func (m Mock) GetSecret(string) (*apiv1.Secret, error) {
|
||||
return nil, nil
|
||||
|
|
|
|||
|
|
@ -20,25 +20,36 @@ import (
|
|||
"crypto/x509"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// SSLCert describes a SSL certificate to be used in a server
|
||||
type SSLCert struct {
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
Certificate *x509.Certificate `json:"certificate,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
|
||||
Certificate *x509.Certificate `json:"-"`
|
||||
|
||||
// CAFileName contains the path to the file with the root certificate
|
||||
CAFileName string `json:"caFileName"`
|
||||
|
||||
// CASHA contains the sha1 of the ca file.
|
||||
// This is used to detect changes in the secret that contains certificates
|
||||
CASHA string `json:"caSha"`
|
||||
|
||||
// PemFileName contains the path to the file with the certificate and key concatenated
|
||||
PemFileName string `json:"pemFileName"`
|
||||
|
||||
// PemSHA contains the sha1 of the pem file.
|
||||
// This is used to detect changes in the secret that contains the certificates
|
||||
// This is used to detect changes in the secret that contains certificates
|
||||
PemSHA string `json:"pemSha"`
|
||||
|
||||
// CN contains all the common names defined in the SSL certificate
|
||||
CN []string `json:"cn"`
|
||||
|
||||
// ExpiresTime contains the expiration of this SSL certificate in timestamp format
|
||||
ExpireTime time.Time `json:"expires"`
|
||||
|
||||
// Pem encoded certificate and key concatenated
|
||||
PemCertKey string `json:"pemCertKey,omitempty"`
|
||||
}
|
||||
|
|
@ -50,5 +61,5 @@ func (s SSLCert) GetObjectKind() schema.ObjectKind {
|
|||
|
||||
// HashInclude defines if a field should be used or not to calculate the hash
|
||||
func (s SSLCert) HashInclude(field string, v interface{}) (bool, error) {
|
||||
return (field != "PemSHA" && field != "ExpireTime"), nil
|
||||
return (field != "PemSHA" && field != "CASHA" && field != "ExpireTime"), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
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 ingress
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestGetObjectKindForSSLCert(t *testing.T) {
|
||||
fk := &SSLCert{
|
||||
ObjectMeta: metav1.ObjectMeta{},
|
||||
CAFileName: "ca_file",
|
||||
PemFileName: "pemfile",
|
||||
PemSHA: "pem_sha",
|
||||
CN: []string{},
|
||||
}
|
||||
|
||||
r := fk.GetObjectKind()
|
||||
if r == nil {
|
||||
t.Errorf("Returned nil but expected a valid ObjectKind")
|
||||
}
|
||||
}
|
||||
|
|
@ -27,10 +27,12 @@ import (
|
|||
"k8s.io/ingress-nginx/internal/ingress/annotations/authtls"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/connection"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/cors"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/fastcgi"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/influxdb"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/log"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/luarestywaf"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/mirror"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/modsecurity"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/proxy"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/proxyssl"
|
||||
|
|
@ -179,7 +181,7 @@ type Server struct {
|
|||
// the server or in the remote endpoint
|
||||
SSLPassthrough bool `json:"sslPassthrough"`
|
||||
// SSLCert describes the certificate that will be used on the server
|
||||
SSLCert SSLCert `json:"sslCert"`
|
||||
SSLCert *SSLCert `json:"sslCert"`
|
||||
// Locations list of URIs configured in the server.
|
||||
Locations []*Location `json:"locations,omitempty"`
|
||||
// Alias return the alias of the server name
|
||||
|
|
@ -230,7 +232,7 @@ type Location struct {
|
|||
// Backend describes the name of the backend to use.
|
||||
Backend string `json:"backend"`
|
||||
// Service describes the referenced services from the ingress
|
||||
Service *apiv1.Service `json:"service,omitempty"`
|
||||
Service *apiv1.Service `json:"-"`
|
||||
// Port describes to which port from the service
|
||||
Port intstr.IntOrString `json:"port"`
|
||||
// Overwrite the Host header passed into the backend. Defaults to
|
||||
|
|
@ -297,7 +299,7 @@ type Location struct {
|
|||
ClientBodyBufferSize string `json:"clientBodyBufferSize,omitempty"`
|
||||
// DefaultBackend allows the use of a custom default backend for this location.
|
||||
// +optional
|
||||
DefaultBackend *apiv1.Service `json:"defaultBackend,omitempty"`
|
||||
DefaultBackend *apiv1.Service `json:"-"`
|
||||
// DefaultBackendUpstreamName is the upstream-formatted string for the name of
|
||||
// this location's custom default backend
|
||||
DefaultBackendUpstreamName string `json:"defaultBackendUpstreamName,omitempty"`
|
||||
|
|
@ -316,6 +318,9 @@ type Location struct {
|
|||
// BackendProtocol indicates which protocol should be used to communicate with the service
|
||||
// By default this is HTTP
|
||||
BackendProtocol string `json:"backend-protocol"`
|
||||
// FastCGI allows the ingress to act as a FastCGI client for a given location.
|
||||
// +optional
|
||||
FastCGI fastcgi.Config `json:"fastcgi,omitempty"`
|
||||
// CustomHTTPErrors specifies the error codes that should be intercepted.
|
||||
// +optional
|
||||
CustomHTTPErrors []int `json:"custom-http-errors"`
|
||||
|
|
@ -324,6 +329,9 @@ type Location struct {
|
|||
ModSecurity modsecurity.Config `json:"modsecurity"`
|
||||
// Satisfy dictates allow access if any or all is set
|
||||
Satisfy string `json:"satisfy"`
|
||||
// Mirror allows you to mirror traffic to a "test" backend
|
||||
// +optional
|
||||
Mirror mirror.Config `json:"mirror,omitempty"`
|
||||
}
|
||||
|
||||
// SSLPassthroughBackend describes a SSL upstream server configured
|
||||
|
|
@ -331,7 +339,7 @@ type Location struct {
|
|||
// The endpoints must provide the TLS termination exposing the required SSL certificate.
|
||||
// The ingress controller only pipes the underlying TCP connection
|
||||
type SSLPassthroughBackend struct {
|
||||
Service *apiv1.Service `json:"service,omitempty"`
|
||||
Service *apiv1.Service `json:"-"`
|
||||
Port intstr.IntOrString `json:"port"`
|
||||
// Backend describes the endpoints to use.
|
||||
Backend string `json:"namespace,omitempty"`
|
||||
|
|
@ -348,7 +356,7 @@ type L4Service struct {
|
|||
// Endpoints active endpoints of the service
|
||||
Endpoints []Endpoint `json:"endpoints,omitempty"`
|
||||
// k8s Service
|
||||
Service *apiv1.Service `json:"service,omitempty"`
|
||||
Service *apiv1.Service `json:"-"`
|
||||
}
|
||||
|
||||
// L4Backend describes the kubernetes service behind L4 Ingress service
|
||||
|
|
@ -369,8 +377,8 @@ type ProxyProtocol struct {
|
|||
|
||||
// Ingress holds the definition of an Ingress plus its annotations
|
||||
type Ingress struct {
|
||||
networking.Ingress
|
||||
ParsedAnnotations *annotations.Ingress
|
||||
networking.Ingress `json:"-"`
|
||||
ParsedAnnotations *annotations.Ingress `json:"parsedAnnotations"`
|
||||
}
|
||||
|
||||
// GeneralConfig holds the definition of lua general configuration data
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ func (s1 *Server) Equal(s2 *Server) bool {
|
|||
if s1.SSLPassthrough != s2.SSLPassthrough {
|
||||
return false
|
||||
}
|
||||
if !(&s1.SSLCert).Equal(&s2.SSLCert) {
|
||||
if !(s1.SSLCert).Equal(s2.SSLCert) {
|
||||
return false
|
||||
}
|
||||
if s1.Alias != s2.Alias {
|
||||
|
|
@ -401,6 +401,10 @@ func (l1 *Location) Equal(l2 *Location) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
if !(&l1.FastCGI).Equal(&l2.FastCGI) {
|
||||
return false
|
||||
}
|
||||
|
||||
match := compareInts(l1.CustomHTTPErrors, l2.CustomHTTPErrors)
|
||||
if !match {
|
||||
return false
|
||||
|
|
@ -418,6 +422,14 @@ func (l1 *Location) Equal(l2 *Location) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
if l1.Mirror.URI != l2.Mirror.URI {
|
||||
return false
|
||||
}
|
||||
|
||||
if l1.Mirror.RequestBody != l2.Mirror.RequestBody {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -504,7 +516,7 @@ func (s1 *SSLCert) Equal(s2 *SSLCert) bool {
|
|||
if s1 == nil || s2 == nil {
|
||||
return false
|
||||
}
|
||||
if s1.PemFileName != s2.PemFileName {
|
||||
if s1.CASHA != s2.CASHA {
|
||||
return false
|
||||
}
|
||||
if s1.PemSHA != s2.PemSHA {
|
||||
|
|
|
|||
|
|
@ -122,13 +122,13 @@ func NetworkingIngressAvailable(client clientset.Interface) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
serverVersion, _ := client.Discovery().ServerVersion()
|
||||
serverVersion, err := client.Discovery().ServerVersion()
|
||||
if err != nil {
|
||||
klog.Errorf("unexpected error parsing Kubernetes version: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
runningVersion, _ := version.ParseGeneric(serverVersion.String())
|
||||
runningVersion, err := version.ParseGeneric(serverVersion.String())
|
||||
if err != nil {
|
||||
klog.Errorf("unexpected error parsing running Kubernetes version: %v", err)
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -27,8 +27,10 @@ import (
|
|||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
|
@ -51,6 +53,22 @@ const (
|
|||
fakeCertificateName = "default-fake-certificate"
|
||||
)
|
||||
|
||||
func init() {
|
||||
_, err := os.Stat(file.DefaultSSLDirectory)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = os.MkdirAll(file.DefaultSSLDirectory, file.ReadWriteByUser)
|
||||
if err != nil {
|
||||
klog.Fatalf("Unexpected error checking for default SSL directory: %v", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
klog.Fatalf("Unexpected error checking for default SSL directory: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// getPemFileName returns absolute file path and file name of pem cert related to given fullSecretName
|
||||
func getPemFileName(fullSecretName string) (string, string) {
|
||||
pemName := fmt.Sprintf("%v.pem", fullSecretName)
|
||||
|
|
@ -164,88 +182,58 @@ func CreateCACert(ca []byte) (*ingress.SSLCert, error) {
|
|||
|
||||
// StoreSSLCertOnDisk creates a .pem file with content PemCertKey from the given sslCert
|
||||
// and sets relevant remaining fields of sslCert object
|
||||
func StoreSSLCertOnDisk(fs file.Filesystem, name string, sslCert *ingress.SSLCert) error {
|
||||
func StoreSSLCertOnDisk(name string, sslCert *ingress.SSLCert) (string, error) {
|
||||
pemFileName, _ := getPemFileName(name)
|
||||
|
||||
pemFile, err := fs.Create(pemFileName)
|
||||
err := ioutil.WriteFile(pemFileName, []byte(sslCert.PemCertKey), file.ReadWriteByUser)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create PEM certificate file %v: %v", pemFileName, err)
|
||||
}
|
||||
defer pemFile.Close()
|
||||
|
||||
_, err = pemFile.Write([]byte(sslCert.PemCertKey))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write data to PEM file %v: %v", pemFileName, err)
|
||||
return "", fmt.Errorf("could not create PEM certificate file %v: %v", pemFileName, err)
|
||||
}
|
||||
|
||||
sslCert.PemFileName = pemFileName
|
||||
sslCert.PemSHA = file.SHA1(pemFileName)
|
||||
|
||||
return nil
|
||||
return pemFileName, nil
|
||||
}
|
||||
|
||||
// ConfigureCACertWithCertAndKey appends ca into existing PEM file consisting of cert and key
|
||||
// and sets relevant fields in sslCert object
|
||||
func ConfigureCACertWithCertAndKey(fs file.Filesystem, name string, ca []byte, sslCert *ingress.SSLCert) error {
|
||||
func ConfigureCACertWithCertAndKey(name string, ca []byte, sslCert *ingress.SSLCert) error {
|
||||
err := verifyPemCertAgainstRootCA(sslCert.Certificate, ca)
|
||||
if err != nil {
|
||||
oe := fmt.Sprintf("failed to verify certificate chain: \n\t%s\n", err)
|
||||
return errors.New(oe)
|
||||
}
|
||||
|
||||
certAndKey, err := fs.ReadFile(sslCert.PemFileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read file %v for writing additional CA chains: %v", sslCert.PemFileName, err)
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
|
||||
f, err := fs.Create(sslCert.PemFileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create PEM file %v: %v", sslCert.PemFileName, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Write(certAndKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write cert and key bundle to cert file %v: %v", sslCert.PemFileName, err)
|
||||
}
|
||||
|
||||
_, err = f.Write([]byte("\n"))
|
||||
_, err = buffer.Write([]byte(sslCert.PemCertKey))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not append newline to cert file %v: %v", sslCert.PemFileName, err)
|
||||
}
|
||||
|
||||
_, err = f.Write(ca)
|
||||
_, err = buffer.Write([]byte("\n"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not append newline to cert file %v: %v", sslCert.PemFileName, err)
|
||||
}
|
||||
|
||||
_, err = buffer.Write(ca)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write ca data to cert file %v: %v", sslCert.PemFileName, err)
|
||||
}
|
||||
|
||||
sslCert.CAFileName = sslCert.PemFileName
|
||||
// since we updated sslCert.PemFileName we need to recalculate the checksum
|
||||
sslCert.PemSHA = file.SHA1(sslCert.PemFileName)
|
||||
|
||||
return nil
|
||||
return ioutil.WriteFile(sslCert.CAFileName, buffer.Bytes(), 0644)
|
||||
}
|
||||
|
||||
// ConfigureCACert is similar to ConfigureCACertWithCertAndKey but it creates a separate file
|
||||
// for CA cert and writes only ca into it and then sets relevant fields in sslCert
|
||||
func ConfigureCACert(fs file.Filesystem, name string, ca []byte, sslCert *ingress.SSLCert) error {
|
||||
func ConfigureCACert(name string, ca []byte, sslCert *ingress.SSLCert) error {
|
||||
caName := fmt.Sprintf("ca-%v.pem", name)
|
||||
fileName := fmt.Sprintf("%v/%v", file.DefaultSSLDirectory, caName)
|
||||
|
||||
f, err := fs.Create(fileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write CA file %v: %v", fileName, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Write(ca)
|
||||
err := ioutil.WriteFile(fileName, ca, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write CA file %v: %v", fileName, err)
|
||||
}
|
||||
|
||||
sslCert.PemFileName = fileName
|
||||
sslCert.CAFileName = fileName
|
||||
sslCert.PemSHA = file.SHA1(fileName)
|
||||
|
||||
klog.V(3).Infof("Created CA Certificate for Authentication: %v", fileName)
|
||||
|
||||
|
|
@ -319,10 +307,10 @@ func parseSANExtension(value []byte) (dnsNames, emailAddresses []string, ipAddre
|
|||
}
|
||||
|
||||
// AddOrUpdateDHParam creates a dh parameters file with the specified name
|
||||
func AddOrUpdateDHParam(name string, dh []byte, fs file.Filesystem) (string, error) {
|
||||
func AddOrUpdateDHParam(name string, dh []byte) (string, error) {
|
||||
pemFileName, pemName := getPemFileName(name)
|
||||
|
||||
tempPemFile, err := fs.TempFile(file.DefaultSSLDirectory, pemName)
|
||||
tempPemFile, err := ioutil.TempFile(file.DefaultSSLDirectory, pemName)
|
||||
|
||||
klog.V(3).Infof("Creating temp file %v for DH param: %v", tempPemFile.Name(), pemName)
|
||||
if err != nil {
|
||||
|
|
@ -339,9 +327,9 @@ func AddOrUpdateDHParam(name string, dh []byte, fs file.Filesystem) (string, err
|
|||
return "", fmt.Errorf("could not close temp pem file %v: %v", tempPemFile.Name(), err)
|
||||
}
|
||||
|
||||
defer fs.RemoveAll(tempPemFile.Name())
|
||||
defer os.Remove(tempPemFile.Name())
|
||||
|
||||
pemCerts, err := fs.ReadFile(tempPemFile.Name())
|
||||
pemCerts, err := ioutil.ReadFile(tempPemFile.Name())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
@ -356,7 +344,7 @@ func AddOrUpdateDHParam(name string, dh []byte, fs file.Filesystem) (string, err
|
|||
return "", fmt.Errorf("certificate %v contains invalid data", name)
|
||||
}
|
||||
|
||||
err = fs.Rename(tempPemFile.Name(), pemFileName)
|
||||
err = os.Rename(tempPemFile.Name(), pemFileName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not move temp pem file %v to destination %v: %v", tempPemFile.Name(), pemFileName, err)
|
||||
}
|
||||
|
|
@ -366,7 +354,7 @@ func AddOrUpdateDHParam(name string, dh []byte, fs file.Filesystem) (string, err
|
|||
|
||||
// GetFakeSSLCert creates a Self Signed Certificate
|
||||
// Based in the code https://golang.org/src/crypto/tls/generate_cert.go
|
||||
func GetFakeSSLCert(fs file.Filesystem) *ingress.SSLCert {
|
||||
func GetFakeSSLCert() *ingress.SSLCert {
|
||||
cert, key := getFakeHostSSLCert("ingress.local")
|
||||
|
||||
sslCert, err := CreateSSLCert(cert, key)
|
||||
|
|
@ -374,11 +362,14 @@ func GetFakeSSLCert(fs file.Filesystem) *ingress.SSLCert {
|
|||
klog.Fatalf("unexpected error creating fake SSL Cert: %v", err)
|
||||
}
|
||||
|
||||
err = StoreSSLCertOnDisk(fs, fakeCertificateName, sslCert)
|
||||
path, err := StoreSSLCertOnDisk(fakeCertificateName, sslCert)
|
||||
if err != nil {
|
||||
klog.Fatalf("unexpected error storing fake SSL Cert: %v", err)
|
||||
}
|
||||
|
||||
sslCert.PemFileName = path
|
||||
sslCert.PemSHA = file.SHA1(path)
|
||||
|
||||
return sslCert
|
||||
}
|
||||
|
||||
|
|
@ -478,7 +469,6 @@ func IsValidHostname(hostname string, commonNames []string) bool {
|
|||
type TLSListener struct {
|
||||
certificatePath string
|
||||
keyPath string
|
||||
fs file.Filesystem
|
||||
certificate *tls.Certificate
|
||||
err error
|
||||
lock sync.Mutex
|
||||
|
|
@ -487,14 +477,9 @@ type TLSListener struct {
|
|||
// NewTLSListener watches changes to th certificate and key paths
|
||||
// and reloads it whenever it changes
|
||||
func NewTLSListener(certificate, key string) *TLSListener {
|
||||
fs, err := file.NewLocalFS()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to instanciate certificate: %v", err))
|
||||
}
|
||||
l := TLSListener{
|
||||
certificatePath: certificate,
|
||||
keyPath: key,
|
||||
fs: fs,
|
||||
lock: sync.Mutex{},
|
||||
}
|
||||
l.load()
|
||||
|
|
@ -519,12 +504,12 @@ func (tl *TLSListener) TLSConfig() *tls.Config {
|
|||
|
||||
func (tl *TLSListener) load() {
|
||||
klog.Infof("loading tls certificate from certificate path %s and key path %s", tl.certificatePath, tl.keyPath)
|
||||
certBytes, err := tl.fs.ReadFile(tl.certificatePath)
|
||||
certBytes, err := ioutil.ReadFile(tl.certificatePath)
|
||||
if err != nil {
|
||||
tl.certificate = nil
|
||||
tl.err = err
|
||||
}
|
||||
keyBytes, err := tl.fs.ReadFile(tl.keyPath)
|
||||
keyBytes, err := ioutil.ReadFile(tl.keyPath)
|
||||
if err != nil {
|
||||
tl.certificate = nil
|
||||
tl.err = err
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import (
|
|||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/http"
|
||||
|
|
@ -38,9 +39,6 @@ import (
|
|||
"time"
|
||||
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
"k8s.io/kubernetes/pkg/util/filesystem"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/file"
|
||||
)
|
||||
|
||||
// generateRSACerts generates a self signed certificate using a self generated ca
|
||||
|
|
@ -71,8 +69,6 @@ func generateRSACerts(host string) (*keyPair, *keyPair, error) {
|
|||
}
|
||||
|
||||
func TestStoreSSLCertOnDisk(t *testing.T) {
|
||||
fs := newFS(t)
|
||||
|
||||
cert, _, err := generateRSACerts("echoheaders")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating SSL certificate: %v", err)
|
||||
|
|
@ -88,13 +84,13 @@ func TestStoreSSLCertOnDisk(t *testing.T) {
|
|||
t.Fatalf("unexpected error creating SSL certificate: %v", err)
|
||||
}
|
||||
|
||||
err = StoreSSLCertOnDisk(fs, name, sslCert)
|
||||
_, err = StoreSSLCertOnDisk(name, sslCert)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error storing SSL certificate: %v", err)
|
||||
}
|
||||
|
||||
if sslCert.PemFileName == "" {
|
||||
t.Fatalf("expected path to pem file but returned empty")
|
||||
if sslCert.PemCertKey == "" {
|
||||
t.Fatalf("expected a pem certificate returned empty")
|
||||
}
|
||||
|
||||
if len(sslCert.CN) == 0 {
|
||||
|
|
@ -107,8 +103,6 @@ func TestStoreSSLCertOnDisk(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCACert(t *testing.T) {
|
||||
fs := newFS(t)
|
||||
|
||||
cert, CA, err := generateRSACerts("echoheaders")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating SSL certificate: %v", err)
|
||||
|
|
@ -125,16 +119,14 @@ func TestCACert(t *testing.T) {
|
|||
t.Fatalf("unexpected error creating SSL certificate: %v", err)
|
||||
}
|
||||
|
||||
err = StoreSSLCertOnDisk(fs, name, sslCert)
|
||||
path, err := StoreSSLCertOnDisk(name, sslCert)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error storing SSL certificate: %v", err)
|
||||
}
|
||||
|
||||
if sslCert.CAFileName != "" {
|
||||
t.Fatalf("expected CA file name to be empty")
|
||||
}
|
||||
sslCert.CAFileName = path
|
||||
|
||||
err = ConfigureCACertWithCertAndKey(fs, name, ca, sslCert)
|
||||
err = ConfigureCACertWithCertAndKey(name, ca, sslCert)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error configuring CA certificate: %v", err)
|
||||
}
|
||||
|
|
@ -145,9 +137,7 @@ func TestCACert(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetFakeSSLCert(t *testing.T) {
|
||||
fs := newFS(t)
|
||||
|
||||
sslCert := GetFakeSSLCert(fs)
|
||||
sslCert := GetFakeSSLCert()
|
||||
|
||||
if len(sslCert.PemCertKey) == 0 {
|
||||
t.Fatalf("expected PemCertKey to not be empty")
|
||||
|
|
@ -171,8 +161,6 @@ func TestGetFakeSSLCert(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConfigureCACert(t *testing.T) {
|
||||
fs := newFS(t)
|
||||
|
||||
cn := "demo-ca"
|
||||
_, ca, err := generateRSACerts(cn)
|
||||
if err != nil {
|
||||
|
|
@ -191,7 +179,7 @@ func TestConfigureCACert(t *testing.T) {
|
|||
t.Fatalf("expected Certificate to be set")
|
||||
}
|
||||
|
||||
err = ConfigureCACert(fs, cn, c, sslCert)
|
||||
err = ConfigureCACert(cn, c, sslCert)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating SSL certificate: %v", err)
|
||||
}
|
||||
|
|
@ -200,14 +188,6 @@ func TestConfigureCACert(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func newFS(t *testing.T) file.Filesystem {
|
||||
fs, err := file.NewFakeFS()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating filesystem: %v", err)
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
func TestCreateSSLCert(t *testing.T) {
|
||||
cert, _, err := generateRSACerts("echoheaders")
|
||||
if err != nil {
|
||||
|
|
@ -360,19 +340,26 @@ func encodeCertPEM(cert *x509.Certificate) []byte {
|
|||
return pem.EncodeToMemory(&block)
|
||||
}
|
||||
|
||||
func fakeCertificate(t *testing.T, fs filesystem.Filesystem) []byte {
|
||||
func newFakeCertificate(t *testing.T) ([]byte, string, string) {
|
||||
cert, key := getFakeHostSSLCert("localhost")
|
||||
fd, err := fs.Create("/key.crt")
|
||||
|
||||
certFile, err := ioutil.TempFile("", "crt-")
|
||||
if err != nil {
|
||||
t.Errorf("failed to write test key: %v", err)
|
||||
}
|
||||
fd.Write(cert)
|
||||
fd, err = fs.Create("/key.key")
|
||||
|
||||
certFile.Write(cert)
|
||||
defer certFile.Close()
|
||||
|
||||
keyFile, err := ioutil.TempFile("", "key-")
|
||||
if err != nil {
|
||||
t.Errorf("failed to write test key: %v", err)
|
||||
}
|
||||
fd.Write(key)
|
||||
return cert
|
||||
|
||||
keyFile.Write(key)
|
||||
defer keyFile.Close()
|
||||
|
||||
return cert, certFile.Name(), keyFile.Name()
|
||||
}
|
||||
|
||||
func dialTestServer(port string, rootCertificates ...[]byte) error {
|
||||
|
|
@ -386,6 +373,7 @@ func dialTestServer(port string, rootCertificates ...[]byte) error {
|
|||
resp, err := tls.Dial("tcp", "localhost:"+port, &tls.Config{
|
||||
RootCAs: roots,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -396,13 +384,11 @@ func dialTestServer(port string, rootCertificates ...[]byte) error {
|
|||
}
|
||||
|
||||
func TestTLSKeyReloader(t *testing.T) {
|
||||
fs := filesystem.NewFakeFs()
|
||||
cert := fakeCertificate(t, fs)
|
||||
cert, certFile, keyFile := newFakeCertificate(t)
|
||||
|
||||
watcher := TLSListener{
|
||||
certificatePath: "/key.crt",
|
||||
keyPath: "/key.key",
|
||||
fs: fs,
|
||||
certificatePath: certFile,
|
||||
keyPath: keyFile,
|
||||
lock: sync.Mutex{},
|
||||
}
|
||||
watcher.load()
|
||||
|
|
@ -427,19 +413,22 @@ func TestTLSKeyReloader(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("with a new certificate", func(t *testing.T) {
|
||||
newCert := fakeCertificate(t, fs)
|
||||
cert, certFile, keyFile = newFakeCertificate(t)
|
||||
t.Run("when the certificate is not reloaded", func(t *testing.T) {
|
||||
if dialTestServer(port, newCert) == nil {
|
||||
if dialTestServer(port, cert) == nil {
|
||||
t.Errorf("TLS dial should fail")
|
||||
}
|
||||
})
|
||||
// simulate watch.NewFileWatcher to call the load function
|
||||
watcher.load()
|
||||
t.Run("when the certificate is reloaded", func(t *testing.T) {
|
||||
if err := dialTestServer(port, newCert); err != nil {
|
||||
t.Errorf("TLS dial should succeed, got error: %v", err)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
//TODO: fix
|
||||
/*
|
||||
// simulate watch.NewFileWatcher to call the load function
|
||||
watcher.load()
|
||||
t.Run("when the certificate is reloaded", func(t *testing.T) {
|
||||
if err := dialTestServer(port, cert); err != nil {
|
||||
t.Errorf("TLS dial should succeed, got error: %v", err)
|
||||
}
|
||||
})
|
||||
*/
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,12 +23,17 @@ import (
|
|||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tv42/httpunix"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
// TemplatePath path of the NGINX template
|
||||
var TemplatePath = "/etc/nginx/template/nginx.tmpl"
|
||||
|
||||
// PID defines the location of the pid file used by NGINX
|
||||
var PID = "/tmp/nginx.pid"
|
||||
|
||||
|
|
@ -50,11 +55,17 @@ var StreamSocket = "/tmp/ingress-stream.sock"
|
|||
|
||||
var statusLocation = "nginx-status"
|
||||
|
||||
var httpClient *http.Client
|
||||
|
||||
func init() {
|
||||
httpClient = buildUnixSocketClient(HealthCheckTimeout)
|
||||
}
|
||||
|
||||
// NewGetStatusRequest creates a new GET request to the internal NGINX status server
|
||||
func NewGetStatusRequest(path string) (int, []byte, error) {
|
||||
url := fmt.Sprintf("http+unix://%v%v", statusLocation, path)
|
||||
url := fmt.Sprintf("%v://%v%v", httpunix.Scheme, statusLocation, path)
|
||||
|
||||
res, err := buildUnixSocketClient(HealthCheckTimeout).Get(url)
|
||||
res, err := httpClient.Get(url)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
|
@ -70,14 +81,14 @@ func NewGetStatusRequest(path string) (int, []byte, error) {
|
|||
|
||||
// NewPostStatusRequest creates a new POST request to the internal NGINX status server
|
||||
func NewPostStatusRequest(path, contentType string, data interface{}) (int, []byte, error) {
|
||||
url := fmt.Sprintf("http+unix://%v%v", statusLocation, path)
|
||||
url := fmt.Sprintf("%v://%v%v", httpunix.Scheme, statusLocation, path)
|
||||
|
||||
buf, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
res, err := buildUnixSocketClient(HealthCheckTimeout).Post(url, contentType, bytes.NewReader(buf))
|
||||
res, err := httpClient.Post(url, contentType, bytes.NewReader(buf))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
|
@ -112,11 +123,11 @@ func GetServerBlock(conf string, host string) (string, error) {
|
|||
|
||||
// ReadNginxConf reads the nginx configuration file into a string
|
||||
func ReadNginxConf() (string, error) {
|
||||
return ReadFileToString("/etc/nginx/nginx.conf")
|
||||
return readFileToString("/etc/nginx/nginx.conf")
|
||||
}
|
||||
|
||||
// ReadFileToString reads any file into a string
|
||||
func ReadFileToString(path string) (string, error) {
|
||||
// readFileToString reads any file into a string
|
||||
func readFileToString(path string) (string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
@ -142,3 +153,21 @@ func buildUnixSocketClient(timeout time.Duration) *http.Client {
|
|||
Transport: u,
|
||||
}
|
||||
}
|
||||
|
||||
// Version return details about NGINX
|
||||
func Version() string {
|
||||
flag := "-v"
|
||||
|
||||
if klog.V(2) {
|
||||
flag = "-V"
|
||||
}
|
||||
|
||||
cmd := exec.Command("nginx", flag)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
klog.Errorf("unexpected error obtaining NGINX version: %v", err)
|
||||
return "N/A"
|
||||
}
|
||||
|
||||
return string(out)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue