Skip to content

Commit c55b27a

Browse files
committed
Refactor admin token to use with RAs.
1 parent db337de commit c55b27a

File tree

9 files changed

+114
-50
lines changed

9 files changed

+114
-50
lines changed

Diff for: authority/authorize.go

+5-8
Original file line numberDiff line numberDiff line change
@@ -130,22 +130,19 @@ func (a *Authority) AuthorizeAdminToken(r *http.Request, token string) (*linkedc
130130
// According to "rfc7519 JSON Web Token" acceptable skew should be no
131131
// more than a few minutes.
132132
if err := claims.ValidateWithLeeway(jose.Expected{
133-
Issuer: prov.GetName(),
133+
Issuer: "step-admin-client/1.0",
134134
Time: time.Now().UTC(),
135135
}, time.Minute); err != nil {
136136
return nil, admin.WrapError(admin.ErrorUnauthorizedType, err, "x5c.authorizeToken; invalid x5c claims")
137137
}
138138

139139
// validate audience: path matches the current path
140-
if r.URL.Path != claims.Audience[0] {
141-
return nil, admin.NewError(admin.ErrorUnauthorizedType,
142-
"x5c.authorizeToken; x5c token has invalid audience "+
143-
"claim (aud); expected %s, but got %s", r.URL.Path, claims.Audience)
140+
if !matchesAudience(claims.Audience, a.config.Audience(r.URL.Path)) {
141+
return nil, admin.NewError(admin.ErrorUnauthorizedType, "x5c.authorizeToken; x5c token has invalid audience claim (aud)")
144142
}
145143

146144
if claims.Subject == "" {
147-
return nil, admin.NewError(admin.ErrorUnauthorizedType,
148-
"x5c.authorizeToken; x5c token subject cannot be empty")
145+
return nil, admin.NewError(admin.ErrorUnauthorizedType, "x5c.authorizeToken; x5c token subject cannot be empty")
149146
}
150147

151148
var (
@@ -156,7 +153,7 @@ func (a *Authority) AuthorizeAdminToken(r *http.Request, token string) (*linkedc
156153
adminSANs := append([]string{leaf.Subject.CommonName}, leaf.DNSNames...)
157154
adminSANs = append(adminSANs, leaf.EmailAddresses...)
158155
for _, san := range adminSANs {
159-
if adm, ok = a.LoadAdminBySubProv(san, claims.Issuer); ok {
156+
if adm, ok = a.LoadAdminBySubProv(san, prov.GetName()); ok {
160157
adminFound = true
161158
break
162159
}

Diff for: authority/config/config.go

+12
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,18 @@ func (c *Config) GetAudiences() provisioner.Audiences {
304304
return audiences
305305
}
306306

307+
// Audience returns the list of audiences for a given path.
308+
func (c *Config) Audience(path string) []string {
309+
audiences := make([]string, len(c.DNSNames)+1)
310+
for i, name := range c.DNSNames {
311+
hostname := toHostname(name)
312+
audiences[i] = "https://" + hostname + path
313+
}
314+
// For backward compatibility
315+
audiences[len(c.DNSNames)] = path
316+
return audiences
317+
}
318+
307319
func toHostname(name string) string {
308320
// ensure an IPv6 address is represented with square brackets when used as hostname
309321
if ip := net.ParseIP(name); ip != nil && ip.To4() == nil {

Diff for: authority/config/config_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package config
22

33
import (
44
"fmt"
5+
"reflect"
56
"testing"
67

78
"github.com/pkg/errors"
@@ -317,3 +318,38 @@ func Test_toHostname(t *testing.T) {
317318
})
318319
}
319320
}
321+
322+
func TestConfig_Audience(t *testing.T) {
323+
type fields struct {
324+
DNSNames []string
325+
}
326+
type args struct {
327+
path string
328+
}
329+
tests := []struct {
330+
name string
331+
fields fields
332+
args args
333+
want []string
334+
}{
335+
{"ok", fields{[]string{
336+
"ca", "ca.example.com", "127.0.0.1", "::1",
337+
}}, args{"/path"}, []string{
338+
"https://ca/path",
339+
"https://ca.example.com/path",
340+
"https://127.0.0.1/path",
341+
"https://[::1]/path",
342+
"/path",
343+
}},
344+
}
345+
for _, tt := range tests {
346+
t.Run(tt.name, func(t *testing.T) {
347+
c := &Config{
348+
DNSNames: tt.fields.DNSNames,
349+
}
350+
if got := c.Audience(tt.args.path); !reflect.DeepEqual(got, tt.want) {
351+
t.Errorf("Config.Audience() = %v, want %v", got, tt.want)
352+
}
353+
})
354+
}
355+
}

Diff for: authority/linkedca.go

+22
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,28 @@ func (c *linkedCaClient) DeleteAdmin(ctx context.Context, id string) error {
235235
return errors.Wrap(err, "error deleting admin")
236236
}
237237

238+
func (c *linkedCaClient) GetCertificateData(serial string) (*db.CertificateData, error) {
239+
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
240+
defer cancel()
241+
242+
resp, err := c.client.GetCertificate(ctx, &linkedca.GetCertificateRequest{
243+
Serial: serial,
244+
})
245+
if err != nil {
246+
return nil, err
247+
}
248+
249+
var provisioner *db.ProvisionerData
250+
if p := resp.Provisioner; p != nil {
251+
provisioner = &db.ProvisionerData{
252+
ID: p.Id, Name: p.Name, Type: p.Type.String(),
253+
}
254+
}
255+
return &db.CertificateData{
256+
Provisioner: provisioner,
257+
}, nil
258+
}
259+
238260
func (c *linkedCaClient) StoreCertificateChain(prov provisioner.Interface, fullchain ...*x509.Certificate) error {
239261
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
240262
defer cancel()

Diff for: authority/provisioners.go

+12-7
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,19 @@ func (a *Authority) LoadProvisionerByCertificate(crt *x509.Certificate) (provisi
5454
return p, nil
5555
}
5656

57-
// Attempt to load the provisioner using the linked db
58-
// TODO:(mariano)
59-
60-
// Attempt to load the provisioner from the db
61-
if db, ok := a.db.(interface {
57+
// certificateDataGetter is an interface that can be use to retrieve the
58+
// provisioner from a db or a linked ca.
59+
type certificateDataGetter interface {
6260
GetCertificateData(string) (*db.CertificateData, error)
63-
}); ok {
64-
if data, err := db.GetCertificateData(crt.SerialNumber.String()); err == nil && data.Provisioner != nil {
61+
}
62+
var cdg certificateDataGetter
63+
if getter, ok := a.adminDB.(certificateDataGetter); ok {
64+
cdg = getter
65+
} else if getter, ok := a.db.(certificateDataGetter); ok {
66+
cdg = getter
67+
}
68+
if cdg != nil {
69+
if data, err := cdg.GetCertificateData(crt.SerialNumber.String()); err == nil && data.Provisioner != nil {
6570
loadProvisioner = func() (provisioner.Interface, error) {
6671
p, ok := a.provisioners.Load(data.Provisioner.ID)
6772
if !ok {

Diff for: ca/adminClient.go

+19-18
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ import (
2323
"google.golang.org/protobuf/encoding/protojson"
2424
)
2525

26-
var adminURLPrefix = "admin"
26+
const (
27+
adminURLPrefix = "admin"
28+
adminIssuer = "step-admin-client/1.0"
29+
)
2730

2831
// AdminClient implements an HTTP client for the CA server.
2932
type AdminClient struct {
@@ -35,7 +38,6 @@ type AdminClient struct {
3538
x5cCertFile string
3639
x5cCertStrs []string
3740
x5cCert *x509.Certificate
38-
x5cIssuer string
3941
x5cSubject string
4042
}
4143

@@ -77,12 +79,11 @@ func NewAdminClient(endpoint string, opts ...ClientOption) (*AdminClient, error)
7779
x5cCertFile: o.x5cCertFile,
7880
x5cCertStrs: o.x5cCertStrs,
7981
x5cCert: o.x5cCert,
80-
x5cIssuer: o.x5cIssuer,
8182
x5cSubject: o.x5cSubject,
8283
}, nil
8384
}
8485

85-
func (c *AdminClient) generateAdminToken(urlPath string) (string, error) {
86+
func (c *AdminClient) generateAdminToken(aud *url.URL) (string, error) {
8687
// A random jwt id will be used to identify duplicated tokens
8788
jwtID, err := randutil.Hex(64) // 256 bits
8889
if err != nil {
@@ -93,8 +94,8 @@ func (c *AdminClient) generateAdminToken(urlPath string) (string, error) {
9394
tokOptions := []token.Options{
9495
token.WithJWTID(jwtID),
9596
token.WithKid(c.x5cJWK.KeyID),
96-
token.WithIssuer(c.x5cIssuer),
97-
token.WithAudience(urlPath),
97+
token.WithIssuer(adminIssuer),
98+
token.WithAudience(aud.String()),
9899
token.WithValidity(now, now.Add(token.DefaultValidity)),
99100
token.WithX5CCerts(c.x5cCertStrs),
100101
}
@@ -205,7 +206,7 @@ func (c *AdminClient) GetAdminsPaginate(opts ...AdminOption) (*adminAPI.GetAdmin
205206
Path: "/admin/admins",
206207
RawQuery: o.rawQuery(),
207208
})
208-
tok, err := c.generateAdminToken(u.Path)
209+
tok, err := c.generateAdminToken(u)
209210
if err != nil {
210211
return nil, errors.Wrapf(err, "error generating admin token")
211212
}
@@ -260,7 +261,7 @@ func (c *AdminClient) CreateAdmin(createAdminRequest *adminAPI.CreateAdminReques
260261
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
261262
}
262263
u := c.endpoint.ResolveReference(&url.URL{Path: "/admin/admins"})
263-
tok, err := c.generateAdminToken(u.Path)
264+
tok, err := c.generateAdminToken(u)
264265
if err != nil {
265266
return nil, errors.Wrapf(err, "error generating admin token")
266267
}
@@ -292,7 +293,7 @@ retry:
292293
func (c *AdminClient) RemoveAdmin(id string) error {
293294
var retried bool
294295
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "admins", id)})
295-
tok, err := c.generateAdminToken(u.Path)
296+
tok, err := c.generateAdminToken(u)
296297
if err != nil {
297298
return errors.Wrapf(err, "error generating admin token")
298299
}
@@ -324,7 +325,7 @@ func (c *AdminClient) UpdateAdmin(id string, uar *adminAPI.UpdateAdminRequest) (
324325
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
325326
}
326327
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "admins", id)})
327-
tok, err := c.generateAdminToken(u.Path)
328+
tok, err := c.generateAdminToken(u)
328329
if err != nil {
329330
return nil, errors.Wrapf(err, "error generating admin token")
330331
}
@@ -371,7 +372,7 @@ func (c *AdminClient) GetProvisioner(opts ...ProvisionerOption) (*linkedca.Provi
371372
default:
372373
return nil, errors.New("must set either name or id in method options")
373374
}
374-
tok, err := c.generateAdminToken(u.Path)
375+
tok, err := c.generateAdminToken(u)
375376
if err != nil {
376377
return nil, errors.Wrapf(err, "error generating admin token")
377378
}
@@ -410,7 +411,7 @@ func (c *AdminClient) GetProvisionersPaginate(opts ...ProvisionerOption) (*admin
410411
Path: "/admin/provisioners",
411412
RawQuery: o.rawQuery(),
412413
})
413-
tok, err := c.generateAdminToken(u.Path)
414+
tok, err := c.generateAdminToken(u)
414415
if err != nil {
415416
return nil, errors.Wrapf(err, "error generating admin token")
416417
}
@@ -480,7 +481,7 @@ func (c *AdminClient) RemoveProvisioner(opts ...ProvisionerOption) error {
480481
default:
481482
return errors.New("must set either name or id in method options")
482483
}
483-
tok, err := c.generateAdminToken(u.Path)
484+
tok, err := c.generateAdminToken(u)
484485
if err != nil {
485486
return errors.Wrapf(err, "error generating admin token")
486487
}
@@ -512,7 +513,7 @@ func (c *AdminClient) CreateProvisioner(prov *linkedca.Provisioner) (*linkedca.P
512513
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
513514
}
514515
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners")})
515-
tok, err := c.generateAdminToken(u.Path)
516+
tok, err := c.generateAdminToken(u)
516517
if err != nil {
517518
return nil, errors.Wrapf(err, "error generating admin token")
518519
}
@@ -548,7 +549,7 @@ func (c *AdminClient) UpdateProvisioner(name string, prov *linkedca.Provisioner)
548549
return errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
549550
}
550551
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", name)})
551-
tok, err := c.generateAdminToken(u.Path)
552+
tok, err := c.generateAdminToken(u)
552553
if err != nil {
553554
return errors.Wrapf(err, "error generating admin token")
554555
}
@@ -587,7 +588,7 @@ func (c *AdminClient) GetExternalAccountKeysPaginate(provisionerName, reference
587588
Path: p,
588589
RawQuery: o.rawQuery(),
589590
})
590-
tok, err := c.generateAdminToken(u.Path)
591+
tok, err := c.generateAdminToken(u)
591592
if err != nil {
592593
return nil, errors.Wrapf(err, "error generating admin token")
593594
}
@@ -623,7 +624,7 @@ func (c *AdminClient) CreateExternalAccountKey(provisionerName string, eakReques
623624
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
624625
}
625626
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "acme/eab/", provisionerName)})
626-
tok, err := c.generateAdminToken(u.Path)
627+
tok, err := c.generateAdminToken(u)
627628
if err != nil {
628629
return nil, errors.Wrapf(err, "error generating admin token")
629630
}
@@ -655,7 +656,7 @@ retry:
655656
func (c *AdminClient) RemoveExternalAccountKey(provisionerName, keyID string) error {
656657
var retried bool
657658
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "acme/eab", provisionerName, "/", keyID)})
658-
tok, err := c.generateAdminToken(u.Path)
659+
tok, err := c.generateAdminToken(u)
659660
if err != nil {
660661
return errors.Wrapf(err, "error generating admin token")
661662
}

Diff for: ca/client.go

+7-14
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@ type clientOptions struct {
116116
x5cCertFile string
117117
x5cCertStrs []string
118118
x5cCert *x509.Certificate
119-
x5cIssuer string
120119
x5cSubject string
121120
}
122121

@@ -332,19 +331,13 @@ func WithAdminX5C(certs []*x509.Certificate, key interface{}, passwordFile strin
332331
}
333332

334333
o.x5cCert = certs[0]
335-
o.x5cSubject = o.x5cCert.Subject.CommonName
336-
337-
for _, e := range o.x5cCert.Extensions {
338-
if e.Id.Equal(stepOIDProvisioner) {
339-
var prov stepProvisionerASN1
340-
if _, err := asn1.Unmarshal(e.Value, &prov); err != nil {
341-
return errors.Wrap(err, "error unmarshaling provisioner OID from certificate")
342-
}
343-
o.x5cIssuer = string(prov.Name)
344-
}
345-
}
346-
if o.x5cIssuer == "" {
347-
return errors.New("provisioner extension not found in certificate")
334+
switch leaf := certs[0]; {
335+
case leaf.Subject.CommonName != "":
336+
o.x5cSubject = leaf.Subject.CommonName
337+
case len(leaf.DNSNames) > 0:
338+
o.x5cSubject = leaf.DNSNames[0]
339+
case len(leaf.EmailAddresses) > 0:
340+
o.x5cSubject = leaf.EmailAddresses[0]
348341
}
349342

350343
return nil

Diff for: go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,4 @@ require (
5050
// replace github.com/smallstep/nosql => ../nosql
5151
// replace go.step.sm/crypto => ../crypto
5252
// replace go.step.sm/cli-utils => ../cli-utils
53-
// replace go.step.sm/linkedca => ../linkedca
53+
replace go.step.sm/linkedca => ../linkedca

Diff for: go.sum

-2
Original file line numberDiff line numberDiff line change
@@ -709,8 +709,6 @@ go.step.sm/cli-utils v0.7.0/go.mod h1:Ur6bqA/yl636kCUJbp30J7Unv5JJ226eW2KqXPDwF/
709709
go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
710710
go.step.sm/crypto v0.15.3 h1:f3GMl+aCydt294BZRjTYwpaXRqwwndvoTY2NLN4wu10=
711711
go.step.sm/crypto v0.15.3/go.mod h1:3G0yQr5lQqfEG0CMYz8apC/qMtjLRQlzflL2AxkcN+g=
712-
go.step.sm/linkedca v0.12.0 h1:FA18uJO5P6W2pklcezMs+w+N3dVbpKEE1LP9HLsJgg4=
713-
go.step.sm/linkedca v0.12.0/go.mod h1:W59ucS4vFpuR0g4PtkGbbtXAwxbDEnNCg+ovkej1ANM=
714712
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
715713
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
716714
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=

0 commit comments

Comments
 (0)