Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ingress Fake Certificate generation #382

Merged
merged 3 commits into from
Mar 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 1 addition & 20 deletions core/pkg/ingress/controller/backend_ssl.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,28 +43,9 @@ func (ic *GenericController) syncSecret(k interface{}) error {
return fmt.Errorf("deferring sync till endpoints controller has synced")
}

// check if the default certificate is configured
key := fmt.Sprintf("default/%v", defServerName)
_, exists := ic.sslCertTracker.Get(key)
var key string
var cert *ingress.SSLCert
var err error
if !exists {
if ic.cfg.DefaultSSLCertificate != "" {
cert, err = ic.getPemCertificate(ic.cfg.DefaultSSLCertificate)
if err != nil {
return err
}
} else {
defCert, defKey := ssl.GetFakeSSLCert()
cert, err = ssl.AddOrUpdateCertAndKey("system-snake-oil-certificate", defCert, defKey, []byte{})
if err != nil {
return nil
}
}
cert.Name = defServerName
cert.Namespace = api.NamespaceDefault
ic.sslCertTracker.Add(key, cert)
}

key = k.(string)

Expand Down
31 changes: 20 additions & 11 deletions core/pkg/ingress/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package controller

import (
"fmt"
"os"
"reflect"
"sort"
"strconv"
Expand Down Expand Up @@ -837,19 +838,30 @@ func (ic *GenericController) createServers(data []interface{},
CookiePath: bdef.ProxyCookiePath,
}

// This adds the Default Certificate to Default Backend and also for vhosts missing the secret
// This adds the Default Certificate to Default Backend (or generates a new self signed one)
var defaultPemFileName, defaultPemSHA string

// Tries to fetch the default Certificate. If it does not exists, generate a new self signed one.
defaultCertificate, err := ic.getPemCertificate(ic.cfg.DefaultSSLCertificate)
// If no default Certificate was supplied, tries to generate a new dumb one
if err != nil {
var cert *ingress.SSLCert
defCert, defKey := ssl.GetFakeSSLCert()
cert, err = ssl.AddOrUpdateCertAndKey("system-snake-oil-certificate", defCert, defKey, []byte{})
// This means the Default Secret does not exists, so we will create a new one.
fakeCertificate := "default-fake-certificate"
fakeCertificatePath := fmt.Sprintf("%v/%v.pem", ingress.DefaultSSLDirectory, fakeCertificate)

// Only generates a new certificate if it doesn't exists physically
_, err := os.Stat(fakeCertificatePath)
if err != nil {
glog.Fatalf("Error generating self signed certificate: %v", err)
glog.V(3).Infof("No Default SSL Certificate found. Generating a new one")
defCert, defKey := ssl.GetFakeSSLCert()
defaultCertificate, err = ssl.AddOrUpdateCertAndKey(fakeCertificate, defCert, defKey, []byte{})
if err != nil {
glog.Fatalf("Error generating self signed certificate: %v", err)
}
defaultPemFileName = defaultCertificate.PemFileName
defaultPemSHA = defaultCertificate.PemSHA
} else {
defaultPemFileName = cert.PemFileName
defaultPemSHA = cert.PemSHA
defaultPemFileName = fakeCertificatePath
defaultPemSHA = ssl.PemSHA1(fakeCertificatePath)
}
} else {
defaultPemFileName = defaultCertificate.PemFileName
Expand Down Expand Up @@ -944,9 +956,6 @@ func (ic *GenericController) createServers(data []interface{},
servers[host].SSLCertificate = cert.PemFileName
servers[host].SSLPemChecksum = cert.PemSHA
}
} else {
servers[host].SSLCertificate = defaultPemFileName
servers[host].SSLPemChecksum = defaultPemSHA
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this else statement was removed? This broke default TLS assignment on FQDN without a secretName. May we reinclude it? @rikatz @aledbf

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was removed due to a request from @bprashanth to not assign the default certificate on misconfigured ingresses.

Instead, I'm going to create a new ConfigMap to make this behaviour configurable (a heroku like), and it will be an admin choice to enable or not this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bprashanth PR #459 adds if tlsSecretName == "" before looking for a secret. Could this be changed to "use the default secret" instead a warning? This would fix a misbehavior on haproxy ingress. Note that this change doesn't assign the default cert on a misconfigured ingress.

}
}
Expand Down
70 changes: 54 additions & 16 deletions core/pkg/net/ssl/ssl.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@ limitations under the License.
package ssl

import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"math/big"
"os"
"time"

"github.com/golang/glog"

Expand Down Expand Up @@ -63,21 +68,25 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,

pemCerts, err := ioutil.ReadFile(tempPemFile.Name())
if err != nil {
_ = os.Remove(tempPemFile.Name())
return nil, err
}

pemBlock, _ := pem.Decode(pemCerts)
if pemBlock == nil {
_ = os.Remove(tempPemFile.Name())
return nil, fmt.Errorf("No valid PEM formatted block found")
}

// If the file does not start with 'BEGIN CERTIFICATE' it's invalid and must not be used.
if pemBlock.Type != "CERTIFICATE" {
_ = os.Remove(tempPemFile.Name())
return nil, fmt.Errorf("Certificate %v contains invalid data, and must be created with 'kubectl create secret tls'", name)
}

pemCert, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
_ = os.Remove(tempPemFile.Name())
return nil, err
}

Expand Down Expand Up @@ -120,14 +129,14 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
return &ingress.SSLCert{
CAFileName: pemFileName,
PemFileName: pemFileName,
PemSHA: pemSHA1(pemFileName),
PemSHA: PemSHA1(pemFileName),
CN: cn,
}, nil
}

return &ingress.SSLCert{
PemFileName: pemFileName,
PemSHA: pemSHA1(pemFileName),
PemSHA: PemSHA1(pemFileName),
CN: cn,
}, nil
}
Expand Down Expand Up @@ -162,7 +171,7 @@ func AddCertAuth(name string, ca []byte) (*ingress.SSLCert, error) {
return &ingress.SSLCert{
CAFileName: caFileName,
PemFileName: caFileName,
PemSHA: pemSHA1(caFileName),
PemSHA: PemSHA1(caFileName),
}, nil
}

Expand All @@ -187,9 +196,9 @@ func SearchDHParamFile(baseDir string) string {
return ""
}

// pemSHA1 returns the SHA1 of a pem file. This is used to
// PemSHA1 returns the SHA1 of a pem file. This is used to
// reload NGINX in case a secret with a SSL certificate changed.
func pemSHA1(filename string) string {
func PemSHA1(filename string) string {
hasher := sha1.New()
s, err := ioutil.ReadFile(filename)
if err != nil {
Expand All @@ -200,23 +209,52 @@ func pemSHA1(filename string) string {
return hex.EncodeToString(hasher.Sum(nil))
}

const (
snakeOilPem = "/etc/ssl/certs/ssl-cert-snakeoil.pem"
snakeOilKey = "/etc/ssl/private/ssl-cert-snakeoil.key"
)

// GetFakeSSLCert returns the snake oil ssl certificate created by the command
// make-ssl-cert generate-default-snakeoil --force-overwrite
// GetFakeSSLCert creates a Self Signed Certificate
// Based in the code https://golang.org/src/crypto/tls/generate_cert.go
func GetFakeSSLCert() ([]byte, []byte) {
cert, err := ioutil.ReadFile(snakeOilPem)

var priv interface{}
var err error

priv, err = rsa.GenerateKey(rand.Reader, 2048)

if err != nil {
return nil, nil
glog.Fatalf("failed to generate fake private key: %s", err)
}

key, err := ioutil.ReadFile(snakeOilKey)
notBefore := time.Now()
// This certificate is valid for 365 days
notAfter := notBefore.Add(365 * 24 * time.Hour)

serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)

if err != nil {
return nil, nil
glog.Fatalf("failed to generate fake serial number: %s", err)
}

template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Acme Co"},
CommonName: "Kubernetes Ingress Controller Fake Certificate",
},
NotBefore: notBefore,
NotAfter: notAfter,

KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
DNSNames: []string{"ingress.local"},
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.(*rsa.PrivateKey).PublicKey, priv)
if err != nil {
glog.Fatalf("Failed to create fake certificate: %s", err)
}

cert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})

key := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv.(*rsa.PrivateKey))})

return cert, key
}