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: script:
- go get github.com/mattn/goveralls - go get github.com/mattn/goveralls
- go get github.com/modocache/gover - go get github.com/modocache/gover
- if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; - if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover;fi
fi - if ! go get github.com/jteeuwen/go-bindata/...; then github.com/jteeuwen/go-bindata/...;fi
- make cover - make cover
- stage: e2e - stage: e2e
before_script: before_script:
- if ! go get github.com/jteeuwen/go-bindata/...; then github.com/jteeuwen/go-bindata/...;fi
- make e2e-image - make e2e-image
- test/e2e/up.sh - test/e2e/up.sh
- test/e2e/wait-for-nginx.sh - test/e2e/wait-for-nginx.sh

View file

@ -133,8 +133,12 @@ endif
clean: clean:
$(DOCKER) rmi -f $(MULTI_ARCH_IMG):$(TAG) || true $(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 .PHONY: build
build: clean build: clean gobindata
CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build -a -installsuffix cgo \ 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}" \ -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 -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 @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 .PHONY: test
test: fmt lint vet test: fmt lint vet gobindata
@echo "+ $@" @echo "+ $@"
@go test -v -race -tags "$(BUILDTAGS) cgo" $(shell go list ${PKG}/... | grep -v vendor | grep -v '/test/e2e') @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 @KUBECONFIG=${HOME}/.kube/config INGRESSNGINXCONFIG=${HOME}/.kube/config ./e2e-tests
.PHONY: cover .PHONY: cover
cover: cover: gobindata
@echo "+ $@" @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 @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 gover

View file

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

View file

@ -23,6 +23,7 @@ import (
"testing" "testing"
"time" "time"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress/controller" "k8s.io/ingress-nginx/internal/ingress/controller"
) )
@ -70,7 +71,12 @@ func TestHandleSigterm(t *testing.T) {
} }
conf.Client = cli 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) { go handleSigterm(ngx, func(code int) {
if code != 1 { 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" extensions "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 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/alias"
"k8s.io/ingress-nginx/internal/ingress/annotations/auth" "k8s.io/ingress-nginx/internal/ingress/annotations/auth"
"k8s.io/ingress-nginx/internal/ingress/annotations/authreq" "k8s.io/ingress-nginx/internal/ingress/annotations/authreq"
@ -89,11 +90,11 @@ type Extractor struct {
} }
// NewAnnotationExtractor creates a new annotations extractor // NewAnnotationExtractor creates a new annotations extractor
func NewAnnotationExtractor(cfg resolver.Resolver) Extractor { func NewAnnotationExtractor(cfg resolver.Resolver, fs file.Filesystem) Extractor {
return Extractor{ return Extractor{
map[string]parser.IngressAnnotation{ map[string]parser.IngressAnnotation{
"Alias": alias.NewParser(cfg), "Alias": alias.NewParser(cfg),
"BasicDigestAuth": auth.NewParser(auth.AuthDirectory, cfg), "BasicDigestAuth": auth.NewParser(file.AuthDirectory, fs, cfg),
"CertificateAuth": authtls.NewParser(cfg), "CertificateAuth": authtls.NewParser(cfg),
"ClientBodyBufferSize": clientbodybuffersize.NewParser(), "ClientBodyBufferSize": clientbodybuffersize.NewParser(),
"ConfigurationSnippet": snippet.NewParser(), "ConfigurationSnippet": snippet.NewParser(),

View file

@ -24,6 +24,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr" "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/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/defaults" "k8s.io/ingress-nginx/internal/ingress/defaults"
"k8s.io/ingress-nginx/internal/ingress/resolver" "k8s.io/ingress-nginx/internal/ingress/resolver"
@ -113,7 +114,8 @@ func buildIngress() *extensions.Ingress {
} }
func TestSecureUpstream(t *testing.T) { func TestSecureUpstream(t *testing.T) {
ec := NewAnnotationExtractor(mockCfg{}) fs := newFS(t)
ec := NewAnnotationExtractor(mockCfg{}, fs)
ing := buildIngress() ing := buildIngress()
fooAnns := []struct { fooAnns := []struct {
@ -137,6 +139,7 @@ func TestSecureUpstream(t *testing.T) {
} }
func TestSecureVerifyCACert(t *testing.T) { func TestSecureVerifyCACert(t *testing.T) {
fs := newFS(t)
ec := NewAnnotationExtractor(mockCfg{ ec := NewAnnotationExtractor(mockCfg{
MockSecrets: map[string]*apiv1.Secret{ MockSecrets: map[string]*apiv1.Secret{
"default/secure-verify-ca": { "default/secure-verify-ca": {
@ -145,7 +148,7 @@ func TestSecureVerifyCACert(t *testing.T) {
}, },
}, },
}, },
}) }, fs)
anns := []struct { anns := []struct {
it int it int
@ -172,7 +175,8 @@ func TestSecureVerifyCACert(t *testing.T) {
} }
func TestHealthCheck(t *testing.T) { func TestHealthCheck(t *testing.T) {
ec := NewAnnotationExtractor(mockCfg{}) fs := newFS(t)
ec := NewAnnotationExtractor(mockCfg{}, fs)
ing := buildIngress() ing := buildIngress()
fooAnns := []struct { fooAnns := []struct {
@ -202,7 +206,8 @@ func TestHealthCheck(t *testing.T) {
} }
func TestSSLPassthrough(t *testing.T) { func TestSSLPassthrough(t *testing.T) {
ec := NewAnnotationExtractor(mockCfg{}) fs := newFS(t)
ec := NewAnnotationExtractor(mockCfg{}, fs)
ing := buildIngress() ing := buildIngress()
fooAnns := []struct { fooAnns := []struct {
@ -226,7 +231,8 @@ func TestSSLPassthrough(t *testing.T) {
} }
func TestUpstreamHashBy(t *testing.T) { func TestUpstreamHashBy(t *testing.T) {
ec := NewAnnotationExtractor(mockCfg{}) fs := newFS(t)
ec := NewAnnotationExtractor(mockCfg{}, fs)
ing := buildIngress() ing := buildIngress()
fooAnns := []struct { fooAnns := []struct {
@ -250,7 +256,8 @@ func TestUpstreamHashBy(t *testing.T) {
} }
func TestAffinitySession(t *testing.T) { func TestAffinitySession(t *testing.T) {
ec := NewAnnotationExtractor(mockCfg{}) fs := newFS(t)
ec := NewAnnotationExtractor(mockCfg{}, fs)
ing := buildIngress() ing := buildIngress()
fooAnns := []struct { fooAnns := []struct {
@ -282,7 +289,8 @@ func TestAffinitySession(t *testing.T) {
} }
func TestCors(t *testing.T) { func TestCors(t *testing.T) {
ec := NewAnnotationExtractor(mockCfg{}) fs := newFS(t)
ec := NewAnnotationExtractor(mockCfg{}, fs)
ing := buildIngress() ing := buildIngress()
fooAnns := []struct { 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 ( import (
"fmt" "fmt"
"io/ioutil"
"os"
"path"
"regexp" "regexp"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -35,9 +32,6 @@ import (
var ( var (
authTypeRegex = regexp.MustCompile(`basic|digest`) 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 // Config returns authentication configuration for an Ingress rule
@ -78,23 +72,13 @@ func (bd1 *Config) Equal(bd2 *Config) bool {
type auth struct { type auth struct {
r resolver.Resolver r resolver.Resolver
fs file.Filesystem
authDirectory string authDirectory string
} }
// NewParser creates a new authentication annotation parser // NewParser creates a new authentication annotation parser
func NewParser(authDirectory string, r resolver.Resolver) parser.IngressAnnotation { func NewParser(authDirectory string, fs file.Filesystem, r resolver.Resolver) parser.IngressAnnotation {
os.MkdirAll(authDirectory, 0755) return auth{r, fs, authDirectory}
currPath := authDirectory
for currPath != "/" {
currPath = path.Dir(currPath)
err := os.Chmod(currPath, 0755)
if err != nil {
break
}
}
return auth{r, authDirectory}
} }
// Parse parses the annotations contained in the ingress // 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) realm, _ := parser.GetStringAnnotation("auth-realm", ing)
passFile := fmt.Sprintf("%v/%v-%v.passwd", a.authDirectory, ing.GetNamespace(), ing.GetName()) 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 { if err != nil {
return nil, err 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 // dumpSecret dumps the content of a secret into a file
// in the expected format for the specified authorization // 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"] val, ok := secret.Data["auth"]
if !ok { if !ok {
return ing_errors.LocationDenied{ return ing_errors.LocationDenied{
@ -153,13 +137,26 @@ func dumpSecret(filename string, secret *api.Secret) error {
} }
} }
// TODO: check permissions required f, err := fs.Create(filename)
err := ioutil.WriteFile(filename, val, 0777)
if err != nil { if err != nil {
return ing_errors.LocationDenied{ return ing_errors.LocationDenied{
Reason: errors.Wrap(err, "unexpected error creating password file"), 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 return nil
} }

View file

@ -29,6 +29,7 @@ import (
extensions "k8s.io/api/extensions/v1beta1" extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr" "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/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver" "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) { func TestIngressWithoutAuth(t *testing.T) {
fs := newFS(t)
ing := buildIngress() ing := buildIngress()
_, dir, _ := dummySecretContent(t) _, dir, _ := dummySecretContent(t)
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
_, err := NewParser(dir, &mockSecret{}).Parse(ing) _, err := NewParser(dir, fs, &mockSecret{}).Parse(ing)
if err == nil { if err == nil {
t.Error("Expected error with ingress without annotations") t.Error("Expected error with ingress without annotations")
} }
@ -108,7 +110,9 @@ func TestIngressAuth(t *testing.T) {
_, dir, _ := dummySecretContent(t) _, dir, _ := dummySecretContent(t)
defer os.RemoveAll(dir) 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 { if err != nil {
t.Errorf("Uxpected error with ingress: %v", err) t.Errorf("Uxpected error with ingress: %v", err)
} }
@ -139,7 +143,9 @@ func TestIngressAuthWithoutSecret(t *testing.T) {
_, dir, _ := dummySecretContent(t) _, dir, _ := dummySecretContent(t)
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
_, err := NewParser(dir, mockSecret{}).Parse(ing) fs := newFS(t)
_, err := NewParser(dir, fs, mockSecret{}).Parse(ing)
if err == nil { if err == nil {
t.Errorf("expected an error with invalid secret name") t.Errorf("expected an error with invalid secret name")
} }
@ -167,14 +173,24 @@ func TestDumpSecret(t *testing.T) {
sd := s.Data sd := s.Data
s.Data = nil s.Data = nil
err := dumpSecret(tmpfile, s) fs := newFS(t)
err := dumpSecret(tmpfile, s, fs)
if err == nil { if err == nil {
t.Errorf("Expected error with secret without auth") t.Errorf("Expected error with secret without auth")
} }
s.Data = sd s.Data = sd
err = dumpSecret(tmpfile, s) err = dumpSecret(tmpfile, s, fs)
if err != nil { if err != nil {
t.Errorf("Unexpected error creating htpasswd file %v: %v", tmpfile, err) 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" "testing"
"k8s.io/apiserver/pkg/server/healthz" "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" 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 // port to be used in the check
p := server.Listener.Addr().(*net.TCPAddr).Port p := server.Listener.Addr().(*net.TCPAddr).Port
// mock filesystem fs, err := file.NewFakeFS()
fs := filesystem.NewFakeFs() if err != nil {
t.Fatalf("unexpected error: %v", err)
}
n := &NGINXController{ n := &NGINXController{
cfg: &Configuration{ 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) { t.Run("no process", func(t *testing.T) {
if err := callHealthz(true, mux); err == nil { if err := callHealthz(true, mux); err == nil {
t.Errorf("expected an error but none returned") t.Errorf("expected an error but none returned")
@ -81,18 +76,9 @@ func TestNginxCheck(t *testing.T) {
cmd.Wait() cmd.Wait()
}() }()
pidFile.Write([]byte(fmt.Sprintf("%v", pid)))
pidFile.Close()
healthz.InstallHandler(mux, n) healthz.InstallHandler(mux, n)
t.Run("valid request", func(t *testing.T) { pidFile, err := fs.Create("/run/nginx.pid")
if err := callHealthz(false, mux); err != nil {
t.Error(err)
}
})
pidFile, err = fs.Create("/run/nginx.pid")
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }

View file

@ -23,8 +23,6 @@ import (
"reflect" "reflect"
"sort" "sort"
"strconv" "strconv"
"strings"
"sync/atomic"
"time" "time"
"github.com/golang/glog" "github.com/golang/glog"
@ -40,7 +38,6 @@ import (
"k8s.io/ingress-nginx/internal/ingress/annotations/healthcheck" "k8s.io/ingress-nginx/internal/ingress/annotations/healthcheck"
"k8s.io/ingress-nginx/internal/ingress/annotations/proxy" "k8s.io/ingress-nginx/internal/ingress/annotations/proxy"
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config" ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
"k8s.io/ingress-nginx/internal/k8s"
) )
const ( const (
@ -102,16 +99,6 @@ type Configuration struct {
FakeCertificateSHA string 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 // sync collects all the pieces required to assemble the configuration file and
// then sends the content to the backend (OnUpdate) receiving the populated // then sends the content to the backend (OnUpdate) receiving the populated
// template as response reloading the backend if is required. // template as response reloading the backend if is required.
@ -185,131 +172,6 @@ func (n *NGINXController) syncIngress(item interface{}) error {
return nil 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 // getDefaultUpstream returns an upstream associated with the
// default backend service. In case of error retrieving information // default backend service. In case of error retrieving information
// configure the upstream to return http code 503. // configure the upstream to return http code 503.
@ -664,7 +526,9 @@ func (n *NGINXController) createUpstreams(data []*extensions.Ingress, du *ingres
return upstreams 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) svc, err := n.storeLister.GetService(svcKey)
if err != nil { if err != nil {
return endpoint, err return endpoint, err
@ -1067,18 +931,3 @@ func (n *NGINXController) getEndpoints(
glog.V(3).Infof("endpoints found: %v", upsServers) glog.V(3).Infof("endpoints found: %v", upsServers)
return 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/client-go/util/flowcontrol"
"k8s.io/kubernetes/pkg/util/filesystem" "k8s.io/kubernetes/pkg/util/filesystem"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations/class" "k8s.io/ingress-nginx/internal/ingress/annotations/class"
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config" 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/dns"
"k8s.io/ingress-nginx/internal/net/ssl" "k8s.io/ingress-nginx/internal/net/ssl"
"k8s.io/ingress-nginx/internal/task" "k8s.io/ingress-nginx/internal/task"
"k8s.io/ingress-nginx/internal/watch"
) )
type statusModule string type statusModule string
@ -70,7 +72,7 @@ var (
// NewNGINXController creates a new NGINX Ingress controller. // NewNGINXController creates a new NGINX Ingress controller.
// If the environment variable NGINX_BINARY exists it will be used // If the environment variable NGINX_BINARY exists it will be used
// as source for nginx commands // as source for nginx commands
func NewNGINXController(config *Configuration) *NGINXController { func NewNGINXController(config *Configuration, fs file.Filesystem) *NGINXController {
ngx := os.Getenv("NGINX_BINARY") ngx := os.Getenv("NGINX_BINARY")
if ngx == "" { if ngx == "" {
ngx = nginxBinary ngx = nginxBinary
@ -98,7 +100,7 @@ func NewNGINXController(config *Configuration) *NGINXController {
stopLock: &sync.Mutex{}, stopLock: &sync.Mutex{},
fileSystem: filesystem.DefaultFs{}, fileSystem: fs,
} }
n.stats = newStatsCollector(config.Namespace, class.IngressClass, n.binary, n.cfg.ListenPorts.Status) 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.UDPConfigMapName,
n.cfg.ResyncPeriod, n.cfg.ResyncPeriod,
n.cfg.Client, n.cfg.Client,
n.fileSystem,
n.updateCh, 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)") glog.Warning("Update of ingress status is disabled (flag --update-status=false was specified)")
} }
var onChange func() onChange := func() {
onChange = func() { template, err := ngx_template.NewTemplate(tmplPath, n.fileSystem)
template, err := ngx_template.NewTemplate(tmplPath, onChange)
if err != nil { if err != nil {
// this error is different from the rest because it must be clear why nginx is not working // this error is different from the rest because it must be clear why nginx is not working
glog.Errorf(` glog.Errorf(`
@ -163,7 +165,17 @@ Error loading new template : %v
n.SetForceReload(true) 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 { if err != nil {
glog.Fatalf("invalid NGINX template: %v", err) 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"] dh, ok := secret.Data["dhparam.pem"]
if ok { if ok {
pemFileName, err := ssl.AddOrUpdateDHParam(nsSecName, dh) pemFileName, err := ssl.AddOrUpdateDHParam(nsSecName, dh, n.fileSystem)
if err != nil { if err != nil {
glog.Warningf("unexpected error adding or updating dhparam %v file: %v", nsSecName, err) glog.Warningf("unexpected error adding or updating dhparam %v file: %v", nsSecName, err)
} else { } else {
@ -584,6 +596,8 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
cfg.EnableBrotli = false cfg.EnableBrotli = false
} }
svc, _ := n.storeLister.GetService(n.cfg.PublishService)
tc := ngx_config.TemplateConfig{ tc := ngx_config.TemplateConfig{
ProxySetHeaders: setHeaders, ProxySetHeaders: setHeaders,
AddHeaders: addHeaders, AddHeaders: addHeaders,
@ -601,7 +615,7 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
RedirectServers: redirectServers, RedirectServers: redirectServers,
IsSSLPassthroughEnabled: n.isSSLPassthroughEnabled, IsSSLPassthroughEnabled: n.isSSLPassthroughEnabled,
ListenPorts: n.cfg.ListenPorts, ListenPorts: n.cfg.ListenPorts,
PublishService: n.GetPublishService(), PublishService: svc,
} }
content, err := n.t.Write(tc) 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" text_template "text/template"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/pkg/errors"
"github.com/pborman/uuid" "github.com/pborman/uuid"
extensions "k8s.io/api/extensions/v1beta1" extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations/ratelimit" "k8s.io/ingress-nginx/internal/ingress/annotations/ratelimit"
"k8s.io/ingress-nginx/internal/ingress/controller/config" "k8s.io/ingress-nginx/internal/ingress/controller/config"
ing_net "k8s.io/ingress-nginx/internal/net" ing_net "k8s.io/ingress-nginx/internal/net"
"k8s.io/ingress-nginx/internal/watch"
) )
const ( const (
@ -50,32 +51,33 @@ const (
// Template ... // Template ...
type Template struct { type Template struct {
tmpl *text_template.Template tmpl *text_template.Template
fw watch.FileWatcher //fw watch.FileWatcher
bp *BufferPool bp *BufferPool
} }
//NewTemplate returns a new Template instance or an //NewTemplate returns a new Template instance or an
//error if the specified template file contains errors //error if the specified template file contains errors
func NewTemplate(file string, onChange func()) (*Template, error) { func NewTemplate(file string, fs file.Filesystem) (*Template, error) {
tmpl, err := text_template.New("nginx.tmpl").Funcs(funcMap).ParseFiles(file) data, err := fs.ReadFile(file)
if err != nil { 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 { if err != nil {
return nil, err return nil, err
} }
return &Template{ return &Template{
tmpl: tmpl, tmpl: tmpl,
fw: fw, // fw: fw,
bp: NewBufferPool(defBufferSize), bp: NewBufferPool(defBufferSize),
}, nil }, nil
} }
// Close removes the file watcher // Close removes the file watcher
func (t *Template) Close() { func (t *Template) Close() {
t.fw.Close() //t.fw.Close()
} }
// Write populates a buffer using a template with NGINX configuration // Write populates a buffer using a template with NGINX configuration

View file

@ -26,6 +26,7 @@ import (
"strings" "strings"
"testing" "testing"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations/authreq" "k8s.io/ingress-nginx/internal/ingress/annotations/authreq"
"k8s.io/ingress-nginx/internal/ingress/annotations/rewrite" "k8s.io/ingress-nginx/internal/ingress/annotations/rewrite"
@ -174,13 +175,13 @@ func TestTemplateWithData(t *testing.T) {
if dat.ListenPorts == nil { if dat.ListenPorts == nil {
dat.ListenPorts = &config.ListenPorts{} 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 { if err != nil {
t.Errorf("invalid NGINX template: %v", err) t.Errorf("invalid NGINX template: %v", err)
} }
@ -207,13 +208,12 @@ func BenchmarkTemplateWithData(b *testing.B) {
b.Errorf("unexpected error unmarshalling json: %v", err) 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 { 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 { if err != nil {
b.Errorf("invalid NGINX template: %v", err) b.Errorf("invalid NGINX template: %v", err)
} }

View file

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

View file

@ -18,7 +18,6 @@ package store
import ( import (
"fmt" "fmt"
"io/ioutil"
"strings" "strings"
"github.com/golang/glog" "github.com/golang/glog"
@ -27,6 +26,7 @@ import (
apiv1 "k8s.io/api/core/v1" apiv1 "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1" extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser" "k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/k8s" "k8s.io/ingress-nginx/internal/k8s"
@ -57,7 +57,7 @@ func (s k8sStore) syncSecret(key string) {
s.sslStore.Update(key, cert) s.sslStore.Update(key, cert)
// this update must trigger an update // this update must trigger an update
// (like an update event from a change in Ingress) // (like an update event from a change in Ingress)
//ic.syncQueue.Enqueue(&extensions.Ingress{}) s.sendDummyEvent()
return return
} }
@ -65,7 +65,7 @@ func (s k8sStore) syncSecret(key string) {
s.sslStore.Add(key, cert) s.sslStore.Add(key, cert)
// this update must trigger an update // this update must trigger an update
// (like an update event from a change in Ingress) // (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. // 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 // If 'ca.crt' is also present, it will allow this secret to be used in the
// 'nginx.ingress.kubernetes.io/auth-tls-secret' annotation // '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 { if err != nil {
return nil, fmt.Errorf("unexpected error creating pem file: %v", err) 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) glog.V(3).Infof("found 'ca.crt', secret %v can also be used for Certificate Authentication", secretName)
} }
} else if ca != nil { } else if ca != nil {
sslCert, err = ssl.AddCertAuth(nsSecName, ca) sslCert, err = ssl.AddCertAuth(nsSecName, ca, s.filesystem)
if err != nil { if err != nil {
return nil, fmt.Errorf("unexpected error creating pem file: %v", err) return nil, fmt.Errorf("unexpected error creating pem file: %v", err)
@ -137,14 +137,21 @@ func (s k8sStore) checkSSLChainIssues() {
continue continue
} }
data, err := ssl.FullChainCert(secret.PemFileName) data, err := ssl.FullChainCert(secret.PemFileName, s.filesystem)
if err != nil { if err != nil {
glog.Errorf("unexpected error generating SSL certificate with full intermediate chain CA certs: %v", err) glog.Errorf("unexpected error generating SSL certificate with full intermediate chain CA certs: %v", err)
continue continue
} }
fullChainPemFileName := fmt.Sprintf("%v/%v-%v-full-chain.pem", ingress.DefaultSSLDirectory, secret.Namespace, secret.Name) fullChainPemFileName := fmt.Sprintf("%v/%v-%v-full-chain.pem", file.DefaultSSLDirectory, secret.Namespace, secret.Name)
err = ioutil.WriteFile(fullChainPemFileName, data, 0655)
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 { if err != nil {
glog.Errorf("unexpected error creating SSL certificate: %v", err) glog.Errorf("unexpected error creating SSL certificate: %v", err)
continue continue
@ -164,7 +171,7 @@ func (s k8sStore) checkSSLChainIssues() {
s.sslStore.Update(secretName, dst) s.sslStore.Update(secretName, dst)
// this update must trigger an update // this update must trigger an update
// (like an update event from a change in Ingress) // (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 ( import (
"encoding/base64" "encoding/base64"
"fmt"
"io/ioutil"
apiv1 "k8s.io/api/core/v1" apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
testclient "k8s.io/client-go/kubernetes/fake" testclient "k8s.io/client-go/kubernetes/fake"
cache_client "k8s.io/client-go/tools/cache" cache_client "k8s.io/client-go/tools/cache"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/ingress-nginx/internal/ingress"
) )
const ( const (
@ -115,14 +111,8 @@ func buildGenericControllerForBackendSSL() *NGINXController {
return gc 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) dCrt, err := base64.StdEncoding.DecodeString(tlsCrt)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err

View file

@ -25,6 +25,7 @@ import (
apiv1 "k8s.io/api/core/v1" apiv1 "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1" extensions "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
@ -35,6 +36,7 @@ import (
cache_client "k8s.io/client-go/tools/cache" cache_client "k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations" "k8s.io/ingress-nginx/internal/ingress/annotations"
"k8s.io/ingress-nginx/internal/ingress/annotations/class" "k8s.io/ingress-nginx/internal/ingress/annotations/class"
@ -162,6 +164,10 @@ type k8sStore struct {
sslStore *SSLCertTracker sslStore *SSLCertTracker
annotations annotations.Extractor annotations annotations.Extractor
filesystem file.Filesystem
updateCh chan Event
} }
// New creates a new object store to be used in the ingress controller // 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, namespace, configmap, tcp, udp string,
resyncPeriod time.Duration, resyncPeriod time.Duration,
client clientset.Interface, client clientset.Interface,
fs file.Filesystem,
updateCh chan Event) Storer { updateCh chan Event) Storer {
store := &k8sStore{ store := &k8sStore{
@ -176,6 +183,8 @@ func New(checkOCSP bool,
cache: &Controller{}, cache: &Controller{},
listers: &Lister{}, listers: &Lister{},
sslStore: NewSSLCertTracker(), sslStore: NewSSLCertTracker(),
filesystem: fs,
updateCh: updateCh,
} }
eventBroadcaster := record.NewBroadcaster() eventBroadcaster := record.NewBroadcaster()
@ -188,7 +197,7 @@ func New(checkOCSP bool,
}) })
// k8sStore fulfils resolver.Resolver interface // k8sStore fulfils resolver.Resolver interface
store.annotations = annotations.NewAnnotationExtractor(store) store.annotations = annotations.NewAnnotationExtractor(store, fs)
ingEventHandler := cache.ResourceEventHandlerFuncs{ ingEventHandler := cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) { AddFunc: func(obj interface{}) {
@ -494,9 +503,22 @@ func (s k8sStore) Run(stopCh chan struct{}) {
} }
// start goroutine to check for missing local secrets // 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 { if s.isOCSPCheckEnabled {
go wait.Until(s.checkSSLChainIssues, 60*time.Second, stopCh) 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" apiv1 "k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1" "k8s.io/api/extensions/v1beta1"
extensions "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" k8sErrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/test/e2e/framework" "k8s.io/ingress-nginx/test/e2e/framework"
) )
@ -68,6 +67,7 @@ func TestStore(t *testing.T) {
} }
}(updateCh) }(updateCh)
fs := newFS(t)
storer := New(true, storer := New(true,
ns.Name, ns.Name,
fmt.Sprintf("%v/config", ns.Name), fmt.Sprintf("%v/config", ns.Name),
@ -75,6 +75,7 @@ func TestStore(t *testing.T) {
fmt.Sprintf("%v/udp", ns.Name), fmt.Sprintf("%v/udp", ns.Name),
10*time.Minute, 10*time.Minute,
clientSet, clientSet,
fs,
updateCh) updateCh)
storer.Run(stopCh) storer.Run(stopCh)
@ -150,6 +151,7 @@ func TestStore(t *testing.T) {
} }
}(updateCh) }(updateCh)
fs := newFS(t)
storer := New(true, storer := New(true,
ns.Name, ns.Name,
fmt.Sprintf("%v/config", ns.Name), fmt.Sprintf("%v/config", ns.Name),
@ -157,6 +159,7 @@ func TestStore(t *testing.T) {
fmt.Sprintf("%v/udp", ns.Name), fmt.Sprintf("%v/udp", ns.Name),
10*time.Minute, 10*time.Minute,
clientSet, clientSet,
fs,
updateCh) updateCh)
storer.Run(stopCh) storer.Run(stopCh)
@ -239,7 +242,7 @@ func TestStore(t *testing.T) {
t.Errorf("unexpected error creating ingress: %v", err) 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 { if atomic.LoadUint64(&add) != 1 {
t.Errorf("expected 1 event of type Create but %v ocurred", add) 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 t.Run("should not receive events from new secret no referenced from ingress", func(t *testing.T) {
// test add ingress with secret it doesn't exists 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 // test add ingress with secret it doesn't exists and then add secret
// check secret is generated on fs // check secret is generated on fs
// check ocsp // check ocsp
@ -293,23 +511,10 @@ func ensureIngress(ingress *extensions.Ingress, clientSet *kubernetes.Clientset)
return s, nil return s, nil
} }
func waitForNoIngressInNamespace(c kubernetes.Interface, namespace, name string) error { func newFS(t *testing.T) file.Filesystem {
return wait.PollImmediate(1*time.Second, time.Minute*2, noIngressInNamespace(c, namespace, name)) fs, err := file.NewFakeFS()
} if err != nil {
t.Fatalf("unexpected error creating filesystem: %v", err)
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
} }
return fs
} }

View file

@ -35,14 +35,6 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver" "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 // Configuration holds the definition of all the parts required to describe all
// ingresses reachable by the ingress controller (using a filter by namespace) // ingresses reachable by the ingress controller (using a filter by namespace)
type Configuration struct { type Configuration struct {

View file

@ -26,10 +26,8 @@ import (
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"math/big" "math/big"
"net" "net"
"os"
"strconv" "strconv"
"time" "time"
@ -47,10 +45,12 @@ var (
) )
// AddOrUpdateCertAndKey creates a .pem file wth the cert and the key with the specified name // 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) 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)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not create temp pem file %v: %v", pemFileName, err) 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 { if err != nil {
return nil, fmt.Errorf("could not close temp pem file %v: %v", tempPemFile.Name(), err) 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 { if err != nil {
_ = os.Remove(tempPemFile.Name())
return nil, err return nil, err
} }
pemBlock, _ := pem.Decode(pemCerts) pemBlock, _ := pem.Decode(pemCerts)
if pemBlock == nil { if pemBlock == nil {
_ = os.Remove(tempPemFile.Name())
return nil, fmt.Errorf("no valid PEM formatted block found") 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 the file does not start with 'BEGIN CERTIFICATE' it's invalid and must not be used.
if pemBlock.Type != "CERTIFICATE" { 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) 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) pemCert, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil { if err != nil {
_ = os.Remove(tempPemFile.Name())
return nil, err return nil, err
} }
//Ensure that certificate and private key have a matching public key //Ensure that certificate and private key have a matching public key
if _, err := tls.X509KeyPair(cert, key); err != nil { if _, err := tls.X509KeyPair(cert, key); err != nil {
_ = os.Remove(tempPemFile.Name())
return nil, err 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 { if err != nil {
return nil, fmt.Errorf("could not move temp pem file %v to destination %v: %v", tempPemFile.Name(), pemFileName, err) 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) 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 { if err != nil {
return nil, fmt.Errorf("could not open file %v for writing additional CA chains: %v", pemFileName, err) 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")) _, err = caFile.Write([]byte("\n"))
if err != nil { if err != nil {
return nil, fmt.Errorf("could not append CA to cert file %v: %v", pemFileName, err) return nil, fmt.Errorf("could not append CA to cert file %v: %v", pemFileName, err)
} }
caFile.Write(ca) caFile.Write(ca)
caFile.Write([]byte("\n")) caFile.Write([]byte("\n"))
defer caFile.Close()
return &ingress.SSLCert{ return &ingress.SSLCert{
Certificate: pemCert, 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 // AddCertAuth creates a .pem file with the specified CAs to be used in Cert Authentication
// If it's already exists, it's clobbered. // 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) 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) pemCABlock, _ := pem.Decode(ca)
if pemCABlock == nil { if pemCABlock == nil {
@ -268,7 +270,13 @@ func AddCertAuth(name string, ca []byte) (*ingress.SSLCert, error) {
return nil, err 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 { if err != nil {
return nil, fmt.Errorf("could not write CA file %v: %v", caFileName, err) 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 // 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) 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) glog.V(3).Infof("Creating temp file %v for DH param: %v", tempPemFile.Name(), pemName)
if err != nil { 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) 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 { if err != nil {
_ = os.Remove(tempPemFile.Name())
return "", err return "", err
} }
pemBlock, _ := pem.Decode(pemCerts) pemBlock, _ := pem.Decode(pemCerts)
if pemBlock == nil { if pemBlock == nil {
_ = os.Remove(tempPemFile.Name())
return "", fmt.Errorf("no valid PEM formatted block found") 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 the file does not start with 'BEGIN DH PARAMETERS' it's invalid and must not be used.
if pemBlock.Type != "DH PARAMETERS" { if pemBlock.Type != "DH PARAMETERS" {
_ = os.Remove(tempPemFile.Name())
return "", fmt.Errorf("certificate %v contains invalid data", 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 { if err != nil {
return "", fmt.Errorf("could not move temp pem file %v to destination %v: %v", tempPemFile.Name(), pemFileName, err) 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 // FullChainCert checks if a certificate file contains issues in the intermediate CA chain
// Returns a new certificate with the intermediate certificates. // Returns a new certificate with the intermediate certificates.
// If the certificate does not contains issues with the chain it return an empty byte array // If the certificate does not contains issues with the chain it return an empty byte array
func FullChainCert(in string) ([]byte, error) { func FullChainCert(in string, fs file.Filesystem) ([]byte, error) {
inputFile, err := os.Open(in) data, err := fs.ReadFile(in)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(inputFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -19,14 +19,13 @@ package ssl
import ( import (
"crypto/x509" "crypto/x509"
"fmt" "fmt"
"io/ioutil"
"testing" "testing"
"time" "time"
certutil "k8s.io/client-go/util/cert" certutil "k8s.io/client-go/util/cert"
"k8s.io/client-go/util/cert/triple" "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 // 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) { func TestAddOrUpdateCertAndKey(t *testing.T) {
td, err := ioutil.TempDir("", "ssl") fs := newFS(t)
if err != nil {
t.Fatalf("Unexpected error creating temporal directory: %v", err)
}
ingress.DefaultSSLDirectory = td
cert, _, err := generateRSACerts("echoheaders") cert, _, err := generateRSACerts("echoheaders")
if err != nil { if err != nil {
@ -73,7 +68,7 @@ func TestAddOrUpdateCertAndKey(t *testing.T) {
c := certutil.EncodeCertPEM(cert.Cert) c := certutil.EncodeCertPEM(cert.Cert)
k := certutil.EncodePrivateKeyPEM(cert.Key) k := certutil.EncodePrivateKeyPEM(cert.Key)
ngxCert, err := AddOrUpdateCertAndKey(name, c, k, []byte{}) ngxCert, err := AddOrUpdateCertAndKey(name, c, k, []byte{}, fs)
if err != nil { if err != nil {
t.Fatalf("unexpected error checking SSL certificate: %v", err) t.Fatalf("unexpected error checking SSL certificate: %v", err)
} }
@ -92,11 +87,7 @@ func TestAddOrUpdateCertAndKey(t *testing.T) {
} }
func TestCACert(t *testing.T) { func TestCACert(t *testing.T) {
td, err := ioutil.TempDir("", "ssl") fs := newFS(t)
if err != nil {
t.Fatalf("Unexpected error creating temporal directory: %v", err)
}
ingress.DefaultSSLDirectory = td
cert, CA, err := generateRSACerts("echoheaders") cert, CA, err := generateRSACerts("echoheaders")
if err != nil { if err != nil {
@ -109,7 +100,7 @@ func TestCACert(t *testing.T) {
k := certutil.EncodePrivateKeyPEM(cert.Key) k := certutil.EncodePrivateKeyPEM(cert.Key)
ca := certutil.EncodeCertPEM(CA.Cert) ca := certutil.EncodeCertPEM(CA.Cert)
ngxCert, err := AddOrUpdateCertAndKey(name, c, k, ca) ngxCert, err := AddOrUpdateCertAndKey(name, c, k, ca, fs)
if err != nil { if err != nil {
t.Fatalf("unexpected error checking SSL certificate: %v", err) t.Fatalf("unexpected error checking SSL certificate: %v", err)
} }
@ -129,11 +120,10 @@ func TestGetFakeSSLCert(t *testing.T) {
} }
func TestAddCertAuth(t *testing.T) { func TestAddCertAuth(t *testing.T) {
td, err := ioutil.TempDir("", "ssl") fs, err := file.NewFakeFS()
if err != nil { 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" cn := "demo-ca"
_, ca, err := generateRSACerts(cn) _, ca, err := generateRSACerts(cn)
@ -141,7 +131,7 @@ func TestAddCertAuth(t *testing.T) {
t.Fatalf("unexpected error creating SSL certificate: %v", err) t.Fatalf("unexpected error creating SSL certificate: %v", err)
} }
c := certutil.EncodeCertPEM(ca.Cert) c := certutil.EncodeCertPEM(ca.Cert)
ic, err := AddCertAuth(cn, c) ic, err := AddCertAuth(cn, c, fs)
if err != nil { if err != nil {
t.Fatalf("unexpected error creating SSL certificate: %v", err) 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") 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" "gopkg.in/fsnotify.v1"
) )
// FileWatcher defines a watch over a file type FileWatcher interface {
type FileWatcher struct { Close() error
}
// OSFileWatcher defines a watch over a file
type OSFileWatcher struct {
file string file string
watcher *fsnotify.Watcher watcher *fsnotify.Watcher
// onEvent callback to be invoked after the file being watched changes // onEvent callback to be invoked after the file being watched changes
@ -34,7 +38,7 @@ type FileWatcher struct {
// NewFileWatcher creates a new FileWatcher // NewFileWatcher creates a new FileWatcher
func NewFileWatcher(file string, onEvent func()) (FileWatcher, error) { func NewFileWatcher(file string, onEvent func()) (FileWatcher, error) {
fw := FileWatcher{ fw := OSFileWatcher{
file: file, file: file,
onEvent: onEvent, onEvent: onEvent,
} }
@ -43,12 +47,12 @@ func NewFileWatcher(file string, onEvent func()) (FileWatcher, error) {
} }
// Close ends the watch // Close ends the watch
func (f *FileWatcher) Close() error { func (f OSFileWatcher) Close() error {
return f.watcher.Close() return f.watcher.Close()
} }
// watch creates a fsnotify watcher for a file and create of write events // 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() watcher, err := fsnotify.NewWatcher()
if err != nil { if err != nil {
return err 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 { func NewInt32(val int32) *int32 {
p := new(int32) p := new(int32)
*p = val *p = val

View file

@ -17,16 +17,7 @@ limitations under the License.
package ssl package ssl
import ( import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt" "fmt"
"io"
"math/big"
"net"
"strings" "strings"
"time" "time"
@ -37,15 +28,9 @@ import (
"k8s.io/api/extensions/v1beta1" "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
"k8s.io/ingress-nginx/test/e2e/framework" "k8s.io/ingress-nginx/test/e2e/framework"
) )
const (
rsaBits = 2048
validFor = 365 * 24 * time.Hour
)
var _ = framework.IngressNginxDescribe("SSL", func() { var _ = framework.IngressNginxDescribe("SSL", func() {
f := framework.NewDefaultFramework("ssl") f := framework.NewDefaultFramework("ssl")
@ -107,7 +92,8 @@ var _ = framework.IngressNginxDescribe("SSL", func() {
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(ing).ToNot(BeNil()) 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()) Expect(err).ToNot(HaveOccurred())
err = f.WaitForNginxServer(host, 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))) 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
}