forked from gravitational/teleport
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cache.go
165 lines (136 loc) · 4.16 KB
/
cache.go
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/*
Copyright 2017 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package reversetunnel
import (
"net"
"sync"
"time"
"golang.org/x/crypto/ssh"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/sshca"
"github.com/gravitational/trace"
"github.com/gravitational/ttlmap"
)
type certificateCache struct {
mu sync.Mutex
cache *ttlmap.TTLMap
authClient auth.ClientI
keygen sshca.Authority
}
// NewHostCertificateCache creates a shared host certificate cache that is
// used by the forwarding server.
func NewHostCertificateCache(keygen sshca.Authority, authClient auth.ClientI) (*certificateCache, error) {
cache, err := ttlmap.New(defaults.HostCertCacheSize)
if err != nil {
return nil, trace.Wrap(err)
}
return &certificateCache{
keygen: keygen,
cache: cache,
authClient: authClient,
}, nil
}
// GetHostCertificate will fetch a certificate from the cache. If the certificate
// is not in the cache, it will be generated, put in the cache, and returned. Mul
// Multiple callers can arrive and generate a host certificate at the same time.
// This is a tradeoff to prevent long delays here due to the expensive
// certificate generation call.
func (c *certificateCache) GetHostCertificate(addr string) (ssh.Signer, error) {
var certificate ssh.Signer
var err error
var ok bool
// extract the principal from the address
principal, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, trace.Wrap(err)
}
certificate, ok = c.get(principal)
if !ok {
certificate, err = c.generateHostCert(principal)
if err != nil {
return nil, trace.Wrap(err)
}
err = c.set(principal, certificate, defaults.HostCertCacheTime)
if err != nil {
return nil, trace.Wrap(err)
}
}
return certificate, nil
}
// get is goroutine safe and will return a ssh.Signer for a principal from
// the cache.
func (c *certificateCache) get(principal string) (ssh.Signer, bool) {
c.mu.Lock()
defer c.mu.Unlock()
certificate, ok := c.cache.Get(principal)
if !ok {
return nil, false
}
certificateSigner, ok := certificate.(ssh.Signer)
if !ok {
return nil, false
}
return certificateSigner, true
}
// set is goroutine safe and will set a ssh.Signer for a principal in
// the cache.
func (c *certificateCache) set(principal string, certificate ssh.Signer, ttl time.Duration) error {
c.mu.Lock()
defer c.mu.Unlock()
err := c.cache.Set(principal, certificate, ttl)
if err != nil {
return trace.Wrap(err)
}
return nil
}
// generateHostCert will generate a SSH host certificate for a given
// principal.
func (c *certificateCache) generateHostCert(principal string) (ssh.Signer, error) {
// generate public/private keypair
privBytes, pubBytes, err := c.keygen.GetNewKeyPairFromPool()
if err != nil {
return nil, trace.Wrap(err)
}
// have auth server sign and return a host certificate to us
clusterName, err := c.authClient.GetDomainName()
if err != nil {
return nil, trace.Wrap(err)
}
certBytes, err := c.authClient.GenerateHostCert(pubBytes,
principal, principal, nil,
clusterName, teleport.Roles{teleport.RoleNode}, 0)
if err != nil {
return nil, trace.Wrap(err)
}
// create a *ssh.Certificate
privateKey, err := ssh.ParsePrivateKey(privBytes)
if err != nil {
return nil, trace.Wrap(err)
}
publicKey, _, _, _, err := ssh.ParseAuthorizedKey(certBytes)
if err != nil {
return nil, err
}
cert, ok := publicKey.(*ssh.Certificate)
if !ok {
return nil, trace.BadParameter("not a certificate")
}
// return a ssh.Signer
s, err := ssh.NewCertSigner(cert, privateKey)
if err != nil {
return nil, trace.Wrap(err)
}
return s, nil
}