Skip to content

Commit 6e129c7

Browse files
committed
feat: support TLS
Signed-off-by: Jesse Suen <[email protected]>
1 parent 2c3611d commit 6e129c7

File tree

2 files changed

+251
-4
lines changed

2 files changed

+251
-4
lines changed

main.go

+19-4
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const (
2323
// (e.g. during a rolling update). This gives some time for ingress controllers to react to
2424
// the Pod IP being removed from the Service's Endpoint list, which prevents traffic from being
2525
// directed to terminated pods, which otherwise would cause timeout errors and/or request delays.
26-
// See: See: https://github.com/kubernetes/ingress-nginx/issues/3335#issuecomment-434970950
26+
// See: https://github.com/kubernetes/ingress-nginx/issues/3335#issuecomment-434970950
2727
defaultTerminationDelay = 10
2828
)
2929

@@ -46,10 +46,12 @@ func main() {
4646
listenAddr string
4747
terminationDelay int
4848
numCPUBurn string
49+
tls bool
4950
)
5051
flag.StringVar(&listenAddr, "listen-addr", ":8080", "server listen address")
5152
flag.IntVar(&terminationDelay, "termination-delay", defaultTerminationDelay, "termination delay in seconds")
5253
flag.StringVar(&numCPUBurn, "cpu-burn", "", "burn specified number of cpus (number or 'all')")
54+
flag.BoolVar(&tls, "tls", false, "Enable TLS (with self-signed certificate)")
5355
flag.Parse()
5456

5557
rand.Seed(time.Now().UnixNano())
@@ -62,6 +64,13 @@ func main() {
6264
Addr: listenAddr,
6365
Handler: router,
6466
}
67+
if tls {
68+
tlsConfig, err := CreateServerTLSConfig("", "", []string{"localhost", "rollouts-demo"})
69+
if err != nil {
70+
log.Fatalf("Could not generate TLS config: %v\n", err)
71+
}
72+
server.TLSConfig = tlsConfig
73+
}
6574

6675
done := make(chan bool)
6776
quit := make(chan os.Signal, 1)
@@ -89,7 +98,13 @@ func main() {
8998

9099
cpuBurn(done, numCPUBurn)
91100
log.Printf("Started server on %s", listenAddr)
92-
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
101+
var err error
102+
if tls {
103+
err = server.ListenAndServeTLS("", "")
104+
} else {
105+
err = server.ListenAndServe()
106+
}
107+
if err != nil && err != http.ErrServerClosed {
93108
log.Fatalf("Could not listen on %s: %v\n", listenAddr, err)
94109
}
95110

@@ -182,14 +197,14 @@ func printColor(colorToPrint string, w http.ResponseWriter, healthy bool) {
182197
case "":
183198
randomColor := randomColor()
184199
if healthy {
185-
log.Printf("Successful %s\n", randomColor)
200+
log.Printf("200 - %s\n", randomColor)
186201
} else {
187202
log.Printf("500 - %s\n", randomColor)
188203
}
189204
fmt.Fprintf(w, "\"%s\"", randomColor)
190205
default:
191206
if healthy {
192-
log.Printf("Successful %s\n", colorToPrint)
207+
log.Printf("200 - %s\n", colorToPrint)
193208
} else {
194209
log.Printf("500 - %s\n", colorToPrint)
195210
}

tls.go

+232
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
package main
2+
3+
import (
4+
"crypto"
5+
"crypto/ecdsa"
6+
"crypto/elliptic"
7+
"crypto/rand"
8+
"crypto/rsa"
9+
"crypto/tls"
10+
"crypto/x509"
11+
"crypto/x509/pkix"
12+
"encoding/pem"
13+
"errors"
14+
"fmt"
15+
"log"
16+
"math/big"
17+
"net"
18+
"os"
19+
"time"
20+
)
21+
22+
const (
23+
DefaultRSABits = 2048
24+
)
25+
26+
type CertOptions struct {
27+
// Hostnames and IPs to generate a certificate for
28+
Hosts []string
29+
// Name of organization in certificate
30+
Organization string
31+
// Creation date
32+
ValidFrom time.Time
33+
// Duration that certificate is valid for
34+
ValidFor time.Duration
35+
// whether this cert should be its own Certificate Authority
36+
IsCA bool
37+
// Size of RSA key to generate. Ignored if --ecdsa-curve is set
38+
RSABits int
39+
// ECDSA curve to use to generate a key. Valid values are P224, P256 (recommended), P384, P521
40+
ECDSACurve string
41+
}
42+
43+
func publicKey(priv interface{}) interface{} {
44+
switch k := priv.(type) {
45+
case *rsa.PrivateKey:
46+
return &k.PublicKey
47+
case *ecdsa.PrivateKey:
48+
return &k.PublicKey
49+
default:
50+
return nil
51+
}
52+
}
53+
54+
func pemBlockForKey(priv interface{}) *pem.Block {
55+
switch k := priv.(type) {
56+
case *rsa.PrivateKey:
57+
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
58+
case *ecdsa.PrivateKey:
59+
b, err := x509.MarshalECPrivateKey(k)
60+
if err != nil {
61+
fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err)
62+
os.Exit(2)
63+
}
64+
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
65+
default:
66+
return nil
67+
}
68+
}
69+
70+
func generate(opts CertOptions) ([]byte, crypto.PrivateKey, error) {
71+
if len(opts.Hosts) == 0 {
72+
return nil, nil, fmt.Errorf("hosts not supplied")
73+
}
74+
75+
var privateKey crypto.PrivateKey
76+
var err error
77+
switch opts.ECDSACurve {
78+
case "":
79+
rsaBits := DefaultRSABits
80+
if opts.RSABits != 0 {
81+
rsaBits = opts.RSABits
82+
}
83+
privateKey, err = rsa.GenerateKey(rand.Reader, rsaBits)
84+
case "P224":
85+
privateKey, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
86+
case "P256":
87+
privateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
88+
case "P384":
89+
privateKey, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
90+
case "P521":
91+
privateKey, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
92+
default:
93+
return nil, nil, fmt.Errorf("Unrecognized elliptic curve: %q", opts.ECDSACurve)
94+
}
95+
if err != nil {
96+
return nil, nil, fmt.Errorf("failed to generate private key: %s", err)
97+
}
98+
99+
var notBefore time.Time
100+
if opts.ValidFrom.IsZero() {
101+
notBefore = time.Now()
102+
} else {
103+
notBefore = opts.ValidFrom
104+
}
105+
var validFor time.Duration
106+
if opts.ValidFor == 0 {
107+
validFor = 365 * 24 * time.Hour
108+
}
109+
notAfter := notBefore.Add(validFor)
110+
111+
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
112+
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
113+
if err != nil {
114+
return nil, nil, fmt.Errorf("failed to generate serial number: %s", err)
115+
}
116+
117+
if opts.Organization == "" {
118+
return nil, nil, fmt.Errorf("organization not supplied")
119+
}
120+
template := x509.Certificate{
121+
SerialNumber: serialNumber,
122+
Subject: pkix.Name{
123+
Organization: []string{opts.Organization},
124+
},
125+
NotBefore: notBefore,
126+
NotAfter: notAfter,
127+
128+
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
129+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
130+
BasicConstraintsValid: true,
131+
}
132+
133+
for _, h := range opts.Hosts {
134+
if ip := net.ParseIP(h); ip != nil {
135+
template.IPAddresses = append(template.IPAddresses, ip)
136+
} else {
137+
template.DNSNames = append(template.DNSNames, h)
138+
}
139+
}
140+
141+
if opts.IsCA {
142+
template.IsCA = true
143+
template.KeyUsage |= x509.KeyUsageCertSign
144+
}
145+
146+
certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(privateKey), privateKey)
147+
if err != nil {
148+
return nil, nil, fmt.Errorf("Failed to create certificate: %s", err)
149+
}
150+
return certBytes, privateKey, nil
151+
}
152+
153+
// generatePEM generates a new certificate and key and returns it as PEM encoded bytes
154+
func generatePEM(opts CertOptions) ([]byte, []byte, error) {
155+
certBytes, privateKey, err := generate(opts)
156+
if err != nil {
157+
return nil, nil, err
158+
}
159+
certpem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes})
160+
keypem := pem.EncodeToMemory(pemBlockForKey(privateKey))
161+
return certpem, keypem, nil
162+
}
163+
164+
// GenerateX509KeyPair generates a X509 key pair
165+
func GenerateX509KeyPair(opts CertOptions) (*tls.Certificate, error) {
166+
certpem, keypem, err := generatePEM(opts)
167+
if err != nil {
168+
return nil, err
169+
}
170+
cert, err := tls.X509KeyPair(certpem, keypem)
171+
if err != nil {
172+
return nil, err
173+
}
174+
return &cert, nil
175+
}
176+
177+
// CreateServerTLSConfig will provide a TLS configuration for a server. It will
178+
// either use a certificate and key provided at tlsCertPath and tlsKeyPath, or
179+
// if these are not given, will generate a self-signed certificate valid for
180+
// the specified list of hosts. If hosts is nil or empty, self-signed cert
181+
// creation will be disabled.
182+
func CreateServerTLSConfig(tlsCertPath, tlsKeyPath string, hosts []string) (*tls.Config, error) {
183+
var cert *tls.Certificate
184+
var err error
185+
186+
tlsCertExists := false
187+
tlsKeyExists := false
188+
189+
// If cert and key paths were specified, ensure they exist
190+
if tlsCertPath != "" && tlsKeyPath != "" {
191+
_, err = os.Stat(tlsCertPath)
192+
if err != nil {
193+
if !errors.Is(err, os.ErrNotExist) {
194+
log.Printf("could not read TLS cert from %s: %v", tlsCertPath, err)
195+
}
196+
} else {
197+
tlsCertExists = true
198+
}
199+
200+
_, err = os.Stat(tlsKeyPath)
201+
if err != nil {
202+
if !errors.Is(err, os.ErrNotExist) {
203+
log.Printf("could not read TLS cert from %s: %v", tlsKeyPath, err)
204+
}
205+
} else {
206+
tlsKeyExists = true
207+
}
208+
}
209+
210+
if !tlsCertExists || !tlsKeyExists {
211+
log.Printf("Generating self-signed gRPC TLS certificate for this session")
212+
c, err := GenerateX509KeyPair(CertOptions{
213+
Hosts: hosts,
214+
Organization: "Argo Rollouts Demo",
215+
IsCA: true,
216+
})
217+
if err != nil {
218+
return nil, err
219+
}
220+
cert = c
221+
} else {
222+
log.Printf("Loading gRPC TLS configuration from cert=%s and key=%s", tlsCertPath, tlsKeyPath)
223+
c, err := tls.LoadX509KeyPair(tlsCertPath, tlsKeyPath)
224+
if err != nil {
225+
return nil, fmt.Errorf("Unable to initalize gRPC TLS configuration with cert=%s and key=%s: %v", tlsCertPath, tlsKeyPath, err)
226+
}
227+
cert = &c
228+
}
229+
230+
return &tls.Config{Certificates: []tls.Certificate{*cert}}, nil
231+
232+
}

0 commit comments

Comments
 (0)