blob: 5e0d87abc52ebcf39e8207f65c658f9de7ebde3a [file] [log] [blame]
khenaidoo26721882021-08-11 17:42:52 -04001// Copyright 2015 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package transport
16
17import (
18 "crypto/ecdsa"
19 "crypto/elliptic"
20 "crypto/rand"
21 "crypto/tls"
22 "crypto/x509"
23 "crypto/x509/pkix"
24 "encoding/pem"
25 "errors"
26 "fmt"
27 "math/big"
28 "net"
29 "os"
30 "path/filepath"
31 "strings"
32 "time"
33
34 "github.com/coreos/etcd/pkg/fileutil"
35 "github.com/coreos/etcd/pkg/tlsutil"
36)
37
38func NewListener(addr, scheme string, tlsinfo *TLSInfo) (l net.Listener, err error) {
39 if l, err = newListener(addr, scheme); err != nil {
40 return nil, err
41 }
42 return wrapTLS(addr, scheme, tlsinfo, l)
43}
44
45func newListener(addr string, scheme string) (net.Listener, error) {
46 if scheme == "unix" || scheme == "unixs" {
47 // unix sockets via unix://laddr
48 return NewUnixListener(addr)
49 }
50 return net.Listen("tcp", addr)
51}
52
53func wrapTLS(addr, scheme string, tlsinfo *TLSInfo, l net.Listener) (net.Listener, error) {
54 if scheme != "https" && scheme != "unixs" {
55 return l, nil
56 }
57 if tlsinfo != nil && tlsinfo.SkipClientSANVerify {
58 return NewTLSListener(l, tlsinfo)
59 }
60 return newTLSListener(l, tlsinfo, checkSAN)
61}
62
63type TLSInfo struct {
64 CertFile string
65 KeyFile string
66 CAFile string // TODO: deprecate this in v4
67 TrustedCAFile string
68 ClientCertAuth bool
69 CRLFile string
70 InsecureSkipVerify bool
71
72 SkipClientSANVerify bool
73
74 // ServerName ensures the cert matches the given host in case of discovery / virtual hosting
75 ServerName string
76
77 // HandshakeFailure is optionally called when a connection fails to handshake. The
78 // connection will be closed immediately afterwards.
79 HandshakeFailure func(*tls.Conn, error)
80
81 // CipherSuites is a list of supported cipher suites.
82 // If empty, Go auto-populates it by default.
83 // Note that cipher suites are prioritized in the given order.
84 CipherSuites []uint16
85
86 selfCert bool
87
88 // parseFunc exists to simplify testing. Typically, parseFunc
89 // should be left nil. In that case, tls.X509KeyPair will be used.
90 parseFunc func([]byte, []byte) (tls.Certificate, error)
91
92 // AllowedCN is a CN which must be provided by a client.
93 AllowedCN string
94}
95
96func (info TLSInfo) String() string {
97 return fmt.Sprintf("cert = %s, key = %s, ca = %s, trusted-ca = %s, client-cert-auth = %v, crl-file = %s", info.CertFile, info.KeyFile, info.CAFile, info.TrustedCAFile, info.ClientCertAuth, info.CRLFile)
98}
99
100func (info TLSInfo) Empty() bool {
101 return info.CertFile == "" && info.KeyFile == ""
102}
103
104func SelfCert(dirpath string, hosts []string, additionalUsages ...x509.ExtKeyUsage) (info TLSInfo, err error) {
105 err = fileutil.TouchDirAll(dirpath)
106 if err != nil {
107 return
108 }
109
110 certPath := filepath.Join(dirpath, "cert.pem")
111 keyPath := filepath.Join(dirpath, "key.pem")
112 _, errcert := os.Stat(certPath)
113 _, errkey := os.Stat(keyPath)
114 if errcert == nil && errkey == nil {
115 info.CertFile = certPath
116 info.KeyFile = keyPath
117 info.selfCert = true
118 return
119 }
120
121 serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
122 serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
123 if err != nil {
124 return
125 }
126
127 tmpl := x509.Certificate{
128 SerialNumber: serialNumber,
129 Subject: pkix.Name{Organization: []string{"etcd"}},
130 NotBefore: time.Now(),
131 NotAfter: time.Now().Add(365 * (24 * time.Hour)),
132
133 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
134 ExtKeyUsage: append([]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, additionalUsages...),
135 BasicConstraintsValid: true,
136 }
137
138 for _, host := range hosts {
139 h, _, _ := net.SplitHostPort(host)
140 if ip := net.ParseIP(h); ip != nil {
141 tmpl.IPAddresses = append(tmpl.IPAddresses, ip)
142 } else {
143 tmpl.DNSNames = append(tmpl.DNSNames, h)
144 }
145 }
146
147 priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
148 if err != nil {
149 return
150 }
151
152 derBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &priv.PublicKey, priv)
153 if err != nil {
154 return
155 }
156
157 certOut, err := os.Create(certPath)
158 if err != nil {
159 return
160 }
161 pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
162 certOut.Close()
163
164 b, err := x509.MarshalECPrivateKey(priv)
165 if err != nil {
166 return
167 }
168 keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
169 if err != nil {
170 return
171 }
172 pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: b})
173 keyOut.Close()
174
175 return SelfCert(dirpath, hosts)
176}
177
178func (info TLSInfo) baseConfig() (*tls.Config, error) {
179 if info.KeyFile == "" || info.CertFile == "" {
180 return nil, fmt.Errorf("KeyFile and CertFile must both be present[key: %v, cert: %v]", info.KeyFile, info.CertFile)
181 }
182
183 _, err := tlsutil.NewCert(info.CertFile, info.KeyFile, info.parseFunc)
184 if err != nil {
185 return nil, err
186 }
187
188 cfg := &tls.Config{
189 MinVersion: tls.VersionTLS12,
190 ServerName: info.ServerName,
191 }
192
193 if len(info.CipherSuites) > 0 {
194 cfg.CipherSuites = info.CipherSuites
195 }
196
197 if info.AllowedCN != "" {
198 cfg.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
199 for _, chains := range verifiedChains {
200 if len(chains) != 0 {
201 if info.AllowedCN == chains[0].Subject.CommonName {
202 return nil
203 }
204 }
205 }
206 return errors.New("CommonName authentication failed")
207 }
208 }
209
210 // this only reloads certs when there's a client request
211 // TODO: support server-side refresh (e.g. inotify, SIGHUP), caching
212 cfg.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
213 return tlsutil.NewCert(info.CertFile, info.KeyFile, info.parseFunc)
214 }
215 cfg.GetClientCertificate = func(unused *tls.CertificateRequestInfo) (*tls.Certificate, error) {
216 return tlsutil.NewCert(info.CertFile, info.KeyFile, info.parseFunc)
217 }
218 return cfg, nil
219}
220
221// cafiles returns a list of CA file paths.
222func (info TLSInfo) cafiles() []string {
223 cs := make([]string, 0)
224 if info.CAFile != "" {
225 cs = append(cs, info.CAFile)
226 }
227 if info.TrustedCAFile != "" {
228 cs = append(cs, info.TrustedCAFile)
229 }
230 return cs
231}
232
233// ServerConfig generates a tls.Config object for use by an HTTP server.
234func (info TLSInfo) ServerConfig() (*tls.Config, error) {
235 cfg, err := info.baseConfig()
236 if err != nil {
237 return nil, err
238 }
239
240 cfg.ClientAuth = tls.NoClientCert
241 if info.CAFile != "" || info.ClientCertAuth {
242 cfg.ClientAuth = tls.RequireAndVerifyClientCert
243 }
244
245 CAFiles := info.cafiles()
246 if len(CAFiles) > 0 {
247 cp, err := tlsutil.NewCertPool(CAFiles)
248 if err != nil {
249 return nil, err
250 }
251 cfg.ClientCAs = cp
252 }
253
254 // "h2" NextProtos is necessary for enabling HTTP2 for go's HTTP server
255 cfg.NextProtos = []string{"h2"}
256
257 return cfg, nil
258}
259
260// ClientConfig generates a tls.Config object for use by an HTTP client.
261func (info TLSInfo) ClientConfig() (*tls.Config, error) {
262 var cfg *tls.Config
263 var err error
264
265 if !info.Empty() {
266 cfg, err = info.baseConfig()
267 if err != nil {
268 return nil, err
269 }
270 } else {
271 cfg = &tls.Config{ServerName: info.ServerName}
272 }
273 cfg.InsecureSkipVerify = info.InsecureSkipVerify
274
275 CAFiles := info.cafiles()
276 if len(CAFiles) > 0 {
277 cfg.RootCAs, err = tlsutil.NewCertPool(CAFiles)
278 if err != nil {
279 return nil, err
280 }
281 }
282
283 if info.selfCert {
284 cfg.InsecureSkipVerify = true
285 }
286 return cfg, nil
287}
288
289// IsClosedConnError returns true if the error is from closing listener, cmux.
290// copied from golang.org/x/net/http2/http2.go
291func IsClosedConnError(err error) bool {
292 // 'use of closed network connection' (Go <=1.8)
293 // 'use of closed file or network connection' (Go >1.8, internal/poll.ErrClosing)
294 // 'mux: listener closed' (cmux.ErrListenerClosed)
295 return err != nil && strings.Contains(err.Error(), "closed")
296}