forked from TrueCloudLab/certificates
Enable client certificate rotation with GetClientCertificate.
This commit is contained in:
parent
3ba6e33791
commit
79a030960b
4 changed files with 72 additions and 12 deletions
|
@ -59,7 +59,7 @@ languages are appreciated!
|
||||||
- [ ] Root certificate rotation
|
- [ ] Root certificate rotation
|
||||||
- [X] Client using autocert root certificate
|
- [X] Client using autocert root certificate
|
||||||
- [X] mTLS (send client certificate if server asks for it)
|
- [X] mTLS (send client certificate if server asks for it)
|
||||||
- [ ] Automatic certificate rotation
|
- [X] Automatic certificate rotation
|
||||||
- [X] Restrict to safe ciphersuites and TLS versions
|
- [X] Restrict to safe ciphersuites and TLS versions
|
||||||
- [ ] TLS stack configuration loaded from `step-ca`
|
- [ ] TLS stack configuration loaded from `step-ca`
|
||||||
- [ ] Root certificate rotation
|
- [ ] Root certificate rotation
|
||||||
|
|
|
@ -112,6 +112,12 @@ func main() {
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||||
},
|
},
|
||||||
|
// GetClientCertificate is called when a server requests a
|
||||||
|
// certificate from a client.
|
||||||
|
//
|
||||||
|
// In this example keep alives will cause the certificate to
|
||||||
|
// only be called once, but if we disable them,
|
||||||
|
// GetClientCertificate will be called on every request.
|
||||||
GetClientCertificate: r.getClientCertificate,
|
GetClientCertificate: r.getClientCertificate,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,8 +19,34 @@ const (
|
||||||
autocertKey = "/var/run/autocert.step.sm/site.key"
|
autocertKey = "/var/run/autocert.step.sm/site.key"
|
||||||
autocertRoot = "/var/run/autocert.step.sm/root.crt"
|
autocertRoot = "/var/run/autocert.step.sm/root.crt"
|
||||||
requestFrequency = 5 * time.Second
|
requestFrequency = 5 * time.Second
|
||||||
|
tickFrequency = 15 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type rotator struct {
|
||||||
|
sync.RWMutex
|
||||||
|
certificate *tls.Certificate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rotator) getClientCertificate(cri *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||||
|
r.RLock()
|
||||||
|
defer r.RUnlock()
|
||||||
|
return r.certificate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rotator) loadCertificate(certFile, keyFile string) error {
|
||||||
|
r.Lock()
|
||||||
|
defer r.Unlock()
|
||||||
|
|
||||||
|
c, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.certificate = &c
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func loadRootCertPool() (*x509.CertPool, error) {
|
func loadRootCertPool() (*x509.CertPool, error) {
|
||||||
root, err := ioutil.ReadFile(autocertRoot)
|
root, err := ioutil.ReadFile(autocertRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -37,34 +64,62 @@ func loadRootCertPool() (*x509.CertPool, error) {
|
||||||
func main() {
|
func main() {
|
||||||
url := os.Getenv("HELLO_MTLS_URL")
|
url := os.Getenv("HELLO_MTLS_URL")
|
||||||
|
|
||||||
// Read our leaf certificate and key from disk
|
|
||||||
cert, err := tls.LoadX509KeyPair(autocertFile, autocertKey)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the root certificate for our CA from disk
|
// Read the root certificate for our CA from disk
|
||||||
roots, err := loadRootCertPool()
|
roots, err := loadRootCertPool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load certificate
|
||||||
|
r := &rotator{}
|
||||||
|
if err := r.loadCertificate(autocertFile, autocertKey); err != nil {
|
||||||
|
log.Fatal("error loading certificate and key", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Create an HTTPS client using our cert, key & pool
|
// Create an HTTPS client using our cert, key & pool
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{
|
TLSClientConfig: &tls.Config{
|
||||||
RootCAs: roots,
|
RootCAs: roots,
|
||||||
Certificates: []tls.Certificate{cert},
|
|
||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
|
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
|
||||||
CipherSuites: []uint16{
|
CipherSuites: []uint16{
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||||
},
|
},
|
||||||
|
// GetClientCertificate is called when a server requests a
|
||||||
|
// certificate from a client.
|
||||||
|
//
|
||||||
|
// In this example keep alives will cause the certificate to
|
||||||
|
// only be called once, but if we disable them,
|
||||||
|
// GetClientCertificate will be called on every request.
|
||||||
|
GetClientCertificate: r.getClientCertificate,
|
||||||
},
|
},
|
||||||
|
// Add this line to get the certificate on every request.
|
||||||
|
// DisableKeepAlives: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Schedule periodic re-load of certificate
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(tickFrequency)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
fmt.Println("Checking for new certificate...")
|
||||||
|
err := r.loadCertificate(autocertFile, autocertKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error loading certificate and key", err)
|
||||||
|
}
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// Make request
|
// Make request
|
||||||
r, err := client.Get(url)
|
r, err := client.Get(url)
|
||||||
|
|
|
@ -23,14 +23,13 @@ const (
|
||||||
// to automatically rotate certificates when they're renewed.
|
// to automatically rotate certificates when they're renewed.
|
||||||
|
|
||||||
type rotator struct {
|
type rotator struct {
|
||||||
sync.Mutex
|
sync.RWMutex
|
||||||
certificate *tls.Certificate
|
certificate *tls.Certificate
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rotator) getCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
func (r *rotator) getCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
r.Lock()
|
r.RLock()
|
||||||
defer r.Unlock()
|
defer r.RUnlock()
|
||||||
|
|
||||||
return r.certificate, nil
|
return r.certificate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue