mirror of
https://github.com/rancher/dynamiclistener.git
synced 2025-09-15 06:20:30 +00:00
Refactor to not include a server by default
This commit is contained in:
80
factory/ca.go
Normal file
80
factory/ca.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package factory
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/x509"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
func GenCA() (*x509.Certificate, *ecdsa.PrivateKey, error) {
|
||||
caKey, err := NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
caCert, err := NewSelfSignedCACert(caKey, "dynamiclistener-ca", "dynamiclistener-org")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return caCert, caKey, nil
|
||||
}
|
||||
|
||||
func LoadOrGenCA() (*x509.Certificate, *ecdsa.PrivateKey, error) {
|
||||
cert, key, err := loadCA()
|
||||
if err == nil {
|
||||
return cert, key, nil
|
||||
}
|
||||
|
||||
cert, key, err = GenCA()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
certBytes, keyBytes, err := Marshal(cert, key)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll("./certs", 0700); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile("./certs/ca.pem", certBytes, 0600); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile("./certs/ca.key", keyBytes, 0600); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return cert, key, nil
|
||||
}
|
||||
|
||||
func loadCA() (*x509.Certificate, *ecdsa.PrivateKey, error) {
|
||||
return LoadCerts("./certs/ca.pem", "./certs/ca.key")
|
||||
}
|
||||
|
||||
func LoadCerts(certFile, keyFile string) (*x509.Certificate, *ecdsa.PrivateKey, error) {
|
||||
caPem, err := ioutil.ReadFile(certFile)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
caKey, err := ioutil.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
key, err := ParseECPrivateKeyPEM(caKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cert, err := ParseCertPEM(caPem)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return cert, key, nil
|
||||
}
|
105
factory/cert_utils.go
Normal file
105
factory/cert_utils.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package factory
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ECPrivateKeyBlockType = "EC PRIVATE KEY"
|
||||
CertificateBlockType = "CERTIFICATE"
|
||||
)
|
||||
|
||||
func NewSelfSignedCACert(key crypto.Signer, cn string, org ...string) (*x509.Certificate, error) {
|
||||
now := time.Now()
|
||||
tmpl := x509.Certificate{
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
NotAfter: now.Add(time.Hour * 24 * 365 * 10).UTC(),
|
||||
NotBefore: now.UTC(),
|
||||
SerialNumber: new(big.Int).SetInt64(0),
|
||||
Subject: pkix.Name{
|
||||
CommonName: cn,
|
||||
Organization: org,
|
||||
},
|
||||
}
|
||||
|
||||
certDERBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, key.Public(), key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return x509.ParseCertificate(certDERBytes)
|
||||
}
|
||||
|
||||
func NewSignedCert(signer crypto.Signer, caCert *x509.Certificate, caKey crypto.Signer, cn string, orgs []string,
|
||||
domains []string, ips []net.IP) (*x509.Certificate, error) {
|
||||
|
||||
serialNumber, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parent := x509.Certificate{
|
||||
DNSNames: domains,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
IPAddresses: ips,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * 365).UTC(),
|
||||
NotBefore: caCert.NotBefore,
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: cn,
|
||||
Organization: orgs,
|
||||
},
|
||||
}
|
||||
|
||||
cert, err := x509.CreateCertificate(rand.Reader, &parent, caCert, signer.Public(), caKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return x509.ParseCertificate(cert)
|
||||
}
|
||||
|
||||
func ParseECPrivateKeyPEM(keyData []byte) (*ecdsa.PrivateKey, error) {
|
||||
var privateKeyPemBlock *pem.Block
|
||||
for {
|
||||
privateKeyPemBlock, keyData = pem.Decode(keyData)
|
||||
if privateKeyPemBlock == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if privateKeyPemBlock.Type == ECPrivateKeyBlockType {
|
||||
return x509.ParseECPrivateKey(privateKeyPemBlock.Bytes)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("pem does not include a valid EC private key")
|
||||
}
|
||||
|
||||
func ParseCertPEM(pemCerts []byte) (*x509.Certificate, error) {
|
||||
var pemBlock *pem.Block
|
||||
for {
|
||||
pemBlock, pemCerts = pem.Decode(pemCerts)
|
||||
if pemBlock == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if pemBlock.Type == CertificateBlockType {
|
||||
return x509.ParseCertificate(pemBlock.Bytes)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("pem does not include a valid x509 cert")
|
||||
}
|
164
factory/gen.go
Normal file
164
factory/gen.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package factory
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
cnPrefix = "listener.cattle.io/cn-"
|
||||
static = "listener.cattle.io/static"
|
||||
hashKey = "listener.cattle.io/hash"
|
||||
)
|
||||
|
||||
type TLS struct {
|
||||
CACert *x509.Certificate
|
||||
CAKey crypto.Signer
|
||||
CN string
|
||||
Organization []string
|
||||
}
|
||||
|
||||
func collectCNs(secret *v1.Secret) (domains []string, ips []net.IP, hash string, err error) {
|
||||
var (
|
||||
cns []string
|
||||
digest = sha256.New()
|
||||
)
|
||||
for k, v := range secret.Annotations {
|
||||
if strings.HasPrefix(k, cnPrefix) {
|
||||
cns = append(cns, v)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(cns)
|
||||
|
||||
for _, v := range cns {
|
||||
digest.Write([]byte(v))
|
||||
ip := net.ParseIP(v)
|
||||
if ip == nil {
|
||||
domains = append(domains, v)
|
||||
} else {
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
}
|
||||
|
||||
hash = hex.EncodeToString(digest.Sum(nil))
|
||||
return
|
||||
}
|
||||
|
||||
func (t *TLS) AddCN(secret *v1.Secret, cn ...string) (*v1.Secret, bool, error) {
|
||||
var (
|
||||
err error
|
||||
)
|
||||
|
||||
if !NeedsUpdate(secret, cn...) {
|
||||
return secret, false, nil
|
||||
}
|
||||
|
||||
secret = populateCN(secret, cn...)
|
||||
|
||||
privateKey, err := getPrivateKey(secret)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
domains, ips, hash, err := collectCNs(secret)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
newCert, err := t.newCert(domains, ips, privateKey)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
certBytes, keyBytes, err := Marshal(newCert, privateKey)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if secret.Data == nil {
|
||||
secret.Data = map[string][]byte{}
|
||||
}
|
||||
secret.Data[v1.TLSCertKey] = certBytes
|
||||
secret.Data[v1.TLSPrivateKeyKey] = keyBytes
|
||||
secret.Annotations[hashKey] = hash
|
||||
|
||||
return secret, true, nil
|
||||
}
|
||||
|
||||
func (t *TLS) newCert(domains []string, ips []net.IP, privateKey *ecdsa.PrivateKey) (*x509.Certificate, error) {
|
||||
return NewSignedCert(privateKey, t.CACert, t.CAKey, t.CN, t.Organization, domains, ips)
|
||||
}
|
||||
|
||||
func populateCN(secret *v1.Secret, cn ...string) *v1.Secret {
|
||||
secret = secret.DeepCopy()
|
||||
if secret.Annotations == nil {
|
||||
secret.Annotations = map[string]string{}
|
||||
}
|
||||
for _, cn := range cn {
|
||||
secret.Annotations[cnPrefix+cn] = cn
|
||||
}
|
||||
return secret
|
||||
}
|
||||
|
||||
func NeedsUpdate(secret *v1.Secret, cn ...string) bool {
|
||||
if secret.Annotations[static] == "true" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, cn := range cn {
|
||||
if secret.Annotations[cnPrefix+cn] == "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func getPrivateKey(secret *v1.Secret) (*ecdsa.PrivateKey, error) {
|
||||
keyBytes := secret.Data[v1.TLSPrivateKeyKey]
|
||||
if len(keyBytes) == 0 {
|
||||
return NewPrivateKey()
|
||||
}
|
||||
|
||||
privateKey, err := ParseECPrivateKeyPEM(keyBytes)
|
||||
if err == nil {
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
return NewPrivateKey()
|
||||
}
|
||||
|
||||
func Marshal(x509Cert *x509.Certificate, privateKey *ecdsa.PrivateKey) ([]byte, []byte, error) {
|
||||
certBlock := pem.Block{
|
||||
Type: CertificateBlockType,
|
||||
Bytes: x509Cert.Raw,
|
||||
}
|
||||
|
||||
keyBytes, err := x509.MarshalECPrivateKey(privateKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
keyBlock := pem.Block{
|
||||
Type: ECPrivateKeyBlockType,
|
||||
Bytes: keyBytes,
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(&certBlock), pem.EncodeToMemory(&keyBlock), nil
|
||||
}
|
||||
|
||||
func NewPrivateKey() (*ecdsa.PrivateKey, error) {
|
||||
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
}
|
Reference in New Issue
Block a user