1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
|
package api
import (
"context"
"net/http"
"github.com/go-chi/chi"
"go.step.sm/linkedca"
"github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/api/read"
"github.com/smallstep/certificates/api/render"
"github.com/smallstep/certificates/authority/admin"
"github.com/smallstep/certificates/authority/provisioner"
)
type adminAuthority interface {
LoadProvisionerByName(string) (provisioner.Interface, error)
GetProvisioners(cursor string, limit int) (provisioner.List, string, error)
IsAdminAPIEnabled() bool
LoadAdminByID(id string) (*linkedca.Admin, bool)
GetAdmins(cursor string, limit int) ([]*linkedca.Admin, string, error)
StoreAdmin(ctx context.Context, adm *linkedca.Admin, prov provisioner.Interface) error
UpdateAdmin(ctx context.Context, id string, nu *linkedca.Admin) (*linkedca.Admin, error)
RemoveAdmin(ctx context.Context, id string) error
AuthorizeAdminToken(r *http.Request, token string) (*linkedca.Admin, error)
StoreProvisioner(ctx context.Context, prov *linkedca.Provisioner) error
LoadProvisionerByID(id string) (provisioner.Interface, error)
UpdateProvisioner(ctx context.Context, nu *linkedca.Provisioner) error
RemoveProvisioner(ctx context.Context, id string) error
GetAuthorityPolicy(ctx context.Context) (*linkedca.Policy, error)
CreateAuthorityPolicy(ctx context.Context, admin *linkedca.Admin, policy *linkedca.Policy) (*linkedca.Policy, error)
UpdateAuthorityPolicy(ctx context.Context, admin *linkedca.Admin, policy *linkedca.Policy) (*linkedca.Policy, error)
RemoveAuthorityPolicy(ctx context.Context) error
}
// CreateAdminRequest represents the body for a CreateAdmin request.
type CreateAdminRequest struct {
Subject string `json:"subject"`
Provisioner string `json:"provisioner"`
Type linkedca.Admin_Type `json:"type"`
}
// Validate validates a new-admin request body.
func (car *CreateAdminRequest) Validate() error {
if car.Subject == "" {
return admin.NewError(admin.ErrorBadRequestType, "subject cannot be empty")
}
if car.Provisioner == "" {
return admin.NewError(admin.ErrorBadRequestType, "provisioner cannot be empty")
}
switch car.Type {
case linkedca.Admin_SUPER_ADMIN, linkedca.Admin_ADMIN:
default:
return admin.NewError(admin.ErrorBadRequestType, "invalid value for admin type")
}
return nil
}
// GetAdminsResponse for returning a list of admins.
type GetAdminsResponse struct {
Admins []*linkedca.Admin `json:"admins"`
NextCursor string `json:"nextCursor"`
}
// UpdateAdminRequest represents the body for a UpdateAdmin request.
type UpdateAdminRequest struct {
Type linkedca.Admin_Type `json:"type"`
}
// Validate validates a new-admin request body.
func (uar *UpdateAdminRequest) Validate() error {
switch uar.Type {
case linkedca.Admin_SUPER_ADMIN, linkedca.Admin_ADMIN:
default:
return admin.NewError(admin.ErrorBadRequestType, "invalid value for admin type")
}
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.
func GetAdmin(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
adm, ok := mustAuthority(r.Context()).LoadAdminByID(id)
if !ok {
render.Error(w, admin.NewError(admin.ErrorNotFoundType,
"admin %s not found", id))
return
}
render.ProtoJSON(w, adm)
}
// GetAdmins returns a segment of admins associated with the authority.
func GetAdmins(w http.ResponseWriter, r *http.Request) {
cursor, limit, err := api.ParseCursor(r)
if err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err,
"error parsing cursor and limit from query params"))
return
}
admins, nextCursor, err := mustAuthority(r.Context()).GetAdmins(cursor, limit)
if err != nil {
render.Error(w, admin.WrapErrorISE(err, "error retrieving paginated admins"))
return
}
render.JSON(w, &GetAdminsResponse{
Admins: admins,
NextCursor: nextCursor,
})
}
// CreateAdmin creates a new admin.
func CreateAdmin(w http.ResponseWriter, r *http.Request) {
var body CreateAdminRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
return
}
if err := body.Validate(); err != nil {
render.Error(w, err)
return
}
auth := mustAuthority(r.Context())
p, err := auth.LoadProvisionerByName(body.Provisioner)
if err != nil {
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", body.Provisioner))
return
}
adm := &linkedca.Admin{
ProvisionerId: p.GetID(),
Subject: body.Subject,
Type: body.Type,
}
// Store to authority collection.
if err := auth.StoreAdmin(r.Context(), adm, p); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error storing admin"))
return
}
render.ProtoJSONStatus(w, adm, http.StatusCreated)
}
// DeleteAdmin deletes admin.
func DeleteAdmin(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
if err := mustAuthority(r.Context()).RemoveAdmin(r.Context(), id); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error deleting admin %s", id))
return
}
render.JSON(w, &DeleteResponse{Status: "ok"})
}
// UpdateAdmin updates an existing admin.
func UpdateAdmin(w http.ResponseWriter, r *http.Request) {
var body UpdateAdminRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
return
}
if err := body.Validate(); err != nil {
render.Error(w, err)
return
}
id := chi.URLParam(r, "id")
auth := mustAuthority(r.Context())
adm, err := auth.UpdateAdmin(r.Context(), id, &linkedca.Admin{Type: body.Type})
if err != nil {
render.Error(w, admin.WrapErrorISE(err, "error updating admin %s", id))
return
}
render.ProtoJSON(w, adm)
}
|