diff --git a/api/api.go b/api/api.go index 090acb62..6c8b2810 100644 --- a/api/api.go +++ b/api/api.go @@ -11,6 +11,7 @@ import ( "github.com/go-chi/chi" "github.com/pkg/errors" + "github.com/smallstep/ca-component/provisioner" "github.com/smallstep/cli/crypto/tlsutil" "github.com/smallstep/cli/crypto/x509util" "github.com/smallstep/cli/jose" @@ -46,7 +47,7 @@ type Authority interface { Root(shasum string) (*x509.Certificate, error) Sign(cr *x509.CertificateRequest, opts SignOptions, claims ...Claim) (*x509.Certificate, *x509.Certificate, error) Renew(cert *x509.Certificate) (*x509.Certificate, *x509.Certificate, error) - GetProvisioners() (map[string]*jose.JSONWebKeySet, error) + GetProvisioners() ([]*provisioner.Provisioner, error) GetEncryptedKey(kid string) (string, error) } @@ -172,10 +173,16 @@ type SignRequest struct { NotBefore time.Time `json:"notBefore"` } -// ProvisionersResponse is the response object that returns the map of +// ProvisionersResponse is the response object that returns the list of // provisioners. type ProvisionersResponse struct { - Provisioners map[string]*jose.JSONWebKeySet `json:"provisioners"` + Provisioners []*provisioner.Provisioner `json:"provisioners"` +} + +// JWKSetByIssuerResponse is the response object that returns the map of +// provisioners. +type JWKSetByIssuerResponse struct { + Map map[string]*jose.JSONWebKeySet `json:"map"` } // ProvisionerKeyResponse is the response object that returns the encryptoed key @@ -250,6 +257,7 @@ func (h *caHandler) Route(r Router) { r.MethodFunc("POST", "/renew", h.Renew) r.MethodFunc("GET", "/provisioners", h.Provisioners) r.MethodFunc("GET", "/provisioners/{kid}/encrypted-key", h.ProvisionerKey) + r.MethodFunc("GET", "/provisioners/jwk-set-by-issuer", h.JWKSetByIssuer) } // 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) { } JSON(w, &ProvisionerKeyResponse{key}) } + +func (h *caHandler) JWKSetByIssuer(w http.ResponseWriter, r *http.Request) { + m := map[string]*jose.JSONWebKeySet{} + ps, err := h.Authority.GetProvisioners() + if err != nil { + WriteError(w, InternalServerError(err)) + return + } + for _, p := range ps { + ks, found := m[p.Issuer] + if found { + ks.Keys = append(ks.Keys, *p.Key) + } else { + ks = new(jose.JSONWebKeySet) + ks.Keys = []jose.JSONWebKey{*p.Key} + m[p.Issuer] = ks + } + } + JSON(w, &JWKSetByIssuerResponse{m}) +} diff --git a/api/api_test.go b/api/api_test.go index 1c02e6da..93fa8748 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -17,6 +17,7 @@ import ( "time" "github.com/go-chi/chi" + "github.com/smallstep/ca-component/provisioner" "github.com/smallstep/cli/crypto/tlsutil" "github.com/smallstep/cli/jose" ) @@ -397,7 +398,7 @@ type mockAuthority struct { root func(shasum string) (*x509.Certificate, error) sign func(cr *x509.CertificateRequest, opts SignOptions, claims ...Claim) (*x509.Certificate, *x509.Certificate, error) renew func(cert *x509.Certificate) (*x509.Certificate, *x509.Certificate, error) - getProvisioners func() (map[string]*jose.JSONWebKeySet, error) + getProvisioners func() ([]*provisioner.Provisioner, error) getEncryptedKey func(kid string) (string, error) } @@ -444,11 +445,11 @@ func (m *mockAuthority) Renew(cert *x509.Certificate) (*x509.Certificate, *x509. return m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate), m.err } -func (m *mockAuthority) GetProvisioners() (map[string]*jose.JSONWebKeySet, error) { +func (m *mockAuthority) GetProvisioners() ([]*provisioner.Provisioner, error) { if m.getProvisioners != nil { return m.getProvisioners() } - return m.ret1.(map[string]*jose.JSONWebKeySet), m.err + return m.ret1.([]*provisioner.Provisioner), m.err } func (m *mockAuthority) GetEncryptedKey(kid string) (string, error) { @@ -670,6 +671,82 @@ func Test_caHandler_Renew(t *testing.T) { } } +func Test_caHandler_JWKSetByIssuer(t *testing.T) { + type fields struct { + Authority Authority + } + type args struct { + w http.ResponseWriter + r *http.Request + } + + req, err := http.NewRequest("GET", "http://example.com/provisioners/jwk-set-by-issuer", nil) + if err != nil { + t.Fatal(err) + } + + var key jose.JSONWebKey + if err := json.Unmarshal([]byte(pubKey), &key); err != nil { + t.Fatal(err) + } + + p := []*provisioner.Provisioner{ + &provisioner.Provisioner{ + Issuer: "p1", + Key: &key, + }, + &provisioner.Provisioner{ + Issuer: "p2", + Key: &key, + }, + } + + tests := []struct { + name string + fields fields + args args + statusCode int + }{ + {"ok", fields{&mockAuthority{ret1: p}}, args{httptest.NewRecorder(), req}, 200}, + {"fail", fields{&mockAuthority{ret1: p, err: fmt.Errorf("the error")}}, args{httptest.NewRecorder(), req}, 500}, + } + + expectedKey, err := json.Marshal(key) + if err != nil { + t.Fatal(err) + } + expected := []byte(`{"map":{"p1":{"keys":[` + string(expectedKey) + `]},"p2":{"keys":[` + string(expectedKey) + `]}}}`) + expectedError := []byte(`{"status":500,"message":"Internal Server Error"}`) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + h := &caHandler{ + Authority: tt.fields.Authority, + } + h.JWKSetByIssuer(tt.args.w, tt.args.r) + + rec := tt.args.w.(*httptest.ResponseRecorder) + res := rec.Result() + if res.StatusCode != tt.statusCode { + t.Errorf("caHandler.JWKSetByIssuer StatusCode = %d, wants %d", res.StatusCode, tt.statusCode) + } + body, err := ioutil.ReadAll(res.Body) + res.Body.Close() + if err != nil { + t.Errorf("caHandler.JWKSetByIssuer unexpected error = %v", err) + } + if tt.statusCode < http.StatusBadRequest { + if !bytes.Equal(bytes.TrimSpace(body), expected) { + t.Errorf("caHandler.JWKSetByIssuer Body = %s, wants %s", body, expected) + } + } else { + if !bytes.Equal(bytes.TrimSpace(body), expectedError) { + t.Errorf("caHandler.JWKSetByIssuer Body = %s, wants %s", body, expectedError) + } + } + }) + } +} + func Test_caHandler_Provisioners(t *testing.T) { type fields struct { Authority Authority @@ -689,14 +766,21 @@ func Test_caHandler_Provisioners(t *testing.T) { t.Fatal(err) } - p := map[string]*jose.JSONWebKeySet{ - "p1": &jose.JSONWebKeySet{ - Keys: []jose.JSONWebKey{key}, + p := []*provisioner.Provisioner{ + &provisioner.Provisioner{ + Type: "JWK", + Issuer: "max", + EncryptedKey: "abc", + Key: &key, }, - "p2": &jose.JSONWebKeySet{ - Keys: []jose.JSONWebKey{key}, + &provisioner.Provisioner{ + Type: "JWK", + Issuer: "mariano", + EncryptedKey: "def", + Key: &key, }, } + pr := ProvisionersResponse{p} tests := []struct { name string @@ -708,11 +792,11 @@ func Test_caHandler_Provisioners(t *testing.T) { {"fail", fields{&mockAuthority{ret1: p, err: fmt.Errorf("the error")}}, args{httptest.NewRecorder(), req}, 500}, } - expectedKey, err := json.Marshal(key) + expected, err := json.Marshal(pr) if err != nil { t.Fatal(err) } - expected := []byte(`{"provisioners":{"p1":{"keys":[` + string(expectedKey) + `]},"p2":{"keys":[` + string(expectedKey) + `]}}}`) + expectedError := []byte(`{"status":500,"message":"Internal Server Error"}`) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/authority/authority_test.go b/authority/authority_test.go index ca26025a..b57108b6 100644 --- a/authority/authority_test.go +++ b/authority/authority_test.go @@ -7,6 +7,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/assert" + "github.com/smallstep/ca-component/provisioner" stepJOSE "github.com/smallstep/cli/jose" ) @@ -15,7 +16,7 @@ func testAuthority(t *testing.T) *Authority { assert.FatalError(t, err) clijwk, err := stepJOSE.ParseKey("testdata/secrets/step_cli_key_pub.jwk") assert.FatalError(t, err) - p := []*Provisioner{ + p := []*provisioner.Provisioner{ { Issuer: "Max", Type: "JWK", diff --git a/authority/authorize.go b/authority/authorize.go index cec5800f..ba98e2cb 100644 --- a/authority/authorize.go +++ b/authority/authorize.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/ca-component/api" + "github.com/smallstep/ca-component/provisioner" "gopkg.in/square/go-jose.v2/jwt" ) @@ -63,7 +64,7 @@ func (a *Authority) Authorize(ott string) ([]api.Claim, error) { return nil, &apiError{errors.Errorf("Provisioner with KeyID %s could not be found", kid), http.StatusUnauthorized, errContext} } - p, ok := val.(*Provisioner) + p, ok := val.(*provisioner.Provisioner) if !ok { return nil, &apiError{errors.Errorf("stored value is not a *Provisioner"), http.StatusInternalServerError, context{}} diff --git a/authority/config.go b/authority/config.go index 4abdd9a0..37e9f478 100644 --- a/authority/config.go +++ b/authority/config.go @@ -6,9 +6,9 @@ import ( "time" "github.com/pkg/errors" + "github.com/smallstep/ca-component/provisioner" "github.com/smallstep/cli/crypto/tlsutil" "github.com/smallstep/cli/crypto/x509util" - jose "gopkg.in/square/go-jose.v2" ) // DefaultTLSOptions represents the default TLS version as well as the cipher @@ -51,30 +51,6 @@ func (d *duration) UnmarshalJSON(data []byte) (err error) { return } -// Provisioner - authorized entity that can sign tokens necessary for signature requests. -type Provisioner struct { - Issuer string `json:"issuer,omitempty"` - Type string `json:"type,omitempty"` - Key *jose.JSONWebKey `json:"key,omitempty"` - EncryptedKey string `json:"encryptedKey,omitempty"` -} - -// Validate validates a provisioner. -func (p *Provisioner) Validate() error { - switch { - case p.Issuer == "": - return errors.New("provisioner issuer cannot be empty") - - case p.Type == "": - return errors.New("provisioner type cannot be empty") - - case p.Key == nil: - return errors.New("provisioner key cannot be empty") - } - - return nil -} - // Config represents the CA configuration and it's mapped to a JSON object. type Config struct { Root string `json:"root"` @@ -91,10 +67,10 @@ type Config struct { // AuthConfig represents the configuration options for the authority. type AuthConfig struct { - Provisioners []*Provisioner `json:"provisioners,omitempty"` - Template *x509util.ASN1DN `json:"template,omitempty"` - MinCertDuration *duration `json:"minCertDuration,omitempty"` - MaxCertDuration *duration `json:"maxCertDuration,omitempty"` + Provisioners []*provisioner.Provisioner `json:"provisioners,omitempty"` + Template *x509util.ASN1DN `json:"template,omitempty"` + MinCertDuration *duration `json:"minCertDuration,omitempty"` + MaxCertDuration *duration `json:"maxCertDuration,omitempty"` } // Validate validates the authority configuration. diff --git a/authority/config_test.go b/authority/config_test.go index 64315fac..05ee450f 100644 --- a/authority/config_test.go +++ b/authority/config_test.go @@ -5,65 +5,20 @@ import ( "github.com/pkg/errors" "github.com/smallstep/assert" + "github.com/smallstep/ca-component/provisioner" "github.com/smallstep/cli/crypto/tlsutil" "github.com/smallstep/cli/crypto/x509util" stepJOSE "github.com/smallstep/cli/jose" jose "gopkg.in/square/go-jose.v2" ) -func TestProvisionerValidate(t *testing.T) { - type ProvisionerValidateTest struct { - p *Provisioner - err error - } - tests := map[string]func(*testing.T) ProvisionerValidateTest{ - "fail-empty-issuer": func(t *testing.T) ProvisionerValidateTest { - return ProvisionerValidateTest{ - p: &Provisioner{}, - err: errors.New("provisioner issuer cannot be empty"), - } - }, - "fail-empty-type": func(t *testing.T) ProvisionerValidateTest { - return ProvisionerValidateTest{ - p: &Provisioner{Issuer: "foo"}, - err: errors.New("provisioner type cannot be empty"), - } - }, - "fail-empty-key": func(t *testing.T) ProvisionerValidateTest { - return ProvisionerValidateTest{ - p: &Provisioner{Issuer: "foo", Type: "bar"}, - err: errors.New("provisioner key cannot be empty"), - } - }, - "ok": func(t *testing.T) ProvisionerValidateTest { - return ProvisionerValidateTest{ - p: &Provisioner{Issuer: "foo", Type: "bar", Key: &jose.JSONWebKey{}}, - } - }, - } - - for name, get := range tests { - t.Run(name, func(t *testing.T) { - tc := get(t) - err := tc.p.Validate() - if err != nil { - if assert.NotNil(t, tc.err) { - assert.Equals(t, tc.err.Error(), err.Error()) - } - } else { - assert.Nil(t, tc.err) - } - }) - } -} - func TestConfigValidate(t *testing.T) { maxjwk, err := stepJOSE.ParseKey("testdata/secrets/max_pub.jwk") assert.FatalError(t, err) clijwk, err := stepJOSE.ParseKey("testdata/secrets/step_cli_key_pub.jwk") assert.FatalError(t, err) ac := &AuthConfig{ - Provisioners: []*Provisioner{ + Provisioners: []*provisioner.Provisioner{ { Issuer: "Max", Type: "JWK", @@ -261,7 +216,7 @@ func TestAuthConfigValidate(t *testing.T) { assert.FatalError(t, err) clijwk, err := stepJOSE.ParseKey("testdata/secrets/step_cli_key_pub.jwk") assert.FatalError(t, err) - p := []*Provisioner{ + p := []*provisioner.Provisioner{ { Issuer: "Max", Type: "JWK", @@ -295,9 +250,9 @@ func TestAuthConfigValidate(t *testing.T) { "fail-invalid-provisioners": func(t *testing.T) AuthConfigValidateTest { return AuthConfigValidateTest{ ac: &AuthConfig{ - Provisioners: []*Provisioner{ - &Provisioner{Issuer: "foo", Type: "bar", Key: &jose.JSONWebKey{}}, - &Provisioner{Issuer: "foo", Key: &jose.JSONWebKey{}}, + Provisioners: []*provisioner.Provisioner{ + {Issuer: "foo", Type: "bar", Key: &jose.JSONWebKey{}}, + {Issuer: "foo", Key: &jose.JSONWebKey{}}, }, }, err: errors.New("provisioner type cannot be empty"), diff --git a/authority/provisioners.go b/authority/provisioners.go index 3e609700..cfcdb738 100644 --- a/authority/provisioners.go +++ b/authority/provisioners.go @@ -1,11 +1,10 @@ package authority import ( - "log" "net/http" "github.com/pkg/errors" - "github.com/smallstep/cli/jose" + "github.com/smallstep/ca-component/provisioner" ) // GetEncryptedKey returns the JWE key corresponding to the given kid argument. @@ -26,24 +25,6 @@ func (a *Authority) GetEncryptedKey(kid string) (string, error) { // GetProvisioners returns a map listing each provisioner and the JWK Key Set // with their public keys. -func (a *Authority) GetProvisioners() (map[string]*jose.JSONWebKeySet, error) { - pks := map[string]*jose.JSONWebKeySet{} - a.provisionerIDIndex.Range(func(key, val interface{}) bool { - p, ok := val.(*Provisioner) - if !ok { - log.Printf("authority.GetProvisioners: expected type *Provisioner, but got %T\n", val) - return true - } - ks, found := pks[p.Issuer] - if found { - ks.Keys = append(ks.Keys, *p.Key) - } else { - ks = new(jose.JSONWebKeySet) - ks.Keys = []jose.JSONWebKey{*p.Key} - pks[p.Issuer] = ks - } - return true - }) - return pks, nil - +func (a *Authority) GetProvisioners() ([]*provisioner.Provisioner, error) { + return a.config.AuthorityConfig.Provisioners, nil } diff --git a/authority/provisioners_test.go b/authority/provisioners_test.go index 9f6343dc..40efa318 100644 --- a/authority/provisioners_test.go +++ b/authority/provisioners_test.go @@ -6,7 +6,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/assert" - "github.com/smallstep/cli/jose" + "github.com/smallstep/ca-component/provisioner" ) func TestGetEncryptedKey(t *testing.T) { @@ -73,7 +73,7 @@ func TestGetEncryptedKey(t *testing.T) { if assert.Nil(t, tc.err) { val, ok := tc.a.provisionerIDIndex.Load(tc.kid) assert.Fatal(t, ok) - p, ok := val.(*Provisioner) + p, ok := val.(*provisioner.Provisioner) assert.Fatal(t, ok) assert.Equals(t, p.EncryptedKey, ek) } @@ -115,19 +115,7 @@ func TestGetProvisioners(t *testing.T) { } } else { if assert.Nil(t, tc.err) { - var cps = tc.a.config.AuthorityConfig.Provisioners - - maxks, found := ps["max"] - assert.Fatal(t, found) - assert.Equals(t, maxks.Keys, []jose.JSONWebKey{*cps[0].Key, *cps[1].Key}) - - marianoks, found := ps["mariano"] - assert.Fatal(t, found) - assert.Equals(t, marianoks.Keys, []jose.JSONWebKey{*cps[3].Key, *cps[4].Key}) - - stepcliks, found := ps["step-cli"] - assert.Fatal(t, found) - assert.Equals(t, stepcliks.Keys, []jose.JSONWebKey{*cps[2].Key}) + assert.Equals(t, ps, tc.a.config.AuthorityConfig.Provisioners) } } }) diff --git a/authority/tls.go b/authority/tls.go index fa9210c5..c2eda620 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -143,15 +143,15 @@ func (a *Authority) Renew(ocx *x509.Certificate) (*x509.Certificate, *x509.Certi ExtKeyUsage: oldCert.ExtKeyUsage, UnknownExtKeyUsage: oldCert.UnknownExtKeyUsage, BasicConstraintsValid: oldCert.BasicConstraintsValid, - IsCA: oldCert.IsCA, - MaxPathLen: oldCert.MaxPathLen, - MaxPathLenZero: oldCert.MaxPathLenZero, - OCSPServer: oldCert.OCSPServer, - IssuingCertificateURL: oldCert.IssuingCertificateURL, - DNSNames: oldCert.DNSNames, - EmailAddresses: oldCert.EmailAddresses, - IPAddresses: oldCert.IPAddresses, - URIs: oldCert.URIs, + IsCA: oldCert.IsCA, + MaxPathLen: oldCert.MaxPathLen, + MaxPathLenZero: oldCert.MaxPathLenZero, + OCSPServer: oldCert.OCSPServer, + IssuingCertificateURL: oldCert.IssuingCertificateURL, + DNSNames: oldCert.DNSNames, + EmailAddresses: oldCert.EmailAddresses, + IPAddresses: oldCert.IPAddresses, + URIs: oldCert.URIs, PermittedDNSDomainsCritical: oldCert.PermittedDNSDomainsCritical, PermittedDNSDomains: oldCert.PermittedDNSDomains, ExcludedDNSDomains: oldCert.ExcludedDNSDomains, diff --git a/ca/ca_test.go b/ca/ca_test.go index 48463f5f..0c7e17e8 100644 --- a/ca/ca_test.go +++ b/ca/ca_test.go @@ -298,16 +298,65 @@ func TestCAProvisioners(t *testing.T) { tc.ca.srv.Handler.ServeHTTP(rr, rq) + if assert.Equals(t, rr.Code, tc.status) { + body := &ClosingBuffer{rr.Body} + if rr.Code < http.StatusBadRequest { + var resp api.ProvisionersResponse + + assert.FatalError(t, readJSON(body, &resp)) + assert.Equals(t, config.AuthorityConfig.Provisioners, resp.Provisioners) + } else { + err := readError(body) + if len(tc.errMsg) == 0 { + assert.FatalError(t, errors.New("must validate response error")) + } + assert.HasPrefix(t, err.Error(), tc.errMsg) + } + } + }) + } +} + +func TestCAJWKSetByIssuer(t *testing.T) { + config, err := authority.LoadConfiguration("testdata/ca.json") + assert.FatalError(t, err) + ca, err := New(config) + assert.FatalError(t, err) + + type ekt struct { + ca *CA + status int + errMsg string + } + tests := map[string]func(t *testing.T) *ekt{ + "ok": func(t *testing.T) *ekt { + return &ekt{ + ca: ca, + status: http.StatusOK, + } + }, + } + + for name, genTestCase := range tests { + t.Run(name, func(t *testing.T) { + tc := genTestCase(t) + + rq, err := http.NewRequest("GET", fmt.Sprintf("/provisioners/jwk-set-by-issuer"), strings.NewReader("")) + assert.FatalError(t, err) + rr := httptest.NewRecorder() + + tc.ca.srv.Handler.ServeHTTP(rr, rq) + if assert.Equals(t, rr.Code, tc.status) { body := &ClosingBuffer{rr.Body} if rr.Code < http.StatusBadRequest { var ( - resp api.ProvisionersResponse + resp api.JWKSetByIssuerResponse psList = config.AuthorityConfig.Provisioners ) assert.FatalError(t, readJSON(body, &resp)) - psMap := resp.Provisioners + psMap := resp.Map maxks, found := psMap["max"] assert.Fatal(t, found) diff --git a/ca/client_test.go b/ca/client_test.go index 8540356c..0d6f555d 100644 --- a/ca/client_test.go +++ b/ca/client_test.go @@ -13,7 +13,7 @@ import ( "time" "github.com/smallstep/ca-component/api" - "github.com/smallstep/cli/jose" + "github.com/smallstep/ca-component/provisioner" ) const ( @@ -388,6 +388,66 @@ func TestClient_Renew(t *testing.T) { } } +func TestClient_Provisioners(t *testing.T) { + ok := &api.ProvisionersResponse{ + Provisioners: []*provisioner.Provisioner{}, + } + internalServerError := api.InternalServerError(fmt.Errorf("Internal Server Error")) + + tests := []struct { + name string + response interface{} + responseCode int + wantErr bool + }{ + {"ok", ok, 200, false}, + {"fail", internalServerError, 500, true}, + } + + srv := httptest.NewServer(nil) + defer srv.Close() + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) + if err != nil { + t.Errorf("NewClient() error = %v", err) + return + } + + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + expected := "/provisioners" + if req.RequestURI != expected { + t.Errorf("RequestURI = %s, want %s", req.RequestURI, expected) + } + w.WriteHeader(tt.responseCode) + api.JSON(w, tt.response) + }) + + got, err := c.Provisioners() + if (err != nil) != tt.wantErr { + t.Errorf("Client.Provisioners() error = %v, wantErr %v", err, tt.wantErr) + return + } + + switch { + case err != nil: + if got != nil { + t.Errorf("Client.Provisioners() = %v, want nil", got) + } + if !reflect.DeepEqual(err, tt.response) { + t.Errorf("Client.Provisioners() error = %v, want %v", err, tt.response) + } + default: + if !reflect.DeepEqual(got, tt.response) { + t.Errorf("Client.Provisioners() = %v, want %v", got, tt.response) + } + } + }) + } +} + +/* func TestClient_Provisioners(t *testing.T) { ok := &api.ProvisionersResponse{ Provisioners: map[string]*jose.JSONWebKeySet{}, @@ -446,6 +506,7 @@ func TestClient_Provisioners(t *testing.T) { }) } } +*/ func TestClient_ProvisionerKey(t *testing.T) { ok := &api.ProvisionerKeyResponse{ diff --git a/provisioner/provisioner.go b/provisioner/provisioner.go new file mode 100644 index 00000000..594da019 --- /dev/null +++ b/provisioner/provisioner.go @@ -0,0 +1,31 @@ +package provisioner + +import ( + "errors" + + jose "gopkg.in/square/go-jose.v2" +) + +// Provisioner - authorized entity that can sign tokens necessary for signature requests. +type Provisioner struct { + Issuer string `json:"issuer,omitempty"` + Type string `json:"type,omitempty"` + Key *jose.JSONWebKey `json:"key,omitempty"` + EncryptedKey string `json:"encryptedKey,omitempty"` +} + +// Validate validates a provisioner. +func (p *Provisioner) Validate() error { + switch { + case p.Issuer == "": + return errors.New("provisioner issuer cannot be empty") + + case p.Type == "": + return errors.New("provisioner type cannot be empty") + + case p.Key == nil: + return errors.New("provisioner key cannot be empty") + } + + return nil +} diff --git a/provisioner/provisioner_test.go b/provisioner/provisioner_test.go new file mode 100644 index 00000000..8f1e6f18 --- /dev/null +++ b/provisioner/provisioner_test.go @@ -0,0 +1,55 @@ +package provisioner + +import ( + "errors" + "testing" + + "github.com/smallstep/assert" + jose "gopkg.in/square/go-jose.v2" +) + +func TestProvisionerValidate(t *testing.T) { + type ProvisionerValidateTest struct { + p *Provisioner + err error + } + tests := map[string]func(*testing.T) ProvisionerValidateTest{ + "fail-empty-issuer": func(t *testing.T) ProvisionerValidateTest { + return ProvisionerValidateTest{ + p: &Provisioner{}, + err: errors.New("provisioner issuer cannot be empty"), + } + }, + "fail-empty-type": func(t *testing.T) ProvisionerValidateTest { + return ProvisionerValidateTest{ + p: &Provisioner{Issuer: "foo"}, + err: errors.New("provisioner type cannot be empty"), + } + }, + "fail-empty-key": func(t *testing.T) ProvisionerValidateTest { + return ProvisionerValidateTest{ + p: &Provisioner{Issuer: "foo", Type: "bar"}, + err: errors.New("provisioner key cannot be empty"), + } + }, + "ok": func(t *testing.T) ProvisionerValidateTest { + return ProvisionerValidateTest{ + p: &Provisioner{Issuer: "foo", Type: "bar", Key: &jose.JSONWebKey{}}, + } + }, + } + + for name, get := range tests { + t.Run(name, func(t *testing.T) { + tc := get(t) + err := tc.p.Validate() + if err != nil { + if assert.NotNil(t, tc.err) { + assert.Equals(t, tc.err.Error(), err.Error()) + } + } else { + assert.Nil(t, tc.err) + } + }) + } +}