Add virtual filesystem for testing

This commit is contained in:
Manuel de Brito Fontes 2017-11-20 19:06:00 -03:00
parent 6e3b3f83c7
commit 926b029874
31 changed files with 1305 additions and 471 deletions

View file

@ -40,11 +40,12 @@ jobs:
script:
- go get github.com/mattn/goveralls
- go get github.com/modocache/gover
- if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover;
fi
- if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover;fi
- if ! go get github.com/jteeuwen/go-bindata/...; then github.com/jteeuwen/go-bindata/...;fi
- make cover
- stage: e2e
before_script:
- if ! go get github.com/jteeuwen/go-bindata/...; then github.com/jteeuwen/go-bindata/...;fi
- make e2e-image
- test/e2e/up.sh
- test/e2e/wait-for-nginx.sh

View file

@ -133,8 +133,12 @@ endif
clean:
$(DOCKER) rmi -f $(MULTI_ARCH_IMG):$(TAG) || true
.PHONE: gobindata
gobindata:
go-bindata -o internal/file/bindata.go -prefix="rootfs" -pkg=file -ignore=Dockerfile -ignore=".DS_Store" rootfs/...
.PHONY: build
build: clean
build: clean gobindata
CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build -a -installsuffix cgo \
-ldflags "-s -w -X ${PKG}/version.RELEASE=${TAG} -X ${PKG}/version.COMMIT=${COMMIT} -X ${PKG}/version.REPO=${REPO_INFO}" \
-o ${TEMP_DIR}/rootfs/nginx-ingress-controller ${PKG}/cmd/nginx
@ -150,7 +154,7 @@ lint:
@go list -f '{{if len .TestGoFiles}}"golint {{.Dir}}/..."{{end}}' $(shell go list ${PKG}/... | grep -v vendor | grep -v '/test/e2e') | xargs -L 1 sh -c
.PHONY: test
test: fmt lint vet
test: fmt lint vet gobindata
@echo "+ $@"
@go test -v -race -tags "$(BUILDTAGS) cgo" $(shell go list ${PKG}/... | grep -v vendor | grep -v '/test/e2e')
@ -165,7 +169,7 @@ e2e-test:
@KUBECONFIG=${HOME}/.kube/config INGRESSNGINXCONFIG=${HOME}/.kube/config ./e2e-tests
.PHONY: cover
cover:
cover: gobindata
@echo "+ $@"
@go list -f '{{if len .TestGoFiles}}"go test -coverprofile={{.Dir}}/.coverprofile {{.ImportPath}}"{{end}}' $(shell go list ${PKG}/... | grep -v vendor | grep -v '/test/e2e') | xargs -L 1 sh -c
gover

View file

@ -39,7 +39,7 @@ import (
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress/controller"
"k8s.io/ingress-nginx/internal/k8s"
"k8s.io/ingress-nginx/internal/net/ssl"
@ -58,6 +58,11 @@ func main() {
glog.Fatal(err)
}
fs, err := file.NewLocalFS()
if err != nil {
glog.Fatal(err)
}
kubeClient, err := createApiserverClient(conf.APIServerHost, conf.KubeConfigFile)
if err != nil {
handleFatalInitError(err)
@ -111,15 +116,9 @@ func main() {
glog.Fatalf("resync period (%vs) is too low", conf.ResyncPeriod.Seconds())
}
// create directory that will contains the SSL Certificates
err = os.MkdirAll(ingress.DefaultSSLDirectory, 0655)
if err != nil {
glog.Errorf("Failed to mkdir SSL directory: %v", err)
}
// create the default SSL certificate (dummy)
defCert, defKey := ssl.GetFakeSSLCert()
c, err := ssl.AddOrUpdateCertAndKey(fakeCertificate, defCert, defKey, []byte{})
c, err := ssl.AddOrUpdateCertAndKey(fakeCertificate, defCert, defKey, []byte{}, fs)
if err != nil {
glog.Fatalf("Error generating self signed certificate: %v", err)
}
@ -129,7 +128,7 @@ func main() {
conf.Client = kubeClient
ngx := controller.NewNGINXController(conf)
ngx := controller.NewNGINXController(conf, fs)
if conf.EnableSSLPassthrough {
setupSSLProxy(conf.ListenPorts.HTTPS, conf.ListenPorts.SSLProxy, ngx)

View file

@ -23,6 +23,7 @@ import (
"testing"
"time"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress/controller"
)
@ -70,7 +71,12 @@ func TestHandleSigterm(t *testing.T) {
}
conf.Client = cli
ngx := controller.NewNGINXController(conf)
fs, err := file.NewFakeFS()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
ngx := controller.NewNGINXController(conf, fs)
go handleSigterm(ngx, func(code int) {
if code != 1 {

289
internal/file/bindata.go Normal file

File diff suppressed because one or more lines are too long

144
internal/file/filesystem.go Normal file
View file

@ -0,0 +1,144 @@
package file
import (
"os"
"path/filepath"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/util/filesystem"
)
// 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{}
err := initialize(false, fs)
if err != nil {
return nil, err
}
return fs, nil
}
// NewFakeFS creates an in-memory filesytem with all the required
// paths used by the ingress controller.
// This allows running test without polluting the local machine.
func NewFakeFS() (Filesystem, error) {
fs := filesystem.NewFakeFs()
err := initialize(true, fs)
if err != nil {
return nil, err
}
return fs, nil
}
// initialize creates the required directory structure and when
// runs as virtual filesystem it copies the local files to it
func initialize(isVirtual bool, fs Filesystem) error {
for _, directory := range directories {
err := fs.MkdirAll(directory, 0655)
if err != nil {
return err
}
}
if !isVirtual {
return nil
}
for _, file := range files {
f, err := fs.Create(file)
if err != nil {
return err
}
_, err = f.Write([]byte(""))
if err != nil {
return err
}
err = f.Close()
if err != nil {
return err
}
}
err := fs.MkdirAll("/proc", 0655)
if err != nil {
return err
}
glog.Info("Restoring generated (go-bindata) assets in virtual filesystem...")
for _, assetName := range AssetNames() {
err := restoreAsset("/", assetName, fs)
if err != nil {
return err
}
}
return nil
}
// restoreAsset restores an asset under the given directory
func restoreAsset(dir, name string, fs Filesystem) error {
data, err := Asset(name)
if err != nil {
return err
}
info, err := AssetInfo(name)
if err != nil {
return err
}
err = fs.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
if err != nil {
return err
}
f, err := fs.Create(_filePath(dir, name))
if err != nil {
return err
}
_, err = f.Write(data)
if err != nil {
return err
}
err = f.Close()
if err != nil {
return err
}
//Missing info.Mode()
err = fs.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
if err != nil {
return err
}
return nil
}
// restoreAssets restores an asset under the given directory recursively
func restoreAssets(dir, name string, fs Filesystem) error {
children, err := AssetDir(name)
// File
if err != nil {
return restoreAsset(dir, name, fs)
}
// Dir
for _, child := range children {
err = restoreAssets(dir, filepath.Join(name, child), fs)
if err != nil {
return err
}
}
return nil
}

View file

@ -0,0 +1,26 @@
package file
const (
// AuthDirectory default directory used to store files
// to authenticate request
AuthDirectory = "/etc/ingress-controller/auth"
// DefaultSSLDirectory defines the location where the SSL certificates will be generated
// This directory contains all the SSL certificates that are specified in Ingress rules.
// The name of each file is <namespace>-<secret name>.pem. The content is the concatenated
// certificate and key.
DefaultSSLDirectory = "/ingress-controller/ssl"
)
var (
directories = []string{
"/etc/nginx/template",
"/run",
DefaultSSLDirectory,
AuthDirectory,
}
files = []string{
"/run/nginx.pid",
}
)

View file

@ -23,6 +23,7 @@ import (
extensions "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress/annotations/alias"
"k8s.io/ingress-nginx/internal/ingress/annotations/auth"
"k8s.io/ingress-nginx/internal/ingress/annotations/authreq"
@ -89,11 +90,11 @@ type Extractor struct {
}
// NewAnnotationExtractor creates a new annotations extractor
func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
func NewAnnotationExtractor(cfg resolver.Resolver, fs file.Filesystem) Extractor {
return Extractor{
map[string]parser.IngressAnnotation{
"Alias": alias.NewParser(cfg),
"BasicDigestAuth": auth.NewParser(auth.AuthDirectory, cfg),
"BasicDigestAuth": auth.NewParser(file.AuthDirectory, fs, cfg),
"CertificateAuth": authtls.NewParser(cfg),
"ClientBodyBufferSize": clientbodybuffersize.NewParser(),
"ConfigurationSnippet": snippet.NewParser(),

View file

@ -24,6 +24,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/defaults"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -113,7 +114,8 @@ func buildIngress() *extensions.Ingress {
}
func TestSecureUpstream(t *testing.T) {
ec := NewAnnotationExtractor(mockCfg{})
fs := newFS(t)
ec := NewAnnotationExtractor(mockCfg{}, fs)
ing := buildIngress()
fooAnns := []struct {
@ -137,6 +139,7 @@ func TestSecureUpstream(t *testing.T) {
}
func TestSecureVerifyCACert(t *testing.T) {
fs := newFS(t)
ec := NewAnnotationExtractor(mockCfg{
MockSecrets: map[string]*apiv1.Secret{
"default/secure-verify-ca": {
@ -145,7 +148,7 @@ func TestSecureVerifyCACert(t *testing.T) {
},
},
},
})
}, fs)
anns := []struct {
it int
@ -172,7 +175,8 @@ func TestSecureVerifyCACert(t *testing.T) {
}
func TestHealthCheck(t *testing.T) {
ec := NewAnnotationExtractor(mockCfg{})
fs := newFS(t)
ec := NewAnnotationExtractor(mockCfg{}, fs)
ing := buildIngress()
fooAnns := []struct {
@ -202,7 +206,8 @@ func TestHealthCheck(t *testing.T) {
}
func TestSSLPassthrough(t *testing.T) {
ec := NewAnnotationExtractor(mockCfg{})
fs := newFS(t)
ec := NewAnnotationExtractor(mockCfg{}, fs)
ing := buildIngress()
fooAnns := []struct {
@ -226,7 +231,8 @@ func TestSSLPassthrough(t *testing.T) {
}
func TestUpstreamHashBy(t *testing.T) {
ec := NewAnnotationExtractor(mockCfg{})
fs := newFS(t)
ec := NewAnnotationExtractor(mockCfg{}, fs)
ing := buildIngress()
fooAnns := []struct {
@ -250,7 +256,8 @@ func TestUpstreamHashBy(t *testing.T) {
}
func TestAffinitySession(t *testing.T) {
ec := NewAnnotationExtractor(mockCfg{})
fs := newFS(t)
ec := NewAnnotationExtractor(mockCfg{}, fs)
ing := buildIngress()
fooAnns := []struct {
@ -282,7 +289,8 @@ func TestAffinitySession(t *testing.T) {
}
func TestCors(t *testing.T) {
ec := NewAnnotationExtractor(mockCfg{})
fs := newFS(t)
ec := NewAnnotationExtractor(mockCfg{}, fs)
ing := buildIngress()
fooAnns := []struct {
@ -372,3 +380,11 @@ func TestMergeLocationAnnotations(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
}

View file

@ -18,9 +18,6 @@ package auth
import (
"fmt"
"io/ioutil"
"os"
"path"
"regexp"
"github.com/pkg/errors"
@ -35,9 +32,6 @@ import (
var (
authTypeRegex = regexp.MustCompile(`basic|digest`)
// AuthDirectory default directory used to store files
// to authenticate request
AuthDirectory = "/etc/ingress-controller/auth"
)
// Config returns authentication configuration for an Ingress rule
@ -78,23 +72,13 @@ func (bd1 *Config) Equal(bd2 *Config) bool {
type auth struct {
r resolver.Resolver
fs file.Filesystem
authDirectory string
}
// NewParser creates a new authentication annotation parser
func NewParser(authDirectory string, r resolver.Resolver) parser.IngressAnnotation {
os.MkdirAll(authDirectory, 0755)
currPath := authDirectory
for currPath != "/" {
currPath = path.Dir(currPath)
err := os.Chmod(currPath, 0755)
if err != nil {
break
}
}
return auth{r, authDirectory}
func NewParser(authDirectory string, fs file.Filesystem, r resolver.Resolver) parser.IngressAnnotation {
return auth{r, fs, authDirectory}
}
// Parse parses the annotations contained in the ingress
@ -129,7 +113,7 @@ func (a auth) Parse(ing *extensions.Ingress) (interface{}, error) {
realm, _ := parser.GetStringAnnotation("auth-realm", ing)
passFile := fmt.Sprintf("%v/%v-%v.passwd", a.authDirectory, ing.GetNamespace(), ing.GetName())
err = dumpSecret(passFile, secret)
err = dumpSecret(passFile, secret, a.fs)
if err != nil {
return nil, err
}
@ -145,7 +129,7 @@ func (a auth) Parse(ing *extensions.Ingress) (interface{}, error) {
// dumpSecret dumps the content of a secret into a file
// in the expected format for the specified authorization
func dumpSecret(filename string, secret *api.Secret) error {
func dumpSecret(filename string, secret *api.Secret, fs file.Filesystem) error {
val, ok := secret.Data["auth"]
if !ok {
return ing_errors.LocationDenied{
@ -153,13 +137,26 @@ func dumpSecret(filename string, secret *api.Secret) error {
}
}
// TODO: check permissions required
err := ioutil.WriteFile(filename, val, 0777)
f, err := fs.Create(filename)
if err != nil {
return ing_errors.LocationDenied{
Reason: errors.Wrap(err, "unexpected error creating password file"),
}
}
_, err = f.Write(val)
if err != nil {
return ing_errors.LocationDenied{
Reason: errors.Wrap(err, "unexpected error writing password file"),
}
}
err = f.Close()
if err != nil {
return ing_errors.LocationDenied{
Reason: errors.Wrap(err, "unexpected error closing password file"),
}
}
return nil
}

View file

@ -29,6 +29,7 @@ import (
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
@ -87,10 +88,11 @@ func (m mockSecret) GetSecret(name string) (*api.Secret, error) {
}
func TestIngressWithoutAuth(t *testing.T) {
fs := newFS(t)
ing := buildIngress()
_, dir, _ := dummySecretContent(t)
defer os.RemoveAll(dir)
_, err := NewParser(dir, &mockSecret{}).Parse(ing)
_, err := NewParser(dir, fs, &mockSecret{}).Parse(ing)
if err == nil {
t.Error("Expected error with ingress without annotations")
}
@ -108,7 +110,9 @@ func TestIngressAuth(t *testing.T) {
_, dir, _ := dummySecretContent(t)
defer os.RemoveAll(dir)
i, err := NewParser(dir, &mockSecret{}).Parse(ing)
fs := newFS(t)
i, err := NewParser(dir, fs, &mockSecret{}).Parse(ing)
if err != nil {
t.Errorf("Uxpected error with ingress: %v", err)
}
@ -139,7 +143,9 @@ func TestIngressAuthWithoutSecret(t *testing.T) {
_, dir, _ := dummySecretContent(t)
defer os.RemoveAll(dir)
_, err := NewParser(dir, mockSecret{}).Parse(ing)
fs := newFS(t)
_, err := NewParser(dir, fs, mockSecret{}).Parse(ing)
if err == nil {
t.Errorf("expected an error with invalid secret name")
}
@ -167,14 +173,24 @@ func TestDumpSecret(t *testing.T) {
sd := s.Data
s.Data = nil
err := dumpSecret(tmpfile, s)
fs := newFS(t)
err := dumpSecret(tmpfile, s, fs)
if err == nil {
t.Errorf("Expected error with secret without auth")
}
s.Data = sd
err = dumpSecret(tmpfile, s)
err = dumpSecret(tmpfile, s, fs)
if err != nil {
t.Errorf("Unexpected error creating htpasswd file %v: %v", tmpfile, err)
}
}
func newFS(t *testing.T) file.Filesystem {
fs, err := file.NewFakeFS()
if err != nil {
t.Fatalf("unexpected error creating filesystem: %v", err)
}
return fs
}

View file

@ -25,8 +25,8 @@ 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"
)
@ -41,8 +41,10 @@ func TestNginxCheck(t *testing.T) {
// port to be used in the check
p := server.Listener.Addr().(*net.TCPAddr).Port
// mock filesystem
fs := filesystem.NewFakeFs()
fs, err := file.NewFakeFS()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
n := &NGINXController{
cfg: &Configuration{
@ -59,13 +61,6 @@ func TestNginxCheck(t *testing.T) {
}
})
// create required files
fs.MkdirAll("/run", 0655)
pidFile, err := fs.Create("/run/nginx.pid")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
t.Run("no process", func(t *testing.T) {
if err := callHealthz(true, mux); err == nil {
t.Errorf("expected an error but none returned")
@ -81,18 +76,9 @@ func TestNginxCheck(t *testing.T) {
cmd.Wait()
}()
pidFile.Write([]byte(fmt.Sprintf("%v", pid)))
pidFile.Close()
healthz.InstallHandler(mux, n)
t.Run("valid request", func(t *testing.T) {
if err := callHealthz(false, mux); err != nil {
t.Error(err)
}
})
pidFile, err = fs.Create("/run/nginx.pid")
pidFile, err := fs.Create("/run/nginx.pid")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

View file

@ -23,8 +23,6 @@ import (
"reflect"
"sort"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/golang/glog"
@ -40,7 +38,6 @@ import (
"k8s.io/ingress-nginx/internal/ingress/annotations/healthcheck"
"k8s.io/ingress-nginx/internal/ingress/annotations/proxy"
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
"k8s.io/ingress-nginx/internal/k8s"
)
const (
@ -102,16 +99,6 @@ type Configuration struct {
FakeCertificateSHA string
}
// GetPublishService returns the configured service used to set ingress status
func (n NGINXController) GetPublishService() *apiv1.Service {
s, err := n.storeLister.GetService(n.cfg.PublishService)
if err != nil {
return nil
}
return s
}
// sync collects all the pieces required to assemble the configuration file and
// then sends the content to the backend (OnUpdate) receiving the populated
// template as response reloading the backend if is required.
@ -185,131 +172,6 @@ func (n *NGINXController) syncIngress(item interface{}) error {
return nil
}
func (n *NGINXController) getStreamServices(configmapName string, proto apiv1.Protocol) []ingress.L4Service {
glog.V(3).Infof("obtaining information about stream services of type %v located in configmap %v", proto, configmapName)
if configmapName == "" {
// no configmap configured
return []ingress.L4Service{}
}
_, _, err := k8s.ParseNameNS(configmapName)
if err != nil {
glog.Errorf("unexpected error reading configmap %v: %v", configmapName, err)
return []ingress.L4Service{}
}
configmap, err := n.storeLister.GetConfigMap(configmapName)
if err != nil {
glog.Errorf("unexpected error reading configmap %v: %v", configmapName, err)
return []ingress.L4Service{}
}
var svcs []ingress.L4Service
var svcProxyProtocol ingress.ProxyProtocol
// k -> port to expose
// v -> <namespace>/<service name>:<port from service to be used>
for k, v := range configmap.Data {
externalPort, err := strconv.Atoi(k)
if err != nil {
glog.Warningf("%v is not valid as a TCP/UDP port", k)
continue
}
rp := []int{
n.cfg.ListenPorts.HTTP,
n.cfg.ListenPorts.HTTPS,
n.cfg.ListenPorts.SSLProxy,
n.cfg.ListenPorts.Status,
n.cfg.ListenPorts.Health,
n.cfg.ListenPorts.Default,
}
if intInSlice(externalPort, rp) {
glog.Warningf("port %v cannot be used for TCP or UDP services. It is reserved for the Ingress controller", k)
continue
}
nsSvcPort := strings.Split(v, ":")
if len(nsSvcPort) < 2 {
glog.Warningf("invalid format (namespace/name:port:[PROXY]:[PROXY]) '%v'", k)
continue
}
nsName := nsSvcPort[0]
svcPort := nsSvcPort[1]
svcProxyProtocol.Decode = false
svcProxyProtocol.Encode = false
// Proxy protocol is possible if the service is TCP
if len(nsSvcPort) >= 3 && proto == apiv1.ProtocolTCP {
if len(nsSvcPort) >= 3 && strings.ToUpper(nsSvcPort[2]) == "PROXY" {
svcProxyProtocol.Decode = true
}
if len(nsSvcPort) == 4 && strings.ToUpper(nsSvcPort[3]) == "PROXY" {
svcProxyProtocol.Encode = true
}
}
svcNs, svcName, err := k8s.ParseNameNS(nsName)
if err != nil {
glog.Warningf("%v", err)
continue
}
svc, err := n.storeLister.GetService(nsName)
if err != nil {
glog.Warningf("error getting service %v: %v", nsName, err)
continue
}
var endps []ingress.Endpoint
targetPort, err := strconv.Atoi(svcPort)
if err != nil {
glog.V(3).Infof("searching service %v endpoints using the name '%v'", svcNs, svcName, svcPort)
for _, sp := range svc.Spec.Ports {
if sp.Name == svcPort {
if sp.Protocol == proto {
endps = n.getEndpoints(svc, &sp, proto, &healthcheck.Config{})
break
}
}
}
} else {
// we need to use the TargetPort (where the endpoints are running)
glog.V(3).Infof("searching service %v/%v endpoints using the target port '%v'", svcNs, svcName, targetPort)
for _, sp := range svc.Spec.Ports {
if sp.Port == int32(targetPort) {
if sp.Protocol == proto {
endps = n.getEndpoints(svc, &sp, proto, &healthcheck.Config{})
break
}
}
}
}
// stream services cannot contain empty upstreams and there is no
// default backend equivalent
if len(endps) == 0 {
glog.Warningf("service %v/%v does not have any active endpoints for port %v and protocol %v", svcNs, svcName, svcPort, proto)
continue
}
svcs = append(svcs, ingress.L4Service{
Port: externalPort,
Backend: ingress.L4Backend{
Name: svcName,
Namespace: svcNs,
Port: intstr.FromString(svcPort),
Protocol: proto,
ProxyProtocol: svcProxyProtocol,
},
Endpoints: endps,
})
}
return svcs
}
// getDefaultUpstream returns an upstream associated with the
// default backend service. In case of error retrieving information
// configure the upstream to return http code 503.
@ -664,7 +526,9 @@ func (n *NGINXController) createUpstreams(data []*extensions.Ingress, du *ingres
return upstreams
}
func (n *NGINXController) getServiceClusterEndpoint(svcKey string, backend *extensions.IngressBackend) (endpoint ingress.Endpoint, err error) {
func (n *NGINXController) getServiceClusterEndpoint(svcKey string,
backend *extensions.IngressBackend) (endpoint ingress.Endpoint, err error) {
svc, err := n.storeLister.GetService(svcKey)
if err != nil {
return endpoint, err
@ -1067,18 +931,3 @@ func (n *NGINXController) getEndpoints(
glog.V(3).Infof("endpoints found: %v", upsServers)
return upsServers
}
func (n *NGINXController) isForceReload() bool {
return atomic.LoadInt32(&n.forceReload) != 0
}
// SetForceReload sets if the ingress controller should be reloaded or not
func (n *NGINXController) SetForceReload(shouldReload bool) {
if shouldReload {
atomic.StoreInt32(&n.forceReload, 1)
n.syncQueue.Enqueue(&extensions.Ingress{})
return
}
atomic.StoreInt32(&n.forceReload, 0)
}

View file

@ -39,6 +39,7 @@ import (
"k8s.io/client-go/util/flowcontrol"
"k8s.io/kubernetes/pkg/util/filesystem"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations/class"
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
@ -50,6 +51,7 @@ import (
"k8s.io/ingress-nginx/internal/net/dns"
"k8s.io/ingress-nginx/internal/net/ssl"
"k8s.io/ingress-nginx/internal/task"
"k8s.io/ingress-nginx/internal/watch"
)
type statusModule string
@ -70,7 +72,7 @@ var (
// NewNGINXController creates a new NGINX Ingress controller.
// If the environment variable NGINX_BINARY exists it will be used
// as source for nginx commands
func NewNGINXController(config *Configuration) *NGINXController {
func NewNGINXController(config *Configuration, fs file.Filesystem) *NGINXController {
ngx := os.Getenv("NGINX_BINARY")
if ngx == "" {
ngx = nginxBinary
@ -98,7 +100,7 @@ func NewNGINXController(config *Configuration) *NGINXController {
stopLock: &sync.Mutex{},
fileSystem: filesystem.DefaultFs{},
fileSystem: fs,
}
n.stats = newStatsCollector(config.Namespace, class.IngressClass, n.binary, n.cfg.ListenPorts.Status)
@ -128,6 +130,7 @@ func NewNGINXController(config *Configuration) *NGINXController {
n.cfg.UDPConfigMapName,
n.cfg.ResyncPeriod,
n.cfg.Client,
n.fileSystem,
n.updateCh,
)
@ -144,9 +147,8 @@ func NewNGINXController(config *Configuration) *NGINXController {
glog.Warning("Update of ingress status is disabled (flag --update-status=false was specified)")
}
var onChange func()
onChange = func() {
template, err := ngx_template.NewTemplate(tmplPath, onChange)
onChange := func() {
template, err := ngx_template.NewTemplate(tmplPath, n.fileSystem)
if err != nil {
// this error is different from the rest because it must be clear why nginx is not working
glog.Errorf(`
@ -163,7 +165,17 @@ Error loading new template : %v
n.SetForceReload(true)
}
ngxTpl, err := ngx_template.NewTemplate(tmplPath, onChange)
// TODO: refactor
if _, ok := fs.(filesystem.DefaultFs); !ok {
watch.NewDummyFileWatcher(tmplPath, onChange)
} else {
_, err = watch.NewFileWatcher(tmplPath, onChange)
if err != nil {
glog.Fatalf("unexpected error watching template %v: %v", tmplPath, err)
}
}
ngxTpl, err := ngx_template.NewTemplate(tmplPath, n.fileSystem)
if err != nil {
glog.Fatalf("invalid NGINX template: %v", err)
}
@ -563,7 +575,7 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
dh, ok := secret.Data["dhparam.pem"]
if ok {
pemFileName, err := ssl.AddOrUpdateDHParam(nsSecName, dh)
pemFileName, err := ssl.AddOrUpdateDHParam(nsSecName, dh, n.fileSystem)
if err != nil {
glog.Warningf("unexpected error adding or updating dhparam %v file: %v", nsSecName, err)
} else {
@ -584,6 +596,8 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
cfg.EnableBrotli = false
}
svc, _ := n.storeLister.GetService(n.cfg.PublishService)
tc := ngx_config.TemplateConfig{
ProxySetHeaders: setHeaders,
AddHeaders: addHeaders,
@ -601,7 +615,7 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
RedirectServers: redirectServers,
IsSSLPassthroughEnabled: n.isSSLPassthroughEnabled,
ListenPorts: n.cfg.ListenPorts,
PublishService: n.GetPublishService(),
PublishService: svc,
}
content, err := n.t.Write(tc)

View file

@ -0,0 +1,22 @@
package controller
import (
"sync/atomic"
extensions "k8s.io/api/extensions/v1beta1"
)
func (n *NGINXController) isForceReload() bool {
return atomic.LoadInt32(&n.forceReload) != 0
}
// SetForceReload sets if the ingress controller should be reloaded or not
func (n *NGINXController) SetForceReload(shouldReload bool) {
if shouldReload {
atomic.StoreInt32(&n.forceReload, 1)
n.syncQueue.Enqueue(&extensions.Ingress{})
return
}
atomic.StoreInt32(&n.forceReload, 0)
}

View file

@ -0,0 +1,140 @@
package controller
import (
"strconv"
"strings"
"github.com/golang/glog"
apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations/healthcheck"
"k8s.io/ingress-nginx/internal/k8s"
)
func (n *NGINXController) getStreamServices(configmapName string, proto apiv1.Protocol) []ingress.L4Service {
glog.V(3).Infof("obtaining information about stream services of type %v located in configmap %v", proto, configmapName)
if configmapName == "" {
// no configmap configured
return []ingress.L4Service{}
}
_, _, err := k8s.ParseNameNS(configmapName)
if err != nil {
glog.Errorf("unexpected error reading configmap %v: %v", configmapName, err)
return []ingress.L4Service{}
}
configmap, err := n.storeLister.GetConfigMap(configmapName)
if err != nil {
glog.Errorf("unexpected error reading configmap %v: %v", configmapName, err)
return []ingress.L4Service{}
}
var svcs []ingress.L4Service
var svcProxyProtocol ingress.ProxyProtocol
// k -> port to expose
// v -> <namespace>/<service name>:<port from service to be used>
for k, v := range configmap.Data {
externalPort, err := strconv.Atoi(k)
if err != nil {
glog.Warningf("%v is not valid as a TCP/UDP port", k)
continue
}
rp := []int{
n.cfg.ListenPorts.HTTP,
n.cfg.ListenPorts.HTTPS,
n.cfg.ListenPorts.SSLProxy,
n.cfg.ListenPorts.Status,
n.cfg.ListenPorts.Health,
n.cfg.ListenPorts.Default,
}
if intInSlice(externalPort, rp) {
glog.Warningf("port %v cannot be used for TCP or UDP services. It is reserved for the Ingress controller", k)
continue
}
nsSvcPort := strings.Split(v, ":")
if len(nsSvcPort) < 2 {
glog.Warningf("invalid format (namespace/name:port:[PROXY]:[PROXY]) '%v'", k)
continue
}
nsName := nsSvcPort[0]
svcPort := nsSvcPort[1]
svcProxyProtocol.Decode = false
svcProxyProtocol.Encode = false
// Proxy protocol is possible if the service is TCP
if len(nsSvcPort) >= 3 && proto == apiv1.ProtocolTCP {
if len(nsSvcPort) >= 3 && strings.ToUpper(nsSvcPort[2]) == "PROXY" {
svcProxyProtocol.Decode = true
}
if len(nsSvcPort) == 4 && strings.ToUpper(nsSvcPort[3]) == "PROXY" {
svcProxyProtocol.Encode = true
}
}
svcNs, svcName, err := k8s.ParseNameNS(nsName)
if err != nil {
glog.Warningf("%v", err)
continue
}
svc, err := n.storeLister.GetService(nsName)
if err != nil {
glog.Warningf("error getting service %v: %v", nsName, err)
continue
}
var endps []ingress.Endpoint
targetPort, err := strconv.Atoi(svcPort)
if err != nil {
glog.V(3).Infof("searching service %v endpoints using the name '%v'", svcNs, svcName, svcPort)
for _, sp := range svc.Spec.Ports {
if sp.Name == svcPort {
if sp.Protocol == proto {
endps = n.getEndpoints(svc, &sp, proto, &healthcheck.Config{})
break
}
}
}
} else {
// we need to use the TargetPort (where the endpoints are running)
glog.V(3).Infof("searching service %v/%v endpoints using the target port '%v'", svcNs, svcName, targetPort)
for _, sp := range svc.Spec.Ports {
if sp.Port == int32(targetPort) {
if sp.Protocol == proto {
endps = n.getEndpoints(svc, &sp, proto, &healthcheck.Config{})
break
}
}
}
}
// stream services cannot contain empty upstreams and there is no
// default backend equivalent
if len(endps) == 0 {
glog.Warningf("service %v/%v does not have any active endpoints for port %v and protocol %v", svcNs, svcName, svcPort, proto)
continue
}
svcs = append(svcs, ingress.L4Service{
Port: externalPort,
Backend: ingress.L4Backend{
Name: svcName,
Namespace: svcNs,
Port: intstr.FromString(svcPort),
Protocol: proto,
ProxyProtocol: svcProxyProtocol,
},
Endpoints: endps,
})
}
return svcs
}

View file

@ -29,16 +29,17 @@ import (
text_template "text/template"
"github.com/golang/glog"
"github.com/pkg/errors"
"github.com/pborman/uuid"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress"
"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/ingress-nginx/internal/watch"
)
const (
@ -50,32 +51,33 @@ const (
// Template ...
type Template struct {
tmpl *text_template.Template
fw watch.FileWatcher
bp *BufferPool
//fw watch.FileWatcher
bp *BufferPool
}
//NewTemplate returns a new Template instance or an
//error if the specified template file contains errors
func NewTemplate(file string, onChange func()) (*Template, error) {
tmpl, err := text_template.New("nginx.tmpl").Funcs(funcMap).ParseFiles(file)
func NewTemplate(file string, fs file.Filesystem) (*Template, error) {
data, err := fs.ReadFile(file)
if err != nil {
return nil, err
return nil, errors.Wrapf(err, "unexpected error reading template %v", file)
}
fw, err := watch.NewFileWatcher(file, onChange)
tmpl, err := text_template.New("nginx.tmpl").Funcs(funcMap).Parse(string(data))
if err != nil {
return nil, err
}
return &Template{
tmpl: tmpl,
fw: fw,
bp: NewBufferPool(defBufferSize),
// fw: fw,
bp: NewBufferPool(defBufferSize),
}, nil
}
// Close removes the file watcher
func (t *Template) Close() {
t.fw.Close()
//t.fw.Close()
}
// Write populates a buffer using a template with NGINX configuration

View file

@ -26,6 +26,7 @@ import (
"strings"
"testing"
"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/rewrite"
@ -174,13 +175,13 @@ func TestTemplateWithData(t *testing.T) {
if dat.ListenPorts == nil {
dat.ListenPorts = &config.ListenPorts{}
}
tf, err := os.Open(path.Join(pwd, "../../../../rootfs/etc/nginx/template/nginx.tmpl"))
if err != nil {
t.Errorf("unexpected error reading json file: %v", err)
}
defer tf.Close()
ngxTpl, err := NewTemplate(tf.Name(), func() {})
fs, err := file.NewFakeFS()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
ngxTpl, err := NewTemplate("/etc/nginx/template/nginx.tmpl", fs)
if err != nil {
t.Errorf("invalid NGINX template: %v", err)
}
@ -207,13 +208,12 @@ func BenchmarkTemplateWithData(b *testing.B) {
b.Errorf("unexpected error unmarshalling json: %v", err)
}
tf, err := os.Open(path.Join(pwd, "../../../rootfs/etc/nginx/template/nginx.tmpl"))
fs, err := file.NewFakeFS()
if err != nil {
b.Errorf("unexpected error reading json file: %v", err)
b.Fatalf("unexpected error: %v", err)
}
defer tf.Close()
ngxTpl, err := NewTemplate(tf.Name(), func() {})
ngxTpl, err := NewTemplate("/etc/nginx/template/nginx.tmpl", fs)
if err != nil {
b.Errorf("invalid NGINX template: %v", err)
}

View file

@ -214,12 +214,12 @@ func buildExtensionsIngresses() []extensions.Ingress {
func buildIngressListener() []*extensions.Ingress {
return []*extensions.Ingress{
&extensions.Ingress{
{
ObjectMeta: metav1.ObjectMeta{
Name: "foo_ingress_non_01",
Namespace: apiv1.NamespaceDefault,
}},
&extensions.Ingress{
{
ObjectMeta: metav1.ObjectMeta{
Name: "foo_ingress_1",
Namespace: apiv1.NamespaceDefault,

View file

@ -18,7 +18,6 @@ package store
import (
"fmt"
"io/ioutil"
"strings"
"github.com/golang/glog"
@ -27,6 +26,7 @@ import (
apiv1 "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/k8s"
@ -57,7 +57,7 @@ func (s k8sStore) syncSecret(key string) {
s.sslStore.Update(key, cert)
// this update must trigger an update
// (like an update event from a change in Ingress)
//ic.syncQueue.Enqueue(&extensions.Ingress{})
s.sendDummyEvent()
return
}
@ -65,7 +65,7 @@ func (s k8sStore) syncSecret(key string) {
s.sslStore.Add(key, cert)
// this update must trigger an update
// (like an update event from a change in Ingress)
//ic.syncQueue.Enqueue(&extensions.Ingress{})
s.sendDummyEvent()
}
// getPemCertificate receives a secret, and creates a ingress.SSLCert as return.
@ -94,7 +94,7 @@ func (s k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error)
// If 'ca.crt' is also present, it will allow this secret to be used in the
// 'nginx.ingress.kubernetes.io/auth-tls-secret' annotation
sslCert, err = ssl.AddOrUpdateCertAndKey(nsSecName, cert, key, ca)
sslCert, err = ssl.AddOrUpdateCertAndKey(nsSecName, cert, key, ca, s.filesystem)
if err != nil {
return nil, fmt.Errorf("unexpected error creating pem file: %v", err)
}
@ -104,7 +104,7 @@ func (s k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error)
glog.V(3).Infof("found 'ca.crt', secret %v can also be used for Certificate Authentication", secretName)
}
} else if ca != nil {
sslCert, err = ssl.AddCertAuth(nsSecName, ca)
sslCert, err = ssl.AddCertAuth(nsSecName, ca, s.filesystem)
if err != nil {
return nil, fmt.Errorf("unexpected error creating pem file: %v", err)
@ -137,14 +137,21 @@ func (s k8sStore) checkSSLChainIssues() {
continue
}
data, err := ssl.FullChainCert(secret.PemFileName)
data, err := ssl.FullChainCert(secret.PemFileName, s.filesystem)
if err != nil {
glog.Errorf("unexpected error generating SSL certificate with full intermediate chain CA certs: %v", err)
continue
}
fullChainPemFileName := fmt.Sprintf("%v/%v-%v-full-chain.pem", ingress.DefaultSSLDirectory, secret.Namespace, secret.Name)
err = ioutil.WriteFile(fullChainPemFileName, data, 0655)
fullChainPemFileName := fmt.Sprintf("%v/%v-%v-full-chain.pem", file.DefaultSSLDirectory, secret.Namespace, secret.Name)
file, err := s.filesystem.Create(fullChainPemFileName)
if err != nil {
glog.Errorf("unexpected error creating SSL certificate file %v: %v", fullChainPemFileName, err)
continue
}
_, err = file.Write(data)
if err != nil {
glog.Errorf("unexpected error creating SSL certificate: %v", err)
continue
@ -164,7 +171,7 @@ func (s k8sStore) checkSSLChainIssues() {
s.sslStore.Update(secretName, dst)
// this update must trigger an update
// (like an update event from a change in Ingress)
//ic.syncQueue.Enqueue(&extensions.Ingress{})
s.sendDummyEvent()
}
}

View file

@ -18,16 +18,12 @@ package store
import (
"encoding/base64"
"fmt"
"io/ioutil"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
testclient "k8s.io/client-go/kubernetes/fake"
cache_client "k8s.io/client-go/tools/cache"
"k8s.io/kubernetes/pkg/api"
"k8s.io/ingress-nginx/internal/ingress"
)
const (
@ -115,14 +111,8 @@ func buildGenericControllerForBackendSSL() *NGINXController {
return gc
}
*/
func buildCrtKeyAndCA() ([]byte, []byte, []byte, error) {
// prepare
td, err := ioutil.TempDir("", "ssl")
if err != nil {
return nil, nil, nil, fmt.Errorf("error occurs while creating temp directory: %v", err)
}
ingress.DefaultSSLDirectory = td
func buildCrtKeyAndCA() ([]byte, []byte, []byte, error) {
dCrt, err := base64.StdEncoding.DecodeString(tlsCrt)
if err != nil {
return nil, nil, nil, err

View file

@ -25,6 +25,7 @@ import (
apiv1 "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
@ -35,6 +36,7 @@ import (
cache_client "k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"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/class"
@ -162,6 +164,10 @@ type k8sStore struct {
sslStore *SSLCertTracker
annotations annotations.Extractor
filesystem file.Filesystem
updateCh chan Event
}
// New creates a new object store to be used in the ingress controller
@ -169,6 +175,7 @@ func New(checkOCSP bool,
namespace, configmap, tcp, udp string,
resyncPeriod time.Duration,
client clientset.Interface,
fs file.Filesystem,
updateCh chan Event) Storer {
store := &k8sStore{
@ -176,6 +183,8 @@ func New(checkOCSP bool,
cache: &Controller{},
listers: &Lister{},
sslStore: NewSSLCertTracker(),
filesystem: fs,
updateCh: updateCh,
}
eventBroadcaster := record.NewBroadcaster()
@ -188,7 +197,7 @@ func New(checkOCSP bool,
})
// k8sStore fulfils resolver.Resolver interface
store.annotations = annotations.NewAnnotationExtractor(store)
store.annotations = annotations.NewAnnotationExtractor(store, fs)
ingEventHandler := cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
@ -494,9 +503,22 @@ func (s k8sStore) Run(stopCh chan struct{}) {
}
// start goroutine to check for missing local secrets
go wait.Until(s.checkMissingSecrets, 30*time.Second, stopCh)
go wait.Until(s.checkMissingSecrets, 10*time.Second, stopCh)
if s.isOCSPCheckEnabled {
go wait.Until(s.checkSSLChainIssues, 60*time.Second, stopCh)
}
}
// sendDummyEvent sends a dummy event to trigger an update
func (s *k8sStore) sendDummyEvent() {
s.updateCh <- Event{
Type: UpdateEvent,
Obj: &extensions.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "dummy",
Namespace: "dummy",
},
},
}
}

View file

@ -26,13 +26,12 @@ import (
apiv1 "k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
extensions "k8s.io/api/extensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/test/e2e/framework"
)
@ -68,6 +67,7 @@ func TestStore(t *testing.T) {
}
}(updateCh)
fs := newFS(t)
storer := New(true,
ns.Name,
fmt.Sprintf("%v/config", ns.Name),
@ -75,6 +75,7 @@ func TestStore(t *testing.T) {
fmt.Sprintf("%v/udp", ns.Name),
10*time.Minute,
clientSet,
fs,
updateCh)
storer.Run(stopCh)
@ -150,6 +151,7 @@ func TestStore(t *testing.T) {
}
}(updateCh)
fs := newFS(t)
storer := New(true,
ns.Name,
fmt.Sprintf("%v/config", ns.Name),
@ -157,6 +159,7 @@ func TestStore(t *testing.T) {
fmt.Sprintf("%v/udp", ns.Name),
10*time.Minute,
clientSet,
fs,
updateCh)
storer.Run(stopCh)
@ -239,7 +242,7 @@ func TestStore(t *testing.T) {
t.Errorf("unexpected error creating ingress: %v", err)
}
waitForNoIngressInNamespace(clientSet, ni.Namespace, ni.Name)
framework.WaitForNoIngressInNamespace(clientSet, ni.Namespace, ni.Name)
if atomic.LoadUint64(&add) != 1 {
t.Errorf("expected 1 event of type Create but %v ocurred", add)
@ -252,8 +255,223 @@ func TestStore(t *testing.T) {
}
})
// test add secret no referenced from ingress
// test add ingress with secret it doesn't exists
t.Run("should not receive events from new secret no referenced from ingress", func(t *testing.T) {
ns := createNamespace(clientSet, t)
defer deleteNamespace(ns, clientSet, t)
stopCh := make(chan struct{})
defer close(stopCh)
updateCh := make(chan Event)
defer close(updateCh)
var add uint64
var upd uint64
var del uint64
go func(ch chan Event) {
for {
e := <-ch
if e.Obj == nil {
continue
}
switch e.Type {
case CreateEvent:
atomic.AddUint64(&add, 1)
break
case UpdateEvent:
atomic.AddUint64(&upd, 1)
break
case DeleteEvent:
atomic.AddUint64(&del, 1)
break
}
}
}(updateCh)
fs := newFS(t)
storer := New(true,
ns.Name,
fmt.Sprintf("%v/config", ns.Name),
fmt.Sprintf("%v/tcp", ns.Name),
fmt.Sprintf("%v/udp", ns.Name),
10*time.Minute,
clientSet,
fs,
updateCh)
storer.Run(stopCh)
secretName := "no-referenced"
_, _, _, err = framework.CreateIngressTLSSecret(clientSet, []string{"foo"}, secretName, ns.Name)
if err != nil {
t.Errorf("unexpected error creating secret: %v", err)
}
time.Sleep(1 * time.Second)
if atomic.LoadUint64(&add) != 0 {
t.Errorf("expected 0 events of type Create but %v ocurred", add)
}
if atomic.LoadUint64(&upd) != 0 {
t.Errorf("expected 0 events of type Update but %v ocurred", upd)
}
if atomic.LoadUint64(&del) != 0 {
t.Errorf("expected 0 events of type Delete but %v ocurred", del)
}
err = clientSet.CoreV1().Secrets(ns.Name).Delete(secretName, &metav1.DeleteOptions{})
if err != nil {
t.Errorf("unexpected error deleting secret: %v", err)
}
if atomic.LoadUint64(&add) != 0 {
t.Errorf("expected 0 events of type Create but %v ocurred", add)
}
if atomic.LoadUint64(&upd) != 0 {
t.Errorf("expected 0 events of type Update but %v ocurred", upd)
}
if atomic.LoadUint64(&del) != 0 {
t.Errorf("expected 0 events of type Delete but %v ocurred", del)
}
})
t.Run("should create an ingress with a secret it doesn't exists", func(t *testing.T) {
ns := createNamespace(clientSet, t)
defer deleteNamespace(ns, clientSet, t)
stopCh := make(chan struct{})
defer close(stopCh)
updateCh := make(chan Event)
defer close(updateCh)
var add uint64
var upd uint64
var del uint64
go func(ch chan Event) {
for {
e := <-ch
if e.Obj == nil {
continue
}
switch e.Type {
case CreateEvent:
atomic.AddUint64(&add, 1)
break
case UpdateEvent:
atomic.AddUint64(&upd, 1)
break
case DeleteEvent:
atomic.AddUint64(&del, 1)
break
}
}
}(updateCh)
fs := newFS(t)
storer := New(true,
ns.Name,
fmt.Sprintf("%v/config", ns.Name),
fmt.Sprintf("%v/tcp", ns.Name),
fmt.Sprintf("%v/udp", ns.Name),
10*time.Minute,
clientSet,
fs,
updateCh)
storer.Run(stopCh)
name := "ingress-with-secret"
secretHosts := []string{name}
// err:= createIngress(client, name, ns.Name)
_, err := ensureIngress(&v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: ns.Name,
},
Spec: v1beta1.IngressSpec{
TLS: []v1beta1.IngressTLS{
{
Hosts: secretHosts,
SecretName: name,
},
},
Rules: []v1beta1.IngressRule{
{
Host: name,
IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []v1beta1.HTTPIngressPath{
{
Path: "/",
Backend: v1beta1.IngressBackend{
ServiceName: "http-svc",
ServicePort: intstr.FromInt(80),
},
},
},
},
},
},
},
},
}, clientSet)
if err != nil {
t.Errorf("unexpected error creating ingress: %v", err)
}
err = framework.WaitForIngressInNamespace(clientSet, ns.Name, name)
if err != nil {
t.Errorf("unexpected error waiting for secret: %v", err)
}
if atomic.LoadUint64(&add) != 1 {
t.Errorf("expected 1 events of type Create but %v ocurred", add)
}
if atomic.LoadUint64(&upd) != 0 {
t.Errorf("expected 0 events of type Update but %v ocurred", upd)
}
if atomic.LoadUint64(&del) != 0 {
t.Errorf("expected 0 events of type Delete but %v ocurred", del)
}
_, _, _, err = framework.CreateIngressTLSSecret(clientSet, secretHosts, name, ns.Name)
if err != nil {
t.Errorf("unexpected error creating secret: %v", err)
}
t.Run("should exists a secret in the local store and filesystem", func(t *testing.T) {
err := framework.WaitForSecretInNamespace(clientSet, ns.Name, name)
if err != nil {
t.Errorf("unexpected error waiting for secret: %v", err)
}
pemFile := fmt.Sprintf("%v/%v-%v.pem", file.DefaultSSLDirectory, ns.Name, name)
stat, err := fs.Stat(pemFile)
if err != nil {
t.Errorf("unexpected error reading secret pem file: %v", err)
}
if stat.Size() < 1 {
t.Errorf("unexpected size of pem file (%v)", stat.Size())
}
secretName := fmt.Sprintf("%v/%v", ns.Name, name)
sslCert, err := storer.GetLocalSecret(secretName)
if err != nil {
t.Errorf("unexpected error reading local secret %v: %v", secretName, err)
}
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
// check secret is generated on fs
// check ocsp
@ -293,23 +511,10 @@ func ensureIngress(ingress *extensions.Ingress, clientSet *kubernetes.Clientset)
return s, nil
}
func waitForNoIngressInNamespace(c kubernetes.Interface, namespace, name string) error {
return wait.PollImmediate(1*time.Second, time.Minute*2, noIngressInNamespace(c, namespace, name))
}
func noIngressInNamespace(c kubernetes.Interface, namespace, name string) wait.ConditionFunc {
return func() (bool, error) {
ing, err := c.ExtensionsV1beta1().Ingresses(namespace).Get(name, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
return true, nil
}
if err != nil {
return false, err
}
if ing == nil {
return true, nil
}
return false, nil
func newFS(t *testing.T) file.Filesystem {
fs, err := file.NewFakeFS()
if err != nil {
t.Fatalf("unexpected error creating filesystem: %v", err)
}
return fs
}

View file

@ -35,14 +35,6 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
var (
// DefaultSSLDirectory defines the location where the SSL certificates will be generated
// This directory contains all the SSL certificates that are specified in Ingress rules.
// The name of each file is <namespace>-<secret name>.pem. The content is the concatenated
// certificate and key.
DefaultSSLDirectory = "/ingress-controller/ssl"
)
// Configuration holds the definition of all the parts required to describe all
// ingresses reachable by the ingress controller (using a filter by namespace)
type Configuration struct {

View file

@ -26,10 +26,8 @@ import (
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"math/big"
"net"
"os"
"strconv"
"time"
@ -47,10 +45,12 @@ var (
)
// AddOrUpdateCertAndKey creates a .pem file wth the cert and the key with the specified name
func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert, error) {
func AddOrUpdateCertAndKey(name string, cert, key, ca []byte,
fs file.Filesystem) (*ingress.SSLCert, error) {
pemName := fmt.Sprintf("%v.pem", name)
pemFileName := fmt.Sprintf("%v/%v", ingress.DefaultSSLDirectory, pemName)
tempPemFile, err := ioutil.TempFile(ingress.DefaultSSLDirectory, pemName)
pemFileName := fmt.Sprintf("%v/%v", file.DefaultSSLDirectory, pemName)
tempPemFile, err := fs.TempFile(file.DefaultSSLDirectory, pemName)
if err != nil {
return nil, fmt.Errorf("could not create temp pem file %v: %v", pemFileName, err)
@ -74,34 +74,30 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
if err != nil {
return nil, fmt.Errorf("could not close temp pem file %v: %v", tempPemFile.Name(), err)
}
defer fs.RemoveAll(tempPemFile.Name())
pemCerts, err := ioutil.ReadFile(tempPemFile.Name())
pemCerts, err := fs.ReadFile(tempPemFile.Name())
if err != nil {
_ = os.Remove(tempPemFile.Name())
return nil, err
}
pemBlock, _ := pem.Decode(pemCerts)
if pemBlock == nil {
_ = os.Remove(tempPemFile.Name())
return nil, fmt.Errorf("no valid PEM formatted block found")
}
// If the file does not start with 'BEGIN CERTIFICATE' it's invalid and must not be used.
if pemBlock.Type != "CERTIFICATE" {
_ = os.Remove(tempPemFile.Name())
return nil, fmt.Errorf("certificate %v contains invalid data, and must be created with 'kubectl create secret tls'", name)
}
pemCert, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
_ = os.Remove(tempPemFile.Name())
return nil, err
}
//Ensure that certificate and private key have a matching public key
if _, err := tls.X509KeyPair(cert, key); err != nil {
_ = os.Remove(tempPemFile.Name())
return nil, err
}
@ -129,7 +125,7 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
}
}
err = os.Rename(tempPemFile.Name(), pemFileName)
err = fs.Rename(tempPemFile.Name(), pemFileName)
if err != nil {
return nil, fmt.Errorf("could not move temp pem file %v to destination %v: %v", tempPemFile.Name(), pemFileName, err)
}
@ -147,18 +143,24 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
return nil, errors.New(oe)
}
caFile, err := os.OpenFile(pemFileName, os.O_RDWR|os.O_APPEND, 0600)
caData, err := fs.ReadFile(pemFileName)
if err != nil {
return nil, fmt.Errorf("could not open file %v for writing additional CA chains: %v", pemFileName, err)
}
defer caFile.Close()
caFile, err := fs.Create(pemFileName)
_, err = caFile.Write(caData)
if err != nil {
return nil, fmt.Errorf("could not append CA to cert file %v: %v", pemFileName, err)
}
_, err = caFile.Write([]byte("\n"))
if err != nil {
return nil, fmt.Errorf("could not append CA to cert file %v: %v", pemFileName, err)
}
caFile.Write(ca)
caFile.Write([]byte("\n"))
defer caFile.Close()
return &ingress.SSLCert{
Certificate: pemCert,
@ -249,10 +251,10 @@ func parseSANExtension(value []byte) (dnsNames, emailAddresses []string, ipAddre
// AddCertAuth creates a .pem file with the specified CAs to be used in Cert Authentication
// If it's already exists, it's clobbered.
func AddCertAuth(name string, ca []byte) (*ingress.SSLCert, error) {
func AddCertAuth(name string, ca []byte, fs file.Filesystem) (*ingress.SSLCert, error) {
caName := fmt.Sprintf("ca-%v.pem", name)
caFileName := fmt.Sprintf("%v/%v", ingress.DefaultSSLDirectory, caName)
caFileName := fmt.Sprintf("%v/%v", file.DefaultSSLDirectory, caName)
pemCABlock, _ := pem.Decode(ca)
if pemCABlock == nil {
@ -268,7 +270,13 @@ func AddCertAuth(name string, ca []byte) (*ingress.SSLCert, error) {
return nil, err
}
err = ioutil.WriteFile(caFileName, ca, 0644)
caFile, err := fs.Create(caFileName)
if err != nil {
return nil, fmt.Errorf("could not write CA file %v: %v", caFileName, err)
}
defer caFile.Close()
_, err = caFile.Write(ca)
if err != nil {
return nil, fmt.Errorf("could not write CA file %v: %v", caFileName, err)
}
@ -282,11 +290,11 @@ func AddCertAuth(name string, ca []byte) (*ingress.SSLCert, error) {
}
// AddOrUpdateDHParam creates a dh parameters file with the specified name
func AddOrUpdateDHParam(name string, dh []byte) (string, error) {
func AddOrUpdateDHParam(name string, dh []byte, fs file.Filesystem) (string, error) {
pemName := fmt.Sprintf("%v.pem", name)
pemFileName := fmt.Sprintf("%v/%v", ingress.DefaultSSLDirectory, pemName)
pemFileName := fmt.Sprintf("%v/%v", file.DefaultSSLDirectory, pemName)
tempPemFile, err := ioutil.TempFile(ingress.DefaultSSLDirectory, pemName)
tempPemFile, err := fs.TempFile(file.DefaultSSLDirectory, pemName)
glog.V(3).Infof("Creating temp file %v for DH param: %v", tempPemFile.Name(), pemName)
if err != nil {
@ -303,25 +311,24 @@ func AddOrUpdateDHParam(name string, dh []byte) (string, error) {
return "", fmt.Errorf("could not close temp pem file %v: %v", tempPemFile.Name(), err)
}
pemCerts, err := ioutil.ReadFile(tempPemFile.Name())
defer fs.RemoveAll(tempPemFile.Name())
pemCerts, err := fs.ReadFile(tempPemFile.Name())
if err != nil {
_ = os.Remove(tempPemFile.Name())
return "", err
}
pemBlock, _ := pem.Decode(pemCerts)
if pemBlock == nil {
_ = os.Remove(tempPemFile.Name())
return "", fmt.Errorf("no valid PEM formatted block found")
}
// If the file does not start with 'BEGIN DH PARAMETERS' it's invalid and must not be used.
if pemBlock.Type != "DH PARAMETERS" {
_ = os.Remove(tempPemFile.Name())
return "", fmt.Errorf("certificate %v contains invalid data", name)
}
err = os.Rename(tempPemFile.Name(), pemFileName)
err = fs.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)
}
@ -382,13 +389,8 @@ func GetFakeSSLCert() ([]byte, []byte) {
// FullChainCert checks if a certificate file contains issues in the intermediate CA chain
// Returns a new certificate with the intermediate certificates.
// If the certificate does not contains issues with the chain it return an empty byte array
func FullChainCert(in string) ([]byte, error) {
inputFile, err := os.Open(in)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(inputFile)
func FullChainCert(in string, fs file.Filesystem) ([]byte, error) {
data, err := fs.ReadFile(in)
if err != nil {
return nil, err
}

View file

@ -19,14 +19,13 @@ package ssl
import (
"crypto/x509"
"fmt"
"io/ioutil"
"testing"
"time"
certutil "k8s.io/client-go/util/cert"
"k8s.io/client-go/util/cert/triple"
"k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/file"
)
// generateRSACerts generates a self signed certificate using a self generated ca
@ -57,11 +56,7 @@ func generateRSACerts(host string) (*triple.KeyPair, *triple.KeyPair, error) {
}
func TestAddOrUpdateCertAndKey(t *testing.T) {
td, err := ioutil.TempDir("", "ssl")
if err != nil {
t.Fatalf("Unexpected error creating temporal directory: %v", err)
}
ingress.DefaultSSLDirectory = td
fs := newFS(t)
cert, _, err := generateRSACerts("echoheaders")
if err != nil {
@ -73,7 +68,7 @@ func TestAddOrUpdateCertAndKey(t *testing.T) {
c := certutil.EncodeCertPEM(cert.Cert)
k := certutil.EncodePrivateKeyPEM(cert.Key)
ngxCert, err := AddOrUpdateCertAndKey(name, c, k, []byte{})
ngxCert, err := AddOrUpdateCertAndKey(name, c, k, []byte{}, fs)
if err != nil {
t.Fatalf("unexpected error checking SSL certificate: %v", err)
}
@ -92,11 +87,7 @@ func TestAddOrUpdateCertAndKey(t *testing.T) {
}
func TestCACert(t *testing.T) {
td, err := ioutil.TempDir("", "ssl")
if err != nil {
t.Fatalf("Unexpected error creating temporal directory: %v", err)
}
ingress.DefaultSSLDirectory = td
fs := newFS(t)
cert, CA, err := generateRSACerts("echoheaders")
if err != nil {
@ -109,7 +100,7 @@ func TestCACert(t *testing.T) {
k := certutil.EncodePrivateKeyPEM(cert.Key)
ca := certutil.EncodeCertPEM(CA.Cert)
ngxCert, err := AddOrUpdateCertAndKey(name, c, k, ca)
ngxCert, err := AddOrUpdateCertAndKey(name, c, k, ca, fs)
if err != nil {
t.Fatalf("unexpected error checking SSL certificate: %v", err)
}
@ -129,11 +120,10 @@ func TestGetFakeSSLCert(t *testing.T) {
}
func TestAddCertAuth(t *testing.T) {
td, err := ioutil.TempDir("", "ssl")
fs, err := file.NewFakeFS()
if err != nil {
t.Fatalf("Unexpected error creating temporal directory: %v", err)
t.Fatalf("unexpected error creating filesystem: %v", err)
}
ingress.DefaultSSLDirectory = td
cn := "demo-ca"
_, ca, err := generateRSACerts(cn)
@ -141,7 +131,7 @@ func TestAddCertAuth(t *testing.T) {
t.Fatalf("unexpected error creating SSL certificate: %v", err)
}
c := certutil.EncodeCertPEM(ca.Cert)
ic, err := AddCertAuth(cn, c)
ic, err := AddCertAuth(cn, c, fs)
if err != nil {
t.Fatalf("unexpected error creating SSL certificate: %v", err)
}
@ -149,3 +139,11 @@ func TestAddCertAuth(t *testing.T) {
t.Fatalf("expected a valid CA file name")
}
}
func newFS(t *testing.T) file.Filesystem {
fs, err := file.NewFakeFS()
if err != nil {
t.Fatalf("unexpected error creating filesystem: %v", err)
}
return fs
}

29
internal/watch/dummy.go Normal file
View file

@ -0,0 +1,29 @@
/*
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 watch
// DummyFileWatcher noop implementation of a file watcher
type DummyFileWatcher struct{}
func NewDummyFileWatcher(file string, onEvent func()) FileWatcher {
return DummyFileWatcher{}
}
// Close ends the watch
func (f DummyFileWatcher) Close() error {
return nil
}

View file

@ -24,8 +24,12 @@ import (
"gopkg.in/fsnotify.v1"
)
// FileWatcher defines a watch over a file
type FileWatcher struct {
type FileWatcher interface {
Close() error
}
// OSFileWatcher defines a watch over a file
type OSFileWatcher struct {
file string
watcher *fsnotify.Watcher
// onEvent callback to be invoked after the file being watched changes
@ -34,7 +38,7 @@ type FileWatcher struct {
// NewFileWatcher creates a new FileWatcher
func NewFileWatcher(file string, onEvent func()) (FileWatcher, error) {
fw := FileWatcher{
fw := OSFileWatcher{
file: file,
onEvent: onEvent,
}
@ -43,12 +47,12 @@ func NewFileWatcher(file string, onEvent func()) (FileWatcher, error) {
}
// Close ends the watch
func (f *FileWatcher) Close() error {
func (f OSFileWatcher) Close() error {
return f.watcher.Close()
}
// watch creates a fsnotify watcher for a file and create of write events
func (f *FileWatcher) watch() error {
func (f *OSFileWatcher) watch() error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err

115
test/e2e/framework/ssl.go Normal file
View file

@ -0,0 +1,115 @@
package framework
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io"
"math/big"
"net"
"strings"
"time"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
const (
rsaBits = 2048
validFor = 365 * 24 * time.Hour
)
// CreateIngressTLSSecret creates a secret containing TLS certificates for the given Ingress.
// If a secret with the same name already pathExists in the namespace of the
// Ingress, it's updated.
func CreateIngressTLSSecret(client kubernetes.Interface, hosts []string, secreName, namespace string) (host string, rootCA, privKey []byte, err error) {
var k, c bytes.Buffer
host = strings.Join(hosts, ",")
if err = generateRSACerts(host, true, &k, &c); err != nil {
return
}
cert := c.Bytes()
key := k.Bytes()
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secreName,
},
Data: map[string][]byte{
v1.TLSCertKey: cert,
v1.TLSPrivateKeyKey: key,
},
}
var s *v1.Secret
if s, err = client.CoreV1().Secrets(namespace).Get(secreName, metav1.GetOptions{}); err == nil {
s.Data = secret.Data
_, err = client.CoreV1().Secrets(namespace).Update(s)
} else {
_, err = client.CoreV1().Secrets(namespace).Create(secret)
}
return host, cert, key, err
}
// generateRSACerts generates a basic self signed certificate using a key length
// of rsaBits, valid for validFor time.
func generateRSACerts(host string, isCA bool, keyOut, certOut io.Writer) error {
if len(host) == 0 {
return fmt.Errorf("Require a non-empty host for client hello")
}
priv, err := rsa.GenerateKey(rand.Reader, rsaBits)
if err != nil {
return fmt.Errorf("Failed to generate key: %v", err)
}
notBefore := time.Now()
notAfter := notBefore.Add(validFor)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return fmt.Errorf("failed to generate serial number: %s", err)
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "default",
Organization: []string{"Acme Co"},
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
hosts := strings.Split(host, ",")
for _, h := range hosts {
if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, h)
}
}
if isCA {
template.IsCA = true
template.KeyUsage |= x509.KeyUsageCertSign
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return fmt.Errorf("Failed to create certificate: %s", err)
}
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
return fmt.Errorf("Failed creating cert: %v", err)
}
if err := pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil {
return fmt.Errorf("Failed creating keay: %v", err)
}
return nil
}

View file

@ -199,6 +199,69 @@ func podRunning(c kubernetes.Interface, podName, namespace string) wait.Conditio
}
}
func WaitForSecretInNamespace(c kubernetes.Interface, namespace, name string) error {
return wait.PollImmediate(1*time.Second, time.Minute*2, secretInNamespace(c, namespace, name))
}
func secretInNamespace(c kubernetes.Interface, namespace, name string) wait.ConditionFunc {
return func() (bool, error) {
s, err := c.CoreV1().Secrets(namespace).Get(name, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
return false, err
}
if err != nil {
return false, err
}
if s != nil {
return true, nil
}
return false, nil
}
}
func WaitForNoIngressInNamespace(c kubernetes.Interface, namespace, name string) error {
return wait.PollImmediate(1*time.Second, time.Minute*2, noIngressInNamespace(c, namespace, name))
}
func noIngressInNamespace(c kubernetes.Interface, namespace, name string) wait.ConditionFunc {
return func() (bool, error) {
ing, err := c.ExtensionsV1beta1().Ingresses(namespace).Get(name, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
return true, nil
}
if err != nil {
return false, err
}
if ing == nil {
return true, nil
}
return false, nil
}
}
func WaitForIngressInNamespace(c kubernetes.Interface, namespace, name string) error {
return wait.PollImmediate(1*time.Second, time.Minute*2, ingressInNamespace(c, namespace, name))
}
func ingressInNamespace(c kubernetes.Interface, namespace, name string) wait.ConditionFunc {
return func() (bool, error) {
ing, err := c.ExtensionsV1beta1().Ingresses(namespace).Get(name, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
return false, err
}
if err != nil {
return false, err
}
if ing != nil {
return true, nil
}
return false, nil
}
}
func NewInt32(val int32) *int32 {
p := new(int32)
*p = val

View file

@ -17,16 +17,7 @@ limitations under the License.
package ssl
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io"
"math/big"
"net"
"strings"
"time"
@ -37,15 +28,9 @@ import (
"k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
"k8s.io/ingress-nginx/test/e2e/framework"
)
const (
rsaBits = 2048
validFor = 365 * 24 * time.Hour
)
var _ = framework.IngressNginxDescribe("SSL", func() {
f := framework.NewDefaultFramework("ssl")
@ -107,7 +92,8 @@ var _ = framework.IngressNginxDescribe("SSL", func() {
Expect(err).ToNot(HaveOccurred())
Expect(ing).ToNot(BeNil())
_, _, _, err = createIngressTLSSecret(f.KubeClientSet, ing)
tls := ing.Spec.TLS[0]
_, _, _, err = framework.CreateIngressTLSSecret(f.KubeClientSet, tls.Hosts, tls.SecretName, ing.Namespace)
Expect(err).ToNot(HaveOccurred())
err = f.WaitForNginxServer(host,
@ -130,94 +116,3 @@ var _ = framework.IngressNginxDescribe("SSL", func() {
Expect(log).ToNot(ContainSubstring(fmt.Sprintf("error obtaining PEM from secret %v/dummy", f.Namespace.Name)))
})
})
// createIngressTLSSecret creates a secret containing TLS certificates for the given Ingress.
// If a secret with the same name already pathExists in the namespace of the
// Ingress, it's updated.
func createIngressTLSSecret(kubeClient kubernetes.Interface, ing *v1beta1.Ingress) (host string, rootCA, privKey []byte, err error) {
var k, c bytes.Buffer
tls := ing.Spec.TLS[0]
host = strings.Join(tls.Hosts, ",")
if err = generateRSACerts(host, true, &k, &c); err != nil {
return
}
cert := c.Bytes()
key := k.Bytes()
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: tls.SecretName,
},
Data: map[string][]byte{
v1.TLSCertKey: cert,
v1.TLSPrivateKeyKey: key,
},
}
var s *v1.Secret
if s, err = kubeClient.CoreV1().Secrets(ing.Namespace).Get(tls.SecretName, metav1.GetOptions{}); err == nil {
s.Data = secret.Data
_, err = kubeClient.CoreV1().Secrets(ing.Namespace).Update(s)
} else {
_, err = kubeClient.CoreV1().Secrets(ing.Namespace).Create(secret)
}
return host, cert, key, err
}
// generateRSACerts generates a basic self signed certificate using a key length
// of rsaBits, valid for validFor time.
func generateRSACerts(host string, isCA bool, keyOut, certOut io.Writer) error {
if len(host) == 0 {
return fmt.Errorf("Require a non-empty host for client hello")
}
priv, err := rsa.GenerateKey(rand.Reader, rsaBits)
if err != nil {
return fmt.Errorf("Failed to generate key: %v", err)
}
notBefore := time.Now()
notAfter := notBefore.Add(validFor)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return fmt.Errorf("failed to generate serial number: %s", err)
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "default",
Organization: []string{"Acme Co"},
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
hosts := strings.Split(host, ",")
for _, h := range hosts {
if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, h)
}
}
if isCA {
template.IsCA = true
template.KeyUsage |= x509.KeyUsageCertSign
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return fmt.Errorf("Failed to create certificate: %s", err)
}
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
return fmt.Errorf("Failed creating cert: %v", err)
}
if err := pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil {
return fmt.Errorf("Failed creating keay: %v", err)
}
return nil
}