Skip to content

Commit 0b5f648

Browse files
committed
change provisioners api
* /provisioners -> /provisioners/jwk-set-by-issuer * /provisioners now returns a list of Provisioners
1 parent 7b6a3ea commit 0b5f648

13 files changed

+354
-144
lines changed

api/api.go

+31-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/go-chi/chi"
1313
"github.com/pkg/errors"
14+
"github.com/smallstep/ca-component/provisioner"
1415
"github.com/smallstep/cli/crypto/tlsutil"
1516
"github.com/smallstep/cli/crypto/x509util"
1617
"github.com/smallstep/cli/jose"
@@ -46,7 +47,7 @@ type Authority interface {
4647
Root(shasum string) (*x509.Certificate, error)
4748
Sign(cr *x509.CertificateRequest, opts SignOptions, claims ...Claim) (*x509.Certificate, *x509.Certificate, error)
4849
Renew(cert *x509.Certificate) (*x509.Certificate, *x509.Certificate, error)
49-
GetProvisioners() (map[string]*jose.JSONWebKeySet, error)
50+
GetProvisioners() ([]*provisioner.Provisioner, error)
5051
GetEncryptedKey(kid string) (string, error)
5152
}
5253

@@ -172,10 +173,16 @@ type SignRequest struct {
172173
NotBefore time.Time `json:"notBefore"`
173174
}
174175

175-
// ProvisionersResponse is the response object that returns the map of
176+
// ProvisionersResponse is the response object that returns the list of
176177
// provisioners.
177178
type ProvisionersResponse struct {
178-
Provisioners map[string]*jose.JSONWebKeySet `json:"provisioners"`
179+
Provisioners []*provisioner.Provisioner `json:"provisioners"`
180+
}
181+
182+
// JWKSetByIssuerResponse is the response object that returns the map of
183+
// provisioners.
184+
type JWKSetByIssuerResponse struct {
185+
Map map[string]*jose.JSONWebKeySet `json:"map"`
179186
}
180187

181188
// ProvisionerKeyResponse is the response object that returns the encryptoed key
@@ -250,6 +257,7 @@ func (h *caHandler) Route(r Router) {
250257
r.MethodFunc("POST", "/renew", h.Renew)
251258
r.MethodFunc("GET", "/provisioners", h.Provisioners)
252259
r.MethodFunc("GET", "/provisioners/{kid}/encrypted-key", h.ProvisionerKey)
260+
r.MethodFunc("GET", "/provisioners/jwk-set-by-issuer", h.JWKSetByIssuer)
253261
}
254262

255263
// Health is an HTTP handler that returns the status of the server.
@@ -353,3 +361,23 @@ func (h *caHandler) ProvisionerKey(w http.ResponseWriter, r *http.Request) {
353361
}
354362
JSON(w, &ProvisionerKeyResponse{key})
355363
}
364+
365+
func (h *caHandler) JWKSetByIssuer(w http.ResponseWriter, r *http.Request) {
366+
m := map[string]*jose.JSONWebKeySet{}
367+
ps, err := h.Authority.GetProvisioners()
368+
if err != nil {
369+
WriteError(w, InternalServerError(err))
370+
return
371+
}
372+
for _, p := range ps {
373+
ks, found := m[p.Issuer]
374+
if found {
375+
ks.Keys = append(ks.Keys, *p.Key)
376+
} else {
377+
ks = new(jose.JSONWebKeySet)
378+
ks.Keys = []jose.JSONWebKey{*p.Key}
379+
m[p.Issuer] = ks
380+
}
381+
}
382+
JSON(w, &JWKSetByIssuerResponse{m})
383+
}

api/api_test.go

+94-10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"time"
1818

1919
"github.com/go-chi/chi"
20+
"github.com/smallstep/ca-component/provisioner"
2021
"github.com/smallstep/cli/crypto/tlsutil"
2122
"github.com/smallstep/cli/jose"
2223
)
@@ -397,7 +398,7 @@ type mockAuthority struct {
397398
root func(shasum string) (*x509.Certificate, error)
398399
sign func(cr *x509.CertificateRequest, opts SignOptions, claims ...Claim) (*x509.Certificate, *x509.Certificate, error)
399400
renew func(cert *x509.Certificate) (*x509.Certificate, *x509.Certificate, error)
400-
getProvisioners func() (map[string]*jose.JSONWebKeySet, error)
401+
getProvisioners func() ([]*provisioner.Provisioner, error)
401402
getEncryptedKey func(kid string) (string, error)
402403
}
403404

@@ -444,11 +445,11 @@ func (m *mockAuthority) Renew(cert *x509.Certificate) (*x509.Certificate, *x509.
444445
return m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate), m.err
445446
}
446447

447-
func (m *mockAuthority) GetProvisioners() (map[string]*jose.JSONWebKeySet, error) {
448+
func (m *mockAuthority) GetProvisioners() ([]*provisioner.Provisioner, error) {
448449
if m.getProvisioners != nil {
449450
return m.getProvisioners()
450451
}
451-
return m.ret1.(map[string]*jose.JSONWebKeySet), m.err
452+
return m.ret1.([]*provisioner.Provisioner), m.err
452453
}
453454

454455
func (m *mockAuthority) GetEncryptedKey(kid string) (string, error) {
@@ -670,6 +671,82 @@ func Test_caHandler_Renew(t *testing.T) {
670671
}
671672
}
672673

674+
func Test_caHandler_JWKSetByIssuer(t *testing.T) {
675+
type fields struct {
676+
Authority Authority
677+
}
678+
type args struct {
679+
w http.ResponseWriter
680+
r *http.Request
681+
}
682+
683+
req, err := http.NewRequest("GET", "http://example.com/provisioners/jwk-set-by-issuer", nil)
684+
if err != nil {
685+
t.Fatal(err)
686+
}
687+
688+
var key jose.JSONWebKey
689+
if err := json.Unmarshal([]byte(pubKey), &key); err != nil {
690+
t.Fatal(err)
691+
}
692+
693+
p := []*provisioner.Provisioner{
694+
&provisioner.Provisioner{
695+
Issuer: "p1",
696+
Key: &key,
697+
},
698+
&provisioner.Provisioner{
699+
Issuer: "p2",
700+
Key: &key,
701+
},
702+
}
703+
704+
tests := []struct {
705+
name string
706+
fields fields
707+
args args
708+
statusCode int
709+
}{
710+
{"ok", fields{&mockAuthority{ret1: p}}, args{httptest.NewRecorder(), req}, 200},
711+
{"fail", fields{&mockAuthority{ret1: p, err: fmt.Errorf("the error")}}, args{httptest.NewRecorder(), req}, 500},
712+
}
713+
714+
expectedKey, err := json.Marshal(key)
715+
if err != nil {
716+
t.Fatal(err)
717+
}
718+
expected := []byte(`{"map":{"p1":{"keys":[` + string(expectedKey) + `]},"p2":{"keys":[` + string(expectedKey) + `]}}}`)
719+
expectedError := []byte(`{"status":500,"message":"Internal Server Error"}`)
720+
for _, tt := range tests {
721+
t.Run(tt.name, func(t *testing.T) {
722+
h := &caHandler{
723+
Authority: tt.fields.Authority,
724+
}
725+
h.JWKSetByIssuer(tt.args.w, tt.args.r)
726+
727+
rec := tt.args.w.(*httptest.ResponseRecorder)
728+
res := rec.Result()
729+
if res.StatusCode != tt.statusCode {
730+
t.Errorf("caHandler.JWKSetByIssuer StatusCode = %d, wants %d", res.StatusCode, tt.statusCode)
731+
}
732+
body, err := ioutil.ReadAll(res.Body)
733+
res.Body.Close()
734+
if err != nil {
735+
t.Errorf("caHandler.JWKSetByIssuer unexpected error = %v", err)
736+
}
737+
if tt.statusCode < http.StatusBadRequest {
738+
if !bytes.Equal(bytes.TrimSpace(body), expected) {
739+
t.Errorf("caHandler.JWKSetByIssuer Body = %s, wants %s", body, expected)
740+
}
741+
} else {
742+
if !bytes.Equal(bytes.TrimSpace(body), expectedError) {
743+
t.Errorf("caHandler.JWKSetByIssuer Body = %s, wants %s", body, expectedError)
744+
}
745+
}
746+
})
747+
}
748+
}
749+
673750
func Test_caHandler_Provisioners(t *testing.T) {
674751
type fields struct {
675752
Authority Authority
@@ -689,14 +766,21 @@ func Test_caHandler_Provisioners(t *testing.T) {
689766
t.Fatal(err)
690767
}
691768

692-
p := map[string]*jose.JSONWebKeySet{
693-
"p1": &jose.JSONWebKeySet{
694-
Keys: []jose.JSONWebKey{key},
769+
p := []*provisioner.Provisioner{
770+
&provisioner.Provisioner{
771+
Type: "JWK",
772+
Issuer: "max",
773+
EncryptedKey: "abc",
774+
Key: &key,
695775
},
696-
"p2": &jose.JSONWebKeySet{
697-
Keys: []jose.JSONWebKey{key},
776+
&provisioner.Provisioner{
777+
Type: "JWK",
778+
Issuer: "mariano",
779+
EncryptedKey: "def",
780+
Key: &key,
698781
},
699782
}
783+
pr := ProvisionersResponse{p}
700784

701785
tests := []struct {
702786
name string
@@ -708,11 +792,11 @@ func Test_caHandler_Provisioners(t *testing.T) {
708792
{"fail", fields{&mockAuthority{ret1: p, err: fmt.Errorf("the error")}}, args{httptest.NewRecorder(), req}, 500},
709793
}
710794

711-
expectedKey, err := json.Marshal(key)
795+
expected, err := json.Marshal(pr)
712796
if err != nil {
713797
t.Fatal(err)
714798
}
715-
expected := []byte(`{"provisioners":{"p1":{"keys":[` + string(expectedKey) + `]},"p2":{"keys":[` + string(expectedKey) + `]}}}`)
799+
716800
expectedError := []byte(`{"status":500,"message":"Internal Server Error"}`)
717801
for _, tt := range tests {
718802
t.Run(tt.name, func(t *testing.T) {

authority/authority_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/pkg/errors"
99
"github.com/smallstep/assert"
10+
"github.com/smallstep/ca-component/provisioner"
1011
stepJOSE "github.com/smallstep/cli/jose"
1112
)
1213

@@ -15,7 +16,7 @@ func testAuthority(t *testing.T) *Authority {
1516
assert.FatalError(t, err)
1617
clijwk, err := stepJOSE.ParseKey("testdata/secrets/step_cli_key_pub.jwk")
1718
assert.FatalError(t, err)
18-
p := []*Provisioner{
19+
p := []*provisioner.Provisioner{
1920
{
2021
Issuer: "Max",
2122
Type: "JWK",

authority/authorize.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/pkg/errors"
88
"github.com/smallstep/ca-component/api"
9+
"github.com/smallstep/ca-component/provisioner"
910
"gopkg.in/square/go-jose.v2/jwt"
1011
)
1112

@@ -63,7 +64,7 @@ func (a *Authority) Authorize(ott string) ([]api.Claim, error) {
6364
return nil, &apiError{errors.Errorf("Provisioner with KeyID %s could not be found", kid),
6465
http.StatusUnauthorized, errContext}
6566
}
66-
p, ok := val.(*Provisioner)
67+
p, ok := val.(*provisioner.Provisioner)
6768
if !ok {
6869
return nil, &apiError{errors.Errorf("stored value is not a *Provisioner"),
6970
http.StatusInternalServerError, context{}}

authority/config.go

+5-29
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import (
66
"time"
77

88
"github.com/pkg/errors"
9+
"github.com/smallstep/ca-component/provisioner"
910
"github.com/smallstep/cli/crypto/tlsutil"
1011
"github.com/smallstep/cli/crypto/x509util"
11-
jose "gopkg.in/square/go-jose.v2"
1212
)
1313

1414
// DefaultTLSOptions represents the default TLS version as well as the cipher
@@ -51,30 +51,6 @@ func (d *duration) UnmarshalJSON(data []byte) (err error) {
5151
return
5252
}
5353

54-
// Provisioner - authorized entity that can sign tokens necessary for signature requests.
55-
type Provisioner struct {
56-
Issuer string `json:"issuer,omitempty"`
57-
Type string `json:"type,omitempty"`
58-
Key *jose.JSONWebKey `json:"key,omitempty"`
59-
EncryptedKey string `json:"encryptedKey,omitempty"`
60-
}
61-
62-
// Validate validates a provisioner.
63-
func (p *Provisioner) Validate() error {
64-
switch {
65-
case p.Issuer == "":
66-
return errors.New("provisioner issuer cannot be empty")
67-
68-
case p.Type == "":
69-
return errors.New("provisioner type cannot be empty")
70-
71-
case p.Key == nil:
72-
return errors.New("provisioner key cannot be empty")
73-
}
74-
75-
return nil
76-
}
77-
7854
// Config represents the CA configuration and it's mapped to a JSON object.
7955
type Config struct {
8056
Root string `json:"root"`
@@ -91,10 +67,10 @@ type Config struct {
9167

9268
// AuthConfig represents the configuration options for the authority.
9369
type AuthConfig struct {
94-
Provisioners []*Provisioner `json:"provisioners,omitempty"`
95-
Template *x509util.ASN1DN `json:"template,omitempty"`
96-
MinCertDuration *duration `json:"minCertDuration,omitempty"`
97-
MaxCertDuration *duration `json:"maxCertDuration,omitempty"`
70+
Provisioners []*provisioner.Provisioner `json:"provisioners,omitempty"`
71+
Template *x509util.ASN1DN `json:"template,omitempty"`
72+
MinCertDuration *duration `json:"minCertDuration,omitempty"`
73+
MaxCertDuration *duration `json:"maxCertDuration,omitempty"`
9874
}
9975

10076
// Validate validates the authority configuration.

0 commit comments

Comments
 (0)