This commit is contained in:
max furman 2021-05-25 21:13:01 -07:00
parent 1726076ea2
commit 01a4460812
10 changed files with 377 additions and 429 deletions

View file

@ -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 mgmt authConfig to certificates authConfig")
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 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.

View file

@ -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)
}
}

View file

@ -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)
*/
}

View file

@ -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)
}

View file

@ -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)
}
}

View file

@ -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()

View file

@ -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)
}

View file

@ -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")
}
jwePrivStr, err := jwe.CompactSerialize()
if err != nil {
return nil, WrapErrorISE(err, "error serializing JWE")
}
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, 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
}

View file

@ -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
}

View file

@ -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)