2021-05-03 19:48:20 +00:00
|
|
|
package nosql
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/smallstep/certificates/authority/admin"
|
|
|
|
"github.com/smallstep/nosql"
|
|
|
|
"go.step.sm/linkedca"
|
|
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
|
|
)
|
|
|
|
|
|
|
|
// dbProvisioner is the database representation of a Provisioner type.
|
|
|
|
type dbProvisioner struct {
|
|
|
|
ID string `json:"id"`
|
|
|
|
AuthorityID string `json:"authorityID"`
|
|
|
|
Type linkedca.Provisioner_Type `json:"type"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Claims *linkedca.Claims `json:"claims"`
|
|
|
|
Details []byte `json:"details"`
|
|
|
|
X509Template *linkedca.Template `json:"x509Template"`
|
|
|
|
SSHTemplate *linkedca.Template `json:"sshTemplate"`
|
|
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
|
|
DeletedAt time.Time `json:"deletedAt"`
|
2022-09-30 00:16:26 +00:00
|
|
|
Webhooks []dbWebhook `json:"webhooks,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type dbBasicAuth struct {
|
|
|
|
Username string `json:"username"`
|
|
|
|
Password string `json:"password"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type dbWebhook struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
ID string `json:"id"`
|
|
|
|
URL string `json:"url"`
|
|
|
|
Kind string `json:"kind"`
|
|
|
|
Secret string `json:"secret"`
|
|
|
|
BearerToken string `json:"bearerToken,omitempty"`
|
|
|
|
BasicAuth *dbBasicAuth `json:"basicAuth,omitempty"`
|
|
|
|
DisableTLSClientAuth bool `json:"disableTLSClientAuth,omitempty"`
|
|
|
|
CertType string `json:"certType,omitempty"`
|
2021-05-03 19:48:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (dbp *dbProvisioner) clone() *dbProvisioner {
|
|
|
|
u := *dbp
|
|
|
|
return &u
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dbp *dbProvisioner) convert2linkedca() (*linkedca.Provisioner, error) {
|
|
|
|
details, err := admin.UnmarshalProvisionerDetails(dbp.Type, dbp.Details)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &linkedca.Provisioner{
|
|
|
|
Id: dbp.ID,
|
|
|
|
AuthorityId: dbp.AuthorityID,
|
|
|
|
Type: dbp.Type,
|
|
|
|
Name: dbp.Name,
|
|
|
|
Claims: dbp.Claims,
|
|
|
|
Details: details,
|
|
|
|
X509Template: dbp.X509Template,
|
|
|
|
SshTemplate: dbp.SSHTemplate,
|
|
|
|
CreatedAt: timestamppb.New(dbp.CreatedAt),
|
|
|
|
DeletedAt: timestamppb.New(dbp.DeletedAt),
|
2022-09-30 00:16:26 +00:00
|
|
|
Webhooks: dbWebhooksToLinkedca(dbp.Webhooks),
|
2021-05-03 19:48:20 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *DB) getDBProvisionerBytes(ctx context.Context, id string) ([]byte, error) {
|
|
|
|
data, err := db.db.Get(provisionersTable, []byte(id))
|
|
|
|
if nosql.IsErrNotFound(err) {
|
|
|
|
return nil, admin.NewError(admin.ErrorNotFoundType, "provisioner %s not found", id)
|
|
|
|
} else if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "error loading provisioner %s", id)
|
|
|
|
}
|
|
|
|
return data, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *DB) unmarshalDBProvisioner(data []byte, id string) (*dbProvisioner, error) {
|
|
|
|
var dbp = new(dbProvisioner)
|
|
|
|
if err := json.Unmarshal(data, dbp); err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "error unmarshaling provisioner %s into dbProvisioner", id)
|
|
|
|
}
|
|
|
|
if !dbp.DeletedAt.IsZero() {
|
|
|
|
return nil, admin.NewError(admin.ErrorDeletedType, "provisioner %s is deleted", id)
|
|
|
|
}
|
|
|
|
if dbp.AuthorityID != db.authorityID {
|
|
|
|
return nil, admin.NewError(admin.ErrorAuthorityMismatchType,
|
|
|
|
"provisioner %s is not owned by authority %s", id, db.authorityID)
|
|
|
|
}
|
|
|
|
return dbp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *DB) getDBProvisioner(ctx context.Context, id string) (*dbProvisioner, error) {
|
|
|
|
data, err := db.getDBProvisionerBytes(ctx, id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
dbp, err := db.unmarshalDBProvisioner(data, id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return dbp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *DB) unmarshalProvisioner(data []byte, id string) (*linkedca.Provisioner, error) {
|
|
|
|
dbp, err := db.unmarshalDBProvisioner(data, id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return dbp.convert2linkedca()
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetProvisioner retrieves and unmarshals a provisioner from the database.
|
|
|
|
func (db *DB) GetProvisioner(ctx context.Context, id string) (*linkedca.Provisioner, error) {
|
|
|
|
data, err := db.getDBProvisionerBytes(ctx, id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
prov, err := db.unmarshalProvisioner(data, id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return prov, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetProvisioners retrieves and unmarshals all active (not deleted) provisioners
|
|
|
|
// from the database.
|
|
|
|
func (db *DB) GetProvisioners(ctx context.Context) ([]*linkedca.Provisioner, error) {
|
|
|
|
dbEntries, err := db.db.List(provisionersTable)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "error loading provisioners")
|
|
|
|
}
|
|
|
|
var provs []*linkedca.Provisioner
|
|
|
|
for _, entry := range dbEntries {
|
|
|
|
prov, err := db.unmarshalProvisioner(entry.Value, string(entry.Key))
|
|
|
|
if err != nil {
|
2022-08-23 19:43:48 +00:00
|
|
|
var ae *admin.Error
|
|
|
|
if errors.As(err, &ae) {
|
|
|
|
if ae.IsType(admin.ErrorDeletedType) || ae.IsType(admin.ErrorAuthorityMismatchType) {
|
2021-05-03 19:48:20 +00:00
|
|
|
continue
|
|
|
|
} else {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-08-23 19:43:48 +00:00
|
|
|
} else {
|
2021-05-03 19:48:20 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if prov.AuthorityId != db.authorityID {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
provs = append(provs, prov)
|
|
|
|
}
|
|
|
|
return provs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateProvisioner stores a new provisioner to the database.
|
|
|
|
func (db *DB) CreateProvisioner(ctx context.Context, prov *linkedca.Provisioner) error {
|
|
|
|
var err error
|
|
|
|
prov.Id, err = randID()
|
|
|
|
if err != nil {
|
|
|
|
return admin.WrapErrorISE(err, "error generating random id for provisioner")
|
|
|
|
}
|
|
|
|
|
|
|
|
details, err := json.Marshal(prov.Details.GetData())
|
|
|
|
if err != nil {
|
|
|
|
return admin.WrapErrorISE(err, "error marshaling details when creating provisioner %s", prov.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
dbp := &dbProvisioner{
|
|
|
|
ID: prov.Id,
|
|
|
|
AuthorityID: db.authorityID,
|
|
|
|
Type: prov.Type,
|
|
|
|
Name: prov.Name,
|
|
|
|
Claims: prov.Claims,
|
|
|
|
Details: details,
|
|
|
|
X509Template: prov.X509Template,
|
|
|
|
SSHTemplate: prov.SshTemplate,
|
|
|
|
CreatedAt: clock.Now(),
|
2022-09-30 00:16:26 +00:00
|
|
|
Webhooks: linkedcaWebhooksToDB(prov.Webhooks),
|
2021-05-03 19:48:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := db.save(ctx, prov.Id, dbp, nil, "provisioner", provisionersTable); err != nil {
|
|
|
|
return admin.WrapErrorISE(err, "error creating provisioner %s", prov.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateProvisioner saves an updated provisioner to the database.
|
|
|
|
func (db *DB) UpdateProvisioner(ctx context.Context, prov *linkedca.Provisioner) error {
|
|
|
|
old, err := db.getDBProvisioner(ctx, prov.Id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
nu := old.clone()
|
|
|
|
|
|
|
|
if old.Type != prov.Type {
|
|
|
|
return admin.NewError(admin.ErrorBadRequestType, "cannot update provisioner type")
|
|
|
|
}
|
|
|
|
nu.Name = prov.Name
|
|
|
|
nu.Claims = prov.Claims
|
|
|
|
nu.Details, err = json.Marshal(prov.Details.GetData())
|
|
|
|
if err != nil {
|
|
|
|
return admin.WrapErrorISE(err, "error marshaling details when updating provisioner %s", prov.Name)
|
|
|
|
}
|
|
|
|
nu.X509Template = prov.X509Template
|
|
|
|
nu.SSHTemplate = prov.SshTemplate
|
2022-09-30 00:16:26 +00:00
|
|
|
nu.Webhooks = linkedcaWebhooksToDB(prov.Webhooks)
|
2021-05-03 19:48:20 +00:00
|
|
|
|
|
|
|
return db.save(ctx, prov.Id, nu, old, "provisioner", provisionersTable)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteProvisioner saves an updated admin to the database.
|
|
|
|
func (db *DB) DeleteProvisioner(ctx context.Context, id string) error {
|
|
|
|
old, err := db.getDBProvisioner(ctx, id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
nu := old.clone()
|
|
|
|
nu.DeletedAt = clock.Now()
|
|
|
|
|
|
|
|
return db.save(ctx, old.ID, nu, old, "provisioner", provisionersTable)
|
|
|
|
}
|
2022-09-30 00:16:26 +00:00
|
|
|
|
|
|
|
func dbWebhooksToLinkedca(dbwhs []dbWebhook) []*linkedca.Webhook {
|
|
|
|
if len(dbwhs) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
lwhs := make([]*linkedca.Webhook, len(dbwhs))
|
|
|
|
|
|
|
|
for i, dbwh := range dbwhs {
|
|
|
|
lwh := &linkedca.Webhook{
|
|
|
|
Name: dbwh.Name,
|
|
|
|
Id: dbwh.ID,
|
|
|
|
Url: dbwh.URL,
|
|
|
|
Kind: linkedca.Webhook_Kind(linkedca.Webhook_Kind_value[dbwh.Kind]),
|
|
|
|
Secret: dbwh.Secret,
|
|
|
|
DisableTlsClientAuth: dbwh.DisableTLSClientAuth,
|
|
|
|
CertType: linkedca.Webhook_CertType(linkedca.Webhook_CertType_value[dbwh.CertType]),
|
|
|
|
}
|
|
|
|
if dbwh.BearerToken != "" {
|
|
|
|
lwh.Auth = &linkedca.Webhook_BearerToken{
|
|
|
|
BearerToken: &linkedca.BearerToken{
|
|
|
|
BearerToken: dbwh.BearerToken,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
} else if dbwh.BasicAuth != nil && (dbwh.BasicAuth.Username != "" || dbwh.BasicAuth.Password != "") {
|
|
|
|
lwh.Auth = &linkedca.Webhook_BasicAuth{
|
|
|
|
BasicAuth: &linkedca.BasicAuth{
|
|
|
|
Username: dbwh.BasicAuth.Username,
|
|
|
|
Password: dbwh.BasicAuth.Password,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lwhs[i] = lwh
|
|
|
|
}
|
|
|
|
|
|
|
|
return lwhs
|
|
|
|
}
|
|
|
|
|
|
|
|
func linkedcaWebhooksToDB(lwhs []*linkedca.Webhook) []dbWebhook {
|
|
|
|
if len(lwhs) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
dbwhs := make([]dbWebhook, len(lwhs))
|
|
|
|
|
|
|
|
for i, lwh := range lwhs {
|
|
|
|
dbwh := dbWebhook{
|
|
|
|
Name: lwh.Name,
|
|
|
|
ID: lwh.Id,
|
|
|
|
URL: lwh.Url,
|
|
|
|
Kind: lwh.Kind.String(),
|
|
|
|
Secret: lwh.Secret,
|
|
|
|
DisableTLSClientAuth: lwh.DisableTlsClientAuth,
|
|
|
|
CertType: lwh.CertType.String(),
|
|
|
|
}
|
|
|
|
switch a := lwh.GetAuth().(type) {
|
|
|
|
case *linkedca.Webhook_BearerToken:
|
|
|
|
dbwh.BearerToken = a.BearerToken.BearerToken
|
|
|
|
case *linkedca.Webhook_BasicAuth:
|
|
|
|
dbwh.BasicAuth = &dbBasicAuth{
|
|
|
|
Username: a.BasicAuth.Username,
|
|
|
|
Password: a.BasicAuth.Password,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dbwhs[i] = dbwh
|
|
|
|
}
|
|
|
|
|
|
|
|
return dbwhs
|
|
|
|
}
|