This commit is contained in:
max furman 2021-05-11 15:25:37 -07:00
parent af3cf7dae9
commit 98a6e54530
7 changed files with 194 additions and 126 deletions

View file

@ -4,25 +4,21 @@ import "context"
// Admin type.
type Admin struct {
ID string `json:"-"`
AuthorityID string `json:"-"`
ProvisionerID string `json:"provisionerID"`
Name string `json:"name"`
ProvisionerName string `json:"provisionerName"`
ProvisionerType string `json:"provisionerType"`
IsSuperAdmin bool `json:"isSuperAdmin"`
Status StatusType `json:"status"`
ID string `json:"id"`
AuthorityID string `json:"-"`
ProvisionerID string `json:"provisionerID"`
Name string `json:"name"`
IsSuperAdmin bool `json:"isSuperAdmin"`
Status StatusType `json:"status"`
}
// CreateAdmin builds and stores an admin type in the DB.
func CreateAdmin(ctx context.Context, db DB, name string, prov *Provisioner, isSuperAdmin bool) (*Admin, error) {
func CreateAdmin(ctx context.Context, db DB, name string, provID string, isSuperAdmin bool) (*Admin, error) {
adm := &Admin{
Name: name,
ProvisionerID: prov.ID,
ProvisionerName: prov.Name,
ProvisionerType: prov.Type,
IsSuperAdmin: isSuperAdmin,
Status: StatusActive,
Name: name,
ProvisionerID: provID,
IsSuperAdmin: isSuperAdmin,
Status: StatusActive,
}
if err := db.CreateAdmin(ctx, adm); err != nil {
return nil, WrapErrorISE(err, "error creating admin")

View file

@ -1,6 +1,7 @@
package api
import (
"fmt"
"net/http"
"github.com/go-chi/chi"
@ -10,12 +11,14 @@ import (
// CreateProvisionerRequest represents the body for a CreateProvisioner request.
type CreateProvisionerRequest struct {
Type string `json:"type"`
Name string `json:"name"`
Claims *mgmt.Claims `json:"claims"`
Details interface{} `json:"details"`
X509Template string `json:"x509Template"`
SSHTemplate string `json:"sshTemplate"`
Type string `json:"type"`
Name string `json:"name"`
Claims *mgmt.Claims `json:"claims"`
Details interface{} `json:"details"`
X509Template string `json:"x509Template"`
X509TemplateData []byte `json:"x509TemplateData"`
SSHTemplate string `json:"sshTemplate"`
SSHTemplateData []byte `json:"sshTemplateData"`
}
// Validate validates a new-provisioner request body.
@ -25,10 +28,12 @@ func (car *CreateProvisionerRequest) Validate() error {
// UpdateProvisionerRequest represents the body for a UpdateProvisioner request.
type UpdateProvisionerRequest struct {
Claims *mgmt.Claims `json:"claims"`
Details interface{} `json:"details"`
X509Template string `json:"x509Template"`
SSHTemplate string `json:"sshTemplate"`
Claims *mgmt.Claims `json:"claims"`
Details interface{} `json:"details"`
X509Template string `json:"x509Template"`
X509TemplateData []byte `json:"x509TemplateData"`
SSHTemplate string `json:"sshTemplate"`
SSHTemplateData []byte `json:"sshTemplateData"`
}
// Validate validates a new-provisioner request body.
@ -58,6 +63,7 @@ func (h *Handler) GetProvisioners(w http.ResponseWriter, r *http.Request) {
api.WriteError(w, err)
return
}
fmt.Printf("provs = %+v\n", provs)
api.JSON(w, provs)
}
@ -75,12 +81,14 @@ func (h *Handler) CreateProvisioner(w http.ResponseWriter, r *http.Request) {
}
prov := &mgmt.Provisioner{
Type: body.Type,
Name: body.Name,
Claims: body.Claims,
Details: body.Details,
X509Template: body.X509Template,
SSHTemplate: body.SSHTemplate,
Type: body.Type,
Name: body.Name,
Claims: body.Claims,
Details: body.Details,
X509Template: body.X509Template,
X509TemplateData: body.X509TemplateData,
SSHTemplate: body.SSHTemplate,
SSHTemplateData: body.SSHTemplateData,
}
if err := h.db.CreateProvisioner(ctx, prov); err != nil {
api.WriteError(w, err)

View file

@ -2,6 +2,7 @@ package mgmt
import (
"context"
"fmt"
"github.com/pkg/errors"
"github.com/smallstep/certificates/authority/config"
@ -24,6 +25,17 @@ const (
StatusDeleted
)
func (st StatusType) String() string {
switch st {
case StatusActive:
return "active"
case StatusDeleted:
return "deleted"
default:
return fmt.Sprintf("status %d not found", st)
}
}
// Claims encapsulates all x509 and ssh claims applied to the authority
// configuration. E.g. maxTLSCertDuration, defaultSSHCertDuration, etc.
type Claims struct {
@ -111,7 +123,7 @@ func CreateAuthority(ctx context.Context, db DB, options ...AuthorityOption) (*A
return nil, WrapErrorISE(err, "error creating first provisioner")
}
admin, err := CreateAdmin(ctx, db, "Change Me", prov, true)
admin, err := CreateAdmin(ctx, db, "Change Me", prov.ID, true)
if err != nil {
// TODO should we try to clean up?
return nil, WrapErrorISE(err, "error creating first provisioner")

View file

@ -63,19 +63,13 @@ func (db *DB) GetAdmin(ctx context.Context, id string) (*mgmt.Admin, error) {
return nil, err
}
if adm.Status == mgmt.StatusDeleted {
return nil, mgmt.NewError(mgmt.ErrorDeletedType, "admin %s is deleted")
return nil, mgmt.NewError(mgmt.ErrorDeletedType, "admin %s is deleted", adm.ID)
}
if adm.AuthorityID != db.authorityID {
return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType,
"admin %s is not owned by authority %s", adm.ID, db.authorityID)
}
prov, err := db.GetProvisioner(ctx, adm.ProvisionerID)
if err != nil {
return nil, err
}
adm.ProvisionerName = prov.Name
adm.ProvisionerType = prov.Type
return adm, nil
}

View file

@ -3,6 +3,7 @@ package nosql
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/pkg/errors"
@ -12,16 +13,18 @@ import (
// dbProvisioner is the database representation of a Provisioner type.
type dbProvisioner struct {
ID string `json:"id"`
AuthorityID string `json:"authorityID"`
Type string `json:"type"`
Name string `json:"name"`
Claims *mgmt.Claims `json:"claims"`
Details interface{} `json:"details"`
X509Template string `json:"x509Template"`
SSHTemplate string `json:"sshTemplate"`
CreatedAt time.Time `json:"createdAt"`
DeletedAt time.Time `json:"deletedAt"`
ID string `json:"id"`
AuthorityID string `json:"authorityID"`
Type string `json:"type"`
Name string `json:"name"`
Claims *mgmt.Claims `json:"claims"`
Details []byte `json:"details"`
X509Template string `json:"x509Template"`
X509TemplateData []byte `json:"x509TemplateData"`
SSHTemplate string `json:"sshTemplate"`
SSHTemplateData []byte `json:"sshTemplateData"`
CreatedAt time.Time `json:"createdAt"`
DeletedAt time.Time `json:"deletedAt"`
}
func (dbp *dbProvisioner) clone() *dbProvisioner {
@ -84,19 +87,36 @@ func unmarshalDBProvisioner(data []byte, id string) (*dbProvisioner, error) {
return dbp, nil
}
type detailsType struct {
Type mgmt.ProvisionerType
}
func unmarshalProvisioner(data []byte, id string) (*mgmt.Provisioner, error) {
dbp, err := unmarshalDBProvisioner(data, id)
if err != nil {
return nil, err
}
dt := new(detailsType)
if err := json.Unmarshal(dbp.Details, dt); err != nil {
return nil, mgmt.WrapErrorISE(err, "error unmarshaling details to detailsType for provisioner %s", id)
}
details, err := unmarshalDetails(dt.Type, dbp.Details)
if err != nil {
return nil, err
}
prov := &mgmt.Provisioner{
ID: dbp.ID,
Type: dbp.Type,
Name: dbp.Name,
Claims: dbp.Claims,
X509Template: dbp.X509Template,
SSHTemplate: dbp.SSHTemplate,
ID: dbp.ID,
AuthorityID: dbp.AuthorityID,
Type: dbp.Type,
Name: dbp.Name,
Claims: dbp.Claims,
Details: details,
X509Template: dbp.X509Template,
X509TemplateData: dbp.X509TemplateData,
SSHTemplate: dbp.SSHTemplate,
SSHTemplateData: dbp.SSHTemplateData,
}
if !dbp.DeletedAt.IsZero() {
prov.Status = mgmt.StatusDeleted
@ -172,3 +192,66 @@ func (db *DB) UpdateProvisioner(ctx context.Context, prov *mgmt.Provisioner) err
return db.save(ctx, old.ID, nu, old, "provisioner", authorityProvisionersTable)
}
func unmarshalDetails(typ ProvisionerType, details []byte) (interface{}, error) {
if !s.Valid {
return nil, nil
}
var v isProvisionerDetails_Data
switch typ {
case ProvisionerTypeJWK:
p := new(ProvisionerDetailsJWK)
if err := json.Unmarshal([]byte(s.String), p); err != nil {
return nil, err
}
if p.JWK.Key.Key == nil {
key, err := LoadKey(ctx, db, p.JWK.Key.Id.Id)
if err != nil {
return nil, err
}
p.JWK.Key = key
}
return &ProvisionerDetails{Data: p}, nil
case ProvisionerType_OIDC:
v = new(ProvisionerDetails_OIDC)
case ProvisionerType_GCP:
v = new(ProvisionerDetails_GCP)
case ProvisionerType_AWS:
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:
return nil, fmt.Errorf("unsupported provisioner type %s", typ)
}
if err := json.Unmarshal([]byte(s.String), v); err != nil {
return nil, err
}
return &ProvisionerDetails{Data: v}, nil
}

View file

@ -20,6 +20,17 @@ type ProvisionerCtx struct {
Password string
}
type ProvisionerType string
var (
ProvisionerTypeJWK = ProvisionerType("JWK")
ProvisionerTypeOIDC = ProvisionerType("OIDC")
ProvisionerTypeACME = ProvisionerType("ACME")
ProvisionerTypeX5C = ProvisionerType("X5C")
ProvisionerTypeK8S = ProvisionerType("K8S")
ProvisionerTypeSSHPOP = ProvisionerType("SSHPOP")
)
func NewProvisionerCtx(opts ...ProvisionerOption) *ProvisionerCtx {
pc := &ProvisionerCtx{
Claims: NewDefaultClaims(),
@ -97,12 +108,14 @@ func CreateProvisioner(ctx context.Context, db DB, typ, name string, opts ...Pro
return p, nil
}
type ProvisionerDetails_JWK struct {
PubKey []byte `json:"pubKey"`
PrivKey string `json:"privKey"`
// ProvisionerDetailsJWK represents the values required by a JWK provisioner.
type ProvisionerDetailsJWK struct {
Type ProvisionerType `json:"type"`
PubKey []byte `json:"pubKey"`
EncPrivKey string `json:"privKey"`
}
func createJWKDetails(pc *ProvisionerCtx) (*ProvisionerDetails_JWK, error) {
func createJWKDetails(pc *ProvisionerCtx) (*ProvisionerDetailsJWK, error) {
var err error
if pc.JWK != nil && pc.JWE == nil {
@ -131,9 +144,10 @@ func createJWKDetails(pc *ProvisionerCtx) (*ProvisionerDetails_JWK, error) {
return nil, WrapErrorISE(err, "error serializing JWE")
}
return &ProvisionerDetails_JWK{
PubKey: jwkPubBytes,
PrivKey: jwePrivStr,
return &ProvisionerDetailsJWK{
Type: ProvisionerTypeJWK,
PubKey: jwkPubBytes,
EncPrivKey: jwePrivStr,
}, nil
}
@ -150,7 +164,7 @@ func (p *Provisioner) ToCertificates() (provisioner.Interface, error) {
}
switch details := p.Details.(type) {
case *ProvisionerDetails_JWK:
case *ProvisionerDetailsJWK:
jwk := new(jose.JSONWebKey)
if err := json.Unmarshal(details.PubKey, &jwk); err != nil {
return nil, err
@ -159,7 +173,7 @@ func (p *Provisioner) ToCertificates() (provisioner.Interface, error) {
Type: p.Type,
Name: p.Name,
Key: jwk,
EncryptedKey: details.PrivKey,
EncryptedKey: details.EncPrivKey,
Claims: claims,
Options: p.GetOptions(),
}, nil
@ -324,68 +338,6 @@ func marshalDetails(d *ProvisionerDetails) (sql.NullString, error) {
}, nil
}
func unmarshalDetails(ctx context.Context, db database.DB, typ ProvisionerType, s sql.NullString) (*ProvisionerDetails, error) {
if !s.Valid {
return nil, nil
}
var v isProvisionerDetails_Data
switch typ {
case ProvisionerType_JWK:
p := new(ProvisionerDetails_JWK)
if err := json.Unmarshal([]byte(s.String), p); err != nil {
return nil, err
}
if p.JWK.Key.Key == nil {
key, err := LoadKey(ctx, db, p.JWK.Key.Id.Id)
if err != nil {
return nil, err
}
p.JWK.Key = key
}
return &ProvisionerDetails{Data: p}, nil
case ProvisionerType_OIDC:
v = new(ProvisionerDetails_OIDC)
case ProvisionerType_GCP:
v = new(ProvisionerDetails_GCP)
case ProvisionerType_AWS:
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:
return nil, fmt.Errorf("unsupported provisioner type %s", typ)
}
if err := json.Unmarshal([]byte(s.String), v); err != nil {
return nil, err
}
return &ProvisionerDetails{Data: v}, nil
}
func marshalClaims(c *Claims) (sql.NullString, error) {
b, err := json.Marshal(c)

View file

@ -105,3 +105,26 @@ retry:
}
return *admins, nil
}
// GetProvisioners performs the GET /mgmt/provisioners request to the CA.
func (c *MgmtClient) GetProvisioners() ([]*mgmt.Provisioner, error) {
var retried bool
u := c.endpoint.ResolveReference(&url.URL{Path: "/mgmt/provisioners"})
retry:
resp, err := c.client.Get(u.String())
if err != nil {
return nil, errors.Wrapf(err, "client GET %s failed", u)
}
if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) {
retried = true
goto retry
}
return nil, readError(resp.Body)
}
var provs = new([]*mgmt.Provisioner)
if err := readJSON(resp.Body, provs); err != nil {
return nil, errors.Wrapf(err, "error reading %s", u)
}
return *provs, nil
}