Skip to content

Commit 4d7bbf4

Browse files
Add SVID hints on workload api client (#220)
Signed-off-by: Guilherme Carvalho <[email protected]> Co-authored-by: Daniel Feldman <[email protected]>
1 parent acf23ce commit 4d7bbf4

File tree

7 files changed

+217
-94
lines changed

7 files changed

+217
-94
lines changed

v2/internal/test/ca.go

Lines changed: 101 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,6 @@ type CA struct {
4040
jwtKid string
4141
}
4242

43-
type CertificateOption interface {
44-
apply(*x509.Certificate)
45-
}
46-
47-
type certificateOption func(*x509.Certificate)
48-
49-
func (co certificateOption) apply(c *x509.Certificate) {
50-
co(c)
51-
}
52-
5343
func NewCA(tb testing.TB, td spiffeid.TrustDomain) *CA {
5444
cert, key := CreateCACertificate(tb, nil, nil)
5545
return &CA{
@@ -62,7 +52,7 @@ func NewCA(tb testing.TB, td spiffeid.TrustDomain) *CA {
6252
}
6353
}
6454

65-
func (ca *CA) ChildCA(options ...CertificateOption) *CA {
55+
func (ca *CA) ChildCA(options ...SVIDOption) *CA {
6656
cert, key := CreateCACertificate(ca.tb, ca.cert, ca.key, options...)
6757
return &CA{
6858
tb: ca.tb,
@@ -74,21 +64,23 @@ func (ca *CA) ChildCA(options ...CertificateOption) *CA {
7464
}
7565
}
7666

77-
func (ca *CA) CreateX509SVID(id spiffeid.ID, options ...CertificateOption) *x509svid.SVID {
67+
func (ca *CA) CreateX509SVID(id spiffeid.ID, options ...SVIDOption) *x509svid.SVID {
7868
cert, key := CreateX509SVID(ca.tb, ca.cert, ca.key, id, options...)
79-
return &x509svid.SVID{
69+
svid := &x509svid.SVID{
8070
ID: id,
8171
Certificates: append([]*x509.Certificate{cert}, ca.chain(false)...),
8272
PrivateKey: key,
8373
}
74+
applyX509SVIDOptions(svid, options...)
75+
return svid
8476
}
8577

86-
func (ca *CA) CreateX509Certificate(options ...CertificateOption) ([]*x509.Certificate, crypto.Signer) {
78+
func (ca *CA) CreateX509Certificate(options ...SVIDOption) ([]*x509.Certificate, crypto.Signer) {
8779
cert, key := CreateX509Certificate(ca.tb, ca.cert, ca.key, options...)
8880
return append([]*x509.Certificate{cert}, ca.chain(false)...), key
8981
}
9082

91-
func (ca *CA) CreateJWTSVID(id spiffeid.ID, audience []string) *jwtsvid.SVID {
83+
func (ca *CA) CreateJWTSVID(id spiffeid.ID, audience []string, options ...SVIDOption) *jwtsvid.SVID {
9284
claims := jwt.Claims{
9385
Subject: id.String(),
9486
Issuer: "FAKECA",
@@ -114,6 +106,9 @@ func (ca *CA) CreateJWTSVID(id spiffeid.ID, audience []string) *jwtsvid.SVID {
114106

115107
svid, err := jwtsvid.ParseInsecure(signedToken, audience)
116108
require.NoError(ca.tb, err)
109+
110+
applyJWTSVIDOptions(svid, options...)
111+
117112
return svid
118113
}
119114

@@ -146,7 +141,7 @@ func (ca *CA) JWTBundle() *jwtbundle.Bundle {
146141
return jwtbundle.FromJWTAuthorities(ca.td, ca.JWTAuthorities())
147142
}
148143

149-
func CreateCACertificate(tb testing.TB, parent *x509.Certificate, parentKey crypto.Signer, options ...CertificateOption) (*x509.Certificate, crypto.Signer) {
144+
func CreateCACertificate(tb testing.TB, parent *x509.Certificate, parentKey crypto.Signer, options ...SVIDOption) (*x509.Certificate, crypto.Signer) {
150145
now := time.Now()
151146
serial := NewSerial(tb)
152147
key := NewEC256Key(tb)
@@ -161,7 +156,7 @@ func CreateCACertificate(tb testing.TB, parent *x509.Certificate, parentKey cryp
161156
NotAfter: now.Add(time.Hour),
162157
}
163158

164-
applyOptions(tmpl, options...)
159+
applyCertOptions(tmpl, options...)
165160

166161
if parent == nil {
167162
parent = tmpl
@@ -170,7 +165,7 @@ func CreateCACertificate(tb testing.TB, parent *x509.Certificate, parentKey cryp
170165
return CreateCertificate(tb, tmpl, parent, key.Public(), parentKey), key
171166
}
172167

173-
func CreateX509Certificate(tb testing.TB, parent *x509.Certificate, parentKey crypto.Signer, options ...CertificateOption) (*x509.Certificate, crypto.Signer) {
168+
func CreateX509Certificate(tb testing.TB, parent *x509.Certificate, parentKey crypto.Signer, options ...SVIDOption) (*x509.Certificate, crypto.Signer) {
174169
now := time.Now()
175170
serial := NewSerial(tb)
176171
key := NewEC256Key(tb)
@@ -184,12 +179,12 @@ func CreateX509Certificate(tb testing.TB, parent *x509.Certificate, parentKey cr
184179
KeyUsage: x509.KeyUsageDigitalSignature,
185180
}
186181

187-
applyOptions(tmpl, options...)
182+
applyCertOptions(tmpl, options...)
188183

189184
return CreateCertificate(tb, tmpl, parent, key.Public(), parentKey), key
190185
}
191186

192-
func CreateX509SVID(tb testing.TB, parent *x509.Certificate, parentKey crypto.Signer, id spiffeid.ID, options ...CertificateOption) (*x509.Certificate, crypto.Signer) {
187+
func CreateX509SVID(tb testing.TB, parent *x509.Certificate, parentKey crypto.Signer, id spiffeid.ID, options ...SVIDOption) (*x509.Certificate, crypto.Signer) {
193188
serial := NewSerial(tb)
194189
options = append(options,
195190
WithSerial(serial),
@@ -230,46 +225,105 @@ func NewSerial(tb testing.TB) *big.Int {
230225
return new(big.Int).SetBytes(b)
231226
}
232227

233-
func WithSerial(serial *big.Int) CertificateOption {
234-
return certificateOption(func(c *x509.Certificate) {
235-
c.SerialNumber = serial
236-
})
228+
type SVIDOption struct {
229+
certificateOption func(*x509.Certificate)
230+
x509SvidOption func(*x509svid.SVID)
231+
jwtSvidOption func(*jwtsvid.SVID)
232+
}
233+
234+
func (s SVIDOption) applyJWTSVIDOption(svid *jwtsvid.SVID) {
235+
if s.jwtSvidOption != nil {
236+
s.jwtSvidOption(svid)
237+
}
238+
}
239+
240+
func (s SVIDOption) applyCertOption(certificate *x509.Certificate) {
241+
if s.certificateOption != nil {
242+
s.certificateOption(certificate)
243+
}
244+
}
245+
246+
func (s SVIDOption) applyX509SVIDOption(svid *x509svid.SVID) {
247+
if s.x509SvidOption != nil {
248+
s.x509SvidOption(svid)
249+
}
250+
}
251+
252+
func WithSerial(serial *big.Int) SVIDOption {
253+
return SVIDOption{
254+
certificateOption: func(c *x509.Certificate) {
255+
c.SerialNumber = serial
256+
},
257+
}
258+
}
259+
260+
func WithKeyUsage(keyUsage x509.KeyUsage) SVIDOption {
261+
return SVIDOption{
262+
certificateOption: func(c *x509.Certificate) {
263+
c.KeyUsage = keyUsage
264+
},
265+
}
266+
}
267+
268+
func WithLifetime(notBefore, notAfter time.Time) SVIDOption {
269+
return SVIDOption{
270+
certificateOption: func(c *x509.Certificate) {
271+
c.NotBefore = notBefore
272+
c.NotAfter = notAfter
273+
},
274+
}
275+
}
276+
277+
func WithIPAddresses(ips ...net.IP) SVIDOption {
278+
return SVIDOption{
279+
certificateOption: func(c *x509.Certificate) {
280+
c.IPAddresses = ips
281+
},
282+
}
237283
}
238284

239-
func WithKeyUsage(keyUsage x509.KeyUsage) CertificateOption {
240-
return certificateOption(func(c *x509.Certificate) {
241-
c.KeyUsage = keyUsage
242-
})
285+
func WithURIs(uris ...*url.URL) SVIDOption {
286+
return SVIDOption{
287+
certificateOption: func(c *x509.Certificate) {
288+
c.URIs = uris
289+
},
290+
}
243291
}
244292

245-
func WithLifetime(notBefore, notAfter time.Time) CertificateOption {
246-
return certificateOption(func(c *x509.Certificate) {
247-
c.NotBefore = notBefore
248-
c.NotAfter = notAfter
249-
})
293+
func WithSubject(subject pkix.Name) SVIDOption {
294+
return SVIDOption{
295+
certificateOption: func(c *x509.Certificate) {
296+
c.Subject = subject
297+
},
298+
}
250299
}
251300

252-
func WithIPAddresses(ips ...net.IP) CertificateOption {
253-
return certificateOption(func(c *x509.Certificate) {
254-
c.IPAddresses = ips
255-
})
301+
func WithHint(hint string) SVIDOption {
302+
return SVIDOption{
303+
x509SvidOption: func(svid *x509svid.SVID) {
304+
svid.Hint = hint
305+
},
306+
jwtSvidOption: func(svid *jwtsvid.SVID) {
307+
svid.Hint = hint
308+
},
309+
}
256310
}
257311

258-
func WithURIs(uris ...*url.URL) CertificateOption {
259-
return certificateOption(func(c *x509.Certificate) {
260-
c.URIs = uris
261-
})
312+
func applyCertOptions(c *x509.Certificate, options ...SVIDOption) {
313+
for _, opt := range options {
314+
opt.applyCertOption(c)
315+
}
262316
}
263317

264-
func WithSubject(subject pkix.Name) CertificateOption {
265-
return certificateOption(func(c *x509.Certificate) {
266-
c.Subject = subject
267-
})
318+
func applyX509SVIDOptions(svid *x509svid.SVID, options ...SVIDOption) {
319+
for _, opt := range options {
320+
opt.applyX509SVIDOption(svid)
321+
}
268322
}
269323

270-
func applyOptions(c *x509.Certificate, options ...CertificateOption) {
324+
func applyJWTSVIDOptions(svid *jwtsvid.SVID, options ...SVIDOption) {
271325
for _, opt := range options {
272-
opt.apply(c)
326+
opt.applyJWTSVIDOption(svid)
273327
}
274328
}
275329

v2/internal/test/fakeworkloadapi/workload_api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ func (r *X509SVIDResponse) ToProto(tb testing.TB) *workload.X509SVIDResponse {
213213
X509Svid: x509util.ConcatRawCertsFromCerts(svid.Certificates),
214214
X509SvidKey: keyDER,
215215
Bundle: bundle,
216+
Hint: svid.Hint,
216217
})
217218
}
218219
for _, v := range r.FederatedBundles {

v2/svid/jwtsvid/svid.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ type SVID struct {
2828
Expiry time.Time
2929
// Claims is the parsed claims from token
3030
Claims map[string]interface{}
31+
// Hint is an operator-specified string used to provide guidance on how this
32+
// identity should be used by a workload when more than one SVID is returned.
33+
Hint string
3134

3235
// token is the serialized JWT token
3336
token string

v2/svid/x509svid/svid.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ type SVID struct {
2626

2727
// PrivateKey is the private key for the X509-SVID.
2828
PrivateKey crypto.Signer
29+
30+
// Hint is an operator-specified string used to provide guidance on how this
31+
// identity should be used by a workload when more than one SVID is returned.
32+
Hint string
2933
}
3034

3135
// Load loads the X509-SVID from PEM encoded files on disk. certFile and

v2/workloadapi/client.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ func parseX509Context(resp *workload.X509SVIDResponse) (*X509Context, error) {
426426

427427
// parseX509SVIDs parses one or all of the SVIDs in the response. If firstOnly
428428
// is true, then only the first SVID in the response is parsed and returned.
429-
// Otherwise all SVIDs are parsed and returned.
429+
// Otherwise, all SVIDs are parsed and returned.
430430
func parseX509SVIDs(resp *workload.X509SVIDResponse, firstOnly bool) ([]*x509svid.SVID, error) {
431431
n := len(resp.Svids)
432432
if n == 0 {
@@ -436,10 +436,20 @@ func parseX509SVIDs(resp *workload.X509SVIDResponse, firstOnly bool) ([]*x509svi
436436
n = 1
437437
}
438438

439+
hints := make(map[string]struct{}, n)
439440
svids := make([]*x509svid.SVID, 0, n)
440441
for i := 0; i < n; i++ {
441442
svid := resp.Svids[i]
443+
// In the event of more than one X509SVID message with the same hint value set, then the first message in the
444+
// list SHOULD be selected.
445+
if _, ok := hints[svid.Hint]; ok && svid.Hint != "" {
446+
continue
447+
}
448+
449+
hints[svid.Hint] = struct{}{}
450+
442451
s, err := x509svid.ParseRaw(svid.X509Svid, svid.X509SvidKey)
452+
s.Hint = svid.Hint
443453
if err != nil {
444454
return nil, err
445455
}
@@ -506,7 +516,7 @@ func parseX509BundlesResponse(resp *workload.X509BundlesResponse) (*x509bundle.S
506516

507517
// parseJWTSVIDs parses one or all of the SVIDs in the response. If firstOnly
508518
// is true, then only the first SVID in the response is parsed and returned.
509-
// Otherwise all SVIDs are parsed and returned.
519+
// Otherwise, all SVIDs are parsed and returned.
510520
func parseJWTSVIDs(resp *workload.JWTSVIDResponse, audience []string, firstOnly bool) ([]*jwtsvid.SVID, error) {
511521
n := len(resp.Svids)
512522
if n == 0 {
@@ -516,10 +526,19 @@ func parseJWTSVIDs(resp *workload.JWTSVIDResponse, audience []string, firstOnly
516526
n = 1
517527
}
518528

529+
hints := make(map[string]struct{}, n)
519530
svids := make([]*jwtsvid.SVID, 0, n)
520531
for i := 0; i < n; i++ {
521532
svid := resp.Svids[i]
533+
// In the event of more than one X509SVID message with the same hint value set, then the first message in the
534+
// list SHOULD be selected.
535+
if _, ok := hints[svid.Hint]; ok && svid.Hint != "" {
536+
continue
537+
}
538+
hints[svid.Hint] = struct{}{}
539+
522540
s, err := jwtsvid.ParseInsecure(svid.Svid, audience)
541+
s.Hint = svid.Hint
523542
if err != nil {
524543
return nil, err
525544
}

0 commit comments

Comments
 (0)