-
Notifications
You must be signed in to change notification settings - Fork 456
/
ca.go
380 lines (313 loc) · 9.96 KB
/
ca.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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
package ca
import (
"context"
"crypto"
"crypto/x509"
"crypto/x509/pkix"
"errors"
"fmt"
"sync"
"time"
"github.com/andres-erbsen/clock"
"github.com/go-jose/go-jose/v4"
"github.com/go-jose/go-jose/v4/cryptosigner"
"github.com/go-jose/go-jose/v4/jwt"
"github.com/sirupsen/logrus"
"github.com/spiffe/go-spiffe/v2/spiffeid"
"github.com/spiffe/spire/pkg/common/cryptoutil"
"github.com/spiffe/spire/pkg/common/health"
"github.com/spiffe/spire/pkg/common/telemetry"
telemetry_server "github.com/spiffe/spire/pkg/common/telemetry/server"
"github.com/spiffe/spire/pkg/common/x509util"
"github.com/spiffe/spire/pkg/server/credtemplate"
"github.com/spiffe/spire/pkg/server/credvalidator"
)
const (
backdate = 10 * time.Second
)
// ServerCA is an interface for Server CAs
type ServerCA interface {
SignDownstreamX509CA(ctx context.Context, params DownstreamX509CAParams) ([]*x509.Certificate, error)
SignServerX509SVID(ctx context.Context, params ServerX509SVIDParams) ([]*x509.Certificate, error)
SignAgentX509SVID(ctx context.Context, params AgentX509SVIDParams) ([]*x509.Certificate, error)
SignWorkloadX509SVID(ctx context.Context, params WorkloadX509SVIDParams) ([]*x509.Certificate, error)
SignWorkloadJWTSVID(ctx context.Context, params WorkloadJWTSVIDParams) (string, error)
}
// DownstreamX509CAParams are parameters relevant to downstream X.509 CA creation
type DownstreamX509CAParams struct {
// Public Key
PublicKey crypto.PublicKey
// TTL is the desired time-to-live of the SVID. Regardless of the TTL, the
// lifetime of the certificate will be capped to that of the signing cert.
TTL time.Duration
}
// ServerX509SVIDParams are parameters relevant to server X509-SVID creation
type ServerX509SVIDParams struct {
// Public Key
PublicKey crypto.PublicKey
}
// AgentX509SVIDParams are parameters relevant to agent X509-SVID creation
type AgentX509SVIDParams struct {
// Public Key
PublicKey crypto.PublicKey
// SPIFFE ID of the agent
SPIFFEID spiffeid.ID
}
// WorkloadX509SVIDParams are parameters relevant to workload X509-SVID creation
type WorkloadX509SVIDParams struct {
// Public Key
PublicKey crypto.PublicKey
// SPIFFE ID of the SVID
SPIFFEID spiffeid.ID
// DNSNames is used to add DNS SAN's to the X509 SVID. The first entry
// is also added as the CN.
DNSNames []string
// TTL is the desired time-to-live of the SVID. Regardless of the TTL, the
// lifetime of the certificate will be capped to that of the signing cert.
TTL time.Duration
// Subject of the SVID. Default subject is used if it is empty.
Subject pkix.Name
}
// WorkloadJWTSVIDParams are parameters relevant to workload JWT-SVID creation
type WorkloadJWTSVIDParams struct {
// SPIFFE ID of the SVID
SPIFFEID spiffeid.ID
// TTL is the desired time-to-live of the SVID. Regardless of the TTL, the
// lifetime of the token will be capped to that of the signing key.
TTL time.Duration
// Audience is used for audience claims
Audience []string
}
type X509CA struct {
// Signer is used to sign child certificates.
Signer crypto.Signer
// Certificate is the CA certificate.
Certificate *x509.Certificate
// UpstreamChain contains the CA certificate and intermediates necessary to
// chain back to the upstream trust bundle. It is only set if the CA is
// signed by an UpstreamCA.
UpstreamChain []*x509.Certificate
}
type JWTKey struct {
// The signer used to sign keys
Signer crypto.Signer
// Kid is the JWT key ID (i.e. "kid" claim)
Kid string
// NotAfter is the expiration time of the JWT key.
NotAfter time.Time
}
type Config struct {
Log logrus.FieldLogger
Clock clock.Clock
Metrics telemetry.Metrics
TrustDomain spiffeid.TrustDomain
CredBuilder *credtemplate.Builder
CredValidator *credvalidator.Validator
HealthChecker health.Checker
}
type CA struct {
c Config
mu sync.RWMutex
x509CA *X509CA
x509CAChain []*x509.Certificate
jwtKey *JWTKey
}
func NewCA(config Config) *CA {
if config.Clock == nil {
config.Clock = clock.New()
}
ca := &CA{
c: config,
}
_ = config.HealthChecker.AddCheck("server.ca", &caHealth{
ca: ca,
td: config.TrustDomain,
})
return ca
}
func (ca *CA) X509CA() *X509CA {
ca.mu.RLock()
defer ca.mu.RUnlock()
return ca.x509CA
}
func (ca *CA) SetX509CA(x509CA *X509CA) {
ca.mu.Lock()
defer ca.mu.Unlock()
ca.x509CA = x509CA
switch {
case x509CA == nil:
ca.x509CAChain = nil
case len(x509CA.UpstreamChain) > 0:
ca.x509CAChain = x509CA.UpstreamChain
default:
ca.x509CAChain = []*x509.Certificate{x509CA.Certificate}
}
}
func (ca *CA) JWTKey() *JWTKey {
ca.mu.RLock()
defer ca.mu.RUnlock()
return ca.jwtKey
}
func (ca *CA) SetJWTKey(jwtKey *JWTKey) {
ca.mu.Lock()
defer ca.mu.Unlock()
ca.jwtKey = jwtKey
}
func (ca *CA) SignDownstreamX509CA(ctx context.Context, params DownstreamX509CAParams) ([]*x509.Certificate, error) {
x509CA, caChain, err := ca.getX509CA()
if err != nil {
return nil, err
}
template, err := ca.c.CredBuilder.BuildDownstreamX509CATemplate(ctx, credtemplate.DownstreamX509CAParams{
ParentChain: caChain,
PublicKey: params.PublicKey,
TTL: params.TTL,
})
if err != nil {
return nil, err
}
downstreamCA, err := x509util.CreateCertificate(template, x509CA.Certificate, template.PublicKey, x509CA.Signer)
if err != nil {
return nil, fmt.Errorf("unable to create downstream X509 CA: %w", err)
}
if err := ca.c.CredValidator.ValidateX509CA(downstreamCA); err != nil {
return nil, fmt.Errorf("invalid downstream X509 CA: %w", err)
}
telemetry_server.IncrServerCASignX509CACounter(ca.c.Metrics)
return makeCertChain(x509CA, downstreamCA), nil
}
func (ca *CA) SignServerX509SVID(ctx context.Context, params ServerX509SVIDParams) ([]*x509.Certificate, error) {
x509CA, caChain, err := ca.getX509CA()
if err != nil {
return nil, err
}
template, err := ca.c.CredBuilder.BuildServerX509SVIDTemplate(ctx, credtemplate.ServerX509SVIDParams{
ParentChain: caChain,
PublicKey: params.PublicKey,
})
if err != nil {
return nil, err
}
svidChain, err := ca.signX509SVID(x509CA, template)
if err != nil {
return nil, err
}
if err := ca.c.CredValidator.ValidateServerX509SVID(svidChain[0]); err != nil {
return nil, fmt.Errorf("invalid server X509-SVID: %w", err)
}
return svidChain, nil
}
func (ca *CA) SignAgentX509SVID(ctx context.Context, params AgentX509SVIDParams) ([]*x509.Certificate, error) {
x509CA, caChain, err := ca.getX509CA()
if err != nil {
return nil, err
}
template, err := ca.c.CredBuilder.BuildAgentX509SVIDTemplate(ctx, credtemplate.AgentX509SVIDParams{
ParentChain: caChain,
PublicKey: params.PublicKey,
SPIFFEID: params.SPIFFEID,
})
if err != nil {
return nil, err
}
svidChain, err := ca.signX509SVID(x509CA, template)
if err != nil {
return nil, err
}
if err := ca.c.CredValidator.ValidateX509SVID(svidChain[0], params.SPIFFEID); err != nil {
return nil, fmt.Errorf("invalid agent X509-SVID: %w", err)
}
return svidChain, nil
}
func (ca *CA) SignWorkloadX509SVID(ctx context.Context, params WorkloadX509SVIDParams) ([]*x509.Certificate, error) {
x509CA, caChain, err := ca.getX509CA()
if err != nil {
return nil, err
}
template, err := ca.c.CredBuilder.BuildWorkloadX509SVIDTemplate(ctx, credtemplate.WorkloadX509SVIDParams{
ParentChain: caChain,
PublicKey: params.PublicKey,
SPIFFEID: params.SPIFFEID,
DNSNames: params.DNSNames,
TTL: params.TTL,
Subject: params.Subject,
})
if err != nil {
return nil, err
}
svidChain, err := ca.signX509SVID(x509CA, template)
if err != nil {
return nil, err
}
if err := ca.c.CredValidator.ValidateX509SVID(svidChain[0], params.SPIFFEID); err != nil {
return nil, fmt.Errorf("invalid workload X509-SVID: %w", err)
}
return svidChain, nil
}
func (ca *CA) SignWorkloadJWTSVID(ctx context.Context, params WorkloadJWTSVIDParams) (string, error) {
jwtKey := ca.JWTKey()
if jwtKey == nil {
return "", errors.New("JWT key is not available for signing")
}
claims, err := ca.c.CredBuilder.BuildWorkloadJWTSVIDClaims(ctx, credtemplate.WorkloadJWTSVIDParams{
SPIFFEID: params.SPIFFEID,
Audience: params.Audience,
TTL: params.TTL,
ExpirationCap: jwtKey.NotAfter,
})
if err != nil {
return "", err
}
token, err := ca.signJWTSVID(jwtKey, claims)
if err != nil {
return "", fmt.Errorf("unable to sign JWT SVID: %w", err)
}
if err := ca.c.CredValidator.ValidateWorkloadJWTSVID(token, params.SPIFFEID); err != nil {
return "", err
}
telemetry_server.IncrServerCASignJWTSVIDCounter(ca.c.Metrics)
return token, nil
}
func (ca *CA) getX509CA() (*X509CA, []*x509.Certificate, error) {
ca.mu.RLock()
defer ca.mu.RUnlock()
if ca.x509CA == nil {
return nil, nil, errors.New("X509 CA is not available for signing")
}
return ca.x509CA, ca.x509CAChain, nil
}
func (ca *CA) signX509SVID(x509CA *X509CA, template *x509.Certificate) ([]*x509.Certificate, error) {
x509SVID, err := x509util.CreateCertificate(template, x509CA.Certificate, template.PublicKey, x509CA.Signer)
if err != nil {
return nil, fmt.Errorf("failed to sign X509 SVID: %w", err)
}
telemetry_server.IncrServerCASignX509Counter(ca.c.Metrics)
return makeCertChain(x509CA, x509SVID), nil
}
func (ca *CA) signJWTSVID(jwtKey *JWTKey, claims map[string]any) (string, error) {
alg, err := cryptoutil.JoseAlgFromPublicKey(jwtKey.Signer.Public())
if err != nil {
return "", fmt.Errorf("failed to determine JWT key algorithm: %w", err)
}
jwtSigner, err := jose.NewSigner(
jose.SigningKey{
Algorithm: alg,
Key: jose.JSONWebKey{
Key: cryptosigner.Opaque(jwtKey.Signer),
KeyID: jwtKey.Kid,
},
},
new(jose.SignerOptions).WithType("JWT"),
)
if err != nil {
return "", fmt.Errorf("failed to configure JWT signer: %w", err)
}
signedToken, err := jwt.Signed(jwtSigner).Claims(claims).Serialize()
if err != nil {
return "", fmt.Errorf("failed to sign JWT SVID: %w", err)
}
return signedToken, nil
}
func makeCertChain(x509CA *X509CA, leaf *x509.Certificate) []*x509.Certificate {
return append([]*x509.Certificate{leaf}, x509CA.UpstreamChain...)
}