From 01a4460812bcdd92b5ca65f059c47b990fb27064 Mon Sep 17 00:00:00 2001 From: max furman Date: Tue, 25 May 2021 21:13:01 -0700 Subject: [PATCH] wip --- authority/authority.go | 76 ++++++--- authority/mgmt/api/admin.go | 40 ++--- authority/mgmt/api/authConfig.go | 87 ---------- authority/mgmt/api/handler.go | 4 - authority/mgmt/api/provisioner.go | 58 +++---- authority/mgmt/config.go | 30 ---- authority/mgmt/db/nosql/provisioner.go | 13 ++ authority/mgmt/provisioner.go | 222 +++++-------------------- authority/provisioners.go | 207 +++++++++++++++++++++++ ca/adminClient.go | 67 +++----- 10 files changed, 376 insertions(+), 428 deletions(-) delete mode 100644 authority/mgmt/api/authConfig.go diff --git a/authority/authority.go b/authority/authority.go index c564f530..3e089397 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -11,6 +11,7 @@ import ( "time" "github.com/smallstep/certificates/cas" + "github.com/smallstep/certificates/linkedca" "github.com/pkg/errors" "github.com/smallstep/certificates/authority/admin" @@ -129,15 +130,18 @@ func NewEmbedded(opts ...Option) (*Authority, error) { return a, nil } -func (a *Authority) ReloadAuthConfig() error { - mgmtAuthConfig, err := a.adminDB.GetAuthConfig(context.Background(), mgmt.DefaultAuthorityID) +func (a *Authority) ReloadAuthConfig(ctx context.Context) error { + provs, err := a.adminDB.GetProvisioners(ctx) if err != nil { - return mgmt.WrapErrorISE(err, "error getting authConfig from db") + return mgmt.WrapErrorISE(err, "error getting provisioners to initialize authority") } - - a.config.AuthorityConfig, err = mgmtAuthConfig.ToCertificates() + a.config.AuthorityConfig.Provisioners, err = provisionerListToCertificates(provs) + if err != nil { + return mgmt.WrapErrorISE(err, "error converting provisioner list to certificates") + } + a.config.AuthorityConfig.Admins, err = a.adminDB.GetAdmins(ctx) if err != nil { - return mgmt.WrapErrorISE(err, "error converting mgmt authConfig to certificates authConfig") + return mgmt.WrapErrorISE(err, "error getting provisioners to initialize authority") } // Merge global and configuration claims @@ -148,7 +152,7 @@ func (a *Authority) ReloadAuthConfig() error { // TODO: should we also be combining the ssh federated roots here? // If we rotate ssh roots keys, sshpop provisioner will lose ability to // validate old SSH certificates, unless they are added as federated certs. - sshKeys, err := a.GetSSHRoots(context.Background()) + sshKeys, err := a.GetSSHRoots(ctx) if err != nil { return err } @@ -201,30 +205,52 @@ func (a *Authority) init() error { } } - // Initialize step-ca Admin Database if it's not already initialized using - // WithAdminDB. - if a.adminDB == nil { - // Check if AuthConfig already exists - a.adminDB, err = authMgmtNosql.New(a.db.(nosql.DB), mgmt.DefaultAuthorityID) - if err != nil { - return err - } - mgmtAuthConfig, err := a.adminDB.GetAuthConfig(context.Background(), mgmt.DefaultAuthorityID) - if err != nil { - if k, ok := err.(*mgmt.Error); ok && k.IsType(mgmt.ErrorNotFoundType) { - mgmtAuthConfig, err = mgmt.CreateAuthority(context.Background(), a.adminDB, mgmt.WithDefaultAuthorityID) - if err != nil { - return mgmt.WrapErrorISE(err, "error creating authConfig") - } - } else { - return mgmt.WrapErrorISE(err, "error getting authConfig from db") + if len(a.config.AuthorityConfig.Provisioners) == 0 { + // Initialize step-ca Admin Database if it's not already initialized using + // WithAdminDB. + if a.adminDB == nil { + // Check if AuthConfig already exists + a.adminDB, err = authMgmtNosql.New(a.db.(nosql.DB), mgmt.DefaultAuthorityID) + if err != nil { + return err } } - a.config.AuthorityConfig, err = mgmtAuthConfig.ToCertificates() + provs, err := a.adminDB.GetProvisioners(context.Background()) if err != nil { return err } + if len(provs) == 0 { + // Create First Provisioner + prov, err := mgmt.CreateFirstProvisioner(context.Background(), a.adminDB, a.config.Password) + if err != nil { + return err + } + // Create First Admin + adm := &linkedca.Admin{ + ProvisionerId: prov.Id, + Subject: "step", + Type: linkedca.Admin_SUPER_ADMIN, + } + if err := a.adminDB.CreateAdmin(context.Background(), adm); err != nil { + // TODO should we try to clean up? + return mgmt.WrapErrorISE(err, "error creating first admin") + } + a.config.AuthorityConfig.Admins = []*linkedca.Admin{adm} + } else { + provs, err := a.adminDB.GetProvisioners(context.Background()) + if err != nil { + return mgmt.WrapErrorISE(err, "error getting provisioners to initialize authority") + } + a.config.AuthorityConfig.Provisioners, err = provisionerListToCertificates(provs) + if err != nil { + return mgmt.WrapErrorISE(err, "error converting provisioner list to certificates") + } + a.config.AuthorityConfig.Admins, err = a.adminDB.GetAdmins(context.Background()) + if err != nil { + return mgmt.WrapErrorISE(err, "error getting provisioners to initialize authority") + } + } } // Initialize key manager if it has not been set in the options. diff --git a/authority/mgmt/api/admin.go b/authority/mgmt/api/admin.go index 04ff2a93..30c78555 100644 --- a/authority/mgmt/api/admin.go +++ b/authority/mgmt/api/admin.go @@ -8,14 +8,14 @@ import ( "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/authority/mgmt" - "github.com/smallstep/certificates/authority/status" + "github.com/smallstep/certificates/linkedca" ) // CreateAdminRequest represents the body for a CreateAdmin request. type CreateAdminRequest struct { - Subject string `json:"subject"` - Provisioner string `json:"provisioner"` - Type admin.Type `json:"type"` + Subject string `json:"subject"` + Provisioner string `json:"provisioner"` + Type linkedca.Admin_Type `json:"type"` } // Validate validates a new-admin request body. @@ -29,13 +29,13 @@ func (car *CreateAdminRequest) Validate(c *admin.Collection) error { // GetAdminsResponse for returning a list of admins. type GetAdminsResponse struct { - Admins []*admin.Admin `json:"admins"` - NextCursor string `json:"nextCursor"` + Admins []*linkedca.Admin `json:"admins"` + NextCursor string `json:"nextCursor"` } // UpdateAdminRequest represents the body for a UpdateAdmin request. type UpdateAdminRequest struct { - Type admin.Type `json:"type"` + Type linkedca.Admin_Type `json:"type"` } // Validate validates a new-admin request body. @@ -98,20 +98,17 @@ func (h *Handler) CreateAdmin(w http.ResponseWriter, r *http.Request) { return } - adm := &mgmt.Admin{ - ProvisionerID: p.GetID(), + adm := &linkedca.Admin{ + ProvisionerId: p.GetID(), Subject: body.Subject, Type: body.Type, - Status: status.Active, } if err := h.db.CreateAdmin(ctx, adm); err != nil { api.WriteError(w, mgmt.WrapErrorISE(err, "error creating admin")) return } - adm.ProvisionerName = p.GetName() - adm.ProvisionerType = p.GetType().String() api.JSON(w, adm) - if err := h.auth.ReloadAuthConfig(); err != nil { + if err := h.auth.ReloadAuthConfig(ctx); err != nil { fmt.Printf("err = %+v\n", err) } } @@ -126,18 +123,13 @@ func (h *Handler) DeleteAdmin(w http.ResponseWriter, r *http.Request) { } ctx := r.Context() - adm, err := h.db.GetAdmin(ctx, id) - if err != nil { - api.WriteError(w, mgmt.WrapErrorISE(err, "error retrieiving admin %s", id)) - return - } - adm.Status = status.Deleted - if err := h.db.UpdateAdmin(ctx, adm); err != nil { - api.WriteError(w, mgmt.WrapErrorISE(err, "error updating admin %s", id)) + if err := h.db.DeleteAdmin(ctx, id); err != nil { + api.WriteError(w, mgmt.WrapErrorISE(err, "error deleting admin %s", id)) return } api.JSON(w, &DeleteResponse{Status: "ok"}) - if err := h.auth.ReloadAuthConfig(); err != nil { + + if err := h.auth.ReloadAuthConfig(ctx); err != nil { fmt.Printf("err = %+v\n", err) } } @@ -166,12 +158,12 @@ func (h *Handler) UpdateAdmin(w http.ResponseWriter, r *http.Request) { adm.Type = body.Type - if err := h.db.UpdateAdmin(ctx, (*mgmt.Admin)(adm)); err != nil { + if err := h.db.UpdateAdmin(ctx, (*linkedca.Admin)(adm)); err != nil { api.WriteError(w, mgmt.WrapErrorISE(err, "error updating admin %s", id)) return } api.JSON(w, adm) - if err := h.auth.ReloadAuthConfig(); err != nil { + if err := h.auth.ReloadAuthConfig(ctx); err != nil { fmt.Printf("err = %+v\n", err) } } diff --git a/authority/mgmt/api/authConfig.go b/authority/mgmt/api/authConfig.go deleted file mode 100644 index 7d5f7afb..00000000 --- a/authority/mgmt/api/authConfig.go +++ /dev/null @@ -1,87 +0,0 @@ -package api - -import ( - "net/http" - - "github.com/go-chi/chi" - "github.com/smallstep/certificates/api" - "github.com/smallstep/certificates/authority/config" - "github.com/smallstep/certificates/authority/mgmt" -) - -// CreateAuthConfigRequest represents the body for a CreateAuthConfig request. -type CreateAuthConfigRequest struct { - ASN1DN *config.ASN1DN `json:"asn1dn,omitempty"` - Claims *mgmt.Claims `json:"claims,omitempty"` - Backdate string `json:"backdate,omitempty"` -} - -// Validate validates a CreateAuthConfig request body. -func (car *CreateAuthConfigRequest) Validate() error { - return nil -} - -// UpdateAuthConfigRequest represents the body for a UpdateAuthConfig request. -type UpdateAuthConfigRequest struct { - ASN1DN *config.ASN1DN `json:"asn1dn"` - Claims *mgmt.Claims `json:"claims"` - Backdate string `json:"backdate,omitempty"` -} - -// Validate validates a new-admin request body. -func (uar *UpdateAuthConfigRequest) Validate() error { - return nil -} - -// GetAuthConfig returns the requested admin, or an error. -func (h *Handler) GetAuthConfig(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - id := chi.URLParam(r, "id") - - ac, err := h.db.GetAuthConfig(ctx, id) - if err != nil { - api.WriteError(w, err) - return - } - api.JSON(w, ac) -} - -// UpdateAuthConfig updates an existing AuthConfig. -func (h *Handler) UpdateAuthConfig(w http.ResponseWriter, r *http.Request) { - /* - ctx := r.Context() - id := chi.URLParam(r, "id") - - var body UpdateAuthConfigRequest - if err := api.ReadJSON(r.Body, &body); err != nil { - api.WriteError(w, err) - return - } - if err := body.Validate(); err != nil { - api.WriteError(w, err) - return - } - ac, err := h.db.GetAuthConfig(ctx, id) - if err != nil { - api.WriteError(w, err) - return - } - - ac.Status = body.Status - if body.ASN1DN != nil { - ac.ASN1DN = body.ASN1DN - } - if body.Claims != nil { - ac.Claims = body.Claims - } - if body.Backdate != "" { - ac.Backdate = body.Backdate - } - - if err := h.db.UpdateAuthConfig(ctx, ac); err != nil { - api.WriteError(w, err) - return - } - api.JSON(w, ac) - */ -} diff --git a/authority/mgmt/api/handler.go b/authority/mgmt/api/handler.go index 8b6f915b..ad8b35ad 100644 --- a/authority/mgmt/api/handler.go +++ b/authority/mgmt/api/handler.go @@ -44,8 +44,4 @@ func (h *Handler) Route(r api.Router) { r.MethodFunc("POST", "/admins", h.CreateAdmin) r.MethodFunc("PATCH", "/admins/{id}", h.UpdateAdmin) r.MethodFunc("DELETE", "/admins/{id}", h.DeleteAdmin) - - // AuthConfig - r.MethodFunc("GET", "/authconfigs/{id}", h.GetAuthConfig) - r.MethodFunc("PUT", "/authconfigs/{id}", h.UpdateAuthConfig) } diff --git a/authority/mgmt/api/provisioner.go b/authority/mgmt/api/provisioner.go index 3697d6e0..7964d36b 100644 --- a/authority/mgmt/api/provisioner.go +++ b/authority/mgmt/api/provisioner.go @@ -8,20 +8,20 @@ import ( "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority/mgmt" "github.com/smallstep/certificates/authority/provisioner" - "github.com/smallstep/certificates/authority/status" "github.com/smallstep/certificates/errs" + "github.com/smallstep/certificates/linkedca" ) // 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 []byte `json:"details"` - X509Template string `json:"x509Template"` - X509TemplateData []byte `json:"x509TemplateData"` - SSHTemplate string `json:"sshTemplate"` - SSHTemplateData []byte `json:"sshTemplateData"` + Type string `json:"type"` + Name string `json:"name"` + Claims *linkedca.Claims `json:"claims"` + Details []byte `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. @@ -40,14 +40,14 @@ type GetProvisionersResponse struct { // UpdateProvisionerRequest represents the body for a UpdateProvisioner request. type UpdateProvisionerRequest struct { - 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"` + Type string `json:"type"` + Name string `json:"name"` + Claims *linkedca.Claims `json:"claims"` + Details []byte `json:"details"` + X509Template string `json:"x509Template"` + X509TemplateData []byte `json:"x509TemplateData"` + SSHTemplate string `json:"sshTemplate"` + SSHTemplateData []byte `json:"sshTemplateData"` } // Validate validates a update-provisioner request body. @@ -101,7 +101,7 @@ func (h *Handler) GetProvisioners(w http.ResponseWriter, r *http.Request) { func (h *Handler) CreateProvisioner(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - var prov = new(mgmt.Provisioner) + var prov = new(linkedca.Provisioner) if err := api.ReadJSON(r.Body, prov); err != nil { api.WriteError(w, err) return @@ -118,7 +118,7 @@ func (h *Handler) CreateProvisioner(w http.ResponseWriter, r *http.Request) { } api.JSONStatus(w, prov, http.StatusCreated) - if err := h.auth.ReloadAuthConfig(); err != nil { + if err := h.auth.ReloadAuthConfig(ctx); err != nil { fmt.Printf("err = %+v\n", err) } } @@ -141,14 +141,8 @@ func (h *Handler) DeleteProvisioner(w http.ResponseWriter, r *http.Request) { } ctx := r.Context() - prov, err := h.db.GetProvisioner(ctx, p.GetID()) - if err != nil { - api.WriteError(w, mgmt.WrapErrorISE(err, "error loading provisioner %s from db", name)) - return - } - prov.Status = status.Deleted - if err := h.db.UpdateProvisioner(ctx, prov); err != nil { - api.WriteError(w, mgmt.WrapErrorISE(err, "error updating provisioner %s", name)) + if err := h.db.DeleteProvisioner(ctx, p.GetID()); err != nil { + api.WriteError(w, mgmt.WrapErrorISE(err, "error deleting provisioner %s", name)) return } @@ -156,13 +150,7 @@ func (h *Handler) DeleteProvisioner(w http.ResponseWriter, r *http.Request) { admins, ok := c.LoadByProvisioner(name) if ok { for _, adm := range admins { - if err := h.db.UpdateAdmin(ctx, &mgmt.Admin{ - ID: adm.ID, - ProvisionerID: adm.ProvisionerID, - Subject: adm.Subject, - Type: adm.Type, - Status: status.Deleted, - }); err != nil { + if err := h.db.DeleteAdmin(ctx, adm.Id); err != nil { api.WriteError(w, mgmt.WrapErrorISE(err, "error deleting admin %s, as part of provisioner %s deletion", adm.Subject, name)) return } @@ -171,7 +159,7 @@ func (h *Handler) DeleteProvisioner(w http.ResponseWriter, r *http.Request) { api.JSON(w, &DeleteResponse{Status: "ok"}) - if err := h.auth.ReloadAuthConfig(); err != nil { + if err := h.auth.ReloadAuthConfig(ctx); err != nil { fmt.Printf("err = %+v\n", err) } } diff --git a/authority/mgmt/config.go b/authority/mgmt/config.go index ba821fc7..3c322415 100644 --- a/authority/mgmt/config.go +++ b/authority/mgmt/config.go @@ -1,10 +1,5 @@ package mgmt -import ( - "github.com/smallstep/certificates/authority/config" - "github.com/smallstep/certificates/linkedca" -) - const ( // DefaultAuthorityID is the default AuthorityID. This will be the ID // of the first Authority created, as well as the default AuthorityID @@ -12,31 +7,6 @@ const ( DefaultAuthorityID = "00000000-0000-0000-0000-000000000000" ) -func NewDefaultClaims() *linkedca.Claims { - return &linkedca.Claims{ - X509: &linkedca.X509Claims{ - Durations: &linkedca.Durations{ - Min: config.GlobalProvisionerClaims.MinTLSDur.String(), - Max: config.GlobalProvisionerClaims.MaxTLSDur.String(), - Default: config.GlobalProvisionerClaims.DefaultTLSDur.String(), - }, - }, - Ssh: &linkedca.SSHClaims{ - UserDurations: &linkedca.Durations{ - Min: config.GlobalProvisionerClaims.MinUserSSHDur.String(), - Max: config.GlobalProvisionerClaims.MaxUserSSHDur.String(), - Default: config.GlobalProvisionerClaims.DefaultUserSSHDur.String(), - }, - HostDurations: &linkedca.Durations{ - Min: config.GlobalProvisionerClaims.MinHostSSHDur.String(), - Max: config.GlobalProvisionerClaims.MaxHostSSHDur.String(), - Default: config.GlobalProvisionerClaims.DefaultHostSSHDur.String(), - }, - }, - DisableRenewal: config.DefaultDisableRenewal, - } -} - /* func CreateAuthority(ctx context.Context, db DB, options ...AuthorityOption) (*AuthConfig, error) { ac := NewDefaultAuthConfig() diff --git a/authority/mgmt/db/nosql/provisioner.go b/authority/mgmt/db/nosql/provisioner.go index 473736e8..1924aee3 100644 --- a/authority/mgmt/db/nosql/provisioner.go +++ b/authority/mgmt/db/nosql/provisioner.go @@ -204,3 +204,16 @@ func (db *DB) UpdateProvisioner(ctx context.Context, prov *linkedca.Provisioner) return nil } + +// 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", authorityProvisionersTable) +} diff --git a/authority/mgmt/provisioner.go b/authority/mgmt/provisioner.go index 74e442f4..0386a894 100644 --- a/authority/mgmt/provisioner.go +++ b/authority/mgmt/provisioner.go @@ -1,10 +1,9 @@ package mgmt import ( - "encoding/json" - "fmt" + "context" - "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/linkedca" "go.step.sm/crypto/jose" ) @@ -57,192 +56,57 @@ func (p *Provisioner) UnmarshalJSON(b []byte) error { } */ -func provisionerGetOptions(p *linkedca.Provisioner) *provisioner.Options { - return &provisioner.Options{ - X509: &provisioner.X509Options{ - Template: string(p.X509Template), - TemplateData: p.X509TemplateData, +func NewDefaultClaims() *linkedca.Claims { + return &linkedca.Claims{ + X509: &linkedca.X509Claims{ + Durations: &linkedca.Durations{ + Min: config.GlobalProvisionerClaims.MinTLSDur.String(), + Max: config.GlobalProvisionerClaims.MaxTLSDur.String(), + Default: config.GlobalProvisionerClaims.DefaultTLSDur.String(), + }, }, - SSH: &provisioner.SSHOptions{ - Template: string(p.SshTemplate), - TemplateData: p.SshTemplateData, + Ssh: &linkedca.SSHClaims{ + UserDurations: &linkedca.Durations{ + Min: config.GlobalProvisionerClaims.MinUserSSHDur.String(), + Max: config.GlobalProvisionerClaims.MaxUserSSHDur.String(), + Default: config.GlobalProvisionerClaims.DefaultUserSSHDur.String(), + }, + HostDurations: &linkedca.Durations{ + Min: config.GlobalProvisionerClaims.MinHostSSHDur.String(), + Max: config.GlobalProvisionerClaims.MaxHostSSHDur.String(), + Default: config.GlobalProvisionerClaims.DefaultHostSSHDur.String(), + }, }, + DisableRenewal: config.DefaultDisableRenewal, } } -// provisionerToCertificates converts the landlord provisioner type to the open source -// provisioner type. -func provisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface, error) { - claims, err := claimsToCertificates(p.Claims) +func CreateFirstProvisioner(ctx context.Context, db DB, password string) (*linkedca.Provisioner, error) { + jwk, jwe, err := jose.GenerateDefaultKeyPair([]byte(password)) if err != nil { - return nil, err + return nil, WrapErrorISE(err, "error generating JWK key pair") } - details := p.Details.GetData() - if details == nil { - return nil, fmt.Errorf("provisioner does not have any details") + jwkPubBytes, err := jwk.MarshalJSON() + if err != nil { + return nil, WrapErrorISE(err, "error marshaling JWK") } - - switch d := details.(type) { - case *linkedca.ProvisionerDetails_JWK: - jwk := new(jose.JSONWebKey) - if err := json.Unmarshal(d.JWK.PublicKey, &jwk); err != nil { - return nil, err - } - return &provisioner.JWK{ - ID: p.Id, - Type: p.Type.String(), - Name: p.Name, - Key: jwk, - EncryptedKey: string(d.JWK.EncryptedPrivateKey), - Claims: claims, - Options: provisionerGetOptions(p), - }, nil - /* - case *ProvisionerDetails_OIDC: - cfg := d.OIDC - return &provisioner.OIDC{ - Type: p.Type.String(), - Name: p.Name, - TenantID: cfg.TenantId, - ClientID: cfg.ClientId, - ClientSecret: cfg.ClientSecret, - ConfigurationEndpoint: cfg.ConfigurationEndpoint, - Admins: cfg.Admins, - Domains: cfg.Domains, - Groups: cfg.Groups, - ListenAddress: cfg.ListenAddress, - Claims: claims, - Options: options, - }, nil - case *ProvisionerDetails_GCP: - cfg := d.GCP - return &provisioner.GCP{ - Type: p.Type.String(), - Name: p.Name, - ServiceAccounts: cfg.ServiceAccounts, - ProjectIDs: cfg.ProjectIds, - DisableCustomSANs: cfg.DisableCustomSans, - DisableTrustOnFirstUse: cfg.DisableTrustOnFirstUse, - InstanceAge: durationValue(cfg.InstanceAge), - Claims: claims, - Options: options, - }, nil - case *ProvisionerDetails_AWS: - cfg := d.AWS - return &provisioner.AWS{ - Type: p.Type.String(), - Name: p.Name, - Accounts: cfg.Accounts, - DisableCustomSANs: cfg.DisableCustomSans, - DisableTrustOnFirstUse: cfg.DisableTrustOnFirstUse, - InstanceAge: durationValue(cfg.InstanceAge), - Claims: claims, - Options: options, - }, nil - case *ProvisionerDetails_Azure: - cfg := d.Azure - return &provisioner.Azure{ - Type: p.Type.String(), - Name: p.Name, - TenantID: cfg.TenantId, - ResourceGroups: cfg.ResourceGroups, - Audience: cfg.Audience, - DisableCustomSANs: cfg.DisableCustomSans, - DisableTrustOnFirstUse: cfg.DisableTrustOnFirstUse, - Claims: claims, - Options: options, - }, nil - case *ProvisionerDetails_X5C: - var roots []byte - for i, k := range d.X5C.GetRoots() { - if b := k.GetKey().GetPublic(); b != nil { - if i > 0 { - roots = append(roots, '\n') - } - roots = append(roots, b...) - } - } - return &provisioner.X5C{ - Type: p.Type.String(), - Name: p.Name, - Roots: roots, - Claims: claims, - Options: options, - }, nil - case *ProvisionerDetails_K8SSA: - var publicKeys []byte - for i, k := range d.K8SSA.GetPublicKeys() { - if b := k.GetKey().GetPublic(); b != nil { - if i > 0 { - publicKeys = append(publicKeys, '\n') - } - publicKeys = append(publicKeys, k.Key.Public...) - } - } - return &provisioner.K8sSA{ - Type: p.Type.String(), - Name: p.Name, - PubKeys: publicKeys, - Claims: claims, - Options: options, - }, nil - case *ProvisionerDetails_SSHPOP: - return &provisioner.SSHPOP{ - Type: p.Type.String(), - Name: p.Name, - Claims: claims, - }, nil - case *ProvisionerDetails_ACME: - cfg := d.ACME - return &provisioner.ACME{ - Type: p.Type.String(), - Name: p.Name, - ForceCN: cfg.ForceCn, - Claims: claims, - Options: options, - }, nil - */ - default: - return nil, fmt.Errorf("provisioner %s not implemented", p.Type) + jwePrivStr, err := jwe.CompactSerialize() + if err != nil { + return nil, WrapErrorISE(err, "error serializing JWE") } -} -// claimsToCertificates converts the landlord provisioner claims type to the open source -// (step-ca) claims type. -func claimsToCertificates(c *linkedca.Claims) (*provisioner.Claims, error) { - var durs = map[string]struct { - durStr string - dur *provisioner.Duration - }{ - "minTLSDur": {durStr: c.X509.Durations.Min}, - "maxTLSDur": {durStr: c.X509.Durations.Max}, - "defaultTLSDur": {durStr: c.X509.Durations.Default}, - "minSSHUserDur": {durStr: c.Ssh.UserDurations.Min}, - "maxSSHUserDur": {durStr: c.Ssh.UserDurations.Max}, - "defaultSSHUserDur": {durStr: c.Ssh.UserDurations.Default}, - "minSSHHostDur": {durStr: c.Ssh.HostDurations.Min}, - "maxSSHHostDur": {durStr: c.Ssh.HostDurations.Max}, - "defaultSSHHostDur": {durStr: c.Ssh.HostDurations.Default}, - } - var err error - for k, v := range durs { - v.dur, err = provisioner.NewDuration(v.durStr) - if err != nil { - return nil, WrapErrorISE(err, "error parsing %s %s from claims", k, v.durStr) - } - } - return &provisioner.Claims{ - MinTLSDur: durs["minTLSDur"].dur, - MaxTLSDur: durs["maxTLSDur"].dur, - DefaultTLSDur: durs["defaultTLSDur"].dur, - DisableRenewal: &c.DisableRenewal, - MinUserSSHDur: durs["minSSHUserDur"].dur, - MaxUserSSHDur: durs["maxSSHUserDur"].dur, - DefaultUserSSHDur: durs["defaultSSHUserDur"].dur, - MinHostSSHDur: durs["minSSHHostDur"].dur, - MaxHostSSHDur: durs["maxSSHHostDur"].dur, - DefaultHostSSHDur: durs["defaultSSHHostDur"].dur, - EnableSSHCA: &c.Ssh.Enabled, + return &linkedca.Provisioner{ + Name: "Admin JWK", + Type: linkedca.Provisioner_JWK, + Claims: NewDefaultClaims(), + Details: &linkedca.ProvisionerDetails{ + Data: &linkedca.ProvisionerDetails_JWK{ + JWK: &linkedca.JWKProvisioner{ + PublicKey: jwkPubBytes, + EncryptedPrivateKey: []byte(jwePrivStr), + }, + }, + }, }, nil } diff --git a/authority/provisioners.go b/authority/provisioners.go index 99a85d46..599db4bd 100644 --- a/authority/provisioners.go +++ b/authority/provisioners.go @@ -2,9 +2,14 @@ package authority import ( "crypto/x509" + "encoding/json" + "fmt" + "github.com/smallstep/certificates/authority/mgmt" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/errs" + "github.com/smallstep/certificates/linkedca" + "go.step.sm/crypto/jose" ) // GetEncryptedKey returns the JWE key corresponding to the given kid argument. @@ -41,3 +46,205 @@ func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error } return p, nil } + +func provisionerGetOptions(p *linkedca.Provisioner) *provisioner.Options { + return &provisioner.Options{ + X509: &provisioner.X509Options{ + Template: string(p.X509Template), + TemplateData: p.X509TemplateData, + }, + SSH: &provisioner.SSHOptions{ + Template: string(p.SshTemplate), + TemplateData: p.SshTemplateData, + }, + } +} + +func provisionerListToCertificates(l []*linkedca.Provisioner) (provisioner.List, error) { + var nu provisioner.List + for _, p := range l { + certProv, err := provisionerToCertificates(p) + if err != nil { + return nil, err + } + nu = append(nu, certProv) + } + return nu, nil +} + +// provisionerToCertificates converts the landlord provisioner type to the open source +// provisioner type. +func provisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface, error) { + claims, err := claimsToCertificates(p.Claims) + if err != nil { + return nil, err + } + + details := p.Details.GetData() + if details == nil { + return nil, fmt.Errorf("provisioner does not have any details") + } + + switch d := details.(type) { + case *linkedca.ProvisionerDetails_JWK: + jwk := new(jose.JSONWebKey) + if err := json.Unmarshal(d.JWK.PublicKey, &jwk); err != nil { + return nil, err + } + return &provisioner.JWK{ + ID: p.Id, + Type: p.Type.String(), + Name: p.Name, + Key: jwk, + EncryptedKey: string(d.JWK.EncryptedPrivateKey), + Claims: claims, + Options: provisionerGetOptions(p), + }, nil + /* + case *ProvisionerDetails_OIDC: + cfg := d.OIDC + return &provisioner.OIDC{ + Type: p.Type.String(), + Name: p.Name, + TenantID: cfg.TenantId, + ClientID: cfg.ClientId, + ClientSecret: cfg.ClientSecret, + ConfigurationEndpoint: cfg.ConfigurationEndpoint, + Admins: cfg.Admins, + Domains: cfg.Domains, + Groups: cfg.Groups, + ListenAddress: cfg.ListenAddress, + Claims: claims, + Options: options, + }, nil + case *ProvisionerDetails_GCP: + cfg := d.GCP + return &provisioner.GCP{ + Type: p.Type.String(), + Name: p.Name, + ServiceAccounts: cfg.ServiceAccounts, + ProjectIDs: cfg.ProjectIds, + DisableCustomSANs: cfg.DisableCustomSans, + DisableTrustOnFirstUse: cfg.DisableTrustOnFirstUse, + InstanceAge: durationValue(cfg.InstanceAge), + Claims: claims, + Options: options, + }, nil + case *ProvisionerDetails_AWS: + cfg := d.AWS + return &provisioner.AWS{ + Type: p.Type.String(), + Name: p.Name, + Accounts: cfg.Accounts, + DisableCustomSANs: cfg.DisableCustomSans, + DisableTrustOnFirstUse: cfg.DisableTrustOnFirstUse, + InstanceAge: durationValue(cfg.InstanceAge), + Claims: claims, + Options: options, + }, nil + case *ProvisionerDetails_Azure: + cfg := d.Azure + return &provisioner.Azure{ + Type: p.Type.String(), + Name: p.Name, + TenantID: cfg.TenantId, + ResourceGroups: cfg.ResourceGroups, + Audience: cfg.Audience, + DisableCustomSANs: cfg.DisableCustomSans, + DisableTrustOnFirstUse: cfg.DisableTrustOnFirstUse, + Claims: claims, + Options: options, + }, nil + case *ProvisionerDetails_X5C: + var roots []byte + for i, k := range d.X5C.GetRoots() { + if b := k.GetKey().GetPublic(); b != nil { + if i > 0 { + roots = append(roots, '\n') + } + roots = append(roots, b...) + } + } + return &provisioner.X5C{ + Type: p.Type.String(), + Name: p.Name, + Roots: roots, + Claims: claims, + Options: options, + }, nil + case *ProvisionerDetails_K8SSA: + var publicKeys []byte + for i, k := range d.K8SSA.GetPublicKeys() { + if b := k.GetKey().GetPublic(); b != nil { + if i > 0 { + publicKeys = append(publicKeys, '\n') + } + publicKeys = append(publicKeys, k.Key.Public...) + } + } + return &provisioner.K8sSA{ + Type: p.Type.String(), + Name: p.Name, + PubKeys: publicKeys, + Claims: claims, + Options: options, + }, nil + case *ProvisionerDetails_SSHPOP: + return &provisioner.SSHPOP{ + Type: p.Type.String(), + Name: p.Name, + Claims: claims, + }, nil + case *ProvisionerDetails_ACME: + cfg := d.ACME + return &provisioner.ACME{ + Type: p.Type.String(), + Name: p.Name, + ForceCN: cfg.ForceCn, + Claims: claims, + Options: options, + }, nil + */ + default: + return nil, fmt.Errorf("provisioner %s not implemented", p.Type) + } +} + +// claimsToCertificates converts the landlord provisioner claims type to the open source +// (step-ca) claims type. +func claimsToCertificates(c *linkedca.Claims) (*provisioner.Claims, error) { + var durs = map[string]struct { + durStr string + dur *provisioner.Duration + }{ + "minTLSDur": {durStr: c.X509.Durations.Min}, + "maxTLSDur": {durStr: c.X509.Durations.Max}, + "defaultTLSDur": {durStr: c.X509.Durations.Default}, + "minSSHUserDur": {durStr: c.Ssh.UserDurations.Min}, + "maxSSHUserDur": {durStr: c.Ssh.UserDurations.Max}, + "defaultSSHUserDur": {durStr: c.Ssh.UserDurations.Default}, + "minSSHHostDur": {durStr: c.Ssh.HostDurations.Min}, + "maxSSHHostDur": {durStr: c.Ssh.HostDurations.Max}, + "defaultSSHHostDur": {durStr: c.Ssh.HostDurations.Default}, + } + var err error + for k, v := range durs { + v.dur, err = provisioner.NewDuration(v.durStr) + if err != nil { + return nil, mgmt.WrapErrorISE(err, "error parsing %s %s from claims", k, v.durStr) + } + } + return &provisioner.Claims{ + MinTLSDur: durs["minTLSDur"].dur, + MaxTLSDur: durs["maxTLSDur"].dur, + DefaultTLSDur: durs["defaultTLSDur"].dur, + DisableRenewal: &c.DisableRenewal, + MinUserSSHDur: durs["minSSHUserDur"].dur, + MaxUserSSHDur: durs["maxSSHUserDur"].dur, + DefaultUserSSHDur: durs["defaultSSHUserDur"].dur, + MinHostSSHDur: durs["minSSHHostDur"].dur, + MaxHostSSHDur: durs["maxSSHHostDur"].dur, + DefaultHostSSHDur: durs["defaultSSHHostDur"].dur, + EnableSSHCA: &c.Ssh.Enabled, + }, nil +} diff --git a/ca/adminClient.go b/ca/adminClient.go index e8374a98..32d6339d 100644 --- a/ca/adminClient.go +++ b/ca/adminClient.go @@ -10,12 +10,14 @@ import ( "strconv" "github.com/pkg/errors" - "github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/authority/mgmt" mgmtAPI "github.com/smallstep/certificates/authority/mgmt/api" "github.com/smallstep/certificates/errs" + "github.com/smallstep/certificates/linkedca" ) +var adminURLPrefix = "admin" + // AdminClient implements an HTTP client for the CA server. type AdminClient struct { client *uaClient @@ -68,9 +70,9 @@ func (c *AdminClient) retryOnError(r *http.Response) bool { } // GetAdmin performs the GET /admin/admin/{id} request to the CA. -func (c *AdminClient) GetAdmin(id string) (*mgmt.Admin, error) { +func (c *AdminClient) GetAdmin(id string) (*linkedca.Admin, error) { var retried bool - u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/admin/admin", id)}) + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "admins", id)}) retry: resp, err := c.client.Get(u.String()) if err != nil { @@ -83,7 +85,7 @@ retry: } return nil, readAdminError(resp.Body) } - var adm = new(mgmt.Admin) + var adm = new(linkedca.Admin) if err := readJSON(resp.Body, adm); err != nil { return nil, errors.Wrapf(err, "error reading %s", u) } @@ -165,7 +167,7 @@ retry: } // CreateAdmin performs the POST /admin/admins request to the CA. -func (c *AdminClient) CreateAdmin(req *mgmtAPI.CreateAdminRequest) (*mgmt.Admin, error) { +func (c *AdminClient) CreateAdmin(req *mgmtAPI.CreateAdminRequest) (*linkedca.Admin, error) { var retried bool body, err := json.Marshal(req) if err != nil { @@ -184,7 +186,7 @@ retry: } return nil, readAdminError(resp.Body) } - var adm = new(mgmt.Admin) + var adm = new(linkedca.Admin) if err := readJSON(resp.Body, adm); err != nil { return nil, errors.Wrapf(err, "error reading %s", u) } @@ -194,7 +196,7 @@ retry: // RemoveAdmin performs the DELETE /admin/admins/{id} request to the CA. func (c *AdminClient) RemoveAdmin(id string) error { var retried bool - u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/admin/admins", id)}) + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "admins", id)}) req, err := http.NewRequest("DELETE", u.String(), nil) if err != nil { return errors.Wrapf(err, "create DELETE %s request failed", u) @@ -215,13 +217,13 @@ retry: } // UpdateAdmin performs the PUT /admin/admins/{id} request to the CA. -func (c *AdminClient) UpdateAdmin(id string, uar *mgmtAPI.UpdateAdminRequest) (*admin.Admin, error) { +func (c *AdminClient) UpdateAdmin(id string, uar *mgmtAPI.UpdateAdminRequest) (*linkedca.Admin, error) { var retried bool body, err := json.Marshal(uar) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request") } - u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/admin/admins", id)}) + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "admins", id)}) req, err := http.NewRequest("PATCH", u.String(), bytes.NewReader(body)) if err != nil { return nil, errors.Wrapf(err, "create PUT %s request failed", u) @@ -238,7 +240,7 @@ retry: } return nil, readAdminError(resp.Body) } - var adm = new(admin.Admin) + var adm = new(linkedca.Admin) if err := readJSON(resp.Body, adm); err != nil { return nil, errors.Wrapf(err, "error reading %s", u) } @@ -246,9 +248,9 @@ retry: } // GetProvisioner performs the GET /admin/provisioners/{name} request to the CA. -func (c *AdminClient) GetProvisioner(name string) (*mgmt.Provisioner, error) { +func (c *AdminClient) GetProvisioner(name string) (*linkedca.Provisioner, error) { var retried bool - u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/admin/provisioners", name)}) + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", name)}) retry: resp, err := c.client.Get(u.String()) if err != nil { @@ -261,7 +263,7 @@ retry: } return nil, readAdminError(resp.Body) } - var prov = new(mgmt.Provisioner) + var prov = new(linkedca.Provisioner) if err := readJSON(resp.Body, prov); err != nil { return nil, errors.Wrapf(err, "error reading %s", u) } @@ -269,7 +271,7 @@ retry: } // GetProvisioners performs the GET /admin/provisioners request to the CA. -func (c *AdminClient) GetProvisioners() ([]*mgmt.Provisioner, error) { +func (c *AdminClient) GetProvisioners() ([]*linkedca.Provisioner, error) { var retried bool u := c.endpoint.ResolveReference(&url.URL{Path: "/admin/provisioners"}) retry: @@ -284,7 +286,7 @@ retry: } return nil, readAdminError(resp.Body) } - var provs = new([]*mgmt.Provisioner) + var provs = new([]*linkedca.Provisioner) if err := readJSON(resp.Body, provs); err != nil { return nil, errors.Wrapf(err, "error reading %s", u) } @@ -294,7 +296,7 @@ retry: // RemoveProvisioner performs the DELETE /admin/provisioners/{name} request to the CA. func (c *AdminClient) RemoveProvisioner(name string) error { var retried bool - u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/admin/provisioners", name)}) + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", name)}) req, err := http.NewRequest("DELETE", u.String(), nil) if err != nil { return errors.Wrapf(err, "create DELETE %s request failed", u) @@ -315,7 +317,7 @@ retry: } // CreateProvisioner performs the POST /admin/provisioners request to the CA. -func (c *AdminClient) CreateProvisioner(prov *mgmt.Provisioner) (*mgmt.Provisioner, error) { +func (c *AdminClient) CreateProvisioner(prov *linkedca.Provisioner) (*linkedca.Provisioner, error) { var retried bool body, err := json.Marshal(prov) if err != nil { @@ -334,7 +336,7 @@ retry: } return nil, readAdminError(resp.Body) } - var nuProv = new(mgmt.Provisioner) + var nuProv = new(linkedca.Provisioner) if err := readJSON(resp.Body, nuProv); err != nil { return nil, errors.Wrapf(err, "error reading %s", u) } @@ -342,13 +344,13 @@ retry: } // UpdateProvisioner performs the PUT /admin/provisioners/{id} request to the CA. -func (c *AdminClient) UpdateProvisioner(id string, upr *mgmtAPI.UpdateProvisionerRequest) (*mgmt.Provisioner, error) { +func (c *AdminClient) UpdateProvisioner(id string, upr *mgmtAPI.UpdateProvisionerRequest) (*linkedca.Provisioner, error) { var retried bool body, err := json.Marshal(upr) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request") } - u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/admin/provisioners", id)}) + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", id)}) req, err := http.NewRequest("PUT", u.String(), bytes.NewReader(body)) if err != nil { return nil, errors.Wrapf(err, "create PUT %s request failed", u) @@ -365,36 +367,13 @@ retry: } return nil, readAdminError(resp.Body) } - var prov = new(mgmt.Provisioner) + var prov = new(linkedca.Provisioner) if err := readJSON(resp.Body, prov); err != nil { return nil, errors.Wrapf(err, "error reading %s", u) } return prov, nil } -// GetAuthConfig performs the GET /admin/authconfig/{id} request to the CA. -func (c *AdminClient) GetAuthConfig(id string) (*mgmt.AuthConfig, error) { - var retried bool - u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/admin/authconfig", id)}) -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, readAdminError(resp.Body) - } - var ac = new(mgmt.AuthConfig) - if err := readJSON(resp.Body, ac); err != nil { - return nil, errors.Wrapf(err, "error reading %s", u) - } - return ac, nil -} - func readAdminError(r io.ReadCloser) error { defer r.Close() mgmtErr := new(mgmt.Error)