Skip to content

Commit af04867

Browse files
Refactor to not include a server by default
1 parent 8a2488b commit af04867

File tree

14 files changed

+906
-680
lines changed

14 files changed

+906
-680
lines changed

factory/ca.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package factory
2+
3+
import (
4+
"crypto/ecdsa"
5+
"crypto/x509"
6+
"io/ioutil"
7+
"os"
8+
)
9+
10+
func GenCA() (*x509.Certificate, *ecdsa.PrivateKey, error) {
11+
caKey, err := NewPrivateKey()
12+
if err != nil {
13+
return nil, nil, err
14+
}
15+
16+
caCert, err := NewSelfSignedCACert(caKey, "dynamiclistener-ca", "dynamiclistener-org")
17+
if err != nil {
18+
return nil, nil, err
19+
}
20+
21+
return caCert, caKey, nil
22+
}
23+
24+
func LoadOrGenCA() (*x509.Certificate, *ecdsa.PrivateKey, error) {
25+
cert, key, err := loadCA()
26+
if err == nil {
27+
return cert, key, nil
28+
}
29+
30+
cert, key, err = GenCA()
31+
if err != nil {
32+
return nil, nil, err
33+
}
34+
35+
certBytes, keyBytes, err := Marshal(cert, key)
36+
if err != nil {
37+
return nil, nil, err
38+
}
39+
40+
if err := os.MkdirAll("./certs", 0700); err != nil {
41+
return nil, nil, err
42+
}
43+
44+
if err := ioutil.WriteFile("./certs/ca.pem", certBytes, 0600); err != nil {
45+
return nil, nil, err
46+
}
47+
48+
if err := ioutil.WriteFile("./certs/ca.key", keyBytes, 0600); err != nil {
49+
return nil, nil, err
50+
}
51+
52+
return cert, key, nil
53+
}
54+
55+
func loadCA() (*x509.Certificate, *ecdsa.PrivateKey, error) {
56+
return LoadCerts("./certs/ca.pem", "./certs/ca.key")
57+
}
58+
59+
func LoadCerts(certFile, keyFile string) (*x509.Certificate, *ecdsa.PrivateKey, error) {
60+
caPem, err := ioutil.ReadFile(certFile)
61+
if err != nil {
62+
return nil, nil, err
63+
}
64+
caKey, err := ioutil.ReadFile(keyFile)
65+
if err != nil {
66+
return nil, nil, err
67+
}
68+
69+
key, err := ParseECPrivateKeyPEM(caKey)
70+
if err != nil {
71+
return nil, nil, err
72+
}
73+
74+
cert, err := ParseCertPEM(caPem)
75+
if err != nil {
76+
return nil, nil, err
77+
}
78+
79+
return cert, key, nil
80+
}

factory/cert_utils.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package factory
2+
3+
import (
4+
"crypto"
5+
"crypto/ecdsa"
6+
"crypto/rand"
7+
"crypto/x509"
8+
"crypto/x509/pkix"
9+
"encoding/pem"
10+
"fmt"
11+
"math"
12+
"math/big"
13+
"net"
14+
"time"
15+
)
16+
17+
const (
18+
ECPrivateKeyBlockType = "EC PRIVATE KEY"
19+
CertificateBlockType = "CERTIFICATE"
20+
)
21+
22+
func NewSelfSignedCACert(key crypto.Signer, cn string, org ...string) (*x509.Certificate, error) {
23+
now := time.Now()
24+
tmpl := x509.Certificate{
25+
BasicConstraintsValid: true,
26+
IsCA: true,
27+
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
28+
NotAfter: now.Add(time.Hour * 24 * 365 * 10).UTC(),
29+
NotBefore: now.UTC(),
30+
SerialNumber: new(big.Int).SetInt64(0),
31+
Subject: pkix.Name{
32+
CommonName: cn,
33+
Organization: org,
34+
},
35+
}
36+
37+
certDERBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, key.Public(), key)
38+
if err != nil {
39+
return nil, err
40+
}
41+
42+
return x509.ParseCertificate(certDERBytes)
43+
}
44+
45+
func NewSignedCert(signer crypto.Signer, caCert *x509.Certificate, caKey crypto.Signer, cn string, orgs []string,
46+
domains []string, ips []net.IP) (*x509.Certificate, error) {
47+
48+
serialNumber, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
49+
if err != nil {
50+
return nil, err
51+
}
52+
53+
parent := x509.Certificate{
54+
DNSNames: domains,
55+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
56+
IPAddresses: ips,
57+
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
58+
NotAfter: time.Now().Add(time.Hour * 24 * 365).UTC(),
59+
NotBefore: caCert.NotBefore,
60+
SerialNumber: serialNumber,
61+
Subject: pkix.Name{
62+
CommonName: cn,
63+
Organization: orgs,
64+
},
65+
}
66+
67+
cert, err := x509.CreateCertificate(rand.Reader, &parent, caCert, signer.Public(), caKey)
68+
if err != nil {
69+
return nil, err
70+
}
71+
72+
return x509.ParseCertificate(cert)
73+
}
74+
75+
func ParseECPrivateKeyPEM(keyData []byte) (*ecdsa.PrivateKey, error) {
76+
var privateKeyPemBlock *pem.Block
77+
for {
78+
privateKeyPemBlock, keyData = pem.Decode(keyData)
79+
if privateKeyPemBlock == nil {
80+
break
81+
}
82+
83+
if privateKeyPemBlock.Type == ECPrivateKeyBlockType {
84+
return x509.ParseECPrivateKey(privateKeyPemBlock.Bytes)
85+
}
86+
}
87+
88+
return nil, fmt.Errorf("pem does not include a valid EC private key")
89+
}
90+
91+
func ParseCertPEM(pemCerts []byte) (*x509.Certificate, error) {
92+
var pemBlock *pem.Block
93+
for {
94+
pemBlock, pemCerts = pem.Decode(pemCerts)
95+
if pemBlock == nil {
96+
break
97+
}
98+
99+
if pemBlock.Type == CertificateBlockType {
100+
return x509.ParseCertificate(pemBlock.Bytes)
101+
}
102+
}
103+
104+
return nil, fmt.Errorf("pem does not include a valid x509 cert")
105+
}

factory/gen.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package factory
2+
3+
import (
4+
"crypto"
5+
"crypto/ecdsa"
6+
"crypto/elliptic"
7+
"crypto/rand"
8+
"crypto/sha256"
9+
"crypto/x509"
10+
"encoding/hex"
11+
"encoding/pem"
12+
"net"
13+
"sort"
14+
"strings"
15+
16+
v1 "k8s.io/api/core/v1"
17+
)
18+
19+
const (
20+
cnPrefix = "listener.cattle.io/cn-"
21+
static = "listener.cattle.io/static"
22+
hashKey = "listener.cattle.io/hash"
23+
)
24+
25+
type TLS struct {
26+
CACert *x509.Certificate
27+
CAKey crypto.Signer
28+
CN string
29+
Organization []string
30+
}
31+
32+
func collectCNs(secret *v1.Secret) (domains []string, ips []net.IP, hash string, err error) {
33+
var (
34+
cns []string
35+
digest = sha256.New()
36+
)
37+
for k, v := range secret.Annotations {
38+
if strings.HasPrefix(k, cnPrefix) {
39+
cns = append(cns, v)
40+
}
41+
}
42+
43+
sort.Strings(cns)
44+
45+
for _, v := range cns {
46+
digest.Write([]byte(v))
47+
ip := net.ParseIP(v)
48+
if ip == nil {
49+
domains = append(domains, v)
50+
} else {
51+
ips = append(ips, ip)
52+
}
53+
}
54+
55+
hash = hex.EncodeToString(digest.Sum(nil))
56+
return
57+
}
58+
59+
func (t *TLS) AddCN(secret *v1.Secret, cn ...string) (*v1.Secret, bool, error) {
60+
var (
61+
err error
62+
)
63+
64+
if !NeedsUpdate(secret, cn...) {
65+
return secret, false, nil
66+
}
67+
68+
secret = populateCN(secret, cn...)
69+
70+
privateKey, err := getPrivateKey(secret)
71+
if err != nil {
72+
return nil, false, err
73+
}
74+
75+
domains, ips, hash, err := collectCNs(secret)
76+
if err != nil {
77+
return nil, false, err
78+
}
79+
80+
newCert, err := t.newCert(domains, ips, privateKey)
81+
if err != nil {
82+
return nil, false, err
83+
}
84+
85+
certBytes, keyBytes, err := Marshal(newCert, privateKey)
86+
if err != nil {
87+
return nil, false, err
88+
}
89+
90+
if secret.Data == nil {
91+
secret.Data = map[string][]byte{}
92+
}
93+
secret.Data[v1.TLSCertKey] = certBytes
94+
secret.Data[v1.TLSPrivateKeyKey] = keyBytes
95+
secret.Annotations[hashKey] = hash
96+
97+
return secret, true, nil
98+
}
99+
100+
func (t *TLS) newCert(domains []string, ips []net.IP, privateKey *ecdsa.PrivateKey) (*x509.Certificate, error) {
101+
return NewSignedCert(privateKey, t.CACert, t.CAKey, t.CN, t.Organization, domains, ips)
102+
}
103+
104+
func populateCN(secret *v1.Secret, cn ...string) *v1.Secret {
105+
secret = secret.DeepCopy()
106+
if secret.Annotations == nil {
107+
secret.Annotations = map[string]string{}
108+
}
109+
for _, cn := range cn {
110+
secret.Annotations[cnPrefix+cn] = cn
111+
}
112+
return secret
113+
}
114+
115+
func NeedsUpdate(secret *v1.Secret, cn ...string) bool {
116+
if secret.Annotations[static] == "true" {
117+
return false
118+
}
119+
120+
for _, cn := range cn {
121+
if secret.Annotations[cnPrefix+cn] == "" {
122+
return true
123+
}
124+
}
125+
126+
return false
127+
}
128+
129+
func getPrivateKey(secret *v1.Secret) (*ecdsa.PrivateKey, error) {
130+
keyBytes := secret.Data[v1.TLSPrivateKeyKey]
131+
if len(keyBytes) == 0 {
132+
return NewPrivateKey()
133+
}
134+
135+
privateKey, err := ParseECPrivateKeyPEM(keyBytes)
136+
if err == nil {
137+
return privateKey, nil
138+
}
139+
140+
return NewPrivateKey()
141+
}
142+
143+
func Marshal(x509Cert *x509.Certificate, privateKey *ecdsa.PrivateKey) ([]byte, []byte, error) {
144+
certBlock := pem.Block{
145+
Type: CertificateBlockType,
146+
Bytes: x509Cert.Raw,
147+
}
148+
149+
keyBytes, err := x509.MarshalECPrivateKey(privateKey)
150+
if err != nil {
151+
return nil, nil, err
152+
}
153+
154+
keyBlock := pem.Block{
155+
Type: ECPrivateKeyBlockType,
156+
Bytes: keyBytes,
157+
}
158+
159+
return pem.EncodeToMemory(&certBlock), pem.EncodeToMemory(&keyBlock), nil
160+
}
161+
162+
func NewPrivateKey() (*ecdsa.PrivateKey, error) {
163+
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
164+
}

go.mod

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,7 @@ module github.com/rancher/dynamiclistener
33
go 1.12
44

55
require (
6-
github.com/hashicorp/golang-lru v0.5.1
7-
github.com/kisielk/gotool v1.0.0 // indirect
8-
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
6+
github.com/rancher/wrangler-api v0.2.0
97
github.com/sirupsen/logrus v1.4.1
10-
github.com/stretchr/testify v1.3.0 // indirect
11-
github.com/stripe/safesql v0.2.0 // indirect
12-
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284
13-
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c // indirect
14-
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 // indirect
15-
golang.org/x/text v0.3.2 // indirect
16-
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect
17-
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect
8+
k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b
189
)

0 commit comments

Comments
 (0)