2021-05-03 19:48:20 +00:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
2021-12-22 14:30:40 +00:00
|
|
|
"context"
|
2021-05-03 19:48:20 +00:00
|
|
|
"net/http"
|
|
|
|
|
|
|
|
"github.com/go-chi/chi"
|
2022-03-18 13:28:42 +00:00
|
|
|
|
|
|
|
"go.step.sm/linkedca"
|
|
|
|
|
2021-05-03 19:48:20 +00:00
|
|
|
"github.com/smallstep/certificates/api"
|
2022-03-18 13:28:42 +00:00
|
|
|
"github.com/smallstep/certificates/api/read"
|
2022-03-30 08:22:22 +00:00
|
|
|
"github.com/smallstep/certificates/api/render"
|
2021-05-18 04:07:25 +00:00
|
|
|
"github.com/smallstep/certificates/authority/admin"
|
2021-12-22 14:30:40 +00:00
|
|
|
"github.com/smallstep/certificates/authority/provisioner"
|
2021-05-03 19:48:20 +00:00
|
|
|
)
|
|
|
|
|
2021-12-22 14:30:40 +00:00
|
|
|
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
|
2022-03-15 14:51:45 +00:00
|
|
|
GetAuthorityPolicy(ctx context.Context) (*linkedca.Policy, error)
|
2022-03-24 09:54:45 +00:00
|
|
|
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)
|
2022-03-15 14:51:45 +00:00
|
|
|
RemoveAuthorityPolicy(ctx context.Context) error
|
2021-12-22 14:30:40 +00:00
|
|
|
}
|
|
|
|
|
2021-05-03 19:48:20 +00:00
|
|
|
// CreateAdminRequest represents the body for a CreateAdmin request.
|
|
|
|
type CreateAdminRequest struct {
|
2021-05-26 04:13:01 +00:00
|
|
|
Subject string `json:"subject"`
|
|
|
|
Provisioner string `json:"provisioner"`
|
|
|
|
Type linkedca.Admin_Type `json:"type"`
|
2021-05-03 19:48:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Validate validates a new-admin request body.
|
2021-05-03 19:48:20 +00:00
|
|
|
func (car *CreateAdminRequest) Validate() error {
|
2021-07-07 00:14:13 +00:00
|
|
|
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")
|
|
|
|
}
|
2021-05-03 19:48:20 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-05-18 23:50:54 +00:00
|
|
|
// GetAdminsResponse for returning a list of admins.
|
|
|
|
type GetAdminsResponse struct {
|
2021-05-26 04:13:01 +00:00
|
|
|
Admins []*linkedca.Admin `json:"admins"`
|
|
|
|
NextCursor string `json:"nextCursor"`
|
2021-05-18 23:50:54 +00:00
|
|
|
}
|
|
|
|
|
2021-05-03 19:48:20 +00:00
|
|
|
// UpdateAdminRequest represents the body for a UpdateAdmin request.
|
|
|
|
type UpdateAdminRequest struct {
|
2021-05-26 04:13:01 +00:00
|
|
|
Type linkedca.Admin_Type `json:"type"`
|
2021-05-03 19:48:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Validate validates a new-admin request body.
|
|
|
|
func (uar *UpdateAdminRequest) Validate() error {
|
2021-07-07 00:14:13 +00:00
|
|
|
switch uar.Type {
|
|
|
|
case linkedca.Admin_SUPER_ADMIN, linkedca.Admin_ADMIN:
|
|
|
|
default:
|
|
|
|
return admin.NewError(admin.ErrorBadRequestType, "invalid value for admin type")
|
|
|
|
}
|
2021-05-03 19:48:20 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-05-12 07:03:40 +00:00
|
|
|
// DeleteResponse is the resource for successful DELETE responses.
|
|
|
|
type DeleteResponse struct {
|
|
|
|
Status string `json:"status"`
|
|
|
|
}
|
|
|
|
|
2021-05-03 19:48:20 +00:00
|
|
|
// GetAdmin returns the requested admin, or an error.
|
2022-04-27 18:59:32 +00:00
|
|
|
func GetAdmin(w http.ResponseWriter, r *http.Request) {
|
2021-05-03 19:48:20 +00:00
|
|
|
id := chi.URLParam(r, "id")
|
|
|
|
|
2022-04-27 18:59:32 +00:00
|
|
|
adm, ok := mustAuthority(r.Context()).LoadAdminByID(id)
|
2021-05-18 23:50:54 +00:00
|
|
|
if !ok {
|
2022-03-30 08:22:22 +00:00
|
|
|
render.Error(w, admin.NewError(admin.ErrorNotFoundType,
|
2021-05-18 23:50:54 +00:00
|
|
|
"admin %s not found", id))
|
2021-05-03 19:48:20 +00:00
|
|
|
return
|
|
|
|
}
|
2022-03-30 08:22:22 +00:00
|
|
|
render.ProtoJSON(w, adm)
|
2021-05-03 19:48:20 +00:00
|
|
|
}
|
|
|
|
|
2021-05-03 19:48:20 +00:00
|
|
|
// GetAdmins returns a segment of admins associated with the authority.
|
2022-04-27 18:59:32 +00:00
|
|
|
func GetAdmins(w http.ResponseWriter, r *http.Request) {
|
2021-05-18 23:50:54 +00:00
|
|
|
cursor, limit, err := api.ParseCursor(r)
|
2021-05-03 19:48:20 +00:00
|
|
|
if err != nil {
|
2022-03-30 08:22:22 +00:00
|
|
|
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err,
|
2021-05-18 23:50:54 +00:00
|
|
|
"error parsing cursor and limit from query params"))
|
2021-05-03 19:48:20 +00:00
|
|
|
return
|
|
|
|
}
|
2021-05-18 23:50:54 +00:00
|
|
|
|
2022-04-27 18:59:32 +00:00
|
|
|
admins, nextCursor, err := mustAuthority(r.Context()).GetAdmins(cursor, limit)
|
2021-05-03 19:48:20 +00:00
|
|
|
if err != nil {
|
2022-03-30 08:22:22 +00:00
|
|
|
render.Error(w, admin.WrapErrorISE(err, "error retrieving paginated admins"))
|
2021-05-03 19:48:20 +00:00
|
|
|
return
|
|
|
|
}
|
2022-03-30 08:22:22 +00:00
|
|
|
render.JSON(w, &GetAdminsResponse{
|
2021-05-18 23:50:54 +00:00
|
|
|
Admins: admins,
|
|
|
|
NextCursor: nextCursor,
|
|
|
|
})
|
2021-05-03 19:48:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// CreateAdmin creates a new admin.
|
2022-04-27 18:59:32 +00:00
|
|
|
func CreateAdmin(w http.ResponseWriter, r *http.Request) {
|
2021-05-12 07:03:40 +00:00
|
|
|
var body CreateAdminRequest
|
2022-03-18 13:28:42 +00:00
|
|
|
if err := read.JSON(r.Body, &body); err != nil {
|
2022-03-30 08:22:22 +00:00
|
|
|
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
|
2021-05-12 07:03:40 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-03 19:48:20 +00:00
|
|
|
if err := body.Validate(); err != nil {
|
2022-03-30 08:22:22 +00:00
|
|
|
render.Error(w, err)
|
2021-05-18 04:07:25 +00:00
|
|
|
return
|
|
|
|
}
|
2021-05-12 07:03:40 +00:00
|
|
|
|
2022-04-27 18:59:32 +00:00
|
|
|
auth := mustAuthority(r.Context())
|
|
|
|
p, err := auth.LoadProvisionerByName(body.Provisioner)
|
2021-05-03 19:48:20 +00:00
|
|
|
if err != nil {
|
2022-03-30 08:22:22 +00:00
|
|
|
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", body.Provisioner))
|
2021-05-18 23:50:54 +00:00
|
|
|
return
|
|
|
|
}
|
2021-05-26 04:13:01 +00:00
|
|
|
adm := &linkedca.Admin{
|
|
|
|
ProvisionerId: p.GetID(),
|
2021-05-18 23:50:54 +00:00
|
|
|
Subject: body.Subject,
|
|
|
|
Type: body.Type,
|
2021-05-12 07:03:40 +00:00
|
|
|
}
|
2021-05-03 19:48:20 +00:00
|
|
|
// Store to authority collection.
|
2022-04-27 18:59:32 +00:00
|
|
|
if err := auth.StoreAdmin(r.Context(), adm, p); err != nil {
|
2022-03-30 08:22:22 +00:00
|
|
|
render.Error(w, admin.WrapErrorISE(err, "error storing admin"))
|
2021-05-12 07:03:40 +00:00
|
|
|
return
|
|
|
|
}
|
2021-05-03 19:48:20 +00:00
|
|
|
|
2022-03-30 08:22:22 +00:00
|
|
|
render.ProtoJSONStatus(w, adm, http.StatusCreated)
|
2021-05-12 07:03:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteAdmin deletes admin.
|
2022-04-27 18:59:32 +00:00
|
|
|
func DeleteAdmin(w http.ResponseWriter, r *http.Request) {
|
2021-05-12 07:03:40 +00:00
|
|
|
id := chi.URLParam(r, "id")
|
|
|
|
|
2022-04-27 18:59:32 +00:00
|
|
|
if err := mustAuthority(r.Context()).RemoveAdmin(r.Context(), id); err != nil {
|
2022-03-30 08:22:22 +00:00
|
|
|
render.Error(w, admin.WrapErrorISE(err, "error deleting admin %s", id))
|
2021-05-18 04:07:25 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-03-30 08:22:22 +00:00
|
|
|
render.JSON(w, &DeleteResponse{Status: "ok"})
|
2021-05-03 19:48:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateAdmin updates an existing admin.
|
2022-04-27 18:59:32 +00:00
|
|
|
func UpdateAdmin(w http.ResponseWriter, r *http.Request) {
|
2021-05-12 07:03:40 +00:00
|
|
|
var body UpdateAdminRequest
|
2022-03-18 13:28:42 +00:00
|
|
|
if err := read.JSON(r.Body, &body); err != nil {
|
2022-03-30 08:22:22 +00:00
|
|
|
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
|
2021-05-12 07:03:40 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-07-07 00:14:13 +00:00
|
|
|
if err := body.Validate(); err != nil {
|
2022-03-30 08:22:22 +00:00
|
|
|
render.Error(w, err)
|
2021-07-07 00:14:13 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-12 07:03:40 +00:00
|
|
|
id := chi.URLParam(r, "id")
|
2022-04-27 18:59:32 +00:00
|
|
|
auth := mustAuthority(r.Context())
|
|
|
|
adm, err := auth.UpdateAdmin(r.Context(), id, &linkedca.Admin{Type: body.Type})
|
2021-05-03 19:48:20 +00:00
|
|
|
if err != nil {
|
2022-03-30 08:22:22 +00:00
|
|
|
render.Error(w, admin.WrapErrorISE(err, "error updating admin %s", id))
|
2021-05-12 07:03:40 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-03-30 08:22:22 +00:00
|
|
|
render.ProtoJSON(w, adm)
|
2021-05-03 19:48:20 +00:00
|
|
|
}
|