Merge pull request #382 from rikatz/ingress-fake-cert
Ingress Fake Certificate generation
This commit is contained in:
commit
251990dcd8
3 changed files with 75 additions and 47 deletions
|
@ -43,28 +43,9 @@ func (ic *GenericController) syncSecret(k interface{}) error {
|
||||||
return fmt.Errorf("deferring sync till endpoints controller has synced")
|
return fmt.Errorf("deferring sync till endpoints controller has synced")
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the default certificate is configured
|
var key string
|
||||||
key := fmt.Sprintf("default/%v", defServerName)
|
|
||||||
_, exists := ic.sslCertTracker.Get(key)
|
|
||||||
var cert *ingress.SSLCert
|
var cert *ingress.SSLCert
|
||||||
var err error
|
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)
|
key = k.(string)
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -837,19 +838,30 @@ func (ic *GenericController) createServers(data []interface{},
|
||||||
CookiePath: bdef.ProxyCookiePath,
|
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
|
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)
|
defaultCertificate, err := ic.getPemCertificate(ic.cfg.DefaultSSLCertificate)
|
||||||
// If no default Certificate was supplied, tries to generate a new dumb one
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var cert *ingress.SSLCert
|
// 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.V(3).Infof("No Default SSL Certificate found. Generating a new one")
|
||||||
defCert, defKey := ssl.GetFakeSSLCert()
|
defCert, defKey := ssl.GetFakeSSLCert()
|
||||||
cert, err = ssl.AddOrUpdateCertAndKey("system-snake-oil-certificate", defCert, defKey, []byte{})
|
defaultCertificate, err = ssl.AddOrUpdateCertAndKey(fakeCertificate, defCert, defKey, []byte{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Fatalf("Error generating self signed certificate: %v", err)
|
glog.Fatalf("Error generating self signed certificate: %v", err)
|
||||||
|
}
|
||||||
|
defaultPemFileName = defaultCertificate.PemFileName
|
||||||
|
defaultPemSHA = defaultCertificate.PemSHA
|
||||||
} else {
|
} else {
|
||||||
defaultPemFileName = cert.PemFileName
|
defaultPemFileName = fakeCertificatePath
|
||||||
defaultPemSHA = cert.PemSHA
|
defaultPemSHA = ssl.PemSHA1(fakeCertificatePath)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
defaultPemFileName = defaultCertificate.PemFileName
|
defaultPemFileName = defaultCertificate.PemFileName
|
||||||
|
@ -944,9 +956,6 @@ func (ic *GenericController) createServers(data []interface{},
|
||||||
servers[host].SSLCertificate = cert.PemFileName
|
servers[host].SSLCertificate = cert.PemFileName
|
||||||
servers[host].SSLPemChecksum = cert.PemSHA
|
servers[host].SSLPemChecksum = cert.PemSHA
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
servers[host].SSLCertificate = defaultPemFileName
|
|
||||||
servers[host].SSLPemChecksum = defaultPemSHA
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,19 @@ limitations under the License.
|
||||||
package ssl
|
package ssl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
@ -63,21 +68,25 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
|
||||||
|
|
||||||
pemCerts, err := ioutil.ReadFile(tempPemFile.Name())
|
pemCerts, err := ioutil.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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,14 +129,14 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
|
||||||
return &ingress.SSLCert{
|
return &ingress.SSLCert{
|
||||||
CAFileName: pemFileName,
|
CAFileName: pemFileName,
|
||||||
PemFileName: pemFileName,
|
PemFileName: pemFileName,
|
||||||
PemSHA: pemSHA1(pemFileName),
|
PemSHA: PemSHA1(pemFileName),
|
||||||
CN: cn,
|
CN: cn,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ingress.SSLCert{
|
return &ingress.SSLCert{
|
||||||
PemFileName: pemFileName,
|
PemFileName: pemFileName,
|
||||||
PemSHA: pemSHA1(pemFileName),
|
PemSHA: PemSHA1(pemFileName),
|
||||||
CN: cn,
|
CN: cn,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -162,7 +171,7 @@ func AddCertAuth(name string, ca []byte) (*ingress.SSLCert, error) {
|
||||||
return &ingress.SSLCert{
|
return &ingress.SSLCert{
|
||||||
CAFileName: caFileName,
|
CAFileName: caFileName,
|
||||||
PemFileName: caFileName,
|
PemFileName: caFileName,
|
||||||
PemSHA: pemSHA1(caFileName),
|
PemSHA: PemSHA1(caFileName),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,9 +196,9 @@ func SearchDHParamFile(baseDir string) string {
|
||||||
return ""
|
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.
|
// reload NGINX in case a secret with a SSL certificate changed.
|
||||||
func pemSHA1(filename string) string {
|
func PemSHA1(filename string) string {
|
||||||
hasher := sha1.New()
|
hasher := sha1.New()
|
||||||
s, err := ioutil.ReadFile(filename)
|
s, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -200,23 +209,52 @@ func pemSHA1(filename string) string {
|
||||||
return hex.EncodeToString(hasher.Sum(nil))
|
return hex.EncodeToString(hasher.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
// GetFakeSSLCert creates a Self Signed Certificate
|
||||||
snakeOilPem = "/etc/ssl/certs/ssl-cert-snakeoil.pem"
|
// Based in the code https://golang.org/src/crypto/tls/generate_cert.go
|
||||||
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
|
|
||||||
func GetFakeSSLCert() ([]byte, []byte) {
|
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 {
|
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 {
|
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
|
return cert, key
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue