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. // Admin type.
type Admin struct { type Admin struct {
ID string `json:"-"` ID string `json:"id"`
AuthorityID string `json:"-"` AuthorityID string `json:"-"`
ProvisionerID string `json:"provisionerID"` ProvisionerID string `json:"provisionerID"`
Name string `json:"name"` Name string `json:"name"`
ProvisionerName string `json:"provisionerName"` IsSuperAdmin bool `json:"isSuperAdmin"`
ProvisionerType string `json:"provisionerType"` Status StatusType `json:"status"`
IsSuperAdmin bool `json:"isSuperAdmin"`
Status StatusType `json:"status"`
} }
// CreateAdmin builds and stores an admin type in the DB. // 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{ adm := &Admin{
Name: name, Name: name,
ProvisionerID: prov.ID, ProvisionerID: provID,
ProvisionerName: prov.Name, IsSuperAdmin: isSuperAdmin,
ProvisionerType: prov.Type, Status: StatusActive,
IsSuperAdmin: isSuperAdmin,
Status: StatusActive,
} }
if err := db.CreateAdmin(ctx, adm); err != nil { if err := db.CreateAdmin(ctx, adm); err != nil {
return nil, WrapErrorISE(err, "error creating admin") return nil, WrapErrorISE(err, "error creating admin")

View file

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

View file

@ -2,6 +2,7 @@ package mgmt
import ( import (
"context" "context"
"fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/config"
@ -24,6 +25,17 @@ const (
StatusDeleted 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 // Claims encapsulates all x509 and ssh claims applied to the authority
// configuration. E.g. maxTLSCertDuration, defaultSSHCertDuration, etc. // configuration. E.g. maxTLSCertDuration, defaultSSHCertDuration, etc.
type Claims struct { 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") 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 { if err != nil {
// TODO should we try to clean up? // TODO should we try to clean up?
return nil, WrapErrorISE(err, "error creating first provisioner") 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 return nil, err
} }
if adm.Status == mgmt.StatusDeleted { 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 { if adm.AuthorityID != db.authorityID {
return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType, return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType,
"admin %s is not owned by authority %s", adm.ID, db.authorityID) "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 return adm, nil
} }

View file

@ -3,6 +3,7 @@ package nosql
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -12,16 +13,18 @@ import (
// dbProvisioner is the database representation of a Provisioner type. // dbProvisioner is the database representation of a Provisioner type.
type dbProvisioner struct { type dbProvisioner struct {
ID string `json:"id"` ID string `json:"id"`
AuthorityID string `json:"authorityID"` AuthorityID string `json:"authorityID"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
Claims *mgmt.Claims `json:"claims"` Claims *mgmt.Claims `json:"claims"`
Details interface{} `json:"details"` Details []byte `json:"details"`
X509Template string `json:"x509Template"` X509Template string `json:"x509Template"`
SSHTemplate string `json:"sshTemplate"` X509TemplateData []byte `json:"x509TemplateData"`
CreatedAt time.Time `json:"createdAt"` SSHTemplate string `json:"sshTemplate"`
DeletedAt time.Time `json:"deletedAt"` SSHTemplateData []byte `json:"sshTemplateData"`
CreatedAt time.Time `json:"createdAt"`
DeletedAt time.Time `json:"deletedAt"`
} }
func (dbp *dbProvisioner) clone() *dbProvisioner { func (dbp *dbProvisioner) clone() *dbProvisioner {
@ -84,19 +87,36 @@ func unmarshalDBProvisioner(data []byte, id string) (*dbProvisioner, error) {
return dbp, nil return dbp, nil
} }
type detailsType struct {
Type mgmt.ProvisionerType
}
func unmarshalProvisioner(data []byte, id string) (*mgmt.Provisioner, error) { func unmarshalProvisioner(data []byte, id string) (*mgmt.Provisioner, error) {
dbp, err := unmarshalDBProvisioner(data, id) dbp, err := unmarshalDBProvisioner(data, id)
if err != nil { if err != nil {
return nil, err 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{ prov := &mgmt.Provisioner{
ID: dbp.ID, ID: dbp.ID,
Type: dbp.Type, AuthorityID: dbp.AuthorityID,
Name: dbp.Name, Type: dbp.Type,
Claims: dbp.Claims, Name: dbp.Name,
X509Template: dbp.X509Template, Claims: dbp.Claims,
SSHTemplate: dbp.SSHTemplate, Details: details,
X509Template: dbp.X509Template,
X509TemplateData: dbp.X509TemplateData,
SSHTemplate: dbp.SSHTemplate,
SSHTemplateData: dbp.SSHTemplateData,
} }
if !dbp.DeletedAt.IsZero() { if !dbp.DeletedAt.IsZero() {
prov.Status = mgmt.StatusDeleted 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) 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 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 { func NewProvisionerCtx(opts ...ProvisionerOption) *ProvisionerCtx {
pc := &ProvisionerCtx{ pc := &ProvisionerCtx{
Claims: NewDefaultClaims(), Claims: NewDefaultClaims(),
@ -97,12 +108,14 @@ func CreateProvisioner(ctx context.Context, db DB, typ, name string, opts ...Pro
return p, nil return p, nil
} }
type ProvisionerDetails_JWK struct { // ProvisionerDetailsJWK represents the values required by a JWK provisioner.
PubKey []byte `json:"pubKey"` type ProvisionerDetailsJWK struct {
PrivKey string `json:"privKey"` 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 var err error
if pc.JWK != nil && pc.JWE == nil { 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 nil, WrapErrorISE(err, "error serializing JWE")
} }
return &ProvisionerDetails_JWK{ return &ProvisionerDetailsJWK{
PubKey: jwkPubBytes, Type: ProvisionerTypeJWK,
PrivKey: jwePrivStr, PubKey: jwkPubBytes,
EncPrivKey: jwePrivStr,
}, nil }, nil
} }
@ -150,7 +164,7 @@ func (p *Provisioner) ToCertificates() (provisioner.Interface, error) {
} }
switch details := p.Details.(type) { switch details := p.Details.(type) {
case *ProvisionerDetails_JWK: case *ProvisionerDetailsJWK:
jwk := new(jose.JSONWebKey) jwk := new(jose.JSONWebKey)
if err := json.Unmarshal(details.PubKey, &jwk); err != nil { if err := json.Unmarshal(details.PubKey, &jwk); err != nil {
return nil, err return nil, err
@ -159,7 +173,7 @@ func (p *Provisioner) ToCertificates() (provisioner.Interface, error) {
Type: p.Type, Type: p.Type,
Name: p.Name, Name: p.Name,
Key: jwk, Key: jwk,
EncryptedKey: details.PrivKey, EncryptedKey: details.EncPrivKey,
Claims: claims, Claims: claims,
Options: p.GetOptions(), Options: p.GetOptions(),
}, nil }, nil
@ -324,68 +338,6 @@ func marshalDetails(d *ProvisionerDetails) (sql.NullString, error) {
}, nil }, 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) { func marshalClaims(c *Claims) (sql.NullString, error) {
b, err := json.Marshal(c) b, err := json.Marshal(c)

View file

@ -105,3 +105,26 @@ retry:
} }
return *admins, nil 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
}