Skip to content

Commit

Permalink
add TLS authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
jcmoraisjr committed Mar 10, 2019
1 parent 56b0bf2 commit 411f604
Show file tree
Hide file tree
Showing 17 changed files with 318 additions and 201 deletions.
15 changes: 10 additions & 5 deletions pkg/common/ingress/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -1532,15 +1532,17 @@ func (ic GenericController) Stop() error {
return fmt.Errorf("shutdown already in progress")
}

// StartControllers ...
func (ic *GenericController) StartControllers() {
ic.cacheController.Run(ic.stopCh)
}

// Start starts the Ingress controller.
func (ic *GenericController) Start() {
glog.Infof("starting Ingress controller")

ic.cacheController.Run(ic.stopCh)

createDefaultSSLCertificate()

if ic.cfg.V07 {
ic.CreateDefaultSSLCertificate()
time.Sleep(5 * time.Second)
// initial sync of secrets to avoid unnecessary reloads
glog.Info("running initial sync of secrets")
Expand Down Expand Up @@ -1587,7 +1589,8 @@ func (ic *GenericController) SetForceReload(shouldReload bool) {
}
}

func createDefaultSSLCertificate() {
// CreateDefaultSSLCertificate ...
func (ic *GenericController) CreateDefaultSSLCertificate() (path, hash string) {
defCert, defKey := ssl.GetFakeSSLCert()
c, err := ssl.AddOrUpdateCertAndKey(fakeCertificate, defCert, defKey, []byte{})
if err != nil {
Expand All @@ -1596,4 +1599,6 @@ func createDefaultSSLCertificate() {

fakeCertificateSHA = c.PemSHA
fakeCertificatePath = c.PemFileName

return fakeCertificatePath, fakeCertificateSHA
}
38 changes: 24 additions & 14 deletions pkg/controller/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ import (

api "k8s.io/api/core/v1"

"github.com/jcmoraisjr/haproxy-ingress/pkg/common/file"
"github.com/jcmoraisjr/haproxy-ingress/pkg/common/ingress"
"github.com/jcmoraisjr/haproxy-ingress/pkg/common/ingress/controller"
"github.com/jcmoraisjr/haproxy-ingress/pkg/common/net/ssl"
ingtypes "github.com/jcmoraisjr/haproxy-ingress/pkg/converters/ingress/types"
)

type cache struct {
Expand Down Expand Up @@ -56,44 +58,52 @@ func (c *cache) GetPod(podName string) (*api.Pod, error) {
return c.listers.Pod.GetPod(sname[0], sname[1])
}

func (c *cache) GetTLSSecretPath(secretName string) (string, error) {
func (c *cache) GetTLSSecretPath(secretName string) (ingtypes.File, error) {
sslCert, err := c.controller.GetCertificate(secretName)
if err != nil {
return "", err
return ingtypes.File{}, err
}
if sslCert.PemFileName == "" {
return "", fmt.Errorf("secret '%s' does not have keys 'tls.crt' and 'tls.key'", secretName)
return ingtypes.File{}, fmt.Errorf("secret '%s' does not have keys 'tls.crt' and 'tls.key'", secretName)
}
return sslCert.PemFileName, nil
return ingtypes.File{
Filename: sslCert.PemFileName,
SHA1Hash: sslCert.PemSHA,
}, nil
}

func (c *cache) GetCASecretPath(secretName string) (string, error) {
func (c *cache) GetCASecretPath(secretName string) (ingtypes.File, error) {
sslCert, err := c.controller.GetCertificate(secretName)
if err != nil {
return "", err
return ingtypes.File{}, err
}
if sslCert.CAFileName == "" {
return "", fmt.Errorf("secret '%s' does not have key 'ca.crt'", secretName)
return ingtypes.File{}, fmt.Errorf("secret '%s' does not have key 'ca.crt'", secretName)
}
return sslCert.CAFileName, nil
return ingtypes.File{
Filename: sslCert.CAFileName,
SHA1Hash: sslCert.PemSHA,
}, nil
}

func (c *cache) GetDHSecretPath(secretName string) (string, error) {
func (c *cache) GetDHSecretPath(secretName string) (ingtypes.File, error) {
secret, err := c.listers.Secret.GetByName(secretName)
if err != nil {
return "", err
return ingtypes.File{}, err
}
dh, found := secret.Data[dhparamFilename]
if !found {
return "", fmt.Errorf("secret '%s' does not have key '%s'", secretName, dhparamFilename)
return ingtypes.File{}, fmt.Errorf("secret '%s' does not have key '%s'", secretName, dhparamFilename)
}
pem := strings.Replace(secretName, "/", "_", -1)
pemFileName, err := ssl.AddOrUpdateDHParam(pem, dh)
if err != nil {
return "", fmt.Errorf("error creating dh-param file '%s': %v", pem, err)
return ingtypes.File{}, fmt.Errorf("error creating dh-param file '%s': %v", pem, err)
}
// file.SHA1(pemFileName)
return pemFileName, nil
return ingtypes.File{
Filename: pemFileName,
SHA1Hash: file.SHA1(pemFileName),
}, nil
}

func (c *cache) GetSecretContent(secretName, keyName string) ([]byte, error) {
Expand Down
39 changes: 30 additions & 9 deletions pkg/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ func (hc *HAProxyController) Info() *ingress.BackendInfo {
// Start starts the controller
func (hc *HAProxyController) Start() {
hc.controller = controller.NewIngressController(hc)
hc.controller.StartControllers()
hc.configController()
hc.controller.Start()
}
Expand All @@ -96,13 +97,6 @@ func (hc *HAProxyController) configController() {

// starting v0.8 only config
logger := &logger{depth: 1}
hc.converterOptions = &ingtypes.ConverterOptions{
Logger: logger,
Cache: newCache(hc.storeLister, hc.controller),
AnnotationPrefix: "ingress.kubernetes.io",
DefaultBackend: hc.cfg.DefaultService,
DefaultSSLSecret: hc.cfg.DefaultSSLCertificate,
}
instanceOptions := haproxy.InstanceOptions{
HAProxyCmd: "haproxy",
ReloadCmd: "/haproxy-reload.sh",
Expand All @@ -114,6 +108,32 @@ func (hc *HAProxyController) configController() {
if err := hc.instance.ParseTemplates(); err != nil {
glog.Fatalf("error creating HAProxy instance: %v", err)
}
cache := newCache(hc.storeLister, hc.controller)
hc.converterOptions = &ingtypes.ConverterOptions{
Logger: logger,
Cache: cache,
AnnotationPrefix: "ingress.kubernetes.io",
DefaultBackend: hc.cfg.DefaultService,
DefaultSSLFile: hc.createDefaultSSLFile(cache),
}
}

func (hc *HAProxyController) createDefaultSSLFile(cache *cache) (tlsFile ingtypes.File) {
if hc.cfg.DefaultSSLCertificate != "" {
tlsFile, err := cache.GetTLSSecretPath(hc.cfg.DefaultSSLCertificate)
if err == nil {
return tlsFile
}
glog.Warningf("using auto generated fake certificate due to an error reading default TLS certificate: %v", err)
} else {
glog.Info("using auto generated fake certificate")
}
path, hash := hc.controller.CreateDefaultSSLCertificate()
tlsFile = ingtypes.File{
Filename: path,
SHA1Hash: hash,
}
return tlsFile
}

// CreateX509CertsDir hard link files from certs to a single directory.
Expand All @@ -126,11 +146,12 @@ func (hc *HAProxyController) CreateX509CertsDir(bindName string, certs []string)
return "", err
}
for _, cert := range certs {
file, err := os.Stat(cert)
srcFile, err := os.Stat(cert)
if err != nil {
return "", err
}
if err := os.Link(cert, x509dir+"/"+file.Name()); err != os.ErrExist {
dstFile := x509dir + "/" + srcFile.Name()
if err := os.Link(cert, dstFile); err != nil {
return "", err
}
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/converters/ingress/annotations/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ func (c *updater) buildGlobalSSL(d *globalData) {
d.global.SSL.Ciphers = d.config.SSLCiphers
d.global.SSL.Options = d.config.SSLOptions
if d.config.SSLDHParam != "" {
if dhFilename, err := c.cache.GetDHSecretPath(d.config.SSLDHParam); err == nil {
d.global.SSL.DHParam.Filename = dhFilename
if dhFile, err := c.cache.GetDHSecretPath(d.config.SSLDHParam); err == nil {
d.global.SSL.DHParam.Filename = dhFile.Filename
} else {
c.logger.Error("error reading DH params: %v", err)
}
Expand Down
10 changes: 9 additions & 1 deletion pkg/converters/ingress/annotations/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,18 @@ func (c *updater) buildHostAuthTLS(d *hostData) {
if d.ann.AuthTLSSecret == "" {
return
}
verify := d.ann.AuthTLSVerifyClient
if verify == "off" {
return
}
if cafile, err := c.cache.GetCASecretPath(d.ann.AuthTLSSecret); err == nil {
d.host.TLS.CAFilename = cafile
d.host.TLS.CAFilename = cafile.Filename
d.host.TLS.CAHash = cafile.SHA1Hash
d.host.TLS.CAVerifyOptional = verify == "optional" || verify == "optional_no_ca"
d.host.TLS.CAErrorPage = d.ann.AuthTLSErrorPage
d.host.TLS.AddCertHeader = d.ann.AuthTLSCertHeader
} else {
c.logger.Error("error building TLS auth config: %v", err)
}
}

Expand Down
30 changes: 21 additions & 9 deletions pkg/converters/ingress/helper_test/cachemock.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ limitations under the License.
package helper_test

import (
"crypto/sha1"
"fmt"
"strings"

api "k8s.io/api/core/v1"

ingtypes "github.com/jcmoraisjr/haproxy-ingress/pkg/converters/ingress/types"
)

// SecretContent ...
Expand Down Expand Up @@ -68,27 +71,36 @@ func (c *CacheMock) GetPod(podName string) (*api.Pod, error) {
}

// GetTLSSecretPath ...
func (c *CacheMock) GetTLSSecretPath(secretName string) (string, error) {
func (c *CacheMock) GetTLSSecretPath(secretName string) (ingtypes.File, error) {
if path, found := c.SecretTLSPath[secretName]; found {
return path, nil
return ingtypes.File{
Filename: path,
SHA1Hash: fmt.Sprintf("%x", sha1.Sum([]byte(path))),
}, nil
}
return "", fmt.Errorf("secret not found: '%s'", secretName)
return ingtypes.File{}, fmt.Errorf("secret not found: '%s'", secretName)
}

// GetCASecretPath ...
func (c *CacheMock) GetCASecretPath(secretName string) (string, error) {
func (c *CacheMock) GetCASecretPath(secretName string) (ingtypes.File, error) {
if path, found := c.SecretCAPath[secretName]; found {
return path, nil
return ingtypes.File{
Filename: path,
SHA1Hash: fmt.Sprintf("%x", sha1.Sum([]byte(path))),
}, nil
}
return "", fmt.Errorf("secret not found: '%s'", secretName)
return ingtypes.File{}, fmt.Errorf("secret not found: '%s'", secretName)
}

// GetDHSecretPath ...
func (c *CacheMock) GetDHSecretPath(secretName string) (string, error) {
func (c *CacheMock) GetDHSecretPath(secretName string) (ingtypes.File, error) {
if path, found := c.SecretDHPath[secretName]; found {
return path, nil
return ingtypes.File{
Filename: path,
SHA1Hash: fmt.Sprintf("%x", sha1.Sum([]byte(path))),
}, nil
}
return "", fmt.Errorf("secret not found: '%s'", secretName)
return ingtypes.File{}, fmt.Errorf("secret not found: '%s'", secretName)
}

// GetSecretContent ...
Expand Down
43 changes: 16 additions & 27 deletions pkg/converters/ingress/ingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func NewIngressConverter(options *ingtypes.ConverterOptions, haproxy haproxy.Con
hostAnnotations: map[*hatypes.Host]*ingtypes.HostAnnotations{},
backendAnnotations: map[*hatypes.Backend]*ingtypes.BackendAnnotations{},
}
haproxy.ConfigDefaultX509Cert(options.DefaultSSLFile.Filename)
if options.DefaultBackend != "" {
if backend, err := c.addBackend(options.DefaultBackend, 0, &ingtypes.BackendAnnotations{}); err == nil {
haproxy.ConfigDefaultBackend(backend)
Expand Down Expand Up @@ -121,19 +122,16 @@ func (c *converter) syncIngress(ing *extensions.Ingress) {
for _, tls := range ing.Spec.TLS {
for _, tlshost := range tls.Hosts {
if tlshost == hostname {
tlsPath, err := c.addTLS(ing.Namespace, tls.SecretName)
if err == nil {
if host.TLS.TLSFilename == "" {
host.TLS.TLSFilename = tlsPath
} else if host.TLS.TLSFilename != tlsPath {
err = fmt.Errorf("TLS of host '%s' was already assigned", host.Hostname)
}
}
if err != nil {
tlsPath := c.addTLS(ing.Namespace, tls.SecretName)
if host.TLS.TLSHash == "" {
host.TLS.TLSFilename = tlsPath.Filename
host.TLS.TLSHash = tlsPath.SHA1Hash
} else if host.TLS.TLSHash != tlsPath.SHA1Hash {
msg := fmt.Sprintf("TLS of host '%s' was already assigned", host.Hostname)
if tls.SecretName != "" {
c.logger.Warn("skipping TLS secret '%s' of ingress '%s': %v", tls.SecretName, fullIngName, err)
c.logger.Warn("skipping TLS secret '%s' of ingress '%s': %s", tls.SecretName, fullIngName, msg)
} else {
c.logger.Warn("skipping default TLS secret of ingress '%s': %v", fullIngName, err)
c.logger.Warn("skipping default TLS secret of ingress '%s': %s", fullIngName, msg)
}
}
}
Expand Down Expand Up @@ -230,25 +228,16 @@ func (c *converter) addHTTPPassthrough(fullSvcName string, ingFrontAnn *ingtypes
}
}

func (c *converter) addTLS(namespace, secretName string) (string, error) {
defaultSecret := c.options.DefaultSSLSecret
tlsSecretName := defaultSecret
func (c *converter) addTLS(namespace, secretName string) ingtypes.File {
if secretName != "" {
tlsSecretName = namespace + "/" + secretName
}
tlsPath, err := c.cache.GetTLSSecretPath(tlsSecretName)
if err != nil {
if tlsSecretName == defaultSecret {
return "", err
}
tlsSecretErr := err
tlsPath, err = c.cache.GetTLSSecretPath(defaultSecret)
if err != nil {
return "", fmt.Errorf("failed to use custom and default certificate. custom: %v; default: %v", tlsSecretErr, err)
tlsSecretName := namespace + "/" + secretName
tlsFile, err := c.cache.GetTLSSecretPath(tlsSecretName)
if err == nil {
return tlsFile
}
c.logger.Warn("using default certificate due to an error reading secret '%s': %v", tlsSecretName, tlsSecretErr)
c.logger.Warn("using default certificate due to an error reading secret '%s': %v", tlsSecretName, err)
}
return tlsPath, nil
return c.options.DefaultSSLFile
}

func (c *converter) addEndpoints(svc *api.Service, servicePort int, backend *hatypes.Backend) error {
Expand Down
Loading

0 comments on commit 411f604

Please sign in to comment.