Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ go.work.sum

# Output of the go coverage tool, specifically when used with LiteIDE
*.out
.gocache

# Others
*.swp
Expand Down
22 changes: 16 additions & 6 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,22 +257,32 @@ func scepFromProvisioner(p *provisioner.SCEP) *models.SCEP {
}
}

func estFromProvisioner(p *provisioner.EST) *provisioner.EST {
prov := *p
prov.ClientCertificateRoots = []byte(redacted)
prov.BasicAuthUsername = redacted
prov.BasicAuthPassword = redacted
return &prov
}

// MarshalJSON implements json.Marshaler. It marshals the ProvisionersResponse
// into a byte slice.
//
// Special treatment is given to the SCEP provisioner, as it contains a
// challenge secret that MUST NOT be leaked in (public) HTTP responses. The
// challenge value is thus redacted in HTTP responses.
// challenge value is thus redacted in HTTP responses. EST provisioners also
// contain a shared secret and are redacted in responses.
func (p ProvisionersResponse) MarshalJSON() ([]byte, error) {
var responseProvisioners provisioner.List
for _, item := range p.Provisioners {
scepProv, ok := item.(*provisioner.SCEP)
if !ok {
switch prov := item.(type) {
case *provisioner.SCEP:
responseProvisioners = append(responseProvisioners, scepFromProvisioner(prov))
case *provisioner.EST:
responseProvisioners = append(responseProvisioners, estFromProvisioner(prov))
default:
responseProvisioners = append(responseProvisioners, item)
continue
}

responseProvisioners = append(responseProvisioners, scepFromProvisioner(scepProv))
}

var list = struct {
Expand Down
2 changes: 2 additions & 0 deletions authority/admin/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ func UnmarshalProvisionerDetails(typ linkedca.Provisioner_Type, data []byte) (*l
v.Data = new(linkedca.ProvisionerDetails_SCEP)
case linkedca.Provisioner_NEBULA:
v.Data = new(linkedca.ProvisionerDetails_Nebula)
case linkedca.Provisioner_EST:
v.Data = new(linkedca.ProvisionerDetails_EST)
default:
return nil, fmt.Errorf("unsupported provisioner type %s", typ)
}
Expand Down
82 changes: 79 additions & 3 deletions authority/authority.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/smallstep/certificates/cas"
casapi "github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/est"
"github.com/smallstep/certificates/internal/httptransport"
"github.com/smallstep/certificates/scep"
"github.com/smallstep/certificates/templates"
Expand Down Expand Up @@ -70,6 +71,11 @@ type Authority struct {
scepAuthority *scep.Authority
scepKeyManager provisioner.SCEPKeyManager

// EST CA
estOptions *est.Options
validateEST bool
estAuthority *est.Authority

// SSH CA
sshHostPassword []byte
sshUserPassword []byte
Expand Down Expand Up @@ -133,6 +139,7 @@ func New(cfg *config.Config, opts ...Option) (*Authority, error) {
config: cfg,
certificates: new(sync.Map),
validateSCEP: true,
validateEST: true,
meter: noopMeter{},
wrapTransport: httptransport.NoopWrapper(),
}
Expand Down Expand Up @@ -170,6 +177,7 @@ func NewEmbedded(opts ...Option) (*Authority, error) {
certificates: new(sync.Map),
meter: noopMeter{},
wrapTransport: httptransport.NoopWrapper(),
validateEST: true,
}

// Apply options.
Expand Down Expand Up @@ -314,6 +322,11 @@ func (a *Authority) ReloadAdminResources(ctx context.Context) error {
// TODO(hs): don't remove the authority if we can't also
// reload it.
//a.scepAuthority = nil
case a.requiresEST() && a.GetEST() != nil:
a.estAuthority.UpdateProvisioners(a.getESTProvisionerNames())
if err := a.estAuthority.Validate(); err != nil {
log.Printf("failed validating EST authority: %v\n", err)
}
}

return nil
Expand Down Expand Up @@ -814,6 +827,51 @@ func (a *Authority) init() error {
}
}

// EST functionality is provided through an instance of est.Authority.
switch {
case a.requiresEST() && a.GetEST() == nil:
if a.estOptions == nil {
options := &est.Options{
Roots: a.rootX509Certs,
Intermediates: a.intermediateX509Certs,
}
if len(a.intermediateX509Certs) > 0 {
options.SignerCert = a.intermediateX509Certs[0]
}
if a.config.IntermediateKey != "" {
if signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey,
Password: a.password,
}); err == nil {
options.Signer = signer
}
}
a.estOptions = options
}

a.estOptions.ESTProvisionerNames = a.getESTProvisionerNames()

estAuthority, err := est.New(a, *a.estOptions)
if err != nil {
return err
}

if a.validateEST {
if err := estAuthority.Validate(); err != nil {
a.initLogf("failed validating EST authority: %v", err)
}
}

a.estAuthority = estAuthority
case !a.requiresEST() && a.GetEST() != nil:
a.estAuthority = nil
case a.requiresEST() && a.GetEST() != nil:
a.estAuthority.UpdateProvisioners(a.getESTProvisionerNames())
if err := a.estAuthority.Validate(); err != nil {
log.Printf("failed validating EST authority: %v\n", err)
}
}

// Load X509 constraints engine.
//
// This is currently only available in CA mode.
Expand Down Expand Up @@ -1002,16 +1060,34 @@ func (a *Authority) GetSCEP() *scep.Authority {
return a.scepAuthority
}

// HasACMEProvisioner returns true if at least one ACME provisioner is configured.
func (a *Authority) HasACMEProvisioner() bool {
// requiresEST iterates over the configured provisioners
// and determines if at least one of them is an EST provisioner.
func (a *Authority) requiresEST() bool {
for _, p := range a.config.AuthorityConfig.Provisioners {
if p.GetType() == provisioner.TypeACME {
if p.GetType() == provisioner.TypeEST {
return true
}
}
return false
}

// getESTProvisionerNames returns the names of the EST provisioners
// that are currently available in the CA.
func (a *Authority) getESTProvisionerNames() (names []string) {
for _, p := range a.config.AuthorityConfig.Provisioners {
if p.GetType() == provisioner.TypeEST {
names = append(names, p.GetName())
}
}

return
}

// GetEST returns the configured EST Authority
func (a *Authority) GetEST() *est.Authority {
return a.estAuthority
}

func (a *Authority) startCRLGenerator() error {
if !a.config.CRL.IsEnabled() {
return nil
Expand Down
12 changes: 12 additions & 0 deletions authority/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
casapi "github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/internal/httptransport"
"github.com/smallstep/certificates/est"
"github.com/smallstep/certificates/scep"
)

Expand Down Expand Up @@ -242,6 +243,17 @@ func WithFullSCEPOptions(options *scep.Options) Option {
}
}

// WithFullESTOptions defines the options used for EST support.
//
// This feature is EXPERIMENTAL and might change at any time.
func WithFullESTOptions(options *est.Options) Option {
return func(a *Authority) error {
a.estOptions = options
a.validateEST = false
return nil
}
}

// WithSCEPKeyManager defines the key manager used on SCEP provisioners.
//
// This feature is EXPERIMENTAL and might change at any time.
Expand Down
Loading