wip admin CRUD

This commit is contained in:
max furman 2021-05-12 00:03:40 -07:00
parent 98a6e54530
commit 4d48072746
9 changed files with 293 additions and 154 deletions

View file

@ -162,6 +162,7 @@ func (a *Authority) init() error {
return mgmt.WrapErrorISE(err, "error getting authConfig from db") return mgmt.WrapErrorISE(err, "error getting authConfig from db")
} }
} }
a.config.AuthorityConfig, err = mgmtAuthConfig.ToCertificates() a.config.AuthorityConfig, err = mgmtAuthConfig.ToCertificates()
if err != nil { if err != nil {
return err return err

View file

@ -5,12 +5,13 @@ import (
"github.com/go-chi/chi" "github.com/go-chi/chi"
"github.com/smallstep/certificates/api" "github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/authority/mgmt"
) )
// CreateAdminRequest represents the body for a CreateAdmin request. // CreateAdminRequest represents the body for a CreateAdmin request.
type CreateAdminRequest struct { type CreateAdminRequest struct {
Name string `json:"name"` Name string `json:"name"`
Provisioner string `json:"provisioner"` ProvisionerID string `json:"provisionerID"`
IsSuperAdmin bool `json:"isSuperAdmin"` IsSuperAdmin bool `json:"isSuperAdmin"`
} }
@ -22,8 +23,9 @@ func (car *CreateAdminRequest) Validate() error {
// UpdateAdminRequest represents the body for a UpdateAdmin request. // UpdateAdminRequest represents the body for a UpdateAdmin request.
type UpdateAdminRequest struct { type UpdateAdminRequest struct {
Name string `json:"name"` Name string `json:"name"`
Provisioner string `json:"provisioner"` ProvisionerID string `json:"provisionerID"`
IsSuperAdmin bool `json:"isSuperAdmin"` IsSuperAdmin string `json:"isSuperAdmin"`
Status string `json:"status"`
} }
// Validate validates a new-admin request body. // Validate validates a new-admin request body.
@ -31,6 +33,11 @@ func (uar *UpdateAdminRequest) Validate() error {
return nil return nil
} }
// DeleteResponse is the resource for successful DELETE responses.
type DeleteResponse struct {
Status string `json:"status"`
}
// GetAdmin returns the requested admin, or an error. // GetAdmin returns the requested admin, or an error.
func (h *Handler) GetAdmin(w http.ResponseWriter, r *http.Request) { func (h *Handler) GetAdmin(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
@ -58,59 +65,84 @@ func (h *Handler) GetAdmins(w http.ResponseWriter, r *http.Request) {
// CreateAdmin creates a new admin. // CreateAdmin creates a new admin.
func (h *Handler) CreateAdmin(w http.ResponseWriter, r *http.Request) { func (h *Handler) CreateAdmin(w http.ResponseWriter, r *http.Request) {
/*
ctx := r.Context() ctx := r.Context()
var body CreateAdminRequest var body CreateAdminRequest
if err := ReadJSON(r.Body, &body); err != nil { if err := api.ReadJSON(r.Body, &body); err != nil {
api.WriteError(w, err) api.WriteError(w, mgmt.WrapError(mgmt.ErrorBadRequestType, err, "error reading request body"))
return return
} }
if err := body.Validate(); err != nil {
api.WriteError(w, err)
}
adm := &config.Admin{ // TODO validate
adm := &mgmt.Admin{
ProvisionerID: body.ProvisionerID,
Name: body.Name, Name: body.Name,
Provisioner: body.Provisioner,
IsSuperAdmin: body.IsSuperAdmin, IsSuperAdmin: body.IsSuperAdmin,
Status: mgmt.StatusActive,
} }
if err := h.db.CreateAdmin(ctx, adm); err != nil { if err := h.db.CreateAdmin(ctx, adm); err != nil {
api.WriteError(w, err) api.WriteError(w, mgmt.WrapErrorISE(err, "error creating admin"))
return return
} }
api.JSONStatus(w, adm, http.StatusCreated) api.JSON(w, adm)
*/ }
// DeleteAdmin deletes admin.
func (h *Handler) DeleteAdmin(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id := chi.URLParam(r, "id")
adm, err := h.db.GetAdmin(ctx, id)
if err != nil {
api.WriteError(w, mgmt.WrapErrorISE(err, "error retrieiving admin %s", id))
return
}
adm.Status = mgmt.StatusDeleted
if err := h.db.UpdateAdmin(ctx, adm); err != nil {
api.WriteError(w, mgmt.WrapErrorISE(err, "error updating admin %s", id))
return
}
api.JSON(w, &DeleteResponse{Status: "ok"})
} }
// UpdateAdmin updates an existing admin. // UpdateAdmin updates an existing admin.
func (h *Handler) UpdateAdmin(w http.ResponseWriter, r *http.Request) { func (h *Handler) UpdateAdmin(w http.ResponseWriter, r *http.Request) {
/*
ctx := r.Context() ctx := r.Context()
id := chi.URLParam(r, "id")
var body UpdateAdminRequest var body UpdateAdminRequest
if err := ReadJSON(r.Body, &body); err != nil { if err := api.ReadJSON(r.Body, &body); err != nil {
api.WriteError(w, err) api.WriteError(w, mgmt.WrapError(mgmt.ErrorBadRequestType, err, "error reading request body"))
return
}
if err := body.Validate(); err != nil {
api.WriteError(w, err)
return
}
if adm, err := h.db.GetAdmin(ctx, id); err != nil {
api.WriteError(w, err)
return return
} }
id := chi.URLParam(r, "id")
adm, err := h.db.GetAdmin(ctx, id)
if err != nil {
api.WriteError(w, mgmt.WrapErrorISE(err, "error retrieiving admin %s", id))
return
}
// TODO validate
if len(body.Name) > 0 {
adm.Name = body.Name adm.Name = body.Name
adm.Provisioner = body.Provisioner }
adm.IsSuperAdmin = body.IsSuperAdmin if len(body.Status) > 0 {
adm.Status = mgmt.StatusActive // FIXME
}
// Set IsSuperAdmin iff the string was set in the update request.
if len(body.IsSuperAdmin) > 0 {
adm.IsSuperAdmin = (body.IsSuperAdmin == "true")
}
if len(body.ProvisionerID) > 0 {
adm.ProvisionerID = body.ProvisionerID
}
if err := h.db.UpdateAdmin(ctx, adm); err != nil { if err := h.db.UpdateAdmin(ctx, adm); err != nil {
api.WriteError(w, err) api.WriteError(w, mgmt.WrapErrorISE(err, "error updating admin %s", id))
return return
} }
api.JSON(w, adm) api.JSON(w, adm)
*/
} }

View file

@ -33,13 +33,15 @@ func (h *Handler) Route(r api.Router) {
r.MethodFunc("GET", "/provisioner/{id}", h.GetProvisioner) r.MethodFunc("GET", "/provisioner/{id}", h.GetProvisioner)
r.MethodFunc("GET", "/provisioners", h.GetProvisioners) r.MethodFunc("GET", "/provisioners", h.GetProvisioners)
r.MethodFunc("POST", "/provisioner", h.CreateProvisioner) r.MethodFunc("POST", "/provisioner", h.CreateProvisioner)
r.MethodFunc("PUT", "/provsiioner/{id}", h.UpdateProvisioner) r.MethodFunc("PUT", "/provisioner/{id}", h.UpdateProvisioner)
//r.MethodFunc("DELETE", "/provisioner/{id}", h.UpdateAdmin)
// Admins // Admins
r.MethodFunc("GET", "/admin/{id}", h.GetAdmin) r.MethodFunc("GET", "/admin/{id}", h.GetAdmin)
r.MethodFunc("GET", "/admins", h.GetAdmins) r.MethodFunc("GET", "/admins", h.GetAdmins)
r.MethodFunc("POST", "/admin", h.CreateAdmin) r.MethodFunc("POST", "/admin", h.CreateAdmin)
r.MethodFunc("PUT", "/admin/{id}", h.UpdateAdmin) r.MethodFunc("PUT", "/admin/{id}", h.UpdateAdmin)
r.MethodFunc("DELETE", "/admin/{id}", h.DeleteAdmin)
// AuthConfig // AuthConfig
r.MethodFunc("GET", "/authconfig/{id}", h.GetAuthConfig) r.MethodFunc("GET", "/authconfig/{id}", h.GetAuthConfig)

View file

@ -131,6 +131,7 @@ func (db *DB) CreateAdmin(ctx context.Context, adm *mgmt.Admin) error {
if err != nil { if err != nil {
return errors.Wrap(err, "error generating random id for admin") return errors.Wrap(err, "error generating random id for admin")
} }
adm.AuthorityID = db.authorityID
dba := &dbAdmin{ dba := &dbAdmin{
ID: adm.ID, ID: adm.ID,

View file

@ -126,7 +126,6 @@ func unmarshalProvisioner(data []byte, id string) (*mgmt.Provisioner, error) {
// GetProvisioners retrieves and unmarshals all active (not deleted) provisioners // GetProvisioners retrieves and unmarshals all active (not deleted) provisioners
// from the database. // from the database.
// TODO should we be paginating?
func (db *DB) GetProvisioners(ctx context.Context) ([]*mgmt.Provisioner, error) { func (db *DB) GetProvisioners(ctx context.Context) ([]*mgmt.Provisioner, error) {
dbEntries, err := db.db.List(authorityProvisionersTable) dbEntries, err := db.db.List(authorityProvisionersTable)
if err != nil { if err != nil {
@ -157,13 +156,18 @@ func (db *DB) CreateProvisioner(ctx context.Context, prov *mgmt.Provisioner) err
return errors.Wrap(err, "error generating random id for provisioner") return errors.Wrap(err, "error generating random id for provisioner")
} }
details, err := json.Marshal(prov.Details)
if err != nil {
return mgmt.WrapErrorISE(err, "error marshaling details when creating provisioner")
}
dbp := &dbProvisioner{ dbp := &dbProvisioner{
ID: prov.ID, ID: prov.ID,
AuthorityID: db.authorityID, AuthorityID: db.authorityID,
Type: prov.Type, Type: prov.Type,
Name: prov.Name, Name: prov.Name,
Claims: prov.Claims, Claims: prov.Claims,
Details: prov.Details, Details: details,
X509Template: prov.X509Template, X509Template: prov.X509Template,
SSHTemplate: prov.SSHTemplate, SSHTemplate: prov.SSHTemplate,
CreatedAt: clock.Now(), CreatedAt: clock.Now(),
@ -186,72 +190,44 @@ func (db *DB) UpdateProvisioner(ctx context.Context, prov *mgmt.Provisioner) err
nu.DeletedAt = clock.Now() nu.DeletedAt = clock.Now()
} }
nu.Claims = prov.Claims nu.Claims = prov.Claims
nu.Details = prov.Details
nu.X509Template = prov.X509Template nu.X509Template = prov.X509Template
nu.SSHTemplate = prov.SSHTemplate nu.SSHTemplate = prov.SSHTemplate
nu.Details, err = json.Marshal(prov.Details)
if err != nil {
return mgmt.WrapErrorISE(err, "error marshaling details when creating provisioner")
}
return db.save(ctx, old.ID, nu, old, "provisioner", authorityProvisionersTable) return db.save(ctx, old.ID, nu, old, "provisioner", authorityProvisionersTable)
} }
func unmarshalDetails(typ ProvisionerType, details []byte) (interface{}, error) { func unmarshalDetails(typ mgmt.ProvisionerType, data []byte) (mgmt.ProvisionerDetails, error) {
if !s.Valid { var v mgmt.ProvisionerDetails
return nil, nil
}
var v isProvisionerDetails_Data
switch typ { switch typ {
case ProvisionerTypeJWK: case mgmt.ProvisionerTypeJWK:
p := new(ProvisionerDetailsJWK) v = new(mgmt.ProvisionerDetailsJWK)
if err := json.Unmarshal([]byte(s.String), p); err != nil { case mgmt.ProvisionerTypeOIDC:
return nil, err v = new(mgmt.ProvisionerDetailsOIDC)
} case mgmt.ProvisionerTypeGCP:
if p.JWK.Key.Key == nil { v = new(mgmt.ProvisionerDetailsGCP)
key, err := LoadKey(ctx, db, p.JWK.Key.Id.Id) case mgmt.ProvisionerTypeAWS:
if err != nil { v = new(mgmt.ProvisionerDetailsAWS)
return nil, err case mgmt.ProvisionerTypeAZURE:
} v = new(mgmt.ProvisionerDetailsAzure)
p.JWK.Key = key case mgmt.ProvisionerTypeACME:
} v = new(mgmt.ProvisionerDetailsACME)
return &ProvisionerDetails{Data: p}, nil case mgmt.ProvisionerTypeX5C:
case ProvisionerType_OIDC: v = new(mgmt.ProvisionerDetailsX5C)
v = new(ProvisionerDetails_OIDC) case mgmt.ProvisionerTypeK8SSA:
case ProvisionerType_GCP: v = new(mgmt.ProvisionerDetailsK8SSA)
v = new(ProvisionerDetails_GCP) case mgmt.ProvisionerTypeSSHPOP:
case ProvisionerType_AWS: v = new(mgmt.ProvisionerDetailsSSHPOP)
v = new(ProvisionerDetails_AWS)
case ProvisionerType_AZURE:
v = new(ProvisionerDetails_Azure)
case ProvisionerType_ACME:
v = new(ProvisionerDetails_ACME)
case ProvisionerType_X5C:
p := new(ProvisionerDetails_X5C)
if err := json.Unmarshal([]byte(s.String), p); err != nil {
return nil, err
}
for _, k := range p.X5C.GetRoots() {
if err := k.Select(ctx, db, k.Id.Id); err != nil {
return nil, err
}
}
return &ProvisionerDetails{Data: p}, nil
case ProvisionerType_K8SSA:
p := new(ProvisionerDetails_K8SSA)
if err := json.Unmarshal([]byte(s.String), p); err != nil {
return nil, err
}
for _, k := range p.K8SSA.GetPublicKeys() {
if err := k.Select(ctx, db, k.Id.Id); err != nil {
return nil, err
}
}
return &ProvisionerDetails{Data: p}, nil
case ProvisionerType_SSHPOP:
v = new(ProvisionerDetails_SSHPOP)
default: default:
return nil, fmt.Errorf("unsupported provisioner type %s", typ) return nil, fmt.Errorf("unsupported provisioner type %s", typ)
} }
if err := json.Unmarshal([]byte(s.String), v); err != nil { if err := json.Unmarshal(data, v); err != nil {
return nil, err return nil, err
} }
return &ProvisionerDetails{Data: v}, nil return v, nil
} }

View file

@ -23,6 +23,8 @@ const (
ErrorAuthorityMismatchType ErrorAuthorityMismatchType
// ErrorDeletedType resource has been deleted. // ErrorDeletedType resource has been deleted.
ErrorDeletedType ErrorDeletedType
// ErrorBadRequestType bad request.
ErrorBadRequestType
// ErrorServerInternalType internal server error. // ErrorServerInternalType internal server error.
ErrorServerInternalType ErrorServerInternalType
) )
@ -37,6 +39,8 @@ func (ap ProblemType) String() string {
return "authorityMismatch" return "authorityMismatch"
case ErrorDeletedType: case ErrorDeletedType:
return "deleted" return "deleted"
case ErrorBadRequestType:
return "badRequest"
case ErrorServerInternalType: case ErrorServerInternalType:
return "internalServerError" return "internalServerError"
default: default:
@ -69,10 +73,15 @@ var (
status: 401, status: 401,
}, },
ErrorDeletedType: { ErrorDeletedType: {
typ: ErrorNotFoundType.String(), typ: ErrorDeletedType.String(),
details: "resource is deleted", details: "resource is deleted",
status: 403, status: 403,
}, },
ErrorBadRequestType: {
typ: ErrorBadRequestType.String(),
details: "bad request",
status: 400,
},
ErrorServerInternalType: errorServerInternalMetadata, ErrorServerInternalType: errorServerInternalMetadata,
} }
) )

View file

@ -23,12 +23,15 @@ type ProvisionerCtx struct {
type ProvisionerType string type ProvisionerType string
var ( var (
ProvisionerTypeJWK = ProvisionerType("JWK")
ProvisionerTypeOIDC = ProvisionerType("OIDC")
ProvisionerTypeACME = ProvisionerType("ACME") ProvisionerTypeACME = ProvisionerType("ACME")
ProvisionerTypeX5C = ProvisionerType("X5C") ProvisionerTypeAWS = ProvisionerType("AWS")
ProvisionerTypeK8S = ProvisionerType("K8S") ProvisionerTypeAZURE = ProvisionerType("AZURE")
ProvisionerTypeGCP = ProvisionerType("GCP")
ProvisionerTypeJWK = ProvisionerType("JWK")
ProvisionerTypeK8SSA = ProvisionerType("K8SSA")
ProvisionerTypeOIDC = ProvisionerType("OIDC")
ProvisionerTypeSSHPOP = ProvisionerType("SSHPOP") ProvisionerTypeSSHPOP = ProvisionerType("SSHPOP")
ProvisionerTypeX5C = ProvisionerType("X5C")
) )
func NewProvisionerCtx(opts ...ProvisionerOption) *ProvisionerCtx { func NewProvisionerCtx(opts ...ProvisionerOption) *ProvisionerCtx {
@ -56,8 +59,8 @@ func WithPassword(pass string) func(*ProvisionerCtx) {
// Provisioner type. // Provisioner type.
type Provisioner struct { type Provisioner struct {
ID string `json:"-"` ID string `json:"id"`
AuthorityID string `json:"-"` AuthorityID string `json:"authorityID"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
Claims *Claims `json:"claims"` Claims *Claims `json:"claims"`
@ -108,6 +111,10 @@ func CreateProvisioner(ctx context.Context, db DB, typ, name string, opts ...Pro
return p, nil return p, nil
} }
type ProvisionerDetails interface {
isProvisionerDetails()
}
// ProvisionerDetailsJWK represents the values required by a JWK provisioner. // ProvisionerDetailsJWK represents the values required by a JWK provisioner.
type ProvisionerDetailsJWK struct { type ProvisionerDetailsJWK struct {
Type ProvisionerType `json:"type"` Type ProvisionerType `json:"type"`
@ -115,6 +122,64 @@ type ProvisionerDetailsJWK struct {
EncPrivKey string `json:"privKey"` EncPrivKey string `json:"privKey"`
} }
// ProvisionerDetailsOIDC represents the values required by a OIDC provisioner.
type ProvisionerDetailsOIDC struct {
Type ProvisionerType `json:"type"`
}
// ProvisionerDetailsGCP represents the values required by a GCP provisioner.
type ProvisionerDetailsGCP struct {
Type ProvisionerType `json:"type"`
}
// ProvisionerDetailsAWS represents the values required by a AWS provisioner.
type ProvisionerDetailsAWS struct {
Type ProvisionerType `json:"type"`
}
// ProvisionerDetailsAzure represents the values required by a Azure provisioner.
type ProvisionerDetailsAzure struct {
Type ProvisionerType `json:"type"`
}
// ProvisionerDetailsACME represents the values required by a ACME provisioner.
type ProvisionerDetailsACME struct {
Type ProvisionerType `json:"type"`
}
// ProvisionerDetailsX5C represents the values required by a X5C provisioner.
type ProvisionerDetailsX5C struct {
Type ProvisionerType `json:"type"`
}
// ProvisionerDetailsK8SSA represents the values required by a K8SSA provisioner.
type ProvisionerDetailsK8SSA struct {
Type ProvisionerType `json:"type"`
}
// ProvisionerDetailsSSHPOP represents the values required by a SSHPOP provisioner.
type ProvisionerDetailsSSHPOP struct {
Type ProvisionerType `json:"type"`
}
func (*ProvisionerDetailsJWK) isProvisionerDetails() {}
func (*ProvisionerDetailsOIDC) isProvisionerDetails() {}
func (*ProvisionerDetailsGCP) isProvisionerDetails() {}
func (*ProvisionerDetailsAWS) isProvisionerDetails() {}
func (*ProvisionerDetailsAzure) isProvisionerDetails() {}
func (*ProvisionerDetailsACME) isProvisionerDetails() {}
func (*ProvisionerDetailsX5C) isProvisionerDetails() {}
func (*ProvisionerDetailsK8SSA) isProvisionerDetails() {}
func (*ProvisionerDetailsSSHPOP) isProvisionerDetails() {}
func createJWKDetails(pc *ProvisionerCtx) (*ProvisionerDetailsJWK, error) { func createJWKDetails(pc *ProvisionerCtx) (*ProvisionerDetailsJWK, error) {
var err error var err error
@ -159,10 +224,6 @@ func (p *Provisioner) ToCertificates() (provisioner.Interface, error) {
return nil, err return nil, err
} }
if err != nil {
return nil, err
}
switch details := p.Details.(type) { switch details := p.Details.(type) {
case *ProvisionerDetailsJWK: case *ProvisionerDetailsJWK:
jwk := new(jose.JSONWebKey) jwk := new(jose.JSONWebKey)
@ -325,36 +386,3 @@ func (c *Claims) ToCertificates() (*provisioner.Claims, error) {
EnableSSHCA: &c.SSH.Enabled, EnableSSHCA: &c.SSH.Enabled,
}, nil }, nil
} }
/*
func marshalDetails(d *ProvisionerDetails) (sql.NullString, error) {
b, err := json.Marshal(d.GetData())
if err != nil {
return sql.NullString{}, nil
}
return sql.NullString{
String: string(b),
Valid: len(b) > 0,
}, nil
}
func marshalClaims(c *Claims) (sql.NullString, error) {
b, err := json.Marshal(c)
if err != nil {
return sql.NullString{}, nil
}
return sql.NullString{
String: string(b),
Valid: len(b) > 0,
}, nil
}
func unmarshalClaims(s sql.NullString) (*Claims, error) {
if !s.Valid {
return nil, nil
}
v := new(Claims)
return v, json.Unmarshal([]byte(s.String), v)
}
*/

View file

@ -88,6 +88,11 @@ func (c *uaClient) Post(url, contentType string, body io.Reader) (*http.Response
return c.Client.Do(req) return c.Client.Do(req)
} }
func (c *uaClient) Do(req *http.Request) (*http.Response, error) {
req.Header.Set("User-Agent", UserAgent)
return c.Client.Do(req)
}
// RetryFunc defines the method used to retry a request. If it returns true, the // RetryFunc defines the method used to retry a request. If it returns true, the
// request will be retried once. // request will be retried once.
type RetryFunc func(code int) bool type RetryFunc func(code int) bool

View file

@ -1,12 +1,16 @@
package ca package ca
import ( import (
"bytes"
"encoding/json"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority/mgmt" "github.com/smallstep/certificates/authority/mgmt"
mgmtAPI "github.com/smallstep/certificates/authority/mgmt/api"
"github.com/smallstep/certificates/errs"
) )
// MgmtClient implements an HTTP client for the CA server. // MgmtClient implements an HTTP client for the CA server.
@ -83,6 +87,87 @@ retry:
return adm, nil return adm, nil
} }
// CreateAdmin performs the POST /mgmt/admin request to the CA.
func (c *MgmtClient) CreateAdmin(req *mgmtAPI.CreateAdminRequest) (*mgmt.Admin, error) {
var retried bool
body, err := json.Marshal(req)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
}
u := c.endpoint.ResolveReference(&url.URL{Path: "/mgmt/admin"})
retry:
resp, err := c.client.Post(u.String(), "application/json", bytes.NewReader(body))
if err != nil {
return nil, errors.Wrapf(err, "client POST %s failed", u)
}
if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) {
retried = true
goto retry
}
return nil, readError(resp.Body)
}
var adm = new(mgmt.Admin)
if err := readJSON(resp.Body, adm); err != nil {
return nil, errors.Wrapf(err, "error reading %s", u)
}
return adm, nil
}
// RemoveAdmin performs the DELETE /mgmt/admin/{id} request to the CA.
func (c *MgmtClient) RemoveAdmin(id string) error {
var retried bool
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/mgmt/admin", id)})
req, err := http.NewRequest("DELETE", u.String(), nil)
if err != nil {
return errors.Wrapf(err, "create DELETE %s request failed", u)
}
retry:
resp, err := c.client.Do(req)
if err != nil {
return errors.Wrapf(err, "client DELETE %s failed", u)
}
if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) {
retried = true
goto retry
}
return readError(resp.Body)
}
return nil
}
// UpdateAdmin performs the PUT /mgmt/admin/{id} request to the CA.
func (c *MgmtClient) UpdateAdmin(id string, uar *mgmtAPI.UpdateAdminRequest) (*mgmt.Admin, error) {
var retried bool
body, err := json.Marshal(uar)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
}
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/mgmt/admin", id)})
req, err := http.NewRequest("PUT", u.String(), bytes.NewReader(body))
if err != nil {
return nil, errors.Wrapf(err, "create PUT %s request failed", u)
}
retry:
resp, err := c.client.Do(req)
if err != nil {
return nil, errors.Wrapf(err, "client PUT %s failed", u)
}
if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) {
retried = true
goto retry
}
return nil, readError(resp.Body)
}
var adm = new(mgmt.Admin)
if err := readJSON(resp.Body, adm); err != nil {
return nil, errors.Wrapf(err, "error reading %s", u)
}
return adm, nil
}
// GetAdmins performs the GET /mgmt/admins request to the CA. // GetAdmins performs the GET /mgmt/admins request to the CA.
func (c *MgmtClient) GetAdmins() ([]*mgmt.Admin, error) { func (c *MgmtClient) GetAdmins() ([]*mgmt.Admin, error) {
var retried bool var retried bool