Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package serverutil
import (
"crypto/tls"
"net/http"
"regexp"
common "git.autistici.org/ai3/go-common"
)
// TLSAuthACL describes a single access control entry. Path and
// CommonName are anchored regular expressions (they must match the
// entire string).
type TLSAuthACL struct {
Path string `yaml:"path"`
CommonName string `yaml:"cn"`
pathRx, cnRx *regexp.Regexp
}
func (p *TLSAuthACL) compile() error {
var err error
p.pathRx, err = regexp.Compile("^" + p.Path + "$")
if err != nil {
return err
}
p.cnRx, err = regexp.Compile("^" + p.CommonName + "$")
return err
}
func (p *TLSAuthACL) match(req *http.Request) bool {
if !p.pathRx.MatchString(req.URL.Path) {
return false
}
for _, cert := range req.TLS.PeerCertificates {
if p.cnRx.MatchString(cert.Subject.CommonName) {
return true
}
}
return false
}
// TLSAuthConfig stores access control lists for TLS authentication. Access
// control lists are matched against the request path and the
// CommonName component of the peer certificate subject.
type TLSAuthConfig struct {
Allow []*TLSAuthACL `yaml:"allow"`
}
func (c *TLSAuthConfig) match(req *http.Request) bool {
// Fail *OPEN* if unconfigured.
if c == nil || len(c.Allow) == 0 {
return true
}
for _, acl := range c.Allow {
if acl.match(req) {
return true
}
}
return false
}
// TLSServerConfig configures a TLS server with client authentication
// and authorization based on the client X509 certificate.
type TLSServerConfig struct {
Cert string `yaml:"cert"`
Key string `yaml:"key"`
CA string `yaml:"ca"`
Auth *TLSAuthConfig `yaml:"acl"`
}
// TLSConfig returns a tls.Config created with the current configuration.
func (c *TLSServerConfig) TLSConfig() (*tls.Config, error) {
cert, err := tls.LoadX509KeyPair(c.Cert, c.Key)
if err != nil {
return nil, err
}
cas, err := common.LoadCA(c.CA)
if err != nil {
return nil, err
}
// Set some TLS-level parameters (cipher-related), assuming
// we're using EC keys.
tlsConf := &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: cas,
CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384},
MinVersion: tls.VersionTLS12,
PreferServerCipherSuites: true,
}
tlsConf.BuildNameToCertificate()
return tlsConf, nil
}
// TLSAuthWrapper protects a root HTTP handler with TLS authentication.
func (c *TLSServerConfig) TLSAuthWrapper(h http.Handler) (http.Handler, error) {
// Compile regexps.
if c.Auth != nil {
for _, acl := range c.Auth.Allow {
if err := acl.compile(); err != nil {
return nil, err
}
}
}
// Build the wrapper function to check client certificates
// identities (looking at the CN part of the X509 subject).
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if c.Auth.match(r) {
h.ServeHTTP(w, r)
return
}
http.Error(w, "Unauthorized", http.StatusUnauthorized)
}), nil
}