From 7b5d6968a5e4b7ded7d8f1f30e0c6dba62ab6e2e Mon Sep 17 00:00:00 2001 From: max furman Date: Mon, 3 May 2021 12:48:20 -0700 Subject: [PATCH 01/89] first commit --- acme/db/nosql/nosql.go | 7 +- api/api.go | 3 +- api/sign.go | 12 +- api/ssh.go | 15 +- authority/admin.go | 12 + authority/authority.go | 41 ++- authority/{ => config}/config.go | 26 +- authority/{ => config}/config_test.go | 2 +- authority/config/ssh.go | 94 ++++++ authority/{ => config}/tls_options.go | 2 +- authority/{ => config}/tls_options_test.go | 2 +- authority/{ => config}/types.go | 2 +- authority/{ => config}/types_test.go | 2 +- authority/mgmt/api/admin.go | 112 +++++++ authority/mgmt/api/authConfig.go | 121 +++++++ authority/mgmt/api/handler.go | 50 +++ authority/mgmt/api/provisioner.go | 122 +++++++ authority/mgmt/config.go | 357 +++++++++++++++++++++ authority/mgmt/db.go | 149 +++++++++ authority/mgmt/db/nosql/admin.go | 163 ++++++++++ authority/mgmt/db/nosql/authConfig.go | 113 +++++++ authority/mgmt/db/nosql/nosql.go | 91 ++++++ authority/mgmt/db/nosql/provisioner.go | 174 ++++++++++ authority/mgmt/errors.go | 191 +++++++++++ authority/options.go | 9 +- authority/provisioner/claims.go | 21 ++ authority/ssh.go | 104 +----- authority/tls.go | 5 +- ca/bootstrap.go | 94 +++--- ca/ca.go | 12 +- ca/mgmtClient.go | 107 ++++++ ca/tls.go | 1 - commands/app.go | 4 +- commands/onboard.go | 4 +- pki/pki.go | 26 +- 35 files changed, 2035 insertions(+), 215 deletions(-) create mode 100644 authority/admin.go rename authority/{ => config}/config.go (93%) rename authority/{ => config}/config_test.go (99%) create mode 100644 authority/config/ssh.go rename authority/{ => config}/tls_options.go (99%) rename authority/{ => config}/tls_options_test.go (99%) rename authority/{ => config}/types.go (98%) rename authority/{ => config}/types_test.go (99%) create mode 100644 authority/mgmt/api/admin.go create mode 100644 authority/mgmt/api/authConfig.go create mode 100644 authority/mgmt/api/handler.go create mode 100644 authority/mgmt/api/provisioner.go create mode 100644 authority/mgmt/config.go create mode 100644 authority/mgmt/db.go create mode 100644 authority/mgmt/db/nosql/admin.go create mode 100644 authority/mgmt/db/nosql/authConfig.go create mode 100644 authority/mgmt/db/nosql/nosql.go create mode 100644 authority/mgmt/db/nosql/provisioner.go create mode 100644 authority/mgmt/errors.go create mode 100644 ca/mgmtClient.go diff --git a/acme/db/nosql/nosql.go b/acme/db/nosql/nosql.go index 052f5729..e2edd050 100644 --- a/acme/db/nosql/nosql.go +++ b/acme/db/nosql/nosql.go @@ -23,11 +23,12 @@ var ( // DB is a struct that implements the AcmeDB interface. type DB struct { - db nosqlDB.DB + db nosqlDB.DB + authorityID string } // New configures and returns a new ACME DB backend implemented using a nosql DB. -func New(db nosqlDB.DB) (*DB, error) { +func New(db nosqlDB.DB, authorityID string) (*DB, error) { tables := [][]byte{accountTable, accountByKeyIDTable, authzTable, challengeTable, nonceTable, orderTable, ordersByAccountIDTable, certTable} for _, b := range tables { @@ -36,7 +37,7 @@ func New(db nosqlDB.DB) (*DB, error) { string(b)) } } - return &DB{db}, nil + return &DB{db, authorityID}, nil } // save writes the new data to the database, overwriting the old data if it diff --git a/api/api.go b/api/api.go index 6a0a7e8f..d6cf9ab7 100644 --- a/api/api.go +++ b/api/api.go @@ -21,6 +21,7 @@ import ( "github.com/go-chi/chi" "github.com/pkg/errors" "github.com/smallstep/certificates/authority" + "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/logging" @@ -32,7 +33,7 @@ type Authority interface { // context specifies the Authorize[Sign|Revoke|etc.] method. Authorize(ctx context.Context, ott string) ([]provisioner.SignOption, error) AuthorizeSign(ott string) ([]provisioner.SignOption, error) - GetTLSOptions() *authority.TLSOptions + GetTLSOptions() *config.TLSOptions Root(shasum string) (*x509.Certificate, error) Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) Renew(peer *x509.Certificate) ([]*x509.Certificate, error) diff --git a/api/sign.go b/api/sign.go index 69e9a1a5..d6fd2bc6 100644 --- a/api/sign.go +++ b/api/sign.go @@ -5,7 +5,7 @@ import ( "encoding/json" "net/http" - "github.com/smallstep/certificates/authority" + "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/errs" ) @@ -37,11 +37,11 @@ func (s *SignRequest) Validate() error { // SignResponse is the response object of the certificate signature request. type SignResponse struct { - ServerPEM Certificate `json:"crt"` - CaPEM Certificate `json:"ca"` - CertChainPEM []Certificate `json:"certChain"` - TLSOptions *authority.TLSOptions `json:"tlsOptions,omitempty"` - TLS *tls.ConnectionState `json:"-"` + ServerPEM Certificate `json:"crt"` + CaPEM Certificate `json:"ca"` + CertChainPEM []Certificate `json:"certChain"` + TLSOptions *config.TLSOptions `json:"tlsOptions,omitempty"` + TLS *tls.ConnectionState `json:"-"` } // Sign is an HTTP handler that reads a certificate request and an diff --git a/api/ssh.go b/api/ssh.go index 9962ad4f..8c0c1aa3 100644 --- a/api/ssh.go +++ b/api/ssh.go @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/authority" + "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/templates" @@ -22,12 +23,12 @@ type SSHAuthority interface { RenewSSH(ctx context.Context, cert *ssh.Certificate) (*ssh.Certificate, error) RekeySSH(ctx context.Context, cert *ssh.Certificate, key ssh.PublicKey, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) SignSSHAddUser(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error) - GetSSHRoots(ctx context.Context) (*authority.SSHKeys, error) - GetSSHFederation(ctx context.Context) (*authority.SSHKeys, error) + GetSSHRoots(ctx context.Context) (*config.SSHKeys, error) + GetSSHFederation(ctx context.Context) (*config.SSHKeys, error) GetSSHConfig(ctx context.Context, typ string, data map[string]string) ([]templates.Output, error) CheckSSHHost(ctx context.Context, principal string, token string) (bool, error) - GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]authority.Host, error) - GetSSHBastion(ctx context.Context, user string, hostname string) (*authority.Bastion, error) + GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]config.Host, error) + GetSSHBastion(ctx context.Context, user string, hostname string) (*config.Bastion, error) } // SSHSignRequest is the request body of an SSH certificate request. @@ -86,7 +87,7 @@ type SSHCertificate struct { // SSHGetHostsResponse is the response object that returns the list of valid // hosts for SSH. type SSHGetHostsResponse struct { - Hosts []authority.Host `json:"hosts"` + Hosts []config.Host `json:"hosts"` } // MarshalJSON implements the json.Marshaler interface. Returns a quoted, @@ -239,8 +240,8 @@ func (r *SSHBastionRequest) Validate() error { // SSHBastionResponse is the response body used to return the bastion for a // given host. type SSHBastionResponse struct { - Hostname string `json:"hostname"` - Bastion *authority.Bastion `json:"bastion,omitempty"` + Hostname string `json:"hostname"` + Bastion *config.Bastion `json:"bastion,omitempty"` } // SSHSign is an HTTP handler that reads an SignSSHRequest with a one-time-token diff --git a/authority/admin.go b/authority/admin.go new file mode 100644 index 00000000..6c95de4f --- /dev/null +++ b/authority/admin.go @@ -0,0 +1,12 @@ +package authority + +// Admin is the type definining Authority admins. Admins can update Authority +// configuration, provisioners, and even other admins. +type Admin struct { + ID string `json:"-"` + AuthorityID string `json:"-"` + Name string `json:"name"` + Provisioner string `json:"provisioner"` + IsSuperAdmin bool `json:"isSuperAdmin"` + IsDeleted bool `json:"isDeleted"` +} diff --git a/authority/authority.go b/authority/authority.go index 72fa081f..fcb01a03 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -13,6 +13,9 @@ import ( "github.com/smallstep/certificates/cas" "github.com/pkg/errors" + "github.com/smallstep/certificates/authority/config" + "github.com/smallstep/certificates/authority/mgmt" + authMgmtNosql "github.com/smallstep/certificates/authority/mgmt/db/nosql" "github.com/smallstep/certificates/authority/provisioner" casapi "github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/db" @@ -20,17 +23,15 @@ import ( kmsapi "github.com/smallstep/certificates/kms/apiv1" "github.com/smallstep/certificates/kms/sshagentkms" "github.com/smallstep/certificates/templates" + "github.com/smallstep/nosql" "go.step.sm/crypto/pemutil" "golang.org/x/crypto/ssh" ) -const ( - legacyAuthority = "step-certificate-authority" -) - // Authority implements the Certificate Authority internal interface. type Authority struct { - config *Config + config *config.Config + mgmtDB *mgmt.DB keyManager kms.KeyManager provisioners *provisioner.Collection db db.AuthDB @@ -55,14 +56,14 @@ type Authority struct { startTime time.Time // Custom functions - sshBastionFunc func(ctx context.Context, user, hostname string) (*Bastion, error) + sshBastionFunc func(ctx context.Context, user, hostname string) (*config.Bastion, error) sshCheckHostFunc func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, error) - sshGetHostsFunc func(ctx context.Context, cert *x509.Certificate) ([]Host, error) + sshGetHostsFunc func(ctx context.Context, cert *x509.Certificate) ([]config.Host, error) getIdentityFunc provisioner.GetIdentityFunc } // New creates and initiates a new Authority type. -func New(config *Config, opts ...Option) (*Authority, error) { +func New(config *config.Config, opts ...Option) (*Authority, error) { err := config.Validate() if err != nil { return nil, err @@ -92,7 +93,7 @@ func New(config *Config, opts ...Option) (*Authority, error) { // project without the limitations of the config. func NewEmbedded(opts ...Option) (*Authority, error) { a := &Authority{ - config: &Config{}, + config: &config.Config{}, certificates: new(sync.Map), } @@ -116,7 +117,7 @@ func NewEmbedded(opts ...Option) (*Authority, error) { } // Initialize config required fields. - a.config.init() + a.config.Init() // Initialize authority from options or configuration. if err := a.init(); err != nil { @@ -143,6 +144,22 @@ func (a *Authority) init() error { } } + // Pull AuthConfig from DB. + if true { + mgmtDB, err := authMgmtNosql.New(a.db.(nosql.DB), mgmt.DefaultAuthorityID) + if err != nil { + return err + } + _ac, err := mgmtDB.GetAuthConfig(context.Background(), mgmt.DefaultAuthorityID) + if err != nil { + return err + } + a.config.AuthorityConfig, err = _ac.ToCertificates() + if err != nil { + return err + } + } + // Initialize key manager if it has not been set in the options. if a.keyManager == nil { var options kmsapi.Options @@ -314,7 +331,7 @@ func (a *Authority) init() error { } // Merge global and configuration claims - claimer, err := provisioner.NewClaimer(a.config.AuthorityConfig.Claims, globalProvisionerClaims) + claimer, err := provisioner.NewClaimer(a.config.AuthorityConfig.Claims, config.GlobalProvisionerClaims) if err != nil { return err } @@ -326,7 +343,7 @@ func (a *Authority) init() error { return err } // Initialize provisioners - audiences := a.config.getAudiences() + audiences := a.config.GetAudiences() a.provisioners = provisioner.NewCollection(audiences) config := provisioner.Config{ Claims: claimer.Claims(), diff --git a/authority/config.go b/authority/config/config.go similarity index 93% rename from authority/config.go rename to authority/config/config.go index b02bc2be..1f6b7619 100644 --- a/authority/config.go +++ b/authority/config/config.go @@ -1,4 +1,4 @@ -package authority +package config import ( "encoding/json" @@ -15,6 +15,10 @@ import ( "github.com/smallstep/certificates/templates" ) +const ( + legacyAuthority = "step-certificate-authority" +) + var ( // DefaultTLSOptions represents the default TLS version as well as the cipher // suites used in the TLS certificates. @@ -28,10 +32,12 @@ var ( MaxVersion: 1.2, Renegotiation: false, } - defaultBackdate = time.Minute - defaultDisableRenewal = false - defaultEnableSSHCA = false - globalProvisionerClaims = provisioner.Claims{ + defaultBackdate = time.Minute + defaultDisableRenewal = false + defaultEnableSSHCA = false + // GlobalProvisionerClaims default claims for the Authority. Can be overriden + // by provisioner specific claims. + GlobalProvisionerClaims = provisioner.Claims{ MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute}, // TLS certs MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour}, DefaultTLSDur: &provisioner.Duration{Duration: 24 * time.Hour}, @@ -151,9 +157,9 @@ func LoadConfiguration(filename string) (*Config, error) { return &c, nil } -// initializes the minimal configuration required to create an authority. This +// Init initializes the minimal configuration required to create an authority. This // is mainly used on embedded authorities. -func (c *Config) init() { +func (c *Config) Init() { if c.DNSNames == nil { c.DNSNames = []string{"localhost", "127.0.0.1", "::1"} } @@ -246,13 +252,13 @@ func (c *Config) Validate() error { return err } - return c.AuthorityConfig.Validate(c.getAudiences()) + return c.AuthorityConfig.Validate(c.GetAudiences()) } -// getAudiences returns the legacy and possible urls without the ports that will +// GetAudiences returns the legacy and possible urls without the ports that will // be used as the default provisioner audiences. The CA might have proxies in // front so we cannot rely on the port. -func (c *Config) getAudiences() provisioner.Audiences { +func (c *Config) GetAudiences() provisioner.Audiences { audiences := provisioner.Audiences{ Sign: []string{legacyAuthority}, Revoke: []string{legacyAuthority}, diff --git a/authority/config_test.go b/authority/config/config_test.go similarity index 99% rename from authority/config_test.go rename to authority/config/config_test.go index 87cd3fba..735ac33e 100644 --- a/authority/config_test.go +++ b/authority/config/config_test.go @@ -1,4 +1,4 @@ -package authority +package config import ( "fmt" diff --git a/authority/config/ssh.go b/authority/config/ssh.go new file mode 100644 index 00000000..4ba1bb38 --- /dev/null +++ b/authority/config/ssh.go @@ -0,0 +1,94 @@ +package config + +import ( + "github.com/pkg/errors" + "github.com/smallstep/certificates/authority/provisioner" + "go.step.sm/crypto/jose" + "golang.org/x/crypto/ssh" +) + +// SSHConfig contains the user and host keys. +type SSHConfig struct { + HostKey string `json:"hostKey"` + UserKey string `json:"userKey"` + Keys []*SSHPublicKey `json:"keys,omitempty"` + AddUserPrincipal string `json:"addUserPrincipal,omitempty"` + AddUserCommand string `json:"addUserCommand,omitempty"` + Bastion *Bastion `json:"bastion,omitempty"` +} + +// Bastion contains the custom properties used on bastion. +type Bastion struct { + Hostname string `json:"hostname"` + User string `json:"user,omitempty"` + Port string `json:"port,omitempty"` + Command string `json:"cmd,omitempty"` + Flags string `json:"flags,omitempty"` +} + +// HostTag are tagged with k,v pairs. These tags are how a user is ultimately +// associated with a host. +type HostTag struct { + ID string + Name string + Value string +} + +// Host defines expected attributes for an ssh host. +type Host struct { + HostID string `json:"hid"` + HostTags []HostTag `json:"host_tags"` + Hostname string `json:"hostname"` +} + +// Validate checks the fields in SSHConfig. +func (c *SSHConfig) Validate() error { + if c == nil { + return nil + } + for _, k := range c.Keys { + if err := k.Validate(); err != nil { + return err + } + } + return nil +} + +// SSHPublicKey contains a public key used by federated CAs to keep old signing +// keys for this ca. +type SSHPublicKey struct { + Type string `json:"type"` + Federated bool `json:"federated"` + Key jose.JSONWebKey `json:"key"` + publicKey ssh.PublicKey +} + +// Validate checks the fields in SSHPublicKey. +func (k *SSHPublicKey) Validate() error { + switch { + case k.Type == "": + return errors.New("type cannot be empty") + case k.Type != provisioner.SSHHostCert && k.Type != provisioner.SSHUserCert: + return errors.Errorf("invalid type %s, it must be user or host", k.Type) + case !k.Key.IsPublic(): + return errors.New("invalid key type, it must be a public key") + } + + key, err := ssh.NewPublicKey(k.Key.Key) + if err != nil { + return errors.Wrap(err, "error creating ssh key") + } + k.publicKey = key + return nil +} + +// PublicKey returns the ssh public key. +func (k *SSHPublicKey) PublicKey() ssh.PublicKey { + return k.publicKey +} + +// SSHKeys represents the SSH User and Host public keys. +type SSHKeys struct { + UserKeys []ssh.PublicKey + HostKeys []ssh.PublicKey +} diff --git a/authority/tls_options.go b/authority/config/tls_options.go similarity index 99% rename from authority/tls_options.go rename to authority/config/tls_options.go index 3edde605..5b0575d6 100644 --- a/authority/tls_options.go +++ b/authority/config/tls_options.go @@ -1,4 +1,4 @@ -package authority +package config import ( "crypto/tls" diff --git a/authority/tls_options_test.go b/authority/config/tls_options_test.go similarity index 99% rename from authority/tls_options_test.go rename to authority/config/tls_options_test.go index 96c58c5d..d7ccb20b 100644 --- a/authority/tls_options_test.go +++ b/authority/config/tls_options_test.go @@ -1,4 +1,4 @@ -package authority +package config import ( "crypto/tls" diff --git a/authority/types.go b/authority/config/types.go similarity index 98% rename from authority/types.go rename to authority/config/types.go index 0d0f2a90..6d7b9389 100644 --- a/authority/types.go +++ b/authority/config/types.go @@ -1,4 +1,4 @@ -package authority +package config import ( "encoding/json" diff --git a/authority/types_test.go b/authority/config/types_test.go similarity index 99% rename from authority/types_test.go rename to authority/config/types_test.go index 352c253f..b1a874d6 100644 --- a/authority/types_test.go +++ b/authority/config/types_test.go @@ -1,4 +1,4 @@ -package authority +package config import ( "reflect" diff --git a/authority/mgmt/api/admin.go b/authority/mgmt/api/admin.go new file mode 100644 index 00000000..6f6aaa93 --- /dev/null +++ b/authority/mgmt/api/admin.go @@ -0,0 +1,112 @@ +package api + +import ( + "net/http" + + "github.com/go-chi/chi" + "github.com/smallstep/certificates/api" +) + +// CreateAdminRequest represents the body for a CreateAdmin request. +type CreateAdminRequest struct { + Name string `json:"name"` + Provisioner string `json:"provisioner"` + IsSuperAdmin bool `json:"isSuperAdmin"` +} + +// Validate validates a new-admin request body. +func (car *CreateAdminRequest) Validate() error { + return nil +} + +// UpdateAdminRequest represents the body for a UpdateAdmin request. +type UpdateAdminRequest struct { + Name string `json:"name"` + Provisioner string `json:"provisioner"` + IsSuperAdmin bool `json:"isSuperAdmin"` +} + +// Validate validates a new-admin request body. +func (uar *UpdateAdminRequest) Validate() error { + return nil +} + +// GetAdmin returns the requested admin, or an error. +func (h *Handler) GetAdmin(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + id := chi.URLParam(r, "id") + + prov, err := h.db.GetAdmin(ctx, id) + if err != nil { + api.WriteError(w, err) + return + } + api.JSON(w, prov) +} + +// GetAdmins returns all admins associated with the authority. +func (h *Handler) GetAdmins(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + admins, err := h.db.GetAdmins(ctx) + if err != nil { + api.WriteError(w, err) + return + } + api.JSON(w, admins) +} + +// CreateAdmin creates a new admin. +func (h *Handler) CreateAdmin(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var body CreateAdminRequest + if err := ReadJSON(r.Body, &body); err != nil { + api.WriteError(w, err) + return + } + if err := body.Validate(); err != nil { + api.WriteError(w, err) + } + + adm := &config.Admin{ + Name: body.Name, + Provisioner: body.Provisioner, + IsSuperAdmin: body.IsSuperAdmin, + } + if err := h.db.CreateAdmin(ctx, adm); err != nil { + api.WriteError(w, err) + return + } + api.JSONStatus(w, adm, http.StatusCreated) +} + +// UpdateAdmin updates an existing admin. +func (h *Handler) UpdateAdmin(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + id := chi.URLParam(r, "id") + + var body UpdateAdminRequest + if err := ReadJSON(r.Body, &body); err != nil { + api.WriteError(w, err) + return + } + if err := body.Validate(); err != nil { + api.WriteError(w, err) + return + } + if adm, err := h.db.GetAdmin(ctx, id); err != nil { + api.WriteError(w, err) + return + } + + adm.Name = body.Name + adm.Provisioner = body.Provisioner + adm.IsSuperAdmin = body.IsSuperAdmin + + if err := h.db.UpdateAdmin(ctx, adm); err != nil { + api.WriteError(w, err) + return + } + api.JSON(w, adm) +} diff --git a/authority/mgmt/api/authConfig.go b/authority/mgmt/api/authConfig.go new file mode 100644 index 00000000..fb23f8e8 --- /dev/null +++ b/authority/mgmt/api/authConfig.go @@ -0,0 +1,121 @@ +package api + +import ( + "net/http" + + "github.com/go-chi/chi" + "github.com/smallstep/certificates/api" + "github.com/smallstep/certificates/authority" + "github.com/smallstep/certificates/authority/config" +) + +// CreateAuthConfigRequest represents the body for a CreateAuthConfig request. +type CreateAuthConfigRequest struct { + ASN1DN *authority.ASN1DN `json:"asn1dn,omitempty"` + Claims *config.Claims `json:"claims,omitempty"` + DisableIssuedAtCheck bool `json:"disableIssuedAtCheck,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 *authority.ASN1DN `json:"asn1dn"` + Claims *config.Claims `json:"claims"` + DisableIssuedAtCheck bool `json:"disableIssuedAtCheck,omitempty"` + 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) +} + +// CreateAuthConfig creates a new admin. +func (h *Handler) CreateAuthConfig(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var body CreateAuthConfigRequest + if err := ReadJSON(r.Body, &body); err != nil { + api.WriteError(w, err) + return + } + if err := body.Validate(); err != nil { + api.WriteError(w, err) + } + + ac := config.AuthConfig{ + Status: config.StatusActive, + DisableIssuedAtCheck: body.DisableIssuedAtCheck, + Backdate: "1m", + } + 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.CreateAuthConfig(ctx, ac); err != nil { + api.WriteError(w, err) + return + } + api.JSONStatus(w, ac, http.StatusCreated) +} + +// 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 := ReadJSON(r.Body, &body); err != nil { + api.WriteError(w, err) + return + } + if err := body.Validate(); err != nil { + api.WriteError(w, err) + return + } + if ac, err := h.db.GetAuthConfig(ctx, id); err != nil { + api.WriteError(w, err) + return + } + + ac.DisableIssuedAtCheck = body.DisableIssuedAtCheck + 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 new file mode 100644 index 00000000..f30544c5 --- /dev/null +++ b/authority/mgmt/api/handler.go @@ -0,0 +1,50 @@ +package api + +import ( + "time" + + "github.com/smallstep/certificates/api" + "github.com/smallstep/certificates/authority/config" +) + +// Clock that returns time in UTC rounded to seconds. +type Clock struct{} + +// Now returns the UTC time rounded to seconds. +func (c *Clock) Now() time.Time { + return time.Now().UTC().Truncate(time.Second) +} + +var clock Clock + +// Handler is the ACME API request handler. +type Handler struct { + db config.DB +} + +// NewHandler returns a new Authority Config Handler. +func NewHandler(db config.DB) api.RouterHandler { + return &Handler{ + db: ops.DB, + } +} + +// Route traffic and implement the Router interface. +func (h *Handler) Route(r api.Router) { + // Provisioners + r.MethodFunc("GET", "/provisioner/{id}", h.GetProvisioner) + r.MethodFunc("GET", "/provisioners", h.GetProvisioners) + r.MethodFunc("POST", "/provisioner", h.CreateProvisioner) + r.MethodFunc("PUT", "/provsiioner/{id}", h.UpdateProvisioner) + + // Admins + r.MethodFunc("GET", "/admin/{id}", h.GetAdmin) + r.MethodFunc("GET", "/admins", h.GetAdmins) + r.MethodFunc("POST", "/admin", h.CreateAdmin) + r.MethodFunc("PUT", "/admin/{id}", h.UpdateAdmin) + + // AuthConfig + r.MethodFunc("GET", "/authconfig/{id}", h.GetAuthConfig) + r.MethodFunc("POST", "/authconfig", h.CreateAuthConfig) + r.MethodFunc("PUT", "/authconfig/{id}", h.UpdateAuthConfig) +} diff --git a/authority/mgmt/api/provisioner.go b/authority/mgmt/api/provisioner.go new file mode 100644 index 00000000..76bbe7cf --- /dev/null +++ b/authority/mgmt/api/provisioner.go @@ -0,0 +1,122 @@ +package api + +import ( + "net/http" + + "github.com/go-chi/chi" + "github.com/smallstep/certificates/api" + "github.com/smallstep/certificates/authority/config" +) + +// CreateProvisionerRequest represents the body for a CreateProvisioner request. +type CreateProvisionerRequest struct { + Type string `json:"type"` + Name string `json:"name"` + Claims *config.Claims `json:"claims"` + Details interface{} `json:"details"` + X509Template string `json:"x509Template"` + SSHTemplate string `json:"sshTemplate"` +} + +// Validate validates a new-provisioner request body. +func (car *CreateProvisionerRequest) Validate() error { + return nil +} + +// UpdateProvisionerRequest represents the body for a UpdateProvisioner request. +type UpdateProvisionerRequest struct { + Claims *config.Claims `json:"claims"` + Details interface{} `json:"details"` + X509Template string `json:"x509Template"` + SSHTemplate string `json:"sshTemplate"` +} + +// Validate validates a new-provisioner request body. +func (uar *UpdateProvisionerRequest) Validate() error { + return nil +} + +// GetProvisioner returns the requested provisioner, or an error. +func (h *Handler) GetProvisioner(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + id := chi.URLParam(r, "id") + + prov, err := h.db.GetProvisioner(ctx, id) + if err != nil { + api.WriteError(w, err) + return + } + api.JSON(w, prov) +} + +// GetProvisioners returns all provisioners associated with the authority. +func (h *Handler) GetProvisioners(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + provs, err := h.db.GetProvisioners(ctx) + if err != nil { + api.WriteError(w, err) + return + } + api.JSON(w, provs) +} + +// CreateProvisioner creates a new prov. +func (h *Handler) CreateProvisioner(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var body CreateProvisionerRequest + if err := ReadJSON(r.Body, &body); err != nil { + api.WriteError(w, err) + return + } + if err := body.Validate(); err != nil { + api.WriteError(w, err) + } + + prov := &config.Provisioner{ + Type: body.Type, + Name: body.Name, + Claims: body.Claims, + Details: body.Details, + X509Template: body.X509Template, + SSHTemplate: body.SSHTemplate, + } + if err := h.db.CreateProvisioner(ctx, prov); err != nil { + api.WriteError(w, err) + return + } + api.JSONStatus(w, prov, http.StatusCreated) +} + +// UpdateProvisioner updates an existing prov. +func (h *Handler) UpdateProvisioner(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + id := chi.URLParam(r, "id") + + var body UpdateProvisionerRequest + if err := ReadJSON(r.Body, &body); err != nil { + api.WriteError(w, err) + return + } + if err := body.Validate(); err != nil { + api.WriteError(w, err) + return + } + if prov, err := h.db.GetProvisioner(ctx, id); err != nil { + api.WriteError(w, err) + return + } + + prov.Claims = body.Claims + prov.Details = body.Provisioner + prov.X509Template = body.X509Template + prov.SSHTemplate = body.SSHTemplate + prov.Status = body.Status + + if err := h.db.UpdateProvisioner(ctx, prov); err != nil { + api.WriteError(w, err) + return + } + api.JSON(w, prov) +} diff --git a/authority/mgmt/config.go b/authority/mgmt/config.go new file mode 100644 index 00000000..b5bebceb --- /dev/null +++ b/authority/mgmt/config.go @@ -0,0 +1,357 @@ +package mgmt + +import ( + "github.com/smallstep/certificates/authority/config" + authority "github.com/smallstep/certificates/authority/config" +) + +const ( + DefaultAuthorityID = "00000000-0000-0000-0000-000000000000" +) + +// StatusType is the type for status. +type StatusType int + +const ( + // StatusActive active + StatusActive StatusType = iota + // StatusDeleted deleted + StatusDeleted +) + +type Claims struct { + *X509Claims `json:"x509Claims"` + *SSHClaims `json:"sshClaims"` + DisableRenewal *bool `json:"disableRenewal"` +} + +type X509Claims struct { + Durations *Durations `json:"durations"` +} + +type SSHClaims struct { + UserDuration *Durations `json:"userDurations"` + HostDuration *Durations `json:"hostDuration"` +} + +type Durations struct { + Min string `json:"min"` + Max string `json:"max"` + Default string `json:"default"` +} + +// Admin type. +type Admin struct { + ID string `json:"-"` + AuthorityID string `json:"-"` + Name string `json:"name"` + Provisioner string `json:"provisioner"` + IsSuperAdmin bool `json:"isSuperAdmin"` + Status StatusType `json:"status"` +} + +// Provisioner type. +type Provisioner struct { + ID string `json:"-"` + AuthorityID string `json:"-"` + Type string `json:"type"` + Name string `json:"name"` + Claims *Claims `json:"claims"` + Details interface{} `json:"details"` + X509Template string `json:"x509Template"` + SSHTemplate string `json:"sshTemplate"` + Status StatusType `json:"status"` +} + +// AuthConfig represents the Authority Configuration. +type AuthConfig struct { + //*cas.Options `json:"cas"` + ID string `json:"id"` + ASN1DN *config.ASN1DN `json:"template,omitempty"` + Provisioners []*Provisioner `json:"-"` + Claims *Claims `json:"claims,omitempty"` + DisableIssuedAtCheck bool `json:"disableIssuedAtCheck,omitempty"` + Backdate string `json:"backdate,omitempty"` + Status StatusType `json:"status,omitempty"` +} + +func (ac *AuthConfig) ToCertificates() (*config.AuthConfig, error) { + return &authority.AuthConfig{}, nil +} + +/* +// ToCertificates converts the landlord provisioner type to the open source +// provisioner type. +func (p *Provisioner) ToCertificates(ctx context.Context, db database.DB) (provisioner.Interface, error) { + claims, err := p.Claims.ToCertificates() + if err != nil { + return nil, err + } + + details := p.Details.GetData() + if details == nil { + return nil, fmt.Errorf("provisioner does not have any details") + } + + options, err := p.getOptions(ctx, db) + if err != nil { + return nil, err + } + + switch d := details.(type) { + case *ProvisionerDetails_JWK: + k := d.JWK.GetKey() + jwk := new(jose.JSONWebKey) + if err := json.Unmarshal(k.Key.Public, &jwk); err != nil { + return nil, err + } + return &provisioner.JWK{ + Type: p.Type.String(), + Name: p.Name, + Key: jwk, + EncryptedKey: string(k.Key.Private), + Claims: claims, + Options: options, + }, 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.String()) + } +} + +// ToCertificates converts the landlord provisioner claims type to the open source +// (step-ca) claims type. +func (c *Claims) ToCertificates() (*provisioner.Claims, error) { + x509, ssh := c.GetX509(), c.GetSsh() + x509Durations := x509.GetDurations() + hostDurations := ssh.GetHostDurations() + userDurations := ssh.GetUserDurations() + enableSSHCA := ssh.GetEnabled() + return &provisioner.Claims{ + MinTLSDur: durationPtr(x509Durations.GetMin()), + MaxTLSDur: durationPtr(x509Durations.GetMax()), + DefaultTLSDur: durationPtr(x509Durations.GetDefault()), + DisableRenewal: &c.DisableRenewal, + MinUserSSHDur: durationPtr(userDurations.GetMin()), + MaxUserSSHDur: durationPtr(userDurations.GetMax()), + DefaultUserSSHDur: durationPtr(userDurations.GetDefault()), + MinHostSSHDur: durationPtr(hostDurations.GetMin()), + MaxHostSSHDur: durationPtr(hostDurations.GetMax()), + DefaultHostSSHDur: durationPtr(hostDurations.GetDefault()), + EnableSSHCA: &enableSSHCA, + }, nil +} + +func durationPtr(d *duration.Duration) *provisioner.Duration { + if d == nil { + return nil + } + return &provisioner.Duration{ + Duration: time.Duration(d.Seconds)*time.Second + time.Duration(d.Nanos)*time.Nanosecond, + } +} + +func durationValue(d *duration.Duration) provisioner.Duration { + if d == nil { + return provisioner.Duration{} + } + return provisioner.Duration{ + Duration: time.Duration(d.Seconds)*time.Second + time.Duration(d.Nanos)*time.Nanosecond, + } +} + +func marshalDetails(d *ProvisionerDetails) (sql.NullString, error) { + b, err := json.Marshal(d.GetData()) + if err != nil { + return sql.NullString{}, nil + } + return sql.NullString{ + String: string(b), + Valid: len(b) > 0, + }, nil +} + +func unmarshalDetails(ctx context.Context, db database.DB, typ ProvisionerType, s sql.NullString) (*ProvisionerDetails, error) { + if !s.Valid { + return nil, nil + } + var v isProvisionerDetails_Data + switch typ { + case ProvisionerType_JWK: + p := new(ProvisionerDetails_JWK) + if err := json.Unmarshal([]byte(s.String), p); err != nil { + return nil, err + } + if p.JWK.Key.Key == nil { + key, err := LoadKey(ctx, db, p.JWK.Key.Id.Id) + if err != nil { + return nil, err + } + p.JWK.Key = key + } + return &ProvisionerDetails{Data: p}, nil + case ProvisionerType_OIDC: + v = new(ProvisionerDetails_OIDC) + case ProvisionerType_GCP: + v = new(ProvisionerDetails_GCP) + case ProvisionerType_AWS: + v = new(ProvisionerDetails_AWS) + case ProvisionerType_AZURE: + v = new(ProvisionerDetails_Azure) + case ProvisionerType_ACME: + v = new(ProvisionerDetails_ACME) + case ProvisionerType_X5C: + p := new(ProvisionerDetails_X5C) + if err := json.Unmarshal([]byte(s.String), p); err != nil { + return nil, err + } + for _, k := range p.X5C.GetRoots() { + if err := k.Select(ctx, db, k.Id.Id); err != nil { + return nil, err + } + } + return &ProvisionerDetails{Data: p}, nil + case ProvisionerType_K8SSA: + p := new(ProvisionerDetails_K8SSA) + if err := json.Unmarshal([]byte(s.String), p); err != nil { + return nil, err + } + for _, k := range p.K8SSA.GetPublicKeys() { + if err := k.Select(ctx, db, k.Id.Id); err != nil { + return nil, err + } + } + return &ProvisionerDetails{Data: p}, nil + case ProvisionerType_SSHPOP: + v = new(ProvisionerDetails_SSHPOP) + default: + return nil, fmt.Errorf("unsupported provisioner type %s", typ) + } + + if err := json.Unmarshal([]byte(s.String), v); err != nil { + return nil, err + } + return &ProvisionerDetails{Data: v}, nil +} + +func marshalClaims(c *Claims) (sql.NullString, error) { + b, err := json.Marshal(c) + if err != nil { + return sql.NullString{}, nil + } + return sql.NullString{ + String: string(b), + Valid: len(b) > 0, + }, nil +} + +func unmarshalClaims(s sql.NullString) (*Claims, error) { + if !s.Valid { + return nil, nil + } + v := new(Claims) + return v, json.Unmarshal([]byte(s.String), v) +} +*/ diff --git a/authority/mgmt/db.go b/authority/mgmt/db.go new file mode 100644 index 00000000..99228a4d --- /dev/null +++ b/authority/mgmt/db.go @@ -0,0 +1,149 @@ +package mgmt + +import ( + "context" + + "github.com/pkg/errors" +) + +// ErrNotFound is an error that should be used by the authority.DB interface to +// indicate that an entity does not exist. +var ErrNotFound = errors.New("not found") + +// DB is the DB interface expected by the step-ca ACME API. +type DB interface { + CreateProvisioner(ctx context.Context, prov *Provisioner) error + GetProvisioner(ctx context.Context, id string) (*Provisioner, error) + GetProvisioners(ctx context.Context) ([]*Provisioner, error) + UpdateProvisioner(ctx context.Context, prov *Provisioner) error + + CreateAdmin(ctx context.Context, admin *Admin) error + GetAdmin(ctx context.Context, id string) error + GetAdmins(ctx context.Context) ([]*Admin, error) + UpdateAdmin(ctx context.Context, admin *Admin) error + + CreateAuthConfig(ctx context.Context, ac *AuthConfig) error + GetAuthConfig(ctx context.Context, id string) (*AuthConfig, error) + UpdateAuthConfig(ctx context.Context, ac *AuthConfig) error +} + +// MockDB is an implementation of the DB interface that should only be used as +// a mock in tests. +type MockDB struct { + MockCreateProvisioner func(ctx context.Context, prov *Provisioner) error + MockGetProvisioner func(ctx context.Context, id string) (*Provisioner, error) + MockGetProvisioners func(ctx context.Context) ([]*Provisioner, error) + MockUpdateProvisioner func(ctx context.Context, prov *Provisioner) error + + MockCreateAdmin func(ctx context.Context, adm *Admin) error + MockGetAdmin func(ctx context.Context, id string) (*Admin, error) + MockGetAdmins func(ctx context.Context) ([]*Admin, error) + MockUpdateAdmin func(ctx context.Context, adm *Admin) error + + MockCreateAuthConfig func(ctx context.Context, ac *AuthConfig) error + MockGetAuthConfig func(ctx context.Context, id string) (*AuthConfig, error) + MockUpdateAuthConfig func(ctx context.Context, ac *AuthConfig) error + + MockError error + MockRet1 interface{} +} + +// CreateProvisioner mock. +func (m *MockDB) CreateProvisioner(ctx context.Context, prov *Provisioner) error { + if m.MockCreateProvisioner != nil { + return m.MockCreateProvisioner(ctx, prov) + } else if m.MockError != nil { + return m.MockError + } + return m.MockError +} + +// GetProvisioner mock. +func (m *MockDB) GetProvisioner(ctx context.Context, id string) (*Provisioner, error) { + if m.MockGetProvisioner != nil { + return m.MockGetProvisioner(ctx, id) + } else if m.MockError != nil { + return nil, m.MockError + } + return m.MockRet1.(*Provisioner), m.MockError +} + +// GetProvisioners mock +func (m *MockDB) GetProvisioners(ctx context.Context) ([]*Provisioner, error) { + if m.MockGetProvisioners != nil { + return m.MockGetProvisioners(ctx) + } else if m.MockError != nil { + return nil, m.MockError + } + return m.MockRet1.([]*Provisioner), m.MockError +} + +// UpdateProvisioner mock +func (m *MockDB) UpdateProvisioner(ctx context.Context, prov *Provisioner) error { + if m.MockUpdateProvisioner != nil { + return m.MockUpdateProvisioner(ctx, prov) + } + return m.MockError +} + +// CreateAdmin mock +func (m *MockDB) CreateAdmin(ctx context.Context, admin *Admin) error { + if m.MockCreateAdmin != nil { + return m.MockCreateAdmin(ctx, admin) + } + return m.MockError +} + +// GetAdmin mock. +func (m *MockDB) GetAdmin(ctx context.Context, id string) (*Admin, error) { + if m.MockGetAdmin != nil { + return m.MockGetAdmin(ctx, id) + } else if m.MockError != nil { + return nil, m.MockError + } + return m.MockRet1.(*Admin), m.MockError +} + +// GetAdmins mock +func (m *MockDB) GetAdmins(ctx context.Context) ([]*Admin, error) { + if m.MockGetAdmins != nil { + return m.MockGetAdmins(ctx) + } else if m.MockError != nil { + return nil, m.MockError + } + return m.MockRet1.([]*Admin), m.MockError +} + +// UpdateAdmin mock +func (m *MockDB) UpdateAdmin(ctx context.Context, adm *Admin) error { + if m.UpdateAdmin != nil { + return m.MockUpdateAdmin(ctx, adm) + } + return m.MockError +} + +// CreateAuthConfig mock +func (m *MockDB) CreateAuthConfig(ctx context.Context, admin *AuthConfig) error { + if m.MockCreateAuthConfig != nil { + return m.MockCreateAuthConfig(ctx, admin) + } + return m.MockError +} + +// GetAuthConfig mock. +func (m *MockDB) GetAuthConfig(ctx context.Context, id string) (*AuthConfig, error) { + if m.MockGetAuthConfig != nil { + return m.MockGetAuthConfig(ctx, id) + } else if m.MockError != nil { + return nil, m.MockError + } + return m.MockRet1.(*AuthConfig), m.MockError +} + +// UpdateAuthConfig mock +func (m *MockDB) UpdateAuthConfig(ctx context.Context, adm *AuthConfig) error { + if m.UpdateAuthConfig != nil { + return m.MockUpdateAuthConfig(ctx, adm) + } + return m.MockError +} diff --git a/authority/mgmt/db/nosql/admin.go b/authority/mgmt/db/nosql/admin.go new file mode 100644 index 00000000..8b33110c --- /dev/null +++ b/authority/mgmt/db/nosql/admin.go @@ -0,0 +1,163 @@ +package nosql + +import ( + "context" + "encoding/json" + "time" + + "github.com/pkg/errors" + "github.com/smallstep/certificates/acme" + "github.com/smallstep/certificates/authority/mgmt" + "github.com/smallstep/nosql" +) + +// dbAdmin is the database representation of the Admin type. +type dbAdmin struct { + ID string `json:"id"` + AuthorityID string `json:"authorityID"` + Name string `json:"name"` + Provisioner string `json:"provisioner"` + IsSuperAdmin bool `json:"isSuperAdmin"` + CreatedAt time.Time `json:"createdAt"` + DeletedAt time.Time `json:"deletedAt"` +} + +func (dbp *dbAdmin) clone() *dbAdmin { + u := *dbp + return &u +} + +func (db *DB) getDBAdminBytes(ctx context.Context, id string) ([]byte, error) { + data, err := db.db.Get(authorityAdminsTable, []byte(id)) + if nosql.IsErrNotFound(err) { + return nil, mgmt.NewError(mgmt.ErrorNotFoundType, "admin %s not found", id) + } else if err != nil { + return nil, errors.Wrapf(err, "error loading admin %s", id) + } + return data, nil +} + +func (db *DB) getDBAdmin(ctx context.Context, id string) (*dbAdmin, error) { + data, err := db.getDBAdminBytes(ctx, id) + if err != nil { + return nil, err + } + dba, err := unmarshalDBAdmin(data, id) + if err != nil { + return nil, err + } + if dba.AuthorityID != db.authorityID { + return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType, + "admin %s is not owned by authority %s", dba.ID, db.authorityID) + } + return dba, nil +} + +// GetAdmin retrieves and unmarshals a admin from the database. +func (db *DB) GetAdmin(ctx context.Context, id string) (*mgmt.Admin, error) { + data, err := db.getDBAdminBytes(ctx, id) + if err != nil { + return nil, err + } + adm, err := unmarshalAdmin(data, id) + if err != nil { + return nil, err + } + if adm.Status == mgmt.StatusDeleted { + return nil, mgmt.NewError(mgmt.ErrorDeletedType, "admin %s is deleted") + } + if adm.AuthorityID != db.authorityID { + return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType, + "admin %s is not owned by authority %s", adm.ID, db.authorityID) + } + return adm, nil +} + +func unmarshalDBAdmin(data []byte, id string) (*dbAdmin, error) { + var dba = new(dbAdmin) + if err := json.Unmarshal(data, dba); err != nil { + return nil, errors.Wrapf(err, "error unmarshaling admin %s into dbAdmin", id) + } + return dba, nil +} + +func unmarshalAdmin(data []byte, id string) (*mgmt.Admin, error) { + var dba = new(dbAdmin) + if err := json.Unmarshal(data, dba); err != nil { + return nil, errors.Wrapf(err, "error unmarshaling admin %s into dbAdmin", id) + } + adm := &mgmt.Admin{ + ID: dba.ID, + Name: dba.Name, + Provisioner: dba.Provisioner, + IsSuperAdmin: dba.IsSuperAdmin, + } + if !dba.DeletedAt.IsZero() { + adm.Status = mgmt.StatusDeleted + } + return adm, nil +} + +// GetAdmins retrieves and unmarshals all active (not deleted) admins +// from the database. +// TODO should we be paginating? +func (db *DB) GetAdmins(ctx context.Context, az *acme.Authorization) ([]*mgmt.Admin, error) { + dbEntries, err := db.db.List(authorityAdminsTable) + if err != nil { + return nil, errors.Wrap(err, "error loading admins") + } + var admins []*mgmt.Admin + for _, entry := range dbEntries { + adm, err := unmarshalAdmin(entry.Value, string(entry.Key)) + if err != nil { + return nil, err + } + if adm.Status == mgmt.StatusDeleted { + continue + } + if adm.AuthorityID != db.authorityID { + continue + } + admins = append(admins, adm) + } + return admins, nil +} + +// CreateAdmin stores a new admin to the database. +func (db *DB) CreateAdmin(ctx context.Context, adm *mgmt.Admin) error { + var err error + adm.ID, err = randID() + if err != nil { + return errors.Wrap(err, "error generating random id for admin") + } + + dba := &dbAdmin{ + ID: adm.ID, + AuthorityID: db.authorityID, + Name: adm.Name, + Provisioner: adm.Provisioner, + IsSuperAdmin: adm.IsSuperAdmin, + CreatedAt: clock.Now(), + } + + return db.save(ctx, dba.ID, dba, nil, "admin", authorityAdminsTable) +} + +// UpdateAdmin saves an updated admin to the database. +func (db *DB) UpdateAdmin(ctx context.Context, adm *mgmt.Admin) error { + old, err := db.getDBAdmin(ctx, adm.ID) + if err != nil { + return err + } + + nu := old.clone() + + // If the admin was active but is now deleted ... + if old.DeletedAt.IsZero() && adm.Status == mgmt.StatusDeleted { + nu.DeletedAt = clock.Now() + } + nu.Provisioner = adm.Provisioner + nu.IsSuperAdmin = adm.IsSuperAdmin + + return db.save(ctx, old.ID, nu, old, "admin", authorityAdminsTable) +} diff --git a/authority/mgmt/db/nosql/authConfig.go b/authority/mgmt/db/nosql/authConfig.go new file mode 100644 index 00000000..6fe1266b --- /dev/null +++ b/authority/mgmt/db/nosql/authConfig.go @@ -0,0 +1,113 @@ +package nosql + +import ( + "context" + "encoding/json" + "time" + + "github.com/pkg/errors" + "github.com/smallstep/certificates/authority/config" + "github.com/smallstep/certificates/authority/mgmt" + "github.com/smallstep/nosql" +) + +type dbAuthConfig struct { + ID string `json:"id"` + ASN1DN *config.ASN1DN `json:"asn1dn"` + Claims *mgmt.Claims `json:"claims"` + DisableIssuedAtCheck bool `json:"disableIssuedAtCheck,omitempty"` + Backdate string `json:"backdate,omitempty"` + CreatedAt time.Time `json:"createdAt"` + DeletedAt time.Time `json:"deletedAt"` +} + +func (dbp *dbAuthConfig) clone() *dbAuthConfig { + u := *dbp + return &u +} + +func (db *DB) getDBAuthConfigBytes(ctx context.Context, id string) ([]byte, error) { + data, err := db.db.Get(authorityConfigsTable, []byte(id)) + if nosql.IsErrNotFound(err) { + return nil, mgmt.NewError(mgmt.ErrorNotFoundType, "authConfig %s not found", id) + } else if err != nil { + return nil, errors.Wrapf(err, "error loading authConfig %s", id) + } + return data, nil +} + +func (db *DB) getDBAuthConfig(ctx context.Context, id string) (*dbAuthConfig, error) { + data, err := db.getDBAuthConfigBytes(ctx, id) + if err != nil { + return nil, err + } + + var dba = new(dbAuthConfig) + if err = json.Unmarshal(data, dba); err != nil { + return nil, errors.Wrapf(err, "error unmarshaling authority %s into dbAuthConfig", id) + } + + return dba, nil +} + +// GetAuthConfig retrieves an AuthConfig configuration from the DB. +func (db *DB) GetAuthConfig(ctx context.Context, id string) (*mgmt.AuthConfig, error) { + dba, err := db.getDBAuthConfig(ctx, id) + if err != nil { + return nil, err + } + + provs, err := db.GetProvisioners(ctx) + if err != nil { + return nil, err + } + + return &mgmt.AuthConfig{ + ID: dba.ID, + Provisioners: provs, + ASN1DN: dba.ASN1DN, + Backdate: dba.Backdate, + Claims: dba.Claims, + DisableIssuedAtCheck: dba.DisableIssuedAtCheck, + }, nil +} + +// CreateAuthConfig stores a new provisioner to the database. +func (db *DB) CreateAuthConfig(ctx context.Context, ac *mgmt.AuthConfig) error { + var err error + ac.ID, err = randID() + if err != nil { + return errors.Wrap(err, "error generating random id for provisioner") + } + + dba := &dbAuthConfig{ + ID: ac.ID, + ASN1DN: ac.ASN1DN, + Claims: ac.Claims, + DisableIssuedAtCheck: ac.DisableIssuedAtCheck, + Backdate: ac.Backdate, + CreatedAt: clock.Now(), + } + + return db.save(ctx, dba.ID, dba, nil, "authConfig", authorityConfigsTable) +} + +// UpdateAuthConfig saves an updated provisioner to the database. +func (db *DB) UpdateAuthConfig(ctx context.Context, ac *mgmt.AuthConfig) error { + old, err := db.getDBAuthConfig(ctx, ac.ID) + if err != nil { + return err + } + + nu := old.clone() + + // If the authority was active but is now deleted ... + if old.DeletedAt.IsZero() && ac.Status == mgmt.StatusDeleted { + nu.DeletedAt = clock.Now() + } + nu.Claims = ac.Claims + nu.DisableIssuedAtCheck = ac.DisableIssuedAtCheck + nu.Backdate = ac.Backdate + + return db.save(ctx, old.ID, nu, old, "authConfig", authorityProvisionersTable) +} diff --git a/authority/mgmt/db/nosql/nosql.go b/authority/mgmt/db/nosql/nosql.go new file mode 100644 index 00000000..d71b2804 --- /dev/null +++ b/authority/mgmt/db/nosql/nosql.go @@ -0,0 +1,91 @@ +package nosql + +import ( + "context" + "encoding/json" + "time" + + "github.com/pkg/errors" + nosqlDB "github.com/smallstep/nosql/database" + "go.step.sm/crypto/randutil" +) + +var ( + authorityAdminsTable = []byte("authority_admins") + authorityConfigsTable = []byte("authority_configs") + authorityProvisionersTable = []byte("authority_provisioners") +) + +// DB is a struct that implements the AcmeDB interface. +type DB struct { + db nosqlDB.DB + authorityID string +} + +// New configures and returns a new Authority DB backend implemented using a nosql DB. +func New(db nosqlDB.DB, authorityID string) (*DB, error) { + tables := [][]byte{authorityAdminsTable, authorityConfigsTable, authorityProvisionersTable} + for _, b := range tables { + if err := db.CreateTable(b); err != nil { + return nil, errors.Wrapf(err, "error creating table %s", + string(b)) + } + } + return &DB{db, authorityID}, nil +} + +// save writes the new data to the database, overwriting the old data if it +// existed. +func (db *DB) save(ctx context.Context, id string, nu interface{}, old interface{}, typ string, table []byte) error { + var ( + err error + newB []byte + ) + if nu == nil { + newB = nil + } else { + newB, err = json.Marshal(nu) + if err != nil { + return errors.Wrapf(err, "error marshaling authority type: %s, value: %v", typ, nu) + } + } + var oldB []byte + if old == nil { + oldB = nil + } else { + oldB, err = json.Marshal(old) + if err != nil { + return errors.Wrapf(err, "error marshaling acme type: %s, value: %v", typ, old) + } + } + + _, swapped, err := db.db.CmpAndSwap(table, []byte(id), oldB, newB) + switch { + case err != nil: + return errors.Wrapf(err, "error saving authority %s", typ) + case !swapped: + return errors.Errorf("error saving authority %s; changed since last read", typ) + default: + return nil + } +} + +var idLen = 32 + +func randID() (val string, err error) { + val, err = randutil.Alphanumeric(idLen) + if err != nil { + return "", errors.Wrap(err, "error generating random alphanumeric ID") + } + return val, nil +} + +// Clock that returns time in UTC rounded to seconds. +type Clock struct{} + +// Now returns the UTC time rounded to seconds. +func (c *Clock) Now() time.Time { + return time.Now().UTC().Truncate(time.Second) +} + +var clock = new(Clock) diff --git a/authority/mgmt/db/nosql/provisioner.go b/authority/mgmt/db/nosql/provisioner.go new file mode 100644 index 00000000..93dcc47b --- /dev/null +++ b/authority/mgmt/db/nosql/provisioner.go @@ -0,0 +1,174 @@ +package nosql + +import ( + "context" + "encoding/json" + "time" + + "github.com/pkg/errors" + "github.com/smallstep/certificates/authority/mgmt" + "github.com/smallstep/nosql" +) + +// dbProvisioner is the database representation of a Provisioner type. +type dbProvisioner struct { + ID string `json:"id"` + AuthorityID string `json:"authorityID"` + Type string `json:"type"` + Name string `json:"name"` + Claims *mgmt.Claims `json:"claims"` + Details interface{} `json:"details"` + X509Template string `json:"x509Template"` + SSHTemplate string `json:"sshTemplate"` + CreatedAt time.Time `json:"createdAt"` + DeletedAt time.Time `json:"deletedAt"` +} + +func (dbp *dbProvisioner) clone() *dbProvisioner { + u := *dbp + return &u +} + +func (db *DB) getDBProvisionerBytes(ctx context.Context, id string) ([]byte, error) { + data, err := db.db.Get(authorityProvisionersTable, []byte(id)) + if nosql.IsErrNotFound(err) { + return nil, mgmt.NewError(mgmt.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) getDBProvisioner(ctx context.Context, id string) (*dbProvisioner, error) { + data, err := db.getDBProvisionerBytes(ctx, id) + if err != nil { + return nil, err + } + dbp, err := unmarshalDBProvisioner(data, id) + if err != nil { + return nil, err + } + if dbp.AuthorityID != db.authorityID { + return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType, + "provisioner %s is not owned by authority %s", dbp.ID, db.authorityID) + } + return dbp, nil +} + +// GetProvisioner retrieves and unmarshals a provisioner from the database. +func (db *DB) GetProvisioner(ctx context.Context, id string) (*mgmt.Provisioner, error) { + data, err := db.getDBProvisionerBytes(ctx, id) + if err != nil { + return nil, err + } + + prov, err := unmarshalProvisioner(data, id) + if err != nil { + return nil, err + } + if prov.Status == mgmt.StatusDeleted { + return nil, mgmt.NewError(mgmt.ErrorDeletedType, "provisioner %s is deleted", prov.ID) + } + if prov.AuthorityID != db.authorityID { + return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType, + "provisioner %s is not owned by authority %s", prov.ID, db.authorityID) + } + return prov, nil +} + +func 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) + } + return dbp, nil +} + +func unmarshalProvisioner(data []byte, id string) (*mgmt.Provisioner, error) { + dbp, err := unmarshalDBProvisioner(data, id) + if err != nil { + return nil, err + } + + prov := &mgmt.Provisioner{ + ID: dbp.ID, + Type: dbp.Type, + Name: dbp.Name, + Claims: dbp.Claims, + X509Template: dbp.X509Template, + SSHTemplate: dbp.SSHTemplate, + } + if !dbp.DeletedAt.IsZero() { + prov.Status = mgmt.StatusDeleted + } + return prov, nil +} + +// GetProvisioners retrieves and unmarshals all active (not deleted) provisioners +// from the database. +// TODO should we be paginating? +func (db *DB) GetProvisioners(ctx context.Context) ([]*mgmt.Provisioner, error) { + dbEntries, err := db.db.List(authorityProvisionersTable) + if err != nil { + return nil, errors.Wrap(err, "error loading provisioners") + } + var provs []*mgmt.Provisioner + for _, entry := range dbEntries { + prov, err := unmarshalProvisioner(entry.Value, string(entry.Key)) + if err != nil { + return nil, err + } + if prov.Status == mgmt.StatusDeleted { + continue + } + 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 *mgmt.Provisioner) error { + var err error + prov.ID, err = randID() + if err != nil { + return errors.Wrap(err, "error generating random id for provisioner") + } + + dbp := &dbProvisioner{ + ID: prov.ID, + AuthorityID: db.authorityID, + Type: prov.Type, + Name: prov.Name, + Claims: prov.Claims, + Details: prov.Details, + X509Template: prov.X509Template, + SSHTemplate: prov.SSHTemplate, + CreatedAt: clock.Now(), + } + + return db.save(ctx, dbp.ID, dbp, nil, "provisioner", authorityProvisionersTable) +} + +// UpdateProvisioner saves an updated provisioner to the database. +func (db *DB) UpdateProvisioner(ctx context.Context, prov *mgmt.Provisioner) error { + old, err := db.getDBProvisioner(ctx, prov.ID) + if err != nil { + return err + } + + nu := old.clone() + + // If the provisioner was active but is now deleted ... + if old.DeletedAt.IsZero() && prov.Status == mgmt.StatusDeleted { + nu.DeletedAt = clock.Now() + } + nu.Claims = prov.Claims + nu.Details = prov.Details + nu.X509Template = prov.X509Template + nu.SSHTemplate = prov.SSHTemplate + + return db.save(ctx, old.ID, nu, old, "provisioner", authorityProvisionersTable) +} diff --git a/authority/mgmt/errors.go b/authority/mgmt/errors.go new file mode 100644 index 00000000..f8b6fd65 --- /dev/null +++ b/authority/mgmt/errors.go @@ -0,0 +1,191 @@ +package mgmt + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "os" + + "github.com/pkg/errors" + "github.com/smallstep/certificates/errs" + "github.com/smallstep/certificates/logging" +) + +// ProblemType is the type of the ACME problem. +type ProblemType int + +const ( + // ErrorNotFoundType resource not found. + ErrorNotFoundType ProblemType = iota + // ErrorAuthorityMismatchType resource Authority ID does not match the + // context Authority ID. + ErrorAuthorityMismatchType + // ErrorDeletedType resource has been deleted. + ErrorDeletedType + // ErrorServerInternalType internal server error. + ErrorServerInternalType +) + +// String returns the string representation of the acme problem type, +// fulfilling the Stringer interface. +func (ap ProblemType) String() string { + switch ap { + case ErrorNotFoundType: + return "notFound" + case ErrorAuthorityMismatchType: + return "authorityMismatch" + case ErrorDeletedType: + return "deleted" + case ErrorServerInternalType: + return "internalServerError" + default: + return fmt.Sprintf("unsupported error type '%d'", int(ap)) + } +} + +type errorMetadata struct { + details string + status int + typ string + String string +} + +var ( + errorServerInternalMetadata = errorMetadata{ + typ: ErrorServerInternalType.String(), + details: "the server experienced an internal error", + status: 500, + } + errorMap = map[ProblemType]errorMetadata{ + ErrorNotFoundType: { + typ: ErrorNotFoundType.String(), + details: "resource not found", + status: 400, + }, + ErrorAuthorityMismatchType: { + typ: ErrorAuthorityMismatchType.String(), + details: "resource not owned by authority", + status: 401, + }, + ErrorDeletedType: { + typ: ErrorNotFoundType.String(), + details: "resource is deleted", + status: 403, + }, + ErrorServerInternalType: errorServerInternalMetadata, + } +) + +// Error represents an ACME +type Error struct { + Type string `json:"type"` + Detail string `json:"detail"` + Subproblems []interface{} `json:"subproblems,omitempty"` + Identifier interface{} `json:"identifier,omitempty"` + Err error `json:"-"` + Status int `json:"-"` +} + +// NewError creates a new Error type. +func NewError(pt ProblemType, msg string, args ...interface{}) *Error { + return newError(pt, errors.Errorf(msg, args...)) +} + +func newError(pt ProblemType, err error) *Error { + meta, ok := errorMap[pt] + if !ok { + meta = errorServerInternalMetadata + return &Error{ + Type: meta.typ, + Detail: meta.details, + Status: meta.status, + Err: err, + } + } + + return &Error{ + Type: meta.typ, + Detail: meta.details, + Status: meta.status, + Err: err, + } +} + +// NewErrorISE creates a new ErrorServerInternalType Error. +func NewErrorISE(msg string, args ...interface{}) *Error { + return NewError(ErrorServerInternalType, msg, args...) +} + +// WrapError attempts to wrap the internal error. +func WrapError(typ ProblemType, err error, msg string, args ...interface{}) *Error { + switch e := err.(type) { + case nil: + return nil + case *Error: + if e.Err == nil { + e.Err = errors.Errorf(msg+"; "+e.Detail, args...) + } else { + e.Err = errors.Wrapf(e.Err, msg, args...) + } + return e + default: + return newError(typ, errors.Wrapf(err, msg, args...)) + } +} + +// WrapErrorISE shortcut to wrap an internal server error type. +func WrapErrorISE(err error, msg string, args ...interface{}) *Error { + return WrapError(ErrorServerInternalType, err, msg, args...) +} + +// StatusCode returns the status code and implements the StatusCoder interface. +func (e *Error) StatusCode() int { + return e.Status +} + +// Error allows AError to implement the error interface. +func (e *Error) Error() string { + return e.Detail +} + +// Cause returns the internal error and implements the Causer interface. +func (e *Error) Cause() error { + if e.Err == nil { + return errors.New(e.Detail) + } + return e.Err +} + +// ToLog implements the EnableLogger interface. +func (e *Error) ToLog() (interface{}, error) { + b, err := json.Marshal(e) + if err != nil { + return nil, WrapErrorISE(err, "error marshaling authority.Error for logging") + } + return string(b), nil +} + +// WriteError writes to w a JSON representation of the given error. +func WriteError(w http.ResponseWriter, err *Error) { + w.Header().Set("Content-Type", "application/problem+json") + w.WriteHeader(err.StatusCode()) + + // Write errors in the response writer + if rl, ok := w.(logging.ResponseLogger); ok { + rl.WithFields(map[string]interface{}{ + "error": err.Err, + }) + if os.Getenv("STEPDEBUG") == "1" { + if e, ok := err.Err.(errs.StackTracer); ok { + rl.WithFields(map[string]interface{}{ + "stack-trace": fmt.Sprintf("%+v", e), + }) + } + } + } + + if err := json.NewEncoder(w).Encode(err); err != nil { + log.Println(err) + } +} diff --git a/authority/options.go b/authority/options.go index 9594f989..aaf8ffb3 100644 --- a/authority/options.go +++ b/authority/options.go @@ -7,6 +7,7 @@ import ( "encoding/pem" "github.com/pkg/errors" + "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/cas" casapi "github.com/smallstep/certificates/cas/apiv1" @@ -20,7 +21,7 @@ type Option func(*Authority) error // WithConfig replaces the current config with the given one. No validation is // performed in the given value. -func WithConfig(config *Config) Option { +func WithConfig(config *config.Config) Option { return func(a *Authority) error { a.config = config return nil @@ -31,7 +32,7 @@ func WithConfig(config *Config) Option { // the current one. No validation is performed in the given configuration. func WithConfigFile(filename string) Option { return func(a *Authority) (err error) { - a.config, err = LoadConfiguration(filename) + a.config, err = config.LoadConfiguration(filename) return } } @@ -56,7 +57,7 @@ func WithGetIdentityFunc(fn func(ctx context.Context, p provisioner.Interface, e // WithSSHBastionFunc sets a custom function to get the bastion for a // given user-host pair. -func WithSSHBastionFunc(fn func(ctx context.Context, user, host string) (*Bastion, error)) Option { +func WithSSHBastionFunc(fn func(ctx context.Context, user, host string) (*config.Bastion, error)) Option { return func(a *Authority) error { a.sshBastionFunc = fn return nil @@ -65,7 +66,7 @@ func WithSSHBastionFunc(fn func(ctx context.Context, user, host string) (*Bastio // WithSSHGetHosts sets a custom function to get the bastion for a // given user-host pair. -func WithSSHGetHosts(fn func(ctx context.Context, cert *x509.Certificate) ([]Host, error)) Option { +func WithSSHGetHosts(fn func(ctx context.Context, cert *x509.Certificate) ([]config.Host, error)) Option { return func(a *Authority) error { a.sshGetHostsFunc = fn return nil diff --git a/authority/provisioner/claims.go b/authority/provisioner/claims.go index 997d9ba3..6c87792c 100644 --- a/authority/provisioner/claims.go +++ b/authority/provisioner/claims.go @@ -7,6 +7,27 @@ import ( "golang.org/x/crypto/ssh" ) +type _Claims struct { + *X509Claims `json:"x509Claims"` + *SSHClaims `json:"sshClaims"` + DisableRenewal *bool `json:"disableRenewal"` +} + +type X509Claims struct { + Durations *Durations `json:"durations"` +} + +type SSHClaims struct { + UserDuration *Durations `json:"userDurations"` + HostDuration *Durations `json:"hostDuration"` +} + +type Durations struct { + Min string `json:"min"` + Max string `json:"max"` + Default string `json:"default"` +} + // Claims so that individual provisioners can override global claims. type Claims struct { // TLS CA properties diff --git a/authority/ssh.go b/authority/ssh.go index bb0ff562..335b6702 100644 --- a/authority/ssh.go +++ b/authority/ssh.go @@ -10,11 +10,11 @@ import ( "time" "github.com/pkg/errors" + "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/templates" - "go.step.sm/crypto/jose" "go.step.sm/crypto/randutil" "go.step.sm/crypto/sshutil" "golang.org/x/crypto/ssh" @@ -32,103 +32,17 @@ const ( SSHAddUserCommand = "sudo useradd -m ; nc -q0 localhost 22" ) -// SSHConfig contains the user and host keys. -type SSHConfig struct { - HostKey string `json:"hostKey"` - UserKey string `json:"userKey"` - Keys []*SSHPublicKey `json:"keys,omitempty"` - AddUserPrincipal string `json:"addUserPrincipal,omitempty"` - AddUserCommand string `json:"addUserCommand,omitempty"` - Bastion *Bastion `json:"bastion,omitempty"` -} - -// Bastion contains the custom properties used on bastion. -type Bastion struct { - Hostname string `json:"hostname"` - User string `json:"user,omitempty"` - Port string `json:"port,omitempty"` - Command string `json:"cmd,omitempty"` - Flags string `json:"flags,omitempty"` -} - -// HostTag are tagged with k,v pairs. These tags are how a user is ultimately -// associated with a host. -type HostTag struct { - ID string - Name string - Value string -} - -// Host defines expected attributes for an ssh host. -type Host struct { - HostID string `json:"hid"` - HostTags []HostTag `json:"host_tags"` - Hostname string `json:"hostname"` -} - -// Validate checks the fields in SSHConfig. -func (c *SSHConfig) Validate() error { - if c == nil { - return nil - } - for _, k := range c.Keys { - if err := k.Validate(); err != nil { - return err - } - } - return nil -} - -// SSHPublicKey contains a public key used by federated CAs to keep old signing -// keys for this ca. -type SSHPublicKey struct { - Type string `json:"type"` - Federated bool `json:"federated"` - Key jose.JSONWebKey `json:"key"` - publicKey ssh.PublicKey -} - -// Validate checks the fields in SSHPublicKey. -func (k *SSHPublicKey) Validate() error { - switch { - case k.Type == "": - return errors.New("type cannot be empty") - case k.Type != provisioner.SSHHostCert && k.Type != provisioner.SSHUserCert: - return errors.Errorf("invalid type %s, it must be user or host", k.Type) - case !k.Key.IsPublic(): - return errors.New("invalid key type, it must be a public key") - } - - key, err := ssh.NewPublicKey(k.Key.Key) - if err != nil { - return errors.Wrap(err, "error creating ssh key") - } - k.publicKey = key - return nil -} - -// PublicKey returns the ssh public key. -func (k *SSHPublicKey) PublicKey() ssh.PublicKey { - return k.publicKey -} - -// SSHKeys represents the SSH User and Host public keys. -type SSHKeys struct { - UserKeys []ssh.PublicKey - HostKeys []ssh.PublicKey -} - // GetSSHRoots returns the SSH User and Host public keys. -func (a *Authority) GetSSHRoots(context.Context) (*SSHKeys, error) { - return &SSHKeys{ +func (a *Authority) GetSSHRoots(context.Context) (*config.SSHKeys, error) { + return &config.SSHKeys{ HostKeys: a.sshCAHostCerts, UserKeys: a.sshCAUserCerts, }, nil } // GetSSHFederation returns the public keys for federated SSH signers. -func (a *Authority) GetSSHFederation(context.Context) (*SSHKeys, error) { - return &SSHKeys{ +func (a *Authority) GetSSHFederation(context.Context) (*config.SSHKeys, error) { + return &config.SSHKeys{ HostKeys: a.sshCAHostFederatedCerts, UserKeys: a.sshCAUserFederatedCerts, }, nil @@ -194,7 +108,7 @@ func (a *Authority) GetSSHConfig(ctx context.Context, typ string, data map[strin // GetSSHBastion returns the bastion configuration, for the given pair user, // hostname. -func (a *Authority) GetSSHBastion(ctx context.Context, user string, hostname string) (*Bastion, error) { +func (a *Authority) GetSSHBastion(ctx context.Context, user string, hostname string) (*config.Bastion, error) { if a.sshBastionFunc != nil { bs, err := a.sshBastionFunc(ctx, user, hostname) return bs, errs.Wrap(http.StatusInternalServerError, err, "authority.GetSSHBastion") @@ -568,7 +482,7 @@ func (a *Authority) CheckSSHHost(ctx context.Context, principal string, token st } // GetSSHHosts returns a list of valid host principals. -func (a *Authority) GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]Host, error) { +func (a *Authority) GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]config.Host, error) { if a.sshGetHostsFunc != nil { hosts, err := a.sshGetHostsFunc(ctx, cert) return hosts, errs.Wrap(http.StatusInternalServerError, err, "getSSHHosts") @@ -578,9 +492,9 @@ func (a *Authority) GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([] return nil, errs.Wrap(http.StatusInternalServerError, err, "getSSHHosts") } - hosts := make([]Host, len(hostnames)) + hosts := make([]config.Host, len(hostnames)) for i, hn := range hostnames { - hosts[i] = Host{Hostname: hn} + hosts[i] = config.Host{Hostname: hn} } return hosts, nil } diff --git a/authority/tls.go b/authority/tls.go index b7b2f936..1c8b3b8c 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -12,6 +12,7 @@ import ( "time" "github.com/pkg/errors" + "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/provisioner" casapi "github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/db" @@ -23,14 +24,14 @@ import ( ) // GetTLSOptions returns the tls options configured. -func (a *Authority) GetTLSOptions() *TLSOptions { +func (a *Authority) GetTLSOptions() *config.TLSOptions { return a.config.TLS } var oidAuthorityKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 35} var oidSubjectKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 14} -func withDefaultASN1DN(def *ASN1DN) provisioner.CertificateModifierFunc { +func withDefaultASN1DN(def *config.ASN1DN) provisioner.CertificateModifierFunc { return func(crt *x509.Certificate, opts provisioner.SignOptions) error { if def == nil { return errors.New("default ASN1DN template cannot be nil") diff --git a/ca/bootstrap.go b/ca/bootstrap.go index c9e859bf..5f06e986 100644 --- a/ca/bootstrap.go +++ b/ca/bootstrap.go @@ -39,6 +39,53 @@ func Bootstrap(token string) (*Client, error) { return NewClient(claims.Audience[0], WithRootSHA256(claims.SHA)) } +// BootstrapClient is a helper function that using the given bootstrap token +// return an http.Client configured with a Transport prepared to do TLS +// connections using the client certificate returned by the certificate +// authority. By default the server will kick off a routine that will renew the +// certificate after 2/3rd of the certificate's lifetime has expired. +// +// Usage: +// // Default example with certificate rotation. +// client, err := ca.BootstrapClient(ctx.Background(), token) +// +// // Example canceling automatic certificate rotation. +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() +// client, err := ca.BootstrapClient(ctx, token) +// if err != nil { +// return err +// } +// resp, err := client.Get("https://internal.smallstep.com") +func BootstrapClient(ctx context.Context, token string, options ...TLSOption) (*http.Client, error) { + client, err := Bootstrap(token) + if err != nil { + return nil, err + } + + req, pk, err := CreateSignRequest(token) + if err != nil { + return nil, err + } + + sign, err := client.Sign(req) + if err != nil { + return nil, err + } + + // Make sure the tlsConfig have all supported roots on RootCAs + options = append(options, AddRootsToRootCAs()) + + transport, err := client.Transport(ctx, sign, pk, options...) + if err != nil { + return nil, err + } + + return &http.Client{ + Transport: transport, + }, nil +} + // BootstrapServer is a helper function that using the given token returns the // given http.Server configured with a TLS certificate signed by the Certificate // Authority. By default the server will kick off a routine that will renew the @@ -100,53 +147,6 @@ func BootstrapServer(ctx context.Context, token string, base *http.Server, optio return base, nil } -// BootstrapClient is a helper function that using the given bootstrap token -// return an http.Client configured with a Transport prepared to do TLS -// connections using the client certificate returned by the certificate -// authority. By default the server will kick off a routine that will renew the -// certificate after 2/3rd of the certificate's lifetime has expired. -// -// Usage: -// // Default example with certificate rotation. -// client, err := ca.BootstrapClient(ctx.Background(), token) -// -// // Example canceling automatic certificate rotation. -// ctx, cancel := context.WithCancel(context.Background()) -// defer cancel() -// client, err := ca.BootstrapClient(ctx, token) -// if err != nil { -// return err -// } -// resp, err := client.Get("https://internal.smallstep.com") -func BootstrapClient(ctx context.Context, token string, options ...TLSOption) (*http.Client, error) { - client, err := Bootstrap(token) - if err != nil { - return nil, err - } - - req, pk, err := CreateSignRequest(token) - if err != nil { - return nil, err - } - - sign, err := client.Sign(req) - if err != nil { - return nil, err - } - - // Make sure the tlsConfig have all supported roots on RootCAs - options = append(options, AddRootsToRootCAs()) - - transport, err := client.Transport(ctx, sign, pk, options...) - if err != nil { - return nil, err - } - - return &http.Client{ - Transport: transport, - }, nil -} - // BootstrapListener is a helper function that using the given token returns a // TLS listener which accepts connections from an inner listener and wraps each // connection with Server. diff --git a/ca/ca.go b/ca/ca.go index e23be140..d9bcdd5e 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -16,6 +16,8 @@ import ( acmeNoSQL "github.com/smallstep/certificates/acme/db/nosql" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority" + "github.com/smallstep/certificates/authority/config" + "github.com/smallstep/certificates/authority/mgmt" "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/monitoring" @@ -74,14 +76,14 @@ func WithDatabase(db db.AuthDB) Option { // the HTTP server, set ups the middlewares and the HTTP handlers. type CA struct { auth *authority.Authority - config *authority.Config + config *config.Config srv *server.Server opts *options renewer *TLSRenewer } // New creates and initializes the CA with the given configuration and options. -func New(config *authority.Config, opts ...Option) (*CA, error) { +func New(config *config.Config, opts ...Option) (*CA, error) { ca := &CA{ config: config, opts: new(options), @@ -91,7 +93,7 @@ func New(config *authority.Config, opts ...Option) (*CA, error) { } // Init initializes the CA with the given configuration. -func (ca *CA) Init(config *authority.Config) (*CA, error) { +func (ca *CA) Init(config *config.Config) (*CA, error) { // Intermediate Password. if len(ca.opts.password) > 0 { ca.config.Password = string(ca.opts.password) @@ -146,7 +148,7 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { if config.DB == nil { acmeDB = nil } else { - acmeDB, err = acmeNoSQL.New(auth.GetDatabase().(nosql.DB)) + acmeDB, err = acmeNoSQL.New(auth.GetDatabase().(nosql.DB), mgmt.DefaultAuthorityID) if err != nil { return nil, errors.Wrap(err, "error configuring ACME DB interface") } @@ -218,7 +220,7 @@ func (ca *CA) Stop() error { // Reload reloads the configuration of the CA and calls to the server Reload // method. func (ca *CA) Reload() error { - config, err := authority.LoadConfiguration(ca.opts.configFile) + config, err := config.LoadConfiguration(ca.opts.configFile) if err != nil { return errors.Wrap(err, "error reloading ca configuration") } diff --git a/ca/mgmtClient.go b/ca/mgmtClient.go new file mode 100644 index 00000000..1172218f --- /dev/null +++ b/ca/mgmtClient.go @@ -0,0 +1,107 @@ +package ca + +import ( + "net/http" + "net/url" + "path" + + "github.com/pkg/errors" + "github.com/smallstep/certificates/authority/mgmt" +) + +// MgmtClient implements an HTTP client for the CA server. +type MgmtClient struct { + client *uaClient + endpoint *url.URL + retryFunc RetryFunc + opts []ClientOption +} + +// NewMgmtClient creates a new MgmtClient with the given endpoint and options. +func NewMgmtClient(endpoint string, opts ...ClientOption) (*MgmtClient, error) { + u, err := parseEndpoint(endpoint) + if err != nil { + return nil, err + } + // Retrieve transport from options. + o := new(clientOptions) + if err := o.apply(opts); err != nil { + return nil, err + } + tr, err := o.getTransport(endpoint) + if err != nil { + return nil, err + } + + return &MgmtClient{ + client: newClient(tr), + endpoint: u, + retryFunc: o.retryFunc, + opts: opts, + }, nil +} + +func (c *MgmtClient) retryOnError(r *http.Response) bool { + if c.retryFunc != nil { + if c.retryFunc(r.StatusCode) { + o := new(clientOptions) + if err := o.apply(c.opts); err != nil { + return false + } + tr, err := o.getTransport(c.endpoint.String()) + if err != nil { + return false + } + r.Body.Close() + c.client.SetTransport(tr) + return true + } + } + return false +} + +// GetAdmin performs the GET /config/admin/{id} request to the CA. +func (c *MgmtClient) GetAdmin(id string) (*mgmt.Admin, error) { + var retried bool + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/config/admin", 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, readError(resp.Body) + } + var adm = new(mgmt.Admin) + if err := readJSON(resp.Body, adm); err != nil { + return nil, errors.Wrapf(err, "error reading %s", u) + } + return adm, nil +} + +// GetAdmins performs the GET /config/admins request to the CA. +func (c *MgmtClient) GetAdmins() ([]*mgmt.Admin, error) { + var retried bool + u := c.endpoint.ResolveReference(&url.URL{Path: "/config/admins"}) +retry: + resp, err := c.client.Get(u.String()) + if err != nil { + return nil, errors.Wrapf(err, "client GET %s failed", u) + } + if resp.StatusCode >= 400 { + if !retried && c.retryOnError(resp) { + retried = true + goto retry + } + return nil, readError(resp.Body) + } + var admins = new([]*mgmt.Admin) + if err := readJSON(resp.Body, admins); err != nil { + return nil, errors.Wrapf(err, "error reading %s", u) + } + return *admins, nil +} diff --git a/ca/tls.go b/ca/tls.go index e4f585fe..cb9f4707 100644 --- a/ca/tls.go +++ b/ca/tls.go @@ -103,7 +103,6 @@ func (c *Client) getClientTLSConfig(ctx context.Context, sign *api.SignResponse, return nil, nil, err } - // Update renew function with transport tr := getDefaultTransport(tlsConfig) // Use mutable tls.Config on renew tr.DialTLS = c.buildDialTLS(tlsCtx) // nolint:staticcheck diff --git a/commands/app.go b/commands/app.go index aff9d473..8833726c 100644 --- a/commands/app.go +++ b/commands/app.go @@ -11,7 +11,7 @@ import ( "unicode" "github.com/pkg/errors" - "github.com/smallstep/certificates/authority" + "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/ca" "github.com/urfave/cli" "go.step.sm/cli-utils/errs" @@ -56,7 +56,7 @@ func appAction(ctx *cli.Context) error { } configFile := ctx.Args().Get(0) - config, err := authority.LoadConfiguration(configFile) + config, err := config.LoadConfiguration(configFile) if err != nil { fatal(err) } diff --git a/commands/onboard.go b/commands/onboard.go index 13c32304..251a4b47 100644 --- a/commands/onboard.go +++ b/commands/onboard.go @@ -9,7 +9,7 @@ import ( "os" "github.com/pkg/errors" - "github.com/smallstep/certificates/authority" + "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/ca" "github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/pki" @@ -162,7 +162,7 @@ func onboardAction(ctx *cli.Context) error { return nil } -func onboardPKI(config onboardingConfiguration) (*authority.Config, string, error) { +func onboardPKI(config onboardingConfiguration) (*config.Config, string, error) { p, err := pki.New(apiv1.Options{ Type: apiv1.SoftCAS, IsCreator: true, diff --git a/pki/pki.go b/pki/pki.go index c95ca985..e4e7bad3 100644 --- a/pki/pki.go +++ b/pki/pki.go @@ -19,7 +19,7 @@ import ( "time" "github.com/pkg/errors" - "github.com/smallstep/certificates/authority" + authconfig "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/ca" "github.com/smallstep/certificates/cas" @@ -481,12 +481,12 @@ type caDefaults struct { } // Option is the type for modifiers over the auth config object. -type Option func(c *authority.Config) error +type Option func(c *authconfig.Config) error // WithDefaultDB is a configuration modifier that adds a default DB stanza to // the authority config. func WithDefaultDB() Option { - return func(c *authority.Config) error { + return func(c *authconfig.Config) error { c.DB = &db.Config{ Type: "badger", DataSource: GetDBPath(), @@ -498,14 +498,14 @@ func WithDefaultDB() Option { // WithoutDB is a configuration modifier that adds a default DB stanza to // the authority config. func WithoutDB() Option { - return func(c *authority.Config) error { + return func(c *authconfig.Config) error { c.DB = nil return nil } } // GenerateConfig returns the step certificates configuration. -func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) { +func (p *PKI) GenerateConfig(opt ...Option) (*authconfig.Config, error) { key, err := p.ottPrivateKey.CompactSerialize() if err != nil { return nil, errors.Wrap(err, "error serializing private key") @@ -523,7 +523,7 @@ func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) { authorityOptions = &p.casOptions } - config := &authority.Config{ + config := &authconfig.Config{ Root: []string{p.root}, FederatedRoots: []string{}, IntermediateCert: p.intermediate, @@ -535,22 +535,22 @@ func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) { Type: "badger", DataSource: GetDBPath(), }, - AuthorityConfig: &authority.AuthConfig{ + AuthorityConfig: &authconfig.AuthConfig{ Options: authorityOptions, DisableIssuedAtCheck: false, Provisioners: provisioner.List{prov}, }, - TLS: &authority.TLSOptions{ - MinVersion: authority.DefaultTLSMinVersion, - MaxVersion: authority.DefaultTLSMaxVersion, - Renegotiation: authority.DefaultTLSRenegotiation, - CipherSuites: authority.DefaultTLSCipherSuites, + TLS: &authconfig.TLSOptions{ + MinVersion: authconfig.DefaultTLSMinVersion, + MaxVersion: authconfig.DefaultTLSMaxVersion, + Renegotiation: authconfig.DefaultTLSRenegotiation, + CipherSuites: authconfig.DefaultTLSCipherSuites, }, Templates: p.getTemplates(), } if p.enableSSH { enableSSHCA := true - config.SSH = &authority.SSHConfig{ + config.SSH = &authconfig.SSHConfig{ HostKey: p.sshHostKey, UserKey: p.sshUserKey, } From 2f60f20b0b2e2df785affa893a09e764b2030bd6 Mon Sep 17 00:00:00 2001 From: max furman Date: Wed, 5 May 2021 23:02:42 -0700 Subject: [PATCH 02/89] lots of codes --- authority/authority.go | 24 +- authority/config/config.go | 13 +- authority/mgmt/admin.go | 31 +++ authority/mgmt/authConfig.go | 77 ++++++ authority/mgmt/config.go | 349 ++++----------------------- authority/mgmt/db/nosql/admin.go | 44 ++-- authority/mgmt/provisioner.go | 401 +++++++++++++++++++++++++++++++ 7 files changed, 600 insertions(+), 339 deletions(-) create mode 100644 authority/mgmt/admin.go create mode 100644 authority/mgmt/authConfig.go create mode 100644 authority/mgmt/provisioner.go diff --git a/authority/authority.go b/authority/authority.go index fcb01a03..53a3ee72 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -146,17 +146,19 @@ func (a *Authority) init() error { // Pull AuthConfig from DB. if true { - mgmtDB, err := authMgmtNosql.New(a.db.(nosql.DB), mgmt.DefaultAuthorityID) - if err != nil { - return err - } - _ac, err := mgmtDB.GetAuthConfig(context.Background(), mgmt.DefaultAuthorityID) - if err != nil { - return err - } - a.config.AuthorityConfig, err = _ac.ToCertificates() - if err != nil { - return err + if len(a.config.AuthConfig.AuthorityID)_== 0 { + mgmtDB, err := authMgmtNosql.New(a.db.(nosql.DB), mgmt.DefaultAuthorityID) + if err != nil { + return err + } + mgmtAuthConfig, err := mgmt.CreateAuthority(context.Background, mgmtDB, WithDefaultAuthorityID) + if err != nil { + return err + } + a.config.AuthConfig, err := mgmtAuthConfig.ToCertificates() + if err != nil { + return err + } } } diff --git a/authority/config/config.go b/authority/config/config.go index 1f6b7619..852641c2 100644 --- a/authority/config/config.go +++ b/authority/config/config.go @@ -32,23 +32,23 @@ var ( MaxVersion: 1.2, Renegotiation: false, } - defaultBackdate = time.Minute - defaultDisableRenewal = false - defaultEnableSSHCA = false + DefaultBackdate = time.Minute + DefaultDisableRenewal = false + DefaultEnableSSHCA = false // GlobalProvisionerClaims default claims for the Authority. Can be overriden // by provisioner specific claims. GlobalProvisionerClaims = provisioner.Claims{ MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute}, // TLS certs MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour}, DefaultTLSDur: &provisioner.Duration{Duration: 24 * time.Hour}, - DisableRenewal: &defaultDisableRenewal, + DisableRenewal: &DefaultDisableRenewal, MinUserSSHDur: &provisioner.Duration{Duration: 5 * time.Minute}, // User SSH certs MaxUserSSHDur: &provisioner.Duration{Duration: 24 * time.Hour}, DefaultUserSSHDur: &provisioner.Duration{Duration: 16 * time.Hour}, MinHostSSHDur: &provisioner.Duration{Duration: 5 * time.Minute}, // Host SSH certs MaxHostSSHDur: &provisioner.Duration{Duration: 30 * 24 * time.Hour}, DefaultHostSSHDur: &provisioner.Duration{Duration: 30 * 24 * time.Hour}, - EnableSSHCA: &defaultEnableSSHCA, + EnableSSHCA: &DefaultEnableSSHCA, } ) @@ -88,6 +88,7 @@ type ASN1DN struct { // cas.Options. type AuthConfig struct { *cas.Options + AuthorityID string `json:"authorityID,omitempty"` Provisioners provisioner.List `json:"provisioners"` Template *ASN1DN `json:"template,omitempty"` Claims *provisioner.Claims `json:"claims,omitempty"` @@ -106,7 +107,7 @@ func (c *AuthConfig) init() { } if c.Backdate == nil { c.Backdate = &provisioner.Duration{ - Duration: defaultBackdate, + Duration: DefaultBackdate, } } } diff --git a/authority/mgmt/admin.go b/authority/mgmt/admin.go new file mode 100644 index 00000000..8a4104a5 --- /dev/null +++ b/authority/mgmt/admin.go @@ -0,0 +1,31 @@ +package mgmt + +import "context" + +// Admin type. +type Admin struct { + ID string `json:"-"` + AuthorityID string `json:"-"` + ProvisionerID string `json:"provisionerID"` + Name string `json:"name"` + ProvisionerName string `json:"provisionerName"` + ProvisionerType string `json:"provisionerType"` + IsSuperAdmin bool `json:"isSuperAdmin"` + Status StatusType `json:"status"` +} + +// CreateAdmin builds and stores an admin type in the DB. +func CreateAdmin(ctx context.Context, db DB, name string, prov *Provisioner, isSuperAdmin bool) (*Admin, error) { + adm := &Admin{ + Name: name, + ProvisionerID: prov.ID, + ProvisionerName: prov.Name, + ProvisionerType: prov.Type, + IsSuperAdmin: isSuperAdmin, + Status: StatusActive, + } + if err := db.CreateAdmin(ctx, adm); err != nil { + return nil, WrapErrorISE(err, "error creating admin") + } + return adm, nil +} diff --git a/authority/mgmt/authConfig.go b/authority/mgmt/authConfig.go new file mode 100644 index 00000000..595b2ed0 --- /dev/null +++ b/authority/mgmt/authConfig.go @@ -0,0 +1,77 @@ +package mgmt + +import ( + "github.com/smallstep/certificates/authority/config" + "github.com/smallstep/certificates/authority/provisioner" +) + +// AuthConfig represents the Authority Configuration. +type AuthConfig struct { + //*cas.Options `json:"cas"` + ID string `json:"id"` + ASN1DN *config.ASN1DN `json:"template,omitempty"` + Provisioners []*Provisioner `json:"-"` + Admins []*Admin `json:"-"` + Claims *Claims `json:"claims,omitempty"` + Backdate string `json:"backdate,omitempty"` + Status StatusType `json:"status,omitempty"` +} + +func NewDefaultAuthConfig() *AuthConfig { + return &AuthConfig{ + Claims: &Claims{ + X509: &X509Claims{ + Durations: &Durations{ + Min: config.GlobalProvisionerClaims.MinTLSDur.String(), + Max: config.GlobalProvisionerClaims.MaxTLSDur.String(), + Default: config.GlobalProvisionerClaims.DefaultTLSDur.String(), + }, + }, + SSH: &SSHClaims{ + UserDurations: &Durations{ + Min: config.GlobalProvisionerClaims.MinUserSSHDur.String(), + Max: config.GlobalProvisionerClaims.MaxUserSSHDur.String(), + Default: config.GlobalProvisionerClaims.DefaultUserSSHDur.String(), + }, + HostDurations: &Durations{ + Min: config.GlobalProvisionerClaims.MinHostSSHDur.String(), + Max: config.GlobalProvisionerClaims.MaxHostSSHDur.String(), + Default: config.GlobalProvisionerClaims.DefaultHostSSHDur.String(), + }, + }, + DisableRenewal: config.DefaultDisableRenewal, + }, + Backdate: config.DefaultBackdate.String(), + Status: StatusActive, + } +} + +// ToCertificates converts a mgmt AuthConfig to configuration that can be +// directly used by the `step-ca` process. Resources are normalized and +// initialized. +func (ac *AuthConfig) ToCertificates() (*config.AuthConfig, error) { + claims, err := ac.Claims.ToCertificates() + if err != nil { + return nil, err + } + backdate, err := provisioner.NewDuration(ac.Backdate) + if err != nil { + return nil, WrapErrorISE(err, "error converting backdate %s to duration", ac.Backdate) + } + var provs []provisioner.Interface + for _, p := range ac.Provisioners { + authProv, err := p.ToCertificates() + if err != nil { + return nil, err + } + provs = append(provs, authProv) + } + return &config.AuthConfig{ + AuthorityID: ac.ID, + Provisioners: provs, + Template: ac.ASN1DN, + Claims: claims, + DisableIssuedAtCheck: false, + Backdate: backdate, + }, nil +} diff --git a/authority/mgmt/config.go b/authority/mgmt/config.go index b5bebceb..db70e6e5 100644 --- a/authority/mgmt/config.go +++ b/authority/mgmt/config.go @@ -1,11 +1,15 @@ package mgmt import ( - "github.com/smallstep/certificates/authority/config" - authority "github.com/smallstep/certificates/authority/config" + "context" + + "github.com/pkg/errors" ) const ( + // DefaultAuthorityID is the default AuthorityID. This will be the ID + // of the first Authority created, as well as the default AuthorityID + // if one is not specified in the configuration. DefaultAuthorityID = "00000000-0000-0000-0000-000000000000" ) @@ -19,339 +23,76 @@ const ( StatusDeleted ) +// Claims encapsulates all x509 and ssh claims applied to the authority +// configuration. E.g. maxTLSCertDuration, defaultSSHCertDuration, etc. type Claims struct { - *X509Claims `json:"x509Claims"` - *SSHClaims `json:"sshClaims"` - DisableRenewal *bool `json:"disableRenewal"` + X509 *X509Claims `json:"x509Claims"` + SSH *SSHClaims `json:"sshClaims"` + DisableRenewal bool `json:"disableRenewal"` } +// X509Claims are the x509 claims applied to the authority. type X509Claims struct { Durations *Durations `json:"durations"` } +// SSHClaims are the ssh claims applied to the authority. type SSHClaims struct { - UserDuration *Durations `json:"userDurations"` - HostDuration *Durations `json:"hostDuration"` + Enabled bool `json:"enabled"` + UserDurations *Durations `json:"userDurations"` + HostDurations *Durations `json:"hostDurations"` } +// Durations represents min, max, default, duration. type Durations struct { Min string `json:"min"` Max string `json:"max"` Default string `json:"default"` } -// Admin type. -type Admin struct { - ID string `json:"-"` - AuthorityID string `json:"-"` - Name string `json:"name"` - Provisioner string `json:"provisioner"` - IsSuperAdmin bool `json:"isSuperAdmin"` - Status StatusType `json:"status"` +type AuthorityOption func(*AuthConfig) error + +func WithDefaultAuthorityID(ac *AuthConfig) error { + ac.ID = DefaultAuthorityID + return nil } -// Provisioner type. -type Provisioner struct { - ID string `json:"-"` - AuthorityID string `json:"-"` - Type string `json:"type"` - Name string `json:"name"` - Claims *Claims `json:"claims"` - Details interface{} `json:"details"` - X509Template string `json:"x509Template"` - SSHTemplate string `json:"sshTemplate"` - Status StatusType `json:"status"` +func CreateDefaultAuthority(ctx context.Context, db DB) (*AuthConfig, error) { + options := []AuthorityOption{WithDefaultAuthorityID} + + return CreateAuthority(ctx, db, options...) } -// AuthConfig represents the Authority Configuration. -type AuthConfig struct { - //*cas.Options `json:"cas"` - ID string `json:"id"` - ASN1DN *config.ASN1DN `json:"template,omitempty"` - Provisioners []*Provisioner `json:"-"` - Claims *Claims `json:"claims,omitempty"` - DisableIssuedAtCheck bool `json:"disableIssuedAtCheck,omitempty"` - Backdate string `json:"backdate,omitempty"` - Status StatusType `json:"status,omitempty"` -} +func CreateAuthority(ctx context.Context, db DB, options ...AuthorityOption) (*AuthConfig, error) { + ac := NewDefaultAuthConfig() -func (ac *AuthConfig) ToCertificates() (*config.AuthConfig, error) { - return &authority.AuthConfig{}, nil -} - -/* -// ToCertificates converts the landlord provisioner type to the open source -// provisioner type. -func (p *Provisioner) ToCertificates(ctx context.Context, db database.DB) (provisioner.Interface, error) { - claims, err := p.Claims.ToCertificates() - if err != nil { - return nil, err - } - - details := p.Details.GetData() - if details == nil { - return nil, fmt.Errorf("provisioner does not have any details") - } - - options, err := p.getOptions(ctx, db) - if err != nil { - return nil, err - } - - switch d := details.(type) { - case *ProvisionerDetails_JWK: - k := d.JWK.GetKey() - jwk := new(jose.JSONWebKey) - if err := json.Unmarshal(k.Key.Public, &jwk); err != nil { + for _, o := range options { + if err := o(ac); err != nil { return nil, err } - return &provisioner.JWK{ - Type: p.Type.String(), - Name: p.Name, - Key: jwk, - EncryptedKey: string(k.Key.Private), - Claims: claims, - Options: options, - }, 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.String()) } -} -// ToCertificates converts the landlord provisioner claims type to the open source -// (step-ca) claims type. -func (c *Claims) ToCertificates() (*provisioner.Claims, error) { - x509, ssh := c.GetX509(), c.GetSsh() - x509Durations := x509.GetDurations() - hostDurations := ssh.GetHostDurations() - userDurations := ssh.GetUserDurations() - enableSSHCA := ssh.GetEnabled() - return &provisioner.Claims{ - MinTLSDur: durationPtr(x509Durations.GetMin()), - MaxTLSDur: durationPtr(x509Durations.GetMax()), - DefaultTLSDur: durationPtr(x509Durations.GetDefault()), - DisableRenewal: &c.DisableRenewal, - MinUserSSHDur: durationPtr(userDurations.GetMin()), - MaxUserSSHDur: durationPtr(userDurations.GetMax()), - DefaultUserSSHDur: durationPtr(userDurations.GetDefault()), - MinHostSSHDur: durationPtr(hostDurations.GetMin()), - MaxHostSSHDur: durationPtr(hostDurations.GetMax()), - DefaultHostSSHDur: durationPtr(hostDurations.GetDefault()), - EnableSSHCA: &enableSSHCA, - }, nil -} + if err := db.CreateAuthConfig(ctx, ac); err != nil { + return nil, errors.Wrap(err, "error creating authConfig") + } -func durationPtr(d *duration.Duration) *provisioner.Duration { - if d == nil { - return nil - } - return &provisioner.Duration{ - Duration: time.Duration(d.Seconds)*time.Second + time.Duration(d.Nanos)*time.Nanosecond, - } -} + // Generate default JWK provisioner. -func durationValue(d *duration.Duration) provisioner.Duration { - if d == nil { - return provisioner.Duration{} - } - return provisioner.Duration{ - Duration: time.Duration(d.Seconds)*time.Second + time.Duration(d.Nanos)*time.Nanosecond, - } -} - -func marshalDetails(d *ProvisionerDetails) (sql.NullString, error) { - b, err := json.Marshal(d.GetData()) + provOpts := []ProvisionerOption{WithPassword("pass")} + prov, err := CreateProvisioner(ctx, db, "JWK", "changeme", provOpts...) if err != nil { - return sql.NullString{}, nil - } - return sql.NullString{ - String: string(b), - Valid: len(b) > 0, - }, nil -} - -func unmarshalDetails(ctx context.Context, db database.DB, typ ProvisionerType, s sql.NullString) (*ProvisionerDetails, error) { - if !s.Valid { - return nil, nil - } - var v isProvisionerDetails_Data - switch typ { - case ProvisionerType_JWK: - p := new(ProvisionerDetails_JWK) - if err := json.Unmarshal([]byte(s.String), p); err != nil { - return nil, err - } - if p.JWK.Key.Key == nil { - key, err := LoadKey(ctx, db, p.JWK.Key.Id.Id) - if err != nil { - return nil, err - } - p.JWK.Key = key - } - return &ProvisionerDetails{Data: p}, nil - case ProvisionerType_OIDC: - v = new(ProvisionerDetails_OIDC) - case ProvisionerType_GCP: - v = new(ProvisionerDetails_GCP) - case ProvisionerType_AWS: - v = new(ProvisionerDetails_AWS) - case ProvisionerType_AZURE: - v = new(ProvisionerDetails_Azure) - case ProvisionerType_ACME: - v = new(ProvisionerDetails_ACME) - case ProvisionerType_X5C: - p := new(ProvisionerDetails_X5C) - if err := json.Unmarshal([]byte(s.String), p); err != nil { - return nil, err - } - for _, k := range p.X5C.GetRoots() { - if err := k.Select(ctx, db, k.Id.Id); err != nil { - return nil, err - } - } - return &ProvisionerDetails{Data: p}, nil - case ProvisionerType_K8SSA: - p := new(ProvisionerDetails_K8SSA) - if err := json.Unmarshal([]byte(s.String), p); err != nil { - return nil, err - } - for _, k := range p.K8SSA.GetPublicKeys() { - if err := k.Select(ctx, db, k.Id.Id); err != nil { - return nil, err - } - } - return &ProvisionerDetails{Data: p}, nil - case ProvisionerType_SSHPOP: - v = new(ProvisionerDetails_SSHPOP) - default: - return nil, fmt.Errorf("unsupported provisioner type %s", typ) + // TODO should we try to clean up? + return nil, WrapErrorISE(err, "error creating first provisioner") } - if err := json.Unmarshal([]byte(s.String), v); err != nil { - return nil, err - } - return &ProvisionerDetails{Data: v}, nil -} - -func marshalClaims(c *Claims) (sql.NullString, error) { - b, err := json.Marshal(c) + admin, err := CreateAdmin(ctx, db, "Change Me", prov, true) if err != nil { - return sql.NullString{}, nil + // TODO should we try to clean up? + return nil, WrapErrorISE(err, "error creating first provisioner") } - return sql.NullString{ - String: string(b), - Valid: len(b) > 0, - }, nil -} -func unmarshalClaims(s sql.NullString) (*Claims, error) { - if !s.Valid { - return nil, nil - } - v := new(Claims) - return v, json.Unmarshal([]byte(s.String), v) + ac.Provisioners = []*Provisioner{prov} + ac.Admins = []*Admin{admin} + + return ac, nil } -*/ diff --git a/authority/mgmt/db/nosql/admin.go b/authority/mgmt/db/nosql/admin.go index 8b33110c..cba8c59d 100644 --- a/authority/mgmt/db/nosql/admin.go +++ b/authority/mgmt/db/nosql/admin.go @@ -13,13 +13,13 @@ import ( // dbAdmin is the database representation of the Admin type. type dbAdmin struct { - ID string `json:"id"` - AuthorityID string `json:"authorityID"` - Name string `json:"name"` - Provisioner string `json:"provisioner"` - IsSuperAdmin bool `json:"isSuperAdmin"` - CreatedAt time.Time `json:"createdAt"` - DeletedAt time.Time `json:"deletedAt"` + ID string `json:"id"` + AuthorityID string `json:"authorityID"` + ProvisionerID string `json:"provisionerID"` + Name string `json:"name"` + IsSuperAdmin bool `json:"isSuperAdmin"` + CreatedAt time.Time `json:"createdAt"` + DeletedAt time.Time `json:"deletedAt"` } func (dbp *dbAdmin) clone() *dbAdmin { @@ -70,6 +70,13 @@ func (db *DB) GetAdmin(ctx context.Context, id string) (*mgmt.Admin, error) { return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType, "admin %s is not owned by authority %s", adm.ID, db.authorityID) } + + prov, err := db.GetProvisioner(ctx, adm.ProvisionerID) + if err != nil { + return nil, err + } + adm.ProvisionerName = prov.Name + adm.ProvisionerType = prov.Type return adm, nil } @@ -87,10 +94,11 @@ func unmarshalAdmin(data []byte, id string) (*mgmt.Admin, error) { return nil, errors.Wrapf(err, "error unmarshaling admin %s into dbAdmin", id) } adm := &mgmt.Admin{ - ID: dba.ID, - Name: dba.Name, - Provisioner: dba.Provisioner, - IsSuperAdmin: dba.IsSuperAdmin, + ID: dba.ID, + AuthorityID: dba.AuthorityID, + ProvisionerID: dba.ProvisionerID, + Name: dba.Name, + IsSuperAdmin: dba.IsSuperAdmin, } if !dba.DeletedAt.IsZero() { adm.Status = mgmt.StatusDeleted @@ -132,12 +140,12 @@ func (db *DB) CreateAdmin(ctx context.Context, adm *mgmt.Admin) error { } dba := &dbAdmin{ - ID: adm.ID, - AuthorityID: db.authorityID, - Name: adm.Name, - Provisioner: adm.Provisioner, - IsSuperAdmin: adm.IsSuperAdmin, - CreatedAt: clock.Now(), + ID: adm.ID, + AuthorityID: db.authorityID, + ProvisionerID: adm.ProvisionerID, + Name: adm.Name, + IsSuperAdmin: adm.IsSuperAdmin, + CreatedAt: clock.Now(), } return db.save(ctx, dba.ID, dba, nil, "admin", authorityAdminsTable) @@ -156,7 +164,7 @@ func (db *DB) UpdateAdmin(ctx context.Context, adm *mgmt.Admin) error { if old.DeletedAt.IsZero() && adm.Status == mgmt.StatusDeleted { nu.DeletedAt = clock.Now() } - nu.Provisioner = adm.Provisioner + nu.ProvisionerID = adm.ProvisionerID nu.IsSuperAdmin = adm.IsSuperAdmin return db.save(ctx, old.ID, nu, old, "admin", authorityAdminsTable) diff --git a/authority/mgmt/provisioner.go b/authority/mgmt/provisioner.go new file mode 100644 index 00000000..2cb21403 --- /dev/null +++ b/authority/mgmt/provisioner.go @@ -0,0 +1,401 @@ +package mgmt + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/smallstep/certificates/authority/provisioner" + "go.step.sm/crypto/jose" +) + +type ProvisionerOption func(*ProvisionerCtx) + +type ProvisionerCtx struct { + JWK *jose.JSONWebKey + JWE *jose.JSONWebEncryption + X509Template, SSHTemplate string + X509TemplateData, SSHTemplateData []byte + Claims *Claims + Password string +} + +func WithJWK(jwk *jose.JSONWebKey, jwe *jose.JSONWebEncryption) func(*ProvisionerCtx) { + return func(ctx *ProvisionerCtx) { + ctx.JWK = jwk + ctx.JWE = jwe + } +} + +func WithPassword(pass string) func(*ProvisionerCtx) { + return func(ctx *ProvisionerCtx) { + ctx.Password = pass + } +} + +// Provisioner type. +type Provisioner struct { + ID string `json:"-"` + AuthorityID string `json:"-"` + Type string `json:"type"` + Name string `json:"name"` + Claims *Claims `json:"claims"` + Details interface{} `json:"details"` + X509Template string `json:"x509Template"` + X509TemplateData []byte `json:"x509TemplateData"` + SSHTemplate string `json:"sshTemplate"` + SSHTemplateData []byte `json:"sshTemplateData"` + Status StatusType `json:"status"` +} + +func (p *Provisioner) GetOptions() *provisioner.Options { + return &provisioner.Options{ + X509: &provisioner.X509Options{ + Template: p.X509Template, + TemplateData: p.X509TemplateData, + }, + SSH: &provisioner.SSHOptions{ + Template: p.SSHTemplate, + TemplateData: p.SSHTemplateData, + }, + } +} + +func CreateProvisioner(ctx context.Context, db DB, typ, name string, opts ...ProvisionerOption) (*Provisioner, error) { + pc := new(ProvisionerCtx) + for _, o := range opts { + o(pc) + } + + details, err := createJWKDetails(pc) + if err != nil { + return nil, err + } + + p := &Provisioner{ + Type: typ, + Name: name, + Claims: pc.Claims, + Details: details, + X509Template: pc.X509Template, + X509TemplateData: pc.X509TemplateData, + SSHTemplate: pc.SSHTemplate, + SSHTemplateData: pc.SSHTemplateData, + Status: StatusActive, + } + + if err := db.CreateProvisioner(ctx, p); err != nil { + return nil, WrapErrorISE(err, "error creating provisioner") + } + return p, nil +} + +type ProvisionerDetails_JWK struct { + PubKey []byte `json:"pubKey"` + PrivKey string `json:"privKey"` +} + +func createJWKDetails(pc *ProvisionerCtx) (*ProvisionerDetails_JWK, error) { + var err error + + if pc.JWK != nil && pc.JWE == nil { + return nil, NewErrorISE("JWE is required with JWK for createJWKProvisioner") + } + if pc.JWE != nil && pc.JWK == nil { + return nil, NewErrorISE("JWK is required with JWE for createJWKProvisioner") + } + if pc.JWK == nil && pc.JWE == nil { + // Create a new JWK w/ encrypted private key. + if pc.Password == "" { + return nil, NewErrorISE("password is required to provisioner with new keys") + } + pc.JWK, pc.JWE, err = jose.GenerateDefaultKeyPair([]byte(pc.Password)) + if err != nil { + return nil, WrapErrorISE(err, "error generating JWK key pair") + } + } + + jwkPubBytes, err := pc.JWK.MarshalJSON() + if err != nil { + return nil, WrapErrorISE(err, "error marshaling JWK") + } + jwePrivStr, err := pc.JWE.CompactSerialize() + if err != nil { + return nil, WrapErrorISE(err, "error serializing JWE") + } + + return &ProvisionerDetails_JWK{ + PubKey: jwkPubBytes, + PrivKey: jwePrivStr, + }, nil +} + +// ToCertificates converts the landlord provisioner type to the open source +// provisioner type. +func (p *Provisioner) ToCertificates() (provisioner.Interface, error) { + claims, err := p.Claims.ToCertificates() + if err != nil { + return nil, err + } + + if err != nil { + return nil, err + } + + switch details := p.Details.(type) { + case *ProvisionerDetails_JWK: + jwk := new(jose.JSONWebKey) + if err := json.Unmarshal(details.PubKey, &jwk); err != nil { + return nil, err + } + return &provisioner.JWK{ + Type: p.Type, + Name: p.Name, + Key: jwk, + EncryptedKey: details.PrivKey, + Claims: claims, + Options: p.GetOptions(), + }, 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) + } +} + +// ToCertificates converts the landlord provisioner claims type to the open source +// (step-ca) claims type. +func (c *Claims) ToCertificates() (*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, + }, nil +} + +/* +func marshalDetails(d *ProvisionerDetails) (sql.NullString, error) { + b, err := json.Marshal(d.GetData()) + if err != nil { + return sql.NullString{}, nil + } + return sql.NullString{ + String: string(b), + Valid: len(b) > 0, + }, nil +} + +func unmarshalDetails(ctx context.Context, db database.DB, typ ProvisionerType, s sql.NullString) (*ProvisionerDetails, error) { + if !s.Valid { + return nil, nil + } + var v isProvisionerDetails_Data + switch typ { + case ProvisionerType_JWK: + p := new(ProvisionerDetails_JWK) + if err := json.Unmarshal([]byte(s.String), p); err != nil { + return nil, err + } + if p.JWK.Key.Key == nil { + key, err := LoadKey(ctx, db, p.JWK.Key.Id.Id) + if err != nil { + return nil, err + } + p.JWK.Key = key + } + return &ProvisionerDetails{Data: p}, nil + case ProvisionerType_OIDC: + v = new(ProvisionerDetails_OIDC) + case ProvisionerType_GCP: + v = new(ProvisionerDetails_GCP) + case ProvisionerType_AWS: + v = new(ProvisionerDetails_AWS) + case ProvisionerType_AZURE: + v = new(ProvisionerDetails_Azure) + case ProvisionerType_ACME: + v = new(ProvisionerDetails_ACME) + case ProvisionerType_X5C: + p := new(ProvisionerDetails_X5C) + if err := json.Unmarshal([]byte(s.String), p); err != nil { + return nil, err + } + for _, k := range p.X5C.GetRoots() { + if err := k.Select(ctx, db, k.Id.Id); err != nil { + return nil, err + } + } + return &ProvisionerDetails{Data: p}, nil + case ProvisionerType_K8SSA: + p := new(ProvisionerDetails_K8SSA) + if err := json.Unmarshal([]byte(s.String), p); err != nil { + return nil, err + } + for _, k := range p.K8SSA.GetPublicKeys() { + if err := k.Select(ctx, db, k.Id.Id); err != nil { + return nil, err + } + } + return &ProvisionerDetails{Data: p}, nil + case ProvisionerType_SSHPOP: + v = new(ProvisionerDetails_SSHPOP) + default: + return nil, fmt.Errorf("unsupported provisioner type %s", typ) + } + + if err := json.Unmarshal([]byte(s.String), v); err != nil { + return nil, err + } + return &ProvisionerDetails{Data: v}, nil +} + +func marshalClaims(c *Claims) (sql.NullString, error) { + b, err := json.Marshal(c) + if err != nil { + return sql.NullString{}, nil + } + return sql.NullString{ + String: string(b), + Valid: len(b) > 0, + }, nil +} + +func unmarshalClaims(s sql.NullString) (*Claims, error) { + if !s.Valid { + return nil, nil + } + v := new(Claims) + return v, json.Unmarshal([]byte(s.String), v) +} +*/ From af3cf7dae958d99cdf80c6f37119eaa8380730d3 Mon Sep 17 00:00:00 2001 From: max furman Date: Thu, 6 May 2021 17:03:12 -0700 Subject: [PATCH 03/89] first steps --- authority/authority.go | 37 +++++++---- authority/config/config.go | 11 +++- authority/mgmt/api/admin.go | 88 +++++++++++++------------- authority/mgmt/api/authConfig.go | 89 +++++++++++++-------------- authority/mgmt/api/handler.go | 10 ++- authority/mgmt/api/provisioner.go | 78 +++++++++++------------ authority/mgmt/authConfig.go | 24 +------- authority/mgmt/config.go | 26 ++++++++ authority/mgmt/db.go | 6 +- authority/mgmt/db/nosql/admin.go | 3 +- authority/mgmt/db/nosql/authConfig.go | 44 +++++++------ authority/mgmt/errors.go | 5 ++ authority/mgmt/provisioner.go | 15 +++-- ca/ca.go | 12 ++++ ca/mgmtClient.go | 8 +-- 15 files changed, 251 insertions(+), 205 deletions(-) diff --git a/authority/authority.go b/authority/authority.go index 53a3ee72..016b838a 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -31,7 +31,7 @@ import ( // Authority implements the Certificate Authority internal interface. type Authority struct { config *config.Config - mgmtDB *mgmt.DB + mgmtDB mgmt.DB keyManager kms.KeyManager provisioners *provisioner.Collection db db.AuthDB @@ -146,20 +146,26 @@ func (a *Authority) init() error { // Pull AuthConfig from DB. if true { - if len(a.config.AuthConfig.AuthorityID)_== 0 { - mgmtDB, err := authMgmtNosql.New(a.db.(nosql.DB), mgmt.DefaultAuthorityID) - if err != nil { - return err - } - mgmtAuthConfig, err := mgmt.CreateAuthority(context.Background, mgmtDB, WithDefaultAuthorityID) - if err != nil { - return err - } - a.config.AuthConfig, err := mgmtAuthConfig.ToCertificates() - if err != nil { - return err + // Check if AuthConfig already exists + a.mgmtDB, err = authMgmtNosql.New(a.db.(nosql.DB), mgmt.DefaultAuthorityID) + if err != nil { + return err + } + mgmtAuthConfig, err := a.mgmtDB.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.mgmtDB, mgmt.WithDefaultAuthorityID) + if err != nil { + return mgmt.WrapErrorISE(err, "error creating authConfig") + } + } else { + return mgmt.WrapErrorISE(err, "error getting authConfig from db") } } + a.config.AuthorityConfig, err = mgmtAuthConfig.ToCertificates() + if err != nil { + return err + } } // Initialize key manager if it has not been set in the options. @@ -394,6 +400,11 @@ func (a *Authority) GetDatabase() db.AuthDB { return a.db } +// GetMgmtDatabase returns the mgmt database, if one exists. +func (a *Authority) GetMgmtDatabase() mgmt.DB { + return a.mgmtDB +} + // Shutdown safely shuts down any clients, databases, etc. held by the Authority. func (a *Authority) Shutdown() error { if err := a.keyManager.Close(); err != nil { diff --git a/authority/config/config.go b/authority/config/config.go index 852641c2..66b8bbe0 100644 --- a/authority/config/config.go +++ b/authority/config/config.go @@ -32,9 +32,14 @@ var ( MaxVersion: 1.2, Renegotiation: false, } - DefaultBackdate = time.Minute + // DefaultBackdate length of time to backdate certificates to avoid + // clock skew validation issues. + DefaultBackdate = time.Minute + // DefaultDisableRenewal disables renewals per provisioner. DefaultDisableRenewal = false - DefaultEnableSSHCA = false + // DefaultEnableSSHCA enable SSH CA features per provisioner or globally + // for all provisioners. + DefaultEnableSSHCA = false // GlobalProvisionerClaims default claims for the Authority. Can be overriden // by provisioner specific claims. GlobalProvisionerClaims = provisioner.Claims{ @@ -153,7 +158,7 @@ func LoadConfiguration(filename string) (*Config, error) { return nil, errors.Wrapf(err, "error parsing %s", filename) } - c.init() + c.Init() return &c, nil } diff --git a/authority/mgmt/api/admin.go b/authority/mgmt/api/admin.go index 6f6aaa93..f544b631 100644 --- a/authority/mgmt/api/admin.go +++ b/authority/mgmt/api/admin.go @@ -58,55 +58,59 @@ func (h *Handler) GetAdmins(w http.ResponseWriter, r *http.Request) { // CreateAdmin creates a new admin. func (h *Handler) CreateAdmin(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + /* + ctx := r.Context() - var body CreateAdminRequest - if err := ReadJSON(r.Body, &body); err != nil { - api.WriteError(w, err) - return - } - if err := body.Validate(); err != nil { - api.WriteError(w, err) - } + var body CreateAdminRequest + if err := ReadJSON(r.Body, &body); err != nil { + api.WriteError(w, err) + return + } + if err := body.Validate(); err != nil { + api.WriteError(w, err) + } - adm := &config.Admin{ - Name: body.Name, - Provisioner: body.Provisioner, - IsSuperAdmin: body.IsSuperAdmin, - } - if err := h.db.CreateAdmin(ctx, adm); err != nil { - api.WriteError(w, err) - return - } - api.JSONStatus(w, adm, http.StatusCreated) + adm := &config.Admin{ + Name: body.Name, + Provisioner: body.Provisioner, + IsSuperAdmin: body.IsSuperAdmin, + } + if err := h.db.CreateAdmin(ctx, adm); err != nil { + api.WriteError(w, err) + return + } + api.JSONStatus(w, adm, http.StatusCreated) + */ } // UpdateAdmin updates an existing admin. func (h *Handler) UpdateAdmin(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - id := chi.URLParam(r, "id") + /* + ctx := r.Context() + id := chi.URLParam(r, "id") - var body UpdateAdminRequest - if err := ReadJSON(r.Body, &body); err != nil { - api.WriteError(w, err) - return - } - if err := body.Validate(); err != nil { - api.WriteError(w, err) - return - } - if adm, err := h.db.GetAdmin(ctx, id); err != nil { - api.WriteError(w, err) - return - } + var body UpdateAdminRequest + if err := ReadJSON(r.Body, &body); err != nil { + api.WriteError(w, err) + return + } + if err := body.Validate(); err != nil { + api.WriteError(w, err) + return + } + if adm, err := h.db.GetAdmin(ctx, id); err != nil { + api.WriteError(w, err) + return + } - adm.Name = body.Name - adm.Provisioner = body.Provisioner - adm.IsSuperAdmin = body.IsSuperAdmin + adm.Name = body.Name + adm.Provisioner = body.Provisioner + adm.IsSuperAdmin = body.IsSuperAdmin - if err := h.db.UpdateAdmin(ctx, adm); err != nil { - api.WriteError(w, err) - return - } - api.JSON(w, adm) + if err := h.db.UpdateAdmin(ctx, adm); err != nil { + api.WriteError(w, err) + return + } + api.JSON(w, adm) + */ } diff --git a/authority/mgmt/api/authConfig.go b/authority/mgmt/api/authConfig.go index fb23f8e8..283a4b66 100644 --- a/authority/mgmt/api/authConfig.go +++ b/authority/mgmt/api/authConfig.go @@ -5,16 +5,15 @@ import ( "github.com/go-chi/chi" "github.com/smallstep/certificates/api" - "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority/config" + "github.com/smallstep/certificates/authority/mgmt" ) // CreateAuthConfigRequest represents the body for a CreateAuthConfig request. type CreateAuthConfigRequest struct { - ASN1DN *authority.ASN1DN `json:"asn1dn,omitempty"` - Claims *config.Claims `json:"claims,omitempty"` - DisableIssuedAtCheck bool `json:"disableIssuedAtCheck,omitempty"` - Backdate string `json:"backdate,omitempty"` + ASN1DN *config.ASN1DN `json:"asn1dn,omitempty"` + Claims *mgmt.Claims `json:"claims,omitempty"` + Backdate string `json:"backdate,omitempty"` } // Validate validates a CreateAuthConfig request body. @@ -24,10 +23,9 @@ func (car *CreateAuthConfigRequest) Validate() error { // UpdateAuthConfigRequest represents the body for a UpdateAuthConfig request. type UpdateAuthConfigRequest struct { - ASN1DN *authority.ASN1DN `json:"asn1dn"` - Claims *config.Claims `json:"claims"` - DisableIssuedAtCheck bool `json:"disableIssuedAtCheck,omitempty"` - Backdate string `json:"backdate,omitempty"` + ASN1DN *config.ASN1DN `json:"asn1dn"` + Claims *mgmt.Claims `json:"claims"` + Backdate string `json:"backdate,omitempty"` } // Validate validates a new-admin request body. @@ -53,7 +51,7 @@ func (h *Handler) CreateAuthConfig(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var body CreateAuthConfigRequest - if err := ReadJSON(r.Body, &body); err != nil { + if err := api.ReadJSON(r.Body, &body); err != nil { api.WriteError(w, err) return } @@ -61,10 +59,9 @@ func (h *Handler) CreateAuthConfig(w http.ResponseWriter, r *http.Request) { api.WriteError(w, err) } - ac := config.AuthConfig{ - Status: config.StatusActive, - DisableIssuedAtCheck: body.DisableIssuedAtCheck, - Backdate: "1m", + ac := &mgmt.AuthConfig{ + Status: mgmt.StatusActive, + Backdate: "1m", } if body.ASN1DN != nil { ac.ASN1DN = body.ASN1DN @@ -84,38 +81,40 @@ func (h *Handler) CreateAuthConfig(w http.ResponseWriter, r *http.Request) { // UpdateAuthConfig updates an existing AuthConfig. func (h *Handler) UpdateAuthConfig(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - id := chi.URLParam(r, "id") + /* + ctx := r.Context() + id := chi.URLParam(r, "id") - var body UpdateAuthConfigRequest - if err := ReadJSON(r.Body, &body); err != nil { - api.WriteError(w, err) - return - } - if err := body.Validate(); err != nil { - api.WriteError(w, err) - return - } - if ac, err := h.db.GetAuthConfig(ctx, id); err != nil { - api.WriteError(w, err) - return - } + 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.DisableIssuedAtCheck = body.DisableIssuedAtCheck - 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 - } + 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) + 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 f30544c5..0e512a39 100644 --- a/authority/mgmt/api/handler.go +++ b/authority/mgmt/api/handler.go @@ -4,7 +4,7 @@ import ( "time" "github.com/smallstep/certificates/api" - "github.com/smallstep/certificates/authority/config" + "github.com/smallstep/certificates/authority/mgmt" ) // Clock that returns time in UTC rounded to seconds. @@ -19,14 +19,12 @@ var clock Clock // Handler is the ACME API request handler. type Handler struct { - db config.DB + db mgmt.DB } // NewHandler returns a new Authority Config Handler. -func NewHandler(db config.DB) api.RouterHandler { - return &Handler{ - db: ops.DB, - } +func NewHandler(db mgmt.DB) api.RouterHandler { + return &Handler{db} } // Route traffic and implement the Router interface. diff --git a/authority/mgmt/api/provisioner.go b/authority/mgmt/api/provisioner.go index 76bbe7cf..43293221 100644 --- a/authority/mgmt/api/provisioner.go +++ b/authority/mgmt/api/provisioner.go @@ -5,17 +5,17 @@ import ( "github.com/go-chi/chi" "github.com/smallstep/certificates/api" - "github.com/smallstep/certificates/authority/config" + "github.com/smallstep/certificates/authority/mgmt" ) // CreateProvisionerRequest represents the body for a CreateProvisioner request. type CreateProvisionerRequest struct { - Type string `json:"type"` - Name string `json:"name"` - Claims *config.Claims `json:"claims"` - Details interface{} `json:"details"` - X509Template string `json:"x509Template"` - SSHTemplate string `json:"sshTemplate"` + Type string `json:"type"` + Name string `json:"name"` + Claims *mgmt.Claims `json:"claims"` + Details interface{} `json:"details"` + X509Template string `json:"x509Template"` + SSHTemplate string `json:"sshTemplate"` } // Validate validates a new-provisioner request body. @@ -25,10 +25,10 @@ func (car *CreateProvisionerRequest) Validate() error { // UpdateProvisionerRequest represents the body for a UpdateProvisioner request. type UpdateProvisionerRequest struct { - Claims *config.Claims `json:"claims"` - Details interface{} `json:"details"` - X509Template string `json:"x509Template"` - SSHTemplate string `json:"sshTemplate"` + Claims *mgmt.Claims `json:"claims"` + Details interface{} `json:"details"` + X509Template string `json:"x509Template"` + SSHTemplate string `json:"sshTemplate"` } // Validate validates a new-provisioner request body. @@ -66,7 +66,7 @@ func (h *Handler) CreateProvisioner(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var body CreateProvisionerRequest - if err := ReadJSON(r.Body, &body); err != nil { + if err := api.ReadJSON(r.Body, &body); err != nil { api.WriteError(w, err) return } @@ -74,7 +74,7 @@ func (h *Handler) CreateProvisioner(w http.ResponseWriter, r *http.Request) { api.WriteError(w, err) } - prov := &config.Provisioner{ + prov := &mgmt.Provisioner{ Type: body.Type, Name: body.Name, Claims: body.Claims, @@ -91,32 +91,34 @@ func (h *Handler) CreateProvisioner(w http.ResponseWriter, r *http.Request) { // UpdateProvisioner updates an existing prov. func (h *Handler) UpdateProvisioner(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - id := chi.URLParam(r, "id") + /* + ctx := r.Context() + id := chi.URLParam(r, "id") - var body UpdateProvisionerRequest - if err := ReadJSON(r.Body, &body); err != nil { - api.WriteError(w, err) - return - } - if err := body.Validate(); err != nil { - api.WriteError(w, err) - return - } - if prov, err := h.db.GetProvisioner(ctx, id); err != nil { - api.WriteError(w, err) - return - } + var body UpdateProvisionerRequest + if err := ReadJSON(r.Body, &body); err != nil { + api.WriteError(w, err) + return + } + if err := body.Validate(); err != nil { + api.WriteError(w, err) + return + } + if prov, err := h.db.GetProvisioner(ctx, id); err != nil { + api.WriteError(w, err) + return + } - prov.Claims = body.Claims - prov.Details = body.Provisioner - prov.X509Template = body.X509Template - prov.SSHTemplate = body.SSHTemplate - prov.Status = body.Status + prov.Claims = body.Claims + prov.Details = body.Provisioner + prov.X509Template = body.X509Template + prov.SSHTemplate = body.SSHTemplate + prov.Status = body.Status - if err := h.db.UpdateProvisioner(ctx, prov); err != nil { - api.WriteError(w, err) - return - } - api.JSON(w, prov) + if err := h.db.UpdateProvisioner(ctx, prov); err != nil { + api.WriteError(w, err) + return + } + api.JSON(w, prov) + */ } diff --git a/authority/mgmt/authConfig.go b/authority/mgmt/authConfig.go index 595b2ed0..734bca50 100644 --- a/authority/mgmt/authConfig.go +++ b/authority/mgmt/authConfig.go @@ -19,28 +19,8 @@ type AuthConfig struct { func NewDefaultAuthConfig() *AuthConfig { return &AuthConfig{ - Claims: &Claims{ - X509: &X509Claims{ - Durations: &Durations{ - Min: config.GlobalProvisionerClaims.MinTLSDur.String(), - Max: config.GlobalProvisionerClaims.MaxTLSDur.String(), - Default: config.GlobalProvisionerClaims.DefaultTLSDur.String(), - }, - }, - SSH: &SSHClaims{ - UserDurations: &Durations{ - Min: config.GlobalProvisionerClaims.MinUserSSHDur.String(), - Max: config.GlobalProvisionerClaims.MaxUserSSHDur.String(), - Default: config.GlobalProvisionerClaims.DefaultUserSSHDur.String(), - }, - HostDurations: &Durations{ - Min: config.GlobalProvisionerClaims.MinHostSSHDur.String(), - Max: config.GlobalProvisionerClaims.MaxHostSSHDur.String(), - Default: config.GlobalProvisionerClaims.DefaultHostSSHDur.String(), - }, - }, - DisableRenewal: config.DefaultDisableRenewal, - }, + Claims: NewDefaultClaims(), + ASN1DN: &config.ASN1DN{}, Backdate: config.DefaultBackdate.String(), Status: StatusActive, } diff --git a/authority/mgmt/config.go b/authority/mgmt/config.go index db70e6e5..0cd25aad 100644 --- a/authority/mgmt/config.go +++ b/authority/mgmt/config.go @@ -4,6 +4,7 @@ import ( "context" "github.com/pkg/errors" + "github.com/smallstep/certificates/authority/config" ) const ( @@ -50,6 +51,31 @@ type Durations struct { Default string `json:"default"` } +func NewDefaultClaims() *Claims { + return &Claims{ + X509: &X509Claims{ + Durations: &Durations{ + Min: config.GlobalProvisionerClaims.MinTLSDur.String(), + Max: config.GlobalProvisionerClaims.MaxTLSDur.String(), + Default: config.GlobalProvisionerClaims.DefaultTLSDur.String(), + }, + }, + SSH: &SSHClaims{ + UserDurations: &Durations{ + Min: config.GlobalProvisionerClaims.MinUserSSHDur.String(), + Max: config.GlobalProvisionerClaims.MaxUserSSHDur.String(), + Default: config.GlobalProvisionerClaims.DefaultUserSSHDur.String(), + }, + HostDurations: &Durations{ + Min: config.GlobalProvisionerClaims.MinHostSSHDur.String(), + Max: config.GlobalProvisionerClaims.MaxHostSSHDur.String(), + Default: config.GlobalProvisionerClaims.DefaultHostSSHDur.String(), + }, + }, + DisableRenewal: config.DefaultDisableRenewal, + } +} + type AuthorityOption func(*AuthConfig) error func WithDefaultAuthorityID(ac *AuthConfig) error { diff --git a/authority/mgmt/db.go b/authority/mgmt/db.go index 99228a4d..9cfab3e5 100644 --- a/authority/mgmt/db.go +++ b/authority/mgmt/db.go @@ -18,7 +18,7 @@ type DB interface { UpdateProvisioner(ctx context.Context, prov *Provisioner) error CreateAdmin(ctx context.Context, admin *Admin) error - GetAdmin(ctx context.Context, id string) error + GetAdmin(ctx context.Context, id string) (*Admin, error) GetAdmins(ctx context.Context) ([]*Admin, error) UpdateAdmin(ctx context.Context, admin *Admin) error @@ -116,7 +116,7 @@ func (m *MockDB) GetAdmins(ctx context.Context) ([]*Admin, error) { // UpdateAdmin mock func (m *MockDB) UpdateAdmin(ctx context.Context, adm *Admin) error { - if m.UpdateAdmin != nil { + if m.MockUpdateAdmin != nil { return m.MockUpdateAdmin(ctx, adm) } return m.MockError @@ -142,7 +142,7 @@ func (m *MockDB) GetAuthConfig(ctx context.Context, id string) (*AuthConfig, err // UpdateAuthConfig mock func (m *MockDB) UpdateAuthConfig(ctx context.Context, adm *AuthConfig) error { - if m.UpdateAuthConfig != nil { + if m.MockUpdateAuthConfig != nil { return m.MockUpdateAuthConfig(ctx, adm) } return m.MockError diff --git a/authority/mgmt/db/nosql/admin.go b/authority/mgmt/db/nosql/admin.go index cba8c59d..564ac1c7 100644 --- a/authority/mgmt/db/nosql/admin.go +++ b/authority/mgmt/db/nosql/admin.go @@ -6,7 +6,6 @@ import ( "time" "github.com/pkg/errors" - "github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/authority/mgmt" "github.com/smallstep/nosql" ) @@ -109,7 +108,7 @@ func unmarshalAdmin(data []byte, id string) (*mgmt.Admin, error) { // GetAdmins retrieves and unmarshals all active (not deleted) admins // from the database. // TODO should we be paginating? -func (db *DB) GetAdmins(ctx context.Context, az *acme.Authorization) ([]*mgmt.Admin, error) { +func (db *DB) GetAdmins(ctx context.Context) ([]*mgmt.Admin, error) { dbEntries, err := db.db.List(authorityAdminsTable) if err != nil { return nil, errors.Wrap(err, "error loading admins") diff --git a/authority/mgmt/db/nosql/authConfig.go b/authority/mgmt/db/nosql/authConfig.go index 6fe1266b..fe189ce3 100644 --- a/authority/mgmt/db/nosql/authConfig.go +++ b/authority/mgmt/db/nosql/authConfig.go @@ -12,13 +12,12 @@ import ( ) type dbAuthConfig struct { - ID string `json:"id"` - ASN1DN *config.ASN1DN `json:"asn1dn"` - Claims *mgmt.Claims `json:"claims"` - DisableIssuedAtCheck bool `json:"disableIssuedAtCheck,omitempty"` - Backdate string `json:"backdate,omitempty"` - CreatedAt time.Time `json:"createdAt"` - DeletedAt time.Time `json:"deletedAt"` + ID string `json:"id"` + ASN1DN *config.ASN1DN `json:"asn1dn"` + Claims *mgmt.Claims `json:"claims"` + Backdate string `json:"backdate,omitempty"` + CreatedAt time.Time `json:"createdAt"` + DeletedAt time.Time `json:"deletedAt"` } func (dbp *dbAuthConfig) clone() *dbAuthConfig { @@ -63,30 +62,30 @@ func (db *DB) GetAuthConfig(ctx context.Context, id string) (*mgmt.AuthConfig, e } return &mgmt.AuthConfig{ - ID: dba.ID, - Provisioners: provs, - ASN1DN: dba.ASN1DN, - Backdate: dba.Backdate, - Claims: dba.Claims, - DisableIssuedAtCheck: dba.DisableIssuedAtCheck, + ID: dba.ID, + Provisioners: provs, + ASN1DN: dba.ASN1DN, + Backdate: dba.Backdate, + Claims: dba.Claims, }, nil } // CreateAuthConfig stores a new provisioner to the database. func (db *DB) CreateAuthConfig(ctx context.Context, ac *mgmt.AuthConfig) error { var err error - ac.ID, err = randID() - if err != nil { - return errors.Wrap(err, "error generating random id for provisioner") + if ac.ID == "" { + ac.ID, err = randID() + if err != nil { + return errors.Wrap(err, "error generating random id for provisioner") + } } dba := &dbAuthConfig{ - ID: ac.ID, - ASN1DN: ac.ASN1DN, - Claims: ac.Claims, - DisableIssuedAtCheck: ac.DisableIssuedAtCheck, - Backdate: ac.Backdate, - CreatedAt: clock.Now(), + ID: ac.ID, + ASN1DN: ac.ASN1DN, + Claims: ac.Claims, + Backdate: ac.Backdate, + CreatedAt: clock.Now(), } return db.save(ctx, dba.ID, dba, nil, "authConfig", authorityConfigsTable) @@ -106,7 +105,6 @@ func (db *DB) UpdateAuthConfig(ctx context.Context, ac *mgmt.AuthConfig) error { nu.DeletedAt = clock.Now() } nu.Claims = ac.Claims - nu.DisableIssuedAtCheck = ac.DisableIssuedAtCheck nu.Backdate = ac.Backdate return db.save(ctx, old.ID, nu, old, "authConfig", authorityProvisionersTable) diff --git a/authority/mgmt/errors.go b/authority/mgmt/errors.go index f8b6fd65..ae18619c 100644 --- a/authority/mgmt/errors.go +++ b/authority/mgmt/errors.go @@ -87,6 +87,11 @@ type Error struct { Status int `json:"-"` } +// IsType returns true if the error type matches the input type. +func (e *Error) IsType(pt ProblemType) bool { + return pt.String() == e.Type +} + // NewError creates a new Error type. func NewError(pt ProblemType, msg string, args ...interface{}) *Error { return newError(pt, errors.Errorf(msg, args...)) diff --git a/authority/mgmt/provisioner.go b/authority/mgmt/provisioner.go index 2cb21403..427846fa 100644 --- a/authority/mgmt/provisioner.go +++ b/authority/mgmt/provisioner.go @@ -20,6 +20,16 @@ type ProvisionerCtx struct { Password string } +func NewProvisionerCtx(opts ...ProvisionerOption) *ProvisionerCtx { + pc := &ProvisionerCtx{ + Claims: NewDefaultClaims(), + } + for _, o := range opts { + o(pc) + } + return pc +} + func WithJWK(jwk *jose.JSONWebKey, jwe *jose.JSONWebEncryption) func(*ProvisionerCtx) { return func(ctx *ProvisionerCtx) { ctx.JWK = jwk @@ -62,10 +72,7 @@ func (p *Provisioner) GetOptions() *provisioner.Options { } func CreateProvisioner(ctx context.Context, db DB, typ, name string, opts ...ProvisionerOption) (*Provisioner, error) { - pc := new(ProvisionerCtx) - for _, o := range opts { - o(pc) - } + pc := NewProvisionerCtx(opts...) details, err := createJWKDetails(pc) if err != nil { diff --git a/ca/ca.go b/ca/ca.go index d9bcdd5e..ec10f74c 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -18,6 +18,7 @@ import ( "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/mgmt" + mgmtAPI "github.com/smallstep/certificates/authority/mgmt/api" "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/monitoring" @@ -143,6 +144,7 @@ func (ca *CA) Init(config *config.Config) (*CA, error) { dns = fmt.Sprintf("%s:%s", dns, port) } + // ACME Router prefix := "acme" var acmeDB acme.DB if config.DB == nil { @@ -169,6 +171,16 @@ func (ca *CA) Init(config *config.Config) (*CA, error) { acmeHandler.Route(r) }) + // MGMT Router + + mgmtDB := auth.GetMgmtDatabase() + if mgmtDB != nil { + mgmtHandler := mgmtAPI.NewHandler(mgmtDB) + mux.Route("/mgmt", func(r chi.Router) { + mgmtHandler.Route(r) + }) + } + // helpful routine for logging all routes // /* walkFunc := func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error { diff --git a/ca/mgmtClient.go b/ca/mgmtClient.go index 1172218f..ac494d90 100644 --- a/ca/mgmtClient.go +++ b/ca/mgmtClient.go @@ -60,10 +60,10 @@ func (c *MgmtClient) retryOnError(r *http.Response) bool { return false } -// GetAdmin performs the GET /config/admin/{id} request to the CA. +// GetAdmin performs the GET /mgmt/admin/{id} request to the CA. func (c *MgmtClient) GetAdmin(id string) (*mgmt.Admin, error) { var retried bool - u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/config/admin", id)}) + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/mgmt/admin", id)}) retry: resp, err := c.client.Get(u.String()) if err != nil { @@ -83,10 +83,10 @@ retry: return adm, nil } -// GetAdmins performs the GET /config/admins request to the CA. +// GetAdmins performs the GET /mgmt/admins request to the CA. func (c *MgmtClient) GetAdmins() ([]*mgmt.Admin, error) { var retried bool - u := c.endpoint.ResolveReference(&url.URL{Path: "/config/admins"}) + u := c.endpoint.ResolveReference(&url.URL{Path: "/mgmt/admins"}) retry: resp, err := c.client.Get(u.String()) if err != nil { From 98a6e545301bdfe88bc23786982a2d254f4b0b1c Mon Sep 17 00:00:00 2001 From: max furman Date: Tue, 11 May 2021 15:25:37 -0700 Subject: [PATCH 04/89] wip --- authority/mgmt/admin.go | 26 +++--- authority/mgmt/api/provisioner.go | 40 +++++---- authority/mgmt/config.go | 14 ++- authority/mgmt/db/nosql/admin.go | 8 +- authority/mgmt/db/nosql/provisioner.go | 115 +++++++++++++++++++++---- authority/mgmt/provisioner.go | 94 +++++--------------- ca/mgmtClient.go | 23 +++++ 7 files changed, 194 insertions(+), 126 deletions(-) diff --git a/authority/mgmt/admin.go b/authority/mgmt/admin.go index 8a4104a5..9ceabe93 100644 --- a/authority/mgmt/admin.go +++ b/authority/mgmt/admin.go @@ -4,25 +4,21 @@ import "context" // Admin type. type Admin struct { - ID string `json:"-"` - AuthorityID string `json:"-"` - ProvisionerID string `json:"provisionerID"` - Name string `json:"name"` - ProvisionerName string `json:"provisionerName"` - ProvisionerType string `json:"provisionerType"` - IsSuperAdmin bool `json:"isSuperAdmin"` - Status StatusType `json:"status"` + ID string `json:"id"` + AuthorityID string `json:"-"` + ProvisionerID string `json:"provisionerID"` + Name string `json:"name"` + IsSuperAdmin bool `json:"isSuperAdmin"` + Status StatusType `json:"status"` } // CreateAdmin builds and stores an admin type in the DB. -func CreateAdmin(ctx context.Context, db DB, name string, prov *Provisioner, isSuperAdmin bool) (*Admin, error) { +func CreateAdmin(ctx context.Context, db DB, name string, provID string, isSuperAdmin bool) (*Admin, error) { adm := &Admin{ - Name: name, - ProvisionerID: prov.ID, - ProvisionerName: prov.Name, - ProvisionerType: prov.Type, - IsSuperAdmin: isSuperAdmin, - Status: StatusActive, + Name: name, + ProvisionerID: provID, + IsSuperAdmin: isSuperAdmin, + Status: StatusActive, } if err := db.CreateAdmin(ctx, adm); err != nil { return nil, WrapErrorISE(err, "error creating admin") diff --git a/authority/mgmt/api/provisioner.go b/authority/mgmt/api/provisioner.go index 43293221..b6b4c1c7 100644 --- a/authority/mgmt/api/provisioner.go +++ b/authority/mgmt/api/provisioner.go @@ -1,6 +1,7 @@ package api import ( + "fmt" "net/http" "github.com/go-chi/chi" @@ -10,12 +11,14 @@ import ( // 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 interface{} `json:"details"` - X509Template string `json:"x509Template"` - SSHTemplate string `json:"sshTemplate"` + Type string `json:"type"` + Name string `json:"name"` + Claims *mgmt.Claims `json:"claims"` + Details interface{} `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. @@ -25,10 +28,12 @@ func (car *CreateProvisionerRequest) Validate() error { // UpdateProvisionerRequest represents the body for a UpdateProvisioner request. type UpdateProvisionerRequest struct { - Claims *mgmt.Claims `json:"claims"` - Details interface{} `json:"details"` - X509Template string `json:"x509Template"` - SSHTemplate string `json:"sshTemplate"` + Claims *mgmt.Claims `json:"claims"` + Details interface{} `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. @@ -58,6 +63,7 @@ func (h *Handler) GetProvisioners(w http.ResponseWriter, r *http.Request) { api.WriteError(w, err) return } + fmt.Printf("provs = %+v\n", provs) api.JSON(w, provs) } @@ -75,12 +81,14 @@ func (h *Handler) CreateProvisioner(w http.ResponseWriter, r *http.Request) { } prov := &mgmt.Provisioner{ - Type: body.Type, - Name: body.Name, - Claims: body.Claims, - Details: body.Details, - X509Template: body.X509Template, - SSHTemplate: body.SSHTemplate, + Type: body.Type, + Name: body.Name, + Claims: body.Claims, + Details: body.Details, + X509Template: body.X509Template, + X509TemplateData: body.X509TemplateData, + SSHTemplate: body.SSHTemplate, + SSHTemplateData: body.SSHTemplateData, } if err := h.db.CreateProvisioner(ctx, prov); err != nil { api.WriteError(w, err) diff --git a/authority/mgmt/config.go b/authority/mgmt/config.go index 0cd25aad..b3ece47f 100644 --- a/authority/mgmt/config.go +++ b/authority/mgmt/config.go @@ -2,6 +2,7 @@ package mgmt import ( "context" + "fmt" "github.com/pkg/errors" "github.com/smallstep/certificates/authority/config" @@ -24,6 +25,17 @@ const ( StatusDeleted ) +func (st StatusType) String() string { + switch st { + case StatusActive: + return "active" + case StatusDeleted: + return "deleted" + default: + return fmt.Sprintf("status %d not found", st) + } +} + // Claims encapsulates all x509 and ssh claims applied to the authority // configuration. E.g. maxTLSCertDuration, defaultSSHCertDuration, etc. type Claims struct { @@ -111,7 +123,7 @@ func CreateAuthority(ctx context.Context, db DB, options ...AuthorityOption) (*A return nil, WrapErrorISE(err, "error creating first provisioner") } - admin, err := CreateAdmin(ctx, db, "Change Me", prov, true) + admin, err := CreateAdmin(ctx, db, "Change Me", prov.ID, true) if err != nil { // TODO should we try to clean up? return nil, WrapErrorISE(err, "error creating first provisioner") diff --git a/authority/mgmt/db/nosql/admin.go b/authority/mgmt/db/nosql/admin.go index 564ac1c7..97fc81b0 100644 --- a/authority/mgmt/db/nosql/admin.go +++ b/authority/mgmt/db/nosql/admin.go @@ -63,19 +63,13 @@ func (db *DB) GetAdmin(ctx context.Context, id string) (*mgmt.Admin, error) { return nil, err } if adm.Status == mgmt.StatusDeleted { - return nil, mgmt.NewError(mgmt.ErrorDeletedType, "admin %s is deleted") + return nil, mgmt.NewError(mgmt.ErrorDeletedType, "admin %s is deleted", adm.ID) } if adm.AuthorityID != db.authorityID { return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType, "admin %s is not owned by authority %s", adm.ID, db.authorityID) } - prov, err := db.GetProvisioner(ctx, adm.ProvisionerID) - if err != nil { - return nil, err - } - adm.ProvisionerName = prov.Name - adm.ProvisionerType = prov.Type return adm, nil } diff --git a/authority/mgmt/db/nosql/provisioner.go b/authority/mgmt/db/nosql/provisioner.go index 93dcc47b..dbea49f4 100644 --- a/authority/mgmt/db/nosql/provisioner.go +++ b/authority/mgmt/db/nosql/provisioner.go @@ -3,6 +3,7 @@ package nosql import ( "context" "encoding/json" + "fmt" "time" "github.com/pkg/errors" @@ -12,16 +13,18 @@ import ( // dbProvisioner is the database representation of a Provisioner type. type dbProvisioner struct { - ID string `json:"id"` - AuthorityID string `json:"authorityID"` - Type string `json:"type"` - Name string `json:"name"` - Claims *mgmt.Claims `json:"claims"` - Details interface{} `json:"details"` - X509Template string `json:"x509Template"` - SSHTemplate string `json:"sshTemplate"` - CreatedAt time.Time `json:"createdAt"` - DeletedAt time.Time `json:"deletedAt"` + ID string `json:"id"` + AuthorityID string `json:"authorityID"` + 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"` + CreatedAt time.Time `json:"createdAt"` + DeletedAt time.Time `json:"deletedAt"` } func (dbp *dbProvisioner) clone() *dbProvisioner { @@ -84,19 +87,36 @@ func unmarshalDBProvisioner(data []byte, id string) (*dbProvisioner, error) { return dbp, nil } +type detailsType struct { + Type mgmt.ProvisionerType +} + func unmarshalProvisioner(data []byte, id string) (*mgmt.Provisioner, error) { dbp, err := unmarshalDBProvisioner(data, id) if err != nil { return nil, err } + dt := new(detailsType) + if err := json.Unmarshal(dbp.Details, dt); err != nil { + return nil, mgmt.WrapErrorISE(err, "error unmarshaling details to detailsType for provisioner %s", id) + } + details, err := unmarshalDetails(dt.Type, dbp.Details) + if err != nil { + return nil, err + } + prov := &mgmt.Provisioner{ - ID: dbp.ID, - Type: dbp.Type, - Name: dbp.Name, - Claims: dbp.Claims, - X509Template: dbp.X509Template, - SSHTemplate: dbp.SSHTemplate, + ID: dbp.ID, + AuthorityID: dbp.AuthorityID, + Type: dbp.Type, + Name: dbp.Name, + Claims: dbp.Claims, + Details: details, + X509Template: dbp.X509Template, + X509TemplateData: dbp.X509TemplateData, + SSHTemplate: dbp.SSHTemplate, + SSHTemplateData: dbp.SSHTemplateData, } if !dbp.DeletedAt.IsZero() { prov.Status = mgmt.StatusDeleted @@ -172,3 +192,66 @@ func (db *DB) UpdateProvisioner(ctx context.Context, prov *mgmt.Provisioner) err return db.save(ctx, old.ID, nu, old, "provisioner", authorityProvisionersTable) } + +func unmarshalDetails(typ ProvisionerType, details []byte) (interface{}, error) { + if !s.Valid { + return nil, nil + } + var v isProvisionerDetails_Data + switch typ { + case ProvisionerTypeJWK: + p := new(ProvisionerDetailsJWK) + if err := json.Unmarshal([]byte(s.String), p); err != nil { + return nil, err + } + if p.JWK.Key.Key == nil { + key, err := LoadKey(ctx, db, p.JWK.Key.Id.Id) + if err != nil { + return nil, err + } + p.JWK.Key = key + } + return &ProvisionerDetails{Data: p}, nil + case ProvisionerType_OIDC: + v = new(ProvisionerDetails_OIDC) + case ProvisionerType_GCP: + v = new(ProvisionerDetails_GCP) + case ProvisionerType_AWS: + v = new(ProvisionerDetails_AWS) + case ProvisionerType_AZURE: + v = new(ProvisionerDetails_Azure) + case ProvisionerType_ACME: + v = new(ProvisionerDetails_ACME) + case ProvisionerType_X5C: + p := new(ProvisionerDetails_X5C) + if err := json.Unmarshal([]byte(s.String), p); err != nil { + return nil, err + } + for _, k := range p.X5C.GetRoots() { + if err := k.Select(ctx, db, k.Id.Id); err != nil { + return nil, err + } + } + return &ProvisionerDetails{Data: p}, nil + case ProvisionerType_K8SSA: + p := new(ProvisionerDetails_K8SSA) + if err := json.Unmarshal([]byte(s.String), p); err != nil { + return nil, err + } + for _, k := range p.K8SSA.GetPublicKeys() { + if err := k.Select(ctx, db, k.Id.Id); err != nil { + return nil, err + } + } + return &ProvisionerDetails{Data: p}, nil + case ProvisionerType_SSHPOP: + v = new(ProvisionerDetails_SSHPOP) + default: + return nil, fmt.Errorf("unsupported provisioner type %s", typ) + } + + if err := json.Unmarshal([]byte(s.String), v); err != nil { + return nil, err + } + return &ProvisionerDetails{Data: v}, nil +} diff --git a/authority/mgmt/provisioner.go b/authority/mgmt/provisioner.go index 427846fa..c455bf85 100644 --- a/authority/mgmt/provisioner.go +++ b/authority/mgmt/provisioner.go @@ -20,6 +20,17 @@ type ProvisionerCtx struct { Password string } +type ProvisionerType string + +var ( + ProvisionerTypeJWK = ProvisionerType("JWK") + ProvisionerTypeOIDC = ProvisionerType("OIDC") + ProvisionerTypeACME = ProvisionerType("ACME") + ProvisionerTypeX5C = ProvisionerType("X5C") + ProvisionerTypeK8S = ProvisionerType("K8S") + ProvisionerTypeSSHPOP = ProvisionerType("SSHPOP") +) + func NewProvisionerCtx(opts ...ProvisionerOption) *ProvisionerCtx { pc := &ProvisionerCtx{ Claims: NewDefaultClaims(), @@ -97,12 +108,14 @@ func CreateProvisioner(ctx context.Context, db DB, typ, name string, opts ...Pro return p, nil } -type ProvisionerDetails_JWK struct { - PubKey []byte `json:"pubKey"` - PrivKey string `json:"privKey"` +// ProvisionerDetailsJWK represents the values required by a JWK provisioner. +type ProvisionerDetailsJWK struct { + Type ProvisionerType `json:"type"` + PubKey []byte `json:"pubKey"` + EncPrivKey string `json:"privKey"` } -func createJWKDetails(pc *ProvisionerCtx) (*ProvisionerDetails_JWK, error) { +func createJWKDetails(pc *ProvisionerCtx) (*ProvisionerDetailsJWK, error) { var err error if pc.JWK != nil && pc.JWE == nil { @@ -131,9 +144,10 @@ func createJWKDetails(pc *ProvisionerCtx) (*ProvisionerDetails_JWK, error) { return nil, WrapErrorISE(err, "error serializing JWE") } - return &ProvisionerDetails_JWK{ - PubKey: jwkPubBytes, - PrivKey: jwePrivStr, + return &ProvisionerDetailsJWK{ + Type: ProvisionerTypeJWK, + PubKey: jwkPubBytes, + EncPrivKey: jwePrivStr, }, nil } @@ -150,7 +164,7 @@ func (p *Provisioner) ToCertificates() (provisioner.Interface, error) { } switch details := p.Details.(type) { - case *ProvisionerDetails_JWK: + case *ProvisionerDetailsJWK: jwk := new(jose.JSONWebKey) if err := json.Unmarshal(details.PubKey, &jwk); err != nil { return nil, err @@ -159,7 +173,7 @@ func (p *Provisioner) ToCertificates() (provisioner.Interface, error) { Type: p.Type, Name: p.Name, Key: jwk, - EncryptedKey: details.PrivKey, + EncryptedKey: details.EncPrivKey, Claims: claims, Options: p.GetOptions(), }, nil @@ -324,68 +338,6 @@ func marshalDetails(d *ProvisionerDetails) (sql.NullString, error) { }, nil } -func unmarshalDetails(ctx context.Context, db database.DB, typ ProvisionerType, s sql.NullString) (*ProvisionerDetails, error) { - if !s.Valid { - return nil, nil - } - var v isProvisionerDetails_Data - switch typ { - case ProvisionerType_JWK: - p := new(ProvisionerDetails_JWK) - if err := json.Unmarshal([]byte(s.String), p); err != nil { - return nil, err - } - if p.JWK.Key.Key == nil { - key, err := LoadKey(ctx, db, p.JWK.Key.Id.Id) - if err != nil { - return nil, err - } - p.JWK.Key = key - } - return &ProvisionerDetails{Data: p}, nil - case ProvisionerType_OIDC: - v = new(ProvisionerDetails_OIDC) - case ProvisionerType_GCP: - v = new(ProvisionerDetails_GCP) - case ProvisionerType_AWS: - v = new(ProvisionerDetails_AWS) - case ProvisionerType_AZURE: - v = new(ProvisionerDetails_Azure) - case ProvisionerType_ACME: - v = new(ProvisionerDetails_ACME) - case ProvisionerType_X5C: - p := new(ProvisionerDetails_X5C) - if err := json.Unmarshal([]byte(s.String), p); err != nil { - return nil, err - } - for _, k := range p.X5C.GetRoots() { - if err := k.Select(ctx, db, k.Id.Id); err != nil { - return nil, err - } - } - return &ProvisionerDetails{Data: p}, nil - case ProvisionerType_K8SSA: - p := new(ProvisionerDetails_K8SSA) - if err := json.Unmarshal([]byte(s.String), p); err != nil { - return nil, err - } - for _, k := range p.K8SSA.GetPublicKeys() { - if err := k.Select(ctx, db, k.Id.Id); err != nil { - return nil, err - } - } - return &ProvisionerDetails{Data: p}, nil - case ProvisionerType_SSHPOP: - v = new(ProvisionerDetails_SSHPOP) - default: - return nil, fmt.Errorf("unsupported provisioner type %s", typ) - } - - if err := json.Unmarshal([]byte(s.String), v); err != nil { - return nil, err - } - return &ProvisionerDetails{Data: v}, nil -} func marshalClaims(c *Claims) (sql.NullString, error) { b, err := json.Marshal(c) diff --git a/ca/mgmtClient.go b/ca/mgmtClient.go index ac494d90..67d6631c 100644 --- a/ca/mgmtClient.go +++ b/ca/mgmtClient.go @@ -105,3 +105,26 @@ retry: } return *admins, nil } + +// GetProvisioners performs the GET /mgmt/provisioners request to the CA. +func (c *MgmtClient) GetProvisioners() ([]*mgmt.Provisioner, error) { + var retried bool + u := c.endpoint.ResolveReference(&url.URL{Path: "/mgmt/provisioners"}) +retry: + resp, err := c.client.Get(u.String()) + if err != nil { + return nil, errors.Wrapf(err, "client GET %s failed", u) + } + if resp.StatusCode >= 400 { + if !retried && c.retryOnError(resp) { + retried = true + goto retry + } + return nil, readError(resp.Body) + } + var provs = new([]*mgmt.Provisioner) + if err := readJSON(resp.Body, provs); err != nil { + return nil, errors.Wrapf(err, "error reading %s", u) + } + return *provs, nil +} From 4d48072746864c44f44349f6f3d419a8a452af3f Mon Sep 17 00:00:00 2001 From: max furman Date: Wed, 12 May 2021 00:03:40 -0700 Subject: [PATCH 05/89] wip admin CRUD --- authority/authority.go | 1 + authority/mgmt/api/admin.go | 136 +++++++++++++++---------- authority/mgmt/api/handler.go | 4 +- authority/mgmt/db/nosql/admin.go | 1 + authority/mgmt/db/nosql/provisioner.go | 90 ++++++---------- authority/mgmt/errors.go | 11 +- authority/mgmt/provisioner.go | 114 +++++++++++++-------- ca/client.go | 5 + ca/mgmtClient.go | 85 ++++++++++++++++ 9 files changed, 293 insertions(+), 154 deletions(-) diff --git a/authority/authority.go b/authority/authority.go index 016b838a..ee82eb13 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -162,6 +162,7 @@ func (a *Authority) init() error { return mgmt.WrapErrorISE(err, "error getting authConfig from db") } } + a.config.AuthorityConfig, err = mgmtAuthConfig.ToCertificates() if err != nil { return err diff --git a/authority/mgmt/api/admin.go b/authority/mgmt/api/admin.go index f544b631..ae60b75a 100644 --- a/authority/mgmt/api/admin.go +++ b/authority/mgmt/api/admin.go @@ -5,13 +5,14 @@ import ( "github.com/go-chi/chi" "github.com/smallstep/certificates/api" + "github.com/smallstep/certificates/authority/mgmt" ) // CreateAdminRequest represents the body for a CreateAdmin request. type CreateAdminRequest struct { - Name string `json:"name"` - Provisioner string `json:"provisioner"` - IsSuperAdmin bool `json:"isSuperAdmin"` + Name string `json:"name"` + ProvisionerID string `json:"provisionerID"` + IsSuperAdmin bool `json:"isSuperAdmin"` } // Validate validates a new-admin request body. @@ -21,9 +22,10 @@ func (car *CreateAdminRequest) Validate() error { // UpdateAdminRequest represents the body for a UpdateAdmin request. type UpdateAdminRequest struct { - Name string `json:"name"` - Provisioner string `json:"provisioner"` - IsSuperAdmin bool `json:"isSuperAdmin"` + Name string `json:"name"` + ProvisionerID string `json:"provisionerID"` + IsSuperAdmin string `json:"isSuperAdmin"` + Status string `json:"status"` } // Validate validates a new-admin request body. @@ -31,6 +33,11 @@ func (uar *UpdateAdminRequest) Validate() error { return nil } +// DeleteResponse is the resource for successful DELETE responses. +type DeleteResponse struct { + Status string `json:"status"` +} + // GetAdmin returns the requested admin, or an error. func (h *Handler) GetAdmin(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -58,59 +65,84 @@ func (h *Handler) GetAdmins(w http.ResponseWriter, r *http.Request) { // CreateAdmin creates a new admin. func (h *Handler) CreateAdmin(w http.ResponseWriter, r *http.Request) { - /* - ctx := r.Context() + ctx := r.Context() - var body CreateAdminRequest - if err := ReadJSON(r.Body, &body); err != nil { - api.WriteError(w, err) - return - } - if err := body.Validate(); err != nil { - api.WriteError(w, err) - } + var body CreateAdminRequest + if err := api.ReadJSON(r.Body, &body); err != nil { + api.WriteError(w, mgmt.WrapError(mgmt.ErrorBadRequestType, err, "error reading request body")) + return + } - adm := &config.Admin{ - Name: body.Name, - Provisioner: body.Provisioner, - IsSuperAdmin: body.IsSuperAdmin, - } - if err := h.db.CreateAdmin(ctx, adm); err != nil { - api.WriteError(w, err) - return - } - api.JSONStatus(w, adm, http.StatusCreated) - */ + // TODO validate + + adm := &mgmt.Admin{ + ProvisionerID: body.ProvisionerID, + Name: body.Name, + IsSuperAdmin: body.IsSuperAdmin, + Status: mgmt.StatusActive, + } + if err := h.db.CreateAdmin(ctx, adm); err != nil { + api.WriteError(w, mgmt.WrapErrorISE(err, "error creating admin")) + return + } + api.JSON(w, adm) +} + +// DeleteAdmin deletes admin. +func (h *Handler) DeleteAdmin(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + id := chi.URLParam(r, "id") + + adm, err := h.db.GetAdmin(ctx, id) + if err != nil { + api.WriteError(w, mgmt.WrapErrorISE(err, "error retrieiving admin %s", id)) + return + } + adm.Status = mgmt.StatusDeleted + if err := h.db.UpdateAdmin(ctx, adm); err != nil { + api.WriteError(w, mgmt.WrapErrorISE(err, "error updating admin %s", id)) + return + } + api.JSON(w, &DeleteResponse{Status: "ok"}) } // UpdateAdmin updates an existing admin. func (h *Handler) UpdateAdmin(w http.ResponseWriter, r *http.Request) { - /* - ctx := r.Context() - id := chi.URLParam(r, "id") + ctx := r.Context() - var body UpdateAdminRequest - if err := ReadJSON(r.Body, &body); err != nil { - api.WriteError(w, err) - return - } - if err := body.Validate(); err != nil { - api.WriteError(w, err) - return - } - if adm, err := h.db.GetAdmin(ctx, id); err != nil { - api.WriteError(w, err) - return - } + var body UpdateAdminRequest + if err := api.ReadJSON(r.Body, &body); err != nil { + api.WriteError(w, mgmt.WrapError(mgmt.ErrorBadRequestType, err, "error reading request body")) + return + } + id := chi.URLParam(r, "id") + + adm, err := h.db.GetAdmin(ctx, id) + if err != nil { + api.WriteError(w, mgmt.WrapErrorISE(err, "error retrieiving admin %s", id)) + return + } + + // TODO validate + + if len(body.Name) > 0 { adm.Name = body.Name - adm.Provisioner = body.Provisioner - adm.IsSuperAdmin = body.IsSuperAdmin - - if err := h.db.UpdateAdmin(ctx, adm); err != nil { - api.WriteError(w, err) - return - } - api.JSON(w, adm) - */ + } + if len(body.Status) > 0 { + adm.Status = mgmt.StatusActive // FIXME + } + // Set IsSuperAdmin iff the string was set in the update request. + if len(body.IsSuperAdmin) > 0 { + adm.IsSuperAdmin = (body.IsSuperAdmin == "true") + } + if len(body.ProvisionerID) > 0 { + adm.ProvisionerID = body.ProvisionerID + } + if err := h.db.UpdateAdmin(ctx, adm); err != nil { + api.WriteError(w, mgmt.WrapErrorISE(err, "error updating admin %s", id)) + return + } + api.JSON(w, adm) } diff --git a/authority/mgmt/api/handler.go b/authority/mgmt/api/handler.go index 0e512a39..778cdaea 100644 --- a/authority/mgmt/api/handler.go +++ b/authority/mgmt/api/handler.go @@ -33,13 +33,15 @@ func (h *Handler) Route(r api.Router) { r.MethodFunc("GET", "/provisioner/{id}", h.GetProvisioner) r.MethodFunc("GET", "/provisioners", h.GetProvisioners) r.MethodFunc("POST", "/provisioner", h.CreateProvisioner) - r.MethodFunc("PUT", "/provsiioner/{id}", h.UpdateProvisioner) + r.MethodFunc("PUT", "/provisioner/{id}", h.UpdateProvisioner) + //r.MethodFunc("DELETE", "/provisioner/{id}", h.UpdateAdmin) // Admins r.MethodFunc("GET", "/admin/{id}", h.GetAdmin) r.MethodFunc("GET", "/admins", h.GetAdmins) r.MethodFunc("POST", "/admin", h.CreateAdmin) r.MethodFunc("PUT", "/admin/{id}", h.UpdateAdmin) + r.MethodFunc("DELETE", "/admin/{id}", h.DeleteAdmin) // AuthConfig r.MethodFunc("GET", "/authconfig/{id}", h.GetAuthConfig) diff --git a/authority/mgmt/db/nosql/admin.go b/authority/mgmt/db/nosql/admin.go index 97fc81b0..70cb12d1 100644 --- a/authority/mgmt/db/nosql/admin.go +++ b/authority/mgmt/db/nosql/admin.go @@ -131,6 +131,7 @@ func (db *DB) CreateAdmin(ctx context.Context, adm *mgmt.Admin) error { if err != nil { return errors.Wrap(err, "error generating random id for admin") } + adm.AuthorityID = db.authorityID dba := &dbAdmin{ ID: adm.ID, diff --git a/authority/mgmt/db/nosql/provisioner.go b/authority/mgmt/db/nosql/provisioner.go index dbea49f4..6d9f74ab 100644 --- a/authority/mgmt/db/nosql/provisioner.go +++ b/authority/mgmt/db/nosql/provisioner.go @@ -126,7 +126,6 @@ func unmarshalProvisioner(data []byte, id string) (*mgmt.Provisioner, error) { // GetProvisioners retrieves and unmarshals all active (not deleted) provisioners // from the database. -// TODO should we be paginating? func (db *DB) GetProvisioners(ctx context.Context) ([]*mgmt.Provisioner, error) { dbEntries, err := db.db.List(authorityProvisionersTable) if err != nil { @@ -157,13 +156,18 @@ func (db *DB) CreateProvisioner(ctx context.Context, prov *mgmt.Provisioner) err return errors.Wrap(err, "error generating random id for provisioner") } + details, err := json.Marshal(prov.Details) + if err != nil { + return mgmt.WrapErrorISE(err, "error marshaling details when creating provisioner") + } + dbp := &dbProvisioner{ ID: prov.ID, AuthorityID: db.authorityID, Type: prov.Type, Name: prov.Name, Claims: prov.Claims, - Details: prov.Details, + Details: details, X509Template: prov.X509Template, SSHTemplate: prov.SSHTemplate, CreatedAt: clock.Now(), @@ -186,72 +190,44 @@ func (db *DB) UpdateProvisioner(ctx context.Context, prov *mgmt.Provisioner) err nu.DeletedAt = clock.Now() } nu.Claims = prov.Claims - nu.Details = prov.Details nu.X509Template = prov.X509Template nu.SSHTemplate = prov.SSHTemplate + nu.Details, err = json.Marshal(prov.Details) + if err != nil { + return mgmt.WrapErrorISE(err, "error marshaling details when creating provisioner") + } + return db.save(ctx, old.ID, nu, old, "provisioner", authorityProvisionersTable) } -func unmarshalDetails(typ ProvisionerType, details []byte) (interface{}, error) { - if !s.Valid { - return nil, nil - } - var v isProvisionerDetails_Data +func unmarshalDetails(typ mgmt.ProvisionerType, data []byte) (mgmt.ProvisionerDetails, error) { + var v mgmt.ProvisionerDetails switch typ { - case ProvisionerTypeJWK: - p := new(ProvisionerDetailsJWK) - if err := json.Unmarshal([]byte(s.String), p); err != nil { - return nil, err - } - if p.JWK.Key.Key == nil { - key, err := LoadKey(ctx, db, p.JWK.Key.Id.Id) - if err != nil { - return nil, err - } - p.JWK.Key = key - } - return &ProvisionerDetails{Data: p}, nil - case ProvisionerType_OIDC: - v = new(ProvisionerDetails_OIDC) - case ProvisionerType_GCP: - v = new(ProvisionerDetails_GCP) - case ProvisionerType_AWS: - v = new(ProvisionerDetails_AWS) - case ProvisionerType_AZURE: - v = new(ProvisionerDetails_Azure) - case ProvisionerType_ACME: - v = new(ProvisionerDetails_ACME) - case ProvisionerType_X5C: - p := new(ProvisionerDetails_X5C) - if err := json.Unmarshal([]byte(s.String), p); err != nil { - return nil, err - } - for _, k := range p.X5C.GetRoots() { - if err := k.Select(ctx, db, k.Id.Id); err != nil { - return nil, err - } - } - return &ProvisionerDetails{Data: p}, nil - case ProvisionerType_K8SSA: - p := new(ProvisionerDetails_K8SSA) - if err := json.Unmarshal([]byte(s.String), p); err != nil { - return nil, err - } - for _, k := range p.K8SSA.GetPublicKeys() { - if err := k.Select(ctx, db, k.Id.Id); err != nil { - return nil, err - } - } - return &ProvisionerDetails{Data: p}, nil - case ProvisionerType_SSHPOP: - v = new(ProvisionerDetails_SSHPOP) + case mgmt.ProvisionerTypeJWK: + v = new(mgmt.ProvisionerDetailsJWK) + case mgmt.ProvisionerTypeOIDC: + v = new(mgmt.ProvisionerDetailsOIDC) + case mgmt.ProvisionerTypeGCP: + v = new(mgmt.ProvisionerDetailsGCP) + case mgmt.ProvisionerTypeAWS: + v = new(mgmt.ProvisionerDetailsAWS) + case mgmt.ProvisionerTypeAZURE: + v = new(mgmt.ProvisionerDetailsAzure) + case mgmt.ProvisionerTypeACME: + v = new(mgmt.ProvisionerDetailsACME) + case mgmt.ProvisionerTypeX5C: + v = new(mgmt.ProvisionerDetailsX5C) + case mgmt.ProvisionerTypeK8SSA: + v = new(mgmt.ProvisionerDetailsK8SSA) + case mgmt.ProvisionerTypeSSHPOP: + v = new(mgmt.ProvisionerDetailsSSHPOP) default: return nil, fmt.Errorf("unsupported provisioner type %s", typ) } - if err := json.Unmarshal([]byte(s.String), v); err != nil { + if err := json.Unmarshal(data, v); err != nil { return nil, err } - return &ProvisionerDetails{Data: v}, nil + return v, nil } diff --git a/authority/mgmt/errors.go b/authority/mgmt/errors.go index ae18619c..f0f90400 100644 --- a/authority/mgmt/errors.go +++ b/authority/mgmt/errors.go @@ -23,6 +23,8 @@ const ( ErrorAuthorityMismatchType // ErrorDeletedType resource has been deleted. ErrorDeletedType + // ErrorBadRequestType bad request. + ErrorBadRequestType // ErrorServerInternalType internal server error. ErrorServerInternalType ) @@ -37,6 +39,8 @@ func (ap ProblemType) String() string { return "authorityMismatch" case ErrorDeletedType: return "deleted" + case ErrorBadRequestType: + return "badRequest" case ErrorServerInternalType: return "internalServerError" default: @@ -69,10 +73,15 @@ var ( status: 401, }, ErrorDeletedType: { - typ: ErrorNotFoundType.String(), + typ: ErrorDeletedType.String(), details: "resource is deleted", status: 403, }, + ErrorBadRequestType: { + typ: ErrorBadRequestType.String(), + details: "bad request", + status: 400, + }, ErrorServerInternalType: errorServerInternalMetadata, } ) diff --git a/authority/mgmt/provisioner.go b/authority/mgmt/provisioner.go index c455bf85..961907f8 100644 --- a/authority/mgmt/provisioner.go +++ b/authority/mgmt/provisioner.go @@ -23,12 +23,15 @@ type ProvisionerCtx struct { type ProvisionerType string var ( - ProvisionerTypeJWK = ProvisionerType("JWK") - ProvisionerTypeOIDC = ProvisionerType("OIDC") ProvisionerTypeACME = ProvisionerType("ACME") - ProvisionerTypeX5C = ProvisionerType("X5C") - ProvisionerTypeK8S = ProvisionerType("K8S") + ProvisionerTypeAWS = ProvisionerType("AWS") + ProvisionerTypeAZURE = ProvisionerType("AZURE") + ProvisionerTypeGCP = ProvisionerType("GCP") + ProvisionerTypeJWK = ProvisionerType("JWK") + ProvisionerTypeK8SSA = ProvisionerType("K8SSA") + ProvisionerTypeOIDC = ProvisionerType("OIDC") ProvisionerTypeSSHPOP = ProvisionerType("SSHPOP") + ProvisionerTypeX5C = ProvisionerType("X5C") ) func NewProvisionerCtx(opts ...ProvisionerOption) *ProvisionerCtx { @@ -56,8 +59,8 @@ func WithPassword(pass string) func(*ProvisionerCtx) { // Provisioner type. type Provisioner struct { - ID string `json:"-"` - AuthorityID string `json:"-"` + ID string `json:"id"` + AuthorityID string `json:"authorityID"` Type string `json:"type"` Name string `json:"name"` Claims *Claims `json:"claims"` @@ -108,6 +111,10 @@ func CreateProvisioner(ctx context.Context, db DB, typ, name string, opts ...Pro return p, nil } +type ProvisionerDetails interface { + isProvisionerDetails() +} + // ProvisionerDetailsJWK represents the values required by a JWK provisioner. type ProvisionerDetailsJWK struct { Type ProvisionerType `json:"type"` @@ -115,6 +122,64 @@ type ProvisionerDetailsJWK struct { EncPrivKey string `json:"privKey"` } +// ProvisionerDetailsOIDC represents the values required by a OIDC provisioner. +type ProvisionerDetailsOIDC struct { + Type ProvisionerType `json:"type"` +} + +// ProvisionerDetailsGCP represents the values required by a GCP provisioner. +type ProvisionerDetailsGCP struct { + Type ProvisionerType `json:"type"` +} + +// ProvisionerDetailsAWS represents the values required by a AWS provisioner. +type ProvisionerDetailsAWS struct { + Type ProvisionerType `json:"type"` +} + +// ProvisionerDetailsAzure represents the values required by a Azure provisioner. +type ProvisionerDetailsAzure struct { + Type ProvisionerType `json:"type"` +} + +// ProvisionerDetailsACME represents the values required by a ACME provisioner. +type ProvisionerDetailsACME struct { + Type ProvisionerType `json:"type"` +} + +// ProvisionerDetailsX5C represents the values required by a X5C provisioner. +type ProvisionerDetailsX5C struct { + Type ProvisionerType `json:"type"` +} + +// ProvisionerDetailsK8SSA represents the values required by a K8SSA provisioner. +type ProvisionerDetailsK8SSA struct { + Type ProvisionerType `json:"type"` +} + +// ProvisionerDetailsSSHPOP represents the values required by a SSHPOP provisioner. +type ProvisionerDetailsSSHPOP struct { + Type ProvisionerType `json:"type"` +} + +func (*ProvisionerDetailsJWK) isProvisionerDetails() {} + +func (*ProvisionerDetailsOIDC) isProvisionerDetails() {} + +func (*ProvisionerDetailsGCP) isProvisionerDetails() {} + +func (*ProvisionerDetailsAWS) isProvisionerDetails() {} + +func (*ProvisionerDetailsAzure) isProvisionerDetails() {} + +func (*ProvisionerDetailsACME) isProvisionerDetails() {} + +func (*ProvisionerDetailsX5C) isProvisionerDetails() {} + +func (*ProvisionerDetailsK8SSA) isProvisionerDetails() {} + +func (*ProvisionerDetailsSSHPOP) isProvisionerDetails() {} + func createJWKDetails(pc *ProvisionerCtx) (*ProvisionerDetailsJWK, error) { var err error @@ -159,10 +224,6 @@ func (p *Provisioner) ToCertificates() (provisioner.Interface, error) { return nil, err } - if err != nil { - return nil, err - } - switch details := p.Details.(type) { case *ProvisionerDetailsJWK: jwk := new(jose.JSONWebKey) @@ -325,36 +386,3 @@ func (c *Claims) ToCertificates() (*provisioner.Claims, error) { EnableSSHCA: &c.SSH.Enabled, }, nil } - -/* -func marshalDetails(d *ProvisionerDetails) (sql.NullString, error) { - b, err := json.Marshal(d.GetData()) - if err != nil { - return sql.NullString{}, nil - } - return sql.NullString{ - String: string(b), - Valid: len(b) > 0, - }, nil -} - - -func marshalClaims(c *Claims) (sql.NullString, error) { - b, err := json.Marshal(c) - if err != nil { - return sql.NullString{}, nil - } - return sql.NullString{ - String: string(b), - Valid: len(b) > 0, - }, nil -} - -func unmarshalClaims(s sql.NullString) (*Claims, error) { - if !s.Valid { - return nil, nil - } - v := new(Claims) - return v, json.Unmarshal([]byte(s.String), v) -} -*/ diff --git a/ca/client.go b/ca/client.go index 2292c41e..3a3350ac 100644 --- a/ca/client.go +++ b/ca/client.go @@ -88,6 +88,11 @@ func (c *uaClient) Post(url, contentType string, body io.Reader) (*http.Response return c.Client.Do(req) } +func (c *uaClient) Do(req *http.Request) (*http.Response, error) { + req.Header.Set("User-Agent", UserAgent) + return c.Client.Do(req) +} + // RetryFunc defines the method used to retry a request. If it returns true, the // request will be retried once. type RetryFunc func(code int) bool diff --git a/ca/mgmtClient.go b/ca/mgmtClient.go index 67d6631c..47316ad6 100644 --- a/ca/mgmtClient.go +++ b/ca/mgmtClient.go @@ -1,12 +1,16 @@ package ca import ( + "bytes" + "encoding/json" "net/http" "net/url" "path" "github.com/pkg/errors" "github.com/smallstep/certificates/authority/mgmt" + mgmtAPI "github.com/smallstep/certificates/authority/mgmt/api" + "github.com/smallstep/certificates/errs" ) // MgmtClient implements an HTTP client for the CA server. @@ -83,6 +87,87 @@ retry: return adm, nil } +// CreateAdmin performs the POST /mgmt/admin request to the CA. +func (c *MgmtClient) CreateAdmin(req *mgmtAPI.CreateAdminRequest) (*mgmt.Admin, error) { + var retried bool + body, err := json.Marshal(req) + if err != nil { + return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request") + } + u := c.endpoint.ResolveReference(&url.URL{Path: "/mgmt/admin"}) +retry: + resp, err := c.client.Post(u.String(), "application/json", bytes.NewReader(body)) + if err != nil { + return nil, errors.Wrapf(err, "client POST %s failed", u) + } + if resp.StatusCode >= 400 { + if !retried && c.retryOnError(resp) { + retried = true + goto retry + } + return nil, readError(resp.Body) + } + var adm = new(mgmt.Admin) + if err := readJSON(resp.Body, adm); err != nil { + return nil, errors.Wrapf(err, "error reading %s", u) + } + return adm, nil +} + +// RemoveAdmin performs the DELETE /mgmt/admin/{id} request to the CA. +func (c *MgmtClient) RemoveAdmin(id string) error { + var retried bool + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/mgmt/admin", id)}) + req, err := http.NewRequest("DELETE", u.String(), nil) + if err != nil { + return errors.Wrapf(err, "create DELETE %s request failed", u) + } +retry: + resp, err := c.client.Do(req) + if err != nil { + return errors.Wrapf(err, "client DELETE %s failed", u) + } + if resp.StatusCode >= 400 { + if !retried && c.retryOnError(resp) { + retried = true + goto retry + } + return readError(resp.Body) + } + return nil +} + +// UpdateAdmin performs the PUT /mgmt/admin/{id} request to the CA. +func (c *MgmtClient) UpdateAdmin(id string, uar *mgmtAPI.UpdateAdminRequest) (*mgmt.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("/mgmt/admin", 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) + } +retry: + resp, err := c.client.Do(req) + if err != nil { + return nil, errors.Wrapf(err, "client PUT %s failed", u) + } + if resp.StatusCode >= 400 { + if !retried && c.retryOnError(resp) { + retried = true + goto retry + } + return nil, readError(resp.Body) + } + var adm = new(mgmt.Admin) + if err := readJSON(resp.Body, adm); err != nil { + return nil, errors.Wrapf(err, "error reading %s", u) + } + return adm, nil +} + // GetAdmins performs the GET /mgmt/admins request to the CA. func (c *MgmtClient) GetAdmins() ([]*mgmt.Admin, error) { var retried bool From 5d09d04d144bddd56c9d6490a929fedaf8c0d5a1 Mon Sep 17 00:00:00 2001 From: max furman Date: Mon, 17 May 2021 21:07:25 -0700 Subject: [PATCH 06/89] wip --- api/errors.go | 4 + authority/admin.go | 12 -- authority/admin/admin.go | 15 ++ authority/admin/collection.go | 173 ++++++++++++++++ authority/authority.go | 74 +++++++ authority/config/config.go | 2 + authority/mgmt/admin.go | 52 +++-- authority/mgmt/api/admin.go | 64 +++--- authority/mgmt/api/authConfig.go | 33 --- authority/mgmt/api/handler.go | 17 +- authority/mgmt/api/provisioner.go | 91 +++++++- authority/mgmt/authConfig.go | 12 +- authority/mgmt/config.go | 32 ++- authority/mgmt/db.go | 26 ++- authority/mgmt/db/nosql/admin.go | 112 ++++++---- authority/mgmt/db/nosql/authConfig.go | 5 + authority/mgmt/db/nosql/nosql.go | 13 +- authority/mgmt/db/nosql/provisioner.go | 274 +++++++++++++++++++------ authority/mgmt/errors.go | 17 +- authority/mgmt/provisioner.go | 69 ++++++- authority/provisioner/collection.go | 12 ++ authority/provisioner/jwk.go | 4 + ca/ca.go | 3 +- ca/mgmtClient.go | 151 +++++++++++++- 24 files changed, 1005 insertions(+), 262 deletions(-) delete mode 100644 authority/admin.go create mode 100644 authority/admin/admin.go create mode 100644 authority/admin/collection.go diff --git a/api/errors.go b/api/errors.go index fa2d6a06..085d05cf 100644 --- a/api/errors.go +++ b/api/errors.go @@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/acme" + "github.com/smallstep/certificates/authority/mgmt" "github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/logging" ) @@ -18,6 +19,9 @@ func WriteError(w http.ResponseWriter, err error) { case *acme.Error: acme.WriteError(w, k) return + case *mgmt.Error: + mgmt.WriteError(w, k) + return default: w.Header().Set("Content-Type", "application/json") } diff --git a/authority/admin.go b/authority/admin.go deleted file mode 100644 index 6c95de4f..00000000 --- a/authority/admin.go +++ /dev/null @@ -1,12 +0,0 @@ -package authority - -// Admin is the type definining Authority admins. Admins can update Authority -// configuration, provisioners, and even other admins. -type Admin struct { - ID string `json:"-"` - AuthorityID string `json:"-"` - Name string `json:"name"` - Provisioner string `json:"provisioner"` - IsSuperAdmin bool `json:"isSuperAdmin"` - IsDeleted bool `json:"isDeleted"` -} diff --git a/authority/admin/admin.go b/authority/admin/admin.go new file mode 100644 index 00000000..579538dc --- /dev/null +++ b/authority/admin/admin.go @@ -0,0 +1,15 @@ +package admin + +// Type specifies the type of administrator privileges the admin has. +type Type string + +// Admin type. +type Admin struct { + ID string `json:"id"` + AuthorityID string `json:"-"` + Subject string `json:"subject"` + ProvisionerName string `json:"provisionerName"` + ProvisionerType string `json:"provisionerType"` + ProvisionerID string `json:"provisionerID"` + Type Type `json:"type"` +} diff --git a/authority/admin/collection.go b/authority/admin/collection.go new file mode 100644 index 00000000..87dd63ce --- /dev/null +++ b/authority/admin/collection.go @@ -0,0 +1,173 @@ +package admin + +import ( + "crypto/sha1" + "sync" + + "github.com/pkg/errors" + "go.step.sm/crypto/jose" +) + +// DefaultProvisionersLimit is the default limit for listing provisioners. +const DefaultProvisionersLimit = 20 + +// DefaultProvisionersMax is the maximum limit for listing provisioners. +const DefaultProvisionersMax = 100 + +/* +type uidProvisioner struct { + provisioner Interface + uid string +} + +type provisionerSlice []uidProvisioner + +func (p provisionerSlice) Len() int { return len(p) } +func (p provisionerSlice) Less(i, j int) bool { return p[i].uid < p[j].uid } +func (p provisionerSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +*/ + +// loadByTokenPayload is a payload used to extract the id used to load the +// provisioner. +type loadByTokenPayload struct { + jose.Claims + AuthorizedParty string `json:"azp"` // OIDC client id + TenantID string `json:"tid"` // Microsoft Azure tenant id +} + +// Collection is a memory map of admins. +type Collection struct { + byID *sync.Map + bySubProv *sync.Map + byProv *sync.Map + count int + countByProvisioner map[string]int +} + +// NewCollection initializes a collection of provisioners. The given list of +// audiences are the audiences used by the JWT provisioner. +func NewCollection() *Collection { + return &Collection{ + byID: new(sync.Map), + byProv: new(sync.Map), + bySubProv: new(sync.Map), + countByProvisioner: map[string]int{}, + } +} + +// LoadByID a admin by the ID. +func (c *Collection) LoadByID(id string) (*Admin, bool) { + return loadAdmin(c.byID, id) +} + +func subProvNameHash(sub, provName string) string { + subHash := sha1.Sum([]byte(sub)) + provNameHash := sha1.Sum([]byte(provName)) + _res := sha1.Sum(append(subHash[:], provNameHash[:]...)) + return string(_res[:]) +} + +// LoadBySubProv a admin by the subject and provisioner name. +func (c *Collection) LoadBySubProv(sub, provName string) (*Admin, bool) { + return loadAdmin(c.bySubProv, subProvNameHash(sub, provName)) +} + +// LoadByProvisioner a admin by the subject and provisioner name. +func (c *Collection) LoadByProvisioner(provName string) ([]*Admin, bool) { + a, ok := c.byProv.Load(provName) + if !ok { + return nil, false + } + admins, ok := a.([]*Admin) + if !ok { + return nil, false + } + return admins, true +} + +// Store adds an admin to the collection and enforces the uniqueness of +// admin IDs and amdin subject <-> provisioner name combos. +func (c *Collection) Store(adm *Admin) error { + provName := adm.ProvisionerName + // Store admin always in byID. ID must be unique. + if _, loaded := c.byID.LoadOrStore(adm.ID, adm); loaded { + return errors.New("cannot add multiple admins with the same id") + } + + // Store admin alwasy in bySubProv. Subject <-> ProvisionerName must be unique. + if _, loaded := c.bySubProv.LoadOrStore(subProvNameHash(adm.Subject, provName), adm); loaded { + c.byID.Delete(adm.ID) + return errors.New("cannot add multiple admins with the same subject and provisioner") + } + + if admins, ok := c.LoadByProvisioner(provName); ok { + c.byProv.Store(provName, append(admins, adm)) + c.countByProvisioner[provName]++ + } else { + c.byProv.Store(provName, []*Admin{adm}) + c.countByProvisioner[provName] = 1 + } + c.count++ + + return nil +} + +// Count returns the total number of admins. +func (c *Collection) Count() int { + return c.count +} + +// CountByProvisioner returns the total number of admins. +func (c *Collection) CountByProvisioner(provName string) int { + if cnt, ok := c.countByProvisioner[provName]; ok { + return cnt + } + return 0 +} + +/* +// Find implements pagination on a list of sorted provisioners. +func (c *Collection) Find(cursor string, limit int) (List, string) { + switch { + case limit <= 0: + limit = DefaultProvisionersLimit + case limit > DefaultProvisionersMax: + limit = DefaultProvisionersMax + } + + n := c.sorted.Len() + cursor = fmt.Sprintf("%040s", cursor) + i := sort.Search(n, func(i int) bool { return c.sorted[i].uid >= cursor }) + + slice := List{} + for ; i < n && len(slice) < limit; i++ { + slice = append(slice, c.sorted[i].provisioner) + } + + if i < n { + return slice, strings.TrimLeft(c.sorted[i].uid, "0") + } + return slice, "" +} +*/ + +func loadAdmin(m *sync.Map, key string) (*Admin, bool) { + a, ok := m.Load(key) + if !ok { + return nil, false + } + adm, ok := a.(*Admin) + if !ok { + return nil, false + } + return adm, true +} + +/* +// provisionerSum returns the SHA1 of the provisioners ID. From this we will +// create the unique and sorted id. +func provisionerSum(p Interface) []byte { + sum := sha1.Sum([]byte(p.GetID())) + return sum[:] +} +*/ diff --git a/authority/authority.go b/authority/authority.go index ee82eb13..2da5b341 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -13,6 +13,7 @@ import ( "github.com/smallstep/certificates/cas" "github.com/pkg/errors" + "github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/mgmt" authMgmtNosql "github.com/smallstep/certificates/authority/mgmt/db/nosql" @@ -34,6 +35,7 @@ type Authority struct { mgmtDB mgmt.DB keyManager kms.KeyManager provisioners *provisioner.Collection + admins *admin.Collection db db.AuthDB templates *templates.Templates @@ -127,6 +129,61 @@ func NewEmbedded(opts ...Option) (*Authority, error) { return a, nil } +func (a *Authority) ReloadAuthConfig() error { + mgmtAuthConfig, err := a.mgmtDB.GetAuthConfig(context.Background(), mgmt.DefaultAuthorityID) + if err != nil { + return mgmt.WrapErrorISE(err, "error getting authConfig from db") + } + + a.config.AuthorityConfig, err = mgmtAuthConfig.ToCertificates() + if err != nil { + return mgmt.WrapErrorISE(err, "error converting mgmt authConfig to certificates authConfig") + } + + // Merge global and configuration claims + claimer, err := provisioner.NewClaimer(a.config.AuthorityConfig.Claims, config.GlobalProvisionerClaims) + if err != nil { + return err + } + // 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()) + if err != nil { + return err + } + // Initialize provisioners + audiences := a.config.GetAudiences() + a.provisioners = provisioner.NewCollection(audiences) + config := provisioner.Config{ + Claims: claimer.Claims(), + Audiences: audiences, + DB: a.db, + SSHKeys: &provisioner.SSHKeys{ + UserKeys: sshKeys.UserKeys, + HostKeys: sshKeys.HostKeys, + }, + GetIdentityFunc: a.getIdentityFunc, + } + // Store all the provisioners + for _, p := range a.config.AuthorityConfig.Provisioners { + if err := p.Init(config); err != nil { + return err + } + if err := a.provisioners.Store(p); err != nil { + return err + } + } + // Store all the admins + a.admins = admin.NewCollection() + for _, adm := range a.config.AuthorityConfig.Admins { + if err := a.admins.Store(adm); err != nil { + return err + } + } + return nil +} + // init performs validation and initializes the fields of an Authority struct. func (a *Authority) init() error { // Check if handler has already been validated/initialized. @@ -373,6 +430,13 @@ func (a *Authority) init() error { return err } } + // Store all the admins + a.admins = admin.NewCollection() + for _, adm := range a.config.AuthorityConfig.Admins { + if err := a.admins.Store(adm); err != nil { + return err + } + } // Configure templates, currently only ssh templates are supported. if a.sshCAHostCertSignKey != nil || a.sshCAUserCertSignKey != nil { @@ -406,6 +470,16 @@ func (a *Authority) GetMgmtDatabase() mgmt.DB { return a.mgmtDB } +// GetAdminCollection returns the admin collection. +func (a *Authority) GetAdminCollection() *admin.Collection { + return a.admins +} + +// GetProvisionerCollection returns the admin collection. +func (a *Authority) GetProvisionerCollection() *provisioner.Collection { + return a.provisioners +} + // Shutdown safely shuts down any clients, databases, etc. held by the Authority. func (a *Authority) Shutdown() error { if err := a.keyManager.Close(); err != nil { diff --git a/authority/config/config.go b/authority/config/config.go index 66b8bbe0..9fbf18e0 100644 --- a/authority/config/config.go +++ b/authority/config/config.go @@ -8,6 +8,7 @@ import ( "time" "github.com/pkg/errors" + "github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/authority/provisioner" cas "github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/db" @@ -95,6 +96,7 @@ type AuthConfig struct { *cas.Options AuthorityID string `json:"authorityID,omitempty"` Provisioners provisioner.List `json:"provisioners"` + Admins []*admin.Admin `json:"-"` Template *ASN1DN `json:"template,omitempty"` Claims *provisioner.Claims `json:"claims,omitempty"` DisableIssuedAtCheck bool `json:"disableIssuedAtCheck,omitempty"` diff --git a/authority/mgmt/admin.go b/authority/mgmt/admin.go index 9ceabe93..f21abdce 100644 --- a/authority/mgmt/admin.go +++ b/authority/mgmt/admin.go @@ -1,27 +1,55 @@ package mgmt -import "context" +import ( + "context" + + "github.com/smallstep/certificates/authority/admin" +) + +// AdminType specifies the type of the admin. e.g. SUPER_ADMIN, REGULAR +type AdminType string + +var ( + // AdminTypeSuper superadmin + AdminTypeSuper = AdminType("SUPER_ADMIN") + // AdminTypeRegular regular + AdminTypeRegular = AdminType("REGULAR") +) // Admin type. type Admin struct { - ID string `json:"id"` - AuthorityID string `json:"-"` - ProvisionerID string `json:"provisionerID"` - Name string `json:"name"` - IsSuperAdmin bool `json:"isSuperAdmin"` - Status StatusType `json:"status"` + ID string `json:"id"` + AuthorityID string `json:"-"` + ProvisionerID string `json:"provisionerID"` + Subject string `json:"subject"` + ProvisionerName string `json:"provisionerName"` + ProvisionerType string `json:"provisionerType"` + Type AdminType `json:"type"` + Status StatusType `json:"status"` } // CreateAdmin builds and stores an admin type in the DB. -func CreateAdmin(ctx context.Context, db DB, name string, provID string, isSuperAdmin bool) (*Admin, error) { +func CreateAdmin(ctx context.Context, db DB, provName, sub string, typ AdminType) (*Admin, error) { adm := &Admin{ - Name: name, - ProvisionerID: provID, - IsSuperAdmin: isSuperAdmin, - Status: StatusActive, + Subject: sub, + ProvisionerName: provName, + Type: typ, + Status: StatusActive, } if err := db.CreateAdmin(ctx, adm); err != nil { return nil, WrapErrorISE(err, "error creating admin") } return adm, nil } + +// ToCertificates converts an Admin to the Admin type expected by the authority. +func (adm *Admin) ToCertificates() (*admin.Admin, error) { + return &admin.Admin{ + ID: adm.ID, + Subject: adm.Subject, + ProvisionerID: adm.ProvisionerID, + ProvisionerName: adm.ProvisionerName, + ProvisionerType: adm.ProvisionerType, + Type: admin.Type(adm.Type), + }, nil +} diff --git a/authority/mgmt/api/admin.go b/authority/mgmt/api/admin.go index ae60b75a..f997095a 100644 --- a/authority/mgmt/api/admin.go +++ b/authority/mgmt/api/admin.go @@ -1,31 +1,34 @@ package api import ( + "fmt" "net/http" "github.com/go-chi/chi" "github.com/smallstep/certificates/api" + "github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/authority/mgmt" ) // CreateAdminRequest represents the body for a CreateAdmin request. type CreateAdminRequest struct { - Name string `json:"name"` - ProvisionerID string `json:"provisionerID"` - IsSuperAdmin bool `json:"isSuperAdmin"` + Subject string `json:"subject"` + Provisioner string `json:"provisioner"` + Type mgmt.AdminType `json:"type"` } // Validate validates a new-admin request body. -func (car *CreateAdminRequest) Validate() error { +func (car *CreateAdminRequest) Validate(c *admin.Collection) error { + if _, ok := c.LoadBySubProv(car.Subject, car.Provisioner); ok { + return mgmt.NewError(mgmt.ErrorBadRequestType, + "admin with subject %s and provisioner name %s already exists", car.Subject, car.Provisioner) + } return nil } // UpdateAdminRequest represents the body for a UpdateAdmin request. type UpdateAdminRequest struct { - Name string `json:"name"` - ProvisionerID string `json:"provisionerID"` - IsSuperAdmin string `json:"isSuperAdmin"` - Status string `json:"status"` + Type mgmt.AdminType `json:"type"` } // Validate validates a new-admin request body. @@ -73,27 +76,37 @@ func (h *Handler) CreateAdmin(w http.ResponseWriter, r *http.Request) { return } - // TODO validate + if err := body.Validate(h.auth.GetAdminCollection()); err != nil { + api.WriteError(w, err) + return + } adm := &mgmt.Admin{ - ProvisionerID: body.ProvisionerID, - Name: body.Name, - IsSuperAdmin: body.IsSuperAdmin, - Status: mgmt.StatusActive, + ProvisionerName: body.Provisioner, + Subject: body.Subject, + Type: body.Type, + Status: mgmt.StatusActive, } if err := h.db.CreateAdmin(ctx, adm); err != nil { api.WriteError(w, mgmt.WrapErrorISE(err, "error creating admin")) return } api.JSON(w, adm) + if err := h.auth.ReloadAuthConfig(); err != nil { + fmt.Printf("err = %+v\n", err) + } } // DeleteAdmin deletes admin. func (h *Handler) DeleteAdmin(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - id := chi.URLParam(r, "id") + if h.auth.GetAdminCollection().Count() == 1 { + api.WriteError(w, mgmt.NewError(mgmt.ErrorBadRequestType, "cannot remove last admin")) + return + } + + ctx := r.Context() adm, err := h.db.GetAdmin(ctx, id) if err != nil { api.WriteError(w, mgmt.WrapErrorISE(err, "error retrieiving admin %s", id)) @@ -105,6 +118,9 @@ func (h *Handler) DeleteAdmin(w http.ResponseWriter, r *http.Request) { return } api.JSON(w, &DeleteResponse{Status: "ok"}) + if err := h.auth.ReloadAuthConfig(); err != nil { + fmt.Printf("err = %+v\n", err) + } } // UpdateAdmin updates an existing admin. @@ -127,22 +143,14 @@ func (h *Handler) UpdateAdmin(w http.ResponseWriter, r *http.Request) { // TODO validate - if len(body.Name) > 0 { - adm.Name = body.Name - } - if len(body.Status) > 0 { - adm.Status = mgmt.StatusActive // FIXME - } - // Set IsSuperAdmin iff the string was set in the update request. - if len(body.IsSuperAdmin) > 0 { - adm.IsSuperAdmin = (body.IsSuperAdmin == "true") - } - if len(body.ProvisionerID) > 0 { - adm.ProvisionerID = body.ProvisionerID - } + adm.Type = body.Type + if err := h.db.UpdateAdmin(ctx, 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 { + fmt.Printf("err = %+v\n", err) + } } diff --git a/authority/mgmt/api/authConfig.go b/authority/mgmt/api/authConfig.go index 283a4b66..7d5f7afb 100644 --- a/authority/mgmt/api/authConfig.go +++ b/authority/mgmt/api/authConfig.go @@ -46,39 +46,6 @@ func (h *Handler) GetAuthConfig(w http.ResponseWriter, r *http.Request) { api.JSON(w, ac) } -// CreateAuthConfig creates a new admin. -func (h *Handler) CreateAuthConfig(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - var body CreateAuthConfigRequest - if err := api.ReadJSON(r.Body, &body); err != nil { - api.WriteError(w, err) - return - } - if err := body.Validate(); err != nil { - api.WriteError(w, err) - } - - ac := &mgmt.AuthConfig{ - Status: mgmt.StatusActive, - Backdate: "1m", - } - 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.CreateAuthConfig(ctx, ac); err != nil { - api.WriteError(w, err) - return - } - api.JSONStatus(w, ac, http.StatusCreated) -} - // UpdateAuthConfig updates an existing AuthConfig. func (h *Handler) UpdateAuthConfig(w http.ResponseWriter, r *http.Request) { /* diff --git a/authority/mgmt/api/handler.go b/authority/mgmt/api/handler.go index 778cdaea..cb52736f 100644 --- a/authority/mgmt/api/handler.go +++ b/authority/mgmt/api/handler.go @@ -4,6 +4,7 @@ import ( "time" "github.com/smallstep/certificates/api" + "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority/mgmt" ) @@ -19,32 +20,32 @@ var clock Clock // Handler is the ACME API request handler. type Handler struct { - db mgmt.DB + db mgmt.DB + auth *authority.Authority } // NewHandler returns a new Authority Config Handler. -func NewHandler(db mgmt.DB) api.RouterHandler { - return &Handler{db} +func NewHandler(db mgmt.DB, auth *authority.Authority) api.RouterHandler { + return &Handler{db, auth} } // Route traffic and implement the Router interface. func (h *Handler) Route(r api.Router) { // Provisioners - r.MethodFunc("GET", "/provisioner/{id}", h.GetProvisioner) + r.MethodFunc("GET", "/provisioner/{name}", h.GetProvisioner) r.MethodFunc("GET", "/provisioners", h.GetProvisioners) r.MethodFunc("POST", "/provisioner", h.CreateProvisioner) - r.MethodFunc("PUT", "/provisioner/{id}", h.UpdateProvisioner) - //r.MethodFunc("DELETE", "/provisioner/{id}", h.UpdateAdmin) + r.MethodFunc("PUT", "/provisioner/{name}", h.UpdateProvisioner) + r.MethodFunc("DELETE", "/provisioner/{name}", h.DeleteProvisioner) // Admins r.MethodFunc("GET", "/admin/{id}", h.GetAdmin) r.MethodFunc("GET", "/admins", h.GetAdmins) r.MethodFunc("POST", "/admin", h.CreateAdmin) - r.MethodFunc("PUT", "/admin/{id}", h.UpdateAdmin) + r.MethodFunc("PATCH", "/admin/{id}", h.UpdateAdmin) r.MethodFunc("DELETE", "/admin/{id}", h.DeleteAdmin) // AuthConfig r.MethodFunc("GET", "/authconfig/{id}", h.GetAuthConfig) - r.MethodFunc("POST", "/authconfig", h.CreateAuthConfig) r.MethodFunc("PUT", "/authconfig/{id}", h.UpdateAuthConfig) } diff --git a/authority/mgmt/api/provisioner.go b/authority/mgmt/api/provisioner.go index b6b4c1c7..8a8da08f 100644 --- a/authority/mgmt/api/provisioner.go +++ b/authority/mgmt/api/provisioner.go @@ -7,6 +7,7 @@ import ( "github.com/go-chi/chi" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority/mgmt" + "github.com/smallstep/certificates/authority/provisioner" ) // CreateProvisionerRequest represents the body for a CreateProvisioner request. @@ -14,7 +15,7 @@ type CreateProvisionerRequest struct { Type string `json:"type"` Name string `json:"name"` Claims *mgmt.Claims `json:"claims"` - Details interface{} `json:"details"` + Details []byte `json:"details"` X509Template string `json:"x509Template"` X509TemplateData []byte `json:"x509TemplateData"` SSHTemplate string `json:"sshTemplate"` @@ -22,31 +23,39 @@ type CreateProvisionerRequest struct { } // Validate validates a new-provisioner request body. -func (car *CreateProvisionerRequest) Validate() error { +func (cpr *CreateProvisionerRequest) Validate(c *provisioner.Collection) error { + if _, ok := c.LoadByName(cpr.Name); ok { + return mgmt.NewError(mgmt.ErrorBadRequestType, "provisioner with name %s already exists", cpr.Name) + } return nil } // 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 interface{} `json:"details"` + 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. -func (uar *UpdateProvisionerRequest) Validate() error { +// Validate validates a update-provisioner request body. +func (upr *UpdateProvisionerRequest) Validate(c *provisioner.Collection) error { + if _, ok := c.LoadByName(upr.Name); ok { + return mgmt.NewError(mgmt.ErrorBadRequestType, "provisioner with name %s already exists", upr.Name) + } return nil } // GetProvisioner returns the requested provisioner, or an error. func (h *Handler) GetProvisioner(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - id := chi.URLParam(r, "id") + name := chi.URLParam(r, "name") - prov, err := h.db.GetProvisioner(ctx, id) + prov, err := h.db.GetProvisionerByName(ctx, name) if err != nil { api.WriteError(w, err) return @@ -63,7 +72,6 @@ func (h *Handler) GetProvisioners(w http.ResponseWriter, r *http.Request) { api.WriteError(w, err) return } - fmt.Printf("provs = %+v\n", provs) api.JSON(w, provs) } @@ -76,15 +84,24 @@ func (h *Handler) CreateProvisioner(w http.ResponseWriter, r *http.Request) { api.WriteError(w, err) return } - if err := body.Validate(); err != nil { + if err := body.Validate(h.auth.GetProvisionerCollection()); err != nil { api.WriteError(w, err) + return } + details, err := mgmt.UnmarshalProvisionerDetails(body.Details) + if err != nil { + api.WriteError(w, mgmt.WrapErrorISE(err, "error unmarshaling provisioner details")) + return + } + + claims := mgmt.NewDefaultClaims() + prov := &mgmt.Provisioner{ Type: body.Type, Name: body.Name, - Claims: body.Claims, - Details: body.Details, + Claims: claims, + Details: details, X509Template: body.X509Template, X509TemplateData: body.X509TemplateData, SSHTemplate: body.SSHTemplate, @@ -95,6 +112,58 @@ func (h *Handler) CreateProvisioner(w http.ResponseWriter, r *http.Request) { return } api.JSONStatus(w, prov, http.StatusCreated) + + if err := h.auth.ReloadAuthConfig(); err != nil { + fmt.Printf("err = %+v\n", err) + } +} + +// DeleteProvisioner deletes a provisioner. +func (h *Handler) DeleteProvisioner(w http.ResponseWriter, r *http.Request) { + name := chi.URLParam(r, "name") + + c := h.auth.GetAdminCollection() + if c.Count() == c.CountByProvisioner(name) { + api.WriteError(w, mgmt.NewError(mgmt.ErrorBadRequestType, + "cannot remove provisioner %s because no admins will remain", name)) + return + } + + ctx := r.Context() + prov, err := h.db.GetProvisionerByName(ctx, name) + if err != nil { + api.WriteError(w, mgmt.WrapErrorISE(err, "error retrieiving provisioner %s", name)) + return + } + fmt.Printf("prov = %+v\n", prov) + prov.Status = mgmt.StatusDeleted + if err := h.db.UpdateProvisioner(ctx, name, prov); err != nil { + api.WriteError(w, mgmt.WrapErrorISE(err, "error updating provisioner %s", name)) + return + } + + // Delete all admins associated with the provisioner. + 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: mgmt.AdminType(adm.Type), + Status: mgmt.StatusDeleted, + }); err != nil { + api.WriteError(w, mgmt.WrapErrorISE(err, "error deleting admin %s, as part of provisioner %s deletion", adm.Subject, name)) + return + } + } + } + + api.JSON(w, &DeleteResponse{Status: "ok"}) + + if err := h.auth.ReloadAuthConfig(); err != nil { + fmt.Printf("err = %+v\n", err) + } } // UpdateProvisioner updates an existing prov. diff --git a/authority/mgmt/authConfig.go b/authority/mgmt/authConfig.go index 734bca50..14448af4 100644 --- a/authority/mgmt/authConfig.go +++ b/authority/mgmt/authConfig.go @@ -1,6 +1,7 @@ package mgmt import ( + "github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/provisioner" ) @@ -9,7 +10,7 @@ import ( type AuthConfig struct { //*cas.Options `json:"cas"` ID string `json:"id"` - ASN1DN *config.ASN1DN `json:"template,omitempty"` + ASN1DN *config.ASN1DN `json:"asn1dn,omitempty"` Provisioners []*Provisioner `json:"-"` Admins []*Admin `json:"-"` Claims *Claims `json:"claims,omitempty"` @@ -46,9 +47,18 @@ func (ac *AuthConfig) ToCertificates() (*config.AuthConfig, error) { } provs = append(provs, authProv) } + var admins []*admin.Admin + for _, adm := range ac.Admins { + authAdmin, err := adm.ToCertificates() + if err != nil { + return nil, err + } + admins = append(admins, authAdmin) + } return &config.AuthConfig{ AuthorityID: ac.ID, Provisioners: provs, + Admins: admins, Template: ac.ASN1DN, Claims: claims, DisableIssuedAtCheck: false, diff --git a/authority/mgmt/config.go b/authority/mgmt/config.go index b3ece47f..d569ad6f 100644 --- a/authority/mgmt/config.go +++ b/authority/mgmt/config.go @@ -2,7 +2,6 @@ package mgmt import ( "context" - "fmt" "github.com/pkg/errors" "github.com/smallstep/certificates/authority/config" @@ -16,26 +15,15 @@ const ( ) // StatusType is the type for status. -type StatusType int +type StatusType string -const ( +var ( // StatusActive active - StatusActive StatusType = iota + StatusActive = StatusType("active") // StatusDeleted deleted - StatusDeleted + StatusDeleted = StatusType("deleted") ) -func (st StatusType) String() string { - switch st { - case StatusActive: - return "active" - case StatusDeleted: - return "deleted" - default: - return fmt.Sprintf("status %d not found", st) - } -} - // Claims encapsulates all x509 and ssh claims applied to the authority // configuration. E.g. maxTLSCertDuration, defaultSSHCertDuration, etc. type Claims struct { @@ -123,14 +111,18 @@ func CreateAuthority(ctx context.Context, db DB, options ...AuthorityOption) (*A return nil, WrapErrorISE(err, "error creating first provisioner") } - admin, err := CreateAdmin(ctx, db, "Change Me", prov.ID, true) - if err != nil { + adm := &Admin{ + ProvisionerID: prov.ID, + Subject: "Change Me", + Type: AdminTypeSuper, + } + if err := db.CreateAdmin(ctx, adm); err != nil { // TODO should we try to clean up? - return nil, WrapErrorISE(err, "error creating first provisioner") + return nil, WrapErrorISE(err, "error creating first admin") } ac.Provisioners = []*Provisioner{prov} - ac.Admins = []*Admin{admin} + ac.Admins = []*Admin{adm} return ac, nil } diff --git a/authority/mgmt/db.go b/authority/mgmt/db.go index 9cfab3e5..8546c4b4 100644 --- a/authority/mgmt/db.go +++ b/authority/mgmt/db.go @@ -14,8 +14,9 @@ var ErrNotFound = errors.New("not found") type DB interface { CreateProvisioner(ctx context.Context, prov *Provisioner) error GetProvisioner(ctx context.Context, id string) (*Provisioner, error) + GetProvisionerByName(ctx context.Context, name string) (*Provisioner, error) GetProvisioners(ctx context.Context) ([]*Provisioner, error) - UpdateProvisioner(ctx context.Context, prov *Provisioner) error + UpdateProvisioner(ctx context.Context, name string, prov *Provisioner) error CreateAdmin(ctx context.Context, admin *Admin) error GetAdmin(ctx context.Context, id string) (*Admin, error) @@ -30,10 +31,11 @@ type DB interface { // MockDB is an implementation of the DB interface that should only be used as // a mock in tests. type MockDB struct { - MockCreateProvisioner func(ctx context.Context, prov *Provisioner) error - MockGetProvisioner func(ctx context.Context, id string) (*Provisioner, error) - MockGetProvisioners func(ctx context.Context) ([]*Provisioner, error) - MockUpdateProvisioner func(ctx context.Context, prov *Provisioner) error + MockCreateProvisioner func(ctx context.Context, prov *Provisioner) error + MockGetProvisioner func(ctx context.Context, id string) (*Provisioner, error) + MockGetProvisionerByName func(ctx context.Context, name string) (*Provisioner, error) + MockGetProvisioners func(ctx context.Context) ([]*Provisioner, error) + MockUpdateProvisioner func(ctx context.Context, name string, prov *Provisioner) error MockCreateAdmin func(ctx context.Context, adm *Admin) error MockGetAdmin func(ctx context.Context, id string) (*Admin, error) @@ -68,6 +70,16 @@ func (m *MockDB) GetProvisioner(ctx context.Context, id string) (*Provisioner, e return m.MockRet1.(*Provisioner), m.MockError } +// GetProvisionerByName mock. +func (m *MockDB) GetProvisionerByName(ctx context.Context, id string) (*Provisioner, error) { + if m.MockGetProvisionerByName != nil { + return m.MockGetProvisionerByName(ctx, id) + } else if m.MockError != nil { + return nil, m.MockError + } + return m.MockRet1.(*Provisioner), m.MockError +} + // GetProvisioners mock func (m *MockDB) GetProvisioners(ctx context.Context) ([]*Provisioner, error) { if m.MockGetProvisioners != nil { @@ -79,9 +91,9 @@ func (m *MockDB) GetProvisioners(ctx context.Context) ([]*Provisioner, error) { } // UpdateProvisioner mock -func (m *MockDB) UpdateProvisioner(ctx context.Context, prov *Provisioner) error { +func (m *MockDB) UpdateProvisioner(ctx context.Context, name string, prov *Provisioner) error { if m.MockUpdateProvisioner != nil { - return m.MockUpdateProvisioner(ctx, prov) + return m.MockUpdateProvisioner(ctx, name, prov) } return m.MockError } diff --git a/authority/mgmt/db/nosql/admin.go b/authority/mgmt/db/nosql/admin.go index 70cb12d1..4e465489 100644 --- a/authority/mgmt/db/nosql/admin.go +++ b/authority/mgmt/db/nosql/admin.go @@ -12,13 +12,13 @@ import ( // dbAdmin is the database representation of the Admin type. type dbAdmin struct { - ID string `json:"id"` - AuthorityID string `json:"authorityID"` - ProvisionerID string `json:"provisionerID"` - Name string `json:"name"` - IsSuperAdmin bool `json:"isSuperAdmin"` - CreatedAt time.Time `json:"createdAt"` - DeletedAt time.Time `json:"deletedAt"` + ID string `json:"id"` + AuthorityID string `json:"authorityID"` + ProvisionerID string `json:"provisionerID"` + Subject string `json:"subject"` + Type mgmt.AdminType `json:"type"` + CreatedAt time.Time `json:"createdAt"` + DeletedAt time.Time `json:"deletedAt"` } func (dbp *dbAdmin) clone() *dbAdmin { @@ -52,27 +52,6 @@ func (db *DB) getDBAdmin(ctx context.Context, id string) (*dbAdmin, error) { return dba, nil } -// GetAdmin retrieves and unmarshals a admin from the database. -func (db *DB) GetAdmin(ctx context.Context, id string) (*mgmt.Admin, error) { - data, err := db.getDBAdminBytes(ctx, id) - if err != nil { - return nil, err - } - adm, err := unmarshalAdmin(data, id) - if err != nil { - return nil, err - } - if adm.Status == mgmt.StatusDeleted { - return nil, mgmt.NewError(mgmt.ErrorDeletedType, "admin %s is deleted", adm.ID) - } - if adm.AuthorityID != db.authorityID { - return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType, - "admin %s is not owned by authority %s", adm.ID, db.authorityID) - } - - return adm, nil -} - func unmarshalDBAdmin(data []byte, id string) (*dbAdmin, error) { var dba = new(dbAdmin) if err := json.Unmarshal(data, dba); err != nil { @@ -90,8 +69,9 @@ func unmarshalAdmin(data []byte, id string) (*mgmt.Admin, error) { ID: dba.ID, AuthorityID: dba.AuthorityID, ProvisionerID: dba.ProvisionerID, - Name: dba.Name, - IsSuperAdmin: dba.IsSuperAdmin, + Subject: dba.Subject, + Type: dba.Type, + Status: mgmt.StatusActive, } if !dba.DeletedAt.IsZero() { adm.Status = mgmt.StatusDeleted @@ -99,6 +79,33 @@ func unmarshalAdmin(data []byte, id string) (*mgmt.Admin, error) { return adm, nil } +// GetAdmin retrieves and unmarshals a admin from the database. +func (db *DB) GetAdmin(ctx context.Context, id string) (*mgmt.Admin, error) { + data, err := db.getDBAdminBytes(ctx, id) + if err != nil { + return nil, err + } + adm, err := unmarshalAdmin(data, id) + if err != nil { + return nil, err + } + if adm.Status == mgmt.StatusDeleted { + return nil, mgmt.NewError(mgmt.ErrorDeletedType, "admin %s is deleted", adm.ID) + } + if adm.AuthorityID != db.authorityID { + return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType, + "admin %s is not owned by authority %s", adm.ID, db.authorityID) + } + prov, err := db.GetProvisioner(ctx, adm.ProvisionerID) + if err != nil { + return nil, err + } + adm.ProvisionerName = prov.Name + adm.ProvisionerType = prov.Type + + return adm, nil +} + // GetAdmins retrieves and unmarshals all active (not deleted) admins // from the database. // TODO should we be paginating? @@ -107,7 +114,10 @@ func (db *DB) GetAdmins(ctx context.Context) ([]*mgmt.Admin, error) { if err != nil { return nil, errors.Wrap(err, "error loading admins") } - var admins []*mgmt.Admin + var ( + provCache = map[string]*mgmt.Provisioner{} + admins []*mgmt.Admin + ) for _, entry := range dbEntries { adm, err := unmarshalAdmin(entry.Value, string(entry.Key)) if err != nil { @@ -119,6 +129,19 @@ func (db *DB) GetAdmins(ctx context.Context) ([]*mgmt.Admin, error) { if adm.AuthorityID != db.authorityID { continue } + var ( + prov *mgmt.Provisioner + ok bool + ) + if prov, ok = provCache[adm.ProvisionerID]; !ok { + prov, err = db.GetProvisioner(ctx, adm.ProvisionerID) + if err != nil { + return nil, err + } + provCache[adm.ProvisionerID] = prov + } + adm.ProvisionerName = prov.Name + adm.ProvisionerType = prov.Type admins = append(admins, adm) } return admins, nil @@ -129,16 +152,34 @@ func (db *DB) CreateAdmin(ctx context.Context, adm *mgmt.Admin) error { var err error adm.ID, err = randID() if err != nil { - return errors.Wrap(err, "error generating random id for admin") + return mgmt.WrapErrorISE(err, "error generating random id for admin") } adm.AuthorityID = db.authorityID + // If provisionerID is set, then use it, otherwise load the provisioner + // to get the name. + if adm.ProvisionerID == "" { + prov, err := db.GetProvisionerByName(ctx, adm.ProvisionerName) + if err != nil { + return err + } + adm.ProvisionerID = prov.ID + adm.ProvisionerType = prov.Type + } else { + prov, err := db.GetProvisioner(ctx, adm.ProvisionerID) + if err != nil { + return err + } + adm.ProvisionerName = prov.Name + adm.ProvisionerType = prov.Type + } + dba := &dbAdmin{ ID: adm.ID, AuthorityID: db.authorityID, ProvisionerID: adm.ProvisionerID, - Name: adm.Name, - IsSuperAdmin: adm.IsSuperAdmin, + Subject: adm.Subject, + Type: adm.Type, CreatedAt: clock.Now(), } @@ -158,8 +199,7 @@ func (db *DB) UpdateAdmin(ctx context.Context, adm *mgmt.Admin) error { if old.DeletedAt.IsZero() && adm.Status == mgmt.StatusDeleted { nu.DeletedAt = clock.Now() } - nu.ProvisionerID = adm.ProvisionerID - nu.IsSuperAdmin = adm.IsSuperAdmin + nu.Type = adm.Type return db.save(ctx, old.ID, nu, old, "admin", authorityAdminsTable) } diff --git a/authority/mgmt/db/nosql/authConfig.go b/authority/mgmt/db/nosql/authConfig.go index fe189ce3..b9108fdc 100644 --- a/authority/mgmt/db/nosql/authConfig.go +++ b/authority/mgmt/db/nosql/authConfig.go @@ -60,9 +60,14 @@ func (db *DB) GetAuthConfig(ctx context.Context, id string) (*mgmt.AuthConfig, e if err != nil { return nil, err } + admins, err := db.GetAdmins(ctx) + if err != nil { + return nil, err + } return &mgmt.AuthConfig{ ID: dba.ID, + Admins: admins, Provisioners: provs, ASN1DN: dba.ASN1DN, Backdate: dba.Backdate, diff --git a/authority/mgmt/db/nosql/nosql.go b/authority/mgmt/db/nosql/nosql.go index d71b2804..d97b7700 100644 --- a/authority/mgmt/db/nosql/nosql.go +++ b/authority/mgmt/db/nosql/nosql.go @@ -3,6 +3,7 @@ package nosql import ( "context" "encoding/json" + "fmt" "time" "github.com/pkg/errors" @@ -11,9 +12,10 @@ import ( ) var ( - authorityAdminsTable = []byte("authority_admins") - authorityConfigsTable = []byte("authority_configs") - authorityProvisionersTable = []byte("authority_provisioners") + authorityAdminsTable = []byte("authority_admins") + authorityConfigsTable = []byte("authority_configs") + authorityProvisionersTable = []byte("authority_provisioners") + authorityProvisionersNameIDIndexTable = []byte("authority_provisioners_name_id_index") ) // DB is a struct that implements the AcmeDB interface. @@ -24,7 +26,7 @@ type DB struct { // New configures and returns a new Authority DB backend implemented using a nosql DB. func New(db nosqlDB.DB, authorityID string) (*DB, error) { - tables := [][]byte{authorityAdminsTable, authorityConfigsTable, authorityProvisionersTable} + tables := [][]byte{authorityAdminsTable, authorityConfigsTable, authorityProvisionersTable, authorityProvisionersNameIDIndexTable} for _, b := range tables { if err := db.CreateTable(b); err != nil { return nil, errors.Wrapf(err, "error creating table %s", @@ -58,6 +60,7 @@ func (db *DB) save(ctx context.Context, id string, nu interface{}, old interface return errors.Wrapf(err, "error marshaling acme type: %s, value: %v", typ, old) } } + fmt.Printf("oldB = %+v\n", oldB) _, swapped, err := db.db.CmpAndSwap(table, []byte(id), oldB, newB) switch { @@ -73,7 +76,7 @@ func (db *DB) save(ctx context.Context, id string, nu interface{}, old interface var idLen = 32 func randID() (val string, err error) { - val, err = randutil.Alphanumeric(idLen) + val, err = randutil.UUIDv4() if err != nil { return "", errors.Wrap(err, "error generating random alphanumeric ID") } diff --git a/authority/mgmt/db/nosql/provisioner.go b/authority/mgmt/db/nosql/provisioner.go index 6d9f74ab..d44c3be1 100644 --- a/authority/mgmt/db/nosql/provisioner.go +++ b/authority/mgmt/db/nosql/provisioner.go @@ -9,13 +9,15 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/authority/mgmt" "github.com/smallstep/nosql" + "github.com/smallstep/nosql/database" ) // dbProvisioner is the database representation of a Provisioner type. type dbProvisioner struct { - ID string `json:"id"` - AuthorityID string `json:"authorityID"` - Type string `json:"type"` + ID string `json:"id"` + AuthorityID string `json:"authorityID"` + Type string `json:"type"` + // Name is the key Name string `json:"name"` Claims *mgmt.Claims `json:"claims"` Details []byte `json:"details"` @@ -27,11 +29,30 @@ type dbProvisioner struct { DeletedAt time.Time `json:"deletedAt"` } +type provisionerNameID struct { + Name string `json:"name"` + ID string `json:"id"` +} + func (dbp *dbProvisioner) clone() *dbProvisioner { u := *dbp return &u } +func (db *DB) getProvisionerIDByName(ctx context.Context, name string) (string, error) { + data, err := db.db.Get(authorityProvisionersNameIDIndexTable, []byte(name)) + if nosql.IsErrNotFound(err) { + return "", mgmt.NewError(mgmt.ErrorNotFoundType, "provisioner %s not found", name) + } else if err != nil { + return "", mgmt.WrapErrorISE(err, "error loading provisioner %s", name) + } + ni := new(provisionerNameID) + if err := json.Unmarshal(data, ni); err != nil { + return "", mgmt.WrapErrorISE(err, "error unmarshaling provisionerNameID for provisioner %s", name) + } + return ni.ID, nil +} + func (db *DB) getDBProvisionerBytes(ctx context.Context, id string) ([]byte, error) { data, err := db.db.Get(authorityProvisionersTable, []byte(id)) if nosql.IsErrNotFound(err) { @@ -51,6 +72,9 @@ func (db *DB) getDBProvisioner(ctx context.Context, id string) (*dbProvisioner, if err != nil { return nil, err } + if !dbp.DeletedAt.IsZero() { + return nil, mgmt.NewError(mgmt.ErrorDeletedType, "provisioner %s is deleted", id) + } if dbp.AuthorityID != db.authorityID { return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType, "provisioner %s is not owned by authority %s", dbp.ID, db.authorityID) @@ -58,6 +82,25 @@ func (db *DB) getDBProvisioner(ctx context.Context, id string) (*dbProvisioner, return dbp, nil } +func (db *DB) getDBProvisionerByName(ctx context.Context, name string) (*dbProvisioner, error) { + id, err := db.getProvisionerIDByName(ctx, name) + if err != nil { + return nil, err + } + dbp, err := db.getDBProvisioner(ctx, id) + if err != nil { + return nil, err + } + if !dbp.DeletedAt.IsZero() { + return nil, mgmt.NewError(mgmt.ErrorDeletedType, "provisioner %s is deleted", name) + } + if dbp.AuthorityID != db.authorityID { + return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType, + "provisioner %s is not owned by authority %s", name, db.authorityID) + } + return dbp, nil +} + // GetProvisioner retrieves and unmarshals a provisioner from the database. func (db *DB) GetProvisioner(ctx context.Context, id string) (*mgmt.Provisioner, error) { data, err := db.getDBProvisionerBytes(ctx, id) @@ -79,29 +122,30 @@ func (db *DB) GetProvisioner(ctx context.Context, id string) (*mgmt.Provisioner, return prov, nil } -func unmarshalDBProvisioner(data []byte, id string) (*dbProvisioner, error) { +// GetProvisionerByName retrieves a provisioner from the database by name. +func (db *DB) GetProvisionerByName(ctx context.Context, name string) (*mgmt.Provisioner, error) { + p, err := db.getProvisionerIDByName(ctx, name) + if err != nil { + return nil, err + } + return db.GetProvisioner(ctx, id) +} + +func unmarshalDBProvisioner(data []byte, name 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) + return nil, errors.Wrapf(err, "error unmarshaling provisioner %s into dbProvisioner", name) } return dbp, nil } -type detailsType struct { - Type mgmt.ProvisionerType -} - -func unmarshalProvisioner(data []byte, id string) (*mgmt.Provisioner, error) { - dbp, err := unmarshalDBProvisioner(data, id) +func unmarshalProvisioner(data []byte, name string) (*mgmt.Provisioner, error) { + dbp, err := unmarshalDBProvisioner(data, name) if err != nil { return nil, err } - dt := new(detailsType) - if err := json.Unmarshal(dbp.Details, dt); err != nil { - return nil, mgmt.WrapErrorISE(err, "error unmarshaling details to detailsType for provisioner %s", id) - } - details, err := unmarshalDetails(dt.Type, dbp.Details) + details, err := mgmt.UnmarshalProvisionerDetails(dbp.Details) if err != nil { return nil, err } @@ -113,6 +157,7 @@ func unmarshalProvisioner(data []byte, id string) (*mgmt.Provisioner, error) { Name: dbp.Name, Claims: dbp.Claims, Details: details, + Status: mgmt.StatusActive, X509Template: dbp.X509Template, X509TemplateData: dbp.X509TemplateData, SSHTemplate: dbp.SSHTemplate, @@ -129,7 +174,7 @@ func unmarshalProvisioner(data []byte, id string) (*mgmt.Provisioner, error) { func (db *DB) GetProvisioners(ctx context.Context) ([]*mgmt.Provisioner, error) { dbEntries, err := db.db.List(authorityProvisionersTable) if err != nil { - return nil, errors.Wrap(err, "error loading provisioners") + return nil, mgmt.WrapErrorISE(err, "error loading provisioners") } var provs []*mgmt.Provisioner for _, entry := range dbEntries { @@ -158,76 +203,169 @@ func (db *DB) CreateProvisioner(ctx context.Context, prov *mgmt.Provisioner) err details, err := json.Marshal(prov.Details) if err != nil { - return mgmt.WrapErrorISE(err, "error marshaling details when creating provisioner") + return mgmt.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(), + ID: prov.ID, + AuthorityID: db.authorityID, + Type: prov.Type, + Name: prov.Name, + Claims: prov.Claims, + Details: details, + X509Template: prov.X509Template, + X509TemplateData: prov.X509TemplateData, + SSHTemplate: prov.SSHTemplate, + SSHTemplateData: prov.SSHTemplateData, + CreatedAt: clock.Now(), + } + dbpBytes, err := json.Marshal(dbp) + if err != nil { + return mgmt.WrapErrorISE(err, "error marshaling dbProvisioner %s", prov.Name) + } + pni := &provisionerNameID{ + Name: prov.Name, + ID: prov.ID, + } + pniBytes, err := json.Marshal(pni) + if err != nil { + return mgmt.WrapErrorISE(err, "error marshaling provisionerNameIndex %s", prov.Name) } - return db.save(ctx, dbp.ID, dbp, nil, "provisioner", authorityProvisionersTable) + if err := db.db.Update(&database.Tx{ + Operations: []*database.TxEntry{ + { + Bucket: authorityProvisionersTable, + Key: []byte(dbp.ID), + Cmd: database.CmpAndSwap, + Value: dbpBytes, + CmpValue: nil, + }, + { + Bucket: authorityProvisionersNameIDIndexTable, + Key: []byte(dbp.Name), + Cmd: database.CmpAndSwap, + Value: pniBytes, + CmpValue: nil, + }, + }, + }); err != nil { + return mgmt.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 *mgmt.Provisioner) error { - old, err := db.getDBProvisioner(ctx, prov.ID) +func (db *DB) UpdateProvisioner(ctx context.Context, name string, prov *mgmt.Provisioner) error { + id, err := db.getProvisionerIDByName(ctx, name) if err != nil { return err } + prov.ID = id + oldBytes, err := db.getDBProvisionerBytes(ctx, id) + if err != nil { + return err + } + fmt.Printf("oldBytes = %+v\n", oldBytes) + old, err := unmarshalDBProvisioner(oldBytes, id) + if err != nil { + return err + } + fmt.Printf("old = %+v\n", old) nu := old.clone() + nu.Type = prov.Type + nu.Name = prov.Name + nu.Claims = prov.Claims + nu.Details, err = json.Marshal(prov.Details) + if err != nil { + return mgmt.WrapErrorISE(err, "error marshaling details when updating provisioner %s", name) + } + nu.X509Template = prov.X509Template + nu.X509TemplateData = prov.X509TemplateData + nu.SSHTemplateData = prov.SSHTemplateData + + var txs = []*database.TxEntry{} // If the provisioner was active but is now deleted ... if old.DeletedAt.IsZero() && prov.Status == mgmt.StatusDeleted { nu.DeletedAt = clock.Now() + txs = append(txs, &database.TxEntry{ + Bucket: authorityProvisionersNameIDIndexTable, + Key: []byte(name), + Cmd: database.Delete, + }) } - nu.Claims = prov.Claims - nu.X509Template = prov.X509Template - nu.SSHTemplate = prov.SSHTemplate - nu.Details, err = json.Marshal(prov.Details) + if prov.Name != name { + // If the new name does not match the old name then: + // 1) check that the new name is not already taken + // 2) delete the old name-id index resource + // 3) create a new name-id index resource + // 4) update the provisioner resource + nuBytes, err := json.Marshal(nu) + if err != nil { + return mgmt.WrapErrorISE(err, "error marshaling dbProvisioner %s", prov.Name) + } + pni := &provisionerNameID{ + Name: prov.Name, + ID: prov.ID, + } + pniBytes, err := json.Marshal(pni) + if err != nil { + return mgmt.WrapErrorISE(err, "error marshaling provisionerNameID for provisioner %s", prov.Name) + } + + _, err = db.db.Get(authorityProvisionersNameIDIndexTable, []byte(name)) + if err == nil { + return mgmt.NewError(mgmt.ErrorBadRequestType, "provisioner with name %s already exists", prov.Name) + } else if !nosql.IsErrNotFound(err) { + return mgmt.WrapErrorISE(err, "error loading provisionerNameID %s", prov.Name) + } + err = db.db.Update(&database.Tx{ + Operations: []*database.TxEntry{ + { + Bucket: authorityProvisionersNameIDIndexTable, + Key: []byte(name), + Cmd: database.Delete, + }, + { + Bucket: authorityProvisionersNameIDIndexTable, + Key: []byte(prov.Name), + Cmd: database.CmpAndSwap, + Value: pniBytes, + CmpValue: nil, + }, + { + Bucket: authorityProvisionersTable, + Key: []byte(nu.ID), + Cmd: database.CmpAndSwap, + Value: nuBytes, + CmpValue: oldBytes, + }, + }, + }) + } else { + err = db.db.Update(&database.Tx{ + Operations: []*database.TxEntry{ + { + Bucket: authorityProvisionersNameIDIndexTable, + Key: []byte(name), + Cmd: database.Delete, + }, + { + Bucket: authorityProvisionersTable, + Key: []byte(nu.ID), + Cmd: database.CmpAndSwap, + Value: nuBytes, + CmpValue: oldBytes, + }, + }, + }) + } if err != nil { - return mgmt.WrapErrorISE(err, "error marshaling details when creating provisioner") + return mgmt.WrapErrorISE(err, "error updating provisioner %s", prov.Name) } - - return db.save(ctx, old.ID, nu, old, "provisioner", authorityProvisionersTable) -} - -func unmarshalDetails(typ mgmt.ProvisionerType, data []byte) (mgmt.ProvisionerDetails, error) { - var v mgmt.ProvisionerDetails - switch typ { - case mgmt.ProvisionerTypeJWK: - v = new(mgmt.ProvisionerDetailsJWK) - case mgmt.ProvisionerTypeOIDC: - v = new(mgmt.ProvisionerDetailsOIDC) - case mgmt.ProvisionerTypeGCP: - v = new(mgmt.ProvisionerDetailsGCP) - case mgmt.ProvisionerTypeAWS: - v = new(mgmt.ProvisionerDetailsAWS) - case mgmt.ProvisionerTypeAZURE: - v = new(mgmt.ProvisionerDetailsAzure) - case mgmt.ProvisionerTypeACME: - v = new(mgmt.ProvisionerDetailsACME) - case mgmt.ProvisionerTypeX5C: - v = new(mgmt.ProvisionerDetailsX5C) - case mgmt.ProvisionerTypeK8SSA: - v = new(mgmt.ProvisionerDetailsK8SSA) - case mgmt.ProvisionerTypeSSHPOP: - v = new(mgmt.ProvisionerDetailsSSHPOP) - default: - return nil, fmt.Errorf("unsupported provisioner type %s", typ) - } - - if err := json.Unmarshal(data, v); err != nil { - return nil, err - } - return v, nil + return nil } diff --git a/authority/mgmt/errors.go b/authority/mgmt/errors.go index f0f90400..63c0652e 100644 --- a/authority/mgmt/errors.go +++ b/authority/mgmt/errors.go @@ -88,12 +88,11 @@ var ( // Error represents an ACME type Error struct { - Type string `json:"type"` - Detail string `json:"detail"` - Subproblems []interface{} `json:"subproblems,omitempty"` - Identifier interface{} `json:"identifier,omitempty"` - Err error `json:"-"` - Status int `json:"-"` + Type string `json:"type"` + Detail string `json:"detail"` + Message string `json:"message"` + Err error `json:"-"` + Status int `json:"-"` } // IsType returns true if the error type matches the input type. @@ -160,7 +159,7 @@ func (e *Error) StatusCode() int { // Error allows AError to implement the error interface. func (e *Error) Error() string { - return e.Detail + return e.Err.Error() } // Cause returns the internal error and implements the Causer interface. @@ -182,9 +181,10 @@ func (e *Error) ToLog() (interface{}, error) { // WriteError writes to w a JSON representation of the given error. func WriteError(w http.ResponseWriter, err *Error) { - w.Header().Set("Content-Type", "application/problem+json") + w.Header().Set("Content-Type", "application/json") w.WriteHeader(err.StatusCode()) + err.Message = err.Err.Error() // Write errors in the response writer if rl, ok := w.(logging.ResponseLogger); ok { rl.WithFields(map[string]interface{}{ @@ -199,6 +199,7 @@ func WriteError(w http.ResponseWriter, err *Error) { } } + fmt.Printf("err = %+v\n", err) if err := json.NewEncoder(w).Encode(err); err != nil { log.Println(err) } diff --git a/authority/mgmt/provisioner.go b/authority/mgmt/provisioner.go index 961907f8..fb39f6bc 100644 --- a/authority/mgmt/provisioner.go +++ b/authority/mgmt/provisioner.go @@ -59,8 +59,8 @@ func WithPassword(pass string) func(*ProvisionerCtx) { // Provisioner type. type Provisioner struct { - ID string `json:"id"` - AuthorityID string `json:"authorityID"` + ID string `json:"-"` + AuthorityID string `json:"-"` Type string `json:"type"` Name string `json:"name"` Claims *Claims `json:"claims"` @@ -87,8 +87,7 @@ func (p *Provisioner) GetOptions() *provisioner.Options { func CreateProvisioner(ctx context.Context, db DB, typ, name string, opts ...ProvisionerOption) (*Provisioner, error) { pc := NewProvisionerCtx(opts...) - - details, err := createJWKDetails(pc) + details, err := NewProvisionerDetails(ProvisionerType(typ), pc) if err != nil { return nil, err } @@ -180,6 +179,27 @@ func (*ProvisionerDetailsK8SSA) isProvisionerDetails() {} func (*ProvisionerDetailsSSHPOP) isProvisionerDetails() {} +func NewProvisionerDetails(typ ProvisionerType, pc *ProvisionerCtx) (ProvisionerDetails, error) { + switch typ { + case ProvisionerTypeJWK: + return createJWKDetails(pc) + /* + case ProvisionerTypeOIDC: + return createOIDCDetails(pc) + case ProvisionerTypeACME: + return createACMEDetails(pc) + case ProvisionerTypeK8SSA: + return createK8SSADetails(pc) + case ProvisionerTypeSSHPOP: + return createSSHPOPDetails(pc) + case ProvisionerTypeX5C: + return createSSHPOPDetails(pc) + */ + default: + return nil, NewErrorISE("unsupported provisioner type %s", typ) + } +} + func createJWKDetails(pc *ProvisionerCtx) (*ProvisionerDetailsJWK, error) { var err error @@ -231,6 +251,7 @@ func (p *Provisioner) ToCertificates() (provisioner.Interface, error) { return nil, err } return &provisioner.JWK{ + ID: p.ID, Type: p.Type, Name: p.Name, Key: jwk, @@ -386,3 +407,43 @@ func (c *Claims) ToCertificates() (*provisioner.Claims, error) { EnableSSHCA: &c.SSH.Enabled, }, nil } + +type detailsType struct { + Type ProvisionerType +} + +func UnmarshalProvisionerDetails(data []byte) (ProvisionerDetails, error) { + dt := new(detailsType) + if err := json.Unmarshal(data, dt); err != nil { + return nil, WrapErrorISE(err, "error unmarshaling provisioner details") + } + + var v ProvisionerDetails + switch dt.Type { + case ProvisionerTypeJWK: + v = new(ProvisionerDetailsJWK) + case ProvisionerTypeOIDC: + v = new(ProvisionerDetailsOIDC) + case ProvisionerTypeGCP: + v = new(ProvisionerDetailsGCP) + case ProvisionerTypeAWS: + v = new(ProvisionerDetailsAWS) + case ProvisionerTypeAZURE: + v = new(ProvisionerDetailsAzure) + case ProvisionerTypeACME: + v = new(ProvisionerDetailsACME) + case ProvisionerTypeX5C: + v = new(ProvisionerDetailsX5C) + case ProvisionerTypeK8SSA: + v = new(ProvisionerDetailsK8SSA) + case ProvisionerTypeSSHPOP: + v = new(ProvisionerDetailsSSHPOP) + default: + return nil, fmt.Errorf("unsupported provisioner type %s", dt.Type) + } + + if err := json.Unmarshal(data, v); err != nil { + return nil, err + } + return v, nil +} diff --git a/authority/provisioner/collection.go b/authority/provisioner/collection.go index 13b7be4d..b6ff8b9e 100644 --- a/authority/provisioner/collection.go +++ b/authority/provisioner/collection.go @@ -45,6 +45,7 @@ type loadByTokenPayload struct { type Collection struct { byID *sync.Map byKey *sync.Map + byName *sync.Map sorted provisionerSlice audiences Audiences } @@ -55,6 +56,7 @@ func NewCollection(audiences Audiences) *Collection { return &Collection{ byID: new(sync.Map), byKey: new(sync.Map), + byName: new(sync.Map), audiences: audiences, } } @@ -64,6 +66,11 @@ func (c *Collection) Load(id string) (Interface, bool) { return loadProvisioner(c.byID, id) } +// LoadByName a provisioner by name. +func (c *Collection) LoadByName(name string) (Interface, bool) { + return loadProvisioner(c.byName, name) +} + // LoadByToken parses the token claims and loads the provisioner associated. func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims) (Interface, bool) { var audiences []string @@ -173,6 +180,11 @@ func (c *Collection) Store(p Interface) error { if _, loaded := c.byID.LoadOrStore(p.GetID(), p); loaded { return errors.New("cannot add multiple provisioners with the same id") } + // Store provisioner always by name. + if _, loaded := c.byName.LoadOrStore(p.GetName(), p); loaded { + c.byID.Delete(p.GetID()) + return errors.New("cannot add multiple provisioners with the same id") + } // Store provisioner in byKey if EncryptedKey is defined. if kid, _, ok := p.GetEncryptedKey(); ok { diff --git a/authority/provisioner/jwk.go b/authority/provisioner/jwk.go index d6a97e2b..a2d3e0b1 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -28,6 +28,7 @@ type stepPayload struct { // signature requests. type JWK struct { *base + ID string `json:"-"` Type string `json:"type"` Name string `json:"name"` Key *jose.JSONWebKey `json:"key"` @@ -41,6 +42,9 @@ type JWK struct { // GetID returns the provisioner unique identifier. The name and credential id // should uniquely identify any JWK provisioner. func (p *JWK) GetID() string { + if p.ID != "" { + return p.ID + } return p.Name + ":" + p.Key.KeyID } diff --git a/ca/ca.go b/ca/ca.go index ec10f74c..ecbc55e2 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -172,10 +172,9 @@ func (ca *CA) Init(config *config.Config) (*CA, error) { }) // MGMT Router - mgmtDB := auth.GetMgmtDatabase() if mgmtDB != nil { - mgmtHandler := mgmtAPI.NewHandler(mgmtDB) + mgmtHandler := mgmtAPI.NewHandler(mgmtDB, auth) mux.Route("/mgmt", func(r chi.Router) { mgmtHandler.Route(r) }) diff --git a/ca/mgmtClient.go b/ca/mgmtClient.go index 47316ad6..96c33f03 100644 --- a/ca/mgmtClient.go +++ b/ca/mgmtClient.go @@ -3,6 +3,7 @@ package ca import ( "bytes" "encoding/json" + "io" "net/http" "net/url" "path" @@ -78,7 +79,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readMgmtError(resp.Body) } var adm = new(mgmt.Admin) if err := readJSON(resp.Body, adm); err != nil { @@ -105,7 +106,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readMgmtError(resp.Body) } var adm = new(mgmt.Admin) if err := readJSON(resp.Body, adm); err != nil { @@ -132,7 +133,7 @@ retry: retried = true goto retry } - return readError(resp.Body) + return readMgmtError(resp.Body) } return nil } @@ -145,7 +146,7 @@ func (c *MgmtClient) UpdateAdmin(id string, uar *mgmtAPI.UpdateAdminRequest) (*m return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request") } u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/mgmt/admin", id)}) - req, err := http.NewRequest("PUT", u.String(), bytes.NewReader(body)) + req, err := http.NewRequest("PATCH", u.String(), bytes.NewReader(body)) if err != nil { return nil, errors.Wrapf(err, "create PUT %s request failed", u) } @@ -159,7 +160,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readMgmtError(resp.Body) } var adm = new(mgmt.Admin) if err := readJSON(resp.Body, adm); err != nil { @@ -182,7 +183,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readMgmtError(resp.Body) } var admins = new([]*mgmt.Admin) if err := readJSON(resp.Body, admins); err != nil { @@ -191,6 +192,29 @@ retry: return *admins, nil } +// GetProvisioner performs the GET /mgmt/provisioner/{id} request to the CA. +func (c *MgmtClient) GetProvisioner(id string) (*mgmt.Provisioner, error) { + var retried bool + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/mgmt/provisioner", 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, readMgmtError(resp.Body) + } + var prov = new(mgmt.Provisioner) + if err := readJSON(resp.Body, prov); err != nil { + return nil, errors.Wrapf(err, "error reading %s", u) + } + return prov, nil +} + // GetProvisioners performs the GET /mgmt/provisioners request to the CA. func (c *MgmtClient) GetProvisioners() ([]*mgmt.Provisioner, error) { var retried bool @@ -205,7 +229,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readMgmtError(resp.Body) } var provs = new([]*mgmt.Provisioner) if err := readJSON(resp.Body, provs); err != nil { @@ -213,3 +237,116 @@ retry: } return *provs, nil } + +// RemoveProvisioner performs the DELETE /mgmt/provisioner/{name} request to the CA. +func (c *MgmtClient) RemoveProvisioner(name string) error { + var retried bool + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/mgmt/provisioner", name)}) + req, err := http.NewRequest("DELETE", u.String(), nil) + if err != nil { + return errors.Wrapf(err, "create DELETE %s request failed", u) + } +retry: + resp, err := c.client.Do(req) + if err != nil { + return errors.Wrapf(err, "client DELETE %s failed", u) + } + if resp.StatusCode >= 400 { + if !retried && c.retryOnError(resp) { + retried = true + goto retry + } + return readMgmtError(resp.Body) + } + return nil +} + +// CreateProvisioner performs the POST /mgmt/provisioner request to the CA. +func (c *MgmtClient) CreateProvisioner(req *mgmtAPI.CreateProvisionerRequest) (*mgmt.Provisioner, error) { + var retried bool + body, err := json.Marshal(req) + if err != nil { + return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request") + } + u := c.endpoint.ResolveReference(&url.URL{Path: "/mgmt/provisioner"}) +retry: + resp, err := c.client.Post(u.String(), "application/json", bytes.NewReader(body)) + if err != nil { + return nil, errors.Wrapf(err, "client POST %s failed", u) + } + if resp.StatusCode >= 400 { + if !retried && c.retryOnError(resp) { + retried = true + goto retry + } + return nil, readMgmtError(resp.Body) + } + var prov = new(mgmt.Provisioner) + if err := readJSON(resp.Body, prov); err != nil { + return nil, errors.Wrapf(err, "error reading %s", u) + } + return prov, nil +} + +// UpdateProvisioner performs the PUT /mgmt/provisioner/{id} request to the CA. +func (c *MgmtClient) UpdateProvisioner(id string, upr *mgmtAPI.UpdateProvisionerRequest) (*mgmt.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("/mgmt/provisioner", 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) + } +retry: + resp, err := c.client.Do(req) + if err != nil { + return nil, errors.Wrapf(err, "client PUT %s failed", u) + } + if resp.StatusCode >= 400 { + if !retried && c.retryOnError(resp) { + retried = true + goto retry + } + return nil, readMgmtError(resp.Body) + } + var prov = new(mgmt.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 /mgmt/authconfig/{id} request to the CA. +func (c *MgmtClient) GetAuthConfig(id string) (*mgmt.AuthConfig, error) { + var retried bool + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/mgmt/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, readMgmtError(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 readMgmtError(r io.ReadCloser) error { + defer r.Close() + mgmtErr := new(mgmt.Error) + if err := json.NewDecoder(r).Decode(mgmtErr); err != nil { + return err + } + return errors.New(mgmtErr.Message) +} From 4f3e5ef64df64948795c3be50ed92ee3ab853b8e Mon Sep 17 00:00:00 2001 From: max furman Date: Tue, 18 May 2021 16:50:54 -0700 Subject: [PATCH 07/89] wip --- api/api.go | 5 +- authority/admin/admin.go | 26 ++-- authority/admin/collection.go | 81 ++++++----- authority/authority.go | 4 +- authority/mgmt/admin.go | 42 +----- authority/mgmt/api/admin.go | 73 ++++++---- authority/mgmt/api/provisioner.go | 31 +++-- authority/mgmt/authConfig.go | 5 +- authority/mgmt/config.go | 10 -- authority/mgmt/db.go | 26 +--- authority/mgmt/db/nosql/admin.go | 68 ++-------- authority/mgmt/db/nosql/authConfig.go | 3 +- authority/mgmt/db/nosql/nosql.go | 2 - authority/mgmt/db/nosql/provisioner.go | 181 ++----------------------- authority/mgmt/provisioner.go | 5 +- authority/provisioner/collection.go | 2 +- authority/status/status.go | 11 ++ ca/mgmtClient.go | 103 ++++++++++---- 18 files changed, 277 insertions(+), 401 deletions(-) create mode 100644 authority/status/status.go diff --git a/api/api.go b/api/api.go index d6cf9ab7..20ef7e14 100644 --- a/api/api.go +++ b/api/api.go @@ -316,7 +316,7 @@ func certChainToPEM(certChain []*x509.Certificate) []Certificate { // Provisioners returns the list of provisioners configured in the authority. func (h *caHandler) Provisioners(w http.ResponseWriter, r *http.Request) { - cursor, limit, err := parseCursor(r) + cursor, limit, err := ParseCursor(r) if err != nil { WriteError(w, errs.BadRequestErr(err)) return @@ -427,7 +427,8 @@ func LogCertificate(w http.ResponseWriter, cert *x509.Certificate) { } } -func parseCursor(r *http.Request) (cursor string, limit int, err error) { +// ParseCursor parses the cursor and limit from the request query params. +func ParseCursor(r *http.Request) (cursor string, limit int, err error) { q := r.URL.Query() cursor = q.Get("cursor") if v := q.Get("limit"); len(v) > 0 { diff --git a/authority/admin/admin.go b/authority/admin/admin.go index 579538dc..f0058777 100644 --- a/authority/admin/admin.go +++ b/authority/admin/admin.go @@ -1,15 +1,25 @@ package admin -// Type specifies the type of administrator privileges the admin has. +import "github.com/smallstep/certificates/authority/status" + +// Type specifies the type of the admin. e.g. SUPER_ADMIN, REGULAR type Type string +var ( + // TypeSuper superadmin + TypeSuper = Type("SUPER_ADMIN") + // TypeRegular regular + TypeRegular = Type("REGULAR") +) + // Admin type. type Admin struct { - ID string `json:"id"` - AuthorityID string `json:"-"` - Subject string `json:"subject"` - ProvisionerName string `json:"provisionerName"` - ProvisionerType string `json:"provisionerType"` - ProvisionerID string `json:"provisionerID"` - Type Type `json:"type"` + ID string `json:"id"` + AuthorityID string `json:"-"` + Subject string `json:"subject"` + ProvisionerName string `json:"provisionerName"` + ProvisionerType string `json:"provisionerType"` + ProvisionerID string `json:"provisionerID"` + Type Type `json:"type"` + Status status.Type `json:"status"` } diff --git a/authority/admin/collection.go b/authority/admin/collection.go index 87dd63ce..9e8a2926 100644 --- a/authority/admin/collection.go +++ b/authority/admin/collection.go @@ -2,56 +2,54 @@ package admin import ( "crypto/sha1" + "encoding/binary" + "encoding/hex" + "fmt" + "sort" + "strings" "sync" "github.com/pkg/errors" - "go.step.sm/crypto/jose" + "github.com/smallstep/certificates/authority/provisioner" ) -// DefaultProvisionersLimit is the default limit for listing provisioners. -const DefaultProvisionersLimit = 20 +// DefaultAdminLimit is the default limit for listing provisioners. +const DefaultAdminLimit = 20 -// DefaultProvisionersMax is the maximum limit for listing provisioners. -const DefaultProvisionersMax = 100 +// DefaultAdminMax is the maximum limit for listing provisioners. +const DefaultAdminMax = 100 -/* -type uidProvisioner struct { - provisioner Interface - uid string +type uidAdmin struct { + admin *Admin + uid string } -type provisionerSlice []uidProvisioner +type adminSlice []uidAdmin -func (p provisionerSlice) Len() int { return len(p) } -func (p provisionerSlice) Less(i, j int) bool { return p[i].uid < p[j].uid } -func (p provisionerSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } -*/ - -// loadByTokenPayload is a payload used to extract the id used to load the -// provisioner. -type loadByTokenPayload struct { - jose.Claims - AuthorizedParty string `json:"azp"` // OIDC client id - TenantID string `json:"tid"` // Microsoft Azure tenant id -} +func (p adminSlice) Len() int { return len(p) } +func (p adminSlice) Less(i, j int) bool { return p[i].uid < p[j].uid } +func (p adminSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } // Collection is a memory map of admins. type Collection struct { byID *sync.Map bySubProv *sync.Map byProv *sync.Map + sorted adminSlice + provisioners *provisioner.Collection count int countByProvisioner map[string]int } // NewCollection initializes a collection of provisioners. The given list of // audiences are the audiences used by the JWT provisioner. -func NewCollection() *Collection { +func NewCollection(provisioners *provisioner.Collection) *Collection { return &Collection{ byID: new(sync.Map), byProv: new(sync.Map), bySubProv: new(sync.Map), countByProvisioner: map[string]int{}, + provisioners: provisioners, } } @@ -88,12 +86,18 @@ func (c *Collection) LoadByProvisioner(provName string) ([]*Admin, bool) { // Store adds an admin to the collection and enforces the uniqueness of // admin IDs and amdin subject <-> provisioner name combos. func (c *Collection) Store(adm *Admin) error { - provName := adm.ProvisionerName + p, ok := c.provisioners.Load(adm.ProvisionerID) + if !ok { + return fmt.Errorf("provisioner %s not found", adm.ProvisionerID) + } + adm.ProvisionerName = p.GetName() + adm.ProvisionerType = p.GetType().String() // Store admin always in byID. ID must be unique. if _, loaded := c.byID.LoadOrStore(adm.ID, adm); loaded { return errors.New("cannot add multiple admins with the same id") } + provName := adm.ProvisionerName // Store admin alwasy in bySubProv. Subject <-> ProvisionerName must be unique. if _, loaded := c.bySubProv.LoadOrStore(subProvNameHash(adm.Subject, provName), adm); loaded { c.byID.Delete(adm.ID) @@ -109,6 +113,21 @@ func (c *Collection) Store(adm *Admin) error { } c.count++ + // Store sorted admins. + // Use the first 4 bytes (32bit) of the sum to insert the order + // Using big endian format to get the strings sorted: + // 0x00000000, 0x00000001, 0x00000002, ... + bi := make([]byte, 4) + _sum := sha1.Sum([]byte(adm.ID)) + sum := _sum[:] + binary.BigEndian.PutUint32(bi, uint32(c.sorted.Len())) + sum[0], sum[1], sum[2], sum[3] = bi[0], bi[1], bi[2], bi[3] + c.sorted = append(c.sorted, uidAdmin{ + admin: adm, + uid: hex.EncodeToString(sum), + }) + sort.Sort(c.sorted) + return nil } @@ -125,23 +144,22 @@ func (c *Collection) CountByProvisioner(provName string) int { return 0 } -/* // Find implements pagination on a list of sorted provisioners. -func (c *Collection) Find(cursor string, limit int) (List, string) { +func (c *Collection) Find(cursor string, limit int) ([]*Admin, string) { switch { case limit <= 0: - limit = DefaultProvisionersLimit - case limit > DefaultProvisionersMax: - limit = DefaultProvisionersMax + limit = DefaultAdminLimit + case limit > DefaultAdminMax: + limit = DefaultAdminMax } n := c.sorted.Len() cursor = fmt.Sprintf("%040s", cursor) i := sort.Search(n, func(i int) bool { return c.sorted[i].uid >= cursor }) - slice := List{} + slice := []*Admin{} for ; i < n && len(slice) < limit; i++ { - slice = append(slice, c.sorted[i].provisioner) + slice = append(slice, c.sorted[i].admin) } if i < n { @@ -149,7 +167,6 @@ func (c *Collection) Find(cursor string, limit int) (List, string) { } return slice, "" } -*/ func loadAdmin(m *sync.Map, key string) (*Admin, bool) { a, ok := m.Load(key) diff --git a/authority/authority.go b/authority/authority.go index 2da5b341..2772c444 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -175,7 +175,7 @@ func (a *Authority) ReloadAuthConfig() error { } } // Store all the admins - a.admins = admin.NewCollection() + a.admins = admin.NewCollection(a.provisioners) for _, adm := range a.config.AuthorityConfig.Admins { if err := a.admins.Store(adm); err != nil { return err @@ -431,7 +431,7 @@ func (a *Authority) init() error { } } // Store all the admins - a.admins = admin.NewCollection() + a.admins = admin.NewCollection(a.provisioners) for _, adm := range a.config.AuthorityConfig.Admins { if err := a.admins.Store(adm); err != nil { return err diff --git a/authority/mgmt/admin.go b/authority/mgmt/admin.go index f21abdce..d265a001 100644 --- a/authority/mgmt/admin.go +++ b/authority/mgmt/admin.go @@ -1,55 +1,23 @@ package mgmt import ( - "context" - "github.com/smallstep/certificates/authority/admin" ) // AdminType specifies the type of the admin. e.g. SUPER_ADMIN, REGULAR -type AdminType string +type AdminType admin.Type var ( // AdminTypeSuper superadmin - AdminTypeSuper = AdminType("SUPER_ADMIN") + AdminTypeSuper = admin.TypeSuper // AdminTypeRegular regular - AdminTypeRegular = AdminType("REGULAR") + AdminTypeRegular = admin.TypeRegular ) // Admin type. -type Admin struct { - ID string `json:"id"` - AuthorityID string `json:"-"` - ProvisionerID string `json:"provisionerID"` - Subject string `json:"subject"` - ProvisionerName string `json:"provisionerName"` - ProvisionerType string `json:"provisionerType"` - Type AdminType `json:"type"` - Status StatusType `json:"status"` -} - -// CreateAdmin builds and stores an admin type in the DB. -func CreateAdmin(ctx context.Context, db DB, provName, sub string, typ AdminType) (*Admin, error) { - adm := &Admin{ - Subject: sub, - ProvisionerName: provName, - Type: typ, - Status: StatusActive, - } - if err := db.CreateAdmin(ctx, adm); err != nil { - return nil, WrapErrorISE(err, "error creating admin") - } - return adm, nil -} +type Admin admin.Admin // ToCertificates converts an Admin to the Admin type expected by the authority. func (adm *Admin) ToCertificates() (*admin.Admin, error) { - return &admin.Admin{ - ID: adm.ID, - Subject: adm.Subject, - ProvisionerID: adm.ProvisionerID, - ProvisionerName: adm.ProvisionerName, - ProvisionerType: adm.ProvisionerType, - Type: admin.Type(adm.Type), - }, nil + return (*admin.Admin)(adm), nil } diff --git a/authority/mgmt/api/admin.go b/authority/mgmt/api/admin.go index f997095a..b474e131 100644 --- a/authority/mgmt/api/admin.go +++ b/authority/mgmt/api/admin.go @@ -8,27 +8,34 @@ import ( "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/authority/mgmt" + "github.com/smallstep/certificates/authority/status" ) // CreateAdminRequest represents the body for a CreateAdmin request. type CreateAdminRequest struct { - Subject string `json:"subject"` - Provisioner string `json:"provisioner"` - Type mgmt.AdminType `json:"type"` + Subject string `json:"subject"` + Provisioner string `json:"provisioner"` + Type admin.Type `json:"type"` } // Validate validates a new-admin request body. func (car *CreateAdminRequest) Validate(c *admin.Collection) error { if _, ok := c.LoadBySubProv(car.Subject, car.Provisioner); ok { return mgmt.NewError(mgmt.ErrorBadRequestType, - "admin with subject %s and provisioner name %s already exists", car.Subject, car.Provisioner) + "admin with subject: '%s' and provisioner: '%s' already exists", car.Subject, car.Provisioner) } return nil } +// GetAdminsResponse for returning a list of admins. +type GetAdminsResponse struct { + Admins []*admin.Admin `json:"admins"` + NextCursor string `json:"nextCursor"` +} + // UpdateAdminRequest represents the body for a UpdateAdmin request. type UpdateAdminRequest struct { - Type mgmt.AdminType `json:"type"` + Type admin.Type `json:"type"` } // Validate validates a new-admin request body. @@ -43,27 +50,31 @@ type DeleteResponse struct { // GetAdmin returns the requested admin, or an error. func (h *Handler) GetAdmin(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() id := chi.URLParam(r, "id") - prov, err := h.db.GetAdmin(ctx, id) - if err != nil { - api.WriteError(w, err) + adm, ok := h.auth.GetAdminCollection().LoadByID(id) + if !ok { + api.WriteError(w, mgmt.NewError(mgmt.ErrorNotFoundType, + "admin %s not found", id)) return } - api.JSON(w, prov) + api.JSON(w, adm) } // GetAdmins returns all admins associated with the authority. func (h *Handler) GetAdmins(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - admins, err := h.db.GetAdmins(ctx) + cursor, limit, err := api.ParseCursor(r) if err != nil { - api.WriteError(w, err) + api.WriteError(w, mgmt.WrapError(mgmt.ErrorBadRequestType, err, + "error parsing cursor and limit from query params")) return } - api.JSON(w, admins) + + admins, nextCursor := h.auth.GetAdminCollection().Find(cursor, limit) + api.JSON(w, &GetAdminsResponse{ + Admins: admins, + NextCursor: nextCursor, + }) } // CreateAdmin creates a new admin. @@ -81,16 +92,24 @@ func (h *Handler) CreateAdmin(w http.ResponseWriter, r *http.Request) { return } + p, ok := h.auth.GetProvisionerCollection().LoadByName(body.Provisioner) + if !ok { + api.WriteError(w, mgmt.NewError(mgmt.ErrorNotFoundType, "provisioner %s not found", body.Provisioner)) + return + } + adm := &mgmt.Admin{ - ProvisionerName: body.Provisioner, - Subject: body.Subject, - Type: body.Type, - Status: mgmt.StatusActive, + 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 { fmt.Printf("err = %+v\n", err) @@ -112,7 +131,7 @@ func (h *Handler) DeleteAdmin(w http.ResponseWriter, r *http.Request) { api.WriteError(w, mgmt.WrapErrorISE(err, "error retrieiving admin %s", id)) return } - adm.Status = mgmt.StatusDeleted + adm.Status = status.Deleted if err := h.db.UpdateAdmin(ctx, adm); err != nil { api.WriteError(w, mgmt.WrapErrorISE(err, "error updating admin %s", id)) return @@ -135,17 +154,19 @@ func (h *Handler) UpdateAdmin(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") - adm, err := h.db.GetAdmin(ctx, id) - if err != nil { - api.WriteError(w, mgmt.WrapErrorISE(err, "error retrieiving admin %s", id)) + adm, ok := h.auth.GetAdminCollection().LoadByID(id) + if !ok { + api.WriteError(w, mgmt.NewError(mgmt.ErrorNotFoundType, "admin %s not found", id)) + return + } + if adm.Type == body.Type { + api.WriteError(w, mgmt.NewError(mgmt.ErrorBadRequestType, "admin %s already has type %s", id, adm.Type)) return } - // TODO validate - adm.Type = body.Type - if err := h.db.UpdateAdmin(ctx, adm); err != nil { + if err := h.db.UpdateAdmin(ctx, (*mgmt.Admin)(adm)); err != nil { api.WriteError(w, mgmt.WrapErrorISE(err, "error updating admin %s", id)) return } diff --git a/authority/mgmt/api/provisioner.go b/authority/mgmt/api/provisioner.go index 8a8da08f..79ef5f74 100644 --- a/authority/mgmt/api/provisioner.go +++ b/authority/mgmt/api/provisioner.go @@ -8,6 +8,7 @@ import ( "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority/mgmt" "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/authority/status" ) // CreateProvisionerRequest represents the body for a CreateProvisioner request. @@ -55,7 +56,13 @@ func (h *Handler) GetProvisioner(w http.ResponseWriter, r *http.Request) { ctx := r.Context() name := chi.URLParam(r, "name") - prov, err := h.db.GetProvisionerByName(ctx, name) + p, ok := h.auth.GetProvisionerCollection().LoadByName(name) + if !ok { + api.WriteError(w, mgmt.NewError(mgmt.ErrorNotFoundType, "provisioner %s not found", name)) + return + } + + prov, err := h.db.GetProvisioner(ctx, p.GetID()) if err != nil { api.WriteError(w, err) return @@ -123,6 +130,8 @@ func (h *Handler) DeleteProvisioner(w http.ResponseWriter, r *http.Request) { name := chi.URLParam(r, "name") c := h.auth.GetAdminCollection() + fmt.Printf("c.Count() = %+v\n", c.Count()) + fmt.Printf("c.CountByProvisioner() = %+v\n", c.CountByProvisioner(name)) if c.Count() == c.CountByProvisioner(name) { api.WriteError(w, mgmt.NewError(mgmt.ErrorBadRequestType, "cannot remove provisioner %s because no admins will remain", name)) @@ -130,14 +139,18 @@ func (h *Handler) DeleteProvisioner(w http.ResponseWriter, r *http.Request) { } ctx := r.Context() - prov, err := h.db.GetProvisionerByName(ctx, name) - if err != nil { - api.WriteError(w, mgmt.WrapErrorISE(err, "error retrieiving provisioner %s", name)) + p, ok := h.auth.GetProvisionerCollection().LoadByName(name) + if !ok { + api.WriteError(w, mgmt.NewError(mgmt.ErrorNotFoundType, "provisioner %s not found", name)) return } - fmt.Printf("prov = %+v\n", prov) - prov.Status = mgmt.StatusDeleted - if err := h.db.UpdateProvisioner(ctx, name, prov); err != nil { + 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)) return } @@ -150,8 +163,8 @@ func (h *Handler) DeleteProvisioner(w http.ResponseWriter, r *http.Request) { ID: adm.ID, ProvisionerID: adm.ProvisionerID, Subject: adm.Subject, - Type: mgmt.AdminType(adm.Type), - Status: mgmt.StatusDeleted, + Type: adm.Type, + Status: status.Deleted, }); err != nil { api.WriteError(w, mgmt.WrapErrorISE(err, "error deleting admin %s, as part of provisioner %s deletion", adm.Subject, name)) return diff --git a/authority/mgmt/authConfig.go b/authority/mgmt/authConfig.go index 14448af4..6284a9e7 100644 --- a/authority/mgmt/authConfig.go +++ b/authority/mgmt/authConfig.go @@ -4,6 +4,7 @@ import ( "github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/authority/status" ) // AuthConfig represents the Authority Configuration. @@ -15,7 +16,7 @@ type AuthConfig struct { Admins []*Admin `json:"-"` Claims *Claims `json:"claims,omitempty"` Backdate string `json:"backdate,omitempty"` - Status StatusType `json:"status,omitempty"` + Status status.Type `json:"status,omitempty"` } func NewDefaultAuthConfig() *AuthConfig { @@ -23,7 +24,7 @@ func NewDefaultAuthConfig() *AuthConfig { Claims: NewDefaultClaims(), ASN1DN: &config.ASN1DN{}, Backdate: config.DefaultBackdate.String(), - Status: StatusActive, + Status: status.Active, } } diff --git a/authority/mgmt/config.go b/authority/mgmt/config.go index d569ad6f..8b23cad0 100644 --- a/authority/mgmt/config.go +++ b/authority/mgmt/config.go @@ -14,16 +14,6 @@ const ( DefaultAuthorityID = "00000000-0000-0000-0000-000000000000" ) -// StatusType is the type for status. -type StatusType string - -var ( - // StatusActive active - StatusActive = StatusType("active") - // StatusDeleted deleted - StatusDeleted = StatusType("deleted") -) - // Claims encapsulates all x509 and ssh claims applied to the authority // configuration. E.g. maxTLSCertDuration, defaultSSHCertDuration, etc. type Claims struct { diff --git a/authority/mgmt/db.go b/authority/mgmt/db.go index 8546c4b4..9cfab3e5 100644 --- a/authority/mgmt/db.go +++ b/authority/mgmt/db.go @@ -14,9 +14,8 @@ var ErrNotFound = errors.New("not found") type DB interface { CreateProvisioner(ctx context.Context, prov *Provisioner) error GetProvisioner(ctx context.Context, id string) (*Provisioner, error) - GetProvisionerByName(ctx context.Context, name string) (*Provisioner, error) GetProvisioners(ctx context.Context) ([]*Provisioner, error) - UpdateProvisioner(ctx context.Context, name string, prov *Provisioner) error + UpdateProvisioner(ctx context.Context, prov *Provisioner) error CreateAdmin(ctx context.Context, admin *Admin) error GetAdmin(ctx context.Context, id string) (*Admin, error) @@ -31,11 +30,10 @@ type DB interface { // MockDB is an implementation of the DB interface that should only be used as // a mock in tests. type MockDB struct { - MockCreateProvisioner func(ctx context.Context, prov *Provisioner) error - MockGetProvisioner func(ctx context.Context, id string) (*Provisioner, error) - MockGetProvisionerByName func(ctx context.Context, name string) (*Provisioner, error) - MockGetProvisioners func(ctx context.Context) ([]*Provisioner, error) - MockUpdateProvisioner func(ctx context.Context, name string, prov *Provisioner) error + MockCreateProvisioner func(ctx context.Context, prov *Provisioner) error + MockGetProvisioner func(ctx context.Context, id string) (*Provisioner, error) + MockGetProvisioners func(ctx context.Context) ([]*Provisioner, error) + MockUpdateProvisioner func(ctx context.Context, prov *Provisioner) error MockCreateAdmin func(ctx context.Context, adm *Admin) error MockGetAdmin func(ctx context.Context, id string) (*Admin, error) @@ -70,16 +68,6 @@ func (m *MockDB) GetProvisioner(ctx context.Context, id string) (*Provisioner, e return m.MockRet1.(*Provisioner), m.MockError } -// GetProvisionerByName mock. -func (m *MockDB) GetProvisionerByName(ctx context.Context, id string) (*Provisioner, error) { - if m.MockGetProvisionerByName != nil { - return m.MockGetProvisionerByName(ctx, id) - } else if m.MockError != nil { - return nil, m.MockError - } - return m.MockRet1.(*Provisioner), m.MockError -} - // GetProvisioners mock func (m *MockDB) GetProvisioners(ctx context.Context) ([]*Provisioner, error) { if m.MockGetProvisioners != nil { @@ -91,9 +79,9 @@ func (m *MockDB) GetProvisioners(ctx context.Context) ([]*Provisioner, error) { } // UpdateProvisioner mock -func (m *MockDB) UpdateProvisioner(ctx context.Context, name string, prov *Provisioner) error { +func (m *MockDB) UpdateProvisioner(ctx context.Context, prov *Provisioner) error { if m.MockUpdateProvisioner != nil { - return m.MockUpdateProvisioner(ctx, name, prov) + return m.MockUpdateProvisioner(ctx, prov) } return m.MockError } diff --git a/authority/mgmt/db/nosql/admin.go b/authority/mgmt/db/nosql/admin.go index 4e465489..9732b3f0 100644 --- a/authority/mgmt/db/nosql/admin.go +++ b/authority/mgmt/db/nosql/admin.go @@ -6,19 +6,21 @@ import ( "time" "github.com/pkg/errors" + "github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/authority/mgmt" + "github.com/smallstep/certificates/authority/status" "github.com/smallstep/nosql" ) // dbAdmin is the database representation of the Admin type. type dbAdmin struct { - ID string `json:"id"` - AuthorityID string `json:"authorityID"` - ProvisionerID string `json:"provisionerID"` - Subject string `json:"subject"` - Type mgmt.AdminType `json:"type"` - CreatedAt time.Time `json:"createdAt"` - DeletedAt time.Time `json:"deletedAt"` + ID string `json:"id"` + AuthorityID string `json:"authorityID"` + ProvisionerID string `json:"provisionerID"` + Subject string `json:"subject"` + Type admin.Type `json:"type"` + CreatedAt time.Time `json:"createdAt"` + DeletedAt time.Time `json:"deletedAt"` } func (dbp *dbAdmin) clone() *dbAdmin { @@ -71,10 +73,10 @@ func unmarshalAdmin(data []byte, id string) (*mgmt.Admin, error) { ProvisionerID: dba.ProvisionerID, Subject: dba.Subject, Type: dba.Type, - Status: mgmt.StatusActive, + Status: status.Active, } if !dba.DeletedAt.IsZero() { - adm.Status = mgmt.StatusDeleted + adm.Status = status.Deleted } return adm, nil } @@ -89,19 +91,13 @@ func (db *DB) GetAdmin(ctx context.Context, id string) (*mgmt.Admin, error) { if err != nil { return nil, err } - if adm.Status == mgmt.StatusDeleted { + if adm.Status == status.Deleted { return nil, mgmt.NewError(mgmt.ErrorDeletedType, "admin %s is deleted", adm.ID) } if adm.AuthorityID != db.authorityID { return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType, "admin %s is not owned by authority %s", adm.ID, db.authorityID) } - prov, err := db.GetProvisioner(ctx, adm.ProvisionerID) - if err != nil { - return nil, err - } - adm.ProvisionerName = prov.Name - adm.ProvisionerType = prov.Type return adm, nil } @@ -114,34 +110,18 @@ func (db *DB) GetAdmins(ctx context.Context) ([]*mgmt.Admin, error) { if err != nil { return nil, errors.Wrap(err, "error loading admins") } - var ( - provCache = map[string]*mgmt.Provisioner{} - admins []*mgmt.Admin - ) + var admins = []*mgmt.Admin{} for _, entry := range dbEntries { adm, err := unmarshalAdmin(entry.Value, string(entry.Key)) if err != nil { return nil, err } - if adm.Status == mgmt.StatusDeleted { + if adm.Status == status.Deleted { continue } if adm.AuthorityID != db.authorityID { continue } - var ( - prov *mgmt.Provisioner - ok bool - ) - if prov, ok = provCache[adm.ProvisionerID]; !ok { - prov, err = db.GetProvisioner(ctx, adm.ProvisionerID) - if err != nil { - return nil, err - } - provCache[adm.ProvisionerID] = prov - } - adm.ProvisionerName = prov.Name - adm.ProvisionerType = prov.Type admins = append(admins, adm) } return admins, nil @@ -156,24 +136,6 @@ func (db *DB) CreateAdmin(ctx context.Context, adm *mgmt.Admin) error { } adm.AuthorityID = db.authorityID - // If provisionerID is set, then use it, otherwise load the provisioner - // to get the name. - if adm.ProvisionerID == "" { - prov, err := db.GetProvisionerByName(ctx, adm.ProvisionerName) - if err != nil { - return err - } - adm.ProvisionerID = prov.ID - adm.ProvisionerType = prov.Type - } else { - prov, err := db.GetProvisioner(ctx, adm.ProvisionerID) - if err != nil { - return err - } - adm.ProvisionerName = prov.Name - adm.ProvisionerType = prov.Type - } - dba := &dbAdmin{ ID: adm.ID, AuthorityID: db.authorityID, @@ -196,7 +158,7 @@ func (db *DB) UpdateAdmin(ctx context.Context, adm *mgmt.Admin) error { nu := old.clone() // If the admin was active but is now deleted ... - if old.DeletedAt.IsZero() && adm.Status == mgmt.StatusDeleted { + if old.DeletedAt.IsZero() && adm.Status == status.Deleted { nu.DeletedAt = clock.Now() } nu.Type = adm.Type diff --git a/authority/mgmt/db/nosql/authConfig.go b/authority/mgmt/db/nosql/authConfig.go index b9108fdc..222e729d 100644 --- a/authority/mgmt/db/nosql/authConfig.go +++ b/authority/mgmt/db/nosql/authConfig.go @@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/mgmt" + "github.com/smallstep/certificates/authority/status" "github.com/smallstep/nosql" ) @@ -106,7 +107,7 @@ func (db *DB) UpdateAuthConfig(ctx context.Context, ac *mgmt.AuthConfig) error { nu := old.clone() // If the authority was active but is now deleted ... - if old.DeletedAt.IsZero() && ac.Status == mgmt.StatusDeleted { + if old.DeletedAt.IsZero() && ac.Status == status.Deleted { nu.DeletedAt = clock.Now() } nu.Claims = ac.Claims diff --git a/authority/mgmt/db/nosql/nosql.go b/authority/mgmt/db/nosql/nosql.go index d97b7700..47f79bde 100644 --- a/authority/mgmt/db/nosql/nosql.go +++ b/authority/mgmt/db/nosql/nosql.go @@ -3,7 +3,6 @@ package nosql import ( "context" "encoding/json" - "fmt" "time" "github.com/pkg/errors" @@ -60,7 +59,6 @@ func (db *DB) save(ctx context.Context, id string, nu interface{}, old interface return errors.Wrapf(err, "error marshaling acme type: %s, value: %v", typ, old) } } - fmt.Printf("oldB = %+v\n", oldB) _, swapped, err := db.db.CmpAndSwap(table, []byte(id), oldB, newB) switch { diff --git a/authority/mgmt/db/nosql/provisioner.go b/authority/mgmt/db/nosql/provisioner.go index d44c3be1..2c2d65b0 100644 --- a/authority/mgmt/db/nosql/provisioner.go +++ b/authority/mgmt/db/nosql/provisioner.go @@ -3,13 +3,12 @@ package nosql import ( "context" "encoding/json" - "fmt" "time" "github.com/pkg/errors" "github.com/smallstep/certificates/authority/mgmt" + "github.com/smallstep/certificates/authority/status" "github.com/smallstep/nosql" - "github.com/smallstep/nosql/database" ) // dbProvisioner is the database representation of a Provisioner type. @@ -39,20 +38,6 @@ func (dbp *dbProvisioner) clone() *dbProvisioner { return &u } -func (db *DB) getProvisionerIDByName(ctx context.Context, name string) (string, error) { - data, err := db.db.Get(authorityProvisionersNameIDIndexTable, []byte(name)) - if nosql.IsErrNotFound(err) { - return "", mgmt.NewError(mgmt.ErrorNotFoundType, "provisioner %s not found", name) - } else if err != nil { - return "", mgmt.WrapErrorISE(err, "error loading provisioner %s", name) - } - ni := new(provisionerNameID) - if err := json.Unmarshal(data, ni); err != nil { - return "", mgmt.WrapErrorISE(err, "error unmarshaling provisionerNameID for provisioner %s", name) - } - return ni.ID, nil -} - func (db *DB) getDBProvisionerBytes(ctx context.Context, id string) ([]byte, error) { data, err := db.db.Get(authorityProvisionersTable, []byte(id)) if nosql.IsErrNotFound(err) { @@ -82,25 +67,6 @@ func (db *DB) getDBProvisioner(ctx context.Context, id string) (*dbProvisioner, return dbp, nil } -func (db *DB) getDBProvisionerByName(ctx context.Context, name string) (*dbProvisioner, error) { - id, err := db.getProvisionerIDByName(ctx, name) - if err != nil { - return nil, err - } - dbp, err := db.getDBProvisioner(ctx, id) - if err != nil { - return nil, err - } - if !dbp.DeletedAt.IsZero() { - return nil, mgmt.NewError(mgmt.ErrorDeletedType, "provisioner %s is deleted", name) - } - if dbp.AuthorityID != db.authorityID { - return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType, - "provisioner %s is not owned by authority %s", name, db.authorityID) - } - return dbp, nil -} - // GetProvisioner retrieves and unmarshals a provisioner from the database. func (db *DB) GetProvisioner(ctx context.Context, id string) (*mgmt.Provisioner, error) { data, err := db.getDBProvisionerBytes(ctx, id) @@ -112,7 +78,7 @@ func (db *DB) GetProvisioner(ctx context.Context, id string) (*mgmt.Provisioner, if err != nil { return nil, err } - if prov.Status == mgmt.StatusDeleted { + if prov.Status == status.Deleted { return nil, mgmt.NewError(mgmt.ErrorDeletedType, "provisioner %s is deleted", prov.ID) } if prov.AuthorityID != db.authorityID { @@ -122,15 +88,6 @@ func (db *DB) GetProvisioner(ctx context.Context, id string) (*mgmt.Provisioner, return prov, nil } -// GetProvisionerByName retrieves a provisioner from the database by name. -func (db *DB) GetProvisionerByName(ctx context.Context, name string) (*mgmt.Provisioner, error) { - p, err := db.getProvisionerIDByName(ctx, name) - if err != nil { - return nil, err - } - return db.GetProvisioner(ctx, id) -} - func unmarshalDBProvisioner(data []byte, name string) (*dbProvisioner, error) { var dbp = new(dbProvisioner) if err := json.Unmarshal(data, dbp); err != nil { @@ -157,14 +114,14 @@ func unmarshalProvisioner(data []byte, name string) (*mgmt.Provisioner, error) { Name: dbp.Name, Claims: dbp.Claims, Details: details, - Status: mgmt.StatusActive, + Status: status.Active, X509Template: dbp.X509Template, X509TemplateData: dbp.X509TemplateData, SSHTemplate: dbp.SSHTemplate, SSHTemplateData: dbp.SSHTemplateData, } if !dbp.DeletedAt.IsZero() { - prov.Status = mgmt.StatusDeleted + prov.Status = status.Deleted } return prov, nil } @@ -182,7 +139,7 @@ func (db *DB) GetProvisioners(ctx context.Context) ([]*mgmt.Provisioner, error) if err != nil { return nil, err } - if prov.Status == mgmt.StatusDeleted { + if prov.Status == status.Deleted { continue } if prov.AuthorityID != db.authorityID { @@ -219,37 +176,8 @@ func (db *DB) CreateProvisioner(ctx context.Context, prov *mgmt.Provisioner) err SSHTemplateData: prov.SSHTemplateData, CreatedAt: clock.Now(), } - dbpBytes, err := json.Marshal(dbp) - if err != nil { - return mgmt.WrapErrorISE(err, "error marshaling dbProvisioner %s", prov.Name) - } - pni := &provisionerNameID{ - Name: prov.Name, - ID: prov.ID, - } - pniBytes, err := json.Marshal(pni) - if err != nil { - return mgmt.WrapErrorISE(err, "error marshaling provisionerNameIndex %s", prov.Name) - } - if err := db.db.Update(&database.Tx{ - Operations: []*database.TxEntry{ - { - Bucket: authorityProvisionersTable, - Key: []byte(dbp.ID), - Cmd: database.CmpAndSwap, - Value: dbpBytes, - CmpValue: nil, - }, - { - Bucket: authorityProvisionersNameIDIndexTable, - Key: []byte(dbp.Name), - Cmd: database.CmpAndSwap, - Value: pniBytes, - CmpValue: nil, - }, - }, - }); err != nil { + if err := db.save(ctx, prov.ID, dbp, nil, "provisioner", authorityProvisionersTable); err != nil { return mgmt.WrapErrorISE(err, "error creating provisioner %s", prov.Name) } @@ -257,22 +185,11 @@ func (db *DB) CreateProvisioner(ctx context.Context, prov *mgmt.Provisioner) err } // UpdateProvisioner saves an updated provisioner to the database. -func (db *DB) UpdateProvisioner(ctx context.Context, name string, prov *mgmt.Provisioner) error { - id, err := db.getProvisionerIDByName(ctx, name) +func (db *DB) UpdateProvisioner(ctx context.Context, prov *mgmt.Provisioner) error { + old, err := db.getDBProvisioner(ctx, prov.ID) if err != nil { return err } - prov.ID = id - oldBytes, err := db.getDBProvisionerBytes(ctx, id) - if err != nil { - return err - } - fmt.Printf("oldBytes = %+v\n", oldBytes) - old, err := unmarshalDBProvisioner(oldBytes, id) - if err != nil { - return err - } - fmt.Printf("old = %+v\n", old) nu := old.clone() @@ -281,91 +198,15 @@ func (db *DB) UpdateProvisioner(ctx context.Context, name string, prov *mgmt.Pro nu.Claims = prov.Claims nu.Details, err = json.Marshal(prov.Details) if err != nil { - return mgmt.WrapErrorISE(err, "error marshaling details when updating provisioner %s", name) + return mgmt.WrapErrorISE(err, "error marshaling details when updating provisioner %s", prov.Name) } nu.X509Template = prov.X509Template nu.X509TemplateData = prov.X509TemplateData nu.SSHTemplateData = prov.SSHTemplateData - var txs = []*database.TxEntry{} - // If the provisioner was active but is now deleted ... - if old.DeletedAt.IsZero() && prov.Status == mgmt.StatusDeleted { - nu.DeletedAt = clock.Now() - txs = append(txs, &database.TxEntry{ - Bucket: authorityProvisionersNameIDIndexTable, - Key: []byte(name), - Cmd: database.Delete, - }) - } - - if prov.Name != name { - // If the new name does not match the old name then: - // 1) check that the new name is not already taken - // 2) delete the old name-id index resource - // 3) create a new name-id index resource - // 4) update the provisioner resource - nuBytes, err := json.Marshal(nu) - if err != nil { - return mgmt.WrapErrorISE(err, "error marshaling dbProvisioner %s", prov.Name) - } - pni := &provisionerNameID{ - Name: prov.Name, - ID: prov.ID, - } - pniBytes, err := json.Marshal(pni) - if err != nil { - return mgmt.WrapErrorISE(err, "error marshaling provisionerNameID for provisioner %s", prov.Name) - } - - _, err = db.db.Get(authorityProvisionersNameIDIndexTable, []byte(name)) - if err == nil { - return mgmt.NewError(mgmt.ErrorBadRequestType, "provisioner with name %s already exists", prov.Name) - } else if !nosql.IsErrNotFound(err) { - return mgmt.WrapErrorISE(err, "error loading provisionerNameID %s", prov.Name) - } - err = db.db.Update(&database.Tx{ - Operations: []*database.TxEntry{ - { - Bucket: authorityProvisionersNameIDIndexTable, - Key: []byte(name), - Cmd: database.Delete, - }, - { - Bucket: authorityProvisionersNameIDIndexTable, - Key: []byte(prov.Name), - Cmd: database.CmpAndSwap, - Value: pniBytes, - CmpValue: nil, - }, - { - Bucket: authorityProvisionersTable, - Key: []byte(nu.ID), - Cmd: database.CmpAndSwap, - Value: nuBytes, - CmpValue: oldBytes, - }, - }, - }) - } else { - err = db.db.Update(&database.Tx{ - Operations: []*database.TxEntry{ - { - Bucket: authorityProvisionersNameIDIndexTable, - Key: []byte(name), - Cmd: database.Delete, - }, - { - Bucket: authorityProvisionersTable, - Key: []byte(nu.ID), - Cmd: database.CmpAndSwap, - Value: nuBytes, - CmpValue: oldBytes, - }, - }, - }) - } - if err != nil { + if err := db.save(ctx, prov.ID, nu, old, "provisioner", authorityProvisionersTable); err != nil { return mgmt.WrapErrorISE(err, "error updating provisioner %s", prov.Name) } + return nil } diff --git a/authority/mgmt/provisioner.go b/authority/mgmt/provisioner.go index fb39f6bc..10f472de 100644 --- a/authority/mgmt/provisioner.go +++ b/authority/mgmt/provisioner.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/authority/status" "go.step.sm/crypto/jose" ) @@ -69,7 +70,7 @@ type Provisioner struct { X509TemplateData []byte `json:"x509TemplateData"` SSHTemplate string `json:"sshTemplate"` SSHTemplateData []byte `json:"sshTemplateData"` - Status StatusType `json:"status"` + Status status.Type `json:"status"` } func (p *Provisioner) GetOptions() *provisioner.Options { @@ -101,7 +102,7 @@ func CreateProvisioner(ctx context.Context, db DB, typ, name string, opts ...Pro X509TemplateData: pc.X509TemplateData, SSHTemplate: pc.SSHTemplate, SSHTemplateData: pc.SSHTemplateData, - Status: StatusActive, + Status: status.Active, } if err := db.CreateProvisioner(ctx, p); err != nil { diff --git a/authority/provisioner/collection.go b/authority/provisioner/collection.go index b6ff8b9e..23a4a2a0 100644 --- a/authority/provisioner/collection.go +++ b/authority/provisioner/collection.go @@ -183,7 +183,7 @@ func (c *Collection) Store(p Interface) error { // Store provisioner always by name. if _, loaded := c.byName.LoadOrStore(p.GetName(), p); loaded { c.byID.Delete(p.GetID()) - return errors.New("cannot add multiple provisioners with the same id") + return errors.New("cannot add multiple provisioners with the same name") } // Store provisioner in byKey if EncryptedKey is defined. diff --git a/authority/status/status.go b/authority/status/status.go new file mode 100644 index 00000000..49e4c0bb --- /dev/null +++ b/authority/status/status.go @@ -0,0 +1,11 @@ +package status + +// Type is the type for status. +type Type string + +var ( + // Active active + Active = Type("active") + // Deleted deleted + Deleted = Type("deleted") +) diff --git a/ca/mgmtClient.go b/ca/mgmtClient.go index 96c33f03..7a50e369 100644 --- a/ca/mgmtClient.go +++ b/ca/mgmtClient.go @@ -7,8 +7,10 @@ import ( "net/http" "net/url" "path" + "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" @@ -88,6 +90,80 @@ retry: return adm, nil } +// AdminOption is the type of options passed to the Provisioner method. +type AdminOption func(o *adminOptions) error + +type adminOptions struct { + cursor string + limit int +} + +func (o *adminOptions) apply(opts []AdminOption) (err error) { + for _, fn := range opts { + if err = fn(o); err != nil { + return + } + } + return +} + +func (o *adminOptions) rawQuery() string { + v := url.Values{} + if len(o.cursor) > 0 { + v.Set("cursor", o.cursor) + } + if o.limit > 0 { + v.Set("limit", strconv.Itoa(o.limit)) + } + return v.Encode() +} + +// WithAdminCursor will request the admins starting with the given cursor. +func WithAdminCursor(cursor string) AdminOption { + return func(o *adminOptions) error { + o.cursor = cursor + return nil + } +} + +// WithAdminLimit will request the given number of admins. +func WithAdminLimit(limit int) AdminOption { + return func(o *adminOptions) error { + o.limit = limit + return nil + } +} + +// GetAdmins performs the GET /mgmt/admins request to the CA. +func (c *MgmtClient) GetAdmins(opts ...AdminOption) (*mgmtAPI.GetAdminsResponse, error) { + var retried bool + o := new(adminOptions) + if err := o.apply(opts); err != nil { + return nil, err + } + u := c.endpoint.ResolveReference(&url.URL{ + Path: "/mgmt/admins", + RawQuery: o.rawQuery(), + }) +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, readMgmtError(resp.Body) + } + var body = new(mgmtAPI.GetAdminsResponse) + if err := readJSON(resp.Body, body); err != nil { + return nil, errors.Wrapf(err, "error reading %s", u) + } + return body, nil +} + // CreateAdmin performs the POST /mgmt/admin request to the CA. func (c *MgmtClient) CreateAdmin(req *mgmtAPI.CreateAdminRequest) (*mgmt.Admin, error) { var retried bool @@ -139,7 +215,7 @@ retry: } // UpdateAdmin performs the PUT /mgmt/admin/{id} request to the CA. -func (c *MgmtClient) UpdateAdmin(id string, uar *mgmtAPI.UpdateAdminRequest) (*mgmt.Admin, error) { +func (c *MgmtClient) UpdateAdmin(id string, uar *mgmtAPI.UpdateAdminRequest) (*admin.Admin, error) { var retried bool body, err := json.Marshal(uar) if err != nil { @@ -162,36 +238,13 @@ retry: } return nil, readMgmtError(resp.Body) } - var adm = new(mgmt.Admin) + var adm = new(admin.Admin) if err := readJSON(resp.Body, adm); err != nil { return nil, errors.Wrapf(err, "error reading %s", u) } return adm, nil } -// GetAdmins performs the GET /mgmt/admins request to the CA. -func (c *MgmtClient) GetAdmins() ([]*mgmt.Admin, error) { - var retried bool - u := c.endpoint.ResolveReference(&url.URL{Path: "/mgmt/admins"}) -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, readMgmtError(resp.Body) - } - var admins = new([]*mgmt.Admin) - if err := readJSON(resp.Body, admins); err != nil { - return nil, errors.Wrapf(err, "error reading %s", u) - } - return *admins, nil -} - // GetProvisioner performs the GET /mgmt/provisioner/{id} request to the CA. func (c *MgmtClient) GetProvisioner(id string) (*mgmt.Provisioner, error) { var retried bool From 638766c615cb313d8f597ef472003fa3156fba0f Mon Sep 17 00:00:00 2001 From: max furman Date: Wed, 19 May 2021 18:23:20 -0700 Subject: [PATCH 08/89] wip --- authority/provisioner/acme.go | 10 ++++++++++ authority/provisioner/aws.go | 10 ++++++++++ authority/provisioner/azure.go | 10 ++++++++++ authority/provisioner/collection.go | 28 ++++++++++++++++++++++------ authority/provisioner/gcp.go | 11 +++++++++++ authority/provisioner/jwk.go | 6 ++++++ authority/provisioner/k8sSA.go | 10 ++++++++++ authority/provisioner/noop.go | 4 ++++ authority/provisioner/oidc.go | 10 ++++++++++ authority/provisioner/provisioner.go | 10 ++++++++++ authority/provisioner/sshpop.go | 10 ++++++++++ authority/provisioner/x5c.go | 10 ++++++++++ 12 files changed, 123 insertions(+), 6 deletions(-) diff --git a/authority/provisioner/acme.go b/authority/provisioner/acme.go index a36c496d..4109d217 100644 --- a/authority/provisioner/acme.go +++ b/authority/provisioner/acme.go @@ -13,6 +13,7 @@ import ( // provisioning flow. type ACME struct { *base + ID string `json:"-"` Type string `json:"type"` Name string `json:"name"` ForceCN bool `json:"forceCN,omitempty"` @@ -23,6 +24,15 @@ type ACME struct { // GetID returns the provisioner unique identifier. func (p ACME) GetID() string { + if p.ID != "" { + return p.ID + } + return p.GetIDForToken() +} + +// GetIDForToken returns an identifier that will be used to load the provisioner +// from a token. +func (p *ACME) GetIDForToken() string { return "acme/" + p.Name } diff --git a/authority/provisioner/aws.go b/authority/provisioner/aws.go index 75115154..8a443554 100644 --- a/authority/provisioner/aws.go +++ b/authority/provisioner/aws.go @@ -252,6 +252,7 @@ type awsInstanceIdentityDocument struct { // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html type AWS struct { *base + ID string `json:"-"` Type string `json:"type"` Name string `json:"name"` Accounts []string `json:"accounts"` @@ -269,6 +270,15 @@ type AWS struct { // GetID returns the provisioner unique identifier. func (p *AWS) GetID() string { + if p.ID != "" { + return p.ID + } + return p.GetIDForToken() +} + +// GetIDForToken returns an identifier that will be used to load the provisioner +// from a token. +func (p *AWS) GetIDForToken() string { return "aws/" + p.Name } diff --git a/authority/provisioner/azure.go b/authority/provisioner/azure.go index ea8b08ec..b077d735 100644 --- a/authority/provisioner/azure.go +++ b/authority/provisioner/azure.go @@ -84,6 +84,7 @@ type azurePayload struct { // and https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service type Azure struct { *base + ID string `json:"-"` Type string `json:"type"` Name string `json:"name"` TenantID string `json:"tenantID"` @@ -101,6 +102,15 @@ type Azure struct { // GetID returns the provisioner unique identifier. func (p *Azure) GetID() string { + if p.ID != "" { + return p.ID + } + return p.GetIDForToken() +} + +// GetIDForToken returns an identifier that will be used to load the provisioner +// from a token. +func (p *Azure) GetIDForToken() string { return p.TenantID } diff --git a/authority/provisioner/collection.go b/authority/provisioner/collection.go index 23a4a2a0..ccfbc60a 100644 --- a/authority/provisioner/collection.go +++ b/authority/provisioner/collection.go @@ -46,6 +46,7 @@ type Collection struct { byID *sync.Map byKey *sync.Map byName *sync.Map + byTokenID *sync.Map sorted provisionerSlice audiences Audiences } @@ -57,6 +58,7 @@ func NewCollection(audiences Audiences) *Collection { byID: new(sync.Map), byKey: new(sync.Map), byName: new(sync.Map), + byTokenID: new(sync.Map), audiences: audiences, } } @@ -71,6 +73,13 @@ func (c *Collection) LoadByName(name string) (Interface, bool) { return loadProvisioner(c.byName, name) } +// LoadByTokenID a provisioner by identifier found in token. +// For different provisioner types this identifier may be found in in different +// attributes of the token. +func (c *Collection) LoadByTokenID(tokenProvisionerID string) (Interface, bool) { + return loadProvisioner(c.byTokenID, tokenProvisionerID) +} + // LoadByToken parses the token claims and loads the provisioner associated. func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims) (Interface, bool) { var audiences []string @@ -86,11 +95,12 @@ func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims) if matchesAudience(claims.Audience, audiences) { // Use fragment to get provisioner name (GCP, AWS, SSHPOP) if fragment != "" { - return c.Load(fragment) + return c.LoadByTokenID(fragment) } // If matches with stored audiences it will be a JWT token (default), and // the id would be :. - return c.Load(claims.Issuer + ":" + token.Headers[0].KeyID) + // TODO: is this ok? + return c.LoadByTokenID(claims.Issuer + ":" + token.Headers[0].KeyID) } // The ID will be just the clientID stored in azp, aud or tid. @@ -101,7 +111,7 @@ func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims) // Kubernetes Service Account tokens. if payload.Issuer == k8sSAIssuer { - if p, ok := c.Load(K8sSAID); ok { + if p, ok := c.LoadByTokenID(K8sSAID); ok { return p, ok } // Kubernetes service account provisioner not found @@ -115,18 +125,18 @@ func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims) // Try with azp (OIDC) if len(payload.AuthorizedParty) > 0 { - if p, ok := c.Load(payload.AuthorizedParty); ok { + if p, ok := c.LoadByTokenID(payload.AuthorizedParty); ok { return p, ok } } // Try with tid (Azure) if payload.TenantID != "" { - if p, ok := c.Load(payload.TenantID); ok { + if p, ok := c.LoadByTokenID(payload.TenantID); ok { return p, ok } } // Fallback to aud - return c.Load(payload.Audience[0]) + return c.LoadByTokenID(payload.Audience[0]) } // LoadByCertificate looks for the provisioner extension and extracts the @@ -185,6 +195,12 @@ func (c *Collection) Store(p Interface) error { c.byID.Delete(p.GetID()) return errors.New("cannot add multiple provisioners with the same name") } + // Store provisioner always by ID presented in token. + if _, loaded := c.byTokenID.LoadOrStore(p.GetIDForToken(), p); loaded { + c.byID.Delete(p.GetID()) + c.byName.Delete(p.GetName()) + return errors.New("cannot add multiple provisioners with the same token identifier") + } // Store provisioner in byKey if EncryptedKey is defined. if kid, _, ok := p.GetEncryptedKey(); ok { diff --git a/authority/provisioner/gcp.go b/authority/provisioner/gcp.go index 830e7965..6d19d052 100644 --- a/authority/provisioner/gcp.go +++ b/authority/provisioner/gcp.go @@ -78,6 +78,7 @@ func newGCPConfig() *gcpConfig { // https://cloud.google.com/compute/docs/instances/verifying-instance-identity type GCP struct { *base + ID string `json:"-"` Type string `json:"type"` Name string `json:"name"` ServiceAccounts []string `json:"serviceAccounts"` @@ -96,6 +97,16 @@ type GCP struct { // GetID returns the provisioner unique identifier. The name should uniquely // identify any GCP provisioner. func (p *GCP) GetID() string { + if p.ID != "" { + return p.ID + } + return p.GetIDForToken() + +} + +// GetIDForToken returns an identifier that will be used to load the provisioner +// from a token. +func (p *GCP) GetIDForToken() string { return "gcp/" + p.Name } diff --git a/authority/provisioner/jwk.go b/authority/provisioner/jwk.go index a2d3e0b1..d57ff4c1 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -45,6 +45,12 @@ func (p *JWK) GetID() string { if p.ID != "" { return p.ID } + return p.GetIDForToken() +} + +// GetIDForToken returns an identifier that will be used to load the provisioner +// from a token. +func (p *JWK) GetIDForToken() string { return p.Name + ":" + p.Key.KeyID } diff --git a/authority/provisioner/k8sSA.go b/authority/provisioner/k8sSA.go index 209a7dd4..876131e1 100644 --- a/authority/provisioner/k8sSA.go +++ b/authority/provisioner/k8sSA.go @@ -42,6 +42,7 @@ type k8sSAPayload struct { // entity trusted to make signature requests. type K8sSA struct { *base + ID string `json:"-"` Type string `json:"type"` Name string `json:"name"` PubKeys []byte `json:"publicKeys,omitempty"` @@ -56,6 +57,15 @@ type K8sSA struct { // GetID returns the provisioner unique identifier. The name and credential id // should uniquely identify any K8sSA provisioner. func (p *K8sSA) GetID() string { + if p.ID != "" { + return p.ID + } + return p.GetIDForToken() +} + +// GetIDForToken returns an identifier that will be used to load the provisioner +// from a token. +func (p *K8sSA) GetIDForToken() string { return K8sSAID } diff --git a/authority/provisioner/noop.go b/authority/provisioner/noop.go index ccdeccf4..18a38331 100644 --- a/authority/provisioner/noop.go +++ b/authority/provisioner/noop.go @@ -14,6 +14,10 @@ func (p *noop) GetID() string { return "noop" } +func (p *noop) GetIDForToken() string { + return "noop" +} + func (p *noop) GetTokenID(token string) (string, error) { return "", nil } diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index 46e1c623..df3c1f9e 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -54,6 +54,7 @@ type openIDPayload struct { // ClientSecret is mandatory, but it can be an empty string. type OIDC struct { *base + ID string `json:"-"` Type string `json:"type"` Name string `json:"name"` ClientID string `json:"clientID"` @@ -111,6 +112,15 @@ func sanitizeEmail(email string) string { // GetID returns the provisioner unique identifier, the OIDC provisioner the // uses the clientID for this. func (o *OIDC) GetID() string { + if o.ID != "" { + return o.ID + } + return o.GetIDForToken() +} + +// GetIDForToken returns an identifier that will be used to load the provisioner +// from a token. +func (o *OIDC) GetIDForToken() string { return o.ClientID } diff --git a/authority/provisioner/provisioner.go b/authority/provisioner/provisioner.go index 197bd26c..981d0b0a 100644 --- a/authority/provisioner/provisioner.go +++ b/authority/provisioner/provisioner.go @@ -17,6 +17,7 @@ import ( // Interface is the interface that all provisioner types must implement. type Interface interface { GetID() string + GetIDForToken() string GetTokenID(token string) (string, error) GetName() string GetType() Type @@ -388,6 +389,7 @@ type MockProvisioner struct { Mret1, Mret2, Mret3 interface{} Merr error MgetID func() string + MgetIDForToken func() string MgetTokenID func(string) (string, error) MgetName func() string MgetType func() Type @@ -410,6 +412,14 @@ func (m *MockProvisioner) GetID() string { return m.Mret1.(string) } +// GetIDForToken mock +func (m *MockProvisioner) GetIDForToken() string { + if m.MgetIDForToken != nil { + return m.MgetIDForToken() + } + return m.Mret1.(string) +} + // GetTokenID mock func (m *MockProvisioner) GetTokenID(token string) (string, error) { if m.MgetTokenID != nil { diff --git a/authority/provisioner/sshpop.go b/authority/provisioner/sshpop.go index 223f0b9e..dd9c7d1f 100644 --- a/authority/provisioner/sshpop.go +++ b/authority/provisioner/sshpop.go @@ -26,6 +26,7 @@ type sshPOPPayload struct { // signature requests. type SSHPOP struct { *base + ID string `json:"-"` Type string `json:"type"` Name string `json:"name"` Claims *Claims `json:"claims,omitempty"` @@ -38,6 +39,15 @@ type SSHPOP struct { // GetID returns the provisioner unique identifier. The name and credential id // should uniquely identify any SSH-POP provisioner. func (p *SSHPOP) GetID() string { + if p.ID != "" { + return p.ID + } + return p.GetIDForToken() +} + +// GetIDForToken returns an identifier that will be used to load the provisioner +// from a token. +func (p *SSHPOP) GetIDForToken() string { return "sshpop/" + p.Name } diff --git a/authority/provisioner/x5c.go b/authority/provisioner/x5c.go index 2b05f4c8..9e47aaed 100644 --- a/authority/provisioner/x5c.go +++ b/authority/provisioner/x5c.go @@ -26,6 +26,7 @@ type x5cPayload struct { // signature requests. type X5C struct { *base + ID string `json:"-"` Type string `json:"type"` Name string `json:"name"` Roots []byte `json:"roots"` @@ -39,6 +40,15 @@ type X5C struct { // GetID returns the provisioner unique identifier. The name and credential id // should uniquely identify any X5C provisioner. func (p *X5C) GetID() string { + if p.ID != "" { + return p.ID + } + return p.GetIDForToken() +} + +// GetIDForToken returns an identifier that will be used to load the provisioner +// from a token. +func (p *X5C) GetIDForToken() string { return "x5c/" + p.Name } From 9bf9bf142d250f580a125bf83f0f2a45c2eb5786 Mon Sep 17 00:00:00 2001 From: max furman Date: Thu, 20 May 2021 13:01:58 -0700 Subject: [PATCH 09/89] wip --- authority/authority.go | 16 ++++++------ authority/config.go | 37 +++++++++++++++++++++++++++ authority/mgmt/api/handler.go | 4 +-- authority/mgmt/api/provisioner.go | 25 ++++++++++++++---- authority/mgmt/provisioner.go | 36 +++++++++++++------------- authority/options.go | 9 +++++++ ca/ca.go | 10 ++++---- ca/mgmtClient.go | 42 +++++++++++++++---------------- 8 files changed, 121 insertions(+), 58 deletions(-) create mode 100644 authority/config.go diff --git a/authority/authority.go b/authority/authority.go index 2772c444..e26ff22d 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -32,7 +32,7 @@ import ( // Authority implements the Certificate Authority internal interface. type Authority struct { config *config.Config - mgmtDB mgmt.DB + adminDB mgmt.DB keyManager kms.KeyManager provisioners *provisioner.Collection admins *admin.Collection @@ -130,7 +130,7 @@ func NewEmbedded(opts ...Option) (*Authority, error) { } func (a *Authority) ReloadAuthConfig() error { - mgmtAuthConfig, err := a.mgmtDB.GetAuthConfig(context.Background(), mgmt.DefaultAuthorityID) + mgmtAuthConfig, err := a.adminDB.GetAuthConfig(context.Background(), mgmt.DefaultAuthorityID) if err != nil { return mgmt.WrapErrorISE(err, "error getting authConfig from db") } @@ -204,14 +204,14 @@ func (a *Authority) init() error { // Pull AuthConfig from DB. if true { // Check if AuthConfig already exists - a.mgmtDB, err = authMgmtNosql.New(a.db.(nosql.DB), mgmt.DefaultAuthorityID) + a.adminDB, err = authMgmtNosql.New(a.db.(nosql.DB), mgmt.DefaultAuthorityID) if err != nil { return err } - mgmtAuthConfig, err := a.mgmtDB.GetAuthConfig(context.Background(), mgmt.DefaultAuthorityID) + 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.mgmtDB, mgmt.WithDefaultAuthorityID) + mgmtAuthConfig, err = mgmt.CreateAuthority(context.Background(), a.adminDB, mgmt.WithDefaultAuthorityID) if err != nil { return mgmt.WrapErrorISE(err, "error creating authConfig") } @@ -465,9 +465,9 @@ func (a *Authority) GetDatabase() db.AuthDB { return a.db } -// GetMgmtDatabase returns the mgmt database, if one exists. -func (a *Authority) GetMgmtDatabase() mgmt.DB { - return a.mgmtDB +// GetAdminDatabase returns the mgmt database, if one exists. +func (a *Authority) GetAdminDatabase() mgmt.DB { + return a.adminDB } // GetAdminCollection returns the admin collection. diff --git a/authority/config.go b/authority/config.go new file mode 100644 index 00000000..79bdc7ff --- /dev/null +++ b/authority/config.go @@ -0,0 +1,37 @@ +package authority + +import "github.com/smallstep/certificates/authority/config" + +// Config is an alias to support older APIs. +type Config = config.Config + +// AuthConfig is an alias to support older APIs. +type AuthConfig = config.AuthConfig + +// ASN1DN is an alias to support older APIs. +type ASN1DN = config.ASN1DN + +// TLS + +// TLSOptions is an alias to support older APIs. +type TLSOptions = config.TLSOptions + +// SSH + +// SSHConfig is an alias to support older APIs. +type SSHConfig = config.SSHConfig + +// Bastion is an alias to support older APIs. +type Bastion = config.Bastion + +// HostTag is an alias to support older APIs. +type HostTag = config.HostTag + +// Host is an alias to support older APIs. +type Host = config.Host + +// SSHPublicKey is an alias to support older APIs. +type SSHPublicKey = config.SSHPublicKey + +// SSHKeys is an alias to support older APIs. +type SSHKeys = config.SSHKeys diff --git a/authority/mgmt/api/handler.go b/authority/mgmt/api/handler.go index cb52736f..11dcbd86 100644 --- a/authority/mgmt/api/handler.go +++ b/authority/mgmt/api/handler.go @@ -25,8 +25,8 @@ type Handler struct { } // NewHandler returns a new Authority Config Handler. -func NewHandler(db mgmt.DB, auth *authority.Authority) api.RouterHandler { - return &Handler{db, auth} +func NewHandler(auth *authority.Authority) api.RouterHandler { + return &Handler{auth.GetAdminDatabase(), auth} } // Route traffic and implement the Router interface. diff --git a/authority/mgmt/api/provisioner.go b/authority/mgmt/api/provisioner.go index 79ef5f74..d383a27f 100644 --- a/authority/mgmt/api/provisioner.go +++ b/authority/mgmt/api/provisioner.go @@ -9,6 +9,7 @@ import ( "github.com/smallstep/certificates/authority/mgmt" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/status" + "github.com/smallstep/certificates/errs" ) // CreateProvisionerRequest represents the body for a CreateProvisioner request. @@ -31,6 +32,12 @@ func (cpr *CreateProvisionerRequest) Validate(c *provisioner.Collection) error { return nil } +// GetProvisionersResponse is the type for GET /admin/provisioners responses. +type GetProvisionersResponse struct { + Provisioners provisioner.List `json:"provisioners"` + NextCursor string `json:"nextCursor"` +} + // UpdateProvisionerRequest represents the body for a UpdateProvisioner request. type UpdateProvisionerRequest struct { Type string `json:"type"` @@ -72,14 +79,22 @@ func (h *Handler) GetProvisioner(w http.ResponseWriter, r *http.Request) { // GetProvisioners returns all provisioners associated with the authority. func (h *Handler) GetProvisioners(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - provs, err := h.db.GetProvisioners(ctx) + cursor, limit, err := api.ParseCursor(r) if err != nil { - api.WriteError(w, err) + api.WriteError(w, mgmt.WrapError(mgmt.ErrorBadRequestType, err, + "error parsing cursor / limt query params")) return } - api.JSON(w, provs) + + p, next, err := h.auth.GetProvisioners(cursor, limit) + if err != nil { + api.WriteError(w, errs.InternalServerErr(err)) + return + } + api.JSON(w, &GetProvisionersResponse{ + Provisioners: p, + NextCursor: next, + }) } // CreateProvisioner creates a new prov. diff --git a/authority/mgmt/provisioner.go b/authority/mgmt/provisioner.go index 10f472de..ea9d0da5 100644 --- a/authority/mgmt/provisioner.go +++ b/authority/mgmt/provisioner.go @@ -60,17 +60,17 @@ func WithPassword(pass string) func(*ProvisionerCtx) { // Provisioner type. type Provisioner struct { - ID string `json:"-"` - AuthorityID string `json:"-"` - Type string `json:"type"` - Name string `json:"name"` - Claims *Claims `json:"claims"` - Details interface{} `json:"details"` - X509Template string `json:"x509Template"` - X509TemplateData []byte `json:"x509TemplateData"` - SSHTemplate string `json:"sshTemplate"` - SSHTemplateData []byte `json:"sshTemplateData"` - Status status.Type `json:"status"` + ID string `json:"-"` + AuthorityID string `json:"-"` + Type string `json:"type"` + Name string `json:"name"` + Claims *Claims `json:"claims"` + Details ProvisionerDetails `json:"details"` + X509Template string `json:"x509Template"` + X509TemplateData []byte `json:"x509TemplateData"` + SSHTemplate string `json:"sshTemplate"` + SSHTemplateData []byte `json:"sshTemplateData"` + Status status.Type `json:"status"` } func (p *Provisioner) GetOptions() *provisioner.Options { @@ -111,6 +111,8 @@ func CreateProvisioner(ctx context.Context, db DB, typ, name string, opts ...Pro return p, nil } +// ProvisionerDetails is the interface implemented by all provisioner details +// attributes. type ProvisionerDetails interface { isProvisionerDetails() } @@ -118,8 +120,8 @@ type ProvisionerDetails interface { // ProvisionerDetailsJWK represents the values required by a JWK provisioner. type ProvisionerDetailsJWK struct { Type ProvisionerType `json:"type"` - PubKey []byte `json:"pubKey"` - EncPrivKey string `json:"privKey"` + PublicKey []byte `json:"publicKey"` + PrivateKey string `json:"PrivateKey"` } // ProvisionerDetailsOIDC represents the values required by a OIDC provisioner. @@ -232,8 +234,8 @@ func createJWKDetails(pc *ProvisionerCtx) (*ProvisionerDetailsJWK, error) { return &ProvisionerDetailsJWK{ Type: ProvisionerTypeJWK, - PubKey: jwkPubBytes, - EncPrivKey: jwePrivStr, + PublicKey: jwkPubBytes, + PrivateKey: jwePrivStr, }, nil } @@ -248,7 +250,7 @@ func (p *Provisioner) ToCertificates() (provisioner.Interface, error) { switch details := p.Details.(type) { case *ProvisionerDetailsJWK: jwk := new(jose.JSONWebKey) - if err := json.Unmarshal(details.PubKey, &jwk); err != nil { + if err := json.Unmarshal(details.PublicKey, &jwk); err != nil { return nil, err } return &provisioner.JWK{ @@ -256,7 +258,7 @@ func (p *Provisioner) ToCertificates() (provisioner.Interface, error) { Type: p.Type, Name: p.Name, Key: jwk, - EncryptedKey: details.EncPrivKey, + EncryptedKey: details.PrivateKey, Claims: claims, Options: p.GetOptions(), }, nil diff --git a/authority/options.go b/authority/options.go index aaf8ffb3..cc2f64af 100644 --- a/authority/options.go +++ b/authority/options.go @@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/authority/config" + "github.com/smallstep/certificates/authority/mgmt" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/cas" casapi "github.com/smallstep/certificates/cas/apiv1" @@ -187,6 +188,14 @@ func WithX509FederatedBundle(pemCerts []byte) Option { } } +// WithAdminDB is an option to set the database backing the admin APIs. +func WithAdminDB(db mgmt.DB) Option { + return func(a *Authority) error { + a.adminDB = db + return nil + } +} + func readCertificateBundle(pemCerts []byte) ([]*x509.Certificate, error) { var block *pem.Block var certs []*x509.Certificate diff --git a/ca/ca.go b/ca/ca.go index ecbc55e2..d97e0ab2 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -171,11 +171,11 @@ func (ca *CA) Init(config *config.Config) (*CA, error) { acmeHandler.Route(r) }) - // MGMT Router - mgmtDB := auth.GetMgmtDatabase() - if mgmtDB != nil { - mgmtHandler := mgmtAPI.NewHandler(mgmtDB, auth) - mux.Route("/mgmt", func(r chi.Router) { + // Admin API Router + adminDB := auth.GetAdminDatabase() + if adminDB != nil { + mgmtHandler := mgmtAPI.NewHandler(auth) + mux.Route("/admin", func(r chi.Router) { mgmtHandler.Route(r) }) } diff --git a/ca/mgmtClient.go b/ca/mgmtClient.go index 7a50e369..7c4207aa 100644 --- a/ca/mgmtClient.go +++ b/ca/mgmtClient.go @@ -134,7 +134,7 @@ func WithAdminLimit(limit int) AdminOption { } } -// GetAdmins performs the GET /mgmt/admins request to the CA. +// GetAdmins performs the GET /admin/admins request to the CA. func (c *MgmtClient) GetAdmins(opts ...AdminOption) (*mgmtAPI.GetAdminsResponse, error) { var retried bool o := new(adminOptions) @@ -142,7 +142,7 @@ func (c *MgmtClient) GetAdmins(opts ...AdminOption) (*mgmtAPI.GetAdminsResponse, return nil, err } u := c.endpoint.ResolveReference(&url.URL{ - Path: "/mgmt/admins", + Path: "/admin/admins", RawQuery: o.rawQuery(), }) retry: @@ -164,14 +164,14 @@ retry: return body, nil } -// CreateAdmin performs the POST /mgmt/admin request to the CA. +// CreateAdmin performs the POST /admin/admin request to the CA. func (c *MgmtClient) CreateAdmin(req *mgmtAPI.CreateAdminRequest) (*mgmt.Admin, error) { var retried bool body, err := json.Marshal(req) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request") } - u := c.endpoint.ResolveReference(&url.URL{Path: "/mgmt/admin"}) + u := c.endpoint.ResolveReference(&url.URL{Path: "/admin/admin"}) retry: resp, err := c.client.Post(u.String(), "application/json", bytes.NewReader(body)) if err != nil { @@ -191,10 +191,10 @@ retry: return adm, nil } -// RemoveAdmin performs the DELETE /mgmt/admin/{id} request to the CA. +// RemoveAdmin performs the DELETE /admin/admin/{id} request to the CA. func (c *MgmtClient) RemoveAdmin(id string) error { var retried bool - u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/mgmt/admin", id)}) + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/admin/admin", id)}) req, err := http.NewRequest("DELETE", u.String(), nil) if err != nil { return errors.Wrapf(err, "create DELETE %s request failed", u) @@ -214,14 +214,14 @@ retry: return nil } -// UpdateAdmin performs the PUT /mgmt/admin/{id} request to the CA. +// UpdateAdmin performs the PUT /admin/admin/{id} request to the CA. func (c *MgmtClient) UpdateAdmin(id string, uar *mgmtAPI.UpdateAdminRequest) (*admin.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("/mgmt/admin", id)}) + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/admin/admin", 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) @@ -245,10 +245,10 @@ retry: return adm, nil } -// GetProvisioner performs the GET /mgmt/provisioner/{id} request to the CA. -func (c *MgmtClient) GetProvisioner(id string) (*mgmt.Provisioner, error) { +// GetProvisioner performs the GET /admin/provisioner/{name} request to the CA. +func (c *MgmtClient) GetProvisioner(name string) (*mgmt.Provisioner, error) { var retried bool - u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/mgmt/provisioner", id)}) + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/admin/provisioner", name)}) retry: resp, err := c.client.Get(u.String()) if err != nil { @@ -268,10 +268,10 @@ retry: return prov, nil } -// GetProvisioners performs the GET /mgmt/provisioners request to the CA. +// GetProvisioners performs the GET /admin/provisioners request to the CA. func (c *MgmtClient) GetProvisioners() ([]*mgmt.Provisioner, error) { var retried bool - u := c.endpoint.ResolveReference(&url.URL{Path: "/mgmt/provisioners"}) + u := c.endpoint.ResolveReference(&url.URL{Path: "/admin/provisioners"}) retry: resp, err := c.client.Get(u.String()) if err != nil { @@ -291,10 +291,10 @@ retry: return *provs, nil } -// RemoveProvisioner performs the DELETE /mgmt/provisioner/{name} request to the CA. +// RemoveProvisioner performs the DELETE /admin/provisioner/{name} request to the CA. func (c *MgmtClient) RemoveProvisioner(name string) error { var retried bool - u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/mgmt/provisioner", name)}) + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/admin/provisioner", name)}) req, err := http.NewRequest("DELETE", u.String(), nil) if err != nil { return errors.Wrapf(err, "create DELETE %s request failed", u) @@ -314,14 +314,14 @@ retry: return nil } -// CreateProvisioner performs the POST /mgmt/provisioner request to the CA. +// CreateProvisioner performs the POST /admin/provisioner request to the CA. func (c *MgmtClient) CreateProvisioner(req *mgmtAPI.CreateProvisionerRequest) (*mgmt.Provisioner, error) { var retried bool body, err := json.Marshal(req) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request") } - u := c.endpoint.ResolveReference(&url.URL{Path: "/mgmt/provisioner"}) + u := c.endpoint.ResolveReference(&url.URL{Path: "/admin/provisioner"}) retry: resp, err := c.client.Post(u.String(), "application/json", bytes.NewReader(body)) if err != nil { @@ -341,14 +341,14 @@ retry: return prov, nil } -// UpdateProvisioner performs the PUT /mgmt/provisioner/{id} request to the CA. +// UpdateProvisioner performs the PUT /admin/provisioner/{id} request to the CA. func (c *MgmtClient) UpdateProvisioner(id string, upr *mgmtAPI.UpdateProvisionerRequest) (*mgmt.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("/mgmt/provisioner", id)}) + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/admin/provisioner", 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) @@ -372,10 +372,10 @@ retry: return prov, nil } -// GetAuthConfig performs the GET /mgmt/authconfig/{id} request to the CA. +// GetAuthConfig performs the GET /admin/authconfig/{id} request to the CA. func (c *MgmtClient) GetAuthConfig(id string) (*mgmt.AuthConfig, error) { var retried bool - u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/mgmt/authconfig", id)}) + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/admin/authconfig", id)}) retry: resp, err := c.client.Get(u.String()) if err != nil { From 5929244fda0324be06a2f61fdfacc3804c496a47 Mon Sep 17 00:00:00 2001 From: max furman Date: Thu, 20 May 2021 13:12:02 -0700 Subject: [PATCH 10/89] wip --- authority/authority.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/authority/authority.go b/authority/authority.go index e26ff22d..c564f530 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -201,8 +201,9 @@ func (a *Authority) init() error { } } - // Pull AuthConfig from DB. - if true { + // 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 { From d8d5d7332b045bd2d1e4f73315e7dd8b68da8949 Mon Sep 17 00:00:00 2001 From: max furman Date: Thu, 20 May 2021 16:02:20 -0700 Subject: [PATCH 11/89] wip --- authority/mgmt/api/handler.go | 20 +++---- authority/mgmt/provisioner.go | 48 ++++++++++++++- ca/{mgmtClient.go => adminClient.go} | 90 ++++++++++++++-------------- 3 files changed, 102 insertions(+), 56 deletions(-) rename ca/{mgmtClient.go => adminClient.go} (77%) diff --git a/authority/mgmt/api/handler.go b/authority/mgmt/api/handler.go index 11dcbd86..8b6f915b 100644 --- a/authority/mgmt/api/handler.go +++ b/authority/mgmt/api/handler.go @@ -32,20 +32,20 @@ func NewHandler(auth *authority.Authority) api.RouterHandler { // Route traffic and implement the Router interface. func (h *Handler) Route(r api.Router) { // Provisioners - r.MethodFunc("GET", "/provisioner/{name}", h.GetProvisioner) + r.MethodFunc("GET", "/provisioners/{name}", h.GetProvisioner) r.MethodFunc("GET", "/provisioners", h.GetProvisioners) - r.MethodFunc("POST", "/provisioner", h.CreateProvisioner) - r.MethodFunc("PUT", "/provisioner/{name}", h.UpdateProvisioner) - r.MethodFunc("DELETE", "/provisioner/{name}", h.DeleteProvisioner) + r.MethodFunc("POST", "/provisioners", h.CreateProvisioner) + r.MethodFunc("PUT", "/provisioners/{name}", h.UpdateProvisioner) + r.MethodFunc("DELETE", "/provisioners/{name}", h.DeleteProvisioner) // Admins - r.MethodFunc("GET", "/admin/{id}", h.GetAdmin) + r.MethodFunc("GET", "/admins/{id}", h.GetAdmin) r.MethodFunc("GET", "/admins", h.GetAdmins) - r.MethodFunc("POST", "/admin", h.CreateAdmin) - r.MethodFunc("PATCH", "/admin/{id}", h.UpdateAdmin) - r.MethodFunc("DELETE", "/admin/{id}", h.DeleteAdmin) + r.MethodFunc("POST", "/admins", h.CreateAdmin) + r.MethodFunc("PATCH", "/admins/{id}", h.UpdateAdmin) + r.MethodFunc("DELETE", "/admins/{id}", h.DeleteAdmin) // AuthConfig - r.MethodFunc("GET", "/authconfig/{id}", h.GetAuthConfig) - r.MethodFunc("PUT", "/authconfig/{id}", h.UpdateAuthConfig) + r.MethodFunc("GET", "/authconfigs/{id}", h.GetAuthConfig) + r.MethodFunc("PUT", "/authconfigs/{id}", h.UpdateAuthConfig) } diff --git a/authority/mgmt/provisioner.go b/authority/mgmt/provisioner.go index ea9d0da5..537b7fdd 100644 --- a/authority/mgmt/provisioner.go +++ b/authority/mgmt/provisioner.go @@ -58,6 +58,20 @@ func WithPassword(pass string) func(*ProvisionerCtx) { } } +type unmarshalProvisioner struct { + ID string `json:"-"` + AuthorityID string `json:"-"` + Type string `json:"type"` + Name string `json:"name"` + Claims *Claims `json:"claims"` + Details json.RawMessage `json:"details"` + X509Template string `json:"x509Template"` + X509TemplateData []byte `json:"x509TemplateData"` + SSHTemplate string `json:"sshTemplate"` + SSHTemplateData []byte `json:"sshTemplateData"` + Status status.Type `json:"status"` +} + // Provisioner type. type Provisioner struct { ID string `json:"-"` @@ -73,6 +87,38 @@ type Provisioner struct { Status status.Type `json:"status"` } +type typ struct { + Type ProvisionerType `json:"type"` +} + +// UnmarshalJSON implements the Unmarshal interface. +func (p *Provisioner) UnmarshalJSON(b []byte) error { + var ( + err error + up = new(unmarshalProvisioner) + ) + if err = json.Unmarshal(b, up); err != nil { + return WrapErrorISE(err, "error unmarshaling provisioner to intermediate type") + } + p.Details, err = UnmarshalProvisionerDetails(up.Details) + if err = json.Unmarshal(b, up); err != nil { + return WrapErrorISE(err, "error unmarshaling provisioner details") + } + + p.ID = up.ID + p.AuthorityID = up.AuthorityID + p.Type = up.Type + p.Name = up.Name + p.Claims = up.Claims + p.X509Template = up.X509Template + p.X509TemplateData = up.X509TemplateData + p.SSHTemplate = up.SSHTemplate + p.SSHTemplateData = up.SSHTemplateData + p.Status = up.Status + + return nil +} + func (p *Provisioner) GetOptions() *provisioner.Options { return &provisioner.Options{ X509: &provisioner.X509Options{ @@ -415,7 +461,7 @@ type detailsType struct { Type ProvisionerType } -func UnmarshalProvisionerDetails(data []byte) (ProvisionerDetails, error) { +func UnmarshalProvisionerDetails(data json.RawMessage) (ProvisionerDetails, error) { dt := new(detailsType) if err := json.Unmarshal(data, dt); err != nil { return nil, WrapErrorISE(err, "error unmarshaling provisioner details") diff --git a/ca/mgmtClient.go b/ca/adminClient.go similarity index 77% rename from ca/mgmtClient.go rename to ca/adminClient.go index 7c4207aa..5fc6a7d4 100644 --- a/ca/mgmtClient.go +++ b/ca/adminClient.go @@ -16,16 +16,16 @@ import ( "github.com/smallstep/certificates/errs" ) -// MgmtClient implements an HTTP client for the CA server. -type MgmtClient struct { +// AdminClient implements an HTTP client for the CA server. +type AdminClient struct { client *uaClient endpoint *url.URL retryFunc RetryFunc opts []ClientOption } -// NewMgmtClient creates a new MgmtClient with the given endpoint and options. -func NewMgmtClient(endpoint string, opts ...ClientOption) (*MgmtClient, error) { +// NewAdminClient creates a new AdminClient with the given endpoint and options. +func NewAdminClient(endpoint string, opts ...ClientOption) (*AdminClient, error) { u, err := parseEndpoint(endpoint) if err != nil { return nil, err @@ -40,7 +40,7 @@ func NewMgmtClient(endpoint string, opts ...ClientOption) (*MgmtClient, error) { return nil, err } - return &MgmtClient{ + return &AdminClient{ client: newClient(tr), endpoint: u, retryFunc: o.retryFunc, @@ -48,7 +48,7 @@ func NewMgmtClient(endpoint string, opts ...ClientOption) (*MgmtClient, error) { }, nil } -func (c *MgmtClient) retryOnError(r *http.Response) bool { +func (c *AdminClient) retryOnError(r *http.Response) bool { if c.retryFunc != nil { if c.retryFunc(r.StatusCode) { o := new(clientOptions) @@ -67,10 +67,10 @@ func (c *MgmtClient) retryOnError(r *http.Response) bool { return false } -// GetAdmin performs the GET /mgmt/admin/{id} request to the CA. -func (c *MgmtClient) GetAdmin(id string) (*mgmt.Admin, error) { +// GetAdmin performs the GET /admin/admin/{id} request to the CA. +func (c *AdminClient) GetAdmin(id string) (*mgmt.Admin, error) { var retried bool - u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/mgmt/admin", id)}) + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/admin/admin", id)}) retry: resp, err := c.client.Get(u.String()) if err != nil { @@ -81,7 +81,7 @@ retry: retried = true goto retry } - return nil, readMgmtError(resp.Body) + return nil, readAdminError(resp.Body) } var adm = new(mgmt.Admin) if err := readJSON(resp.Body, adm); err != nil { @@ -135,7 +135,7 @@ func WithAdminLimit(limit int) AdminOption { } // GetAdmins performs the GET /admin/admins request to the CA. -func (c *MgmtClient) GetAdmins(opts ...AdminOption) (*mgmtAPI.GetAdminsResponse, error) { +func (c *AdminClient) GetAdmins(opts ...AdminOption) (*mgmtAPI.GetAdminsResponse, error) { var retried bool o := new(adminOptions) if err := o.apply(opts); err != nil { @@ -155,7 +155,7 @@ retry: retried = true goto retry } - return nil, readMgmtError(resp.Body) + return nil, readAdminError(resp.Body) } var body = new(mgmtAPI.GetAdminsResponse) if err := readJSON(resp.Body, body); err != nil { @@ -164,14 +164,14 @@ retry: return body, nil } -// CreateAdmin performs the POST /admin/admin request to the CA. -func (c *MgmtClient) CreateAdmin(req *mgmtAPI.CreateAdminRequest) (*mgmt.Admin, error) { +// CreateAdmin performs the POST /admin/admins request to the CA. +func (c *AdminClient) CreateAdmin(req *mgmtAPI.CreateAdminRequest) (*mgmt.Admin, error) { var retried bool body, err := json.Marshal(req) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request") } - u := c.endpoint.ResolveReference(&url.URL{Path: "/admin/admin"}) + u := c.endpoint.ResolveReference(&url.URL{Path: "/admin/admins"}) retry: resp, err := c.client.Post(u.String(), "application/json", bytes.NewReader(body)) if err != nil { @@ -182,7 +182,7 @@ retry: retried = true goto retry } - return nil, readMgmtError(resp.Body) + return nil, readAdminError(resp.Body) } var adm = new(mgmt.Admin) if err := readJSON(resp.Body, adm); err != nil { @@ -191,10 +191,10 @@ retry: return adm, nil } -// RemoveAdmin performs the DELETE /admin/admin/{id} request to the CA. -func (c *MgmtClient) RemoveAdmin(id string) error { +// 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/admin", id)}) + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/admin/admins", id)}) req, err := http.NewRequest("DELETE", u.String(), nil) if err != nil { return errors.Wrapf(err, "create DELETE %s request failed", u) @@ -209,19 +209,19 @@ retry: retried = true goto retry } - return readMgmtError(resp.Body) + return readAdminError(resp.Body) } return nil } -// UpdateAdmin performs the PUT /admin/admin/{id} request to the CA. -func (c *MgmtClient) UpdateAdmin(id string, uar *mgmtAPI.UpdateAdminRequest) (*admin.Admin, error) { +// UpdateAdmin performs the PUT /admin/admins/{id} request to the CA. +func (c *AdminClient) UpdateAdmin(id string, uar *mgmtAPI.UpdateAdminRequest) (*admin.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/admin", id)}) + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/admin/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) @@ -236,7 +236,7 @@ retry: retried = true goto retry } - return nil, readMgmtError(resp.Body) + return nil, readAdminError(resp.Body) } var adm = new(admin.Admin) if err := readJSON(resp.Body, adm); err != nil { @@ -245,10 +245,10 @@ retry: return adm, nil } -// GetProvisioner performs the GET /admin/provisioner/{name} request to the CA. -func (c *MgmtClient) GetProvisioner(name string) (*mgmt.Provisioner, error) { +// GetProvisioner performs the GET /admin/provisioners/{name} request to the CA. +func (c *AdminClient) GetProvisioner(name string) (*mgmt.Provisioner, error) { var retried bool - u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/admin/provisioner", name)}) + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/admin/provisioners", name)}) retry: resp, err := c.client.Get(u.String()) if err != nil { @@ -259,7 +259,7 @@ retry: retried = true goto retry } - return nil, readMgmtError(resp.Body) + return nil, readAdminError(resp.Body) } var prov = new(mgmt.Provisioner) if err := readJSON(resp.Body, prov); err != nil { @@ -269,7 +269,7 @@ retry: } // GetProvisioners performs the GET /admin/provisioners request to the CA. -func (c *MgmtClient) GetProvisioners() ([]*mgmt.Provisioner, error) { +func (c *AdminClient) GetProvisioners() ([]*mgmt.Provisioner, error) { var retried bool u := c.endpoint.ResolveReference(&url.URL{Path: "/admin/provisioners"}) retry: @@ -282,7 +282,7 @@ retry: retried = true goto retry } - return nil, readMgmtError(resp.Body) + return nil, readAdminError(resp.Body) } var provs = new([]*mgmt.Provisioner) if err := readJSON(resp.Body, provs); err != nil { @@ -291,10 +291,10 @@ retry: return *provs, nil } -// RemoveProvisioner performs the DELETE /admin/provisioner/{name} request to the CA. -func (c *MgmtClient) RemoveProvisioner(name string) error { +// 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/provisioner", name)}) + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/admin/provisioners", name)}) req, err := http.NewRequest("DELETE", u.String(), nil) if err != nil { return errors.Wrapf(err, "create DELETE %s request failed", u) @@ -309,19 +309,19 @@ retry: retried = true goto retry } - return readMgmtError(resp.Body) + return readAdminError(resp.Body) } return nil } -// CreateProvisioner performs the POST /admin/provisioner request to the CA. -func (c *MgmtClient) CreateProvisioner(req *mgmtAPI.CreateProvisionerRequest) (*mgmt.Provisioner, error) { +// CreateProvisioner performs the POST /admin/provisioners request to the CA. +func (c *AdminClient) CreateProvisioner(req *mgmtAPI.CreateProvisionerRequest) (*mgmt.Provisioner, error) { var retried bool body, err := json.Marshal(req) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request") } - u := c.endpoint.ResolveReference(&url.URL{Path: "/admin/provisioner"}) + u := c.endpoint.ResolveReference(&url.URL{Path: "/admin/provisioners"}) retry: resp, err := c.client.Post(u.String(), "application/json", bytes.NewReader(body)) if err != nil { @@ -332,7 +332,7 @@ retry: retried = true goto retry } - return nil, readMgmtError(resp.Body) + return nil, readAdminError(resp.Body) } var prov = new(mgmt.Provisioner) if err := readJSON(resp.Body, prov); err != nil { @@ -341,14 +341,14 @@ retry: return prov, nil } -// UpdateProvisioner performs the PUT /admin/provisioner/{id} request to the CA. -func (c *MgmtClient) UpdateProvisioner(id string, upr *mgmtAPI.UpdateProvisionerRequest) (*mgmt.Provisioner, error) { +// UpdateProvisioner performs the PUT /admin/provisioners/{id} request to the CA. +func (c *AdminClient) UpdateProvisioner(id string, upr *mgmtAPI.UpdateProvisionerRequest) (*mgmt.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/provisioner", id)}) + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join("/admin/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) @@ -363,7 +363,7 @@ retry: retried = true goto retry } - return nil, readMgmtError(resp.Body) + return nil, readAdminError(resp.Body) } var prov = new(mgmt.Provisioner) if err := readJSON(resp.Body, prov); err != nil { @@ -373,7 +373,7 @@ retry: } // GetAuthConfig performs the GET /admin/authconfig/{id} request to the CA. -func (c *MgmtClient) GetAuthConfig(id string) (*mgmt.AuthConfig, error) { +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: @@ -386,7 +386,7 @@ retry: retried = true goto retry } - return nil, readMgmtError(resp.Body) + return nil, readAdminError(resp.Body) } var ac = new(mgmt.AuthConfig) if err := readJSON(resp.Body, ac); err != nil { @@ -395,7 +395,7 @@ retry: return ac, nil } -func readMgmtError(r io.ReadCloser) error { +func readAdminError(r io.ReadCloser) error { defer r.Close() mgmtErr := new(mgmt.Error) if err := json.NewDecoder(r).Decode(mgmtErr); err != nil { From 9bfb1c2e7b64ebab8bfd1b2f6fc8a95a646a9585 Mon Sep 17 00:00:00 2001 From: max furman Date: Fri, 21 May 2021 13:31:41 -0700 Subject: [PATCH 12/89] wip --- authority/admin/collection.go | 42 ++++----- authority/mgmt/api/admin.go | 4 +- authority/mgmt/api/provisioner.go | 46 +++------- authority/mgmt/provisioner.go | 139 +++++++++++++++++++++--------- ca/adminClient.go | 10 +-- 5 files changed, 142 insertions(+), 99 deletions(-) diff --git a/authority/admin/collection.go b/authority/admin/collection.go index 9e8a2926..971bbd9c 100644 --- a/authority/admin/collection.go +++ b/authority/admin/collection.go @@ -32,24 +32,24 @@ func (p adminSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } // Collection is a memory map of admins. type Collection struct { - byID *sync.Map - bySubProv *sync.Map - byProv *sync.Map - sorted adminSlice - provisioners *provisioner.Collection - count int - countByProvisioner map[string]int + byID *sync.Map + bySubProv *sync.Map + byProv *sync.Map + sorted adminSlice + provisioners *provisioner.Collection + superCount int + superCountByProvisioner map[string]int } // NewCollection initializes a collection of provisioners. The given list of // audiences are the audiences used by the JWT provisioner. func NewCollection(provisioners *provisioner.Collection) *Collection { return &Collection{ - byID: new(sync.Map), - byProv: new(sync.Map), - bySubProv: new(sync.Map), - countByProvisioner: map[string]int{}, - provisioners: provisioners, + byID: new(sync.Map), + byProv: new(sync.Map), + bySubProv: new(sync.Map), + superCountByProvisioner: map[string]int{}, + provisioners: provisioners, } } @@ -106,12 +106,12 @@ func (c *Collection) Store(adm *Admin) error { if admins, ok := c.LoadByProvisioner(provName); ok { c.byProv.Store(provName, append(admins, adm)) - c.countByProvisioner[provName]++ + c.superCountByProvisioner[provName]++ } else { c.byProv.Store(provName, []*Admin{adm}) - c.countByProvisioner[provName] = 1 + c.superCountByProvisioner[provName] = 1 } - c.count++ + c.superCount++ // Store sorted admins. // Use the first 4 bytes (32bit) of the sum to insert the order @@ -131,14 +131,14 @@ func (c *Collection) Store(adm *Admin) error { return nil } -// Count returns the total number of admins. -func (c *Collection) Count() int { - return c.count +// SuperCount returns the total number of admins. +func (c *Collection) SuperCount() int { + return c.superCount } -// CountByProvisioner returns the total number of admins. -func (c *Collection) CountByProvisioner(provName string) int { - if cnt, ok := c.countByProvisioner[provName]; ok { +// SuperCountByProvisioner returns the total number of admins. +func (c *Collection) SuperCountByProvisioner(provName string) int { + if cnt, ok := c.superCountByProvisioner[provName]; ok { return cnt } return 0 diff --git a/authority/mgmt/api/admin.go b/authority/mgmt/api/admin.go index b474e131..04ff2a93 100644 --- a/authority/mgmt/api/admin.go +++ b/authority/mgmt/api/admin.go @@ -120,8 +120,8 @@ func (h *Handler) CreateAdmin(w http.ResponseWriter, r *http.Request) { func (h *Handler) DeleteAdmin(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") - if h.auth.GetAdminCollection().Count() == 1 { - api.WriteError(w, mgmt.NewError(mgmt.ErrorBadRequestType, "cannot remove last admin")) + if h.auth.GetAdminCollection().SuperCount() == 1 { + api.WriteError(w, mgmt.NewError(mgmt.ErrorBadRequestType, "cannot remove the last super admin")) return } diff --git a/authority/mgmt/api/provisioner.go b/authority/mgmt/api/provisioner.go index d383a27f..3697d6e0 100644 --- a/authority/mgmt/api/provisioner.go +++ b/authority/mgmt/api/provisioner.go @@ -101,34 +101,17 @@ func (h *Handler) GetProvisioners(w http.ResponseWriter, r *http.Request) { func (h *Handler) CreateProvisioner(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - var body CreateProvisionerRequest - if err := api.ReadJSON(r.Body, &body); err != nil { - api.WriteError(w, err) - return - } - if err := body.Validate(h.auth.GetProvisionerCollection()); err != nil { + var prov = new(mgmt.Provisioner) + if err := api.ReadJSON(r.Body, prov); err != nil { api.WriteError(w, err) return } - details, err := mgmt.UnmarshalProvisionerDetails(body.Details) - if err != nil { - api.WriteError(w, mgmt.WrapErrorISE(err, "error unmarshaling provisioner details")) - return - } + // TODO: validate - claims := mgmt.NewDefaultClaims() + // TODO: fix this + prov.Claims = mgmt.NewDefaultClaims() - prov := &mgmt.Provisioner{ - Type: body.Type, - Name: body.Name, - Claims: claims, - Details: details, - X509Template: body.X509Template, - X509TemplateData: body.X509TemplateData, - SSHTemplate: body.SSHTemplate, - SSHTemplateData: body.SSHTemplateData, - } if err := h.db.CreateProvisioner(ctx, prov); err != nil { api.WriteError(w, err) return @@ -144,21 +127,20 @@ func (h *Handler) CreateProvisioner(w http.ResponseWriter, r *http.Request) { func (h *Handler) DeleteProvisioner(w http.ResponseWriter, r *http.Request) { name := chi.URLParam(r, "name") - c := h.auth.GetAdminCollection() - fmt.Printf("c.Count() = %+v\n", c.Count()) - fmt.Printf("c.CountByProvisioner() = %+v\n", c.CountByProvisioner(name)) - if c.Count() == c.CountByProvisioner(name) { - api.WriteError(w, mgmt.NewError(mgmt.ErrorBadRequestType, - "cannot remove provisioner %s because no admins will remain", name)) - return - } - - ctx := r.Context() p, ok := h.auth.GetProvisionerCollection().LoadByName(name) if !ok { api.WriteError(w, mgmt.NewError(mgmt.ErrorNotFoundType, "provisioner %s not found", name)) return } + + c := h.auth.GetAdminCollection() + if c.SuperCount() == c.SuperCountByProvisioner(name) { + api.WriteError(w, mgmt.NewError(mgmt.ErrorBadRequestType, + "cannot remove provisioner %s because no super admins will remain", name)) + return + } + + 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)) diff --git a/authority/mgmt/provisioner.go b/authority/mgmt/provisioner.go index 537b7fdd..493f74b1 100644 --- a/authority/mgmt/provisioner.go +++ b/authority/mgmt/provisioner.go @@ -12,15 +12,6 @@ import ( type ProvisionerOption func(*ProvisionerCtx) -type ProvisionerCtx struct { - JWK *jose.JSONWebKey - JWE *jose.JSONWebEncryption - X509Template, SSHTemplate string - X509TemplateData, SSHTemplateData []byte - Claims *Claims - Password string -} - type ProvisionerType string var ( @@ -35,29 +26,6 @@ var ( ProvisionerTypeX5C = ProvisionerType("X5C") ) -func NewProvisionerCtx(opts ...ProvisionerOption) *ProvisionerCtx { - pc := &ProvisionerCtx{ - Claims: NewDefaultClaims(), - } - for _, o := range opts { - o(pc) - } - return pc -} - -func WithJWK(jwk *jose.JSONWebKey, jwe *jose.JSONWebEncryption) func(*ProvisionerCtx) { - return func(ctx *ProvisionerCtx) { - ctx.JWK = jwk - ctx.JWE = jwe - } -} - -func WithPassword(pass string) func(*ProvisionerCtx) { - return func(ctx *ProvisionerCtx) { - ctx.Password = pass - } -} - type unmarshalProvisioner struct { ID string `json:"-"` AuthorityID string `json:"-"` @@ -157,6 +125,38 @@ func CreateProvisioner(ctx context.Context, db DB, typ, name string, opts ...Pro return p, nil } +type ProvisionerCtx struct { + JWK *jose.JSONWebKey + JWE *jose.JSONWebEncryption + X509Template, SSHTemplate string + X509TemplateData, SSHTemplateData []byte + Claims *Claims + Password string +} + +func NewProvisionerCtx(opts ...ProvisionerOption) *ProvisionerCtx { + pc := &ProvisionerCtx{ + Claims: NewDefaultClaims(), + } + for _, o := range opts { + o(pc) + } + return pc +} + +func WithJWK(jwk *jose.JSONWebKey, jwe *jose.JSONWebEncryption) func(*ProvisionerCtx) { + return func(ctx *ProvisionerCtx) { + ctx.JWK = jwk + ctx.JWE = jwe + } +} + +func WithPassword(pass string) func(*ProvisionerCtx) { + return func(ctx *ProvisionerCtx) { + ctx.Password = pass + } +} + // ProvisionerDetails is the interface implemented by all provisioner details // attributes. type ProvisionerDetails interface { @@ -172,37 +172,61 @@ type ProvisionerDetailsJWK struct { // ProvisionerDetailsOIDC represents the values required by a OIDC provisioner. type ProvisionerDetailsOIDC struct { - Type ProvisionerType `json:"type"` + Type ProvisionerType `json:"type"` + ClientID string `json:"clientID"` + ClientSecret string `json:"clientSecret"` + ConfigurationEndpoint string `json:"configurationEndpoint"` + Admins []string `json:"admins"` + Domains []string `json:"domains"` + Groups []string `json:"groups"` + ListenAddress string `json:"listenAddress"` + TenantID string `json:"tenantID"` } // ProvisionerDetailsGCP represents the values required by a GCP provisioner. type ProvisionerDetailsGCP struct { - Type ProvisionerType `json:"type"` + Type ProvisionerType `json:"type"` + ServiceAccounts []string `json:"serviceAccounts"` + ProjectIDs []string `json:"projectIDs"` + DisableCustomSANs bool `json:"disableCustomSANs"` + DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` + InstanceAge string `json:"instanceAge"` } // ProvisionerDetailsAWS represents the values required by a AWS provisioner. type ProvisionerDetailsAWS struct { - Type ProvisionerType `json:"type"` + Type ProvisionerType `json:"type"` + Accounts []string `json:"accounts"` + DisableCustomSANs bool `json:"disableCustomSANs"` + DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` + InstanceAge string `json:"instanceAge"` } // ProvisionerDetailsAzure represents the values required by a Azure provisioner. type ProvisionerDetailsAzure struct { - Type ProvisionerType `json:"type"` + Type ProvisionerType `json:"type"` + ResourceGroups []string `json:"resourceGroups"` + Audience string `json:"audience"` + DisableCustomSANs bool `json:"disableCustomSANs"` + DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` } // ProvisionerDetailsACME represents the values required by a ACME provisioner. type ProvisionerDetailsACME struct { - Type ProvisionerType `json:"type"` + Type ProvisionerType `json:"type"` + ForceCN bool `json:"forceCN"` } // ProvisionerDetailsX5C represents the values required by a X5C provisioner. type ProvisionerDetailsX5C struct { - Type ProvisionerType `json:"type"` + Type ProvisionerType `json:"type"` + Roots []byte `json:"roots"` } // ProvisionerDetailsK8SSA represents the values required by a K8SSA provisioner. type ProvisionerDetailsK8SSA struct { - Type ProvisionerType `json:"type"` + Type ProvisionerType `json:"type"` + PublicKeys []byte `json:"publicKeys"` } // ProvisionerDetailsSSHPOP represents the values required by a SSHPOP provisioner. @@ -285,6 +309,42 @@ func createJWKDetails(pc *ProvisionerCtx) (*ProvisionerDetailsJWK, error) { }, nil } +func createACMEDetails(pc *ProvisionerCtx) (*ProvisionerDetailsJWK, error) { + var err error + + if pc.JWK != nil && pc.JWE == nil { + return nil, NewErrorISE("JWE is required with JWK for createJWKProvisioner") + } + if pc.JWE != nil && pc.JWK == nil { + return nil, NewErrorISE("JWK is required with JWE for createJWKProvisioner") + } + if pc.JWK == nil && pc.JWE == nil { + // Create a new JWK w/ encrypted private key. + if pc.Password == "" { + return nil, NewErrorISE("password is required to provisioner with new keys") + } + pc.JWK, pc.JWE, err = jose.GenerateDefaultKeyPair([]byte(pc.Password)) + if err != nil { + return nil, WrapErrorISE(err, "error generating JWK key pair") + } + } + + jwkPubBytes, err := pc.JWK.MarshalJSON() + if err != nil { + return nil, WrapErrorISE(err, "error marshaling JWK") + } + jwePrivStr, err := pc.JWE.CompactSerialize() + if err != nil { + return nil, WrapErrorISE(err, "error serializing JWE") + } + + return &ProvisionerDetailsJWK{ + Type: ProvisionerTypeJWK, + PublicKey: jwkPubBytes, + PrivateKey: jwePrivStr, + }, nil +} + // ToCertificates converts the landlord provisioner type to the open source // provisioner type. func (p *Provisioner) ToCertificates() (provisioner.Interface, error) { @@ -461,6 +521,7 @@ type detailsType struct { Type ProvisionerType } +// UnmarshalProvisionerDetails unmarshals bytes into the proper details type. func UnmarshalProvisionerDetails(data json.RawMessage) (ProvisionerDetails, error) { dt := new(detailsType) if err := json.Unmarshal(data, dt); err != nil { diff --git a/ca/adminClient.go b/ca/adminClient.go index 5fc6a7d4..e8374a98 100644 --- a/ca/adminClient.go +++ b/ca/adminClient.go @@ -315,9 +315,9 @@ retry: } // CreateProvisioner performs the POST /admin/provisioners request to the CA. -func (c *AdminClient) CreateProvisioner(req *mgmtAPI.CreateProvisionerRequest) (*mgmt.Provisioner, error) { +func (c *AdminClient) CreateProvisioner(prov *mgmt.Provisioner) (*mgmt.Provisioner, error) { var retried bool - body, err := json.Marshal(req) + body, err := json.Marshal(prov) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request") } @@ -334,11 +334,11 @@ retry: } return nil, readAdminError(resp.Body) } - var prov = new(mgmt.Provisioner) - if err := readJSON(resp.Body, prov); err != nil { + var nuProv = new(mgmt.Provisioner) + if err := readJSON(resp.Body, nuProv); err != nil { return nil, errors.Wrapf(err, "error reading %s", u) } - return prov, nil + return nuProv, nil } // UpdateProvisioner performs the PUT /admin/provisioners/{id} request to the CA. From 64ce4e5c91e19bb1e2aeb151d21a8646d4be0e05 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 24 May 2021 12:14:10 -0700 Subject: [PATCH 13/89] Add and generate majordomo protos. --- majordomo/doc.go | 3 + majordomo/majordomo.pb.go | 1094 +++++++++++++++++++ majordomo/majordomo.proto | 102 ++ majordomo/majordomo_grpc.pb.go | 519 +++++++++ majordomo/provisioners.pb.go | 1875 ++++++++++++++++++++++++++++++++ majordomo/provisioners.proto | 137 +++ 6 files changed, 3730 insertions(+) create mode 100644 majordomo/doc.go create mode 100644 majordomo/majordomo.pb.go create mode 100644 majordomo/majordomo.proto create mode 100644 majordomo/majordomo_grpc.pb.go create mode 100644 majordomo/provisioners.pb.go create mode 100644 majordomo/provisioners.proto diff --git a/majordomo/doc.go b/majordomo/doc.go new file mode 100644 index 00000000..ef9fce3a --- /dev/null +++ b/majordomo/doc.go @@ -0,0 +1,3 @@ +package majordomo + +//go:generate protoc --proto_path=.. --go_out=.. --go-grpc_out=.. --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative majordomo/provisioners.proto majordomo/majordomo.proto diff --git a/majordomo/majordomo.pb.go b/majordomo/majordomo.pb.go new file mode 100644 index 00000000..bce189df --- /dev/null +++ b/majordomo/majordomo.pb.go @@ -0,0 +1,1094 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.15.8 +// source: majordomo/majordomo.proto + +package majordomo + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type TODO struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *TODO) Reset() { + *x = TODO{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_majordomo_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TODO) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TODO) ProtoMessage() {} + +func (x *TODO) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_majordomo_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TODO.ProtoReflect.Descriptor instead. +func (*TODO) Descriptor() ([]byte, []int) { + return file_majordomo_majordomo_proto_rawDescGZIP(), []int{0} +} + +type LoginRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AuthorityId string `protobuf:"bytes,1,opt,name=authority_id,json=authorityId,proto3" json:"authority_id,omitempty"` + Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` + Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` + PemCertificateRequest string `protobuf:"bytes,4,opt,name=pem_certificate_request,json=pemCertificateRequest,proto3" json:"pem_certificate_request,omitempty"` +} + +func (x *LoginRequest) Reset() { + *x = LoginRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_majordomo_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LoginRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginRequest) ProtoMessage() {} + +func (x *LoginRequest) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_majordomo_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead. +func (*LoginRequest) Descriptor() ([]byte, []int) { + return file_majordomo_majordomo_proto_rawDescGZIP(), []int{1} +} + +func (x *LoginRequest) GetAuthorityId() string { + if x != nil { + return x.AuthorityId + } + return "" +} + +func (x *LoginRequest) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *LoginRequest) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *LoginRequest) GetPemCertificateRequest() string { + if x != nil { + return x.PemCertificateRequest + } + return "" +} + +type LoginResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PemCertificate string `protobuf:"bytes,1,opt,name=pem_certificate,json=pemCertificate,proto3" json:"pem_certificate,omitempty"` + PemCertificateChain string `protobuf:"bytes,2,opt,name=pem_certificate_chain,json=pemCertificateChain,proto3" json:"pem_certificate_chain,omitempty"` +} + +func (x *LoginResponse) Reset() { + *x = LoginResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_majordomo_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LoginResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginResponse) ProtoMessage() {} + +func (x *LoginResponse) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_majordomo_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LoginResponse.ProtoReflect.Descriptor instead. +func (*LoginResponse) Descriptor() ([]byte, []int) { + return file_majordomo_majordomo_proto_rawDescGZIP(), []int{2} +} + +func (x *LoginResponse) GetPemCertificate() string { + if x != nil { + return x.PemCertificate + } + return "" +} + +func (x *LoginResponse) GetPemCertificateChain() string { + if x != nil { + return x.PemCertificateChain + } + return "" +} + +type ConfigurationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ConfigurationRequest) Reset() { + *x = ConfigurationRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_majordomo_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConfigurationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfigurationRequest) ProtoMessage() {} + +func (x *ConfigurationRequest) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_majordomo_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConfigurationRequest.ProtoReflect.Descriptor instead. +func (*ConfigurationRequest) Descriptor() ([]byte, []int) { + return file_majordomo_majordomo_proto_rawDescGZIP(), []int{3} +} + +type ConfigurationResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Provisioners []*Provisioner `protobuf:"bytes,1,rep,name=provisioners,proto3" json:"provisioners,omitempty"` + Admins []*Administrator `protobuf:"bytes,2,rep,name=admins,proto3" json:"admins,omitempty"` +} + +func (x *ConfigurationResponse) Reset() { + *x = ConfigurationResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_majordomo_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConfigurationResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfigurationResponse) ProtoMessage() {} + +func (x *ConfigurationResponse) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_majordomo_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConfigurationResponse.ProtoReflect.Descriptor instead. +func (*ConfigurationResponse) Descriptor() ([]byte, []int) { + return file_majordomo_majordomo_proto_rawDescGZIP(), []int{4} +} + +func (x *ConfigurationResponse) GetProvisioners() []*Provisioner { + if x != nil { + return x.Provisioners + } + return nil +} + +func (x *ConfigurationResponse) GetAdmins() []*Administrator { + if x != nil { + return x.Admins + } + return nil +} + +type CreateProvisionerRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type Provisioner_Type `protobuf:"varint,1,opt,name=type,proto3,enum=majordomo.Provisioner_Type" json:"type,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Details *ProvisionerDetails `protobuf:"bytes,3,opt,name=details,proto3" json:"details,omitempty"` + Claims *Claims `protobuf:"bytes,4,opt,name=claims,proto3" json:"claims,omitempty"` +} + +func (x *CreateProvisionerRequest) Reset() { + *x = CreateProvisionerRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_majordomo_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateProvisionerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateProvisionerRequest) ProtoMessage() {} + +func (x *CreateProvisionerRequest) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_majordomo_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateProvisionerRequest.ProtoReflect.Descriptor instead. +func (*CreateProvisionerRequest) Descriptor() ([]byte, []int) { + return file_majordomo_majordomo_proto_rawDescGZIP(), []int{5} +} + +func (x *CreateProvisionerRequest) GetType() Provisioner_Type { + if x != nil { + return x.Type + } + return Provisioner_NOOP +} + +func (x *CreateProvisionerRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateProvisionerRequest) GetDetails() *ProvisionerDetails { + if x != nil { + return x.Details + } + return nil +} + +func (x *CreateProvisionerRequest) GetClaims() *Claims { + if x != nil { + return x.Claims + } + return nil +} + +type DeleteProvisionerRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *DeleteProvisionerRequest) Reset() { + *x = DeleteProvisionerRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_majordomo_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteProvisionerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteProvisionerRequest) ProtoMessage() {} + +func (x *DeleteProvisionerRequest) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_majordomo_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteProvisionerRequest.ProtoReflect.Descriptor instead. +func (*DeleteProvisionerRequest) Descriptor() ([]byte, []int) { + return file_majordomo_majordomo_proto_rawDescGZIP(), []int{6} +} + +func (x *DeleteProvisionerRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type CreateAdministratorRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + ProvisionerId string `protobuf:"bytes,2,opt,name=provisioner_id,json=provisionerId,proto3" json:"provisioner_id,omitempty"` + Type Administrator_Type `protobuf:"varint,3,opt,name=type,proto3,enum=majordomo.Administrator_Type" json:"type,omitempty"` +} + +func (x *CreateAdministratorRequest) Reset() { + *x = CreateAdministratorRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_majordomo_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateAdministratorRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateAdministratorRequest) ProtoMessage() {} + +func (x *CreateAdministratorRequest) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_majordomo_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateAdministratorRequest.ProtoReflect.Descriptor instead. +func (*CreateAdministratorRequest) Descriptor() ([]byte, []int) { + return file_majordomo_majordomo_proto_rawDescGZIP(), []int{7} +} + +func (x *CreateAdministratorRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateAdministratorRequest) GetProvisionerId() string { + if x != nil { + return x.ProvisionerId + } + return "" +} + +func (x *CreateAdministratorRequest) GetType() Administrator_Type { + if x != nil { + return x.Type + } + return Administrator_UNKNOWN +} + +type DeleteAdministratorRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *DeleteAdministratorRequest) Reset() { + *x = DeleteAdministratorRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_majordomo_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteAdministratorRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteAdministratorRequest) ProtoMessage() {} + +func (x *DeleteAdministratorRequest) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_majordomo_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteAdministratorRequest.ProtoReflect.Descriptor instead. +func (*DeleteAdministratorRequest) Descriptor() ([]byte, []int) { + return file_majordomo_majordomo_proto_rawDescGZIP(), []int{8} +} + +func (x *DeleteAdministratorRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type CertificateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PemCertificate string `protobuf:"bytes,1,opt,name=pem_certificate,json=pemCertificate,proto3" json:"pem_certificate,omitempty"` + PemCertificateChain string `protobuf:"bytes,2,opt,name=pem_certificate_chain,json=pemCertificateChain,proto3" json:"pem_certificate_chain,omitempty"` +} + +func (x *CertificateRequest) Reset() { + *x = CertificateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_majordomo_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CertificateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CertificateRequest) ProtoMessage() {} + +func (x *CertificateRequest) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_majordomo_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CertificateRequest.ProtoReflect.Descriptor instead. +func (*CertificateRequest) Descriptor() ([]byte, []int) { + return file_majordomo_majordomo_proto_rawDescGZIP(), []int{9} +} + +func (x *CertificateRequest) GetPemCertificate() string { + if x != nil { + return x.PemCertificate + } + return "" +} + +func (x *CertificateRequest) GetPemCertificateChain() string { + if x != nil { + return x.PemCertificateChain + } + return "" +} + +type CertificateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *CertificateResponse) Reset() { + *x = CertificateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_majordomo_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CertificateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CertificateResponse) ProtoMessage() {} + +func (x *CertificateResponse) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_majordomo_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CertificateResponse.ProtoReflect.Descriptor instead. +func (*CertificateResponse) Descriptor() ([]byte, []int) { + return file_majordomo_majordomo_proto_rawDescGZIP(), []int{10} +} + +func (x *CertificateResponse) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type SSHCertificateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Certificate string `protobuf:"bytes,1,opt,name=certificate,proto3" json:"certificate,omitempty"` +} + +func (x *SSHCertificateRequest) Reset() { + *x = SSHCertificateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_majordomo_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SSHCertificateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SSHCertificateRequest) ProtoMessage() {} + +func (x *SSHCertificateRequest) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_majordomo_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SSHCertificateRequest.ProtoReflect.Descriptor instead. +func (*SSHCertificateRequest) Descriptor() ([]byte, []int) { + return file_majordomo_majordomo_proto_rawDescGZIP(), []int{11} +} + +func (x *SSHCertificateRequest) GetCertificate() string { + if x != nil { + return x.Certificate + } + return "" +} + +type SSHCertificateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *SSHCertificateResponse) Reset() { + *x = SSHCertificateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_majordomo_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SSHCertificateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SSHCertificateResponse) ProtoMessage() {} + +func (x *SSHCertificateResponse) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_majordomo_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SSHCertificateResponse.ProtoReflect.Descriptor instead. +func (*SSHCertificateResponse) Descriptor() ([]byte, []int) { + return file_majordomo_majordomo_proto_rawDescGZIP(), []int{12} +} + +func (x *SSHCertificateResponse) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +var File_majordomo_majordomo_proto protoreflect.FileDescriptor + +var file_majordomo_majordomo_proto_rawDesc = []byte{ + 0x0a, 0x19, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2f, 0x6d, 0x61, 0x6a, 0x6f, + 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x6d, 0x61, 0x6a, + 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x1a, 0x1c, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, + 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x06, 0x0a, 0x04, 0x54, 0x4f, 0x44, 0x4f, 0x22, 0xa1, 0x01, 0x0a, + 0x0c, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, + 0x0c, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x49, 0x64, + 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, + 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x36, 0x0a, 0x17, 0x70, 0x65, 0x6d, 0x5f, + 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x70, 0x65, 0x6d, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x22, 0x6c, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x65, 0x6d, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x65, 0x6d, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x65, + 0x6d, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x65, 0x6d, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x22, 0x16, + 0x0a, 0x14, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x85, 0x01, 0x0a, 0x15, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x3a, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, + 0x6d, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x52, 0x0c, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x30, 0x0a, 0x06, + 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, + 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, + 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x06, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x73, 0x22, 0xc3, + 0x01, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, + 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x37, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1d, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x50, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, + 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x29, 0x0a, 0x06, 0x63, 0x6c, 0x61, + 0x69, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, + 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x06, 0x63, 0x6c, + 0x61, 0x69, 0x6d, 0x73, 0x22, 0x2a, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, + 0x22, 0x8a, 0x01, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x31, 0x0a, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, + 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, + 0x6f, 0x72, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2c, 0x0a, + 0x1a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x71, 0x0a, 0x12, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x65, 0x6d, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x65, 0x6d, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x65, + 0x6d, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x65, 0x6d, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x22, 0x25, + 0x0a, 0x13, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x39, 0x0a, 0x15, 0x53, 0x53, 0x48, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, + 0x0a, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, + 0x22, 0x28, 0x0a, 0x16, 0x53, 0x53, 0x48, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x32, 0xec, 0x06, 0x0a, 0x09, 0x4d, + 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x12, 0x3a, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, + 0x6e, 0x12, 0x17, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x4c, 0x6f, + 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x6a, + 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, + 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, + 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x13, 0x53, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x1f, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x50, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x23, 0x2e, 0x6d, + 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x16, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x50, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x50, 0x0a, 0x11, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x23, + 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, + 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x56, 0x0a, 0x13, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, + 0x6f, 0x72, 0x12, 0x25, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, + 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, + 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, + 0x74, 0x6f, 0x72, 0x12, 0x56, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x64, 0x6d, + 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x25, 0x2e, 0x6d, 0x61, 0x6a, + 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x64, 0x6d, + 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x41, 0x64, + 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x50, 0x0a, 0x0f, 0x50, + 0x6f, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x1d, + 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, + 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, + 0x12, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x53, 0x48, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x12, 0x20, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, + 0x53, 0x53, 0x48, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, + 0x6f, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x11, 0x52, 0x65, 0x76, 0x6f, + 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x0f, 0x2e, + 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x54, 0x4f, 0x44, 0x4f, 0x1a, 0x0f, + 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x54, 0x4f, 0x44, 0x4f, 0x12, + 0x38, 0x0a, 0x14, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x53, 0x48, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x0f, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, + 0x6f, 0x6d, 0x6f, 0x2e, 0x54, 0x4f, 0x44, 0x4f, 0x1a, 0x0f, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, + 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x54, 0x4f, 0x44, 0x4f, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x73, 0x74, 0x65, + 0x70, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2f, 0x6d, + 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_majordomo_majordomo_proto_rawDescOnce sync.Once + file_majordomo_majordomo_proto_rawDescData = file_majordomo_majordomo_proto_rawDesc +) + +func file_majordomo_majordomo_proto_rawDescGZIP() []byte { + file_majordomo_majordomo_proto_rawDescOnce.Do(func() { + file_majordomo_majordomo_proto_rawDescData = protoimpl.X.CompressGZIP(file_majordomo_majordomo_proto_rawDescData) + }) + return file_majordomo_majordomo_proto_rawDescData +} + +var file_majordomo_majordomo_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_majordomo_majordomo_proto_goTypes = []interface{}{ + (*TODO)(nil), // 0: majordomo.TODO + (*LoginRequest)(nil), // 1: majordomo.LoginRequest + (*LoginResponse)(nil), // 2: majordomo.LoginResponse + (*ConfigurationRequest)(nil), // 3: majordomo.ConfigurationRequest + (*ConfigurationResponse)(nil), // 4: majordomo.ConfigurationResponse + (*CreateProvisionerRequest)(nil), // 5: majordomo.CreateProvisionerRequest + (*DeleteProvisionerRequest)(nil), // 6: majordomo.DeleteProvisionerRequest + (*CreateAdministratorRequest)(nil), // 7: majordomo.CreateAdministratorRequest + (*DeleteAdministratorRequest)(nil), // 8: majordomo.DeleteAdministratorRequest + (*CertificateRequest)(nil), // 9: majordomo.CertificateRequest + (*CertificateResponse)(nil), // 10: majordomo.CertificateResponse + (*SSHCertificateRequest)(nil), // 11: majordomo.SSHCertificateRequest + (*SSHCertificateResponse)(nil), // 12: majordomo.SSHCertificateResponse + (*Provisioner)(nil), // 13: majordomo.Provisioner + (*Administrator)(nil), // 14: majordomo.Administrator + (Provisioner_Type)(0), // 15: majordomo.Provisioner.Type + (*ProvisionerDetails)(nil), // 16: majordomo.ProvisionerDetails + (*Claims)(nil), // 17: majordomo.Claims + (Administrator_Type)(0), // 18: majordomo.Administrator.Type +} +var file_majordomo_majordomo_proto_depIdxs = []int32{ + 13, // 0: majordomo.ConfigurationResponse.provisioners:type_name -> majordomo.Provisioner + 14, // 1: majordomo.ConfigurationResponse.admins:type_name -> majordomo.Administrator + 15, // 2: majordomo.CreateProvisionerRequest.type:type_name -> majordomo.Provisioner.Type + 16, // 3: majordomo.CreateProvisionerRequest.details:type_name -> majordomo.ProvisionerDetails + 17, // 4: majordomo.CreateProvisionerRequest.claims:type_name -> majordomo.Claims + 18, // 5: majordomo.CreateAdministratorRequest.type:type_name -> majordomo.Administrator.Type + 1, // 6: majordomo.Majordomo.Login:input_type -> majordomo.LoginRequest + 3, // 7: majordomo.Majordomo.GetConfiguration:input_type -> majordomo.ConfigurationRequest + 3, // 8: majordomo.Majordomo.StreamConfiguration:input_type -> majordomo.ConfigurationRequest + 5, // 9: majordomo.Majordomo.CreateProvisioner:input_type -> majordomo.CreateProvisionerRequest + 6, // 10: majordomo.Majordomo.DeleteProvisioner:input_type -> majordomo.DeleteProvisionerRequest + 7, // 11: majordomo.Majordomo.CreateAdministrator:input_type -> majordomo.CreateAdministratorRequest + 8, // 12: majordomo.Majordomo.DeleteAdministrator:input_type -> majordomo.DeleteAdministratorRequest + 9, // 13: majordomo.Majordomo.PostCertificate:input_type -> majordomo.CertificateRequest + 11, // 14: majordomo.Majordomo.PostSSHCertificate:input_type -> majordomo.SSHCertificateRequest + 0, // 15: majordomo.Majordomo.RevokeCertificate:input_type -> majordomo.TODO + 0, // 16: majordomo.Majordomo.RevokeSSHCertificate:input_type -> majordomo.TODO + 2, // 17: majordomo.Majordomo.Login:output_type -> majordomo.LoginResponse + 4, // 18: majordomo.Majordomo.GetConfiguration:output_type -> majordomo.ConfigurationResponse + 4, // 19: majordomo.Majordomo.StreamConfiguration:output_type -> majordomo.ConfigurationResponse + 13, // 20: majordomo.Majordomo.CreateProvisioner:output_type -> majordomo.Provisioner + 13, // 21: majordomo.Majordomo.DeleteProvisioner:output_type -> majordomo.Provisioner + 14, // 22: majordomo.Majordomo.CreateAdministrator:output_type -> majordomo.Administrator + 14, // 23: majordomo.Majordomo.DeleteAdministrator:output_type -> majordomo.Administrator + 10, // 24: majordomo.Majordomo.PostCertificate:output_type -> majordomo.CertificateResponse + 12, // 25: majordomo.Majordomo.PostSSHCertificate:output_type -> majordomo.SSHCertificateResponse + 0, // 26: majordomo.Majordomo.RevokeCertificate:output_type -> majordomo.TODO + 0, // 27: majordomo.Majordomo.RevokeSSHCertificate:output_type -> majordomo.TODO + 17, // [17:28] is the sub-list for method output_type + 6, // [6:17] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_majordomo_majordomo_proto_init() } +func file_majordomo_majordomo_proto_init() { + if File_majordomo_majordomo_proto != nil { + return + } + file_majordomo_provisioners_proto_init() + if !protoimpl.UnsafeEnabled { + file_majordomo_majordomo_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TODO); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_majordomo_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LoginRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_majordomo_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LoginResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_majordomo_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConfigurationRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_majordomo_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConfigurationResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_majordomo_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateProvisionerRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_majordomo_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteProvisionerRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_majordomo_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateAdministratorRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_majordomo_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteAdministratorRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_majordomo_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CertificateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_majordomo_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CertificateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_majordomo_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SSHCertificateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_majordomo_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SSHCertificateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_majordomo_majordomo_proto_rawDesc, + NumEnums: 0, + NumMessages: 13, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_majordomo_majordomo_proto_goTypes, + DependencyIndexes: file_majordomo_majordomo_proto_depIdxs, + MessageInfos: file_majordomo_majordomo_proto_msgTypes, + }.Build() + File_majordomo_majordomo_proto = out.File + file_majordomo_majordomo_proto_rawDesc = nil + file_majordomo_majordomo_proto_goTypes = nil + file_majordomo_majordomo_proto_depIdxs = nil +} diff --git a/majordomo/majordomo.proto b/majordomo/majordomo.proto new file mode 100644 index 00000000..bfdf0047 --- /dev/null +++ b/majordomo/majordomo.proto @@ -0,0 +1,102 @@ +syntax = "proto3"; + +package majordomo; + +option go_package = "github.com/smallstep/certificates/majordomo"; + +import "majordomo/provisioners.proto"; + +// Majordomo is the public service used to sync configurations to CA's and post +// certificates. +service Majordomo { + // Login creates signs a given CSR and returns the certificate that will be + // used for authentication. + rpc Login(LoginRequest) returns (LoginResponse); + + // GetConfiguration returns the full configuration of an authority. + rpc GetConfiguration(ConfigurationRequest) returns (ConfigurationResponse); + // StreamConfiguration streams the full configuration of an authority. This + // method is not yet supported. + rpc StreamConfiguration(ConfigurationRequest) returns (stream ConfigurationResponse); + + // CreateProvisioner adds a new provisioner to the majordomo authority and + // returns the proto representation. + rpc CreateProvisioner(CreateProvisionerRequest) returns (Provisioner); + // DeleteProvisioner deletes a previously created provisioner. + rpc DeleteProvisioner(DeleteProvisionerRequest) returns (Provisioner); + + // CreateAdministrator adds a new admin user to the majordomo authority. + // Admin users can add or delete provisioners. + rpc CreateAdministrator(CreateAdministratorRequest) returns (Administrator); + // DeleteAdministrator deletes a previously created admin user. + rpc DeleteAdministrator(DeleteAdministratorRequest) returns (Administrator); + + // PostCertificate sends a signed X.509 certificate to majordomo. + rpc PostCertificate(CertificateRequest) returns (CertificateResponse); + // PostSSHCertificate sends a signed SSH certificate to majordomo. + rpc PostSSHCertificate(SSHCertificateRequest) returns (SSHCertificateResponse); + // RevokeCertificate marks an X.509 certificate as revoked. + rpc RevokeCertificate(TODO) returns (TODO); + // RevokeSSHCertificate marks an SSH certificate as revoked. + rpc RevokeSSHCertificate(TODO) returns (TODO); +} + +message TODO {} + +message LoginRequest { + string authority_id = 1; + string username = 2; + string password = 3; + string pem_certificate_request = 4; +} + +message LoginResponse { + string pem_certificate = 1; + string pem_certificate_chain = 2; +} + +message ConfigurationRequest { + // todo +} + +message ConfigurationResponse { + repeated Provisioner provisioners = 1; + repeated Administrator admins = 2; +} + +message CreateProvisionerRequest { + Provisioner.Type type = 1; + string name = 2; + ProvisionerDetails details = 3; + Claims claims = 4; +} + +message DeleteProvisionerRequest { + string id = 1; +} + +message CreateAdministratorRequest { + string name = 1; + string provisioner_id = 2; + Administrator.Type type = 3; +} + +message DeleteAdministratorRequest { + string id = 1; +} +message CertificateRequest { + string pem_certificate = 1; + string pem_certificate_chain = 2; +} + +message CertificateResponse { + string id = 1; +} + +message SSHCertificateRequest { + string certificate = 1; +} + +message SSHCertificateResponse { + string id = 1; +} diff --git a/majordomo/majordomo_grpc.pb.go b/majordomo/majordomo_grpc.pb.go new file mode 100644 index 00000000..44fd648d --- /dev/null +++ b/majordomo/majordomo_grpc.pb.go @@ -0,0 +1,519 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package majordomo + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// MajordomoClient is the client API for Majordomo service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type MajordomoClient interface { + // Login creates signs a given CSR and returns the certificate that will be + // used for authentication. + Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) + // GetConfiguration returns the full configuration of an authority. + GetConfiguration(ctx context.Context, in *ConfigurationRequest, opts ...grpc.CallOption) (*ConfigurationResponse, error) + // StreamConfiguration streams the full configuration of an authority. This + // method is not yet supported. + StreamConfiguration(ctx context.Context, in *ConfigurationRequest, opts ...grpc.CallOption) (Majordomo_StreamConfigurationClient, error) + // CreateProvisioner adds a new provisioner to the majordomo authority and + // returns the proto representation. + CreateProvisioner(ctx context.Context, in *CreateProvisionerRequest, opts ...grpc.CallOption) (*Provisioner, error) + // DeleteProvisioner deletes a previously created provisioner. + DeleteProvisioner(ctx context.Context, in *DeleteProvisionerRequest, opts ...grpc.CallOption) (*Provisioner, error) + // CreateAdministrator adds a new admin user to the majordomo authority. + // Admin users can add or delete provisioners. + CreateAdministrator(ctx context.Context, in *CreateAdministratorRequest, opts ...grpc.CallOption) (*Administrator, error) + // DeleteAdministrator deletes a previously created admin user. + DeleteAdministrator(ctx context.Context, in *DeleteAdministratorRequest, opts ...grpc.CallOption) (*Administrator, error) + // PostCertificate sends a signed X.509 certificate to majordomo. + PostCertificate(ctx context.Context, in *CertificateRequest, opts ...grpc.CallOption) (*CertificateResponse, error) + // PostSSHCertificate sends a signed SSH certificate to majordomo. + PostSSHCertificate(ctx context.Context, in *SSHCertificateRequest, opts ...grpc.CallOption) (*SSHCertificateResponse, error) + // RevokeCertificate marks an X.509 certificate as revoked. + RevokeCertificate(ctx context.Context, in *TODO, opts ...grpc.CallOption) (*TODO, error) + // RevokeSSHCertificate marks an SSH certificate as revoked. + RevokeSSHCertificate(ctx context.Context, in *TODO, opts ...grpc.CallOption) (*TODO, error) +} + +type majordomoClient struct { + cc grpc.ClientConnInterface +} + +func NewMajordomoClient(cc grpc.ClientConnInterface) MajordomoClient { + return &majordomoClient{cc} +} + +func (c *majordomoClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) { + out := new(LoginResponse) + err := c.cc.Invoke(ctx, "/majordomo.Majordomo/Login", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *majordomoClient) GetConfiguration(ctx context.Context, in *ConfigurationRequest, opts ...grpc.CallOption) (*ConfigurationResponse, error) { + out := new(ConfigurationResponse) + err := c.cc.Invoke(ctx, "/majordomo.Majordomo/GetConfiguration", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *majordomoClient) StreamConfiguration(ctx context.Context, in *ConfigurationRequest, opts ...grpc.CallOption) (Majordomo_StreamConfigurationClient, error) { + stream, err := c.cc.NewStream(ctx, &Majordomo_ServiceDesc.Streams[0], "/majordomo.Majordomo/StreamConfiguration", opts...) + if err != nil { + return nil, err + } + x := &majordomoStreamConfigurationClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type Majordomo_StreamConfigurationClient interface { + Recv() (*ConfigurationResponse, error) + grpc.ClientStream +} + +type majordomoStreamConfigurationClient struct { + grpc.ClientStream +} + +func (x *majordomoStreamConfigurationClient) Recv() (*ConfigurationResponse, error) { + m := new(ConfigurationResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *majordomoClient) CreateProvisioner(ctx context.Context, in *CreateProvisionerRequest, opts ...grpc.CallOption) (*Provisioner, error) { + out := new(Provisioner) + err := c.cc.Invoke(ctx, "/majordomo.Majordomo/CreateProvisioner", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *majordomoClient) DeleteProvisioner(ctx context.Context, in *DeleteProvisionerRequest, opts ...grpc.CallOption) (*Provisioner, error) { + out := new(Provisioner) + err := c.cc.Invoke(ctx, "/majordomo.Majordomo/DeleteProvisioner", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *majordomoClient) CreateAdministrator(ctx context.Context, in *CreateAdministratorRequest, opts ...grpc.CallOption) (*Administrator, error) { + out := new(Administrator) + err := c.cc.Invoke(ctx, "/majordomo.Majordomo/CreateAdministrator", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *majordomoClient) DeleteAdministrator(ctx context.Context, in *DeleteAdministratorRequest, opts ...grpc.CallOption) (*Administrator, error) { + out := new(Administrator) + err := c.cc.Invoke(ctx, "/majordomo.Majordomo/DeleteAdministrator", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *majordomoClient) PostCertificate(ctx context.Context, in *CertificateRequest, opts ...grpc.CallOption) (*CertificateResponse, error) { + out := new(CertificateResponse) + err := c.cc.Invoke(ctx, "/majordomo.Majordomo/PostCertificate", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *majordomoClient) PostSSHCertificate(ctx context.Context, in *SSHCertificateRequest, opts ...grpc.CallOption) (*SSHCertificateResponse, error) { + out := new(SSHCertificateResponse) + err := c.cc.Invoke(ctx, "/majordomo.Majordomo/PostSSHCertificate", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *majordomoClient) RevokeCertificate(ctx context.Context, in *TODO, opts ...grpc.CallOption) (*TODO, error) { + out := new(TODO) + err := c.cc.Invoke(ctx, "/majordomo.Majordomo/RevokeCertificate", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *majordomoClient) RevokeSSHCertificate(ctx context.Context, in *TODO, opts ...grpc.CallOption) (*TODO, error) { + out := new(TODO) + err := c.cc.Invoke(ctx, "/majordomo.Majordomo/RevokeSSHCertificate", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MajordomoServer is the server API for Majordomo service. +// All implementations must embed UnimplementedMajordomoServer +// for forward compatibility +type MajordomoServer interface { + // Login creates signs a given CSR and returns the certificate that will be + // used for authentication. + Login(context.Context, *LoginRequest) (*LoginResponse, error) + // GetConfiguration returns the full configuration of an authority. + GetConfiguration(context.Context, *ConfigurationRequest) (*ConfigurationResponse, error) + // StreamConfiguration streams the full configuration of an authority. This + // method is not yet supported. + StreamConfiguration(*ConfigurationRequest, Majordomo_StreamConfigurationServer) error + // CreateProvisioner adds a new provisioner to the majordomo authority and + // returns the proto representation. + CreateProvisioner(context.Context, *CreateProvisionerRequest) (*Provisioner, error) + // DeleteProvisioner deletes a previously created provisioner. + DeleteProvisioner(context.Context, *DeleteProvisionerRequest) (*Provisioner, error) + // CreateAdministrator adds a new admin user to the majordomo authority. + // Admin users can add or delete provisioners. + CreateAdministrator(context.Context, *CreateAdministratorRequest) (*Administrator, error) + // DeleteAdministrator deletes a previously created admin user. + DeleteAdministrator(context.Context, *DeleteAdministratorRequest) (*Administrator, error) + // PostCertificate sends a signed X.509 certificate to majordomo. + PostCertificate(context.Context, *CertificateRequest) (*CertificateResponse, error) + // PostSSHCertificate sends a signed SSH certificate to majordomo. + PostSSHCertificate(context.Context, *SSHCertificateRequest) (*SSHCertificateResponse, error) + // RevokeCertificate marks an X.509 certificate as revoked. + RevokeCertificate(context.Context, *TODO) (*TODO, error) + // RevokeSSHCertificate marks an SSH certificate as revoked. + RevokeSSHCertificate(context.Context, *TODO) (*TODO, error) + mustEmbedUnimplementedMajordomoServer() +} + +// UnimplementedMajordomoServer must be embedded to have forward compatible implementations. +type UnimplementedMajordomoServer struct { +} + +func (UnimplementedMajordomoServer) Login(context.Context, *LoginRequest) (*LoginResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Login not implemented") +} +func (UnimplementedMajordomoServer) GetConfiguration(context.Context, *ConfigurationRequest) (*ConfigurationResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetConfiguration not implemented") +} +func (UnimplementedMajordomoServer) StreamConfiguration(*ConfigurationRequest, Majordomo_StreamConfigurationServer) error { + return status.Errorf(codes.Unimplemented, "method StreamConfiguration not implemented") +} +func (UnimplementedMajordomoServer) CreateProvisioner(context.Context, *CreateProvisionerRequest) (*Provisioner, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateProvisioner not implemented") +} +func (UnimplementedMajordomoServer) DeleteProvisioner(context.Context, *DeleteProvisionerRequest) (*Provisioner, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteProvisioner not implemented") +} +func (UnimplementedMajordomoServer) CreateAdministrator(context.Context, *CreateAdministratorRequest) (*Administrator, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateAdministrator not implemented") +} +func (UnimplementedMajordomoServer) DeleteAdministrator(context.Context, *DeleteAdministratorRequest) (*Administrator, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteAdministrator not implemented") +} +func (UnimplementedMajordomoServer) PostCertificate(context.Context, *CertificateRequest) (*CertificateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PostCertificate not implemented") +} +func (UnimplementedMajordomoServer) PostSSHCertificate(context.Context, *SSHCertificateRequest) (*SSHCertificateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PostSSHCertificate not implemented") +} +func (UnimplementedMajordomoServer) RevokeCertificate(context.Context, *TODO) (*TODO, error) { + return nil, status.Errorf(codes.Unimplemented, "method RevokeCertificate not implemented") +} +func (UnimplementedMajordomoServer) RevokeSSHCertificate(context.Context, *TODO) (*TODO, error) { + return nil, status.Errorf(codes.Unimplemented, "method RevokeSSHCertificate not implemented") +} +func (UnimplementedMajordomoServer) mustEmbedUnimplementedMajordomoServer() {} + +// UnsafeMajordomoServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to MajordomoServer will +// result in compilation errors. +type UnsafeMajordomoServer interface { + mustEmbedUnimplementedMajordomoServer() +} + +func RegisterMajordomoServer(s grpc.ServiceRegistrar, srv MajordomoServer) { + s.RegisterService(&Majordomo_ServiceDesc, srv) +} + +func _Majordomo_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(LoginRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MajordomoServer).Login(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/majordomo.Majordomo/Login", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MajordomoServer).Login(ctx, req.(*LoginRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Majordomo_GetConfiguration_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ConfigurationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MajordomoServer).GetConfiguration(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/majordomo.Majordomo/GetConfiguration", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MajordomoServer).GetConfiguration(ctx, req.(*ConfigurationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Majordomo_StreamConfiguration_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(ConfigurationRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(MajordomoServer).StreamConfiguration(m, &majordomoStreamConfigurationServer{stream}) +} + +type Majordomo_StreamConfigurationServer interface { + Send(*ConfigurationResponse) error + grpc.ServerStream +} + +type majordomoStreamConfigurationServer struct { + grpc.ServerStream +} + +func (x *majordomoStreamConfigurationServer) Send(m *ConfigurationResponse) error { + return x.ServerStream.SendMsg(m) +} + +func _Majordomo_CreateProvisioner_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateProvisionerRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MajordomoServer).CreateProvisioner(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/majordomo.Majordomo/CreateProvisioner", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MajordomoServer).CreateProvisioner(ctx, req.(*CreateProvisionerRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Majordomo_DeleteProvisioner_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteProvisionerRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MajordomoServer).DeleteProvisioner(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/majordomo.Majordomo/DeleteProvisioner", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MajordomoServer).DeleteProvisioner(ctx, req.(*DeleteProvisionerRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Majordomo_CreateAdministrator_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateAdministratorRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MajordomoServer).CreateAdministrator(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/majordomo.Majordomo/CreateAdministrator", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MajordomoServer).CreateAdministrator(ctx, req.(*CreateAdministratorRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Majordomo_DeleteAdministrator_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteAdministratorRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MajordomoServer).DeleteAdministrator(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/majordomo.Majordomo/DeleteAdministrator", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MajordomoServer).DeleteAdministrator(ctx, req.(*DeleteAdministratorRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Majordomo_PostCertificate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CertificateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MajordomoServer).PostCertificate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/majordomo.Majordomo/PostCertificate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MajordomoServer).PostCertificate(ctx, req.(*CertificateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Majordomo_PostSSHCertificate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SSHCertificateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MajordomoServer).PostSSHCertificate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/majordomo.Majordomo/PostSSHCertificate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MajordomoServer).PostSSHCertificate(ctx, req.(*SSHCertificateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Majordomo_RevokeCertificate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TODO) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MajordomoServer).RevokeCertificate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/majordomo.Majordomo/RevokeCertificate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MajordomoServer).RevokeCertificate(ctx, req.(*TODO)) + } + return interceptor(ctx, in, info, handler) +} + +func _Majordomo_RevokeSSHCertificate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TODO) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MajordomoServer).RevokeSSHCertificate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/majordomo.Majordomo/RevokeSSHCertificate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MajordomoServer).RevokeSSHCertificate(ctx, req.(*TODO)) + } + return interceptor(ctx, in, info, handler) +} + +// Majordomo_ServiceDesc is the grpc.ServiceDesc for Majordomo service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Majordomo_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "majordomo.Majordomo", + HandlerType: (*MajordomoServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Login", + Handler: _Majordomo_Login_Handler, + }, + { + MethodName: "GetConfiguration", + Handler: _Majordomo_GetConfiguration_Handler, + }, + { + MethodName: "CreateProvisioner", + Handler: _Majordomo_CreateProvisioner_Handler, + }, + { + MethodName: "DeleteProvisioner", + Handler: _Majordomo_DeleteProvisioner_Handler, + }, + { + MethodName: "CreateAdministrator", + Handler: _Majordomo_CreateAdministrator_Handler, + }, + { + MethodName: "DeleteAdministrator", + Handler: _Majordomo_DeleteAdministrator_Handler, + }, + { + MethodName: "PostCertificate", + Handler: _Majordomo_PostCertificate_Handler, + }, + { + MethodName: "PostSSHCertificate", + Handler: _Majordomo_PostSSHCertificate_Handler, + }, + { + MethodName: "RevokeCertificate", + Handler: _Majordomo_RevokeCertificate_Handler, + }, + { + MethodName: "RevokeSSHCertificate", + Handler: _Majordomo_RevokeSSHCertificate_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "StreamConfiguration", + Handler: _Majordomo_StreamConfiguration_Handler, + ServerStreams: true, + }, + }, + Metadata: "majordomo/majordomo.proto", +} diff --git a/majordomo/provisioners.pb.go b/majordomo/provisioners.pb.go new file mode 100644 index 00000000..ccdd6f2f --- /dev/null +++ b/majordomo/provisioners.pb.go @@ -0,0 +1,1875 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.15.8 +// source: majordomo/provisioners.proto + +package majordomo + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Administrator_Type int32 + +const ( + Administrator_UNKNOWN Administrator_Type = 0 + Administrator_ADMIN Administrator_Type = 1 + Administrator_SUPER_ADMIN Administrator_Type = 2 +) + +// Enum value maps for Administrator_Type. +var ( + Administrator_Type_name = map[int32]string{ + 0: "UNKNOWN", + 1: "ADMIN", + 2: "SUPER_ADMIN", + } + Administrator_Type_value = map[string]int32{ + "UNKNOWN": 0, + "ADMIN": 1, + "SUPER_ADMIN": 2, + } +) + +func (x Administrator_Type) Enum() *Administrator_Type { + p := new(Administrator_Type) + *p = x + return p +} + +func (x Administrator_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Administrator_Type) Descriptor() protoreflect.EnumDescriptor { + return file_majordomo_provisioners_proto_enumTypes[0].Descriptor() +} + +func (Administrator_Type) Type() protoreflect.EnumType { + return &file_majordomo_provisioners_proto_enumTypes[0] +} + +func (x Administrator_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Administrator_Type.Descriptor instead. +func (Administrator_Type) EnumDescriptor() ([]byte, []int) { + return file_majordomo_provisioners_proto_rawDescGZIP(), []int{0, 0} +} + +type Provisioner_Type int32 + +const ( + Provisioner_NOOP Provisioner_Type = 0 + Provisioner_JWK Provisioner_Type = 1 + Provisioner_OIDC Provisioner_Type = 2 + Provisioner_GCP Provisioner_Type = 3 + Provisioner_AWS Provisioner_Type = 4 + Provisioner_AZURE Provisioner_Type = 5 + Provisioner_ACME Provisioner_Type = 6 + Provisioner_X5C Provisioner_Type = 7 + Provisioner_K8SSA Provisioner_Type = 8 + Provisioner_SSHPOP Provisioner_Type = 9 +) + +// Enum value maps for Provisioner_Type. +var ( + Provisioner_Type_name = map[int32]string{ + 0: "NOOP", + 1: "JWK", + 2: "OIDC", + 3: "GCP", + 4: "AWS", + 5: "AZURE", + 6: "ACME", + 7: "X5C", + 8: "K8SSA", + 9: "SSHPOP", + } + Provisioner_Type_value = map[string]int32{ + "NOOP": 0, + "JWK": 1, + "OIDC": 2, + "GCP": 3, + "AWS": 4, + "AZURE": 5, + "ACME": 6, + "X5C": 7, + "K8SSA": 8, + "SSHPOP": 9, + } +) + +func (x Provisioner_Type) Enum() *Provisioner_Type { + p := new(Provisioner_Type) + *p = x + return p +} + +func (x Provisioner_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Provisioner_Type) Descriptor() protoreflect.EnumDescriptor { + return file_majordomo_provisioners_proto_enumTypes[1].Descriptor() +} + +func (Provisioner_Type) Type() protoreflect.EnumType { + return &file_majordomo_provisioners_proto_enumTypes[1] +} + +func (x Provisioner_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Provisioner_Type.Descriptor instead. +func (Provisioner_Type) EnumDescriptor() ([]byte, []int) { + return file_majordomo_provisioners_proto_rawDescGZIP(), []int{1, 0} +} + +type Administrator struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + AuthorityId string `protobuf:"bytes,2,opt,name=authority_id,json=authorityId,proto3" json:"authority_id,omitempty"` + Subject string `protobuf:"bytes,3,opt,name=subject,proto3" json:"subject,omitempty"` + ProvisionerId string `protobuf:"bytes,4,opt,name=provisioner_id,json=provisionerId,proto3" json:"provisioner_id,omitempty"` + Type Administrator_Type `protobuf:"varint,5,opt,name=type,proto3,enum=majordomo.Administrator_Type" json:"type,omitempty"` +} + +func (x *Administrator) Reset() { + *x = Administrator{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_provisioners_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Administrator) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Administrator) ProtoMessage() {} + +func (x *Administrator) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_provisioners_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Administrator.ProtoReflect.Descriptor instead. +func (*Administrator) Descriptor() ([]byte, []int) { + return file_majordomo_provisioners_proto_rawDescGZIP(), []int{0} +} + +func (x *Administrator) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Administrator) GetAuthorityId() string { + if x != nil { + return x.AuthorityId + } + return "" +} + +func (x *Administrator) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *Administrator) GetProvisionerId() string { + if x != nil { + return x.ProvisionerId + } + return "" +} + +func (x *Administrator) GetType() Administrator_Type { + if x != nil { + return x.Type + } + return Administrator_UNKNOWN +} + +type Provisioner struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + AuthorityId string `protobuf:"bytes,2,opt,name=authority_id,json=authorityId,proto3" json:"authority_id,omitempty"` + Type Provisioner_Type `protobuf:"varint,3,opt,name=type,proto3,enum=majordomo.Provisioner_Type" json:"type,omitempty"` + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + Details *ProvisionerDetails `protobuf:"bytes,5,opt,name=details,proto3" json:"details,omitempty"` + Claims *Claims `protobuf:"bytes,6,opt,name=claims,proto3" json:"claims,omitempty"` + X509Template []byte `protobuf:"bytes,7,opt,name=x509_template,json=x509Template,proto3" json:"x509_template,omitempty"` + X509TemplateData []byte `protobuf:"bytes,8,opt,name=x509_template_data,json=x509TemplateData,proto3" json:"x509_template_data,omitempty"` + SshTemplate []byte `protobuf:"bytes,9,opt,name=ssh_template,json=sshTemplate,proto3" json:"ssh_template,omitempty"` + SshTemplateData []byte `protobuf:"bytes,10,opt,name=ssh_template_data,json=sshTemplateData,proto3" json:"ssh_template_data,omitempty"` +} + +func (x *Provisioner) Reset() { + *x = Provisioner{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_provisioners_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Provisioner) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Provisioner) ProtoMessage() {} + +func (x *Provisioner) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_provisioners_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Provisioner.ProtoReflect.Descriptor instead. +func (*Provisioner) Descriptor() ([]byte, []int) { + return file_majordomo_provisioners_proto_rawDescGZIP(), []int{1} +} + +func (x *Provisioner) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Provisioner) GetAuthorityId() string { + if x != nil { + return x.AuthorityId + } + return "" +} + +func (x *Provisioner) GetType() Provisioner_Type { + if x != nil { + return x.Type + } + return Provisioner_NOOP +} + +func (x *Provisioner) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Provisioner) GetDetails() *ProvisionerDetails { + if x != nil { + return x.Details + } + return nil +} + +func (x *Provisioner) GetClaims() *Claims { + if x != nil { + return x.Claims + } + return nil +} + +func (x *Provisioner) GetX509Template() []byte { + if x != nil { + return x.X509Template + } + return nil +} + +func (x *Provisioner) GetX509TemplateData() []byte { + if x != nil { + return x.X509TemplateData + } + return nil +} + +func (x *Provisioner) GetSshTemplate() []byte { + if x != nil { + return x.SshTemplate + } + return nil +} + +func (x *Provisioner) GetSshTemplateData() []byte { + if x != nil { + return x.SshTemplateData + } + return nil +} + +type ProvisionerDetails struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Data: + // *ProvisionerDetails_JWK + // *ProvisionerDetails_OIDC + // *ProvisionerDetails_GCP + // *ProvisionerDetails_AWS + // *ProvisionerDetails_Azure + // *ProvisionerDetails_ACME + // *ProvisionerDetails_X5C + // *ProvisionerDetails_K8SSA + // *ProvisionerDetails_SSHPOP + Data isProvisionerDetails_Data `protobuf_oneof:"data"` +} + +func (x *ProvisionerDetails) Reset() { + *x = ProvisionerDetails{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_provisioners_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ProvisionerDetails) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProvisionerDetails) ProtoMessage() {} + +func (x *ProvisionerDetails) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_provisioners_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProvisionerDetails.ProtoReflect.Descriptor instead. +func (*ProvisionerDetails) Descriptor() ([]byte, []int) { + return file_majordomo_provisioners_proto_rawDescGZIP(), []int{2} +} + +func (m *ProvisionerDetails) GetData() isProvisionerDetails_Data { + if m != nil { + return m.Data + } + return nil +} + +func (x *ProvisionerDetails) GetJWK() *JWKProvisioner { + if x, ok := x.GetData().(*ProvisionerDetails_JWK); ok { + return x.JWK + } + return nil +} + +func (x *ProvisionerDetails) GetOIDC() *OIDCProvisioner { + if x, ok := x.GetData().(*ProvisionerDetails_OIDC); ok { + return x.OIDC + } + return nil +} + +func (x *ProvisionerDetails) GetGCP() *GCPProvisioner { + if x, ok := x.GetData().(*ProvisionerDetails_GCP); ok { + return x.GCP + } + return nil +} + +func (x *ProvisionerDetails) GetAWS() *AWSProvisioner { + if x, ok := x.GetData().(*ProvisionerDetails_AWS); ok { + return x.AWS + } + return nil +} + +func (x *ProvisionerDetails) GetAzure() *AzureProvisioner { + if x, ok := x.GetData().(*ProvisionerDetails_Azure); ok { + return x.Azure + } + return nil +} + +func (x *ProvisionerDetails) GetACME() *ACMEProvisioner { + if x, ok := x.GetData().(*ProvisionerDetails_ACME); ok { + return x.ACME + } + return nil +} + +func (x *ProvisionerDetails) GetX5C() *X5CProvisioner { + if x, ok := x.GetData().(*ProvisionerDetails_X5C); ok { + return x.X5C + } + return nil +} + +func (x *ProvisionerDetails) GetK8SSA() *K8SSAProvisioner { + if x, ok := x.GetData().(*ProvisionerDetails_K8SSA); ok { + return x.K8SSA + } + return nil +} + +func (x *ProvisionerDetails) GetSSHPOP() *SSHPOPProvisioner { + if x, ok := x.GetData().(*ProvisionerDetails_SSHPOP); ok { + return x.SSHPOP + } + return nil +} + +type isProvisionerDetails_Data interface { + isProvisionerDetails_Data() +} + +type ProvisionerDetails_JWK struct { + JWK *JWKProvisioner `protobuf:"bytes,20,opt,name=JWK,proto3,oneof"` +} + +type ProvisionerDetails_OIDC struct { + OIDC *OIDCProvisioner `protobuf:"bytes,21,opt,name=OIDC,proto3,oneof"` +} + +type ProvisionerDetails_GCP struct { + GCP *GCPProvisioner `protobuf:"bytes,22,opt,name=GCP,proto3,oneof"` +} + +type ProvisionerDetails_AWS struct { + AWS *AWSProvisioner `protobuf:"bytes,23,opt,name=AWS,proto3,oneof"` +} + +type ProvisionerDetails_Azure struct { + Azure *AzureProvisioner `protobuf:"bytes,24,opt,name=Azure,proto3,oneof"` +} + +type ProvisionerDetails_ACME struct { + ACME *ACMEProvisioner `protobuf:"bytes,25,opt,name=ACME,proto3,oneof"` +} + +type ProvisionerDetails_X5C struct { + X5C *X5CProvisioner `protobuf:"bytes,26,opt,name=X5C,proto3,oneof"` +} + +type ProvisionerDetails_K8SSA struct { + K8SSA *K8SSAProvisioner `protobuf:"bytes,27,opt,name=K8sSA,proto3,oneof"` +} + +type ProvisionerDetails_SSHPOP struct { + SSHPOP *SSHPOPProvisioner `protobuf:"bytes,28,opt,name=SSHPOP,proto3,oneof"` +} + +func (*ProvisionerDetails_JWK) isProvisionerDetails_Data() {} + +func (*ProvisionerDetails_OIDC) isProvisionerDetails_Data() {} + +func (*ProvisionerDetails_GCP) isProvisionerDetails_Data() {} + +func (*ProvisionerDetails_AWS) isProvisionerDetails_Data() {} + +func (*ProvisionerDetails_Azure) isProvisionerDetails_Data() {} + +func (*ProvisionerDetails_ACME) isProvisionerDetails_Data() {} + +func (*ProvisionerDetails_X5C) isProvisionerDetails_Data() {} + +func (*ProvisionerDetails_K8SSA) isProvisionerDetails_Data() {} + +func (*ProvisionerDetails_SSHPOP) isProvisionerDetails_Data() {} + +type ProvisionerList struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Provisioners []*Provisioner `protobuf:"bytes,1,rep,name=provisioners,proto3" json:"provisioners,omitempty"` +} + +func (x *ProvisionerList) Reset() { + *x = ProvisionerList{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_provisioners_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ProvisionerList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProvisionerList) ProtoMessage() {} + +func (x *ProvisionerList) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_provisioners_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProvisionerList.ProtoReflect.Descriptor instead. +func (*ProvisionerList) Descriptor() ([]byte, []int) { + return file_majordomo_provisioners_proto_rawDescGZIP(), []int{3} +} + +func (x *ProvisionerList) GetProvisioners() []*Provisioner { + if x != nil { + return x.Provisioners + } + return nil +} + +type Claims struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + X509 *X509Claims `protobuf:"bytes,1,opt,name=x509,proto3" json:"x509,omitempty"` + Ssh *SSHClaims `protobuf:"bytes,2,opt,name=ssh,proto3" json:"ssh,omitempty"` + DisableRenewal bool `protobuf:"varint,3,opt,name=disable_renewal,json=disableRenewal,proto3" json:"disable_renewal,omitempty"` +} + +func (x *Claims) Reset() { + *x = Claims{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_provisioners_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Claims) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Claims) ProtoMessage() {} + +func (x *Claims) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_provisioners_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Claims.ProtoReflect.Descriptor instead. +func (*Claims) Descriptor() ([]byte, []int) { + return file_majordomo_provisioners_proto_rawDescGZIP(), []int{4} +} + +func (x *Claims) GetX509() *X509Claims { + if x != nil { + return x.X509 + } + return nil +} + +func (x *Claims) GetSsh() *SSHClaims { + if x != nil { + return x.Ssh + } + return nil +} + +func (x *Claims) GetDisableRenewal() bool { + if x != nil { + return x.DisableRenewal + } + return false +} + +type X509Claims struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + Durations *Durations `protobuf:"bytes,2,opt,name=durations,proto3" json:"durations,omitempty"` +} + +func (x *X509Claims) Reset() { + *x = X509Claims{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_provisioners_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *X509Claims) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*X509Claims) ProtoMessage() {} + +func (x *X509Claims) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_provisioners_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use X509Claims.ProtoReflect.Descriptor instead. +func (*X509Claims) Descriptor() ([]byte, []int) { + return file_majordomo_provisioners_proto_rawDescGZIP(), []int{5} +} + +func (x *X509Claims) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +func (x *X509Claims) GetDurations() *Durations { + if x != nil { + return x.Durations + } + return nil +} + +type SSHClaims struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + UserDurations *Durations `protobuf:"bytes,2,opt,name=user_durations,json=userDurations,proto3" json:"user_durations,omitempty"` + HostDurations *Durations `protobuf:"bytes,3,opt,name=host_durations,json=hostDurations,proto3" json:"host_durations,omitempty"` +} + +func (x *SSHClaims) Reset() { + *x = SSHClaims{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_provisioners_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SSHClaims) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SSHClaims) ProtoMessage() {} + +func (x *SSHClaims) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_provisioners_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SSHClaims.ProtoReflect.Descriptor instead. +func (*SSHClaims) Descriptor() ([]byte, []int) { + return file_majordomo_provisioners_proto_rawDescGZIP(), []int{6} +} + +func (x *SSHClaims) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +func (x *SSHClaims) GetUserDurations() *Durations { + if x != nil { + return x.UserDurations + } + return nil +} + +func (x *SSHClaims) GetHostDurations() *Durations { + if x != nil { + return x.HostDurations + } + return nil +} + +type Durations struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Default string `protobuf:"bytes,1,opt,name=default,proto3" json:"default,omitempty"` + Min string `protobuf:"bytes,2,opt,name=min,proto3" json:"min,omitempty"` + Max string `protobuf:"bytes,3,opt,name=max,proto3" json:"max,omitempty"` +} + +func (x *Durations) Reset() { + *x = Durations{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_provisioners_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Durations) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Durations) ProtoMessage() {} + +func (x *Durations) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_provisioners_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Durations.ProtoReflect.Descriptor instead. +func (*Durations) Descriptor() ([]byte, []int) { + return file_majordomo_provisioners_proto_rawDescGZIP(), []int{7} +} + +func (x *Durations) GetDefault() string { + if x != nil { + return x.Default + } + return "" +} + +func (x *Durations) GetMin() string { + if x != nil { + return x.Min + } + return "" +} + +func (x *Durations) GetMax() string { + if x != nil { + return x.Max + } + return "" +} + +type JWKProvisioner struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PublicKey []byte `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` + EncryptedPrivateKey []byte `protobuf:"bytes,2,opt,name=encrypted_private_key,json=encryptedPrivateKey,proto3" json:"encrypted_private_key,omitempty"` +} + +func (x *JWKProvisioner) Reset() { + *x = JWKProvisioner{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_provisioners_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *JWKProvisioner) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*JWKProvisioner) ProtoMessage() {} + +func (x *JWKProvisioner) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_provisioners_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use JWKProvisioner.ProtoReflect.Descriptor instead. +func (*JWKProvisioner) Descriptor() ([]byte, []int) { + return file_majordomo_provisioners_proto_rawDescGZIP(), []int{8} +} + +func (x *JWKProvisioner) GetPublicKey() []byte { + if x != nil { + return x.PublicKey + } + return nil +} + +func (x *JWKProvisioner) GetEncryptedPrivateKey() []byte { + if x != nil { + return x.EncryptedPrivateKey + } + return nil +} + +type OIDCProvisioner struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + ClientSecret string `protobuf:"bytes,2,opt,name=client_secret,json=clientSecret,proto3" json:"client_secret,omitempty"` + ConfigurationEndpoint string `protobuf:"bytes,3,opt,name=configuration_endpoint,json=configurationEndpoint,proto3" json:"configuration_endpoint,omitempty"` + Admins []string `protobuf:"bytes,4,rep,name=admins,proto3" json:"admins,omitempty"` + Domains []string `protobuf:"bytes,5,rep,name=domains,proto3" json:"domains,omitempty"` + Groups []string `protobuf:"bytes,6,rep,name=groups,proto3" json:"groups,omitempty"` + ListenAddress string `protobuf:"bytes,7,opt,name=listen_address,json=listenAddress,proto3" json:"listen_address,omitempty"` + TenantId string `protobuf:"bytes,8,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` +} + +func (x *OIDCProvisioner) Reset() { + *x = OIDCProvisioner{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_provisioners_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OIDCProvisioner) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OIDCProvisioner) ProtoMessage() {} + +func (x *OIDCProvisioner) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_provisioners_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OIDCProvisioner.ProtoReflect.Descriptor instead. +func (*OIDCProvisioner) Descriptor() ([]byte, []int) { + return file_majordomo_provisioners_proto_rawDescGZIP(), []int{9} +} + +func (x *OIDCProvisioner) GetClientId() string { + if x != nil { + return x.ClientId + } + return "" +} + +func (x *OIDCProvisioner) GetClientSecret() string { + if x != nil { + return x.ClientSecret + } + return "" +} + +func (x *OIDCProvisioner) GetConfigurationEndpoint() string { + if x != nil { + return x.ConfigurationEndpoint + } + return "" +} + +func (x *OIDCProvisioner) GetAdmins() []string { + if x != nil { + return x.Admins + } + return nil +} + +func (x *OIDCProvisioner) GetDomains() []string { + if x != nil { + return x.Domains + } + return nil +} + +func (x *OIDCProvisioner) GetGroups() []string { + if x != nil { + return x.Groups + } + return nil +} + +func (x *OIDCProvisioner) GetListenAddress() string { + if x != nil { + return x.ListenAddress + } + return "" +} + +func (x *OIDCProvisioner) GetTenantId() string { + if x != nil { + return x.TenantId + } + return "" +} + +type GCPProvisioner struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ServiceAccounts []string `protobuf:"bytes,1,rep,name=service_accounts,json=serviceAccounts,proto3" json:"service_accounts,omitempty"` + ProjectIds []string `protobuf:"bytes,2,rep,name=project_ids,json=projectIds,proto3" json:"project_ids,omitempty"` + DisableCustomSans bool `protobuf:"varint,3,opt,name=disable_custom_sans,json=disableCustomSans,proto3" json:"disable_custom_sans,omitempty"` + DisableTrustOnFirstUse bool `protobuf:"varint,4,opt,name=disable_trust_on_first_use,json=disableTrustOnFirstUse,proto3" json:"disable_trust_on_first_use,omitempty"` + InstanceAge string `protobuf:"bytes,5,opt,name=instance_age,json=instanceAge,proto3" json:"instance_age,omitempty"` +} + +func (x *GCPProvisioner) Reset() { + *x = GCPProvisioner{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_provisioners_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GCPProvisioner) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GCPProvisioner) ProtoMessage() {} + +func (x *GCPProvisioner) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_provisioners_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GCPProvisioner.ProtoReflect.Descriptor instead. +func (*GCPProvisioner) Descriptor() ([]byte, []int) { + return file_majordomo_provisioners_proto_rawDescGZIP(), []int{10} +} + +func (x *GCPProvisioner) GetServiceAccounts() []string { + if x != nil { + return x.ServiceAccounts + } + return nil +} + +func (x *GCPProvisioner) GetProjectIds() []string { + if x != nil { + return x.ProjectIds + } + return nil +} + +func (x *GCPProvisioner) GetDisableCustomSans() bool { + if x != nil { + return x.DisableCustomSans + } + return false +} + +func (x *GCPProvisioner) GetDisableTrustOnFirstUse() bool { + if x != nil { + return x.DisableTrustOnFirstUse + } + return false +} + +func (x *GCPProvisioner) GetInstanceAge() string { + if x != nil { + return x.InstanceAge + } + return "" +} + +type AWSProvisioner struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Accounts []string `protobuf:"bytes,1,rep,name=accounts,proto3" json:"accounts,omitempty"` + DisableCustomSans bool `protobuf:"varint,2,opt,name=disable_custom_sans,json=disableCustomSans,proto3" json:"disable_custom_sans,omitempty"` + DisableTrustOnFirstUse bool `protobuf:"varint,3,opt,name=disable_trust_on_first_use,json=disableTrustOnFirstUse,proto3" json:"disable_trust_on_first_use,omitempty"` + InstanceAge string `protobuf:"bytes,4,opt,name=instance_age,json=instanceAge,proto3" json:"instance_age,omitempty"` +} + +func (x *AWSProvisioner) Reset() { + *x = AWSProvisioner{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_provisioners_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AWSProvisioner) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AWSProvisioner) ProtoMessage() {} + +func (x *AWSProvisioner) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_provisioners_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AWSProvisioner.ProtoReflect.Descriptor instead. +func (*AWSProvisioner) Descriptor() ([]byte, []int) { + return file_majordomo_provisioners_proto_rawDescGZIP(), []int{11} +} + +func (x *AWSProvisioner) GetAccounts() []string { + if x != nil { + return x.Accounts + } + return nil +} + +func (x *AWSProvisioner) GetDisableCustomSans() bool { + if x != nil { + return x.DisableCustomSans + } + return false +} + +func (x *AWSProvisioner) GetDisableTrustOnFirstUse() bool { + if x != nil { + return x.DisableTrustOnFirstUse + } + return false +} + +func (x *AWSProvisioner) GetInstanceAge() string { + if x != nil { + return x.InstanceAge + } + return "" +} + +type AzureProvisioner struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TenantId string `protobuf:"bytes,1,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` + ResourceGroups []string `protobuf:"bytes,2,rep,name=resource_groups,json=resourceGroups,proto3" json:"resource_groups,omitempty"` + Audience string `protobuf:"bytes,3,opt,name=audience,proto3" json:"audience,omitempty"` + DisableCustomSans bool `protobuf:"varint,4,opt,name=disable_custom_sans,json=disableCustomSans,proto3" json:"disable_custom_sans,omitempty"` + DisableTrustOnFirstUse bool `protobuf:"varint,5,opt,name=disable_trust_on_first_use,json=disableTrustOnFirstUse,proto3" json:"disable_trust_on_first_use,omitempty"` +} + +func (x *AzureProvisioner) Reset() { + *x = AzureProvisioner{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_provisioners_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AzureProvisioner) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AzureProvisioner) ProtoMessage() {} + +func (x *AzureProvisioner) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_provisioners_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AzureProvisioner.ProtoReflect.Descriptor instead. +func (*AzureProvisioner) Descriptor() ([]byte, []int) { + return file_majordomo_provisioners_proto_rawDescGZIP(), []int{12} +} + +func (x *AzureProvisioner) GetTenantId() string { + if x != nil { + return x.TenantId + } + return "" +} + +func (x *AzureProvisioner) GetResourceGroups() []string { + if x != nil { + return x.ResourceGroups + } + return nil +} + +func (x *AzureProvisioner) GetAudience() string { + if x != nil { + return x.Audience + } + return "" +} + +func (x *AzureProvisioner) GetDisableCustomSans() bool { + if x != nil { + return x.DisableCustomSans + } + return false +} + +func (x *AzureProvisioner) GetDisableTrustOnFirstUse() bool { + if x != nil { + return x.DisableTrustOnFirstUse + } + return false +} + +type ACMEProvisioner struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ForceCn bool `protobuf:"varint,1,opt,name=force_cn,json=forceCn,proto3" json:"force_cn,omitempty"` +} + +func (x *ACMEProvisioner) Reset() { + *x = ACMEProvisioner{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_provisioners_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ACMEProvisioner) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ACMEProvisioner) ProtoMessage() {} + +func (x *ACMEProvisioner) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_provisioners_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ACMEProvisioner.ProtoReflect.Descriptor instead. +func (*ACMEProvisioner) Descriptor() ([]byte, []int) { + return file_majordomo_provisioners_proto_rawDescGZIP(), []int{13} +} + +func (x *ACMEProvisioner) GetForceCn() bool { + if x != nil { + return x.ForceCn + } + return false +} + +type X5CProvisioner struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Roots [][]byte `protobuf:"bytes,1,rep,name=roots,proto3" json:"roots,omitempty"` +} + +func (x *X5CProvisioner) Reset() { + *x = X5CProvisioner{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_provisioners_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *X5CProvisioner) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*X5CProvisioner) ProtoMessage() {} + +func (x *X5CProvisioner) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_provisioners_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use X5CProvisioner.ProtoReflect.Descriptor instead. +func (*X5CProvisioner) Descriptor() ([]byte, []int) { + return file_majordomo_provisioners_proto_rawDescGZIP(), []int{14} +} + +func (x *X5CProvisioner) GetRoots() [][]byte { + if x != nil { + return x.Roots + } + return nil +} + +type K8SSAProvisioner struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PublicKeys [][]byte `protobuf:"bytes,1,rep,name=public_keys,json=publicKeys,proto3" json:"public_keys,omitempty"` +} + +func (x *K8SSAProvisioner) Reset() { + *x = K8SSAProvisioner{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_provisioners_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *K8SSAProvisioner) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*K8SSAProvisioner) ProtoMessage() {} + +func (x *K8SSAProvisioner) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_provisioners_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use K8SSAProvisioner.ProtoReflect.Descriptor instead. +func (*K8SSAProvisioner) Descriptor() ([]byte, []int) { + return file_majordomo_provisioners_proto_rawDescGZIP(), []int{15} +} + +func (x *K8SSAProvisioner) GetPublicKeys() [][]byte { + if x != nil { + return x.PublicKeys + } + return nil +} + +type SSHPOPProvisioner struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *SSHPOPProvisioner) Reset() { + *x = SSHPOPProvisioner{} + if protoimpl.UnsafeEnabled { + mi := &file_majordomo_provisioners_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SSHPOPProvisioner) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SSHPOPProvisioner) ProtoMessage() {} + +func (x *SSHPOPProvisioner) ProtoReflect() protoreflect.Message { + mi := &file_majordomo_provisioners_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SSHPOPProvisioner.ProtoReflect.Descriptor instead. +func (*SSHPOPProvisioner) Descriptor() ([]byte, []int) { + return file_majordomo_provisioners_proto_rawDescGZIP(), []int{16} +} + +var File_majordomo_provisioners_proto protoreflect.FileDescriptor + +var file_majordomo_provisioners_proto_rawDesc = []byte{ + 0x0a, 0x1c, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, + 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x22, 0xe7, 0x01, 0x0a, 0x0d, 0x41, 0x64, + 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x18, + 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x31, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, + 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x22, 0x2f, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, + 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x44, 0x4d, 0x49, 0x4e, + 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x55, 0x50, 0x45, 0x52, 0x5f, 0x41, 0x44, 0x4d, 0x49, + 0x4e, 0x10, 0x02, 0x22, 0xf7, 0x03, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x2f, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, + 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x79, 0x70, + 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x64, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6d, + 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x07, 0x64, 0x65, 0x74, + 0x61, 0x69, 0x6c, 0x73, 0x12, 0x29, 0x0a, 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, + 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, + 0x23, 0x0a, 0x0d, 0x78, 0x35, 0x30, 0x39, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x78, 0x35, 0x30, 0x39, 0x54, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x78, 0x35, 0x30, 0x39, 0x5f, 0x74, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x10, 0x78, 0x35, 0x30, 0x39, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x61, + 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x73, 0x68, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x73, 0x73, 0x68, 0x54, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x73, 0x68, 0x5f, 0x74, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0f, 0x73, 0x73, 0x68, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, + 0x61, 0x22, 0x6a, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4f, + 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4a, 0x57, 0x4b, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, + 0x4f, 0x49, 0x44, 0x43, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x47, 0x43, 0x50, 0x10, 0x03, 0x12, + 0x07, 0x0a, 0x03, 0x41, 0x57, 0x53, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x5a, 0x55, 0x52, + 0x45, 0x10, 0x05, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x43, 0x4d, 0x45, 0x10, 0x06, 0x12, 0x07, 0x0a, + 0x03, 0x58, 0x35, 0x43, 0x10, 0x07, 0x12, 0x09, 0x0a, 0x05, 0x4b, 0x38, 0x53, 0x53, 0x41, 0x10, + 0x08, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x53, 0x48, 0x50, 0x4f, 0x50, 0x10, 0x09, 0x22, 0xde, 0x03, + 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x74, + 0x61, 0x69, 0x6c, 0x73, 0x12, 0x2d, 0x0a, 0x03, 0x4a, 0x57, 0x4b, 0x18, 0x14, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x19, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x4a, 0x57, + 0x4b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, + 0x4a, 0x57, 0x4b, 0x12, 0x30, 0x0a, 0x04, 0x4f, 0x49, 0x44, 0x43, 0x18, 0x15, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x4f, 0x49, + 0x44, 0x43, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, + 0x04, 0x4f, 0x49, 0x44, 0x43, 0x12, 0x2d, 0x0a, 0x03, 0x47, 0x43, 0x50, 0x18, 0x16, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x47, + 0x43, 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, + 0x03, 0x47, 0x43, 0x50, 0x12, 0x2d, 0x0a, 0x03, 0x41, 0x57, 0x53, 0x18, 0x17, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x19, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x41, 0x57, + 0x53, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, + 0x41, 0x57, 0x53, 0x12, 0x33, 0x0a, 0x05, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x18, 0x18, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x41, + 0x7a, 0x75, 0x72, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, + 0x00, 0x52, 0x05, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x12, 0x30, 0x0a, 0x04, 0x41, 0x43, 0x4d, 0x45, + 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, + 0x6d, 0x6f, 0x2e, 0x41, 0x43, 0x4d, 0x45, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x48, 0x00, 0x52, 0x04, 0x41, 0x43, 0x4d, 0x45, 0x12, 0x2d, 0x0a, 0x03, 0x58, 0x35, + 0x43, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, + 0x6f, 0x6d, 0x6f, 0x2e, 0x58, 0x35, 0x43, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, 0x58, 0x35, 0x43, 0x12, 0x33, 0x0a, 0x05, 0x4b, 0x38, 0x73, + 0x53, 0x41, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, + 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x4b, 0x38, 0x73, 0x53, 0x41, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x05, 0x4b, 0x38, 0x73, 0x53, 0x41, 0x12, 0x36, + 0x0a, 0x06, 0x53, 0x53, 0x48, 0x50, 0x4f, 0x50, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, + 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x53, 0x53, 0x48, 0x50, 0x4f, + 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x06, + 0x53, 0x53, 0x48, 0x50, 0x4f, 0x50, 0x42, 0x06, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x4d, + 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x4c, 0x69, 0x73, + 0x74, 0x12, 0x3a, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, + 0x6f, 0x6d, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x52, + 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x22, 0x84, 0x01, + 0x0a, 0x06, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x29, 0x0a, 0x04, 0x78, 0x35, 0x30, 0x39, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, + 0x6d, 0x6f, 0x2e, 0x58, 0x35, 0x30, 0x39, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x04, 0x78, + 0x35, 0x30, 0x39, 0x12, 0x26, 0x0a, 0x03, 0x73, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x14, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x53, 0x53, 0x48, + 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x03, 0x73, 0x73, 0x68, 0x12, 0x27, 0x0a, 0x0f, 0x64, + 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x6e, + 0x65, 0x77, 0x61, 0x6c, 0x22, 0x5a, 0x0a, 0x0a, 0x58, 0x35, 0x30, 0x39, 0x43, 0x6c, 0x61, 0x69, + 0x6d, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x32, 0x0a, 0x09, + 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x14, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x44, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x09, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x22, 0x9f, 0x01, 0x0a, 0x09, 0x53, 0x53, 0x48, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x18, + 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x3b, 0x0a, 0x0e, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x14, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x44, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x44, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3b, 0x0a, 0x0e, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x64, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, + 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x0d, 0x68, 0x6f, 0x73, 0x74, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x22, 0x49, 0x0a, 0x09, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, + 0x18, 0x0a, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6d, + 0x61, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x61, 0x78, 0x22, 0x63, 0x0a, + 0x0e, 0x4a, 0x57, 0x4b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, + 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x32, + 0x0a, 0x15, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x72, 0x69, 0x76, + 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x13, 0x65, + 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, + 0x65, 0x79, 0x22, 0x98, 0x02, 0x0a, 0x0f, 0x4f, 0x49, 0x44, 0x43, 0x50, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x35, 0x0a, 0x16, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, + 0x16, 0x0a, 0x06, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x06, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x73, 0x12, 0x16, 0x0a, 0x06, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x06, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x6c, 0x69, 0x73, + 0x74, 0x65, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x22, 0xeb, 0x01, + 0x0a, 0x0e, 0x47, 0x43, 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x12, 0x29, 0x0a, 0x10, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x70, + 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x73, 0x12, 0x2e, 0x0a, 0x13, + 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x73, + 0x61, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, + 0x6c, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x61, 0x6e, 0x73, 0x12, 0x3a, 0x0a, 0x1a, + 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x5f, 0x6f, 0x6e, + 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x4f, 0x6e, + 0x46, 0x69, 0x72, 0x73, 0x74, 0x55, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x73, 0x74, + 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x67, 0x65, 0x22, 0xbb, 0x01, 0x0a, 0x0e, + 0x41, 0x57, 0x53, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x1a, + 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x69, + 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x73, 0x61, 0x6e, + 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x61, 0x6e, 0x73, 0x12, 0x3a, 0x0a, 0x1a, 0x64, 0x69, + 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x5f, 0x6f, 0x6e, 0x5f, 0x66, + 0x69, 0x72, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, + 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x4f, 0x6e, 0x46, 0x69, + 0x72, 0x73, 0x74, 0x55, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, + 0x63, 0x65, 0x5f, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, + 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x67, 0x65, 0x22, 0xe0, 0x01, 0x0a, 0x10, 0x41, 0x7a, + 0x75, 0x72, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x1b, + 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, + 0x6f, 0x75, 0x70, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, + 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x5f, 0x73, 0x61, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, + 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x61, 0x6e, 0x73, + 0x12, 0x3a, 0x0a, 0x1a, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x75, 0x73, + 0x74, 0x5f, 0x6f, 0x6e, 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x75, + 0x73, 0x74, 0x4f, 0x6e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x55, 0x73, 0x65, 0x22, 0x2c, 0x0a, 0x0f, + 0x41, 0x43, 0x4d, 0x45, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, + 0x19, 0x0a, 0x08, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x07, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6e, 0x22, 0x26, 0x0a, 0x0e, 0x58, 0x35, + 0x43, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, + 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x6f, 0x6f, + 0x74, 0x73, 0x22, 0x33, 0x0a, 0x10, 0x4b, 0x38, 0x73, 0x53, 0x41, 0x50, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x53, 0x53, 0x48, 0x50, 0x4f, + 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x42, 0x2d, 0x5a, 0x2b, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6d, 0x61, 0x6c, 0x6c, + 0x73, 0x74, 0x65, 0x70, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, + 0x73, 0x2f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_majordomo_provisioners_proto_rawDescOnce sync.Once + file_majordomo_provisioners_proto_rawDescData = file_majordomo_provisioners_proto_rawDesc +) + +func file_majordomo_provisioners_proto_rawDescGZIP() []byte { + file_majordomo_provisioners_proto_rawDescOnce.Do(func() { + file_majordomo_provisioners_proto_rawDescData = protoimpl.X.CompressGZIP(file_majordomo_provisioners_proto_rawDescData) + }) + return file_majordomo_provisioners_proto_rawDescData +} + +var file_majordomo_provisioners_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_majordomo_provisioners_proto_msgTypes = make([]protoimpl.MessageInfo, 17) +var file_majordomo_provisioners_proto_goTypes = []interface{}{ + (Administrator_Type)(0), // 0: majordomo.Administrator.Type + (Provisioner_Type)(0), // 1: majordomo.Provisioner.Type + (*Administrator)(nil), // 2: majordomo.Administrator + (*Provisioner)(nil), // 3: majordomo.Provisioner + (*ProvisionerDetails)(nil), // 4: majordomo.ProvisionerDetails + (*ProvisionerList)(nil), // 5: majordomo.ProvisionerList + (*Claims)(nil), // 6: majordomo.Claims + (*X509Claims)(nil), // 7: majordomo.X509Claims + (*SSHClaims)(nil), // 8: majordomo.SSHClaims + (*Durations)(nil), // 9: majordomo.Durations + (*JWKProvisioner)(nil), // 10: majordomo.JWKProvisioner + (*OIDCProvisioner)(nil), // 11: majordomo.OIDCProvisioner + (*GCPProvisioner)(nil), // 12: majordomo.GCPProvisioner + (*AWSProvisioner)(nil), // 13: majordomo.AWSProvisioner + (*AzureProvisioner)(nil), // 14: majordomo.AzureProvisioner + (*ACMEProvisioner)(nil), // 15: majordomo.ACMEProvisioner + (*X5CProvisioner)(nil), // 16: majordomo.X5CProvisioner + (*K8SSAProvisioner)(nil), // 17: majordomo.K8sSAProvisioner + (*SSHPOPProvisioner)(nil), // 18: majordomo.SSHPOPProvisioner +} +var file_majordomo_provisioners_proto_depIdxs = []int32{ + 0, // 0: majordomo.Administrator.type:type_name -> majordomo.Administrator.Type + 1, // 1: majordomo.Provisioner.type:type_name -> majordomo.Provisioner.Type + 4, // 2: majordomo.Provisioner.details:type_name -> majordomo.ProvisionerDetails + 6, // 3: majordomo.Provisioner.claims:type_name -> majordomo.Claims + 10, // 4: majordomo.ProvisionerDetails.JWK:type_name -> majordomo.JWKProvisioner + 11, // 5: majordomo.ProvisionerDetails.OIDC:type_name -> majordomo.OIDCProvisioner + 12, // 6: majordomo.ProvisionerDetails.GCP:type_name -> majordomo.GCPProvisioner + 13, // 7: majordomo.ProvisionerDetails.AWS:type_name -> majordomo.AWSProvisioner + 14, // 8: majordomo.ProvisionerDetails.Azure:type_name -> majordomo.AzureProvisioner + 15, // 9: majordomo.ProvisionerDetails.ACME:type_name -> majordomo.ACMEProvisioner + 16, // 10: majordomo.ProvisionerDetails.X5C:type_name -> majordomo.X5CProvisioner + 17, // 11: majordomo.ProvisionerDetails.K8sSA:type_name -> majordomo.K8sSAProvisioner + 18, // 12: majordomo.ProvisionerDetails.SSHPOP:type_name -> majordomo.SSHPOPProvisioner + 3, // 13: majordomo.ProvisionerList.provisioners:type_name -> majordomo.Provisioner + 7, // 14: majordomo.Claims.x509:type_name -> majordomo.X509Claims + 8, // 15: majordomo.Claims.ssh:type_name -> majordomo.SSHClaims + 9, // 16: majordomo.X509Claims.durations:type_name -> majordomo.Durations + 9, // 17: majordomo.SSHClaims.user_durations:type_name -> majordomo.Durations + 9, // 18: majordomo.SSHClaims.host_durations:type_name -> majordomo.Durations + 19, // [19:19] is the sub-list for method output_type + 19, // [19:19] is the sub-list for method input_type + 19, // [19:19] is the sub-list for extension type_name + 19, // [19:19] is the sub-list for extension extendee + 0, // [0:19] is the sub-list for field type_name +} + +func init() { file_majordomo_provisioners_proto_init() } +func file_majordomo_provisioners_proto_init() { + if File_majordomo_provisioners_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_majordomo_provisioners_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Administrator); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_provisioners_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Provisioner); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_provisioners_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ProvisionerDetails); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_provisioners_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ProvisionerList); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_provisioners_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Claims); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_provisioners_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*X509Claims); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_provisioners_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SSHClaims); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_provisioners_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Durations); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_provisioners_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*JWKProvisioner); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_provisioners_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OIDCProvisioner); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_provisioners_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GCPProvisioner); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_provisioners_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AWSProvisioner); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_provisioners_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AzureProvisioner); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_provisioners_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ACMEProvisioner); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_provisioners_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*X5CProvisioner); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_provisioners_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*K8SSAProvisioner); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_majordomo_provisioners_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SSHPOPProvisioner); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_majordomo_provisioners_proto_msgTypes[2].OneofWrappers = []interface{}{ + (*ProvisionerDetails_JWK)(nil), + (*ProvisionerDetails_OIDC)(nil), + (*ProvisionerDetails_GCP)(nil), + (*ProvisionerDetails_AWS)(nil), + (*ProvisionerDetails_Azure)(nil), + (*ProvisionerDetails_ACME)(nil), + (*ProvisionerDetails_X5C)(nil), + (*ProvisionerDetails_K8SSA)(nil), + (*ProvisionerDetails_SSHPOP)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_majordomo_provisioners_proto_rawDesc, + NumEnums: 2, + NumMessages: 17, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_majordomo_provisioners_proto_goTypes, + DependencyIndexes: file_majordomo_provisioners_proto_depIdxs, + EnumInfos: file_majordomo_provisioners_proto_enumTypes, + MessageInfos: file_majordomo_provisioners_proto_msgTypes, + }.Build() + File_majordomo_provisioners_proto = out.File + file_majordomo_provisioners_proto_rawDesc = nil + file_majordomo_provisioners_proto_goTypes = nil + file_majordomo_provisioners_proto_depIdxs = nil +} diff --git a/majordomo/provisioners.proto b/majordomo/provisioners.proto new file mode 100644 index 00000000..7dd7f0c1 --- /dev/null +++ b/majordomo/provisioners.proto @@ -0,0 +1,137 @@ +syntax = "proto3"; + +package majordomo; + +option go_package = "github.com/smallstep/certificates/majordomo"; + +message Administrator { + enum Type { + UNKNOWN = 0; + ADMIN = 1; + SUPER_ADMIN = 2; + } + string id = 1; + string authority_id = 2; + string subject = 3; + string provisioner_id = 4; + Type type = 5; +} + +message Provisioner { + enum Type { + NOOP = 0; + JWK = 1; + OIDC = 2; + GCP = 3; + AWS = 4; + AZURE = 5; + ACME = 6; + X5C = 7; + K8SSA = 8; + SSHPOP = 9; + } + string id = 1; + string authority_id = 2; + Type type = 3; + string name = 4; + ProvisionerDetails details = 5; + Claims claims = 6; + bytes x509_template = 7; + bytes x509_template_data = 8; + bytes ssh_template = 9; + bytes ssh_template_data = 10; +} + +message ProvisionerDetails { + oneof data { + JWKProvisioner JWK = 20; + OIDCProvisioner OIDC = 21; + GCPProvisioner GCP = 22; + AWSProvisioner AWS = 23; + AzureProvisioner Azure = 24; + ACMEProvisioner ACME = 25; + X5CProvisioner X5C = 26; + K8sSAProvisioner K8sSA = 27; + SSHPOPProvisioner SSHPOP = 28; + } +} + +message ProvisionerList { + repeated Provisioner provisioners = 1; +} + +message Claims { + X509Claims x509 = 1; + SSHClaims ssh = 2; + bool disable_renewal = 3; +} + +message X509Claims { + bool enabled = 1; + Durations durations = 2; +} + +message SSHClaims { + bool enabled = 1; + Durations user_durations = 2; + Durations host_durations = 3; +} + +message Durations { + string default = 1; + string min = 2; + string max = 3; +} + +message JWKProvisioner { + bytes public_key = 1; + bytes encrypted_private_key = 2; +} + +message OIDCProvisioner { + string client_id = 1; + string client_secret = 2; + string configuration_endpoint = 3; + repeated string admins = 4; + repeated string domains = 5; + repeated string groups = 6; + string listen_address = 7; + string tenant_id = 8; +} + +message GCPProvisioner { + repeated string service_accounts = 1; + repeated string project_ids = 2; + bool disable_custom_sans = 3; + bool disable_trust_on_first_use = 4; + string instance_age = 5; +} + +message AWSProvisioner { + repeated string accounts = 1; + bool disable_custom_sans = 2; + bool disable_trust_on_first_use = 3; + string instance_age = 4; +} + +message AzureProvisioner { + string tenant_id = 1; + repeated string resource_groups = 2; + string audience = 3; + bool disable_custom_sans = 4; + bool disable_trust_on_first_use = 5; +} + +message ACMEProvisioner { + bool force_cn = 1; +} + +message X5CProvisioner { + repeated bytes roots = 1; +} + +message K8sSAProvisioner { + repeated bytes public_keys = 1; +} + +message SSHPOPProvisioner {} From 71afc413bf0294332fceee2eeea24b3edbe27673 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 24 May 2021 12:36:01 -0700 Subject: [PATCH 14/89] Rename majordomo to linkedca. --- {majordomo => linkedca}/doc.go | 0 {majordomo => linkedca}/majordomo.pb.go | 0 {majordomo => linkedca}/majordomo.proto | 0 {majordomo => linkedca}/majordomo_grpc.pb.go | 0 {majordomo => linkedca}/provisioners.pb.go | 0 {majordomo => linkedca}/provisioners.proto | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename {majordomo => linkedca}/doc.go (100%) rename {majordomo => linkedca}/majordomo.pb.go (100%) rename {majordomo => linkedca}/majordomo.proto (100%) rename {majordomo => linkedca}/majordomo_grpc.pb.go (100%) rename {majordomo => linkedca}/provisioners.pb.go (100%) rename {majordomo => linkedca}/provisioners.proto (100%) diff --git a/majordomo/doc.go b/linkedca/doc.go similarity index 100% rename from majordomo/doc.go rename to linkedca/doc.go diff --git a/majordomo/majordomo.pb.go b/linkedca/majordomo.pb.go similarity index 100% rename from majordomo/majordomo.pb.go rename to linkedca/majordomo.pb.go diff --git a/majordomo/majordomo.proto b/linkedca/majordomo.proto similarity index 100% rename from majordomo/majordomo.proto rename to linkedca/majordomo.proto diff --git a/majordomo/majordomo_grpc.pb.go b/linkedca/majordomo_grpc.pb.go similarity index 100% rename from majordomo/majordomo_grpc.pb.go rename to linkedca/majordomo_grpc.pb.go diff --git a/majordomo/provisioners.pb.go b/linkedca/provisioners.pb.go similarity index 100% rename from majordomo/provisioners.pb.go rename to linkedca/provisioners.pb.go diff --git a/majordomo/provisioners.proto b/linkedca/provisioners.proto similarity index 100% rename from majordomo/provisioners.proto rename to linkedca/provisioners.proto From 35cfa5b8a298e2a4d96f0daed16f9417052622b7 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 24 May 2021 12:43:23 -0700 Subject: [PATCH 15/89] Remove majordomo client and rename administrator to admin. --- linkedca/admin.pb.go | 240 ++++++++ linkedca/admin.proto | 18 + linkedca/doc.go | 2 +- linkedca/majordomo.pb.go | 1094 --------------------------------- linkedca/majordomo.proto | 102 --- linkedca/majordomo_grpc.pb.go | 519 ---------------- linkedca/provisioners.pb.go | 771 +++++++++-------------- linkedca/provisioners.proto | 17 +- 8 files changed, 567 insertions(+), 2196 deletions(-) create mode 100644 linkedca/admin.pb.go create mode 100644 linkedca/admin.proto delete mode 100644 linkedca/majordomo.pb.go delete mode 100644 linkedca/majordomo.proto delete mode 100644 linkedca/majordomo_grpc.pb.go diff --git a/linkedca/admin.pb.go b/linkedca/admin.pb.go new file mode 100644 index 00000000..02e287c1 --- /dev/null +++ b/linkedca/admin.pb.go @@ -0,0 +1,240 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.15.8 +// source: linkedca/admin.proto + +package linkedca + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Admin_Type int32 + +const ( + Admin_UNKNOWN Admin_Type = 0 + Admin_ADMIN Admin_Type = 1 + Admin_SUPER_ADMIN Admin_Type = 2 +) + +// Enum value maps for Admin_Type. +var ( + Admin_Type_name = map[int32]string{ + 0: "UNKNOWN", + 1: "ADMIN", + 2: "SUPER_ADMIN", + } + Admin_Type_value = map[string]int32{ + "UNKNOWN": 0, + "ADMIN": 1, + "SUPER_ADMIN": 2, + } +) + +func (x Admin_Type) Enum() *Admin_Type { + p := new(Admin_Type) + *p = x + return p +} + +func (x Admin_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Admin_Type) Descriptor() protoreflect.EnumDescriptor { + return file_linkedca_admin_proto_enumTypes[0].Descriptor() +} + +func (Admin_Type) Type() protoreflect.EnumType { + return &file_linkedca_admin_proto_enumTypes[0] +} + +func (x Admin_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Admin_Type.Descriptor instead. +func (Admin_Type) EnumDescriptor() ([]byte, []int) { + return file_linkedca_admin_proto_rawDescGZIP(), []int{0, 0} +} + +type Admin struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + AuthorityId string `protobuf:"bytes,2,opt,name=authority_id,json=authorityId,proto3" json:"authority_id,omitempty"` + Subject string `protobuf:"bytes,3,opt,name=subject,proto3" json:"subject,omitempty"` + ProvisionerId string `protobuf:"bytes,4,opt,name=provisioner_id,json=provisionerId,proto3" json:"provisioner_id,omitempty"` + Type Admin_Type `protobuf:"varint,5,opt,name=type,proto3,enum=linkedca.Admin_Type" json:"type,omitempty"` +} + +func (x *Admin) Reset() { + *x = Admin{} + if protoimpl.UnsafeEnabled { + mi := &file_linkedca_admin_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Admin) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Admin) ProtoMessage() {} + +func (x *Admin) ProtoReflect() protoreflect.Message { + mi := &file_linkedca_admin_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Admin.ProtoReflect.Descriptor instead. +func (*Admin) Descriptor() ([]byte, []int) { + return file_linkedca_admin_proto_rawDescGZIP(), []int{0} +} + +func (x *Admin) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Admin) GetAuthorityId() string { + if x != nil { + return x.AuthorityId + } + return "" +} + +func (x *Admin) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *Admin) GetProvisionerId() string { + if x != nil { + return x.ProvisionerId + } + return "" +} + +func (x *Admin) GetType() Admin_Type { + if x != nil { + return x.Type + } + return Admin_UNKNOWN +} + +var File_linkedca_admin_proto protoreflect.FileDescriptor + +var file_linkedca_admin_proto_rawDesc = []byte{ + 0x0a, 0x14, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2f, 0x61, 0x64, 0x6d, 0x69, 0x6e, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, + 0x22, 0xd6, 0x01, 0x0a, 0x05, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x18, 0x0a, + 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x6c, + 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2f, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, + 0x05, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x55, 0x50, 0x45, + 0x52, 0x5f, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x02, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x73, 0x74, 0x65, + 0x70, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2f, 0x6c, + 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_linkedca_admin_proto_rawDescOnce sync.Once + file_linkedca_admin_proto_rawDescData = file_linkedca_admin_proto_rawDesc +) + +func file_linkedca_admin_proto_rawDescGZIP() []byte { + file_linkedca_admin_proto_rawDescOnce.Do(func() { + file_linkedca_admin_proto_rawDescData = protoimpl.X.CompressGZIP(file_linkedca_admin_proto_rawDescData) + }) + return file_linkedca_admin_proto_rawDescData +} + +var file_linkedca_admin_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_linkedca_admin_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_linkedca_admin_proto_goTypes = []interface{}{ + (Admin_Type)(0), // 0: linkedca.Admin.Type + (*Admin)(nil), // 1: linkedca.Admin +} +var file_linkedca_admin_proto_depIdxs = []int32{ + 0, // 0: linkedca.Admin.type:type_name -> linkedca.Admin.Type + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_linkedca_admin_proto_init() } +func file_linkedca_admin_proto_init() { + if File_linkedca_admin_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_linkedca_admin_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Admin); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_linkedca_admin_proto_rawDesc, + NumEnums: 1, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_linkedca_admin_proto_goTypes, + DependencyIndexes: file_linkedca_admin_proto_depIdxs, + EnumInfos: file_linkedca_admin_proto_enumTypes, + MessageInfos: file_linkedca_admin_proto_msgTypes, + }.Build() + File_linkedca_admin_proto = out.File + file_linkedca_admin_proto_rawDesc = nil + file_linkedca_admin_proto_goTypes = nil + file_linkedca_admin_proto_depIdxs = nil +} diff --git a/linkedca/admin.proto b/linkedca/admin.proto new file mode 100644 index 00000000..29c76139 --- /dev/null +++ b/linkedca/admin.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package linkedca; + +option go_package = "github.com/smallstep/certificates/linkedca"; + +message Admin { + enum Type { + UNKNOWN = 0; + ADMIN = 1; + SUPER_ADMIN = 2; + } + string id = 1; + string authority_id = 2; + string subject = 3; + string provisioner_id = 4; + Type type = 5; +} diff --git a/linkedca/doc.go b/linkedca/doc.go index ef9fce3a..9a64ca9c 100644 --- a/linkedca/doc.go +++ b/linkedca/doc.go @@ -1,3 +1,3 @@ package majordomo -//go:generate protoc --proto_path=.. --go_out=.. --go-grpc_out=.. --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative majordomo/provisioners.proto majordomo/majordomo.proto +//go:generate protoc --proto_path=.. --go_out=.. --go-grpc_out=.. --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative linkedca/provisioners.proto linkedca/admin.proto diff --git a/linkedca/majordomo.pb.go b/linkedca/majordomo.pb.go deleted file mode 100644 index bce189df..00000000 --- a/linkedca/majordomo.pb.go +++ /dev/null @@ -1,1094 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.26.0 -// protoc v3.15.8 -// source: majordomo/majordomo.proto - -package majordomo - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type TODO struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *TODO) Reset() { - *x = TODO{} - if protoimpl.UnsafeEnabled { - mi := &file_majordomo_majordomo_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *TODO) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*TODO) ProtoMessage() {} - -func (x *TODO) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_majordomo_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use TODO.ProtoReflect.Descriptor instead. -func (*TODO) Descriptor() ([]byte, []int) { - return file_majordomo_majordomo_proto_rawDescGZIP(), []int{0} -} - -type LoginRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - AuthorityId string `protobuf:"bytes,1,opt,name=authority_id,json=authorityId,proto3" json:"authority_id,omitempty"` - Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` - Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` - PemCertificateRequest string `protobuf:"bytes,4,opt,name=pem_certificate_request,json=pemCertificateRequest,proto3" json:"pem_certificate_request,omitempty"` -} - -func (x *LoginRequest) Reset() { - *x = LoginRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_majordomo_majordomo_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *LoginRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*LoginRequest) ProtoMessage() {} - -func (x *LoginRequest) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_majordomo_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead. -func (*LoginRequest) Descriptor() ([]byte, []int) { - return file_majordomo_majordomo_proto_rawDescGZIP(), []int{1} -} - -func (x *LoginRequest) GetAuthorityId() string { - if x != nil { - return x.AuthorityId - } - return "" -} - -func (x *LoginRequest) GetUsername() string { - if x != nil { - return x.Username - } - return "" -} - -func (x *LoginRequest) GetPassword() string { - if x != nil { - return x.Password - } - return "" -} - -func (x *LoginRequest) GetPemCertificateRequest() string { - if x != nil { - return x.PemCertificateRequest - } - return "" -} - -type LoginResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - PemCertificate string `protobuf:"bytes,1,opt,name=pem_certificate,json=pemCertificate,proto3" json:"pem_certificate,omitempty"` - PemCertificateChain string `protobuf:"bytes,2,opt,name=pem_certificate_chain,json=pemCertificateChain,proto3" json:"pem_certificate_chain,omitempty"` -} - -func (x *LoginResponse) Reset() { - *x = LoginResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_majordomo_majordomo_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *LoginResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*LoginResponse) ProtoMessage() {} - -func (x *LoginResponse) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_majordomo_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use LoginResponse.ProtoReflect.Descriptor instead. -func (*LoginResponse) Descriptor() ([]byte, []int) { - return file_majordomo_majordomo_proto_rawDescGZIP(), []int{2} -} - -func (x *LoginResponse) GetPemCertificate() string { - if x != nil { - return x.PemCertificate - } - return "" -} - -func (x *LoginResponse) GetPemCertificateChain() string { - if x != nil { - return x.PemCertificateChain - } - return "" -} - -type ConfigurationRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *ConfigurationRequest) Reset() { - *x = ConfigurationRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_majordomo_majordomo_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ConfigurationRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ConfigurationRequest) ProtoMessage() {} - -func (x *ConfigurationRequest) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_majordomo_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ConfigurationRequest.ProtoReflect.Descriptor instead. -func (*ConfigurationRequest) Descriptor() ([]byte, []int) { - return file_majordomo_majordomo_proto_rawDescGZIP(), []int{3} -} - -type ConfigurationResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Provisioners []*Provisioner `protobuf:"bytes,1,rep,name=provisioners,proto3" json:"provisioners,omitempty"` - Admins []*Administrator `protobuf:"bytes,2,rep,name=admins,proto3" json:"admins,omitempty"` -} - -func (x *ConfigurationResponse) Reset() { - *x = ConfigurationResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_majordomo_majordomo_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ConfigurationResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ConfigurationResponse) ProtoMessage() {} - -func (x *ConfigurationResponse) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_majordomo_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ConfigurationResponse.ProtoReflect.Descriptor instead. -func (*ConfigurationResponse) Descriptor() ([]byte, []int) { - return file_majordomo_majordomo_proto_rawDescGZIP(), []int{4} -} - -func (x *ConfigurationResponse) GetProvisioners() []*Provisioner { - if x != nil { - return x.Provisioners - } - return nil -} - -func (x *ConfigurationResponse) GetAdmins() []*Administrator { - if x != nil { - return x.Admins - } - return nil -} - -type CreateProvisionerRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Type Provisioner_Type `protobuf:"varint,1,opt,name=type,proto3,enum=majordomo.Provisioner_Type" json:"type,omitempty"` - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` - Details *ProvisionerDetails `protobuf:"bytes,3,opt,name=details,proto3" json:"details,omitempty"` - Claims *Claims `protobuf:"bytes,4,opt,name=claims,proto3" json:"claims,omitempty"` -} - -func (x *CreateProvisionerRequest) Reset() { - *x = CreateProvisionerRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_majordomo_majordomo_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CreateProvisionerRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CreateProvisionerRequest) ProtoMessage() {} - -func (x *CreateProvisionerRequest) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_majordomo_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CreateProvisionerRequest.ProtoReflect.Descriptor instead. -func (*CreateProvisionerRequest) Descriptor() ([]byte, []int) { - return file_majordomo_majordomo_proto_rawDescGZIP(), []int{5} -} - -func (x *CreateProvisionerRequest) GetType() Provisioner_Type { - if x != nil { - return x.Type - } - return Provisioner_NOOP -} - -func (x *CreateProvisionerRequest) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *CreateProvisionerRequest) GetDetails() *ProvisionerDetails { - if x != nil { - return x.Details - } - return nil -} - -func (x *CreateProvisionerRequest) GetClaims() *Claims { - if x != nil { - return x.Claims - } - return nil -} - -type DeleteProvisionerRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` -} - -func (x *DeleteProvisionerRequest) Reset() { - *x = DeleteProvisionerRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_majordomo_majordomo_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *DeleteProvisionerRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeleteProvisionerRequest) ProtoMessage() {} - -func (x *DeleteProvisionerRequest) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_majordomo_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DeleteProvisionerRequest.ProtoReflect.Descriptor instead. -func (*DeleteProvisionerRequest) Descriptor() ([]byte, []int) { - return file_majordomo_majordomo_proto_rawDescGZIP(), []int{6} -} - -func (x *DeleteProvisionerRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -type CreateAdministratorRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - ProvisionerId string `protobuf:"bytes,2,opt,name=provisioner_id,json=provisionerId,proto3" json:"provisioner_id,omitempty"` - Type Administrator_Type `protobuf:"varint,3,opt,name=type,proto3,enum=majordomo.Administrator_Type" json:"type,omitempty"` -} - -func (x *CreateAdministratorRequest) Reset() { - *x = CreateAdministratorRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_majordomo_majordomo_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CreateAdministratorRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CreateAdministratorRequest) ProtoMessage() {} - -func (x *CreateAdministratorRequest) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_majordomo_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CreateAdministratorRequest.ProtoReflect.Descriptor instead. -func (*CreateAdministratorRequest) Descriptor() ([]byte, []int) { - return file_majordomo_majordomo_proto_rawDescGZIP(), []int{7} -} - -func (x *CreateAdministratorRequest) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *CreateAdministratorRequest) GetProvisionerId() string { - if x != nil { - return x.ProvisionerId - } - return "" -} - -func (x *CreateAdministratorRequest) GetType() Administrator_Type { - if x != nil { - return x.Type - } - return Administrator_UNKNOWN -} - -type DeleteAdministratorRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` -} - -func (x *DeleteAdministratorRequest) Reset() { - *x = DeleteAdministratorRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_majordomo_majordomo_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *DeleteAdministratorRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeleteAdministratorRequest) ProtoMessage() {} - -func (x *DeleteAdministratorRequest) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_majordomo_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DeleteAdministratorRequest.ProtoReflect.Descriptor instead. -func (*DeleteAdministratorRequest) Descriptor() ([]byte, []int) { - return file_majordomo_majordomo_proto_rawDescGZIP(), []int{8} -} - -func (x *DeleteAdministratorRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -type CertificateRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - PemCertificate string `protobuf:"bytes,1,opt,name=pem_certificate,json=pemCertificate,proto3" json:"pem_certificate,omitempty"` - PemCertificateChain string `protobuf:"bytes,2,opt,name=pem_certificate_chain,json=pemCertificateChain,proto3" json:"pem_certificate_chain,omitempty"` -} - -func (x *CertificateRequest) Reset() { - *x = CertificateRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_majordomo_majordomo_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CertificateRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CertificateRequest) ProtoMessage() {} - -func (x *CertificateRequest) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_majordomo_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CertificateRequest.ProtoReflect.Descriptor instead. -func (*CertificateRequest) Descriptor() ([]byte, []int) { - return file_majordomo_majordomo_proto_rawDescGZIP(), []int{9} -} - -func (x *CertificateRequest) GetPemCertificate() string { - if x != nil { - return x.PemCertificate - } - return "" -} - -func (x *CertificateRequest) GetPemCertificateChain() string { - if x != nil { - return x.PemCertificateChain - } - return "" -} - -type CertificateResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` -} - -func (x *CertificateResponse) Reset() { - *x = CertificateResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_majordomo_majordomo_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CertificateResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CertificateResponse) ProtoMessage() {} - -func (x *CertificateResponse) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_majordomo_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CertificateResponse.ProtoReflect.Descriptor instead. -func (*CertificateResponse) Descriptor() ([]byte, []int) { - return file_majordomo_majordomo_proto_rawDescGZIP(), []int{10} -} - -func (x *CertificateResponse) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -type SSHCertificateRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Certificate string `protobuf:"bytes,1,opt,name=certificate,proto3" json:"certificate,omitempty"` -} - -func (x *SSHCertificateRequest) Reset() { - *x = SSHCertificateRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_majordomo_majordomo_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SSHCertificateRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SSHCertificateRequest) ProtoMessage() {} - -func (x *SSHCertificateRequest) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_majordomo_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SSHCertificateRequest.ProtoReflect.Descriptor instead. -func (*SSHCertificateRequest) Descriptor() ([]byte, []int) { - return file_majordomo_majordomo_proto_rawDescGZIP(), []int{11} -} - -func (x *SSHCertificateRequest) GetCertificate() string { - if x != nil { - return x.Certificate - } - return "" -} - -type SSHCertificateResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` -} - -func (x *SSHCertificateResponse) Reset() { - *x = SSHCertificateResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_majordomo_majordomo_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SSHCertificateResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SSHCertificateResponse) ProtoMessage() {} - -func (x *SSHCertificateResponse) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_majordomo_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SSHCertificateResponse.ProtoReflect.Descriptor instead. -func (*SSHCertificateResponse) Descriptor() ([]byte, []int) { - return file_majordomo_majordomo_proto_rawDescGZIP(), []int{12} -} - -func (x *SSHCertificateResponse) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -var File_majordomo_majordomo_proto protoreflect.FileDescriptor - -var file_majordomo_majordomo_proto_rawDesc = []byte{ - 0x0a, 0x19, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2f, 0x6d, 0x61, 0x6a, 0x6f, - 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x6d, 0x61, 0x6a, - 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x1a, 0x1c, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, - 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x06, 0x0a, 0x04, 0x54, 0x4f, 0x44, 0x4f, 0x22, 0xa1, 0x01, 0x0a, - 0x0c, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, - 0x0c, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x49, 0x64, - 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, - 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x36, 0x0a, 0x17, 0x70, 0x65, 0x6d, 0x5f, - 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x70, 0x65, 0x6d, 0x43, 0x65, - 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x22, 0x6c, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x65, 0x6d, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x65, 0x6d, 0x43, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x65, - 0x6d, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x68, - 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x65, 0x6d, 0x43, 0x65, - 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x22, 0x16, - 0x0a, 0x14, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x85, 0x01, 0x0a, 0x15, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x3a, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, - 0x6d, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x52, 0x0c, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x30, 0x0a, 0x06, - 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, - 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, - 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x06, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x73, 0x22, 0xc3, - 0x01, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, - 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x37, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1d, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x50, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, - 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x29, 0x0a, 0x06, 0x63, 0x6c, 0x61, - 0x69, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, - 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x06, 0x63, 0x6c, - 0x61, 0x69, 0x6d, 0x73, 0x22, 0x2a, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, - 0x22, 0x8a, 0x01, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, - 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x31, 0x0a, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, - 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, - 0x6f, 0x72, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2c, 0x0a, - 0x1a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x71, 0x0a, 0x12, 0x43, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x65, 0x6d, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x65, 0x6d, 0x43, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x65, - 0x6d, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x68, - 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x65, 0x6d, 0x43, 0x65, - 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x22, 0x25, - 0x0a, 0x13, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x39, 0x0a, 0x15, 0x53, 0x53, 0x48, 0x43, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, - 0x0a, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x22, 0x28, 0x0a, 0x16, 0x53, 0x53, 0x48, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x32, 0xec, 0x06, 0x0a, 0x09, 0x4d, - 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x12, 0x3a, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, - 0x6e, 0x12, 0x17, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x4c, 0x6f, - 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x6a, - 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, - 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, - 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x13, 0x53, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x1f, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x50, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x23, 0x2e, 0x6d, - 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x50, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x50, 0x0a, 0x11, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x23, - 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x56, 0x0a, 0x13, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, - 0x6f, 0x72, 0x12, 0x25, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, - 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, - 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, - 0x74, 0x6f, 0x72, 0x12, 0x56, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x64, 0x6d, - 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x25, 0x2e, 0x6d, 0x61, 0x6a, - 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x64, 0x6d, - 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x41, 0x64, - 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x50, 0x0a, 0x0f, 0x50, - 0x6f, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x1d, - 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, - 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, - 0x12, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x53, 0x48, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x12, 0x20, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, - 0x53, 0x53, 0x48, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, - 0x6f, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x11, 0x52, 0x65, 0x76, 0x6f, - 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x0f, 0x2e, - 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x54, 0x4f, 0x44, 0x4f, 0x1a, 0x0f, - 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x54, 0x4f, 0x44, 0x4f, 0x12, - 0x38, 0x0a, 0x14, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x53, 0x48, 0x43, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x0f, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, - 0x6f, 0x6d, 0x6f, 0x2e, 0x54, 0x4f, 0x44, 0x4f, 0x1a, 0x0f, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, - 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x54, 0x4f, 0x44, 0x4f, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x73, 0x74, 0x65, - 0x70, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2f, 0x6d, - 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_majordomo_majordomo_proto_rawDescOnce sync.Once - file_majordomo_majordomo_proto_rawDescData = file_majordomo_majordomo_proto_rawDesc -) - -func file_majordomo_majordomo_proto_rawDescGZIP() []byte { - file_majordomo_majordomo_proto_rawDescOnce.Do(func() { - file_majordomo_majordomo_proto_rawDescData = protoimpl.X.CompressGZIP(file_majordomo_majordomo_proto_rawDescData) - }) - return file_majordomo_majordomo_proto_rawDescData -} - -var file_majordomo_majordomo_proto_msgTypes = make([]protoimpl.MessageInfo, 13) -var file_majordomo_majordomo_proto_goTypes = []interface{}{ - (*TODO)(nil), // 0: majordomo.TODO - (*LoginRequest)(nil), // 1: majordomo.LoginRequest - (*LoginResponse)(nil), // 2: majordomo.LoginResponse - (*ConfigurationRequest)(nil), // 3: majordomo.ConfigurationRequest - (*ConfigurationResponse)(nil), // 4: majordomo.ConfigurationResponse - (*CreateProvisionerRequest)(nil), // 5: majordomo.CreateProvisionerRequest - (*DeleteProvisionerRequest)(nil), // 6: majordomo.DeleteProvisionerRequest - (*CreateAdministratorRequest)(nil), // 7: majordomo.CreateAdministratorRequest - (*DeleteAdministratorRequest)(nil), // 8: majordomo.DeleteAdministratorRequest - (*CertificateRequest)(nil), // 9: majordomo.CertificateRequest - (*CertificateResponse)(nil), // 10: majordomo.CertificateResponse - (*SSHCertificateRequest)(nil), // 11: majordomo.SSHCertificateRequest - (*SSHCertificateResponse)(nil), // 12: majordomo.SSHCertificateResponse - (*Provisioner)(nil), // 13: majordomo.Provisioner - (*Administrator)(nil), // 14: majordomo.Administrator - (Provisioner_Type)(0), // 15: majordomo.Provisioner.Type - (*ProvisionerDetails)(nil), // 16: majordomo.ProvisionerDetails - (*Claims)(nil), // 17: majordomo.Claims - (Administrator_Type)(0), // 18: majordomo.Administrator.Type -} -var file_majordomo_majordomo_proto_depIdxs = []int32{ - 13, // 0: majordomo.ConfigurationResponse.provisioners:type_name -> majordomo.Provisioner - 14, // 1: majordomo.ConfigurationResponse.admins:type_name -> majordomo.Administrator - 15, // 2: majordomo.CreateProvisionerRequest.type:type_name -> majordomo.Provisioner.Type - 16, // 3: majordomo.CreateProvisionerRequest.details:type_name -> majordomo.ProvisionerDetails - 17, // 4: majordomo.CreateProvisionerRequest.claims:type_name -> majordomo.Claims - 18, // 5: majordomo.CreateAdministratorRequest.type:type_name -> majordomo.Administrator.Type - 1, // 6: majordomo.Majordomo.Login:input_type -> majordomo.LoginRequest - 3, // 7: majordomo.Majordomo.GetConfiguration:input_type -> majordomo.ConfigurationRequest - 3, // 8: majordomo.Majordomo.StreamConfiguration:input_type -> majordomo.ConfigurationRequest - 5, // 9: majordomo.Majordomo.CreateProvisioner:input_type -> majordomo.CreateProvisionerRequest - 6, // 10: majordomo.Majordomo.DeleteProvisioner:input_type -> majordomo.DeleteProvisionerRequest - 7, // 11: majordomo.Majordomo.CreateAdministrator:input_type -> majordomo.CreateAdministratorRequest - 8, // 12: majordomo.Majordomo.DeleteAdministrator:input_type -> majordomo.DeleteAdministratorRequest - 9, // 13: majordomo.Majordomo.PostCertificate:input_type -> majordomo.CertificateRequest - 11, // 14: majordomo.Majordomo.PostSSHCertificate:input_type -> majordomo.SSHCertificateRequest - 0, // 15: majordomo.Majordomo.RevokeCertificate:input_type -> majordomo.TODO - 0, // 16: majordomo.Majordomo.RevokeSSHCertificate:input_type -> majordomo.TODO - 2, // 17: majordomo.Majordomo.Login:output_type -> majordomo.LoginResponse - 4, // 18: majordomo.Majordomo.GetConfiguration:output_type -> majordomo.ConfigurationResponse - 4, // 19: majordomo.Majordomo.StreamConfiguration:output_type -> majordomo.ConfigurationResponse - 13, // 20: majordomo.Majordomo.CreateProvisioner:output_type -> majordomo.Provisioner - 13, // 21: majordomo.Majordomo.DeleteProvisioner:output_type -> majordomo.Provisioner - 14, // 22: majordomo.Majordomo.CreateAdministrator:output_type -> majordomo.Administrator - 14, // 23: majordomo.Majordomo.DeleteAdministrator:output_type -> majordomo.Administrator - 10, // 24: majordomo.Majordomo.PostCertificate:output_type -> majordomo.CertificateResponse - 12, // 25: majordomo.Majordomo.PostSSHCertificate:output_type -> majordomo.SSHCertificateResponse - 0, // 26: majordomo.Majordomo.RevokeCertificate:output_type -> majordomo.TODO - 0, // 27: majordomo.Majordomo.RevokeSSHCertificate:output_type -> majordomo.TODO - 17, // [17:28] is the sub-list for method output_type - 6, // [6:17] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name -} - -func init() { file_majordomo_majordomo_proto_init() } -func file_majordomo_majordomo_proto_init() { - if File_majordomo_majordomo_proto != nil { - return - } - file_majordomo_provisioners_proto_init() - if !protoimpl.UnsafeEnabled { - file_majordomo_majordomo_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TODO); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_majordomo_majordomo_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LoginRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_majordomo_majordomo_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LoginResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_majordomo_majordomo_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ConfigurationRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_majordomo_majordomo_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ConfigurationResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_majordomo_majordomo_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateProvisionerRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_majordomo_majordomo_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteProvisionerRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_majordomo_majordomo_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateAdministratorRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_majordomo_majordomo_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteAdministratorRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_majordomo_majordomo_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CertificateRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_majordomo_majordomo_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CertificateResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_majordomo_majordomo_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SSHCertificateRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_majordomo_majordomo_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SSHCertificateResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_majordomo_majordomo_proto_rawDesc, - NumEnums: 0, - NumMessages: 13, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_majordomo_majordomo_proto_goTypes, - DependencyIndexes: file_majordomo_majordomo_proto_depIdxs, - MessageInfos: file_majordomo_majordomo_proto_msgTypes, - }.Build() - File_majordomo_majordomo_proto = out.File - file_majordomo_majordomo_proto_rawDesc = nil - file_majordomo_majordomo_proto_goTypes = nil - file_majordomo_majordomo_proto_depIdxs = nil -} diff --git a/linkedca/majordomo.proto b/linkedca/majordomo.proto deleted file mode 100644 index bfdf0047..00000000 --- a/linkedca/majordomo.proto +++ /dev/null @@ -1,102 +0,0 @@ -syntax = "proto3"; - -package majordomo; - -option go_package = "github.com/smallstep/certificates/majordomo"; - -import "majordomo/provisioners.proto"; - -// Majordomo is the public service used to sync configurations to CA's and post -// certificates. -service Majordomo { - // Login creates signs a given CSR and returns the certificate that will be - // used for authentication. - rpc Login(LoginRequest) returns (LoginResponse); - - // GetConfiguration returns the full configuration of an authority. - rpc GetConfiguration(ConfigurationRequest) returns (ConfigurationResponse); - // StreamConfiguration streams the full configuration of an authority. This - // method is not yet supported. - rpc StreamConfiguration(ConfigurationRequest) returns (stream ConfigurationResponse); - - // CreateProvisioner adds a new provisioner to the majordomo authority and - // returns the proto representation. - rpc CreateProvisioner(CreateProvisionerRequest) returns (Provisioner); - // DeleteProvisioner deletes a previously created provisioner. - rpc DeleteProvisioner(DeleteProvisionerRequest) returns (Provisioner); - - // CreateAdministrator adds a new admin user to the majordomo authority. - // Admin users can add or delete provisioners. - rpc CreateAdministrator(CreateAdministratorRequest) returns (Administrator); - // DeleteAdministrator deletes a previously created admin user. - rpc DeleteAdministrator(DeleteAdministratorRequest) returns (Administrator); - - // PostCertificate sends a signed X.509 certificate to majordomo. - rpc PostCertificate(CertificateRequest) returns (CertificateResponse); - // PostSSHCertificate sends a signed SSH certificate to majordomo. - rpc PostSSHCertificate(SSHCertificateRequest) returns (SSHCertificateResponse); - // RevokeCertificate marks an X.509 certificate as revoked. - rpc RevokeCertificate(TODO) returns (TODO); - // RevokeSSHCertificate marks an SSH certificate as revoked. - rpc RevokeSSHCertificate(TODO) returns (TODO); -} - -message TODO {} - -message LoginRequest { - string authority_id = 1; - string username = 2; - string password = 3; - string pem_certificate_request = 4; -} - -message LoginResponse { - string pem_certificate = 1; - string pem_certificate_chain = 2; -} - -message ConfigurationRequest { - // todo -} - -message ConfigurationResponse { - repeated Provisioner provisioners = 1; - repeated Administrator admins = 2; -} - -message CreateProvisionerRequest { - Provisioner.Type type = 1; - string name = 2; - ProvisionerDetails details = 3; - Claims claims = 4; -} - -message DeleteProvisionerRequest { - string id = 1; -} - -message CreateAdministratorRequest { - string name = 1; - string provisioner_id = 2; - Administrator.Type type = 3; -} - -message DeleteAdministratorRequest { - string id = 1; -} -message CertificateRequest { - string pem_certificate = 1; - string pem_certificate_chain = 2; -} - -message CertificateResponse { - string id = 1; -} - -message SSHCertificateRequest { - string certificate = 1; -} - -message SSHCertificateResponse { - string id = 1; -} diff --git a/linkedca/majordomo_grpc.pb.go b/linkedca/majordomo_grpc.pb.go deleted file mode 100644 index 44fd648d..00000000 --- a/linkedca/majordomo_grpc.pb.go +++ /dev/null @@ -1,519 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. - -package majordomo - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 - -// MajordomoClient is the client API for Majordomo service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type MajordomoClient interface { - // Login creates signs a given CSR and returns the certificate that will be - // used for authentication. - Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) - // GetConfiguration returns the full configuration of an authority. - GetConfiguration(ctx context.Context, in *ConfigurationRequest, opts ...grpc.CallOption) (*ConfigurationResponse, error) - // StreamConfiguration streams the full configuration of an authority. This - // method is not yet supported. - StreamConfiguration(ctx context.Context, in *ConfigurationRequest, opts ...grpc.CallOption) (Majordomo_StreamConfigurationClient, error) - // CreateProvisioner adds a new provisioner to the majordomo authority and - // returns the proto representation. - CreateProvisioner(ctx context.Context, in *CreateProvisionerRequest, opts ...grpc.CallOption) (*Provisioner, error) - // DeleteProvisioner deletes a previously created provisioner. - DeleteProvisioner(ctx context.Context, in *DeleteProvisionerRequest, opts ...grpc.CallOption) (*Provisioner, error) - // CreateAdministrator adds a new admin user to the majordomo authority. - // Admin users can add or delete provisioners. - CreateAdministrator(ctx context.Context, in *CreateAdministratorRequest, opts ...grpc.CallOption) (*Administrator, error) - // DeleteAdministrator deletes a previously created admin user. - DeleteAdministrator(ctx context.Context, in *DeleteAdministratorRequest, opts ...grpc.CallOption) (*Administrator, error) - // PostCertificate sends a signed X.509 certificate to majordomo. - PostCertificate(ctx context.Context, in *CertificateRequest, opts ...grpc.CallOption) (*CertificateResponse, error) - // PostSSHCertificate sends a signed SSH certificate to majordomo. - PostSSHCertificate(ctx context.Context, in *SSHCertificateRequest, opts ...grpc.CallOption) (*SSHCertificateResponse, error) - // RevokeCertificate marks an X.509 certificate as revoked. - RevokeCertificate(ctx context.Context, in *TODO, opts ...grpc.CallOption) (*TODO, error) - // RevokeSSHCertificate marks an SSH certificate as revoked. - RevokeSSHCertificate(ctx context.Context, in *TODO, opts ...grpc.CallOption) (*TODO, error) -} - -type majordomoClient struct { - cc grpc.ClientConnInterface -} - -func NewMajordomoClient(cc grpc.ClientConnInterface) MajordomoClient { - return &majordomoClient{cc} -} - -func (c *majordomoClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) { - out := new(LoginResponse) - err := c.cc.Invoke(ctx, "/majordomo.Majordomo/Login", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *majordomoClient) GetConfiguration(ctx context.Context, in *ConfigurationRequest, opts ...grpc.CallOption) (*ConfigurationResponse, error) { - out := new(ConfigurationResponse) - err := c.cc.Invoke(ctx, "/majordomo.Majordomo/GetConfiguration", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *majordomoClient) StreamConfiguration(ctx context.Context, in *ConfigurationRequest, opts ...grpc.CallOption) (Majordomo_StreamConfigurationClient, error) { - stream, err := c.cc.NewStream(ctx, &Majordomo_ServiceDesc.Streams[0], "/majordomo.Majordomo/StreamConfiguration", opts...) - if err != nil { - return nil, err - } - x := &majordomoStreamConfigurationClient{stream} - if err := x.ClientStream.SendMsg(in); err != nil { - return nil, err - } - if err := x.ClientStream.CloseSend(); err != nil { - return nil, err - } - return x, nil -} - -type Majordomo_StreamConfigurationClient interface { - Recv() (*ConfigurationResponse, error) - grpc.ClientStream -} - -type majordomoStreamConfigurationClient struct { - grpc.ClientStream -} - -func (x *majordomoStreamConfigurationClient) Recv() (*ConfigurationResponse, error) { - m := new(ConfigurationResponse) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -func (c *majordomoClient) CreateProvisioner(ctx context.Context, in *CreateProvisionerRequest, opts ...grpc.CallOption) (*Provisioner, error) { - out := new(Provisioner) - err := c.cc.Invoke(ctx, "/majordomo.Majordomo/CreateProvisioner", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *majordomoClient) DeleteProvisioner(ctx context.Context, in *DeleteProvisionerRequest, opts ...grpc.CallOption) (*Provisioner, error) { - out := new(Provisioner) - err := c.cc.Invoke(ctx, "/majordomo.Majordomo/DeleteProvisioner", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *majordomoClient) CreateAdministrator(ctx context.Context, in *CreateAdministratorRequest, opts ...grpc.CallOption) (*Administrator, error) { - out := new(Administrator) - err := c.cc.Invoke(ctx, "/majordomo.Majordomo/CreateAdministrator", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *majordomoClient) DeleteAdministrator(ctx context.Context, in *DeleteAdministratorRequest, opts ...grpc.CallOption) (*Administrator, error) { - out := new(Administrator) - err := c.cc.Invoke(ctx, "/majordomo.Majordomo/DeleteAdministrator", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *majordomoClient) PostCertificate(ctx context.Context, in *CertificateRequest, opts ...grpc.CallOption) (*CertificateResponse, error) { - out := new(CertificateResponse) - err := c.cc.Invoke(ctx, "/majordomo.Majordomo/PostCertificate", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *majordomoClient) PostSSHCertificate(ctx context.Context, in *SSHCertificateRequest, opts ...grpc.CallOption) (*SSHCertificateResponse, error) { - out := new(SSHCertificateResponse) - err := c.cc.Invoke(ctx, "/majordomo.Majordomo/PostSSHCertificate", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *majordomoClient) RevokeCertificate(ctx context.Context, in *TODO, opts ...grpc.CallOption) (*TODO, error) { - out := new(TODO) - err := c.cc.Invoke(ctx, "/majordomo.Majordomo/RevokeCertificate", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *majordomoClient) RevokeSSHCertificate(ctx context.Context, in *TODO, opts ...grpc.CallOption) (*TODO, error) { - out := new(TODO) - err := c.cc.Invoke(ctx, "/majordomo.Majordomo/RevokeSSHCertificate", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// MajordomoServer is the server API for Majordomo service. -// All implementations must embed UnimplementedMajordomoServer -// for forward compatibility -type MajordomoServer interface { - // Login creates signs a given CSR and returns the certificate that will be - // used for authentication. - Login(context.Context, *LoginRequest) (*LoginResponse, error) - // GetConfiguration returns the full configuration of an authority. - GetConfiguration(context.Context, *ConfigurationRequest) (*ConfigurationResponse, error) - // StreamConfiguration streams the full configuration of an authority. This - // method is not yet supported. - StreamConfiguration(*ConfigurationRequest, Majordomo_StreamConfigurationServer) error - // CreateProvisioner adds a new provisioner to the majordomo authority and - // returns the proto representation. - CreateProvisioner(context.Context, *CreateProvisionerRequest) (*Provisioner, error) - // DeleteProvisioner deletes a previously created provisioner. - DeleteProvisioner(context.Context, *DeleteProvisionerRequest) (*Provisioner, error) - // CreateAdministrator adds a new admin user to the majordomo authority. - // Admin users can add or delete provisioners. - CreateAdministrator(context.Context, *CreateAdministratorRequest) (*Administrator, error) - // DeleteAdministrator deletes a previously created admin user. - DeleteAdministrator(context.Context, *DeleteAdministratorRequest) (*Administrator, error) - // PostCertificate sends a signed X.509 certificate to majordomo. - PostCertificate(context.Context, *CertificateRequest) (*CertificateResponse, error) - // PostSSHCertificate sends a signed SSH certificate to majordomo. - PostSSHCertificate(context.Context, *SSHCertificateRequest) (*SSHCertificateResponse, error) - // RevokeCertificate marks an X.509 certificate as revoked. - RevokeCertificate(context.Context, *TODO) (*TODO, error) - // RevokeSSHCertificate marks an SSH certificate as revoked. - RevokeSSHCertificate(context.Context, *TODO) (*TODO, error) - mustEmbedUnimplementedMajordomoServer() -} - -// UnimplementedMajordomoServer must be embedded to have forward compatible implementations. -type UnimplementedMajordomoServer struct { -} - -func (UnimplementedMajordomoServer) Login(context.Context, *LoginRequest) (*LoginResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Login not implemented") -} -func (UnimplementedMajordomoServer) GetConfiguration(context.Context, *ConfigurationRequest) (*ConfigurationResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetConfiguration not implemented") -} -func (UnimplementedMajordomoServer) StreamConfiguration(*ConfigurationRequest, Majordomo_StreamConfigurationServer) error { - return status.Errorf(codes.Unimplemented, "method StreamConfiguration not implemented") -} -func (UnimplementedMajordomoServer) CreateProvisioner(context.Context, *CreateProvisionerRequest) (*Provisioner, error) { - return nil, status.Errorf(codes.Unimplemented, "method CreateProvisioner not implemented") -} -func (UnimplementedMajordomoServer) DeleteProvisioner(context.Context, *DeleteProvisionerRequest) (*Provisioner, error) { - return nil, status.Errorf(codes.Unimplemented, "method DeleteProvisioner not implemented") -} -func (UnimplementedMajordomoServer) CreateAdministrator(context.Context, *CreateAdministratorRequest) (*Administrator, error) { - return nil, status.Errorf(codes.Unimplemented, "method CreateAdministrator not implemented") -} -func (UnimplementedMajordomoServer) DeleteAdministrator(context.Context, *DeleteAdministratorRequest) (*Administrator, error) { - return nil, status.Errorf(codes.Unimplemented, "method DeleteAdministrator not implemented") -} -func (UnimplementedMajordomoServer) PostCertificate(context.Context, *CertificateRequest) (*CertificateResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method PostCertificate not implemented") -} -func (UnimplementedMajordomoServer) PostSSHCertificate(context.Context, *SSHCertificateRequest) (*SSHCertificateResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method PostSSHCertificate not implemented") -} -func (UnimplementedMajordomoServer) RevokeCertificate(context.Context, *TODO) (*TODO, error) { - return nil, status.Errorf(codes.Unimplemented, "method RevokeCertificate not implemented") -} -func (UnimplementedMajordomoServer) RevokeSSHCertificate(context.Context, *TODO) (*TODO, error) { - return nil, status.Errorf(codes.Unimplemented, "method RevokeSSHCertificate not implemented") -} -func (UnimplementedMajordomoServer) mustEmbedUnimplementedMajordomoServer() {} - -// UnsafeMajordomoServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to MajordomoServer will -// result in compilation errors. -type UnsafeMajordomoServer interface { - mustEmbedUnimplementedMajordomoServer() -} - -func RegisterMajordomoServer(s grpc.ServiceRegistrar, srv MajordomoServer) { - s.RegisterService(&Majordomo_ServiceDesc, srv) -} - -func _Majordomo_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(LoginRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MajordomoServer).Login(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/majordomo.Majordomo/Login", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MajordomoServer).Login(ctx, req.(*LoginRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Majordomo_GetConfiguration_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ConfigurationRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MajordomoServer).GetConfiguration(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/majordomo.Majordomo/GetConfiguration", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MajordomoServer).GetConfiguration(ctx, req.(*ConfigurationRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Majordomo_StreamConfiguration_Handler(srv interface{}, stream grpc.ServerStream) error { - m := new(ConfigurationRequest) - if err := stream.RecvMsg(m); err != nil { - return err - } - return srv.(MajordomoServer).StreamConfiguration(m, &majordomoStreamConfigurationServer{stream}) -} - -type Majordomo_StreamConfigurationServer interface { - Send(*ConfigurationResponse) error - grpc.ServerStream -} - -type majordomoStreamConfigurationServer struct { - grpc.ServerStream -} - -func (x *majordomoStreamConfigurationServer) Send(m *ConfigurationResponse) error { - return x.ServerStream.SendMsg(m) -} - -func _Majordomo_CreateProvisioner_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CreateProvisionerRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MajordomoServer).CreateProvisioner(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/majordomo.Majordomo/CreateProvisioner", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MajordomoServer).CreateProvisioner(ctx, req.(*CreateProvisionerRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Majordomo_DeleteProvisioner_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DeleteProvisionerRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MajordomoServer).DeleteProvisioner(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/majordomo.Majordomo/DeleteProvisioner", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MajordomoServer).DeleteProvisioner(ctx, req.(*DeleteProvisionerRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Majordomo_CreateAdministrator_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CreateAdministratorRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MajordomoServer).CreateAdministrator(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/majordomo.Majordomo/CreateAdministrator", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MajordomoServer).CreateAdministrator(ctx, req.(*CreateAdministratorRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Majordomo_DeleteAdministrator_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DeleteAdministratorRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MajordomoServer).DeleteAdministrator(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/majordomo.Majordomo/DeleteAdministrator", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MajordomoServer).DeleteAdministrator(ctx, req.(*DeleteAdministratorRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Majordomo_PostCertificate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CertificateRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MajordomoServer).PostCertificate(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/majordomo.Majordomo/PostCertificate", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MajordomoServer).PostCertificate(ctx, req.(*CertificateRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Majordomo_PostSSHCertificate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(SSHCertificateRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MajordomoServer).PostSSHCertificate(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/majordomo.Majordomo/PostSSHCertificate", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MajordomoServer).PostSSHCertificate(ctx, req.(*SSHCertificateRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Majordomo_RevokeCertificate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(TODO) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MajordomoServer).RevokeCertificate(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/majordomo.Majordomo/RevokeCertificate", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MajordomoServer).RevokeCertificate(ctx, req.(*TODO)) - } - return interceptor(ctx, in, info, handler) -} - -func _Majordomo_RevokeSSHCertificate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(TODO) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MajordomoServer).RevokeSSHCertificate(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/majordomo.Majordomo/RevokeSSHCertificate", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MajordomoServer).RevokeSSHCertificate(ctx, req.(*TODO)) - } - return interceptor(ctx, in, info, handler) -} - -// Majordomo_ServiceDesc is the grpc.ServiceDesc for Majordomo service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var Majordomo_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "majordomo.Majordomo", - HandlerType: (*MajordomoServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "Login", - Handler: _Majordomo_Login_Handler, - }, - { - MethodName: "GetConfiguration", - Handler: _Majordomo_GetConfiguration_Handler, - }, - { - MethodName: "CreateProvisioner", - Handler: _Majordomo_CreateProvisioner_Handler, - }, - { - MethodName: "DeleteProvisioner", - Handler: _Majordomo_DeleteProvisioner_Handler, - }, - { - MethodName: "CreateAdministrator", - Handler: _Majordomo_CreateAdministrator_Handler, - }, - { - MethodName: "DeleteAdministrator", - Handler: _Majordomo_DeleteAdministrator_Handler, - }, - { - MethodName: "PostCertificate", - Handler: _Majordomo_PostCertificate_Handler, - }, - { - MethodName: "PostSSHCertificate", - Handler: _Majordomo_PostSSHCertificate_Handler, - }, - { - MethodName: "RevokeCertificate", - Handler: _Majordomo_RevokeCertificate_Handler, - }, - { - MethodName: "RevokeSSHCertificate", - Handler: _Majordomo_RevokeSSHCertificate_Handler, - }, - }, - Streams: []grpc.StreamDesc{ - { - StreamName: "StreamConfiguration", - Handler: _Majordomo_StreamConfiguration_Handler, - ServerStreams: true, - }, - }, - Metadata: "majordomo/majordomo.proto", -} diff --git a/linkedca/provisioners.pb.go b/linkedca/provisioners.pb.go index ccdd6f2f..1a8eb681 100644 --- a/linkedca/provisioners.pb.go +++ b/linkedca/provisioners.pb.go @@ -2,9 +2,9 @@ // versions: // protoc-gen-go v1.26.0 // protoc v3.15.8 -// source: majordomo/provisioners.proto +// source: linkedca/provisioners.proto -package majordomo +package linkedca import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" @@ -20,55 +20,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -type Administrator_Type int32 - -const ( - Administrator_UNKNOWN Administrator_Type = 0 - Administrator_ADMIN Administrator_Type = 1 - Administrator_SUPER_ADMIN Administrator_Type = 2 -) - -// Enum value maps for Administrator_Type. -var ( - Administrator_Type_name = map[int32]string{ - 0: "UNKNOWN", - 1: "ADMIN", - 2: "SUPER_ADMIN", - } - Administrator_Type_value = map[string]int32{ - "UNKNOWN": 0, - "ADMIN": 1, - "SUPER_ADMIN": 2, - } -) - -func (x Administrator_Type) Enum() *Administrator_Type { - p := new(Administrator_Type) - *p = x - return p -} - -func (x Administrator_Type) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (Administrator_Type) Descriptor() protoreflect.EnumDescriptor { - return file_majordomo_provisioners_proto_enumTypes[0].Descriptor() -} - -func (Administrator_Type) Type() protoreflect.EnumType { - return &file_majordomo_provisioners_proto_enumTypes[0] -} - -func (x Administrator_Type) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use Administrator_Type.Descriptor instead. -func (Administrator_Type) EnumDescriptor() ([]byte, []int) { - return file_majordomo_provisioners_proto_rawDescGZIP(), []int{0, 0} -} - type Provisioner_Type int32 const ( @@ -123,11 +74,11 @@ func (x Provisioner_Type) String() string { } func (Provisioner_Type) Descriptor() protoreflect.EnumDescriptor { - return file_majordomo_provisioners_proto_enumTypes[1].Descriptor() + return file_linkedca_provisioners_proto_enumTypes[0].Descriptor() } func (Provisioner_Type) Type() protoreflect.EnumType { - return &file_majordomo_provisioners_proto_enumTypes[1] + return &file_linkedca_provisioners_proto_enumTypes[0] } func (x Provisioner_Type) Number() protoreflect.EnumNumber { @@ -136,86 +87,7 @@ func (x Provisioner_Type) Number() protoreflect.EnumNumber { // Deprecated: Use Provisioner_Type.Descriptor instead. func (Provisioner_Type) EnumDescriptor() ([]byte, []int) { - return file_majordomo_provisioners_proto_rawDescGZIP(), []int{1, 0} -} - -type Administrator struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - AuthorityId string `protobuf:"bytes,2,opt,name=authority_id,json=authorityId,proto3" json:"authority_id,omitempty"` - Subject string `protobuf:"bytes,3,opt,name=subject,proto3" json:"subject,omitempty"` - ProvisionerId string `protobuf:"bytes,4,opt,name=provisioner_id,json=provisionerId,proto3" json:"provisioner_id,omitempty"` - Type Administrator_Type `protobuf:"varint,5,opt,name=type,proto3,enum=majordomo.Administrator_Type" json:"type,omitempty"` -} - -func (x *Administrator) Reset() { - *x = Administrator{} - if protoimpl.UnsafeEnabled { - mi := &file_majordomo_provisioners_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Administrator) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Administrator) ProtoMessage() {} - -func (x *Administrator) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_provisioners_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Administrator.ProtoReflect.Descriptor instead. -func (*Administrator) Descriptor() ([]byte, []int) { - return file_majordomo_provisioners_proto_rawDescGZIP(), []int{0} -} - -func (x *Administrator) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *Administrator) GetAuthorityId() string { - if x != nil { - return x.AuthorityId - } - return "" -} - -func (x *Administrator) GetSubject() string { - if x != nil { - return x.Subject - } - return "" -} - -func (x *Administrator) GetProvisionerId() string { - if x != nil { - return x.ProvisionerId - } - return "" -} - -func (x *Administrator) GetType() Administrator_Type { - if x != nil { - return x.Type - } - return Administrator_UNKNOWN + return file_linkedca_provisioners_proto_rawDescGZIP(), []int{0, 0} } type Provisioner struct { @@ -225,7 +97,7 @@ type Provisioner struct { Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` AuthorityId string `protobuf:"bytes,2,opt,name=authority_id,json=authorityId,proto3" json:"authority_id,omitempty"` - Type Provisioner_Type `protobuf:"varint,3,opt,name=type,proto3,enum=majordomo.Provisioner_Type" json:"type,omitempty"` + Type Provisioner_Type `protobuf:"varint,3,opt,name=type,proto3,enum=linkedca.Provisioner_Type" json:"type,omitempty"` Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` Details *ProvisionerDetails `protobuf:"bytes,5,opt,name=details,proto3" json:"details,omitempty"` Claims *Claims `protobuf:"bytes,6,opt,name=claims,proto3" json:"claims,omitempty"` @@ -238,7 +110,7 @@ type Provisioner struct { func (x *Provisioner) Reset() { *x = Provisioner{} if protoimpl.UnsafeEnabled { - mi := &file_majordomo_provisioners_proto_msgTypes[1] + mi := &file_linkedca_provisioners_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -251,7 +123,7 @@ func (x *Provisioner) String() string { func (*Provisioner) ProtoMessage() {} func (x *Provisioner) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_provisioners_proto_msgTypes[1] + mi := &file_linkedca_provisioners_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -264,7 +136,7 @@ func (x *Provisioner) ProtoReflect() protoreflect.Message { // Deprecated: Use Provisioner.ProtoReflect.Descriptor instead. func (*Provisioner) Descriptor() ([]byte, []int) { - return file_majordomo_provisioners_proto_rawDescGZIP(), []int{1} + return file_linkedca_provisioners_proto_rawDescGZIP(), []int{0} } func (x *Provisioner) GetId() string { @@ -358,7 +230,7 @@ type ProvisionerDetails struct { func (x *ProvisionerDetails) Reset() { *x = ProvisionerDetails{} if protoimpl.UnsafeEnabled { - mi := &file_majordomo_provisioners_proto_msgTypes[2] + mi := &file_linkedca_provisioners_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -371,7 +243,7 @@ func (x *ProvisionerDetails) String() string { func (*ProvisionerDetails) ProtoMessage() {} func (x *ProvisionerDetails) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_provisioners_proto_msgTypes[2] + mi := &file_linkedca_provisioners_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -384,7 +256,7 @@ func (x *ProvisionerDetails) ProtoReflect() protoreflect.Message { // Deprecated: Use ProvisionerDetails.ProtoReflect.Descriptor instead. func (*ProvisionerDetails) Descriptor() ([]byte, []int) { - return file_majordomo_provisioners_proto_rawDescGZIP(), []int{2} + return file_linkedca_provisioners_proto_rawDescGZIP(), []int{1} } func (m *ProvisionerDetails) GetData() isProvisionerDetails_Data { @@ -526,7 +398,7 @@ type ProvisionerList struct { func (x *ProvisionerList) Reset() { *x = ProvisionerList{} if protoimpl.UnsafeEnabled { - mi := &file_majordomo_provisioners_proto_msgTypes[3] + mi := &file_linkedca_provisioners_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -539,7 +411,7 @@ func (x *ProvisionerList) String() string { func (*ProvisionerList) ProtoMessage() {} func (x *ProvisionerList) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_provisioners_proto_msgTypes[3] + mi := &file_linkedca_provisioners_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -552,7 +424,7 @@ func (x *ProvisionerList) ProtoReflect() protoreflect.Message { // Deprecated: Use ProvisionerList.ProtoReflect.Descriptor instead. func (*ProvisionerList) Descriptor() ([]byte, []int) { - return file_majordomo_provisioners_proto_rawDescGZIP(), []int{3} + return file_linkedca_provisioners_proto_rawDescGZIP(), []int{2} } func (x *ProvisionerList) GetProvisioners() []*Provisioner { @@ -575,7 +447,7 @@ type Claims struct { func (x *Claims) Reset() { *x = Claims{} if protoimpl.UnsafeEnabled { - mi := &file_majordomo_provisioners_proto_msgTypes[4] + mi := &file_linkedca_provisioners_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -588,7 +460,7 @@ func (x *Claims) String() string { func (*Claims) ProtoMessage() {} func (x *Claims) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_provisioners_proto_msgTypes[4] + mi := &file_linkedca_provisioners_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -601,7 +473,7 @@ func (x *Claims) ProtoReflect() protoreflect.Message { // Deprecated: Use Claims.ProtoReflect.Descriptor instead. func (*Claims) Descriptor() ([]byte, []int) { - return file_majordomo_provisioners_proto_rawDescGZIP(), []int{4} + return file_linkedca_provisioners_proto_rawDescGZIP(), []int{3} } func (x *Claims) GetX509() *X509Claims { @@ -637,7 +509,7 @@ type X509Claims struct { func (x *X509Claims) Reset() { *x = X509Claims{} if protoimpl.UnsafeEnabled { - mi := &file_majordomo_provisioners_proto_msgTypes[5] + mi := &file_linkedca_provisioners_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -650,7 +522,7 @@ func (x *X509Claims) String() string { func (*X509Claims) ProtoMessage() {} func (x *X509Claims) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_provisioners_proto_msgTypes[5] + mi := &file_linkedca_provisioners_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -663,7 +535,7 @@ func (x *X509Claims) ProtoReflect() protoreflect.Message { // Deprecated: Use X509Claims.ProtoReflect.Descriptor instead. func (*X509Claims) Descriptor() ([]byte, []int) { - return file_majordomo_provisioners_proto_rawDescGZIP(), []int{5} + return file_linkedca_provisioners_proto_rawDescGZIP(), []int{4} } func (x *X509Claims) GetEnabled() bool { @@ -693,7 +565,7 @@ type SSHClaims struct { func (x *SSHClaims) Reset() { *x = SSHClaims{} if protoimpl.UnsafeEnabled { - mi := &file_majordomo_provisioners_proto_msgTypes[6] + mi := &file_linkedca_provisioners_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -706,7 +578,7 @@ func (x *SSHClaims) String() string { func (*SSHClaims) ProtoMessage() {} func (x *SSHClaims) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_provisioners_proto_msgTypes[6] + mi := &file_linkedca_provisioners_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -719,7 +591,7 @@ func (x *SSHClaims) ProtoReflect() protoreflect.Message { // Deprecated: Use SSHClaims.ProtoReflect.Descriptor instead. func (*SSHClaims) Descriptor() ([]byte, []int) { - return file_majordomo_provisioners_proto_rawDescGZIP(), []int{6} + return file_linkedca_provisioners_proto_rawDescGZIP(), []int{5} } func (x *SSHClaims) GetEnabled() bool { @@ -756,7 +628,7 @@ type Durations struct { func (x *Durations) Reset() { *x = Durations{} if protoimpl.UnsafeEnabled { - mi := &file_majordomo_provisioners_proto_msgTypes[7] + mi := &file_linkedca_provisioners_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -769,7 +641,7 @@ func (x *Durations) String() string { func (*Durations) ProtoMessage() {} func (x *Durations) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_provisioners_proto_msgTypes[7] + mi := &file_linkedca_provisioners_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -782,7 +654,7 @@ func (x *Durations) ProtoReflect() protoreflect.Message { // Deprecated: Use Durations.ProtoReflect.Descriptor instead. func (*Durations) Descriptor() ([]byte, []int) { - return file_majordomo_provisioners_proto_rawDescGZIP(), []int{7} + return file_linkedca_provisioners_proto_rawDescGZIP(), []int{6} } func (x *Durations) GetDefault() string { @@ -818,7 +690,7 @@ type JWKProvisioner struct { func (x *JWKProvisioner) Reset() { *x = JWKProvisioner{} if protoimpl.UnsafeEnabled { - mi := &file_majordomo_provisioners_proto_msgTypes[8] + mi := &file_linkedca_provisioners_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -831,7 +703,7 @@ func (x *JWKProvisioner) String() string { func (*JWKProvisioner) ProtoMessage() {} func (x *JWKProvisioner) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_provisioners_proto_msgTypes[8] + mi := &file_linkedca_provisioners_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -844,7 +716,7 @@ func (x *JWKProvisioner) ProtoReflect() protoreflect.Message { // Deprecated: Use JWKProvisioner.ProtoReflect.Descriptor instead. func (*JWKProvisioner) Descriptor() ([]byte, []int) { - return file_majordomo_provisioners_proto_rawDescGZIP(), []int{8} + return file_linkedca_provisioners_proto_rawDescGZIP(), []int{7} } func (x *JWKProvisioner) GetPublicKey() []byte { @@ -879,7 +751,7 @@ type OIDCProvisioner struct { func (x *OIDCProvisioner) Reset() { *x = OIDCProvisioner{} if protoimpl.UnsafeEnabled { - mi := &file_majordomo_provisioners_proto_msgTypes[9] + mi := &file_linkedca_provisioners_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -892,7 +764,7 @@ func (x *OIDCProvisioner) String() string { func (*OIDCProvisioner) ProtoMessage() {} func (x *OIDCProvisioner) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_provisioners_proto_msgTypes[9] + mi := &file_linkedca_provisioners_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -905,7 +777,7 @@ func (x *OIDCProvisioner) ProtoReflect() protoreflect.Message { // Deprecated: Use OIDCProvisioner.ProtoReflect.Descriptor instead. func (*OIDCProvisioner) Descriptor() ([]byte, []int) { - return file_majordomo_provisioners_proto_rawDescGZIP(), []int{9} + return file_linkedca_provisioners_proto_rawDescGZIP(), []int{8} } func (x *OIDCProvisioner) GetClientId() string { @@ -979,7 +851,7 @@ type GCPProvisioner struct { func (x *GCPProvisioner) Reset() { *x = GCPProvisioner{} if protoimpl.UnsafeEnabled { - mi := &file_majordomo_provisioners_proto_msgTypes[10] + mi := &file_linkedca_provisioners_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -992,7 +864,7 @@ func (x *GCPProvisioner) String() string { func (*GCPProvisioner) ProtoMessage() {} func (x *GCPProvisioner) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_provisioners_proto_msgTypes[10] + mi := &file_linkedca_provisioners_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1005,7 +877,7 @@ func (x *GCPProvisioner) ProtoReflect() protoreflect.Message { // Deprecated: Use GCPProvisioner.ProtoReflect.Descriptor instead. func (*GCPProvisioner) Descriptor() ([]byte, []int) { - return file_majordomo_provisioners_proto_rawDescGZIP(), []int{10} + return file_linkedca_provisioners_proto_rawDescGZIP(), []int{9} } func (x *GCPProvisioner) GetServiceAccounts() []string { @@ -1057,7 +929,7 @@ type AWSProvisioner struct { func (x *AWSProvisioner) Reset() { *x = AWSProvisioner{} if protoimpl.UnsafeEnabled { - mi := &file_majordomo_provisioners_proto_msgTypes[11] + mi := &file_linkedca_provisioners_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1070,7 +942,7 @@ func (x *AWSProvisioner) String() string { func (*AWSProvisioner) ProtoMessage() {} func (x *AWSProvisioner) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_provisioners_proto_msgTypes[11] + mi := &file_linkedca_provisioners_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1083,7 +955,7 @@ func (x *AWSProvisioner) ProtoReflect() protoreflect.Message { // Deprecated: Use AWSProvisioner.ProtoReflect.Descriptor instead. func (*AWSProvisioner) Descriptor() ([]byte, []int) { - return file_majordomo_provisioners_proto_rawDescGZIP(), []int{11} + return file_linkedca_provisioners_proto_rawDescGZIP(), []int{10} } func (x *AWSProvisioner) GetAccounts() []string { @@ -1129,7 +1001,7 @@ type AzureProvisioner struct { func (x *AzureProvisioner) Reset() { *x = AzureProvisioner{} if protoimpl.UnsafeEnabled { - mi := &file_majordomo_provisioners_proto_msgTypes[12] + mi := &file_linkedca_provisioners_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1142,7 +1014,7 @@ func (x *AzureProvisioner) String() string { func (*AzureProvisioner) ProtoMessage() {} func (x *AzureProvisioner) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_provisioners_proto_msgTypes[12] + mi := &file_linkedca_provisioners_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1155,7 +1027,7 @@ func (x *AzureProvisioner) ProtoReflect() protoreflect.Message { // Deprecated: Use AzureProvisioner.ProtoReflect.Descriptor instead. func (*AzureProvisioner) Descriptor() ([]byte, []int) { - return file_majordomo_provisioners_proto_rawDescGZIP(), []int{12} + return file_linkedca_provisioners_proto_rawDescGZIP(), []int{11} } func (x *AzureProvisioner) GetTenantId() string { @@ -1204,7 +1076,7 @@ type ACMEProvisioner struct { func (x *ACMEProvisioner) Reset() { *x = ACMEProvisioner{} if protoimpl.UnsafeEnabled { - mi := &file_majordomo_provisioners_proto_msgTypes[13] + mi := &file_linkedca_provisioners_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1217,7 +1089,7 @@ func (x *ACMEProvisioner) String() string { func (*ACMEProvisioner) ProtoMessage() {} func (x *ACMEProvisioner) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_provisioners_proto_msgTypes[13] + mi := &file_linkedca_provisioners_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1230,7 +1102,7 @@ func (x *ACMEProvisioner) ProtoReflect() protoreflect.Message { // Deprecated: Use ACMEProvisioner.ProtoReflect.Descriptor instead. func (*ACMEProvisioner) Descriptor() ([]byte, []int) { - return file_majordomo_provisioners_proto_rawDescGZIP(), []int{13} + return file_linkedca_provisioners_proto_rawDescGZIP(), []int{12} } func (x *ACMEProvisioner) GetForceCn() bool { @@ -1251,7 +1123,7 @@ type X5CProvisioner struct { func (x *X5CProvisioner) Reset() { *x = X5CProvisioner{} if protoimpl.UnsafeEnabled { - mi := &file_majordomo_provisioners_proto_msgTypes[14] + mi := &file_linkedca_provisioners_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1264,7 +1136,7 @@ func (x *X5CProvisioner) String() string { func (*X5CProvisioner) ProtoMessage() {} func (x *X5CProvisioner) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_provisioners_proto_msgTypes[14] + mi := &file_linkedca_provisioners_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1277,7 +1149,7 @@ func (x *X5CProvisioner) ProtoReflect() protoreflect.Message { // Deprecated: Use X5CProvisioner.ProtoReflect.Descriptor instead. func (*X5CProvisioner) Descriptor() ([]byte, []int) { - return file_majordomo_provisioners_proto_rawDescGZIP(), []int{14} + return file_linkedca_provisioners_proto_rawDescGZIP(), []int{13} } func (x *X5CProvisioner) GetRoots() [][]byte { @@ -1298,7 +1170,7 @@ type K8SSAProvisioner struct { func (x *K8SSAProvisioner) Reset() { *x = K8SSAProvisioner{} if protoimpl.UnsafeEnabled { - mi := &file_majordomo_provisioners_proto_msgTypes[15] + mi := &file_linkedca_provisioners_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1311,7 +1183,7 @@ func (x *K8SSAProvisioner) String() string { func (*K8SSAProvisioner) ProtoMessage() {} func (x *K8SSAProvisioner) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_provisioners_proto_msgTypes[15] + mi := &file_linkedca_provisioners_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1324,7 +1196,7 @@ func (x *K8SSAProvisioner) ProtoReflect() protoreflect.Message { // Deprecated: Use K8SSAProvisioner.ProtoReflect.Descriptor instead. func (*K8SSAProvisioner) Descriptor() ([]byte, []int) { - return file_majordomo_provisioners_proto_rawDescGZIP(), []int{15} + return file_linkedca_provisioners_proto_rawDescGZIP(), []int{14} } func (x *K8SSAProvisioner) GetPublicKeys() [][]byte { @@ -1343,7 +1215,7 @@ type SSHPOPProvisioner struct { func (x *SSHPOPProvisioner) Reset() { *x = SSHPOPProvisioner{} if protoimpl.UnsafeEnabled { - mi := &file_majordomo_provisioners_proto_msgTypes[16] + mi := &file_linkedca_provisioners_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1356,7 +1228,7 @@ func (x *SSHPOPProvisioner) String() string { func (*SSHPOPProvisioner) ProtoMessage() {} func (x *SSHPOPProvisioner) ProtoReflect() protoreflect.Message { - mi := &file_majordomo_provisioners_proto_msgTypes[16] + mi := &file_linkedca_provisioners_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1369,287 +1241,256 @@ func (x *SSHPOPProvisioner) ProtoReflect() protoreflect.Message { // Deprecated: Use SSHPOPProvisioner.ProtoReflect.Descriptor instead. func (*SSHPOPProvisioner) Descriptor() ([]byte, []int) { - return file_majordomo_provisioners_proto_rawDescGZIP(), []int{16} + return file_linkedca_provisioners_proto_rawDescGZIP(), []int{15} } -var File_majordomo_provisioners_proto protoreflect.FileDescriptor +var File_linkedca_provisioners_proto protoreflect.FileDescriptor -var file_majordomo_provisioners_proto_rawDesc = []byte{ - 0x0a, 0x1c, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, - 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x22, 0xe7, 0x01, 0x0a, 0x0d, 0x41, 0x64, - 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x18, - 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x31, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, - 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, - 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x22, 0x2f, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, - 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x44, 0x4d, 0x49, 0x4e, - 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x55, 0x50, 0x45, 0x52, 0x5f, 0x41, 0x44, 0x4d, 0x49, - 0x4e, 0x10, 0x02, 0x22, 0xf7, 0x03, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x2f, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, - 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x79, 0x70, - 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x64, - 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6d, - 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x07, 0x64, 0x65, 0x74, - 0x61, 0x69, 0x6c, 0x73, 0x12, 0x29, 0x0a, 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, - 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, - 0x23, 0x0a, 0x0d, 0x78, 0x35, 0x30, 0x39, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x78, 0x35, 0x30, 0x39, 0x54, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x78, 0x35, 0x30, 0x39, 0x5f, 0x74, 0x65, 0x6d, - 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x10, 0x78, 0x35, 0x30, 0x39, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x61, - 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x73, 0x68, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, - 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x73, 0x73, 0x68, 0x54, 0x65, 0x6d, - 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x73, 0x68, 0x5f, 0x74, 0x65, 0x6d, - 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x0f, 0x73, 0x73, 0x68, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, - 0x61, 0x22, 0x6a, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4f, - 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4a, 0x57, 0x4b, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, - 0x4f, 0x49, 0x44, 0x43, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x47, 0x43, 0x50, 0x10, 0x03, 0x12, - 0x07, 0x0a, 0x03, 0x41, 0x57, 0x53, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x5a, 0x55, 0x52, - 0x45, 0x10, 0x05, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x43, 0x4d, 0x45, 0x10, 0x06, 0x12, 0x07, 0x0a, - 0x03, 0x58, 0x35, 0x43, 0x10, 0x07, 0x12, 0x09, 0x0a, 0x05, 0x4b, 0x38, 0x53, 0x53, 0x41, 0x10, - 0x08, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x53, 0x48, 0x50, 0x4f, 0x50, 0x10, 0x09, 0x22, 0xde, 0x03, - 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x74, - 0x61, 0x69, 0x6c, 0x73, 0x12, 0x2d, 0x0a, 0x03, 0x4a, 0x57, 0x4b, 0x18, 0x14, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x4a, 0x57, +var file_linkedca_provisioners_proto_rawDesc = []byte{ + 0x0a, 0x1b, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x6c, + 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x22, 0xf4, 0x03, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, + 0x64, 0x63, 0x61, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x36, + 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1c, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x07, 0x64, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x28, 0x0a, 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, + 0x61, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, + 0x12, 0x23, 0x0a, 0x0d, 0x78, 0x35, 0x30, 0x39, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x78, 0x35, 0x30, 0x39, 0x54, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x78, 0x35, 0x30, 0x39, 0x5f, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x10, 0x78, 0x35, 0x30, 0x39, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, + 0x61, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x73, 0x68, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x73, 0x73, 0x68, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x73, 0x68, 0x5f, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0f, 0x73, 0x73, 0x68, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x61, + 0x74, 0x61, 0x22, 0x6a, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, + 0x4f, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4a, 0x57, 0x4b, 0x10, 0x01, 0x12, 0x08, 0x0a, + 0x04, 0x4f, 0x49, 0x44, 0x43, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x47, 0x43, 0x50, 0x10, 0x03, + 0x12, 0x07, 0x0a, 0x03, 0x41, 0x57, 0x53, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x5a, 0x55, + 0x52, 0x45, 0x10, 0x05, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x43, 0x4d, 0x45, 0x10, 0x06, 0x12, 0x07, + 0x0a, 0x03, 0x58, 0x35, 0x43, 0x10, 0x07, 0x12, 0x09, 0x0a, 0x05, 0x4b, 0x38, 0x53, 0x53, 0x41, + 0x10, 0x08, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x53, 0x48, 0x50, 0x4f, 0x50, 0x10, 0x09, 0x22, 0xd5, + 0x03, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x65, + 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x2c, 0x0a, 0x03, 0x4a, 0x57, 0x4b, 0x18, 0x14, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x4a, 0x57, 0x4b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, - 0x4a, 0x57, 0x4b, 0x12, 0x30, 0x0a, 0x04, 0x4f, 0x49, 0x44, 0x43, 0x18, 0x15, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x4f, 0x49, - 0x44, 0x43, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, - 0x04, 0x4f, 0x49, 0x44, 0x43, 0x12, 0x2d, 0x0a, 0x03, 0x47, 0x43, 0x50, 0x18, 0x16, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x47, - 0x43, 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, - 0x03, 0x47, 0x43, 0x50, 0x12, 0x2d, 0x0a, 0x03, 0x41, 0x57, 0x53, 0x18, 0x17, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x41, 0x57, - 0x53, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, - 0x41, 0x57, 0x53, 0x12, 0x33, 0x0a, 0x05, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x18, 0x18, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x41, - 0x7a, 0x75, 0x72, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, - 0x00, 0x52, 0x05, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x12, 0x30, 0x0a, 0x04, 0x41, 0x43, 0x4d, 0x45, - 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, - 0x6d, 0x6f, 0x2e, 0x41, 0x43, 0x4d, 0x45, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x48, 0x00, 0x52, 0x04, 0x41, 0x43, 0x4d, 0x45, 0x12, 0x2d, 0x0a, 0x03, 0x58, 0x35, - 0x43, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, - 0x6f, 0x6d, 0x6f, 0x2e, 0x58, 0x35, 0x43, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, 0x58, 0x35, 0x43, 0x12, 0x33, 0x0a, 0x05, 0x4b, 0x38, 0x73, - 0x53, 0x41, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, - 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x4b, 0x38, 0x73, 0x53, 0x41, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x05, 0x4b, 0x38, 0x73, 0x53, 0x41, 0x12, 0x36, - 0x0a, 0x06, 0x53, 0x53, 0x48, 0x50, 0x4f, 0x50, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, - 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x53, 0x53, 0x48, 0x50, 0x4f, - 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x06, - 0x53, 0x53, 0x48, 0x50, 0x4f, 0x50, 0x42, 0x06, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x4d, - 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x4c, 0x69, 0x73, - 0x74, 0x12, 0x3a, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, - 0x6f, 0x6d, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x52, - 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x22, 0x84, 0x01, - 0x0a, 0x06, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x29, 0x0a, 0x04, 0x78, 0x35, 0x30, 0x39, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, - 0x6d, 0x6f, 0x2e, 0x58, 0x35, 0x30, 0x39, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x04, 0x78, - 0x35, 0x30, 0x39, 0x12, 0x26, 0x0a, 0x03, 0x73, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x14, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x53, 0x53, 0x48, - 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x03, 0x73, 0x73, 0x68, 0x12, 0x27, 0x0a, 0x0f, 0x64, - 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x6e, - 0x65, 0x77, 0x61, 0x6c, 0x22, 0x5a, 0x0a, 0x0a, 0x58, 0x35, 0x30, 0x39, 0x43, 0x6c, 0x61, 0x69, + 0x4a, 0x57, 0x4b, 0x12, 0x2f, 0x0a, 0x04, 0x4f, 0x49, 0x44, 0x43, 0x18, 0x15, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x4f, 0x49, 0x44, + 0x43, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x04, + 0x4f, 0x49, 0x44, 0x43, 0x12, 0x2c, 0x0a, 0x03, 0x47, 0x43, 0x50, 0x18, 0x16, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x47, 0x43, 0x50, + 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, 0x47, + 0x43, 0x50, 0x12, 0x2c, 0x0a, 0x03, 0x41, 0x57, 0x53, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x18, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x41, 0x57, 0x53, 0x50, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, 0x41, 0x57, 0x53, + 0x12, 0x32, 0x0a, 0x05, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x41, 0x7a, 0x75, 0x72, 0x65, + 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x05, 0x41, + 0x7a, 0x75, 0x72, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x41, 0x43, 0x4d, 0x45, 0x18, 0x19, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x41, 0x43, + 0x4d, 0x45, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, + 0x04, 0x41, 0x43, 0x4d, 0x45, 0x12, 0x2c, 0x0a, 0x03, 0x58, 0x35, 0x43, 0x18, 0x1a, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x58, 0x35, + 0x43, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, + 0x58, 0x35, 0x43, 0x12, 0x32, 0x0a, 0x05, 0x4b, 0x38, 0x73, 0x53, 0x41, 0x18, 0x1b, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x4b, 0x38, + 0x73, 0x53, 0x41, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, + 0x52, 0x05, 0x4b, 0x38, 0x73, 0x53, 0x41, 0x12, 0x35, 0x0a, 0x06, 0x53, 0x53, 0x48, 0x50, 0x4f, + 0x50, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, + 0x63, 0x61, 0x2e, 0x53, 0x53, 0x48, 0x50, 0x4f, 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x06, 0x53, 0x53, 0x48, 0x50, 0x4f, 0x50, 0x42, 0x06, + 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x4c, 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x39, 0x0a, 0x0c, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x06, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, + 0x28, 0x0a, 0x04, 0x78, 0x35, 0x30, 0x39, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, + 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x58, 0x35, 0x30, 0x39, 0x43, 0x6c, 0x61, + 0x69, 0x6d, 0x73, 0x52, 0x04, 0x78, 0x35, 0x30, 0x39, 0x12, 0x25, 0x0a, 0x03, 0x73, 0x73, 0x68, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, + 0x61, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x03, 0x73, 0x73, 0x68, + 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x6e, 0x65, + 0x77, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x64, 0x69, 0x73, 0x61, 0x62, + 0x6c, 0x65, 0x52, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x22, 0x59, 0x0a, 0x0a, 0x58, 0x35, 0x30, + 0x39, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x12, 0x31, 0x0a, 0x09, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, + 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x09, 0x64, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x9d, 0x01, 0x0a, 0x09, 0x53, 0x53, 0x48, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x32, 0x0a, 0x09, - 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x14, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x44, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x09, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x22, 0x9f, 0x01, 0x0a, 0x09, 0x53, 0x53, 0x48, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x18, - 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x3b, 0x0a, 0x0e, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x14, 0x2e, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x44, 0x75, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x44, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3b, 0x0a, 0x0e, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x64, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, - 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x52, 0x0d, 0x68, 0x6f, 0x73, 0x74, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x22, 0x49, 0x0a, 0x09, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, - 0x18, 0x0a, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x6e, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6d, - 0x61, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x61, 0x78, 0x22, 0x63, 0x0a, - 0x0e, 0x4a, 0x57, 0x4b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, - 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x32, - 0x0a, 0x15, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x72, 0x69, 0x76, - 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x13, 0x65, - 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, - 0x65, 0x79, 0x22, 0x98, 0x02, 0x0a, 0x0f, 0x4f, 0x49, 0x44, 0x43, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, - 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x35, 0x0a, 0x16, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, - 0x16, 0x0a, 0x06, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x06, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, - 0x73, 0x12, 0x16, 0x0a, 0x06, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x06, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x6c, 0x69, 0x73, - 0x74, 0x65, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0d, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x22, 0xeb, 0x01, - 0x0a, 0x0e, 0x47, 0x43, 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x12, 0x29, 0x0a, 0x10, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x70, - 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x73, 0x12, 0x2e, 0x0a, 0x13, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x3a, 0x0a, 0x0e, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, + 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x44, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3a, 0x0a, 0x0e, 0x68, 0x6f, 0x73, 0x74, + 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x13, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x44, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0d, 0x68, 0x6f, 0x73, 0x74, 0x44, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x49, 0x0a, 0x09, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6d, + 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x12, 0x10, 0x0a, + 0x03, 0x6d, 0x61, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x61, 0x78, 0x22, + 0x63, 0x0a, 0x0e, 0x4a, 0x57, 0x4b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, + 0x12, 0x32, 0x0a, 0x15, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x72, + 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x13, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, + 0x65, 0x4b, 0x65, 0x79, 0x22, 0x98, 0x02, 0x0a, 0x0f, 0x4f, 0x49, 0x44, 0x43, 0x50, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, + 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x35, 0x0a, 0x16, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x06, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x06, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x06, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x6c, + 0x69, 0x73, 0x74, 0x65, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x22, + 0xeb, 0x01, 0x0a, 0x0e, 0x47, 0x43, 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x0a, + 0x0b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x73, 0x12, 0x2e, + 0x0a, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x5f, 0x73, 0x61, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, + 0x61, 0x62, 0x6c, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x61, 0x6e, 0x73, 0x12, 0x3a, + 0x0a, 0x1a, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x5f, + 0x6f, 0x6e, 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x4f, 0x6e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x55, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, + 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x67, 0x65, 0x22, 0xbb, 0x01, + 0x0a, 0x0e, 0x41, 0x57, 0x53, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x73, - 0x61, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, + 0x61, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x61, 0x6e, 0x73, 0x12, 0x3a, 0x0a, 0x1a, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x5f, 0x6f, 0x6e, - 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, + 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x4f, 0x6e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x55, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x73, 0x74, - 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, - 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x67, 0x65, 0x22, 0xbb, 0x01, 0x0a, 0x0e, - 0x41, 0x57, 0x53, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x1a, - 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x69, - 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x73, 0x61, 0x6e, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, - 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x61, 0x6e, 0x73, 0x12, 0x3a, 0x0a, 0x1a, 0x64, 0x69, - 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x5f, 0x6f, 0x6e, 0x5f, 0x66, - 0x69, 0x72, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, - 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x4f, 0x6e, 0x46, 0x69, - 0x72, 0x73, 0x74, 0x55, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, - 0x63, 0x65, 0x5f, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, - 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x67, 0x65, 0x22, 0xe0, 0x01, 0x0a, 0x10, 0x41, 0x7a, - 0x75, 0x72, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x1b, - 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, - 0x6f, 0x75, 0x70, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, - 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, - 0x6f, 0x6d, 0x5f, 0x73, 0x61, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, - 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x61, 0x6e, 0x73, - 0x12, 0x3a, 0x0a, 0x1a, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x5f, 0x6f, 0x6e, 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x75, - 0x73, 0x74, 0x4f, 0x6e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x55, 0x73, 0x65, 0x22, 0x2c, 0x0a, 0x0f, - 0x41, 0x43, 0x4d, 0x45, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, - 0x19, 0x0a, 0x08, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x07, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6e, 0x22, 0x26, 0x0a, 0x0e, 0x58, 0x35, - 0x43, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, - 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x6f, 0x6f, - 0x74, 0x73, 0x22, 0x33, 0x0a, 0x10, 0x4b, 0x38, 0x73, 0x53, 0x41, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x70, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x53, 0x53, 0x48, 0x50, 0x4f, - 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x42, 0x2d, 0x5a, 0x2b, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6d, 0x61, 0x6c, 0x6c, - 0x73, 0x74, 0x65, 0x70, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x73, 0x2f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x64, 0x6f, 0x6d, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x67, 0x65, 0x22, 0xe0, 0x01, 0x0a, 0x10, + 0x41, 0x7a, 0x75, 0x72, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x27, 0x0a, + 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, + 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, + 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x75, + 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x73, 0x61, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x61, + 0x6e, 0x73, 0x12, 0x3a, 0x0a, 0x1a, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x72, + 0x75, 0x73, 0x74, 0x5f, 0x6f, 0x6e, 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x54, + 0x72, 0x75, 0x73, 0x74, 0x4f, 0x6e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x55, 0x73, 0x65, 0x22, 0x2c, + 0x0a, 0x0f, 0x41, 0x43, 0x4d, 0x45, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6e, 0x22, 0x26, 0x0a, 0x0e, + 0x58, 0x35, 0x43, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x14, + 0x0a, 0x05, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x72, + 0x6f, 0x6f, 0x74, 0x73, 0x22, 0x33, 0x0a, 0x10, 0x4b, 0x38, 0x73, 0x53, 0x41, 0x50, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x70, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x53, 0x53, 0x48, + 0x50, 0x4f, 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x42, 0x2c, + 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6d, 0x61, + 0x6c, 0x6c, 0x73, 0x74, 0x65, 0x70, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x73, 0x2f, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( - file_majordomo_provisioners_proto_rawDescOnce sync.Once - file_majordomo_provisioners_proto_rawDescData = file_majordomo_provisioners_proto_rawDesc + file_linkedca_provisioners_proto_rawDescOnce sync.Once + file_linkedca_provisioners_proto_rawDescData = file_linkedca_provisioners_proto_rawDesc ) -func file_majordomo_provisioners_proto_rawDescGZIP() []byte { - file_majordomo_provisioners_proto_rawDescOnce.Do(func() { - file_majordomo_provisioners_proto_rawDescData = protoimpl.X.CompressGZIP(file_majordomo_provisioners_proto_rawDescData) +func file_linkedca_provisioners_proto_rawDescGZIP() []byte { + file_linkedca_provisioners_proto_rawDescOnce.Do(func() { + file_linkedca_provisioners_proto_rawDescData = protoimpl.X.CompressGZIP(file_linkedca_provisioners_proto_rawDescData) }) - return file_majordomo_provisioners_proto_rawDescData + return file_linkedca_provisioners_proto_rawDescData } -var file_majordomo_provisioners_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_majordomo_provisioners_proto_msgTypes = make([]protoimpl.MessageInfo, 17) -var file_majordomo_provisioners_proto_goTypes = []interface{}{ - (Administrator_Type)(0), // 0: majordomo.Administrator.Type - (Provisioner_Type)(0), // 1: majordomo.Provisioner.Type - (*Administrator)(nil), // 2: majordomo.Administrator - (*Provisioner)(nil), // 3: majordomo.Provisioner - (*ProvisionerDetails)(nil), // 4: majordomo.ProvisionerDetails - (*ProvisionerList)(nil), // 5: majordomo.ProvisionerList - (*Claims)(nil), // 6: majordomo.Claims - (*X509Claims)(nil), // 7: majordomo.X509Claims - (*SSHClaims)(nil), // 8: majordomo.SSHClaims - (*Durations)(nil), // 9: majordomo.Durations - (*JWKProvisioner)(nil), // 10: majordomo.JWKProvisioner - (*OIDCProvisioner)(nil), // 11: majordomo.OIDCProvisioner - (*GCPProvisioner)(nil), // 12: majordomo.GCPProvisioner - (*AWSProvisioner)(nil), // 13: majordomo.AWSProvisioner - (*AzureProvisioner)(nil), // 14: majordomo.AzureProvisioner - (*ACMEProvisioner)(nil), // 15: majordomo.ACMEProvisioner - (*X5CProvisioner)(nil), // 16: majordomo.X5CProvisioner - (*K8SSAProvisioner)(nil), // 17: majordomo.K8sSAProvisioner - (*SSHPOPProvisioner)(nil), // 18: majordomo.SSHPOPProvisioner +var file_linkedca_provisioners_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_linkedca_provisioners_proto_msgTypes = make([]protoimpl.MessageInfo, 16) +var file_linkedca_provisioners_proto_goTypes = []interface{}{ + (Provisioner_Type)(0), // 0: linkedca.Provisioner.Type + (*Provisioner)(nil), // 1: linkedca.Provisioner + (*ProvisionerDetails)(nil), // 2: linkedca.ProvisionerDetails + (*ProvisionerList)(nil), // 3: linkedca.ProvisionerList + (*Claims)(nil), // 4: linkedca.Claims + (*X509Claims)(nil), // 5: linkedca.X509Claims + (*SSHClaims)(nil), // 6: linkedca.SSHClaims + (*Durations)(nil), // 7: linkedca.Durations + (*JWKProvisioner)(nil), // 8: linkedca.JWKProvisioner + (*OIDCProvisioner)(nil), // 9: linkedca.OIDCProvisioner + (*GCPProvisioner)(nil), // 10: linkedca.GCPProvisioner + (*AWSProvisioner)(nil), // 11: linkedca.AWSProvisioner + (*AzureProvisioner)(nil), // 12: linkedca.AzureProvisioner + (*ACMEProvisioner)(nil), // 13: linkedca.ACMEProvisioner + (*X5CProvisioner)(nil), // 14: linkedca.X5CProvisioner + (*K8SSAProvisioner)(nil), // 15: linkedca.K8sSAProvisioner + (*SSHPOPProvisioner)(nil), // 16: linkedca.SSHPOPProvisioner } -var file_majordomo_provisioners_proto_depIdxs = []int32{ - 0, // 0: majordomo.Administrator.type:type_name -> majordomo.Administrator.Type - 1, // 1: majordomo.Provisioner.type:type_name -> majordomo.Provisioner.Type - 4, // 2: majordomo.Provisioner.details:type_name -> majordomo.ProvisionerDetails - 6, // 3: majordomo.Provisioner.claims:type_name -> majordomo.Claims - 10, // 4: majordomo.ProvisionerDetails.JWK:type_name -> majordomo.JWKProvisioner - 11, // 5: majordomo.ProvisionerDetails.OIDC:type_name -> majordomo.OIDCProvisioner - 12, // 6: majordomo.ProvisionerDetails.GCP:type_name -> majordomo.GCPProvisioner - 13, // 7: majordomo.ProvisionerDetails.AWS:type_name -> majordomo.AWSProvisioner - 14, // 8: majordomo.ProvisionerDetails.Azure:type_name -> majordomo.AzureProvisioner - 15, // 9: majordomo.ProvisionerDetails.ACME:type_name -> majordomo.ACMEProvisioner - 16, // 10: majordomo.ProvisionerDetails.X5C:type_name -> majordomo.X5CProvisioner - 17, // 11: majordomo.ProvisionerDetails.K8sSA:type_name -> majordomo.K8sSAProvisioner - 18, // 12: majordomo.ProvisionerDetails.SSHPOP:type_name -> majordomo.SSHPOPProvisioner - 3, // 13: majordomo.ProvisionerList.provisioners:type_name -> majordomo.Provisioner - 7, // 14: majordomo.Claims.x509:type_name -> majordomo.X509Claims - 8, // 15: majordomo.Claims.ssh:type_name -> majordomo.SSHClaims - 9, // 16: majordomo.X509Claims.durations:type_name -> majordomo.Durations - 9, // 17: majordomo.SSHClaims.user_durations:type_name -> majordomo.Durations - 9, // 18: majordomo.SSHClaims.host_durations:type_name -> majordomo.Durations - 19, // [19:19] is the sub-list for method output_type - 19, // [19:19] is the sub-list for method input_type - 19, // [19:19] is the sub-list for extension type_name - 19, // [19:19] is the sub-list for extension extendee - 0, // [0:19] is the sub-list for field type_name +var file_linkedca_provisioners_proto_depIdxs = []int32{ + 0, // 0: linkedca.Provisioner.type:type_name -> linkedca.Provisioner.Type + 2, // 1: linkedca.Provisioner.details:type_name -> linkedca.ProvisionerDetails + 4, // 2: linkedca.Provisioner.claims:type_name -> linkedca.Claims + 8, // 3: linkedca.ProvisionerDetails.JWK:type_name -> linkedca.JWKProvisioner + 9, // 4: linkedca.ProvisionerDetails.OIDC:type_name -> linkedca.OIDCProvisioner + 10, // 5: linkedca.ProvisionerDetails.GCP:type_name -> linkedca.GCPProvisioner + 11, // 6: linkedca.ProvisionerDetails.AWS:type_name -> linkedca.AWSProvisioner + 12, // 7: linkedca.ProvisionerDetails.Azure:type_name -> linkedca.AzureProvisioner + 13, // 8: linkedca.ProvisionerDetails.ACME:type_name -> linkedca.ACMEProvisioner + 14, // 9: linkedca.ProvisionerDetails.X5C:type_name -> linkedca.X5CProvisioner + 15, // 10: linkedca.ProvisionerDetails.K8sSA:type_name -> linkedca.K8sSAProvisioner + 16, // 11: linkedca.ProvisionerDetails.SSHPOP:type_name -> linkedca.SSHPOPProvisioner + 1, // 12: linkedca.ProvisionerList.provisioners:type_name -> linkedca.Provisioner + 5, // 13: linkedca.Claims.x509:type_name -> linkedca.X509Claims + 6, // 14: linkedca.Claims.ssh:type_name -> linkedca.SSHClaims + 7, // 15: linkedca.X509Claims.durations:type_name -> linkedca.Durations + 7, // 16: linkedca.SSHClaims.user_durations:type_name -> linkedca.Durations + 7, // 17: linkedca.SSHClaims.host_durations:type_name -> linkedca.Durations + 18, // [18:18] is the sub-list for method output_type + 18, // [18:18] is the sub-list for method input_type + 18, // [18:18] is the sub-list for extension type_name + 18, // [18:18] is the sub-list for extension extendee + 0, // [0:18] is the sub-list for field type_name } -func init() { file_majordomo_provisioners_proto_init() } -func file_majordomo_provisioners_proto_init() { - if File_majordomo_provisioners_proto != nil { +func init() { file_linkedca_provisioners_proto_init() } +func file_linkedca_provisioners_proto_init() { + if File_linkedca_provisioners_proto != nil { return } if !protoimpl.UnsafeEnabled { - file_majordomo_provisioners_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Administrator); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_majordomo_provisioners_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_linkedca_provisioners_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Provisioner); i { case 0: return &v.state @@ -1661,7 +1502,7 @@ func file_majordomo_provisioners_proto_init() { return nil } } - file_majordomo_provisioners_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_linkedca_provisioners_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ProvisionerDetails); i { case 0: return &v.state @@ -1673,7 +1514,7 @@ func file_majordomo_provisioners_proto_init() { return nil } } - file_majordomo_provisioners_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + file_linkedca_provisioners_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ProvisionerList); i { case 0: return &v.state @@ -1685,7 +1526,7 @@ func file_majordomo_provisioners_proto_init() { return nil } } - file_majordomo_provisioners_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + file_linkedca_provisioners_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Claims); i { case 0: return &v.state @@ -1697,7 +1538,7 @@ func file_majordomo_provisioners_proto_init() { return nil } } - file_majordomo_provisioners_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + file_linkedca_provisioners_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*X509Claims); i { case 0: return &v.state @@ -1709,7 +1550,7 @@ func file_majordomo_provisioners_proto_init() { return nil } } - file_majordomo_provisioners_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + file_linkedca_provisioners_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SSHClaims); i { case 0: return &v.state @@ -1721,7 +1562,7 @@ func file_majordomo_provisioners_proto_init() { return nil } } - file_majordomo_provisioners_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + file_linkedca_provisioners_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Durations); i { case 0: return &v.state @@ -1733,7 +1574,7 @@ func file_majordomo_provisioners_proto_init() { return nil } } - file_majordomo_provisioners_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + file_linkedca_provisioners_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*JWKProvisioner); i { case 0: return &v.state @@ -1745,7 +1586,7 @@ func file_majordomo_provisioners_proto_init() { return nil } } - file_majordomo_provisioners_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + file_linkedca_provisioners_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*OIDCProvisioner); i { case 0: return &v.state @@ -1757,7 +1598,7 @@ func file_majordomo_provisioners_proto_init() { return nil } } - file_majordomo_provisioners_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + file_linkedca_provisioners_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GCPProvisioner); i { case 0: return &v.state @@ -1769,7 +1610,7 @@ func file_majordomo_provisioners_proto_init() { return nil } } - file_majordomo_provisioners_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + file_linkedca_provisioners_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AWSProvisioner); i { case 0: return &v.state @@ -1781,7 +1622,7 @@ func file_majordomo_provisioners_proto_init() { return nil } } - file_majordomo_provisioners_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + file_linkedca_provisioners_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AzureProvisioner); i { case 0: return &v.state @@ -1793,7 +1634,7 @@ func file_majordomo_provisioners_proto_init() { return nil } } - file_majordomo_provisioners_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + file_linkedca_provisioners_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ACMEProvisioner); i { case 0: return &v.state @@ -1805,7 +1646,7 @@ func file_majordomo_provisioners_proto_init() { return nil } } - file_majordomo_provisioners_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + file_linkedca_provisioners_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*X5CProvisioner); i { case 0: return &v.state @@ -1817,7 +1658,7 @@ func file_majordomo_provisioners_proto_init() { return nil } } - file_majordomo_provisioners_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + file_linkedca_provisioners_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*K8SSAProvisioner); i { case 0: return &v.state @@ -1829,7 +1670,7 @@ func file_majordomo_provisioners_proto_init() { return nil } } - file_majordomo_provisioners_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + file_linkedca_provisioners_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SSHPOPProvisioner); i { case 0: return &v.state @@ -1842,7 +1683,7 @@ func file_majordomo_provisioners_proto_init() { } } } - file_majordomo_provisioners_proto_msgTypes[2].OneofWrappers = []interface{}{ + file_linkedca_provisioners_proto_msgTypes[1].OneofWrappers = []interface{}{ (*ProvisionerDetails_JWK)(nil), (*ProvisionerDetails_OIDC)(nil), (*ProvisionerDetails_GCP)(nil), @@ -1857,19 +1698,19 @@ func file_majordomo_provisioners_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_majordomo_provisioners_proto_rawDesc, - NumEnums: 2, - NumMessages: 17, + RawDescriptor: file_linkedca_provisioners_proto_rawDesc, + NumEnums: 1, + NumMessages: 16, NumExtensions: 0, NumServices: 0, }, - GoTypes: file_majordomo_provisioners_proto_goTypes, - DependencyIndexes: file_majordomo_provisioners_proto_depIdxs, - EnumInfos: file_majordomo_provisioners_proto_enumTypes, - MessageInfos: file_majordomo_provisioners_proto_msgTypes, + GoTypes: file_linkedca_provisioners_proto_goTypes, + DependencyIndexes: file_linkedca_provisioners_proto_depIdxs, + EnumInfos: file_linkedca_provisioners_proto_enumTypes, + MessageInfos: file_linkedca_provisioners_proto_msgTypes, }.Build() - File_majordomo_provisioners_proto = out.File - file_majordomo_provisioners_proto_rawDesc = nil - file_majordomo_provisioners_proto_goTypes = nil - file_majordomo_provisioners_proto_depIdxs = nil + File_linkedca_provisioners_proto = out.File + file_linkedca_provisioners_proto_rawDesc = nil + file_linkedca_provisioners_proto_goTypes = nil + file_linkedca_provisioners_proto_depIdxs = nil } diff --git a/linkedca/provisioners.proto b/linkedca/provisioners.proto index 7dd7f0c1..53f2c140 100644 --- a/linkedca/provisioners.proto +++ b/linkedca/provisioners.proto @@ -1,21 +1,8 @@ syntax = "proto3"; -package majordomo; +package linkedca; -option go_package = "github.com/smallstep/certificates/majordomo"; - -message Administrator { - enum Type { - UNKNOWN = 0; - ADMIN = 1; - SUPER_ADMIN = 2; - } - string id = 1; - string authority_id = 2; - string subject = 3; - string provisioner_id = 4; - Type type = 5; -} +option go_package = "github.com/smallstep/certificates/linkedca"; message Provisioner { enum Type { From 3f30552b60de2cb67860d3e3c3f9994c426306f1 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 24 May 2021 12:46:16 -0700 Subject: [PATCH 16/89] Fix package name. --- linkedca/doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linkedca/doc.go b/linkedca/doc.go index 9a64ca9c..ceddecb0 100644 --- a/linkedca/doc.go +++ b/linkedca/doc.go @@ -1,3 +1,3 @@ -package majordomo +package linkedca //go:generate protoc --proto_path=.. --go_out=.. --go-grpc_out=.. --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative linkedca/provisioners.proto linkedca/admin.proto From 423942da4469a0dc41131b484df99692e9483a9d Mon Sep 17 00:00:00 2001 From: max furman Date: Mon, 24 May 2021 13:38:24 -0700 Subject: [PATCH 17/89] wip --- authority/admin/admin.go | 25 --- authority/admin/collection.go | 39 ++-- authority/mgmt/admin.go | 23 --- authority/mgmt/authConfig.go | 68 ------- authority/mgmt/config.go | 18 +- authority/mgmt/db.go | 91 +++------ authority/mgmt/provisioner.go | 336 ++++------------------------------ 7 files changed, 84 insertions(+), 516 deletions(-) delete mode 100644 authority/admin/admin.go delete mode 100644 authority/mgmt/admin.go delete mode 100644 authority/mgmt/authConfig.go diff --git a/authority/admin/admin.go b/authority/admin/admin.go deleted file mode 100644 index f0058777..00000000 --- a/authority/admin/admin.go +++ /dev/null @@ -1,25 +0,0 @@ -package admin - -import "github.com/smallstep/certificates/authority/status" - -// Type specifies the type of the admin. e.g. SUPER_ADMIN, REGULAR -type Type string - -var ( - // TypeSuper superadmin - TypeSuper = Type("SUPER_ADMIN") - // TypeRegular regular - TypeRegular = Type("REGULAR") -) - -// Admin type. -type Admin struct { - ID string `json:"id"` - AuthorityID string `json:"-"` - Subject string `json:"subject"` - ProvisionerName string `json:"provisionerName"` - ProvisionerType string `json:"provisionerType"` - ProvisionerID string `json:"provisionerID"` - Type Type `json:"type"` - Status status.Type `json:"status"` -} diff --git a/authority/admin/collection.go b/authority/admin/collection.go index 971bbd9c..11ba35fc 100644 --- a/authority/admin/collection.go +++ b/authority/admin/collection.go @@ -11,6 +11,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/linkedca" ) // DefaultAdminLimit is the default limit for listing provisioners. @@ -20,7 +21,7 @@ const DefaultAdminLimit = 20 const DefaultAdminMax = 100 type uidAdmin struct { - admin *Admin + admin *linkedca.Admin uid string } @@ -54,7 +55,7 @@ func NewCollection(provisioners *provisioner.Collection) *Collection { } // LoadByID a admin by the ID. -func (c *Collection) LoadByID(id string) (*Admin, bool) { +func (c *Collection) LoadByID(id string) (*linkedca.Admin, bool) { return loadAdmin(c.byID, id) } @@ -66,17 +67,17 @@ func subProvNameHash(sub, provName string) string { } // LoadBySubProv a admin by the subject and provisioner name. -func (c *Collection) LoadBySubProv(sub, provName string) (*Admin, bool) { +func (c *Collection) LoadBySubProv(sub, provName string) (*linkedca.Admin, bool) { return loadAdmin(c.bySubProv, subProvNameHash(sub, provName)) } // LoadByProvisioner a admin by the subject and provisioner name. -func (c *Collection) LoadByProvisioner(provName string) ([]*Admin, bool) { +func (c *Collection) LoadByProvisioner(provName string) ([]*linkedca.Admin, bool) { a, ok := c.byProv.Load(provName) if !ok { return nil, false } - admins, ok := a.([]*Admin) + admins, ok := a.([]*linkedca.Admin) if !ok { return nil, false } @@ -85,22 +86,20 @@ func (c *Collection) LoadByProvisioner(provName string) ([]*Admin, bool) { // Store adds an admin to the collection and enforces the uniqueness of // admin IDs and amdin subject <-> provisioner name combos. -func (c *Collection) Store(adm *Admin) error { - p, ok := c.provisioners.Load(adm.ProvisionerID) +func (c *Collection) Store(adm *linkedca.Admin) error { + p, ok := c.provisioners.Load(adm.ProvisionerId) if !ok { - return fmt.Errorf("provisioner %s not found", adm.ProvisionerID) + return fmt.Errorf("provisioner %s not found", adm.ProvisionerId) } - adm.ProvisionerName = p.GetName() - adm.ProvisionerType = p.GetType().String() // Store admin always in byID. ID must be unique. - if _, loaded := c.byID.LoadOrStore(adm.ID, adm); loaded { + if _, loaded := c.byID.LoadOrStore(adm.Id, adm); loaded { return errors.New("cannot add multiple admins with the same id") } - provName := adm.ProvisionerName - // Store admin alwasy in bySubProv. Subject <-> ProvisionerName must be unique. + provName := p.GetName() + // Store admin always in bySubProv. Subject <-> ProvisionerName must be unique. if _, loaded := c.bySubProv.LoadOrStore(subProvNameHash(adm.Subject, provName), adm); loaded { - c.byID.Delete(adm.ID) + c.byID.Delete(adm.Id) return errors.New("cannot add multiple admins with the same subject and provisioner") } @@ -108,7 +107,7 @@ func (c *Collection) Store(adm *Admin) error { c.byProv.Store(provName, append(admins, adm)) c.superCountByProvisioner[provName]++ } else { - c.byProv.Store(provName, []*Admin{adm}) + c.byProv.Store(provName, []*linkedca.Admin{adm}) c.superCountByProvisioner[provName] = 1 } c.superCount++ @@ -118,7 +117,7 @@ func (c *Collection) Store(adm *Admin) error { // Using big endian format to get the strings sorted: // 0x00000000, 0x00000001, 0x00000002, ... bi := make([]byte, 4) - _sum := sha1.Sum([]byte(adm.ID)) + _sum := sha1.Sum([]byte(adm.Id)) sum := _sum[:] binary.BigEndian.PutUint32(bi, uint32(c.sorted.Len())) sum[0], sum[1], sum[2], sum[3] = bi[0], bi[1], bi[2], bi[3] @@ -145,7 +144,7 @@ func (c *Collection) SuperCountByProvisioner(provName string) int { } // Find implements pagination on a list of sorted provisioners. -func (c *Collection) Find(cursor string, limit int) ([]*Admin, string) { +func (c *Collection) Find(cursor string, limit int) ([]*linkedca.Admin, string) { switch { case limit <= 0: limit = DefaultAdminLimit @@ -157,7 +156,7 @@ func (c *Collection) Find(cursor string, limit int) ([]*Admin, string) { cursor = fmt.Sprintf("%040s", cursor) i := sort.Search(n, func(i int) bool { return c.sorted[i].uid >= cursor }) - slice := []*Admin{} + slice := []*linkedca.Admin{} for ; i < n && len(slice) < limit; i++ { slice = append(slice, c.sorted[i].admin) } @@ -168,12 +167,12 @@ func (c *Collection) Find(cursor string, limit int) ([]*Admin, string) { return slice, "" } -func loadAdmin(m *sync.Map, key string) (*Admin, bool) { +func loadAdmin(m *sync.Map, key string) (*linkedca.Admin, bool) { a, ok := m.Load(key) if !ok { return nil, false } - adm, ok := a.(*Admin) + adm, ok := a.(*linkedca.Admin) if !ok { return nil, false } diff --git a/authority/mgmt/admin.go b/authority/mgmt/admin.go deleted file mode 100644 index d265a001..00000000 --- a/authority/mgmt/admin.go +++ /dev/null @@ -1,23 +0,0 @@ -package mgmt - -import ( - "github.com/smallstep/certificates/authority/admin" -) - -// AdminType specifies the type of the admin. e.g. SUPER_ADMIN, REGULAR -type AdminType admin.Type - -var ( - // AdminTypeSuper superadmin - AdminTypeSuper = admin.TypeSuper - // AdminTypeRegular regular - AdminTypeRegular = admin.TypeRegular -) - -// Admin type. -type Admin admin.Admin - -// ToCertificates converts an Admin to the Admin type expected by the authority. -func (adm *Admin) ToCertificates() (*admin.Admin, error) { - return (*admin.Admin)(adm), nil -} diff --git a/authority/mgmt/authConfig.go b/authority/mgmt/authConfig.go deleted file mode 100644 index 6284a9e7..00000000 --- a/authority/mgmt/authConfig.go +++ /dev/null @@ -1,68 +0,0 @@ -package mgmt - -import ( - "github.com/smallstep/certificates/authority/admin" - "github.com/smallstep/certificates/authority/config" - "github.com/smallstep/certificates/authority/provisioner" - "github.com/smallstep/certificates/authority/status" -) - -// AuthConfig represents the Authority Configuration. -type AuthConfig struct { - //*cas.Options `json:"cas"` - ID string `json:"id"` - ASN1DN *config.ASN1DN `json:"asn1dn,omitempty"` - Provisioners []*Provisioner `json:"-"` - Admins []*Admin `json:"-"` - Claims *Claims `json:"claims,omitempty"` - Backdate string `json:"backdate,omitempty"` - Status status.Type `json:"status,omitempty"` -} - -func NewDefaultAuthConfig() *AuthConfig { - return &AuthConfig{ - Claims: NewDefaultClaims(), - ASN1DN: &config.ASN1DN{}, - Backdate: config.DefaultBackdate.String(), - Status: status.Active, - } -} - -// ToCertificates converts a mgmt AuthConfig to configuration that can be -// directly used by the `step-ca` process. Resources are normalized and -// initialized. -func (ac *AuthConfig) ToCertificates() (*config.AuthConfig, error) { - claims, err := ac.Claims.ToCertificates() - if err != nil { - return nil, err - } - backdate, err := provisioner.NewDuration(ac.Backdate) - if err != nil { - return nil, WrapErrorISE(err, "error converting backdate %s to duration", ac.Backdate) - } - var provs []provisioner.Interface - for _, p := range ac.Provisioners { - authProv, err := p.ToCertificates() - if err != nil { - return nil, err - } - provs = append(provs, authProv) - } - var admins []*admin.Admin - for _, adm := range ac.Admins { - authAdmin, err := adm.ToCertificates() - if err != nil { - return nil, err - } - admins = append(admins, authAdmin) - } - return &config.AuthConfig{ - AuthorityID: ac.ID, - Provisioners: provs, - Admins: admins, - Template: ac.ASN1DN, - Claims: claims, - DisableIssuedAtCheck: false, - Backdate: backdate, - }, nil -} diff --git a/authority/mgmt/config.go b/authority/mgmt/config.go index 8b23cad0..4df53b75 100644 --- a/authority/mgmt/config.go +++ b/authority/mgmt/config.go @@ -1,9 +1,6 @@ package mgmt import ( - "context" - - "github.com/pkg/errors" "github.com/smallstep/certificates/authority/config" ) @@ -66,19 +63,7 @@ func NewDefaultClaims() *Claims { } } -type AuthorityOption func(*AuthConfig) error - -func WithDefaultAuthorityID(ac *AuthConfig) error { - ac.ID = DefaultAuthorityID - return nil -} - -func CreateDefaultAuthority(ctx context.Context, db DB) (*AuthConfig, error) { - options := []AuthorityOption{WithDefaultAuthorityID} - - return CreateAuthority(ctx, db, options...) -} - +/* func CreateAuthority(ctx context.Context, db DB, options ...AuthorityOption) (*AuthConfig, error) { ac := NewDefaultAuthConfig() @@ -116,3 +101,4 @@ func CreateAuthority(ctx context.Context, db DB, options ...AuthorityOption) (*A return ac, nil } +*/ diff --git a/authority/mgmt/db.go b/authority/mgmt/db.go index 9cfab3e5..9716bb00 100644 --- a/authority/mgmt/db.go +++ b/authority/mgmt/db.go @@ -4,6 +4,7 @@ import ( "context" "github.com/pkg/errors" + "github.com/smallstep/certificates/linkedca" ) // ErrNotFound is an error that should be used by the authority.DB interface to @@ -12,44 +13,36 @@ var ErrNotFound = errors.New("not found") // DB is the DB interface expected by the step-ca ACME API. type DB interface { - CreateProvisioner(ctx context.Context, prov *Provisioner) error - GetProvisioner(ctx context.Context, id string) (*Provisioner, error) - GetProvisioners(ctx context.Context) ([]*Provisioner, error) - UpdateProvisioner(ctx context.Context, prov *Provisioner) error + CreateProvisioner(ctx context.Context, prov *linkedca.Provisioner) error + GetProvisioner(ctx context.Context, id string) (*linkedca.Provisioner, error) + GetProvisioners(ctx context.Context) ([]*linkedca.Provisioner, error) + UpdateProvisioner(ctx context.Context, prov *linkedca.Provisioner) error - CreateAdmin(ctx context.Context, admin *Admin) error - GetAdmin(ctx context.Context, id string) (*Admin, error) - GetAdmins(ctx context.Context) ([]*Admin, error) - UpdateAdmin(ctx context.Context, admin *Admin) error - - CreateAuthConfig(ctx context.Context, ac *AuthConfig) error - GetAuthConfig(ctx context.Context, id string) (*AuthConfig, error) - UpdateAuthConfig(ctx context.Context, ac *AuthConfig) error + CreateAdmin(ctx context.Context, admin *linkedca.Admin) error + GetAdmin(ctx context.Context, id string) (*linkedca.Admin, error) + GetAdmins(ctx context.Context) ([]*linkedca.Admin, error) + UpdateAdmin(ctx context.Context, admin *linkedca.Admin) error } // MockDB is an implementation of the DB interface that should only be used as // a mock in tests. type MockDB struct { - MockCreateProvisioner func(ctx context.Context, prov *Provisioner) error - MockGetProvisioner func(ctx context.Context, id string) (*Provisioner, error) - MockGetProvisioners func(ctx context.Context) ([]*Provisioner, error) - MockUpdateProvisioner func(ctx context.Context, prov *Provisioner) error + MockCreateProvisioner func(ctx context.Context, prov *linkedca.Provisioner) error + MockGetProvisioner func(ctx context.Context, id string) (*linkedca.Provisioner, error) + MockGetProvisioners func(ctx context.Context) ([]*linkedca.Provisioner, error) + MockUpdateProvisioner func(ctx context.Context, prov *linkedca.Provisioner) error - MockCreateAdmin func(ctx context.Context, adm *Admin) error - MockGetAdmin func(ctx context.Context, id string) (*Admin, error) - MockGetAdmins func(ctx context.Context) ([]*Admin, error) - MockUpdateAdmin func(ctx context.Context, adm *Admin) error - - MockCreateAuthConfig func(ctx context.Context, ac *AuthConfig) error - MockGetAuthConfig func(ctx context.Context, id string) (*AuthConfig, error) - MockUpdateAuthConfig func(ctx context.Context, ac *AuthConfig) error + MockCreateAdmin func(ctx context.Context, adm *linkedca.Admin) error + MockGetAdmin func(ctx context.Context, id string) (*linkedca.Admin, error) + MockGetAdmins func(ctx context.Context) ([]*linkedca.Admin, error) + MockUpdateAdmin func(ctx context.Context, adm *linkedca.Admin) error MockError error MockRet1 interface{} } // CreateProvisioner mock. -func (m *MockDB) CreateProvisioner(ctx context.Context, prov *Provisioner) error { +func (m *MockDB) CreateProvisioner(ctx context.Context, prov *linkedca.Provisioner) error { if m.MockCreateProvisioner != nil { return m.MockCreateProvisioner(ctx, prov) } else if m.MockError != nil { @@ -59,27 +52,27 @@ func (m *MockDB) CreateProvisioner(ctx context.Context, prov *Provisioner) error } // GetProvisioner mock. -func (m *MockDB) GetProvisioner(ctx context.Context, id string) (*Provisioner, error) { +func (m *MockDB) GetProvisioner(ctx context.Context, id string) (*linkedca.Provisioner, error) { if m.MockGetProvisioner != nil { return m.MockGetProvisioner(ctx, id) } else if m.MockError != nil { return nil, m.MockError } - return m.MockRet1.(*Provisioner), m.MockError + return m.MockRet1.(*linkedca.Provisioner), m.MockError } // GetProvisioners mock -func (m *MockDB) GetProvisioners(ctx context.Context) ([]*Provisioner, error) { +func (m *MockDB) GetProvisioners(ctx context.Context) ([]*linkedca.Provisioner, error) { if m.MockGetProvisioners != nil { return m.MockGetProvisioners(ctx) } else if m.MockError != nil { return nil, m.MockError } - return m.MockRet1.([]*Provisioner), m.MockError + return m.MockRet1.([]*linkedca.Provisioner), m.MockError } // UpdateProvisioner mock -func (m *MockDB) UpdateProvisioner(ctx context.Context, prov *Provisioner) error { +func (m *MockDB) UpdateProvisioner(ctx context.Context, prov *linkedca.Provisioner) error { if m.MockUpdateProvisioner != nil { return m.MockUpdateProvisioner(ctx, prov) } @@ -87,7 +80,7 @@ func (m *MockDB) UpdateProvisioner(ctx context.Context, prov *Provisioner) error } // CreateAdmin mock -func (m *MockDB) CreateAdmin(ctx context.Context, admin *Admin) error { +func (m *MockDB) CreateAdmin(ctx context.Context, admin *linkedca.Admin) error { if m.MockCreateAdmin != nil { return m.MockCreateAdmin(ctx, admin) } @@ -95,55 +88,29 @@ func (m *MockDB) CreateAdmin(ctx context.Context, admin *Admin) error { } // GetAdmin mock. -func (m *MockDB) GetAdmin(ctx context.Context, id string) (*Admin, error) { +func (m *MockDB) GetAdmin(ctx context.Context, id string) (*linkedca.Admin, error) { if m.MockGetAdmin != nil { return m.MockGetAdmin(ctx, id) } else if m.MockError != nil { return nil, m.MockError } - return m.MockRet1.(*Admin), m.MockError + return m.MockRet1.(*linkedca.Admin), m.MockError } // GetAdmins mock -func (m *MockDB) GetAdmins(ctx context.Context) ([]*Admin, error) { +func (m *MockDB) GetAdmins(ctx context.Context) ([]*linkedca.Admin, error) { if m.MockGetAdmins != nil { return m.MockGetAdmins(ctx) } else if m.MockError != nil { return nil, m.MockError } - return m.MockRet1.([]*Admin), m.MockError + return m.MockRet1.([]*linkedca.Admin), m.MockError } // UpdateAdmin mock -func (m *MockDB) UpdateAdmin(ctx context.Context, adm *Admin) error { +func (m *MockDB) UpdateAdmin(ctx context.Context, adm *linkedca.Admin) error { if m.MockUpdateAdmin != nil { return m.MockUpdateAdmin(ctx, adm) } return m.MockError } - -// CreateAuthConfig mock -func (m *MockDB) CreateAuthConfig(ctx context.Context, admin *AuthConfig) error { - if m.MockCreateAuthConfig != nil { - return m.MockCreateAuthConfig(ctx, admin) - } - return m.MockError -} - -// GetAuthConfig mock. -func (m *MockDB) GetAuthConfig(ctx context.Context, id string) (*AuthConfig, error) { - if m.MockGetAuthConfig != nil { - return m.MockGetAuthConfig(ctx, id) - } else if m.MockError != nil { - return nil, m.MockError - } - return m.MockRet1.(*AuthConfig), m.MockError -} - -// UpdateAuthConfig mock -func (m *MockDB) UpdateAuthConfig(ctx context.Context, adm *AuthConfig) error { - if m.MockUpdateAuthConfig != nil { - return m.MockUpdateAuthConfig(ctx, adm) - } - return m.MockError -} diff --git a/authority/mgmt/provisioner.go b/authority/mgmt/provisioner.go index 493f74b1..3c3042d0 100644 --- a/authority/mgmt/provisioner.go +++ b/authority/mgmt/provisioner.go @@ -1,31 +1,15 @@ package mgmt import ( - "context" "encoding/json" "fmt" "github.com/smallstep/certificates/authority/provisioner" - "github.com/smallstep/certificates/authority/status" + "github.com/smallstep/certificates/linkedca" "go.step.sm/crypto/jose" ) -type ProvisionerOption func(*ProvisionerCtx) - -type ProvisionerType string - -var ( - ProvisionerTypeACME = ProvisionerType("ACME") - ProvisionerTypeAWS = ProvisionerType("AWS") - ProvisionerTypeAZURE = ProvisionerType("AZURE") - ProvisionerTypeGCP = ProvisionerType("GCP") - ProvisionerTypeJWK = ProvisionerType("JWK") - ProvisionerTypeK8SSA = ProvisionerType("K8SSA") - ProvisionerTypeOIDC = ProvisionerType("OIDC") - ProvisionerTypeSSHPOP = ProvisionerType("SSHPOP") - ProvisionerTypeX5C = ProvisionerType("X5C") -) - +/* type unmarshalProvisioner struct { ID string `json:"-"` AuthorityID string `json:"-"` @@ -40,23 +24,8 @@ type unmarshalProvisioner struct { Status status.Type `json:"status"` } -// Provisioner type. -type Provisioner struct { - ID string `json:"-"` - AuthorityID string `json:"-"` - Type string `json:"type"` - Name string `json:"name"` - Claims *Claims `json:"claims"` - Details ProvisionerDetails `json:"details"` - X509Template string `json:"x509Template"` - X509TemplateData []byte `json:"x509TemplateData"` - SSHTemplate string `json:"sshTemplate"` - SSHTemplateData []byte `json:"sshTemplateData"` - Status status.Type `json:"status"` -} - type typ struct { - Type ProvisionerType `json:"type"` + Type linkedca.Provisioner_Type `json:"type"` } // UnmarshalJSON implements the Unmarshal interface. @@ -86,287 +55,48 @@ func (p *Provisioner) UnmarshalJSON(b []byte) error { return nil } +*/ -func (p *Provisioner) GetOptions() *provisioner.Options { +func provisionerGetOptions(p *linkedca.Provisioner) *provisioner.Options { return &provisioner.Options{ X509: &provisioner.X509Options{ - Template: p.X509Template, + Template: string(p.X509Template), TemplateData: p.X509TemplateData, }, SSH: &provisioner.SSHOptions{ - Template: p.SSHTemplate, - TemplateData: p.SSHTemplateData, + Template: string(p.SshTemplate), + TemplateData: p.SshTemplateData, }, } } -func CreateProvisioner(ctx context.Context, db DB, typ, name string, opts ...ProvisionerOption) (*Provisioner, error) { - pc := NewProvisionerCtx(opts...) - details, err := NewProvisionerDetails(ProvisionerType(typ), pc) - if err != nil { - return nil, err - } - - p := &Provisioner{ - Type: typ, - Name: name, - Claims: pc.Claims, - Details: details, - X509Template: pc.X509Template, - X509TemplateData: pc.X509TemplateData, - SSHTemplate: pc.SSHTemplate, - SSHTemplateData: pc.SSHTemplateData, - Status: status.Active, - } - - if err := db.CreateProvisioner(ctx, p); err != nil { - return nil, WrapErrorISE(err, "error creating provisioner") - } - return p, nil -} - -type ProvisionerCtx struct { - JWK *jose.JSONWebKey - JWE *jose.JSONWebEncryption - X509Template, SSHTemplate string - X509TemplateData, SSHTemplateData []byte - Claims *Claims - Password string -} - -func NewProvisionerCtx(opts ...ProvisionerOption) *ProvisionerCtx { - pc := &ProvisionerCtx{ - Claims: NewDefaultClaims(), - } - for _, o := range opts { - o(pc) - } - return pc -} - -func WithJWK(jwk *jose.JSONWebKey, jwe *jose.JSONWebEncryption) func(*ProvisionerCtx) { - return func(ctx *ProvisionerCtx) { - ctx.JWK = jwk - ctx.JWE = jwe - } -} - -func WithPassword(pass string) func(*ProvisionerCtx) { - return func(ctx *ProvisionerCtx) { - ctx.Password = pass - } -} - -// ProvisionerDetails is the interface implemented by all provisioner details -// attributes. -type ProvisionerDetails interface { - isProvisionerDetails() -} - -// ProvisionerDetailsJWK represents the values required by a JWK provisioner. -type ProvisionerDetailsJWK struct { - Type ProvisionerType `json:"type"` - PublicKey []byte `json:"publicKey"` - PrivateKey string `json:"PrivateKey"` -} - -// ProvisionerDetailsOIDC represents the values required by a OIDC provisioner. -type ProvisionerDetailsOIDC struct { - Type ProvisionerType `json:"type"` - ClientID string `json:"clientID"` - ClientSecret string `json:"clientSecret"` - ConfigurationEndpoint string `json:"configurationEndpoint"` - Admins []string `json:"admins"` - Domains []string `json:"domains"` - Groups []string `json:"groups"` - ListenAddress string `json:"listenAddress"` - TenantID string `json:"tenantID"` -} - -// ProvisionerDetailsGCP represents the values required by a GCP provisioner. -type ProvisionerDetailsGCP struct { - Type ProvisionerType `json:"type"` - ServiceAccounts []string `json:"serviceAccounts"` - ProjectIDs []string `json:"projectIDs"` - DisableCustomSANs bool `json:"disableCustomSANs"` - DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` - InstanceAge string `json:"instanceAge"` -} - -// ProvisionerDetailsAWS represents the values required by a AWS provisioner. -type ProvisionerDetailsAWS struct { - Type ProvisionerType `json:"type"` - Accounts []string `json:"accounts"` - DisableCustomSANs bool `json:"disableCustomSANs"` - DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` - InstanceAge string `json:"instanceAge"` -} - -// ProvisionerDetailsAzure represents the values required by a Azure provisioner. -type ProvisionerDetailsAzure struct { - Type ProvisionerType `json:"type"` - ResourceGroups []string `json:"resourceGroups"` - Audience string `json:"audience"` - DisableCustomSANs bool `json:"disableCustomSANs"` - DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` -} - -// ProvisionerDetailsACME represents the values required by a ACME provisioner. -type ProvisionerDetailsACME struct { - Type ProvisionerType `json:"type"` - ForceCN bool `json:"forceCN"` -} - -// ProvisionerDetailsX5C represents the values required by a X5C provisioner. -type ProvisionerDetailsX5C struct { - Type ProvisionerType `json:"type"` - Roots []byte `json:"roots"` -} - -// ProvisionerDetailsK8SSA represents the values required by a K8SSA provisioner. -type ProvisionerDetailsK8SSA struct { - Type ProvisionerType `json:"type"` - PublicKeys []byte `json:"publicKeys"` -} - -// ProvisionerDetailsSSHPOP represents the values required by a SSHPOP provisioner. -type ProvisionerDetailsSSHPOP struct { - Type ProvisionerType `json:"type"` -} - -func (*ProvisionerDetailsJWK) isProvisionerDetails() {} - -func (*ProvisionerDetailsOIDC) isProvisionerDetails() {} - -func (*ProvisionerDetailsGCP) isProvisionerDetails() {} - -func (*ProvisionerDetailsAWS) isProvisionerDetails() {} - -func (*ProvisionerDetailsAzure) isProvisionerDetails() {} - -func (*ProvisionerDetailsACME) isProvisionerDetails() {} - -func (*ProvisionerDetailsX5C) isProvisionerDetails() {} - -func (*ProvisionerDetailsK8SSA) isProvisionerDetails() {} - -func (*ProvisionerDetailsSSHPOP) isProvisionerDetails() {} - -func NewProvisionerDetails(typ ProvisionerType, pc *ProvisionerCtx) (ProvisionerDetails, error) { - switch typ { - case ProvisionerTypeJWK: - return createJWKDetails(pc) - /* - case ProvisionerTypeOIDC: - return createOIDCDetails(pc) - case ProvisionerTypeACME: - return createACMEDetails(pc) - case ProvisionerTypeK8SSA: - return createK8SSADetails(pc) - case ProvisionerTypeSSHPOP: - return createSSHPOPDetails(pc) - case ProvisionerTypeX5C: - return createSSHPOPDetails(pc) - */ - default: - return nil, NewErrorISE("unsupported provisioner type %s", typ) - } -} - -func createJWKDetails(pc *ProvisionerCtx) (*ProvisionerDetailsJWK, error) { - var err error - - if pc.JWK != nil && pc.JWE == nil { - return nil, NewErrorISE("JWE is required with JWK for createJWKProvisioner") - } - if pc.JWE != nil && pc.JWK == nil { - return nil, NewErrorISE("JWK is required with JWE for createJWKProvisioner") - } - if pc.JWK == nil && pc.JWE == nil { - // Create a new JWK w/ encrypted private key. - if pc.Password == "" { - return nil, NewErrorISE("password is required to provisioner with new keys") - } - pc.JWK, pc.JWE, err = jose.GenerateDefaultKeyPair([]byte(pc.Password)) - if err != nil { - return nil, WrapErrorISE(err, "error generating JWK key pair") - } - } - - jwkPubBytes, err := pc.JWK.MarshalJSON() - if err != nil { - return nil, WrapErrorISE(err, "error marshaling JWK") - } - jwePrivStr, err := pc.JWE.CompactSerialize() - if err != nil { - return nil, WrapErrorISE(err, "error serializing JWE") - } - - return &ProvisionerDetailsJWK{ - Type: ProvisionerTypeJWK, - PublicKey: jwkPubBytes, - PrivateKey: jwePrivStr, - }, nil -} - -func createACMEDetails(pc *ProvisionerCtx) (*ProvisionerDetailsJWK, error) { - var err error - - if pc.JWK != nil && pc.JWE == nil { - return nil, NewErrorISE("JWE is required with JWK for createJWKProvisioner") - } - if pc.JWE != nil && pc.JWK == nil { - return nil, NewErrorISE("JWK is required with JWE for createJWKProvisioner") - } - if pc.JWK == nil && pc.JWE == nil { - // Create a new JWK w/ encrypted private key. - if pc.Password == "" { - return nil, NewErrorISE("password is required to provisioner with new keys") - } - pc.JWK, pc.JWE, err = jose.GenerateDefaultKeyPair([]byte(pc.Password)) - if err != nil { - return nil, WrapErrorISE(err, "error generating JWK key pair") - } - } - - jwkPubBytes, err := pc.JWK.MarshalJSON() - if err != nil { - return nil, WrapErrorISE(err, "error marshaling JWK") - } - jwePrivStr, err := pc.JWE.CompactSerialize() - if err != nil { - return nil, WrapErrorISE(err, "error serializing JWE") - } - - return &ProvisionerDetailsJWK{ - Type: ProvisionerTypeJWK, - PublicKey: jwkPubBytes, - PrivateKey: jwePrivStr, - }, nil -} - -// ToCertificates converts the landlord provisioner type to the open source +// provisionerToCertificates converts the landlord provisioner type to the open source // provisioner type. -func (p *Provisioner) ToCertificates() (provisioner.Interface, error) { - claims, err := p.Claims.ToCertificates() +func provisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface, error) { + claims, err := claimsToCertificates(p.Claims) if err != nil { return nil, err } - switch details := p.Details.(type) { - case *ProvisionerDetailsJWK: + 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(details.PublicKey, &jwk); err != nil { + if err := json.Unmarshal(d.JWK.PublicKey, &jwk); err != nil { return nil, err } return &provisioner.JWK{ - ID: p.ID, - Type: p.Type, + ID: p.Id, + Type: p.Type.String(), Name: p.Name, Key: jwk, - EncryptedKey: details.PrivateKey, + EncryptedKey: string(d.JWK.EncryptedPrivateKey), Claims: claims, - Options: p.GetOptions(), + Options: provisionerGetOptions(p), }, nil /* case *ProvisionerDetails_OIDC: @@ -478,9 +208,9 @@ func (p *Provisioner) ToCertificates() (provisioner.Interface, error) { } } -// ToCertificates converts the landlord provisioner claims type to the open source +// claimsToCertificates converts the landlord provisioner claims type to the open source // (step-ca) claims type. -func (c *Claims) ToCertificates() (*provisioner.Claims, error) { +func claimsToCertificates(c *linkedca.Claims) (*provisioner.Claims, error) { var durs = map[string]struct { durStr string dur *provisioner.Duration @@ -488,12 +218,12 @@ func (c *Claims) ToCertificates() (*provisioner.Claims, error) { "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}, + "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 { @@ -513,10 +243,11 @@ func (c *Claims) ToCertificates() (*provisioner.Claims, error) { MinHostSSHDur: durs["minSSHHostDur"].dur, MaxHostSSHDur: durs["maxSSHHostDur"].dur, DefaultHostSSHDur: durs["defaultSSHHostDur"].dur, - EnableSSHCA: &c.SSH.Enabled, + EnableSSHCA: &c.Ssh.Enabled, }, nil } +/* type detailsType struct { Type ProvisionerType } @@ -557,3 +288,4 @@ func UnmarshalProvisionerDetails(data json.RawMessage) (ProvisionerDetails, erro } return v, nil } +*/ From 1726076ea2bc1d1c9466f544af4cf574edee7d2f Mon Sep 17 00:00:00 2001 From: max furman Date: Tue, 25 May 2021 16:52:06 -0700 Subject: [PATCH 18/89] wip --- authority/admin/collection.go | 9 -- authority/config/config.go | 4 +- authority/mgmt/config.go | 42 ++------- authority/mgmt/db.go | 20 +++++ authority/mgmt/db/nosql/admin.go | 93 ++++++++++---------- authority/mgmt/db/nosql/authConfig.go | 117 ------------------------- authority/mgmt/db/nosql/provisioner.go | 86 +++++++++--------- authority/mgmt/provisioner.go | 43 --------- linkedca/provisioners.go | 38 ++++++++ 9 files changed, 154 insertions(+), 298 deletions(-) delete mode 100644 authority/mgmt/db/nosql/authConfig.go create mode 100644 linkedca/provisioners.go diff --git a/authority/admin/collection.go b/authority/admin/collection.go index 11ba35fc..e9d41113 100644 --- a/authority/admin/collection.go +++ b/authority/admin/collection.go @@ -178,12 +178,3 @@ func loadAdmin(m *sync.Map, key string) (*linkedca.Admin, bool) { } return adm, true } - -/* -// provisionerSum returns the SHA1 of the provisioners ID. From this we will -// create the unique and sorted id. -func provisionerSum(p Interface) []byte { - sum := sha1.Sum([]byte(p.GetID())) - return sum[:] -} -*/ diff --git a/authority/config/config.go b/authority/config/config.go index 9fbf18e0..55041142 100644 --- a/authority/config/config.go +++ b/authority/config/config.go @@ -8,11 +8,11 @@ import ( "time" "github.com/pkg/errors" - "github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/authority/provisioner" cas "github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/db" kms "github.com/smallstep/certificates/kms/apiv1" + "github.com/smallstep/certificates/linkedca" "github.com/smallstep/certificates/templates" ) @@ -96,7 +96,7 @@ type AuthConfig struct { *cas.Options AuthorityID string `json:"authorityID,omitempty"` Provisioners provisioner.List `json:"provisioners"` - Admins []*admin.Admin `json:"-"` + Admins []*linkedca.Admin `json:"-"` Template *ASN1DN `json:"template,omitempty"` Claims *provisioner.Claims `json:"claims,omitempty"` DisableIssuedAtCheck bool `json:"disableIssuedAtCheck,omitempty"` diff --git a/authority/mgmt/config.go b/authority/mgmt/config.go index 4df53b75..ba821fc7 100644 --- a/authority/mgmt/config.go +++ b/authority/mgmt/config.go @@ -2,6 +2,7 @@ package mgmt import ( "github.com/smallstep/certificates/authority/config" + "github.com/smallstep/certificates/linkedca" ) const ( @@ -11,49 +12,22 @@ const ( DefaultAuthorityID = "00000000-0000-0000-0000-000000000000" ) -// Claims encapsulates all x509 and ssh claims applied to the authority -// configuration. E.g. maxTLSCertDuration, defaultSSHCertDuration, etc. -type Claims struct { - X509 *X509Claims `json:"x509Claims"` - SSH *SSHClaims `json:"sshClaims"` - DisableRenewal bool `json:"disableRenewal"` -} - -// X509Claims are the x509 claims applied to the authority. -type X509Claims struct { - Durations *Durations `json:"durations"` -} - -// SSHClaims are the ssh claims applied to the authority. -type SSHClaims struct { - Enabled bool `json:"enabled"` - UserDurations *Durations `json:"userDurations"` - HostDurations *Durations `json:"hostDurations"` -} - -// Durations represents min, max, default, duration. -type Durations struct { - Min string `json:"min"` - Max string `json:"max"` - Default string `json:"default"` -} - -func NewDefaultClaims() *Claims { - return &Claims{ - X509: &X509Claims{ - Durations: &Durations{ +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: &SSHClaims{ - UserDurations: &Durations{ + Ssh: &linkedca.SSHClaims{ + UserDurations: &linkedca.Durations{ Min: config.GlobalProvisionerClaims.MinUserSSHDur.String(), Max: config.GlobalProvisionerClaims.MaxUserSSHDur.String(), Default: config.GlobalProvisionerClaims.DefaultUserSSHDur.String(), }, - HostDurations: &Durations{ + HostDurations: &linkedca.Durations{ Min: config.GlobalProvisionerClaims.MinHostSSHDur.String(), Max: config.GlobalProvisionerClaims.MaxHostSSHDur.String(), Default: config.GlobalProvisionerClaims.DefaultHostSSHDur.String(), diff --git a/authority/mgmt/db.go b/authority/mgmt/db.go index 9716bb00..64ed39a2 100644 --- a/authority/mgmt/db.go +++ b/authority/mgmt/db.go @@ -17,11 +17,13 @@ type DB interface { GetProvisioner(ctx context.Context, id string) (*linkedca.Provisioner, error) GetProvisioners(ctx context.Context) ([]*linkedca.Provisioner, error) UpdateProvisioner(ctx context.Context, prov *linkedca.Provisioner) error + DeleteProvisioner(ctx context.Context, id string) error CreateAdmin(ctx context.Context, admin *linkedca.Admin) error GetAdmin(ctx context.Context, id string) (*linkedca.Admin, error) GetAdmins(ctx context.Context) ([]*linkedca.Admin, error) UpdateAdmin(ctx context.Context, admin *linkedca.Admin) error + DeleteAdmin(ctx context.Context, id string) error } // MockDB is an implementation of the DB interface that should only be used as @@ -31,11 +33,13 @@ type MockDB struct { MockGetProvisioner func(ctx context.Context, id string) (*linkedca.Provisioner, error) MockGetProvisioners func(ctx context.Context) ([]*linkedca.Provisioner, error) MockUpdateProvisioner func(ctx context.Context, prov *linkedca.Provisioner) error + MockDeleteProvisioner func(ctx context.Context, id string) error MockCreateAdmin func(ctx context.Context, adm *linkedca.Admin) error MockGetAdmin func(ctx context.Context, id string) (*linkedca.Admin, error) MockGetAdmins func(ctx context.Context) ([]*linkedca.Admin, error) MockUpdateAdmin func(ctx context.Context, adm *linkedca.Admin) error + MockDeleteAdmin func(ctx context.Context, id string) error MockError error MockRet1 interface{} @@ -79,6 +83,14 @@ func (m *MockDB) UpdateProvisioner(ctx context.Context, prov *linkedca.Provision return m.MockError } +// DeleteProvisioner mock +func (m *MockDB) DeleteProvisioner(ctx context.Context, id string) error { + if m.MockDeleteProvisioner != nil { + return m.MockDeleteProvisioner(ctx, id) + } + return m.MockError +} + // CreateAdmin mock func (m *MockDB) CreateAdmin(ctx context.Context, admin *linkedca.Admin) error { if m.MockCreateAdmin != nil { @@ -114,3 +126,11 @@ func (m *MockDB) UpdateAdmin(ctx context.Context, adm *linkedca.Admin) error { } return m.MockError } + +// DeleteAdmin mock +func (m *MockDB) DeleteAdmin(ctx context.Context, id string) error { + if m.MockDeleteAdmin != nil { + return m.MockDeleteAdmin(ctx, id) + } + return m.MockError +} diff --git a/authority/mgmt/db/nosql/admin.go b/authority/mgmt/db/nosql/admin.go index 9732b3f0..8fd0fea5 100644 --- a/authority/mgmt/db/nosql/admin.go +++ b/authority/mgmt/db/nosql/admin.go @@ -6,21 +6,20 @@ import ( "time" "github.com/pkg/errors" - "github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/authority/mgmt" - "github.com/smallstep/certificates/authority/status" + "github.com/smallstep/certificates/linkedca" "github.com/smallstep/nosql" ) // dbAdmin is the database representation of the Admin type. type dbAdmin struct { - ID string `json:"id"` - AuthorityID string `json:"authorityID"` - ProvisionerID string `json:"provisionerID"` - Subject string `json:"subject"` - Type admin.Type `json:"type"` - CreatedAt time.Time `json:"createdAt"` - DeletedAt time.Time `json:"deletedAt"` + ID string `json:"id"` + AuthorityID string `json:"authorityID"` + ProvisionerID string `json:"provisionerID"` + Subject string `json:"subject"` + Type linkedca.Admin_Type `json:"type"` + CreatedAt time.Time `json:"createdAt"` + DeletedAt time.Time `json:"deletedAt"` } func (dbp *dbAdmin) clone() *dbAdmin { @@ -59,30 +58,28 @@ func unmarshalDBAdmin(data []byte, id string) (*dbAdmin, error) { if err := json.Unmarshal(data, dba); err != nil { return nil, errors.Wrapf(err, "error unmarshaling admin %s into dbAdmin", id) } + if !dba.DeletedAt.IsZero() { + return nil, mgmt.NewError(mgmt.ErrorDeletedType, "admin %s is deleted", id) + } return dba, nil } -func unmarshalAdmin(data []byte, id string) (*mgmt.Admin, error) { - var dba = new(dbAdmin) - if err := json.Unmarshal(data, dba); err != nil { - return nil, errors.Wrapf(err, "error unmarshaling admin %s into dbAdmin", id) +func unmarshalAdmin(data []byte, id string) (*linkedca.Admin, error) { + dba, err := unmarshalDBAdmin(data, id) + if err != nil { + return nil, err } - adm := &mgmt.Admin{ - ID: dba.ID, - AuthorityID: dba.AuthorityID, - ProvisionerID: dba.ProvisionerID, + return &linkedca.Admin{ + Id: dba.ID, + AuthorityId: dba.AuthorityID, + ProvisionerId: dba.ProvisionerID, Subject: dba.Subject, Type: dba.Type, - Status: status.Active, - } - if !dba.DeletedAt.IsZero() { - adm.Status = status.Deleted - } - return adm, nil + }, nil } // GetAdmin retrieves and unmarshals a admin from the database. -func (db *DB) GetAdmin(ctx context.Context, id string) (*mgmt.Admin, error) { +func (db *DB) GetAdmin(ctx context.Context, id string) (*linkedca.Admin, error) { data, err := db.getDBAdminBytes(ctx, id) if err != nil { return nil, err @@ -91,12 +88,9 @@ func (db *DB) GetAdmin(ctx context.Context, id string) (*mgmt.Admin, error) { if err != nil { return nil, err } - if adm.Status == status.Deleted { - return nil, mgmt.NewError(mgmt.ErrorDeletedType, "admin %s is deleted", adm.ID) - } - if adm.AuthorityID != db.authorityID { + if adm.AuthorityId != db.authorityID { return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType, - "admin %s is not owned by authority %s", adm.ID, db.authorityID) + "admin %s is not owned by authority %s", adm.Id, db.authorityID) } return adm, nil @@ -105,21 +99,18 @@ func (db *DB) GetAdmin(ctx context.Context, id string) (*mgmt.Admin, error) { // GetAdmins retrieves and unmarshals all active (not deleted) admins // from the database. // TODO should we be paginating? -func (db *DB) GetAdmins(ctx context.Context) ([]*mgmt.Admin, error) { +func (db *DB) GetAdmins(ctx context.Context) ([]*linkedca.Admin, error) { dbEntries, err := db.db.List(authorityAdminsTable) if err != nil { return nil, errors.Wrap(err, "error loading admins") } - var admins = []*mgmt.Admin{} + var admins = []*linkedca.Admin{} for _, entry := range dbEntries { adm, err := unmarshalAdmin(entry.Value, string(entry.Key)) if err != nil { return nil, err } - if adm.Status == status.Deleted { - continue - } - if adm.AuthorityID != db.authorityID { + if adm.AuthorityId != db.authorityID { continue } admins = append(admins, adm) @@ -128,18 +119,18 @@ func (db *DB) GetAdmins(ctx context.Context) ([]*mgmt.Admin, error) { } // CreateAdmin stores a new admin to the database. -func (db *DB) CreateAdmin(ctx context.Context, adm *mgmt.Admin) error { +func (db *DB) CreateAdmin(ctx context.Context, adm *linkedca.Admin) error { var err error - adm.ID, err = randID() + adm.Id, err = randID() if err != nil { return mgmt.WrapErrorISE(err, "error generating random id for admin") } - adm.AuthorityID = db.authorityID + adm.AuthorityId = db.authorityID dba := &dbAdmin{ - ID: adm.ID, + ID: adm.Id, AuthorityID: db.authorityID, - ProvisionerID: adm.ProvisionerID, + ProvisionerID: adm.ProvisionerId, Subject: adm.Subject, Type: adm.Type, CreatedAt: clock.Now(), @@ -149,19 +140,27 @@ func (db *DB) CreateAdmin(ctx context.Context, adm *mgmt.Admin) error { } // UpdateAdmin saves an updated admin to the database. -func (db *DB) UpdateAdmin(ctx context.Context, adm *mgmt.Admin) error { - old, err := db.getDBAdmin(ctx, adm.ID) +func (db *DB) UpdateAdmin(ctx context.Context, adm *linkedca.Admin) error { + old, err := db.getDBAdmin(ctx, adm.Id) if err != nil { return err } nu := old.clone() - - // If the admin was active but is now deleted ... - if old.DeletedAt.IsZero() && adm.Status == status.Deleted { - nu.DeletedAt = clock.Now() - } nu.Type = adm.Type return db.save(ctx, old.ID, nu, old, "admin", authorityAdminsTable) } + +// DeleteAdmin saves an updated admin to the database. +func (db *DB) DeleteAdmin(ctx context.Context, id string) error { + old, err := db.getDBAdmin(ctx, id) + if err != nil { + return err + } + + nu := old.clone() + nu.DeletedAt = clock.Now() + + return db.save(ctx, old.ID, nu, old, "admin", authorityAdminsTable) +} diff --git a/authority/mgmt/db/nosql/authConfig.go b/authority/mgmt/db/nosql/authConfig.go deleted file mode 100644 index 222e729d..00000000 --- a/authority/mgmt/db/nosql/authConfig.go +++ /dev/null @@ -1,117 +0,0 @@ -package nosql - -import ( - "context" - "encoding/json" - "time" - - "github.com/pkg/errors" - "github.com/smallstep/certificates/authority/config" - "github.com/smallstep/certificates/authority/mgmt" - "github.com/smallstep/certificates/authority/status" - "github.com/smallstep/nosql" -) - -type dbAuthConfig struct { - ID string `json:"id"` - ASN1DN *config.ASN1DN `json:"asn1dn"` - Claims *mgmt.Claims `json:"claims"` - Backdate string `json:"backdate,omitempty"` - CreatedAt time.Time `json:"createdAt"` - DeletedAt time.Time `json:"deletedAt"` -} - -func (dbp *dbAuthConfig) clone() *dbAuthConfig { - u := *dbp - return &u -} - -func (db *DB) getDBAuthConfigBytes(ctx context.Context, id string) ([]byte, error) { - data, err := db.db.Get(authorityConfigsTable, []byte(id)) - if nosql.IsErrNotFound(err) { - return nil, mgmt.NewError(mgmt.ErrorNotFoundType, "authConfig %s not found", id) - } else if err != nil { - return nil, errors.Wrapf(err, "error loading authConfig %s", id) - } - return data, nil -} - -func (db *DB) getDBAuthConfig(ctx context.Context, id string) (*dbAuthConfig, error) { - data, err := db.getDBAuthConfigBytes(ctx, id) - if err != nil { - return nil, err - } - - var dba = new(dbAuthConfig) - if err = json.Unmarshal(data, dba); err != nil { - return nil, errors.Wrapf(err, "error unmarshaling authority %s into dbAuthConfig", id) - } - - return dba, nil -} - -// GetAuthConfig retrieves an AuthConfig configuration from the DB. -func (db *DB) GetAuthConfig(ctx context.Context, id string) (*mgmt.AuthConfig, error) { - dba, err := db.getDBAuthConfig(ctx, id) - if err != nil { - return nil, err - } - - provs, err := db.GetProvisioners(ctx) - if err != nil { - return nil, err - } - admins, err := db.GetAdmins(ctx) - if err != nil { - return nil, err - } - - return &mgmt.AuthConfig{ - ID: dba.ID, - Admins: admins, - Provisioners: provs, - ASN1DN: dba.ASN1DN, - Backdate: dba.Backdate, - Claims: dba.Claims, - }, nil -} - -// CreateAuthConfig stores a new provisioner to the database. -func (db *DB) CreateAuthConfig(ctx context.Context, ac *mgmt.AuthConfig) error { - var err error - if ac.ID == "" { - ac.ID, err = randID() - if err != nil { - return errors.Wrap(err, "error generating random id for provisioner") - } - } - - dba := &dbAuthConfig{ - ID: ac.ID, - ASN1DN: ac.ASN1DN, - Claims: ac.Claims, - Backdate: ac.Backdate, - CreatedAt: clock.Now(), - } - - return db.save(ctx, dba.ID, dba, nil, "authConfig", authorityConfigsTable) -} - -// UpdateAuthConfig saves an updated provisioner to the database. -func (db *DB) UpdateAuthConfig(ctx context.Context, ac *mgmt.AuthConfig) error { - old, err := db.getDBAuthConfig(ctx, ac.ID) - if err != nil { - return err - } - - nu := old.clone() - - // If the authority was active but is now deleted ... - if old.DeletedAt.IsZero() && ac.Status == status.Deleted { - nu.DeletedAt = clock.Now() - } - nu.Claims = ac.Claims - nu.Backdate = ac.Backdate - - return db.save(ctx, old.ID, nu, old, "authConfig", authorityProvisionersTable) -} diff --git a/authority/mgmt/db/nosql/provisioner.go b/authority/mgmt/db/nosql/provisioner.go index 2c2d65b0..473736e8 100644 --- a/authority/mgmt/db/nosql/provisioner.go +++ b/authority/mgmt/db/nosql/provisioner.go @@ -7,25 +7,25 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/authority/mgmt" - "github.com/smallstep/certificates/authority/status" + "github.com/smallstep/certificates/linkedca" "github.com/smallstep/nosql" ) // dbProvisioner is the database representation of a Provisioner type. type dbProvisioner struct { - ID string `json:"id"` - AuthorityID string `json:"authorityID"` - Type string `json:"type"` + ID string `json:"id"` + AuthorityID string `json:"authorityID"` + Type linkedca.Provisioner_Type `json:"type"` // Name is the key - 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"` - CreatedAt time.Time `json:"createdAt"` - DeletedAt time.Time `json:"deletedAt"` + Name string `json:"name"` + Claims *linkedca.Claims `json:"claims"` + Details []byte `json:"details"` + X509Template []byte `json:"x509Template"` + X509TemplateData []byte `json:"x509TemplateData"` + SSHTemplate []byte `json:"sshTemplate"` + SSHTemplateData []byte `json:"sshTemplateData"` + CreatedAt time.Time `json:"createdAt"` + DeletedAt time.Time `json:"deletedAt"` } type provisionerNameID struct { @@ -68,7 +68,7 @@ func (db *DB) getDBProvisioner(ctx context.Context, id string) (*dbProvisioner, } // GetProvisioner retrieves and unmarshals a provisioner from the database. -func (db *DB) GetProvisioner(ctx context.Context, id string) (*mgmt.Provisioner, error) { +func (db *DB) GetProvisioner(ctx context.Context, id string) (*linkedca.Provisioner, error) { data, err := db.getDBProvisionerBytes(ctx, id) if err != nil { return nil, err @@ -78,12 +78,9 @@ func (db *DB) GetProvisioner(ctx context.Context, id string) (*mgmt.Provisioner, if err != nil { return nil, err } - if prov.Status == status.Deleted { - return nil, mgmt.NewError(mgmt.ErrorDeletedType, "provisioner %s is deleted", prov.ID) - } - if prov.AuthorityID != db.authorityID { + if prov.AuthorityId != db.authorityID { return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType, - "provisioner %s is not owned by authority %s", prov.ID, db.authorityID) + "provisioner %s is not owned by authority %s", prov.Id, db.authorityID) } return prov, nil } @@ -93,56 +90,52 @@ func unmarshalDBProvisioner(data []byte, name string) (*dbProvisioner, error) { if err := json.Unmarshal(data, dbp); err != nil { return nil, errors.Wrapf(err, "error unmarshaling provisioner %s into dbProvisioner", name) } + if !dbp.DeletedAt.IsZero() { + return nil, mgmt.NewError(mgmt.ErrorDeletedType, "provisioner %s is deleted", name) + } return dbp, nil } -func unmarshalProvisioner(data []byte, name string) (*mgmt.Provisioner, error) { +func unmarshalProvisioner(data []byte, name string) (*linkedca.Provisioner, error) { dbp, err := unmarshalDBProvisioner(data, name) if err != nil { return nil, err } - details, err := mgmt.UnmarshalProvisionerDetails(dbp.Details) + details, err := linkedca.UnmarshalProvisionerDetails(dbp.Type, dbp.Details) if err != nil { return nil, err } - prov := &mgmt.Provisioner{ - ID: dbp.ID, - AuthorityID: dbp.AuthorityID, + prov := &linkedca.Provisioner{ + Id: dbp.ID, + AuthorityId: dbp.AuthorityID, Type: dbp.Type, Name: dbp.Name, Claims: dbp.Claims, Details: details, - Status: status.Active, X509Template: dbp.X509Template, X509TemplateData: dbp.X509TemplateData, - SSHTemplate: dbp.SSHTemplate, - SSHTemplateData: dbp.SSHTemplateData, - } - if !dbp.DeletedAt.IsZero() { - prov.Status = status.Deleted + SshTemplate: dbp.SSHTemplate, + SshTemplateData: dbp.SSHTemplateData, } return prov, nil } // GetProvisioners retrieves and unmarshals all active (not deleted) provisioners // from the database. -func (db *DB) GetProvisioners(ctx context.Context) ([]*mgmt.Provisioner, error) { +func (db *DB) GetProvisioners(ctx context.Context) ([]*linkedca.Provisioner, error) { dbEntries, err := db.db.List(authorityProvisionersTable) if err != nil { return nil, mgmt.WrapErrorISE(err, "error loading provisioners") } - var provs []*mgmt.Provisioner + var provs []*linkedca.Provisioner for _, entry := range dbEntries { prov, err := unmarshalProvisioner(entry.Value, string(entry.Key)) if err != nil { return nil, err } - if prov.Status == status.Deleted { - continue - } - if prov.AuthorityID != db.authorityID { + if prov.AuthorityId != db.authorityID { continue } provs = append(provs, prov) @@ -151,9 +144,9 @@ func (db *DB) GetProvisioners(ctx context.Context) ([]*mgmt.Provisioner, error) } // CreateProvisioner stores a new provisioner to the database. -func (db *DB) CreateProvisioner(ctx context.Context, prov *mgmt.Provisioner) error { +func (db *DB) CreateProvisioner(ctx context.Context, prov *linkedca.Provisioner) error { var err error - prov.ID, err = randID() + prov.Id, err = randID() if err != nil { return errors.Wrap(err, "error generating random id for provisioner") } @@ -164,7 +157,7 @@ func (db *DB) CreateProvisioner(ctx context.Context, prov *mgmt.Provisioner) err } dbp := &dbProvisioner{ - ID: prov.ID, + ID: prov.Id, AuthorityID: db.authorityID, Type: prov.Type, Name: prov.Name, @@ -172,12 +165,12 @@ func (db *DB) CreateProvisioner(ctx context.Context, prov *mgmt.Provisioner) err Details: details, X509Template: prov.X509Template, X509TemplateData: prov.X509TemplateData, - SSHTemplate: prov.SSHTemplate, - SSHTemplateData: prov.SSHTemplateData, + SSHTemplate: prov.SshTemplate, + SSHTemplateData: prov.SshTemplateData, CreatedAt: clock.Now(), } - if err := db.save(ctx, prov.ID, dbp, nil, "provisioner", authorityProvisionersTable); err != nil { + if err := db.save(ctx, prov.Id, dbp, nil, "provisioner", authorityProvisionersTable); err != nil { return mgmt.WrapErrorISE(err, "error creating provisioner %s", prov.Name) } @@ -185,8 +178,8 @@ func (db *DB) CreateProvisioner(ctx context.Context, prov *mgmt.Provisioner) err } // UpdateProvisioner saves an updated provisioner to the database. -func (db *DB) UpdateProvisioner(ctx context.Context, prov *mgmt.Provisioner) error { - old, err := db.getDBProvisioner(ctx, prov.ID) +func (db *DB) UpdateProvisioner(ctx context.Context, prov *linkedca.Provisioner) error { + old, err := db.getDBProvisioner(ctx, prov.Id) if err != nil { return err } @@ -202,9 +195,10 @@ func (db *DB) UpdateProvisioner(ctx context.Context, prov *mgmt.Provisioner) err } nu.X509Template = prov.X509Template nu.X509TemplateData = prov.X509TemplateData - nu.SSHTemplateData = prov.SSHTemplateData + nu.SSHTemplate = prov.SshTemplate + nu.SSHTemplateData = prov.SshTemplateData - if err := db.save(ctx, prov.ID, nu, old, "provisioner", authorityProvisionersTable); err != nil { + if err := db.save(ctx, prov.Id, nu, old, "provisioner", authorityProvisionersTable); err != nil { return mgmt.WrapErrorISE(err, "error updating provisioner %s", prov.Name) } diff --git a/authority/mgmt/provisioner.go b/authority/mgmt/provisioner.go index 3c3042d0..74e442f4 100644 --- a/authority/mgmt/provisioner.go +++ b/authority/mgmt/provisioner.go @@ -246,46 +246,3 @@ func claimsToCertificates(c *linkedca.Claims) (*provisioner.Claims, error) { EnableSSHCA: &c.Ssh.Enabled, }, nil } - -/* -type detailsType struct { - Type ProvisionerType -} - -// UnmarshalProvisionerDetails unmarshals bytes into the proper details type. -func UnmarshalProvisionerDetails(data json.RawMessage) (ProvisionerDetails, error) { - dt := new(detailsType) - if err := json.Unmarshal(data, dt); err != nil { - return nil, WrapErrorISE(err, "error unmarshaling provisioner details") - } - - var v ProvisionerDetails - switch dt.Type { - case ProvisionerTypeJWK: - v = new(ProvisionerDetailsJWK) - case ProvisionerTypeOIDC: - v = new(ProvisionerDetailsOIDC) - case ProvisionerTypeGCP: - v = new(ProvisionerDetailsGCP) - case ProvisionerTypeAWS: - v = new(ProvisionerDetailsAWS) - case ProvisionerTypeAZURE: - v = new(ProvisionerDetailsAzure) - case ProvisionerTypeACME: - v = new(ProvisionerDetailsACME) - case ProvisionerTypeX5C: - v = new(ProvisionerDetailsX5C) - case ProvisionerTypeK8SSA: - v = new(ProvisionerDetailsK8SSA) - case ProvisionerTypeSSHPOP: - v = new(ProvisionerDetailsSSHPOP) - default: - return nil, fmt.Errorf("unsupported provisioner type %s", dt.Type) - } - - if err := json.Unmarshal(data, v); err != nil { - return nil, err - } - return v, nil -} -*/ diff --git a/linkedca/provisioners.go b/linkedca/provisioners.go new file mode 100644 index 00000000..ea46f342 --- /dev/null +++ b/linkedca/provisioners.go @@ -0,0 +1,38 @@ +package linkedca + +import ( + "encoding/json" + "fmt" +) + +// UnmarshalProvisionerDetails unmarshals details type to the specific provisioner details. +func UnmarshalProvisionerDetails(typ Provisioner_Type, data []byte) (*ProvisionerDetails, error) { + var v isProvisionerDetails_Data + switch typ { + case Provisioner_JWK: + v = new(ProvisionerDetails_JWK) + case Provisioner_OIDC: + v = new(ProvisionerDetails_OIDC) + case Provisioner_GCP: + v = new(ProvisionerDetails_GCP) + case Provisioner_AWS: + v = new(ProvisionerDetails_AWS) + case Provisioner_AZURE: + v = new(ProvisionerDetails_Azure) + case Provisioner_ACME: + v = new(ProvisionerDetails_ACME) + case Provisioner_X5C: + v = new(ProvisionerDetails_X5C) + case Provisioner_K8SSA: + v = new(ProvisionerDetails_K8SSA) + case Provisioner_SSHPOP: + v = new(ProvisionerDetails_SSHPOP) + default: + return nil, fmt.Errorf("unsupported provisioner type %s", typ) + } + + if err := json.Unmarshal(data, v); err != nil { + return nil, err + } + return &ProvisionerDetails{Data: v}, nil +} From 01a4460812bcdd92b5ca65f059c47b990fb27064 Mon Sep 17 00:00:00 2001 From: max furman Date: Tue, 25 May 2021 21:13:01 -0700 Subject: [PATCH 19/89] 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 | 224 +++++-------------------- authority/provisioners.go | 207 +++++++++++++++++++++++ ca/adminClient.go | 67 +++----- 10 files changed, 377 insertions(+), 429 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 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. 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") + } + 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 } 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) From 94ba057f014d862e77296f9c6b847aa2476917e7 Mon Sep 17 00:00:00 2001 From: max furman Date: Wed, 26 May 2021 14:55:31 -0700 Subject: [PATCH 20/89] wip --- authority/authority.go | 17 ++++--- authority/mgmt/db/nosql/provisioner.go | 2 +- authority/mgmt/provisioner.go | 8 ++- authority/provisioners.go | 1 + ca/adminClient.go | 70 ++++++++++++++++++++++---- 5 files changed, 77 insertions(+), 21 deletions(-) diff --git a/authority/authority.go b/authority/authority.go index 3e089397..6be0ea8f 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -130,6 +130,7 @@ func NewEmbedded(opts ...Option) (*Authority, error) { return a, nil } +// ReloadAuthConfig reloads dynamic fields of the AuthConfig. func (a *Authority) ReloadAuthConfig(ctx context.Context) error { provs, err := a.adminDB.GetProvisioners(ctx) if err != nil { @@ -218,14 +219,20 @@ func (a *Authority) init() error { provs, err := a.adminDB.GetProvisioners(context.Background()) if err != nil { - return err + return mgmt.WrapErrorISE(err, "error getting provisioners to initialize authority") } if len(provs) == 0 { // Create First Provisioner prov, err := mgmt.CreateFirstProvisioner(context.Background(), a.adminDB, a.config.Password) if err != nil { - return err + return mgmt.WrapErrorISE(err, "error creating first provisioner") } + certProv, err := provisionerToCertificates(prov) + if err != nil { + return mgmt.WrapErrorISE(err, "error converting provisioner to certificates type") + } + a.config.AuthorityConfig.Provisioners = []provisioner.Interface{certProv} + // Create First Admin adm := &linkedca.Admin{ ProvisionerId: prov.Id, @@ -238,13 +245,9 @@ func (a *Authority) init() error { } 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") + return mgmt.WrapErrorISE(err, "error converting provisioner list to certificates type") } a.config.AuthorityConfig.Admins, err = a.adminDB.GetAdmins(context.Background()) if err != nil { diff --git a/authority/mgmt/db/nosql/provisioner.go b/authority/mgmt/db/nosql/provisioner.go index 1924aee3..fa5eaf2a 100644 --- a/authority/mgmt/db/nosql/provisioner.go +++ b/authority/mgmt/db/nosql/provisioner.go @@ -151,7 +151,7 @@ func (db *DB) CreateProvisioner(ctx context.Context, prov *linkedca.Provisioner) return errors.Wrap(err, "error generating random id for provisioner") } - details, err := json.Marshal(prov.Details) + details, err := json.Marshal(prov.Details.GetData()) if err != nil { return mgmt.WrapErrorISE(err, "error marshaling details when creating provisioner %s", prov.Name) } diff --git a/authority/mgmt/provisioner.go b/authority/mgmt/provisioner.go index 0386a894..2177e116 100644 --- a/authority/mgmt/provisioner.go +++ b/authority/mgmt/provisioner.go @@ -96,7 +96,7 @@ func CreateFirstProvisioner(ctx context.Context, db DB, password string) (*linke return nil, WrapErrorISE(err, "error serializing JWE") } - return &linkedca.Provisioner{ + p := &linkedca.Provisioner{ Name: "Admin JWK", Type: linkedca.Provisioner_JWK, Claims: NewDefaultClaims(), @@ -108,5 +108,9 @@ func CreateFirstProvisioner(ctx context.Context, db DB, password string) (*linke }, }, }, - }, nil + } + if err := db.CreateProvisioner(ctx, p); err != nil { + return nil, WrapErrorISE(err, "error creating provisioner") + } + return p, nil } diff --git a/authority/provisioners.go b/authority/provisioners.go index 599db4bd..48f67dc8 100644 --- a/authority/provisioners.go +++ b/authority/provisioners.go @@ -87,6 +87,7 @@ func provisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface, switch d := details.(type) { case *linkedca.ProvisionerDetails_JWK: + fmt.Printf("d = %+v\n", d) jwk := new(jose.JSONWebKey) if err := json.Unmarshal(d.JWK.PublicKey, &jwk); err != nil { return nil, err diff --git a/ca/adminClient.go b/ca/adminClient.go index 32d6339d..ad708146 100644 --- a/ca/adminClient.go +++ b/ca/adminClient.go @@ -12,6 +12,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/authority/mgmt" mgmtAPI "github.com/smallstep/certificates/authority/mgmt/api" + "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/linkedca" ) @@ -92,7 +93,7 @@ retry: return adm, nil } -// AdminOption is the type of options passed to the Provisioner method. +// AdminOption is the type of options passed to the Admin method. type AdminOption func(o *adminOptions) error type adminOptions struct { @@ -136,8 +137,8 @@ func WithAdminLimit(limit int) AdminOption { } } -// GetAdmins performs the GET /admin/admins request to the CA. -func (c *AdminClient) GetAdmins(opts ...AdminOption) (*mgmtAPI.GetAdminsResponse, error) { +// GetAdminsPaginate returns a page from the the GET /admin/admins request to the CA. +func (c *AdminClient) GetAdminsPaginate(opts ...AdminOption) (*mgmtAPI.GetAdminsResponse, error) { var retried bool o := new(adminOptions) if err := o.apply(opts); err != nil { @@ -166,6 +167,26 @@ retry: return body, nil } +// GetAdmins returns all admins from the GET /admin/admins request to the CA. +func (c *AdminClient) GetAdmins(opts ...AdminOption) ([]*linkedca.Admin, error) { + var ( + cursor = "" + admins = []*linkedca.Admin{} + ) + for { + resp, err := c.GetAdminsPaginate(WithAdminCursor(cursor), WithAdminLimit(100)) + if err != nil { + return nil, err + } + admins = append(admins, resp.Admins...) + if resp.NextCursor == "" { + return admins, nil + } + cursor = resp.NextCursor + } + return admins, nil +} + // CreateAdmin performs the POST /admin/admins request to the CA. func (c *AdminClient) CreateAdmin(req *mgmtAPI.CreateAdminRequest) (*linkedca.Admin, error) { var retried bool @@ -247,8 +268,8 @@ retry: return adm, nil } -// GetProvisioner performs the GET /admin/provisioners/{name} request to the CA. -func (c *AdminClient) GetProvisioner(name string) (*linkedca.Provisioner, error) { +// GetProvisionerByName performs the GET /admin/provisioners/{name} request to the CA. +func (c *AdminClient) GetProvisionerByName(name string) (*linkedca.Provisioner, error) { var retried bool u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", name)}) retry: @@ -270,10 +291,17 @@ retry: return prov, nil } -// GetProvisioners performs the GET /admin/provisioners request to the CA. -func (c *AdminClient) GetProvisioners() ([]*linkedca.Provisioner, error) { +// GetProvisionersPaginate performs the GET /admin/provisioners request to the CA. +func (c *AdminClient) GetProvisionersPaginate(opts ...ProvisionerOption) (*mgmtAPI.GetProvisionersResponse, error) { var retried bool - u := c.endpoint.ResolveReference(&url.URL{Path: "/admin/provisioners"}) + o := new(provisionerOptions) + if err := o.apply(opts); err != nil { + return nil, err + } + u := c.endpoint.ResolveReference(&url.URL{ + Path: "/admin/provisioners", + RawQuery: o.rawQuery(), + }) retry: resp, err := c.client.Get(u.String()) if err != nil { @@ -286,11 +314,31 @@ retry: } return nil, readAdminError(resp.Body) } - var provs = new([]*linkedca.Provisioner) - if err := readJSON(resp.Body, provs); err != nil { + var body = new(mgmtAPI.GetProvisionersResponse) + if err := readJSON(resp.Body, body); err != nil { return nil, errors.Wrapf(err, "error reading %s", u) } - return *provs, nil + return body, nil +} + +// GetProvisioners returns all admins from the GET /admin/admins request to the CA. +func (c *AdminClient) GetProvisioners(opts ...AdminOption) (provisioner.List, error) { + var ( + cursor = "" + provs = provisioner.List{} + ) + for { + resp, err := c.GetProvisionersPaginate(WithProvisionerCursor(cursor), WithProvisionerLimit(100)) + if err != nil { + return nil, err + } + provs = append(provs, resp.Provisioners...) + if resp.NextCursor == "" { + return provs, nil + } + cursor = resp.NextCursor + } + return provs, nil } // RemoveProvisioner performs the DELETE /admin/provisioners/{name} request to the CA. From d7a747b92ba90688eac74c354b1d5ccb89cb38ea Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 26 May 2021 15:49:18 -0700 Subject: [PATCH 21/89] Add SCEP in the provisioners proto. Change provisioner.proto to match protobuf style guide. --- linkedca/provisioners.pb.go | 451 +++++++++++++++++++++++------------- linkedca/provisioners.proto | 131 ++++++----- 2 files changed, 354 insertions(+), 228 deletions(-) diff --git a/linkedca/provisioners.pb.go b/linkedca/provisioners.pb.go index 1a8eb681..4b8ffab0 100644 --- a/linkedca/provisioners.pb.go +++ b/linkedca/provisioners.pb.go @@ -33,21 +33,23 @@ const ( Provisioner_X5C Provisioner_Type = 7 Provisioner_K8SSA Provisioner_Type = 8 Provisioner_SSHPOP Provisioner_Type = 9 + Provisioner_SCEP Provisioner_Type = 10 ) // Enum value maps for Provisioner_Type. var ( Provisioner_Type_name = map[int32]string{ - 0: "NOOP", - 1: "JWK", - 2: "OIDC", - 3: "GCP", - 4: "AWS", - 5: "AZURE", - 6: "ACME", - 7: "X5C", - 8: "K8SSA", - 9: "SSHPOP", + 0: "NOOP", + 1: "JWK", + 2: "OIDC", + 3: "GCP", + 4: "AWS", + 5: "AZURE", + 6: "ACME", + 7: "X5C", + 8: "K8SSA", + 9: "SSHPOP", + 10: "SCEP", } Provisioner_Type_value = map[string]int32{ "NOOP": 0, @@ -60,6 +62,7 @@ var ( "X5C": 7, "K8SSA": 8, "SSHPOP": 9, + "SCEP": 10, } ) @@ -224,6 +227,7 @@ type ProvisionerDetails struct { // *ProvisionerDetails_X5C // *ProvisionerDetails_K8SSA // *ProvisionerDetails_SSHPOP + // *ProvisionerDetails_SCEP Data isProvisionerDetails_Data `protobuf_oneof:"data"` } @@ -329,6 +333,13 @@ func (x *ProvisionerDetails) GetSSHPOP() *SSHPOPProvisioner { return nil } +func (x *ProvisionerDetails) GetSCEP() *SCEPProvisioner { + if x, ok := x.GetData().(*ProvisionerDetails_SCEP); ok { + return x.SCEP + } + return nil +} + type isProvisionerDetails_Data interface { isProvisionerDetails_Data() } @@ -369,6 +380,10 @@ type ProvisionerDetails_SSHPOP struct { SSHPOP *SSHPOPProvisioner `protobuf:"bytes,28,opt,name=SSHPOP,proto3,oneof"` } +type ProvisionerDetails_SCEP struct { + SCEP *SCEPProvisioner `protobuf:"bytes,29,opt,name=SCEP,proto3,oneof"` +} + func (*ProvisionerDetails_JWK) isProvisionerDetails_Data() {} func (*ProvisionerDetails_OIDC) isProvisionerDetails_Data() {} @@ -387,6 +402,8 @@ func (*ProvisionerDetails_K8SSA) isProvisionerDetails_Data() {} func (*ProvisionerDetails_SSHPOP) isProvisionerDetails_Data() {} +func (*ProvisionerDetails_SCEP) isProvisionerDetails_Data() {} + type ProvisionerList struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1244,12 +1261,83 @@ func (*SSHPOPProvisioner) Descriptor() ([]byte, []int) { return file_linkedca_provisioners_proto_rawDescGZIP(), []int{15} } +type SCEPProvisioner struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ForceCn bool `protobuf:"varint,1,opt,name=force_cn,json=forceCn,proto3" json:"force_cn,omitempty"` + Challenge string `protobuf:"bytes,2,opt,name=challenge,proto3" json:"challenge,omitempty"` + Capabilities []string `protobuf:"bytes,3,rep,name=capabilities,proto3" json:"capabilities,omitempty"` + MinimumPublicKeyLength int32 `protobuf:"varint,4,opt,name=minimum_public_key_length,json=minimumPublicKeyLength,proto3" json:"minimum_public_key_length,omitempty"` +} + +func (x *SCEPProvisioner) Reset() { + *x = SCEPProvisioner{} + if protoimpl.UnsafeEnabled { + mi := &file_linkedca_provisioners_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SCEPProvisioner) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SCEPProvisioner) ProtoMessage() {} + +func (x *SCEPProvisioner) ProtoReflect() protoreflect.Message { + mi := &file_linkedca_provisioners_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SCEPProvisioner.ProtoReflect.Descriptor instead. +func (*SCEPProvisioner) Descriptor() ([]byte, []int) { + return file_linkedca_provisioners_proto_rawDescGZIP(), []int{16} +} + +func (x *SCEPProvisioner) GetForceCn() bool { + if x != nil { + return x.ForceCn + } + return false +} + +func (x *SCEPProvisioner) GetChallenge() string { + if x != nil { + return x.Challenge + } + return "" +} + +func (x *SCEPProvisioner) GetCapabilities() []string { + if x != nil { + return x.Capabilities + } + return nil +} + +func (x *SCEPProvisioner) GetMinimumPublicKeyLength() int32 { + if x != nil { + return x.MinimumPublicKeyLength + } + return 0 +} + var File_linkedca_provisioners_proto protoreflect.FileDescriptor var file_linkedca_provisioners_proto_rawDesc = []byte{ 0x0a, 0x1b, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x6c, - 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x22, 0xf4, 0x03, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x22, 0xfe, 0x03, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, @@ -1274,155 +1362,169 @@ var file_linkedca_provisioners_proto_rawDesc = []byte{ 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x73, 0x68, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x73, 0x73, 0x68, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x61, - 0x74, 0x61, 0x22, 0x6a, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, + 0x74, 0x61, 0x22, 0x74, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4f, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4a, 0x57, 0x4b, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x4f, 0x49, 0x44, 0x43, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x47, 0x43, 0x50, 0x10, 0x03, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x57, 0x53, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x5a, 0x55, 0x52, 0x45, 0x10, 0x05, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x43, 0x4d, 0x45, 0x10, 0x06, 0x12, 0x07, 0x0a, 0x03, 0x58, 0x35, 0x43, 0x10, 0x07, 0x12, 0x09, 0x0a, 0x05, 0x4b, 0x38, 0x53, 0x53, 0x41, - 0x10, 0x08, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x53, 0x48, 0x50, 0x4f, 0x50, 0x10, 0x09, 0x22, 0xd5, - 0x03, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x65, - 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x2c, 0x0a, 0x03, 0x4a, 0x57, 0x4b, 0x18, 0x14, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x4a, 0x57, - 0x4b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, - 0x4a, 0x57, 0x4b, 0x12, 0x2f, 0x0a, 0x04, 0x4f, 0x49, 0x44, 0x43, 0x18, 0x15, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x4f, 0x49, 0x44, - 0x43, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x04, - 0x4f, 0x49, 0x44, 0x43, 0x12, 0x2c, 0x0a, 0x03, 0x47, 0x43, 0x50, 0x18, 0x16, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x47, 0x43, 0x50, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, 0x47, - 0x43, 0x50, 0x12, 0x2c, 0x0a, 0x03, 0x41, 0x57, 0x53, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x18, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x41, 0x57, 0x53, 0x50, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, 0x41, 0x57, 0x53, - 0x12, 0x32, 0x0a, 0x05, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x41, 0x7a, 0x75, 0x72, 0x65, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x05, 0x41, - 0x7a, 0x75, 0x72, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x41, 0x43, 0x4d, 0x45, 0x18, 0x19, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x41, 0x43, - 0x4d, 0x45, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, - 0x04, 0x41, 0x43, 0x4d, 0x45, 0x12, 0x2c, 0x0a, 0x03, 0x58, 0x35, 0x43, 0x18, 0x1a, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x58, 0x35, - 0x43, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, - 0x58, 0x35, 0x43, 0x12, 0x32, 0x0a, 0x05, 0x4b, 0x38, 0x73, 0x53, 0x41, 0x18, 0x1b, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x4b, 0x38, - 0x73, 0x53, 0x41, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, - 0x52, 0x05, 0x4b, 0x38, 0x73, 0x53, 0x41, 0x12, 0x35, 0x0a, 0x06, 0x53, 0x53, 0x48, 0x50, 0x4f, - 0x50, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, - 0x63, 0x61, 0x2e, 0x53, 0x53, 0x48, 0x50, 0x4f, 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x06, 0x53, 0x53, 0x48, 0x50, 0x4f, 0x50, 0x42, 0x06, - 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x4c, 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x39, 0x0a, 0x0c, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x15, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x06, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, - 0x28, 0x0a, 0x04, 0x78, 0x35, 0x30, 0x39, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, - 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x58, 0x35, 0x30, 0x39, 0x43, 0x6c, 0x61, - 0x69, 0x6d, 0x73, 0x52, 0x04, 0x78, 0x35, 0x30, 0x39, 0x12, 0x25, 0x0a, 0x03, 0x73, 0x73, 0x68, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, - 0x61, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x03, 0x73, 0x73, 0x68, - 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x6e, 0x65, - 0x77, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x64, 0x69, 0x73, 0x61, 0x62, - 0x6c, 0x65, 0x52, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x22, 0x59, 0x0a, 0x0a, 0x58, 0x35, 0x30, - 0x39, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, - 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, - 0x64, 0x12, 0x31, 0x0a, 0x09, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, - 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x09, 0x64, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x9d, 0x01, 0x0a, 0x09, 0x53, 0x53, 0x48, 0x43, 0x6c, 0x61, 0x69, + 0x10, 0x08, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x53, 0x48, 0x50, 0x4f, 0x50, 0x10, 0x09, 0x12, 0x08, + 0x0a, 0x04, 0x53, 0x43, 0x45, 0x50, 0x10, 0x0a, 0x22, 0x86, 0x04, 0x0a, 0x12, 0x50, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, + 0x2c, 0x0a, 0x03, 0x4a, 0x57, 0x4b, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, + 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x4a, 0x57, 0x4b, 0x50, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, 0x4a, 0x57, 0x4b, 0x12, 0x2f, 0x0a, + 0x04, 0x4f, 0x49, 0x44, 0x43, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x69, + 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x4f, 0x49, 0x44, 0x43, 0x50, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x04, 0x4f, 0x49, 0x44, 0x43, 0x12, 0x2c, + 0x0a, 0x03, 0x47, 0x43, 0x50, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, + 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x47, 0x43, 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, 0x47, 0x43, 0x50, 0x12, 0x2c, 0x0a, 0x03, + 0x41, 0x57, 0x53, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, + 0x65, 0x64, 0x63, 0x61, 0x2e, 0x41, 0x57, 0x53, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, 0x41, 0x57, 0x53, 0x12, 0x32, 0x0a, 0x05, 0x41, 0x7a, + 0x75, 0x72, 0x65, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, + 0x65, 0x64, 0x63, 0x61, 0x2e, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x05, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x12, 0x2f, + 0x0a, 0x04, 0x41, 0x43, 0x4d, 0x45, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, + 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x41, 0x43, 0x4d, 0x45, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x04, 0x41, 0x43, 0x4d, 0x45, 0x12, + 0x2c, 0x0a, 0x03, 0x58, 0x35, 0x43, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, + 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x58, 0x35, 0x43, 0x50, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, 0x58, 0x35, 0x43, 0x12, 0x32, 0x0a, + 0x05, 0x4b, 0x38, 0x73, 0x53, 0x41, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, + 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x4b, 0x38, 0x73, 0x53, 0x41, 0x50, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x05, 0x4b, 0x38, 0x73, 0x53, + 0x41, 0x12, 0x35, 0x0a, 0x06, 0x53, 0x53, 0x48, 0x50, 0x4f, 0x50, 0x18, 0x1c, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x53, 0x53, 0x48, + 0x50, 0x4f, 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, + 0x52, 0x06, 0x53, 0x53, 0x48, 0x50, 0x4f, 0x50, 0x12, 0x2f, 0x0a, 0x04, 0x53, 0x43, 0x45, 0x50, + 0x18, 0x1d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, + 0x61, 0x2e, 0x53, 0x43, 0x45, 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x48, 0x00, 0x52, 0x04, 0x53, 0x43, 0x45, 0x50, 0x42, 0x06, 0x0a, 0x04, 0x64, 0x61, 0x74, + 0x61, 0x22, 0x4c, 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x4c, 0x69, 0x73, 0x74, 0x12, 0x39, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x69, 0x6e, + 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x22, + 0x82, 0x01, 0x0a, 0x06, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x28, 0x0a, 0x04, 0x78, 0x35, + 0x30, 0x39, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, + 0x64, 0x63, 0x61, 0x2e, 0x58, 0x35, 0x30, 0x39, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x04, + 0x78, 0x35, 0x30, 0x39, 0x12, 0x25, 0x0a, 0x03, 0x73, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x53, 0x53, 0x48, + 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x03, 0x73, 0x73, 0x68, 0x12, 0x27, 0x0a, 0x0f, 0x64, + 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x6e, + 0x65, 0x77, 0x61, 0x6c, 0x22, 0x59, 0x0a, 0x0a, 0x58, 0x35, 0x30, 0x39, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x3a, 0x0a, 0x0e, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, - 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x44, - 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3a, 0x0a, 0x0e, 0x68, 0x6f, 0x73, 0x74, - 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x13, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x44, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0d, 0x68, 0x6f, 0x73, 0x74, 0x44, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x49, 0x0a, 0x09, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6d, - 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x12, 0x10, 0x0a, - 0x03, 0x6d, 0x61, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x61, 0x78, 0x22, - 0x63, 0x0a, 0x0e, 0x4a, 0x57, 0x4b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, - 0x12, 0x32, 0x0a, 0x15, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x72, - 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x13, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, - 0x65, 0x4b, 0x65, 0x79, 0x22, 0x98, 0x02, 0x0a, 0x0f, 0x4f, 0x49, 0x44, 0x43, 0x50, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, - 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, - 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x35, 0x0a, 0x16, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, - 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x06, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6f, 0x6d, 0x61, - 0x69, 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x06, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x06, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x6c, - 0x69, 0x73, 0x74, 0x65, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x22, - 0xeb, 0x01, 0x0a, 0x0e, 0x47, 0x43, 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x0a, - 0x0b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x73, 0x12, 0x2e, - 0x0a, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, - 0x5f, 0x73, 0x61, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, - 0x61, 0x62, 0x6c, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x61, 0x6e, 0x73, 0x12, 0x3a, - 0x0a, 0x1a, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x5f, - 0x6f, 0x6e, 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, - 0x4f, 0x6e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x55, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, - 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x67, 0x65, 0x22, 0xbb, 0x01, - 0x0a, 0x0e, 0x41, 0x57, 0x53, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x13, - 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x73, - 0x61, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, - 0x6c, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x61, 0x6e, 0x73, 0x12, 0x3a, 0x0a, 0x1a, - 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x5f, 0x6f, 0x6e, - 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x4f, 0x6e, - 0x46, 0x69, 0x72, 0x73, 0x74, 0x55, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x73, 0x74, - 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, - 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x67, 0x65, 0x22, 0xe0, 0x01, 0x0a, 0x10, - 0x41, 0x7a, 0x75, 0x72, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x27, 0x0a, - 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, - 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, - 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x75, - 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x73, 0x61, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x61, - 0x6e, 0x73, 0x12, 0x3a, 0x0a, 0x1a, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x5f, 0x6f, 0x6e, 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x54, - 0x72, 0x75, 0x73, 0x74, 0x4f, 0x6e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x55, 0x73, 0x65, 0x22, 0x2c, - 0x0a, 0x0f, 0x41, 0x43, 0x4d, 0x45, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6e, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x07, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6e, 0x22, 0x26, 0x0a, 0x0e, - 0x58, 0x35, 0x43, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x14, - 0x0a, 0x05, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x72, - 0x6f, 0x6f, 0x74, 0x73, 0x22, 0x33, 0x0a, 0x10, 0x4b, 0x38, 0x73, 0x53, 0x41, 0x50, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x75, 0x62, 0x6c, - 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x70, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x53, 0x53, 0x48, - 0x50, 0x4f, 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x42, 0x2c, - 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6d, 0x61, - 0x6c, 0x6c, 0x73, 0x74, 0x65, 0x70, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x73, 0x2f, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x31, 0x0a, 0x09, + 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x09, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, + 0x9d, 0x01, 0x0a, 0x09, 0x53, 0x53, 0x48, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x18, 0x0a, + 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x3a, 0x0a, 0x0e, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x12, 0x3a, 0x0a, 0x0e, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x64, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x69, + 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x0d, 0x68, 0x6f, 0x73, 0x74, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, + 0x49, 0x0a, 0x09, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, + 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, + 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x78, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x61, 0x78, 0x22, 0x63, 0x0a, 0x0e, 0x4a, 0x57, + 0x4b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, + 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x32, 0x0a, 0x15, 0x65, + 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x13, 0x65, 0x6e, 0x63, 0x72, + 0x79, 0x70, 0x74, 0x65, 0x64, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x22, + 0x98, 0x02, 0x0a, 0x0f, 0x4f, 0x49, 0x44, 0x43, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, + 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x35, 0x0a, 0x16, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x61, 0x64, + 0x6d, 0x69, 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, + 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x16, + 0x0a, 0x06, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, + 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, + 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x22, 0xeb, 0x01, 0x0a, 0x0e, 0x47, + 0x43, 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x29, 0x0a, + 0x10, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x6a, + 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x70, + 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x69, 0x73, + 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x73, 0x61, 0x6e, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x61, 0x6e, 0x73, 0x12, 0x3a, 0x0a, 0x1a, 0x64, 0x69, 0x73, + 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x5f, 0x6f, 0x6e, 0x5f, 0x66, 0x69, + 0x72, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x64, + 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x4f, 0x6e, 0x46, 0x69, 0x72, + 0x73, 0x74, 0x55, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x5f, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x73, + 0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x67, 0x65, 0x22, 0xbb, 0x01, 0x0a, 0x0e, 0x41, 0x57, 0x53, + 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x61, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x61, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, + 0x6c, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x73, 0x61, 0x6e, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x53, 0x61, 0x6e, 0x73, 0x12, 0x3a, 0x0a, 0x1a, 0x64, 0x69, 0x73, 0x61, 0x62, + 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x5f, 0x6f, 0x6e, 0x5f, 0x66, 0x69, 0x72, 0x73, + 0x74, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x64, 0x69, 0x73, + 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x4f, 0x6e, 0x46, 0x69, 0x72, 0x73, 0x74, + 0x55, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, + 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x41, 0x67, 0x65, 0x22, 0xe0, 0x01, 0x0a, 0x10, 0x41, 0x7a, 0x75, 0x72, 0x65, + 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x74, + 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, + 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, + 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, + 0x73, 0x61, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, + 0x62, 0x6c, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x61, 0x6e, 0x73, 0x12, 0x3a, 0x0a, + 0x1a, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x5f, 0x6f, + 0x6e, 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x4f, + 0x6e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x55, 0x73, 0x65, 0x22, 0x2c, 0x0a, 0x0f, 0x41, 0x43, 0x4d, + 0x45, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, + 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x66, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6e, 0x22, 0x26, 0x0a, 0x0e, 0x58, 0x35, 0x43, 0x50, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x6f, + 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x22, + 0x33, 0x0a, 0x10, 0x4b, 0x38, 0x73, 0x53, 0x41, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, + 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x4b, 0x65, 0x79, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x53, 0x53, 0x48, 0x50, 0x4f, 0x50, 0x50, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x22, 0xa9, 0x01, 0x0a, 0x0f, 0x53, 0x43, + 0x45, 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x19, 0x0a, + 0x08, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x07, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x68, 0x61, 0x6c, + 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, 0x61, + 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, + 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x61, + 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x19, 0x6d, 0x69, + 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, + 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x16, 0x6d, + 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x4c, + 0x65, 0x6e, 0x67, 0x74, 0x68, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x73, 0x74, 0x65, 0x70, 0x2f, 0x63, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2f, 0x6c, 0x69, 0x6e, 0x6b, 0x65, + 0x64, 0x63, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1438,7 +1540,7 @@ func file_linkedca_provisioners_proto_rawDescGZIP() []byte { } var file_linkedca_provisioners_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_linkedca_provisioners_proto_msgTypes = make([]protoimpl.MessageInfo, 16) +var file_linkedca_provisioners_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_linkedca_provisioners_proto_goTypes = []interface{}{ (Provisioner_Type)(0), // 0: linkedca.Provisioner.Type (*Provisioner)(nil), // 1: linkedca.Provisioner @@ -1457,6 +1559,7 @@ var file_linkedca_provisioners_proto_goTypes = []interface{}{ (*X5CProvisioner)(nil), // 14: linkedca.X5CProvisioner (*K8SSAProvisioner)(nil), // 15: linkedca.K8sSAProvisioner (*SSHPOPProvisioner)(nil), // 16: linkedca.SSHPOPProvisioner + (*SCEPProvisioner)(nil), // 17: linkedca.SCEPProvisioner } var file_linkedca_provisioners_proto_depIdxs = []int32{ 0, // 0: linkedca.Provisioner.type:type_name -> linkedca.Provisioner.Type @@ -1471,17 +1574,18 @@ var file_linkedca_provisioners_proto_depIdxs = []int32{ 14, // 9: linkedca.ProvisionerDetails.X5C:type_name -> linkedca.X5CProvisioner 15, // 10: linkedca.ProvisionerDetails.K8sSA:type_name -> linkedca.K8sSAProvisioner 16, // 11: linkedca.ProvisionerDetails.SSHPOP:type_name -> linkedca.SSHPOPProvisioner - 1, // 12: linkedca.ProvisionerList.provisioners:type_name -> linkedca.Provisioner - 5, // 13: linkedca.Claims.x509:type_name -> linkedca.X509Claims - 6, // 14: linkedca.Claims.ssh:type_name -> linkedca.SSHClaims - 7, // 15: linkedca.X509Claims.durations:type_name -> linkedca.Durations - 7, // 16: linkedca.SSHClaims.user_durations:type_name -> linkedca.Durations - 7, // 17: linkedca.SSHClaims.host_durations:type_name -> linkedca.Durations - 18, // [18:18] is the sub-list for method output_type - 18, // [18:18] is the sub-list for method input_type - 18, // [18:18] is the sub-list for extension type_name - 18, // [18:18] is the sub-list for extension extendee - 0, // [0:18] is the sub-list for field type_name + 17, // 12: linkedca.ProvisionerDetails.SCEP:type_name -> linkedca.SCEPProvisioner + 1, // 13: linkedca.ProvisionerList.provisioners:type_name -> linkedca.Provisioner + 5, // 14: linkedca.Claims.x509:type_name -> linkedca.X509Claims + 6, // 15: linkedca.Claims.ssh:type_name -> linkedca.SSHClaims + 7, // 16: linkedca.X509Claims.durations:type_name -> linkedca.Durations + 7, // 17: linkedca.SSHClaims.user_durations:type_name -> linkedca.Durations + 7, // 18: linkedca.SSHClaims.host_durations:type_name -> linkedca.Durations + 19, // [19:19] is the sub-list for method output_type + 19, // [19:19] is the sub-list for method input_type + 19, // [19:19] is the sub-list for extension type_name + 19, // [19:19] is the sub-list for extension extendee + 0, // [0:19] is the sub-list for field type_name } func init() { file_linkedca_provisioners_proto_init() } @@ -1682,6 +1786,18 @@ func file_linkedca_provisioners_proto_init() { return nil } } + file_linkedca_provisioners_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SCEPProvisioner); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_linkedca_provisioners_proto_msgTypes[1].OneofWrappers = []interface{}{ (*ProvisionerDetails_JWK)(nil), @@ -1693,6 +1809,7 @@ func file_linkedca_provisioners_proto_init() { (*ProvisionerDetails_X5C)(nil), (*ProvisionerDetails_K8SSA)(nil), (*ProvisionerDetails_SSHPOP)(nil), + (*ProvisionerDetails_SCEP)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -1700,7 +1817,7 @@ func file_linkedca_provisioners_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_linkedca_provisioners_proto_rawDesc, NumEnums: 1, - NumMessages: 16, + NumMessages: 17, NumExtensions: 0, NumServices: 0, }, diff --git a/linkedca/provisioners.proto b/linkedca/provisioners.proto index 53f2c140..ae047391 100644 --- a/linkedca/provisioners.proto +++ b/linkedca/provisioners.proto @@ -5,28 +5,29 @@ package linkedca; option go_package = "github.com/smallstep/certificates/linkedca"; message Provisioner { - enum Type { - NOOP = 0; - JWK = 1; - OIDC = 2; - GCP = 3; - AWS = 4; - AZURE = 5; - ACME = 6; - X5C = 7; - K8SSA = 8; - SSHPOP = 9; - } - string id = 1; - string authority_id = 2; - Type type = 3; - string name = 4; - ProvisionerDetails details = 5; - Claims claims = 6; - bytes x509_template = 7; - bytes x509_template_data = 8; - bytes ssh_template = 9; - bytes ssh_template_data = 10; + enum Type { + NOOP = 0; + JWK = 1; + OIDC = 2; + GCP = 3; + AWS = 4; + AZURE = 5; + ACME = 6; + X5C = 7; + K8SSA = 8; + SSHPOP = 9; + SCEP = 10; + } + string id = 1; + string authority_id = 2; + Type type = 3; + string name = 4; + ProvisionerDetails details = 5; + Claims claims = 6; + bytes x509_template = 7; + bytes x509_template_data = 8; + bytes ssh_template = 9; + bytes ssh_template_data = 10; } message ProvisionerDetails { @@ -40,85 +41,93 @@ message ProvisionerDetails { X5CProvisioner X5C = 26; K8sSAProvisioner K8sSA = 27; SSHPOPProvisioner SSHPOP = 28; + SCEPProvisioner SCEP = 29; } } message ProvisionerList { - repeated Provisioner provisioners = 1; + repeated Provisioner provisioners = 1; } message Claims { - X509Claims x509 = 1; - SSHClaims ssh = 2; - bool disable_renewal = 3; + X509Claims x509 = 1; + SSHClaims ssh = 2; + bool disable_renewal = 3; } message X509Claims { - bool enabled = 1; - Durations durations = 2; + bool enabled = 1; + Durations durations = 2; } message SSHClaims { - bool enabled = 1; - Durations user_durations = 2; - Durations host_durations = 3; + bool enabled = 1; + Durations user_durations = 2; + Durations host_durations = 3; } message Durations { - string default = 1; - string min = 2; - string max = 3; + string default = 1; + string min = 2; + string max = 3; } message JWKProvisioner { - bytes public_key = 1; - bytes encrypted_private_key = 2; + bytes public_key = 1; + bytes encrypted_private_key = 2; } message OIDCProvisioner { - string client_id = 1; - string client_secret = 2; - string configuration_endpoint = 3; - repeated string admins = 4; - repeated string domains = 5; - repeated string groups = 6; - string listen_address = 7; - string tenant_id = 8; + string client_id = 1; + string client_secret = 2; + string configuration_endpoint = 3; + repeated string admins = 4; + repeated string domains = 5; + repeated string groups = 6; + string listen_address = 7; + string tenant_id = 8; } message GCPProvisioner { - repeated string service_accounts = 1; - repeated string project_ids = 2; - bool disable_custom_sans = 3; - bool disable_trust_on_first_use = 4; - string instance_age = 5; + repeated string service_accounts = 1; + repeated string project_ids = 2; + bool disable_custom_sans = 3; + bool disable_trust_on_first_use = 4; + string instance_age = 5; } message AWSProvisioner { - repeated string accounts = 1; - bool disable_custom_sans = 2; - bool disable_trust_on_first_use = 3; - string instance_age = 4; + repeated string accounts = 1; + bool disable_custom_sans = 2; + bool disable_trust_on_first_use = 3; + string instance_age = 4; } message AzureProvisioner { - string tenant_id = 1; - repeated string resource_groups = 2; - string audience = 3; - bool disable_custom_sans = 4; - bool disable_trust_on_first_use = 5; + string tenant_id = 1; + repeated string resource_groups = 2; + string audience = 3; + bool disable_custom_sans = 4; + bool disable_trust_on_first_use = 5; } message ACMEProvisioner { - bool force_cn = 1; + bool force_cn = 1; } message X5CProvisioner { - repeated bytes roots = 1; + repeated bytes roots = 1; } message K8sSAProvisioner { - repeated bytes public_keys = 1; + repeated bytes public_keys = 1; } message SSHPOPProvisioner {} + +message SCEPProvisioner { + bool force_cn = 1; + string challenge = 2; + repeated string capabilities = 3; + int32 minimum_public_key_length = 4; +} \ No newline at end of file From 114627de9387877c101c4e8c49b719d2ef815295 Mon Sep 17 00:00:00 2001 From: max furman Date: Tue, 25 May 2021 12:45:40 -0700 Subject: [PATCH 22/89] [action] labeler to v3 and use default config path location --- .github/needs-triage-labeler.yml | 2 -- .github/workflows/labeler.yml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 .github/needs-triage-labeler.yml diff --git a/.github/needs-triage-labeler.yml b/.github/needs-triage-labeler.yml deleted file mode 100644 index 4f507a77..00000000 --- a/.github/needs-triage-labeler.yml +++ /dev/null @@ -1,2 +0,0 @@ -needs triage: -- "**" diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 9311145c..f0011406 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -8,7 +8,7 @@ jobs: label: runs-on: ubuntu-latest steps: - - uses: actions/labeler@v2 + - uses: actions/labeler@v3 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" configuration-path: .github/needs-triage-labeler.yml From ff7b829aa27fb28cfbfe34af3de2534d77e37feb Mon Sep 17 00:00:00 2001 From: max furman Date: Tue, 25 May 2021 12:49:03 -0700 Subject: [PATCH 23/89] [action] forgot to add default labeler config file --- .github/labeler.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/labeler.yml diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 00000000..538aed15 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,4 @@ +needs triage: + - '**' # index.php | src/main.php + - '.*' # .gitignore + - '.*/**' # .github/workflows/label.yml From 48c86716a0a5534eecdf773e907b37476730f608 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 12 Feb 2021 12:03:08 +0100 Subject: [PATCH 24/89] Add rudimentary (and incomplete) support for SCEP --- authority/authority.go | 10 +- authority/provisioner/provisioner.go | 10 + authority/provisioner/scep.go | 116 +++++++++ ca/ca.go | 61 ++++- go.mod | 1 + go.sum | 2 + scep/api.go | 367 +++++++++++++++++++++++++++ scep/authority.go | 88 +++++++ scep/provisioner.go | 17 ++ scep/scep.go | 38 +++ 10 files changed, 698 insertions(+), 12 deletions(-) create mode 100644 authority/provisioner/scep.go create mode 100644 scep/api.go create mode 100644 scep/authority.go create mode 100644 scep/provisioner.go create mode 100644 scep/scep.go diff --git a/authority/authority.go b/authority/authority.go index 6be0ea8f..a0b15649 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -442,9 +442,13 @@ func (a *Authority) init() error { audiences := a.config.GetAudiences() a.provisioners = provisioner.NewCollection(audiences) config := provisioner.Config{ - Claims: claimer.Claims(), - Audiences: audiences, - DB: a.db, + // TODO: this probably shouldn't happen like this; via SignAuth instead? + IntermediateCert: a.config.IntermediateCert, + SigningKey: a.config.IntermediateKey, + CACertificates: a.rootX509Certs, + Claims: claimer.Claims(), + Audiences: audiences, + DB: a.db, SSHKeys: &provisioner.SSHKeys{ UserKeys: sshKeys.UserKeys, HostKeys: sshKeys.HostKeys, diff --git a/authority/provisioner/provisioner.go b/authority/provisioner/provisioner.go index 981d0b0a..3824bf6c 100644 --- a/authority/provisioner/provisioner.go +++ b/authority/provisioner/provisioner.go @@ -142,6 +142,8 @@ const ( TypeK8sSA Type = 8 // TypeSSHPOP is used to indicate the SSHPOP provisioners. TypeSSHPOP Type = 9 + // TypeSCEP is used to indicate the SCEP provisioners + TypeSCEP Type = 10 ) // String returns the string representation of the type. @@ -165,6 +167,8 @@ func (t Type) String() string { return "K8sSA" case TypeSSHPOP: return "SSHPOP" + case TypeSCEP: + return "SCEP" default: return "" } @@ -179,6 +183,10 @@ type SSHKeys struct { // Config defines the default parameters used in the initialization of // provisioners. type Config struct { + // TODO: these probably shouldn't be here but passed via SignAuth + IntermediateCert string + SigningKey string + CACertificates []*x509.Certificate // Claims are the default claims. Claims Claims // Audiences are the audiences used in the default provisioner, (JWK). @@ -233,6 +241,8 @@ func (l *List) UnmarshalJSON(data []byte) error { p = &K8sSA{} case "sshpop": p = &SSHPOP{} + case "scep": + p = &SCEP{} default: // Skip unsupported provisioners. A client using this method may be // compiled with a version of smallstep/certificates that does not diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go new file mode 100644 index 00000000..741add16 --- /dev/null +++ b/authority/provisioner/scep.go @@ -0,0 +1,116 @@ +package provisioner + +import ( + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + "io/ioutil" + + "github.com/pkg/errors" +) + +// SCEP is the SCEP provisioner type, an entity that can authorize the +// SCEP provisioning flow +type SCEP struct { + *base + Type string `json:"type"` + Name string `json:"name"` + // ForceCN bool `json:"forceCN,omitempty"` + // Claims *Claims `json:"claims,omitempty"` + // Options *Options `json:"options,omitempty"` + // claimer *Claimer + + IntermediateCert string + SigningKey string + CACertificates []*x509.Certificate +} + +// GetID returns the provisioner unique identifier. +func (s SCEP) GetID() string { + return "scep/" + s.Name +} + +// GetName returns the name of the provisioner. +func (s *SCEP) GetName() string { + return s.Name +} + +// GetType returns the type of provisioner. +func (s *SCEP) GetType() Type { + return TypeSCEP +} + +// GetEncryptedKey returns the base provisioner encrypted key if it's defined. +func (s *SCEP) GetEncryptedKey() (string, string, bool) { + return "", "", false +} + +// GetTokenID returns the identifier of the token. +func (s *SCEP) GetTokenID(ott string) (string, error) { + return "", errors.New("scep provisioner does not implement GetTokenID") +} + +// GetCACertificates returns the CA certificate chain +// TODO: this should come from the authority instead? +func (s *SCEP) GetCACertificates() []*x509.Certificate { + + pemtxt, _ := ioutil.ReadFile(s.IntermediateCert) // TODO: move reading key to init? That's probably safer. + block, _ := pem.Decode([]byte(pemtxt)) + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + fmt.Println(err) + } + + // TODO: return chain? I'm not sure if the client understands it correctly + return []*x509.Certificate{cert} +} + +func (s *SCEP) GetSigningKey() *rsa.PrivateKey { + + keyBytes, err := ioutil.ReadFile(s.SigningKey) + if err != nil { + return nil + } + + block, _ := pem.Decode([]byte(keyBytes)) + if block == nil { + return nil + } + + key, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + fmt.Println(err) + return nil + } + + return key +} + +// Init initializes and validates the fields of a JWK type. +func (s *SCEP) Init(config Config) (err error) { + + switch { + case s.Type == "": + return errors.New("provisioner type cannot be empty") + case s.Name == "": + return errors.New("provisioner name cannot be empty") + } + + // // Update claims with global ones + // if p.claimer, err = NewClaimer(p.Claims, config.Claims); err != nil { + // return err + // } + + s.IntermediateCert = config.IntermediateCert + s.SigningKey = config.SigningKey + s.CACertificates = config.CACertificates + + return err +} + +// Interface guards +var ( + _ Interface = (*SCEP)(nil) + //_ scep.Provisioner = (*SCEP)(nil) +) diff --git a/ca/ca.go b/ca/ca.go index d97e0ab2..405ebe47 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -22,6 +22,7 @@ import ( "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/monitoring" + "github.com/smallstep/certificates/scep" "github.com/smallstep/certificates/server" "github.com/smallstep/nosql" ) @@ -179,17 +180,39 @@ func (ca *CA) Init(config *config.Config) (*CA, error) { mgmtHandler.Route(r) }) } + if ca.shouldServeSCEPEndpoints() { + + scepPrefix := "scep" + scepAuthority, err := scep.New(auth, scep.AuthorityOptions{ + Service: auth.GetSCEPService(), + DNS: dns, + Prefix: scepPrefix, + }) + if err != nil { + return nil, errors.Wrap(err, "error creating SCEP authority") + } + scepRouterHandler := scepAPI.New(scepAuthority) + + // According to the RFC (https://tools.ietf.org/html/rfc8894#section-7.10), + // SCEP operations are performed using HTTP, so that's why the API is mounted + // to the insecure mux. + insecureMux.Route("/"+scepPrefix, func(r chi.Router) { + scepRouterHandler.Route(r) + }) + + // The RFC also mentions usage of HTTPS, but seems to advise + // against it, because of potential interoperability issues. + // Currently I think it's not bad to use HTTPS also, so that's + // why I've kept the API endpoints in both muxes and both HTTP + // as well as HTTPS can be used to request certificates + // using SCEP. + mux.Route("/"+scepPrefix, func(r chi.Router) { + scepRouterHandler.Route(r) + }) + } // helpful routine for logging all routes // - /* - walkFunc := func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error { - fmt.Printf("%s %s\n", method, route) - return nil - } - if err := chi.Walk(mux, walkFunc); err != nil { - fmt.Printf("Logging err: %s\n", err.Error()) - } - */ + //dumpRoutes(mux) // Add monitoring if configured if len(config.Monitoring) > 0 { @@ -331,3 +354,23 @@ func (ca *CA) getTLSConfig(auth *authority.Authority) (*tls.Config, error) { return tlsConfig, nil } + +// shouldMountSCEPEndpoints returns if the CA should be +// configured with endpoints for SCEP. This is assumed to be +// true if a SCEPService exists, which is true in case a +// SCEP provisioner was configured. +func (ca *CA) shouldServeSCEPEndpoints() bool { + return ca.auth.GetSCEPService() != nil +} + +//nolint // ignore linters to allow keeping this function around for debugging +func dumpRoutes(mux chi.Routes) { + // helpful routine for logging all routes // + walkFunc := func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error { + fmt.Printf("%s %s\n", method, route) + return nil + } + if err := chi.Walk(mux, walkFunc); err != nil { + fmt.Printf("Logging err: %s\n", err.Error()) + } +} diff --git a/go.mod b/go.mod index 65b395bb..fe0c7f87 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/google/uuid v1.1.2 github.com/googleapis/gax-go/v2 v2.0.5 github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect + github.com/micromdm/scep v1.0.0 github.com/newrelic/go-agent v2.15.0+incompatible github.com/pkg/errors v0.9.1 github.com/rs/xid v1.2.1 diff --git a/go.sum b/go.sum index 9529a1a3..77e0b0cb 100644 --- a/go.sum +++ b/go.sum @@ -234,6 +234,8 @@ github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGe github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f h1:eVB9ELsoq5ouItQBr5Tj334bhPJG/MX+m7rTchmzVUQ= github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/micromdm/scep v1.0.0 h1:ai//kcZnxZPq1YE/MatiE2bIRD94KOAwZRpN1fhVQXY= +github.com/micromdm/scep v1.0.0/go.mod h1:CID2SixSr5FvoauZdAFUSpQkn5MAuSy9oyURMGOJbag= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= diff --git a/scep/api.go b/scep/api.go new file mode 100644 index 00000000..1f33dfd5 --- /dev/null +++ b/scep/api.go @@ -0,0 +1,367 @@ +package scep + +import ( + "context" + "crypto" + "crypto/sha1" + "crypto/x509" + "encoding/base64" + "errors" + "fmt" + "io" + "io/ioutil" + "math/big" + "math/rand" + "net/http" + "strings" + "time" + + "github.com/smallstep/certificates/acme" + "github.com/smallstep/certificates/api" + "github.com/smallstep/certificates/authority/provisioner" + + microscep "github.com/micromdm/scep/scep" +) + +// Handler is the ACME request handler. +type Handler struct { + Auth Interface +} + +// New returns a new ACME API router. +func NewAPI(scepAuth Interface) api.RouterHandler { + return &Handler{scepAuth} +} + +// Route traffic and implement the Router interface. +func (h *Handler) Route(r api.Router) { + //getLink := h.Auth.GetLinkExplicit + //fmt.Println(getLink) + + //r.MethodFunc("GET", "/bla", h.baseURLFromRequest(h.lookupProvisioner(nil))) + //r.MethodFunc("GET", getLink(acme.NewNonceLink, "{provisionerID}", false, nil), h.baseURLFromRequest(h.lookupProvisioner(h.addNonce(h.GetNonce)))) + + r.MethodFunc(http.MethodGet, "/", h.lookupProvisioner(h.Get)) + r.MethodFunc(http.MethodPost, "/", h.lookupProvisioner(h.Post)) + +} + +func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { + + scepRequest, err := decodeSCEPRequest(r) + if err != nil { + fmt.Println(err) + fmt.Println("not a scep get request") + w.WriteHeader(500) + } + + scepResponse := SCEPResponse{Operation: scepRequest.Operation} + + switch scepRequest.Operation { + case opnGetCACert: + err := h.GetCACert(w, r, scepResponse) + if err != nil { + fmt.Println(err) + } + + case opnGetCACaps: + err := h.GetCACaps(w, r, scepResponse) + if err != nil { + fmt.Println(err) + } + case opnPKIOperation: + + default: + + } +} + +func (h *Handler) Post(w http.ResponseWriter, r *http.Request) { + scepRequest, err := decodeSCEPRequest(r) + if err != nil { + fmt.Println(err) + fmt.Println("not a scep post request") + w.WriteHeader(500) + } + + scepResponse := SCEPResponse{Operation: scepRequest.Operation} + + switch scepRequest.Operation { + case opnPKIOperation: + err := h.PKIOperation(w, r, scepRequest, scepResponse) + if err != nil { + fmt.Println(err) + } + default: + + } + +} + +const maxPayloadSize = 2 << 20 + +func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) { + + defer r.Body.Close() + + method := r.Method + query := r.URL.Query() + + var operation string + if _, ok := query["operation"]; ok { + operation = query.Get("operation") + } + + switch method { + case http.MethodGet: + switch operation { + case opnGetCACert, opnGetCACaps: + return SCEPRequest{ + Operation: operation, + Message: []byte{}, + }, nil + case opnPKIOperation: + var message string + if _, ok := query["message"]; ok { + message = query.Get("message") + } + decodedMessage, err := base64.URLEncoding.DecodeString(message) + if err != nil { + return SCEPRequest{}, err + } + return SCEPRequest{ + Operation: operation, + Message: decodedMessage, + }, nil + default: + return SCEPRequest{}, fmt.Errorf("unsupported operation: %s", operation) + } + case http.MethodPost: + body, err := ioutil.ReadAll(io.LimitReader(r.Body, maxPayloadSize)) + if err != nil { + return SCEPRequest{}, err + } + return SCEPRequest{ + Operation: operation, + Message: body, + }, nil + default: + return SCEPRequest{}, fmt.Errorf("unsupported method: %s", method) + } +} + +type nextHTTP = func(http.ResponseWriter, *http.Request) + +// lookupProvisioner loads the provisioner associated with the request. +// Responds 404 if the provisioner does not exist. +func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + // TODO: make this configurable; and we might want to look at being able to provide multiple, + // like the actual ACME one? The below assumes a SCEP provider (scep/) called "scep1" exists. + p, err := h.Auth.LoadProvisionerByID("scep/scep1") + if err != nil { + api.WriteError(w, err) + return + } + + scepProvisioner, ok := p.(*provisioner.SCEP) + if !ok { + api.WriteError(w, acme.AccountDoesNotExistErr(errors.New("provisioner must be of type SCEP"))) + return + } + + ctx = context.WithValue(ctx, acme.ProvisionerContextKey, Provisioner(scepProvisioner)) + next(w, r.WithContext(ctx)) + } +} + +func (h *Handler) GetCACert(w http.ResponseWriter, r *http.Request, scepResponse SCEPResponse) error { + + ctx := r.Context() + + p, err := ProvisionerFromContext(ctx) + if err != nil { + return err + } + + // TODO: get the CA Certificates from the (signing) authority instead? I think that should be doable + certs := p.GetCACertificates() + + if len(certs) == 0 { + scepResponse.CACertNum = 0 + scepResponse.Err = errors.New("missing CA Cert") + } else if len(certs) == 1 { + scepResponse.Data = certs[0].Raw + scepResponse.CACertNum = 1 + } else { + data, err := microscep.DegenerateCertificates(certs) + scepResponse.Data = data + scepResponse.Err = err + } + + return writeSCEPResponse(w, scepResponse) +} + +func (h *Handler) GetCACaps(w http.ResponseWriter, r *http.Request, scepResponse SCEPResponse) error { + + ctx := r.Context() + + _, err := ProvisionerFromContext(ctx) + if err != nil { + return err + } + + // TODO: get the actual capabilities from provisioner config + scepResponse.Data = formatCapabilities(defaultCapabilities) + + return writeSCEPResponse(w, scepResponse) +} + +func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepRequest SCEPRequest, scepResponse SCEPResponse) error { + + msg, err := microscep.ParsePKIMessage(scepRequest.Message) + if err != nil { + return err + } + + ctx := r.Context() + p, err := ProvisionerFromContext(ctx) + if err != nil { + return err + } + certs := p.GetCACertificates() + key := p.GetSigningKey() + + ca := certs[0] + if err := msg.DecryptPKIEnvelope(ca, key); err != nil { + return err + } + + if msg.MessageType == microscep.PKCSReq { + // TODO: CSR validation, like challenge password + } + + csr := msg.CSRReqMessage.CSR + id, err := createKeyIdentifier(csr.PublicKey) + if err != nil { + return err + } + + serial := big.NewInt(int64(rand.Int63())) // TODO: serial logic? + + days := 40 + + template := &x509.Certificate{ + SerialNumber: serial, + Subject: csr.Subject, + NotBefore: time.Now().Add(-600).UTC(), + NotAfter: time.Now().AddDate(0, 0, days).UTC(), + SubjectKeyId: id, + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageClientAuth, + }, + SignatureAlgorithm: csr.SignatureAlgorithm, + EmailAddresses: csr.EmailAddresses, + } + + certRep, err := msg.SignCSR(ca, key, template) + if err != nil { + return err + } + + //cert := certRep.CertRepMessage.Certificate + //name := certName(cert) + + // TODO: check if CN already exists, if renewal is allowed and if existing should be revoked; fail if not + // TODO: store the new cert for CN locally + + scepResponse.Data = certRep.Raw + + return writeSCEPResponse(w, scepResponse) +} + +func certName(cert *x509.Certificate) string { + if cert.Subject.CommonName != "" { + return cert.Subject.CommonName + } + return string(cert.Signature) +} + +// createKeyIdentifier create an identifier for public keys +// according to the first method in RFC5280 section 4.2.1.2. +func createKeyIdentifier(pub crypto.PublicKey) ([]byte, error) { + + keyBytes, err := x509.MarshalPKIXPublicKey(pub) + if err != nil { + return nil, err + } + + id := sha1.Sum(keyBytes) + + return id[:], nil +} + +func formatCapabilities(caps []string) []byte { + return []byte(strings.Join(caps, "\n")) +} + +// writeSCEPResponse writes a SCEP response back to the SCEP client. +func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) error { + if response.Err != nil { + http.Error(w, response.Err.Error(), http.StatusInternalServerError) + return nil + } + w.Header().Set("Content-Type", contentHeader(response.Operation, response.CACertNum)) + w.Write(response.Data) + return nil +} + +var ( + // TODO: check the default capabilities + defaultCapabilities = []string{ + "Renewal", + "SHA-1", + "SHA-256", + "AES", + "DES3", + "SCEPStandard", + "POSTPKIOperation", + } +) + +const ( + certChainHeader = "application/x-x509-ca-ra-cert" + leafHeader = "application/x-x509-ca-cert" + pkiOpHeader = "application/x-pki-message" +) + +func contentHeader(operation string, certNum int) string { + switch operation { + case opnGetCACert: + if certNum > 1 { + return certChainHeader + } + return leafHeader + case opnPKIOperation: + return pkiOpHeader + default: + return "text/plain" + } +} + +// ProvisionerFromContext searches the context for a provisioner. Returns the +// provisioner or an error. +func ProvisionerFromContext(ctx context.Context) (Provisioner, error) { + val := ctx.Value(acme.ProvisionerContextKey) + if val == nil { + return nil, acme.ServerInternalErr(errors.New("provisioner expected in request context")) + } + pval, ok := val.(Provisioner) + if !ok || pval == nil { + return nil, acme.ServerInternalErr(errors.New("provisioner in context is not a SCEP provisioner")) + } + return pval, nil +} diff --git a/scep/authority.go b/scep/authority.go new file mode 100644 index 00000000..9a10357c --- /dev/null +++ b/scep/authority.go @@ -0,0 +1,88 @@ +package scep + +import ( + "crypto/x509" + + "github.com/smallstep/certificates/authority/provisioner" + + "github.com/smallstep/nosql" +) + +// Interface is the SCEP authority interface. +type Interface interface { + // GetDirectory(ctx context.Context) (*Directory, error) + // NewNonce() (string, error) + // UseNonce(string) error + + // DeactivateAccount(ctx context.Context, accID string) (*Account, error) + // GetAccount(ctx context.Context, accID string) (*Account, error) + // GetAccountByKey(ctx context.Context, key *jose.JSONWebKey) (*Account, error) + // NewAccount(ctx context.Context, ao AccountOptions) (*Account, error) + // UpdateAccount(context.Context, string, []string) (*Account, error) + + // GetAuthz(ctx context.Context, accID string, authzID string) (*Authz, error) + // ValidateChallenge(ctx context.Context, accID string, chID string, key *jose.JSONWebKey) (*Challenge, error) + + // FinalizeOrder(ctx context.Context, accID string, orderID string, csr *x509.CertificateRequest) (*Order, error) + // GetOrder(ctx context.Context, accID string, orderID string) (*Order, error) + // GetOrdersByAccount(ctx context.Context, accID string) ([]string, error) + // NewOrder(ctx context.Context, oo OrderOptions) (*Order, error) + + // GetCertificate(string, string) ([]byte, error) + + LoadProvisionerByID(string) (provisioner.Interface, error) + // GetLink(ctx context.Context, linkType Link, absoluteLink bool, inputs ...string) string + // GetLinkExplicit(linkType Link, provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string + + GetCACerts() ([]*x509.Certificate, error) +} + +// Authority is the layer that handles all SCEP interactions. +type Authority struct { + //certificates []*x509.Certificate + //authConfig authority.AuthConfig + backdate provisioner.Duration + db nosql.DB + // dir *directory + signAuth SignAuthority +} + +// AuthorityOptions required to create a new SCEP Authority. +type AuthorityOptions struct { + Certificates []*x509.Certificate + //AuthConfig authority.AuthConfig + Backdate provisioner.Duration + // DB is the database used by nosql. + DB nosql.DB + // DNS the host used to generate accurate SCEP links. By default the authority + // will use the Host from the request, so this value will only be used if + // request.Host is empty. + DNS string + // Prefix is a URL path prefix under which the SCEP api is served. This + // prefix is required to generate accurate SCEP links. + Prefix string +} + +// SignAuthority is the interface implemented by a CA authority. +type SignAuthority interface { + Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) + LoadProvisionerByID(string) (provisioner.Interface, error) +} + +// LoadProvisionerByID calls out to the SignAuthority interface to load a +// provisioner by ID. +func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error) { + return a.signAuth.LoadProvisionerByID(id) +} + +func (a *Authority) GetCACerts() ([]*x509.Certificate, error) { + + // TODO: implement the SCEP authority + + return []*x509.Certificate{}, nil +} + +// Interface guards +var ( + _ Interface = (*Authority)(nil) +) diff --git a/scep/provisioner.go b/scep/provisioner.go new file mode 100644 index 00000000..4de40621 --- /dev/null +++ b/scep/provisioner.go @@ -0,0 +1,17 @@ +package scep + +import ( + "crypto/rsa" + "crypto/x509" +) + +// Provisioner is an interface that implements a subset of the provisioner.Interface -- +// only those methods required by the SCEP api/authority. +type Provisioner interface { + // AuthorizeSign(ctx context.Context, token string) ([]provisioner.SignOption, error) + // GetName() string + // DefaultTLSCertDuration() time.Duration + // GetOptions() *provisioner.Options + GetCACertificates() []*x509.Certificate + GetSigningKey() *rsa.PrivateKey +} diff --git a/scep/scep.go b/scep/scep.go new file mode 100644 index 00000000..c88027ff --- /dev/null +++ b/scep/scep.go @@ -0,0 +1,38 @@ +package scep + +import ( + database "github.com/smallstep/certificates/db" +) + +const ( + opnGetCACert = "GetCACert" + opnGetCACaps = "GetCACaps" + opnPKIOperation = "PKIOperation" +) + +// New returns a new Authority that implements the SCEP interface. +func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { + if _, ok := ops.DB.(*database.SimpleDB); !ok { + // TODO: see ACME implementation + } + return &Authority{ + //certificates: ops.Certificates, + backdate: ops.Backdate, + db: ops.DB, + signAuth: signAuth, + }, nil +} + +// SCEPRequest is a SCEP server request. +type SCEPRequest struct { + Operation string + Message []byte +} + +// SCEPResponse is a SCEP server response. +type SCEPResponse struct { + Operation string + CACertNum int + Data []byte + Err error +} From 339039768c9a52265f156e1f9f93cc1355dcc541 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 12 Feb 2021 17:02:39 +0100 Subject: [PATCH 25/89] Refactor SCEP authority initialization and clean some code --- authority/authority.go | 15 +++-- authority/provisioner/collection.go | 2 + authority/provisioner/provisioner.go | 4 -- authority/provisioner/scep.go | 68 +++++-------------- ca/ca.go | 3 +- scep/{ => api}/api.go | 69 +++++++++++++------- scep/authority.go | 98 +++++++++++++++++++++++++--- scep/provisioner.go | 13 ++-- scep/scep.go | 38 ----------- 9 files changed, 169 insertions(+), 141 deletions(-) rename scep/{ => api}/api.go (85%) delete mode 100644 scep/scep.go diff --git a/authority/authority.go b/authority/authority.go index a0b15649..5652c71a 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -442,13 +442,14 @@ func (a *Authority) init() error { audiences := a.config.GetAudiences() a.provisioners = provisioner.NewCollection(audiences) config := provisioner.Config{ - // TODO: this probably shouldn't happen like this; via SignAuth instead? - IntermediateCert: a.config.IntermediateCert, - SigningKey: a.config.IntermediateKey, - CACertificates: a.rootX509Certs, - Claims: claimer.Claims(), - Audiences: audiences, - DB: a.db, + // TODO: I'm not sure if extending this configuration is a good way to integrate + // It's powerful, but leaks quite some seemingly internal stuff to the provisioner. + // IntermediateCert: a.config.IntermediateCert, + // SigningKey: a.config.IntermediateKey, + // CACertificates: a.rootX509Certs, + Claims: claimer.Claims(), + Audiences: audiences, + DB: a.db, SSHKeys: &provisioner.SSHKeys{ UserKeys: sshKeys.UserKeys, HostKeys: sshKeys.HostKeys, diff --git a/authority/provisioner/collection.go b/authority/provisioner/collection.go index ccfbc60a..eecacf69 100644 --- a/authority/provisioner/collection.go +++ b/authority/provisioner/collection.go @@ -157,6 +157,8 @@ func (c *Collection) LoadByCertificate(cert *x509.Certificate) (Interface, bool) return c.Load("gcp/" + string(provisioner.Name)) case TypeACME: return c.Load("acme/" + string(provisioner.Name)) + case TypeSCEP: + return c.Load("scep/" + string(provisioner.Name)) case TypeX5C: return c.Load("x5c/" + string(provisioner.Name)) case TypeK8sSA: diff --git a/authority/provisioner/provisioner.go b/authority/provisioner/provisioner.go index 3824bf6c..83cc6946 100644 --- a/authority/provisioner/provisioner.go +++ b/authority/provisioner/provisioner.go @@ -183,10 +183,6 @@ type SSHKeys struct { // Config defines the default parameters used in the initialization of // provisioners. type Config struct { - // TODO: these probably shouldn't be here but passed via SignAuth - IntermediateCert string - SigningKey string - CACertificates []*x509.Certificate // Claims are the default claims. Claims Claims // Audiences are the audiences used in the default provisioner, (JWK). diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 741add16..30b4a1b2 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -1,11 +1,7 @@ package provisioner import ( - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "fmt" - "io/ioutil" + "time" "github.com/pkg/errors" ) @@ -16,14 +12,11 @@ type SCEP struct { *base Type string `json:"type"` Name string `json:"name"` - // ForceCN bool `json:"forceCN,omitempty"` - // Claims *Claims `json:"claims,omitempty"` - // Options *Options `json:"options,omitempty"` - // claimer *Claimer - IntermediateCert string - SigningKey string - CACertificates []*x509.Certificate + // ForceCN bool `json:"forceCN,omitempty"` + Options *Options `json:"options,omitempty"` + Claims *Claims `json:"claims,omitempty"` + claimer *Claimer } // GetID returns the provisioner unique identifier. @@ -51,40 +44,15 @@ func (s *SCEP) GetTokenID(ott string) (string, error) { return "", errors.New("scep provisioner does not implement GetTokenID") } -// GetCACertificates returns the CA certificate chain -// TODO: this should come from the authority instead? -func (s *SCEP) GetCACertificates() []*x509.Certificate { - - pemtxt, _ := ioutil.ReadFile(s.IntermediateCert) // TODO: move reading key to init? That's probably safer. - block, _ := pem.Decode([]byte(pemtxt)) - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - fmt.Println(err) - } - - // TODO: return chain? I'm not sure if the client understands it correctly - return []*x509.Certificate{cert} +// GetOptions returns the configured provisioner options. +func (s *SCEP) GetOptions() *Options { + return s.Options } -func (s *SCEP) GetSigningKey() *rsa.PrivateKey { - - keyBytes, err := ioutil.ReadFile(s.SigningKey) - if err != nil { - return nil - } - - block, _ := pem.Decode([]byte(keyBytes)) - if block == nil { - return nil - } - - key, err := x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - fmt.Println(err) - return nil - } - - return key +// DefaultTLSCertDuration returns the default TLS cert duration enforced by +// the provisioner. +func (s *SCEP) DefaultTLSCertDuration() time.Duration { + return s.claimer.DefaultTLSCertDuration() } // Init initializes and validates the fields of a JWK type. @@ -97,14 +65,10 @@ func (s *SCEP) Init(config Config) (err error) { return errors.New("provisioner name cannot be empty") } - // // Update claims with global ones - // if p.claimer, err = NewClaimer(p.Claims, config.Claims); err != nil { - // return err - // } - - s.IntermediateCert = config.IntermediateCert - s.SigningKey = config.SigningKey - s.CACertificates = config.CACertificates + // Update claims with global ones + if s.claimer, err = NewClaimer(s.Claims, config.Claims); err != nil { + return err + } return err } diff --git a/ca/ca.go b/ca/ca.go index 405ebe47..fa71abd6 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -23,6 +23,7 @@ import ( "github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/monitoring" "github.com/smallstep/certificates/scep" + scepAPI "github.com/smallstep/certificates/scep/api" "github.com/smallstep/certificates/server" "github.com/smallstep/nosql" ) @@ -180,8 +181,8 @@ func (ca *CA) Init(config *config.Config) (*CA, error) { mgmtHandler.Route(r) }) } - if ca.shouldServeSCEPEndpoints() { + if ca.shouldServeSCEPEndpoints() { scepPrefix := "scep" scepAuthority, err := scep.New(auth, scep.AuthorityOptions{ Service: auth.GetSCEPService(), diff --git a/scep/api.go b/scep/api/api.go similarity index 85% rename from scep/api.go rename to scep/api/api.go index 1f33dfd5..0e78d544 100644 --- a/scep/api.go +++ b/scep/api/api.go @@ -1,4 +1,4 @@ -package scep +package api import ( "context" @@ -19,17 +19,38 @@ import ( "github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/scep" microscep "github.com/micromdm/scep/scep" ) -// Handler is the ACME request handler. -type Handler struct { - Auth Interface +const ( + opnGetCACert = "GetCACert" + opnGetCACaps = "GetCACaps" + opnPKIOperation = "PKIOperation" +) + +// SCEPRequest is a SCEP server request. +type SCEPRequest struct { + Operation string + Message []byte } -// New returns a new ACME API router. -func NewAPI(scepAuth Interface) api.RouterHandler { +// SCEPResponse is a SCEP server response. +type SCEPResponse struct { + Operation string + CACertNum int + Data []byte + Err error +} + +// Handler is the SCEP request handler. +type Handler struct { + Auth scep.Interface +} + +// New returns a new SCEP API router. +func New(scepAuth scep.Interface) api.RouterHandler { return &Handler{scepAuth} } @@ -156,7 +177,6 @@ type nextHTTP = func(http.ResponseWriter, *http.Request) // Responds 404 if the provisioner does not exist. func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP { return func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() // TODO: make this configurable; and we might want to look at being able to provide multiple, // like the actual ACME one? The below assumes a SCEP provider (scep/) called "scep1" exists. @@ -168,27 +188,23 @@ func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP { scepProvisioner, ok := p.(*provisioner.SCEP) if !ok { - api.WriteError(w, acme.AccountDoesNotExistErr(errors.New("provisioner must be of type SCEP"))) + api.WriteError(w, errors.New("provisioner must be of type SCEP")) return } - ctx = context.WithValue(ctx, acme.ProvisionerContextKey, Provisioner(scepProvisioner)) + ctx := r.Context() + ctx = context.WithValue(ctx, acme.ProvisionerContextKey, scep.Provisioner(scepProvisioner)) next(w, r.WithContext(ctx)) } } func (h *Handler) GetCACert(w http.ResponseWriter, r *http.Request, scepResponse SCEPResponse) error { - ctx := r.Context() - - p, err := ProvisionerFromContext(ctx) + certs, err := h.Auth.GetCACertificates() if err != nil { return err } - // TODO: get the CA Certificates from the (signing) authority instead? I think that should be doable - certs := p.GetCACertificates() - if len(certs) == 0 { scepResponse.CACertNum = 0 scepResponse.Err = errors.New("missing CA Cert") @@ -226,13 +242,16 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque return err } - ctx := r.Context() - p, err := ProvisionerFromContext(ctx) + certs, err := h.Auth.GetCACertificates() + if err != nil { + return err + } + + // TODO: instead of getting the key to decrypt, add a decrypt function to the auth; less leaky + key, err := h.Auth.GetSigningKey() if err != nil { return err } - certs := p.GetCACertificates() - key := p.GetSigningKey() ca := certs[0] if err := msg.DecryptPKIEnvelope(ca, key); err != nil { @@ -276,10 +295,12 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque //name := certName(cert) // TODO: check if CN already exists, if renewal is allowed and if existing should be revoked; fail if not - // TODO: store the new cert for CN locally + // TODO: store the new cert for CN locally; should go into the DB scepResponse.Data = certRep.Raw + api.LogCertificate(w, certRep.Certificate) + return writeSCEPResponse(w, scepResponse) } @@ -354,14 +375,14 @@ func contentHeader(operation string, certNum int) string { // ProvisionerFromContext searches the context for a provisioner. Returns the // provisioner or an error. -func ProvisionerFromContext(ctx context.Context) (Provisioner, error) { +func ProvisionerFromContext(ctx context.Context) (scep.Provisioner, error) { val := ctx.Value(acme.ProvisionerContextKey) if val == nil { - return nil, acme.ServerInternalErr(errors.New("provisioner expected in request context")) + return nil, errors.New("provisioner expected in request context") } - pval, ok := val.(Provisioner) + pval, ok := val.(scep.Provisioner) if !ok || pval == nil { - return nil, acme.ServerInternalErr(errors.New("provisioner in context is not a SCEP provisioner")) + return nil, errors.New("provisioner in context is not a SCEP provisioner") } return pval, nil } diff --git a/scep/authority.go b/scep/authority.go index 9a10357c..27817b0f 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -1,9 +1,15 @@ package scep import ( + "crypto/rsa" "crypto/x509" + "encoding/pem" + "errors" + "io/ioutil" "github.com/smallstep/certificates/authority/provisioner" + database "github.com/smallstep/certificates/db" + "go.step.sm/crypto/pemutil" "github.com/smallstep/nosql" ) @@ -34,23 +40,33 @@ type Interface interface { // GetLink(ctx context.Context, linkType Link, absoluteLink bool, inputs ...string) string // GetLinkExplicit(linkType Link, provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string - GetCACerts() ([]*x509.Certificate, error) + GetCACertificates() ([]*x509.Certificate, error) + GetSigningKey() (*rsa.PrivateKey, error) } // Authority is the layer that handles all SCEP interactions. type Authority struct { - //certificates []*x509.Certificate - //authConfig authority.AuthConfig backdate provisioner.Duration db nosql.DB + prefix string + dns string + // dir *directory + + intermediateCertificate *x509.Certificate + intermediateKey *rsa.PrivateKey + + //signer crypto.Signer + signAuth SignAuthority } // AuthorityOptions required to create a new SCEP Authority. type AuthorityOptions struct { - Certificates []*x509.Certificate - //AuthConfig authority.AuthConfig + IntermediateCertificatePath string + IntermediateKeyPath string + + // Backdate Backdate provisioner.Duration // DB is the database used by nosql. DB nosql.DB @@ -69,17 +85,83 @@ type SignAuthority interface { LoadProvisionerByID(string) (provisioner.Interface, error) } +// New returns a new Authority that implements the SCEP interface. +func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { + if _, ok := ops.DB.(*database.SimpleDB); !ok { + // TODO: see ACME implementation + } + + // TODO: the below is a bit similar as what happens in the core Authority class, which + // creates the full x509 service. However, those aren't accessible directly, which is + // why I reimplemented this (for now). There might be an alternative that I haven't + // found yet. + certificateChain, err := pemutil.ReadCertificateBundle(ops.IntermediateCertificatePath) + if err != nil { + return nil, err + } + + intermediateKey, err := readPrivateKey(ops.IntermediateKeyPath) + if err != nil { + return nil, err + } + + return &Authority{ + backdate: ops.Backdate, + db: ops.DB, + prefix: ops.Prefix, + dns: ops.DNS, + intermediateCertificate: certificateChain[0], + intermediateKey: intermediateKey, + signAuth: signAuth, + }, nil +} + +func readPrivateKey(path string) (*rsa.PrivateKey, error) { + + keyBytes, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + block, _ := pem.Decode([]byte(keyBytes)) + if block == nil { + return nil, nil + } + + key, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + + return key, nil +} + // LoadProvisionerByID calls out to the SignAuthority interface to load a // provisioner by ID. func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error) { return a.signAuth.LoadProvisionerByID(id) } -func (a *Authority) GetCACerts() ([]*x509.Certificate, error) { +// GetCACertificates returns the certificate (chain) for the CA +func (a *Authority) GetCACertificates() ([]*x509.Certificate, error) { - // TODO: implement the SCEP authority + if a.intermediateCertificate == nil { + return nil, errors.New("no intermediate certificate available in SCEP authority") + } - return []*x509.Certificate{}, nil + return []*x509.Certificate{a.intermediateCertificate}, nil +} + +// GetSigningKey returns the RSA private key for the CA +// TODO: we likely should provide utility functions for decrypting and +// signing instead of providing the signing key directly +func (a *Authority) GetSigningKey() (*rsa.PrivateKey, error) { + + if a.intermediateKey == nil { + return nil, errors.New("no intermediate key available in SCEP authority") + } + + return a.intermediateKey, nil } // Interface guards diff --git a/scep/provisioner.go b/scep/provisioner.go index 4de40621..d543d453 100644 --- a/scep/provisioner.go +++ b/scep/provisioner.go @@ -1,17 +1,16 @@ package scep import ( - "crypto/rsa" - "crypto/x509" + "time" + + "github.com/smallstep/certificates/authority/provisioner" ) // Provisioner is an interface that implements a subset of the provisioner.Interface -- // only those methods required by the SCEP api/authority. type Provisioner interface { // AuthorizeSign(ctx context.Context, token string) ([]provisioner.SignOption, error) - // GetName() string - // DefaultTLSCertDuration() time.Duration - // GetOptions() *provisioner.Options - GetCACertificates() []*x509.Certificate - GetSigningKey() *rsa.PrivateKey + GetName() string + DefaultTLSCertDuration() time.Duration + GetOptions() *provisioner.Options } diff --git a/scep/scep.go b/scep/scep.go deleted file mode 100644 index c88027ff..00000000 --- a/scep/scep.go +++ /dev/null @@ -1,38 +0,0 @@ -package scep - -import ( - database "github.com/smallstep/certificates/db" -) - -const ( - opnGetCACert = "GetCACert" - opnGetCACaps = "GetCACaps" - opnPKIOperation = "PKIOperation" -) - -// New returns a new Authority that implements the SCEP interface. -func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { - if _, ok := ops.DB.(*database.SimpleDB); !ok { - // TODO: see ACME implementation - } - return &Authority{ - //certificates: ops.Certificates, - backdate: ops.Backdate, - db: ops.DB, - signAuth: signAuth, - }, nil -} - -// SCEPRequest is a SCEP server request. -type SCEPRequest struct { - Operation string - Message []byte -} - -// SCEPResponse is a SCEP server response. -type SCEPResponse struct { - Operation string - CACertNum int - Data []byte - Err error -} From b905d5fead737831f16899eaecc5ffdfd93884f7 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 19 Feb 2021 11:01:19 +0100 Subject: [PATCH 26/89] Improve setup for multiple SCEP providers (slightly) --- scep/api/api.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scep/api/api.go b/scep/api/api.go index 0e78d544..7ac80cdb 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -178,9 +178,18 @@ type nextHTTP = func(http.ResponseWriter, *http.Request) func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP { return func(w http.ResponseWriter, r *http.Request) { + // name := chi.URLParam(r, "provisionerID") + // provisionerID, err := url.PathUnescape(name) + // if err != nil { + // api.WriteError(w, fmt.Errorf("error url unescaping provisioner id '%s'", name)) + // return + // } + // TODO: make this configurable; and we might want to look at being able to provide multiple, - // like the actual ACME one? The below assumes a SCEP provider (scep/) called "scep1" exists. - p, err := h.Auth.LoadProvisionerByID("scep/scep1") + // like the ACME one? The below assumes a SCEP provider (scep/) called "scep1" exists. + provisionerID := "scep1" + + p, err := h.Auth.LoadProvisionerByID("scep/" + provisionerID) if err != nil { api.WriteError(w, err) return From 393be5b03a21be4eb518dfb907e18192d0177d3d Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 19 Feb 2021 12:06:24 +0100 Subject: [PATCH 27/89] Add number of certs to return and fix CR LF in CACaps --- scep/api/api.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scep/api/api.go b/scep/api/api.go index 7ac80cdb..5a4cb5b2 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -222,6 +222,7 @@ func (h *Handler) GetCACert(w http.ResponseWriter, r *http.Request, scepResponse scepResponse.CACertNum = 1 } else { data, err := microscep.DegenerateCertificates(certs) + scepResponse.CACertNum = len(certs) scepResponse.Data = data scepResponse.Err = err } @@ -335,7 +336,7 @@ func createKeyIdentifier(pub crypto.PublicKey) ([]byte, error) { } func formatCapabilities(caps []string) []byte { - return []byte(strings.Join(caps, "\n")) + return []byte(strings.Join(caps, "\r\n")) } // writeSCEPResponse writes a SCEP response back to the SCEP client. @@ -350,7 +351,7 @@ func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) error { } var ( - // TODO: check the default capabilities + // TODO: check the default capabilities; https://tools.ietf.org/html/rfc8894#section-3.5.2 defaultCapabilities = []string{ "Renewal", "SHA-1", From 99cd3b74fe4d8f3e6eac668a06b81b886a4ec1a0 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Thu, 25 Feb 2021 22:28:08 +0100 Subject: [PATCH 28/89] Add full copy of mozilla/pkcs7 module as internal dependency The full contents of the git repository @432b2356ecb... was copied. Only go.mod was removed from it. --- scep/pkcs7/.gitignore | 24 ++ scep/pkcs7/.travis.yml | 10 + scep/pkcs7/LICENSE | 22 ++ scep/pkcs7/Makefile | 20 ++ scep/pkcs7/README.md | 69 ++++ scep/pkcs7/ber.go | 251 +++++++++++++ scep/pkcs7/ber_test.go | 62 ++++ scep/pkcs7/decrypt.go | 177 +++++++++ scep/pkcs7/decrypt_test.go | 60 ++++ scep/pkcs7/encrypt.go | 399 +++++++++++++++++++++ scep/pkcs7/encrypt_test.go | 102 ++++++ scep/pkcs7/pkcs7.go | 291 +++++++++++++++ scep/pkcs7/pkcs7_test.go | 326 +++++++++++++++++ scep/pkcs7/sign.go | 429 ++++++++++++++++++++++ scep/pkcs7/sign_test.go | 266 ++++++++++++++ scep/pkcs7/verify.go | 264 ++++++++++++++ scep/pkcs7/verify_test.go | 713 +++++++++++++++++++++++++++++++++++++ 17 files changed, 3485 insertions(+) create mode 100644 scep/pkcs7/.gitignore create mode 100644 scep/pkcs7/.travis.yml create mode 100644 scep/pkcs7/LICENSE create mode 100644 scep/pkcs7/Makefile create mode 100644 scep/pkcs7/README.md create mode 100644 scep/pkcs7/ber.go create mode 100644 scep/pkcs7/ber_test.go create mode 100644 scep/pkcs7/decrypt.go create mode 100644 scep/pkcs7/decrypt_test.go create mode 100644 scep/pkcs7/encrypt.go create mode 100644 scep/pkcs7/encrypt_test.go create mode 100644 scep/pkcs7/pkcs7.go create mode 100644 scep/pkcs7/pkcs7_test.go create mode 100644 scep/pkcs7/sign.go create mode 100644 scep/pkcs7/sign_test.go create mode 100644 scep/pkcs7/verify.go create mode 100644 scep/pkcs7/verify_test.go diff --git a/scep/pkcs7/.gitignore b/scep/pkcs7/.gitignore new file mode 100644 index 00000000..daf913b1 --- /dev/null +++ b/scep/pkcs7/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/scep/pkcs7/.travis.yml b/scep/pkcs7/.travis.yml new file mode 100644 index 00000000..eac4c176 --- /dev/null +++ b/scep/pkcs7/.travis.yml @@ -0,0 +1,10 @@ +language: go +go: + - "1.11" + - "1.12" + - "1.13" + - tip +before_install: + - make gettools +script: + - make diff --git a/scep/pkcs7/LICENSE b/scep/pkcs7/LICENSE new file mode 100644 index 00000000..75f32090 --- /dev/null +++ b/scep/pkcs7/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Andrew Smith + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/scep/pkcs7/Makefile b/scep/pkcs7/Makefile new file mode 100644 index 00000000..47c73b86 --- /dev/null +++ b/scep/pkcs7/Makefile @@ -0,0 +1,20 @@ +all: vet staticcheck test + +test: + go test -covermode=count -coverprofile=coverage.out . + +showcoverage: test + go tool cover -html=coverage.out + +vet: + go vet . + +lint: + golint . + +staticcheck: + staticcheck . + +gettools: + go get -u honnef.co/go/tools/... + go get -u golang.org/x/lint/golint diff --git a/scep/pkcs7/README.md b/scep/pkcs7/README.md new file mode 100644 index 00000000..bf37059c --- /dev/null +++ b/scep/pkcs7/README.md @@ -0,0 +1,69 @@ +# pkcs7 + +[![GoDoc](https://godoc.org/go.mozilla.org/pkcs7?status.svg)](https://godoc.org/go.mozilla.org/pkcs7) +[![Build Status](https://travis-ci.org/mozilla-services/pkcs7.svg?branch=master)](https://travis-ci.org/mozilla-services/pkcs7) + +pkcs7 implements parsing and creating signed and enveloped messages. + +```go +package main + +import ( + "bytes" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + "os" + + "go.mozilla.org/pkcs7" +) + +func SignAndDetach(content []byte, cert *x509.Certificate, privkey *rsa.PrivateKey) (signed []byte, err error) { + toBeSigned, err := NewSignedData(content) + if err != nil { + err = fmt.Errorf("Cannot initialize signed data: %s", err) + return + } + if err = toBeSigned.AddSigner(cert, privkey, SignerInfoConfig{}); err != nil { + err = fmt.Errorf("Cannot add signer: %s", err) + return + } + + // Detach signature, omit if you want an embedded signature + toBeSigned.Detach() + + signed, err = toBeSigned.Finish() + if err != nil { + err = fmt.Errorf("Cannot finish signing data: %s", err) + return + } + + // Verify the signature + pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: signed}) + p7, err := pkcs7.Parse(signed) + if err != nil { + err = fmt.Errorf("Cannot parse our signed data: %s", err) + return + } + + // since the signature was detached, reattach the content here + p7.Content = content + + if bytes.Compare(content, p7.Content) != 0 { + err = fmt.Errorf("Our content was not in the parsed data:\n\tExpected: %s\n\tActual: %s", content, p7.Content) + return + } + if err = p7.Verify(); err != nil { + err = fmt.Errorf("Cannot verify our signed data: %s", err) + return + } + + return signed, nil +} +``` + + + +## Credits +This is a fork of [fullsailor/pkcs7](https://github.com/fullsailor/pkcs7) diff --git a/scep/pkcs7/ber.go b/scep/pkcs7/ber.go new file mode 100644 index 00000000..58525673 --- /dev/null +++ b/scep/pkcs7/ber.go @@ -0,0 +1,251 @@ +package pkcs7 + +import ( + "bytes" + "errors" +) + +var encodeIndent = 0 + +type asn1Object interface { + EncodeTo(writer *bytes.Buffer) error +} + +type asn1Structured struct { + tagBytes []byte + content []asn1Object +} + +func (s asn1Structured) EncodeTo(out *bytes.Buffer) error { + //fmt.Printf("%s--> tag: % X\n", strings.Repeat("| ", encodeIndent), s.tagBytes) + encodeIndent++ + inner := new(bytes.Buffer) + for _, obj := range s.content { + err := obj.EncodeTo(inner) + if err != nil { + return err + } + } + encodeIndent-- + out.Write(s.tagBytes) + encodeLength(out, inner.Len()) + out.Write(inner.Bytes()) + return nil +} + +type asn1Primitive struct { + tagBytes []byte + length int + content []byte +} + +func (p asn1Primitive) EncodeTo(out *bytes.Buffer) error { + _, err := out.Write(p.tagBytes) + if err != nil { + return err + } + if err = encodeLength(out, p.length); err != nil { + return err + } + //fmt.Printf("%s--> tag: % X length: %d\n", strings.Repeat("| ", encodeIndent), p.tagBytes, p.length) + //fmt.Printf("%s--> content length: %d\n", strings.Repeat("| ", encodeIndent), len(p.content)) + out.Write(p.content) + + return nil +} + +func ber2der(ber []byte) ([]byte, error) { + if len(ber) == 0 { + return nil, errors.New("ber2der: input ber is empty") + } + //fmt.Printf("--> ber2der: Transcoding %d bytes\n", len(ber)) + out := new(bytes.Buffer) + + obj, _, err := readObject(ber, 0) + if err != nil { + return nil, err + } + obj.EncodeTo(out) + + // if offset < len(ber) { + // return nil, fmt.Errorf("ber2der: Content longer than expected. Got %d, expected %d", offset, len(ber)) + //} + + return out.Bytes(), nil +} + +// encodes lengths that are longer than 127 into string of bytes +func marshalLongLength(out *bytes.Buffer, i int) (err error) { + n := lengthLength(i) + + for ; n > 0; n-- { + err = out.WriteByte(byte(i >> uint((n-1)*8))) + if err != nil { + return + } + } + + return nil +} + +// computes the byte length of an encoded length value +func lengthLength(i int) (numBytes int) { + numBytes = 1 + for i > 255 { + numBytes++ + i >>= 8 + } + return +} + +// encodes the length in DER format +// If the length fits in 7 bits, the value is encoded directly. +// +// Otherwise, the number of bytes to encode the length is first determined. +// This number is likely to be 4 or less for a 32bit length. This number is +// added to 0x80. The length is encoded in big endian encoding follow after +// +// Examples: +// length | byte 1 | bytes n +// 0 | 0x00 | - +// 120 | 0x78 | - +// 200 | 0x81 | 0xC8 +// 500 | 0x82 | 0x01 0xF4 +// +func encodeLength(out *bytes.Buffer, length int) (err error) { + if length >= 128 { + l := lengthLength(length) + err = out.WriteByte(0x80 | byte(l)) + if err != nil { + return + } + err = marshalLongLength(out, length) + if err != nil { + return + } + } else { + err = out.WriteByte(byte(length)) + if err != nil { + return + } + } + return +} + +func readObject(ber []byte, offset int) (asn1Object, int, error) { + berLen := len(ber) + if offset >= berLen { + return nil, 0, errors.New("ber2der: offset is after end of ber data") + } + tagStart := offset + b := ber[offset] + offset++ + if offset >= berLen { + return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") + } + tag := b & 0x1F // last 5 bits + if tag == 0x1F { + tag = 0 + for ber[offset] >= 0x80 { + tag = tag*128 + ber[offset] - 0x80 + offset++ + if offset > berLen { + return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") + } + } + // jvehent 20170227: this doesn't appear to be used anywhere... + //tag = tag*128 + ber[offset] - 0x80 + offset++ + if offset > berLen { + return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") + } + } + tagEnd := offset + + kind := b & 0x20 + if kind == 0 { + debugprint("--> Primitive\n") + } else { + debugprint("--> Constructed\n") + } + // read length + var length int + l := ber[offset] + offset++ + if offset > berLen { + return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") + } + hack := 0 + if l > 0x80 { + numberOfBytes := (int)(l & 0x7F) + if numberOfBytes > 4 { // int is only guaranteed to be 32bit + return nil, 0, errors.New("ber2der: BER tag length too long") + } + if numberOfBytes == 4 && (int)(ber[offset]) > 0x7F { + return nil, 0, errors.New("ber2der: BER tag length is negative") + } + if (int)(ber[offset]) == 0x0 { + return nil, 0, errors.New("ber2der: BER tag length has leading zero") + } + debugprint("--> (compute length) indicator byte: %x\n", l) + debugprint("--> (compute length) length bytes: % X\n", ber[offset:offset+numberOfBytes]) + for i := 0; i < numberOfBytes; i++ { + length = length*256 + (int)(ber[offset]) + offset++ + if offset > berLen { + return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") + } + } + } else if l == 0x80 { + // find length by searching content + markerIndex := bytes.LastIndex(ber[offset:], []byte{0x0, 0x0}) + if markerIndex == -1 { + return nil, 0, errors.New("ber2der: Invalid BER format") + } + length = markerIndex + hack = 2 + debugprint("--> (compute length) marker found at offset: %d\n", markerIndex+offset) + } else { + length = (int)(l) + } + if length < 0 { + return nil, 0, errors.New("ber2der: invalid negative value found in BER tag length") + } + //fmt.Printf("--> length : %d\n", length) + contentEnd := offset + length + if contentEnd > len(ber) { + return nil, 0, errors.New("ber2der: BER tag length is more than available data") + } + debugprint("--> content start : %d\n", offset) + debugprint("--> content end : %d\n", contentEnd) + debugprint("--> content : % X\n", ber[offset:contentEnd]) + var obj asn1Object + if kind == 0 { + obj = asn1Primitive{ + tagBytes: ber[tagStart:tagEnd], + length: length, + content: ber[offset:contentEnd], + } + } else { + var subObjects []asn1Object + for offset < contentEnd { + var subObj asn1Object + var err error + subObj, offset, err = readObject(ber[:contentEnd], offset) + if err != nil { + return nil, 0, err + } + subObjects = append(subObjects, subObj) + } + obj = asn1Structured{ + tagBytes: ber[tagStart:tagEnd], + content: subObjects, + } + } + + return obj, contentEnd + hack, nil +} + +func debugprint(format string, a ...interface{}) { + //fmt.Printf(format, a) +} diff --git a/scep/pkcs7/ber_test.go b/scep/pkcs7/ber_test.go new file mode 100644 index 00000000..fcb4b6a2 --- /dev/null +++ b/scep/pkcs7/ber_test.go @@ -0,0 +1,62 @@ +package pkcs7 + +import ( + "bytes" + "encoding/asn1" + "strings" + "testing" +) + +func TestBer2Der(t *testing.T) { + // indefinite length fixture + ber := []byte{0x30, 0x80, 0x02, 0x01, 0x01, 0x00, 0x00} + expected := []byte{0x30, 0x03, 0x02, 0x01, 0x01} + der, err := ber2der(ber) + if err != nil { + t.Fatalf("ber2der failed with error: %v", err) + } + if !bytes.Equal(der, expected) { + t.Errorf("ber2der result did not match.\n\tExpected: % X\n\tActual: % X", expected, der) + } + + if der2, err := ber2der(der); err != nil { + t.Errorf("ber2der on DER bytes failed with error: %v", err) + } else { + if !bytes.Equal(der, der2) { + t.Error("ber2der is not idempotent") + } + } + var thing struct { + Number int + } + rest, err := asn1.Unmarshal(der, &thing) + if err != nil { + t.Errorf("Cannot parse resulting DER because: %v", err) + } else if len(rest) > 0 { + t.Errorf("Resulting DER has trailing data: % X", rest) + } +} + +func TestBer2Der_Negatives(t *testing.T) { + fixtures := []struct { + Input []byte + ErrorContains string + }{ + {[]byte{0x30, 0x85}, "tag length too long"}, + {[]byte{0x30, 0x84, 0x80, 0x0, 0x0, 0x0}, "length is negative"}, + {[]byte{0x30, 0x82, 0x0, 0x1}, "length has leading zero"}, + {[]byte{0x30, 0x80, 0x1, 0x2}, "Invalid BER format"}, + {[]byte{0x30, 0x03, 0x01, 0x02}, "length is more than available data"}, + {[]byte{0x30}, "end of ber data reached"}, + } + + for _, fixture := range fixtures { + _, err := ber2der(fixture.Input) + if err == nil { + t.Errorf("No error thrown. Expected: %s", fixture.ErrorContains) + } + if !strings.Contains(err.Error(), fixture.ErrorContains) { + t.Errorf("Unexpected error thrown.\n\tExpected: /%s/\n\tActual: %s", fixture.ErrorContains, err.Error()) + } + } +} diff --git a/scep/pkcs7/decrypt.go b/scep/pkcs7/decrypt.go new file mode 100644 index 00000000..0d088d62 --- /dev/null +++ b/scep/pkcs7/decrypt.go @@ -0,0 +1,177 @@ +package pkcs7 + +import ( + "bytes" + "crypto" + "crypto/aes" + "crypto/cipher" + "crypto/des" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/asn1" + "errors" + "fmt" +) + +// ErrUnsupportedAlgorithm tells you when our quick dev assumptions have failed +var ErrUnsupportedAlgorithm = errors.New("pkcs7: cannot decrypt data: only RSA, DES, DES-EDE3, AES-256-CBC and AES-128-GCM supported") + +// ErrNotEncryptedContent is returned when attempting to Decrypt data that is not encrypted data +var ErrNotEncryptedContent = errors.New("pkcs7: content data is a decryptable data type") + +// Decrypt decrypts encrypted content info for recipient cert and private key +func (p7 *PKCS7) Decrypt(cert *x509.Certificate, pkey crypto.PrivateKey) ([]byte, error) { + data, ok := p7.raw.(envelopedData) + if !ok { + return nil, ErrNotEncryptedContent + } + recipient := selectRecipientForCertificate(data.RecipientInfos, cert) + if recipient.EncryptedKey == nil { + return nil, errors.New("pkcs7: no enveloped recipient for provided certificate") + } + switch pkey := pkey.(type) { + case *rsa.PrivateKey: + var contentKey []byte + contentKey, err := rsa.DecryptPKCS1v15(rand.Reader, pkey, recipient.EncryptedKey) + if err != nil { + return nil, err + } + return data.EncryptedContentInfo.decrypt(contentKey) + } + return nil, ErrUnsupportedAlgorithm +} + +// DecryptUsingPSK decrypts encrypted data using caller provided +// pre-shared secret +func (p7 *PKCS7) DecryptUsingPSK(key []byte) ([]byte, error) { + data, ok := p7.raw.(encryptedData) + if !ok { + return nil, ErrNotEncryptedContent + } + return data.EncryptedContentInfo.decrypt(key) +} + +func (eci encryptedContentInfo) decrypt(key []byte) ([]byte, error) { + alg := eci.ContentEncryptionAlgorithm.Algorithm + if !alg.Equal(OIDEncryptionAlgorithmDESCBC) && + !alg.Equal(OIDEncryptionAlgorithmDESEDE3CBC) && + !alg.Equal(OIDEncryptionAlgorithmAES256CBC) && + !alg.Equal(OIDEncryptionAlgorithmAES128CBC) && + !alg.Equal(OIDEncryptionAlgorithmAES128GCM) && + !alg.Equal(OIDEncryptionAlgorithmAES256GCM) { + fmt.Printf("Unsupported Content Encryption Algorithm: %s\n", alg) + return nil, ErrUnsupportedAlgorithm + } + + // EncryptedContent can either be constructed of multple OCTET STRINGs + // or _be_ a tagged OCTET STRING + var cyphertext []byte + if eci.EncryptedContent.IsCompound { + // Complex case to concat all of the children OCTET STRINGs + var buf bytes.Buffer + cypherbytes := eci.EncryptedContent.Bytes + for { + var part []byte + cypherbytes, _ = asn1.Unmarshal(cypherbytes, &part) + buf.Write(part) + if cypherbytes == nil { + break + } + } + cyphertext = buf.Bytes() + } else { + // Simple case, the bytes _are_ the cyphertext + cyphertext = eci.EncryptedContent.Bytes + } + + var block cipher.Block + var err error + + switch { + case alg.Equal(OIDEncryptionAlgorithmDESCBC): + block, err = des.NewCipher(key) + case alg.Equal(OIDEncryptionAlgorithmDESEDE3CBC): + block, err = des.NewTripleDESCipher(key) + case alg.Equal(OIDEncryptionAlgorithmAES256CBC), alg.Equal(OIDEncryptionAlgorithmAES256GCM): + fallthrough + case alg.Equal(OIDEncryptionAlgorithmAES128GCM), alg.Equal(OIDEncryptionAlgorithmAES128CBC): + block, err = aes.NewCipher(key) + } + + if err != nil { + return nil, err + } + + if alg.Equal(OIDEncryptionAlgorithmAES128GCM) || alg.Equal(OIDEncryptionAlgorithmAES256GCM) { + params := aesGCMParameters{} + paramBytes := eci.ContentEncryptionAlgorithm.Parameters.Bytes + + _, err := asn1.Unmarshal(paramBytes, ¶ms) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + if len(params.Nonce) != gcm.NonceSize() { + return nil, errors.New("pkcs7: encryption algorithm parameters are incorrect") + } + if params.ICVLen != gcm.Overhead() { + return nil, errors.New("pkcs7: encryption algorithm parameters are incorrect") + } + + plaintext, err := gcm.Open(nil, params.Nonce, cyphertext, nil) + if err != nil { + return nil, err + } + + return plaintext, nil + } + + iv := eci.ContentEncryptionAlgorithm.Parameters.Bytes + if len(iv) != block.BlockSize() { + return nil, errors.New("pkcs7: encryption algorithm parameters are malformed") + } + mode := cipher.NewCBCDecrypter(block, iv) + plaintext := make([]byte, len(cyphertext)) + mode.CryptBlocks(plaintext, cyphertext) + if plaintext, err = unpad(plaintext, mode.BlockSize()); err != nil { + return nil, err + } + return plaintext, nil +} + +func unpad(data []byte, blocklen int) ([]byte, error) { + if blocklen < 1 { + return nil, fmt.Errorf("invalid blocklen %d", blocklen) + } + if len(data)%blocklen != 0 || len(data) == 0 { + return nil, fmt.Errorf("invalid data len %d", len(data)) + } + + // the last byte is the length of padding + padlen := int(data[len(data)-1]) + + // check padding integrity, all bytes should be the same + pad := data[len(data)-padlen:] + for _, padbyte := range pad { + if padbyte != byte(padlen) { + return nil, errors.New("invalid padding") + } + } + + return data[:len(data)-padlen], nil +} + +func selectRecipientForCertificate(recipients []recipientInfo, cert *x509.Certificate) recipientInfo { + for _, recp := range recipients { + if isCertMatchForIssuerAndSerial(cert, recp.IssuerAndSerialNumber) { + return recp + } + } + return recipientInfo{} +} diff --git a/scep/pkcs7/decrypt_test.go b/scep/pkcs7/decrypt_test.go new file mode 100644 index 00000000..06cc0f80 --- /dev/null +++ b/scep/pkcs7/decrypt_test.go @@ -0,0 +1,60 @@ +package pkcs7 + +import ( + "bytes" + "testing" +) + +func TestDecrypt(t *testing.T) { + fixture := UnmarshalTestFixture(EncryptedTestFixture) + p7, err := Parse(fixture.Input) + if err != nil { + t.Fatal(err) + } + content, err := p7.Decrypt(fixture.Certificate, fixture.PrivateKey) + if err != nil { + t.Errorf("Cannot Decrypt with error: %v", err) + } + expected := []byte("This is a test") + if !bytes.Equal(content, expected) { + t.Errorf("Decrypted result does not match.\n\tExpected:%s\n\tActual:%s", expected, content) + } +} + +// Content is "This is a test" +var EncryptedTestFixture = ` +-----BEGIN PKCS7----- +MIIBFwYJKoZIhvcNAQcDoIIBCDCCAQQCAQAxgcowgccCAQAwMjApMRAwDgYDVQQK +EwdBY21lIENvMRUwEwYDVQQDEwxFZGRhcmQgU3RhcmsCBQDL+CvWMAsGCSqGSIb3 +DQEBAQSBgKyP/5WlRTZD3dWMrLOX6QRNDrXEkQjhmToRwFZdY3LgUh25ZU0S/q4G +dHPV21Fv9lQD+q7l3vfeHw8M6Z1PKi9sHMVfxAkQpvaI96DTIT3YHtuLC1w3geCO +8eFWTq2qS4WChSuS/yhYosjA1kTkE0eLnVZcGw0z/WVuEZznkdyIMDIGCSqGSIb3 +DQEHATARBgUrDgMCBwQImpKsUyMPpQigEgQQRcWWrCRXqpD5Njs0GkJl+g== +-----END PKCS7----- +-----BEGIN CERTIFICATE----- +MIIB1jCCAUGgAwIBAgIFAMv4K9YwCwYJKoZIhvcNAQELMCkxEDAOBgNVBAoTB0Fj +bWUgQ28xFTATBgNVBAMTDEVkZGFyZCBTdGFyazAeFw0xNTA1MDYwMzU2NDBaFw0x +NjA1MDYwMzU2NDBaMCUxEDAOBgNVBAoTB0FjbWUgQ28xETAPBgNVBAMTCEpvbiBT +bm93MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDK6NU0R0eiCYVquU4RcjKc +LzGfx0aa1lMr2TnLQUSeLFZHFxsyyMXXuMPig3HK4A7SGFHupO+/1H/sL4xpH5zg +8+Zg2r8xnnney7abxcuv0uATWSIeKlNnb1ZO1BAxFnESc3GtyOCr2dUwZHX5mRVP ++Zxp2ni5qHNraf3wE2VPIQIDAQABoxIwEDAOBgNVHQ8BAf8EBAMCAKAwCwYJKoZI +hvcNAQELA4GBAIr2F7wsqmEU/J/kLyrCgEVXgaV/sKZq4pPNnzS0tBYk8fkV3V18 +sBJyHKRLL/wFZASvzDcVGCplXyMdAOCyfd8jO3F9Ac/xdlz10RrHJT75hNu3a7/n +9KNwKhfN4A1CQv2x372oGjRhCW5bHNCWx4PIVeNzCyq/KZhyY9sxHE6f +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIICXgIBAAKBgQDK6NU0R0eiCYVquU4RcjKcLzGfx0aa1lMr2TnLQUSeLFZHFxsy +yMXXuMPig3HK4A7SGFHupO+/1H/sL4xpH5zg8+Zg2r8xnnney7abxcuv0uATWSIe +KlNnb1ZO1BAxFnESc3GtyOCr2dUwZHX5mRVP+Zxp2ni5qHNraf3wE2VPIQIDAQAB +AoGBALyvnSt7KUquDen7nXQtvJBudnf9KFPt//OjkdHHxNZNpoF/JCSqfQeoYkeu +MdAVYNLQGMiRifzZz4dDhA9xfUAuy7lcGQcMCxEQ1dwwuFaYkawbS0Tvy2PFlq2d +H5/HeDXU4EDJ3BZg0eYj2Bnkt1sJI35UKQSxblQ0MY2q0uFBAkEA5MMOogkgUx1C +67S1tFqMUSM8D0mZB0O5vOJZC5Gtt2Urju6vywge2ArExWRXlM2qGl8afFy2SgSv +Xk5eybcEiQJBAOMRwwbEoW5NYHuFFbSJyWll4n71CYuWuQOCzehDPyTb80WFZGLV +i91kFIjeERyq88eDE5xVB3ZuRiXqaShO/9kCQQCKOEkpInaDgZSjskZvuJ47kByD +6CYsO4GIXQMMeHML8ncFH7bb6AYq5ybJVb2NTU7QLFJmfeYuhvIm+xdOreRxAkEA +o5FC5Jg2FUfFzZSDmyZ6IONUsdF/i78KDV5nRv1R+hI6/oRlWNCtTNBv/lvBBd6b +dseUE9QoaQZsn5lpILEvmQJAZ0B+Or1rAYjnbjnUhdVZoy9kC4Zov+4UH3N/BtSy +KJRWUR0wTWfZBPZ5hAYZjTBEAFULaYCXlQKsODSp0M1aQA== +-----END PRIVATE KEY-----` diff --git a/scep/pkcs7/encrypt.go b/scep/pkcs7/encrypt.go new file mode 100644 index 00000000..da57ae64 --- /dev/null +++ b/scep/pkcs7/encrypt.go @@ -0,0 +1,399 @@ +package pkcs7 + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/des" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "fmt" +) + +type envelopedData struct { + Version int + RecipientInfos []recipientInfo `asn1:"set"` + EncryptedContentInfo encryptedContentInfo +} + +type encryptedData struct { + Version int + EncryptedContentInfo encryptedContentInfo +} + +type recipientInfo struct { + Version int + IssuerAndSerialNumber issuerAndSerial + KeyEncryptionAlgorithm pkix.AlgorithmIdentifier + EncryptedKey []byte +} + +type encryptedContentInfo struct { + ContentType asn1.ObjectIdentifier + ContentEncryptionAlgorithm pkix.AlgorithmIdentifier + EncryptedContent asn1.RawValue `asn1:"tag:0,optional,explicit"` +} + +const ( + // EncryptionAlgorithmDESCBC is the DES CBC encryption algorithm + EncryptionAlgorithmDESCBC = iota + + // EncryptionAlgorithmAES128CBC is the AES 128 bits with CBC encryption algorithm + // Avoid this algorithm unless required for interoperability; use AES GCM instead. + EncryptionAlgorithmAES128CBC + + // EncryptionAlgorithmAES256CBC is the AES 256 bits with CBC encryption algorithm + // Avoid this algorithm unless required for interoperability; use AES GCM instead. + EncryptionAlgorithmAES256CBC + + // EncryptionAlgorithmAES128GCM is the AES 128 bits with GCM encryption algorithm + EncryptionAlgorithmAES128GCM + + // EncryptionAlgorithmAES256GCM is the AES 256 bits with GCM encryption algorithm + EncryptionAlgorithmAES256GCM +) + +// ContentEncryptionAlgorithm determines the algorithm used to encrypt the +// plaintext message. Change the value of this variable to change which +// algorithm is used in the Encrypt() function. +var ContentEncryptionAlgorithm = EncryptionAlgorithmDESCBC + +// ErrUnsupportedEncryptionAlgorithm is returned when attempting to encrypt +// content with an unsupported algorithm. +var ErrUnsupportedEncryptionAlgorithm = errors.New("pkcs7: cannot encrypt content: only DES-CBC, AES-CBC, and AES-GCM supported") + +// ErrPSKNotProvided is returned when attempting to encrypt +// using a PSK without actually providing the PSK. +var ErrPSKNotProvided = errors.New("pkcs7: cannot encrypt content: PSK not provided") + +const nonceSize = 12 + +type aesGCMParameters struct { + Nonce []byte `asn1:"tag:4"` + ICVLen int +} + +func encryptAESGCM(content []byte, key []byte) ([]byte, *encryptedContentInfo, error) { + var keyLen int + var algID asn1.ObjectIdentifier + switch ContentEncryptionAlgorithm { + case EncryptionAlgorithmAES128GCM: + keyLen = 16 + algID = OIDEncryptionAlgorithmAES128GCM + case EncryptionAlgorithmAES256GCM: + keyLen = 32 + algID = OIDEncryptionAlgorithmAES256GCM + default: + return nil, nil, fmt.Errorf("invalid ContentEncryptionAlgorithm in encryptAESGCM: %d", ContentEncryptionAlgorithm) + } + if key == nil { + // Create AES key + key = make([]byte, keyLen) + + _, err := rand.Read(key) + if err != nil { + return nil, nil, err + } + } + + // Create nonce + nonce := make([]byte, nonceSize) + + _, err := rand.Read(nonce) + if err != nil { + return nil, nil, err + } + + // Encrypt content + block, err := aes.NewCipher(key) + if err != nil { + return nil, nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, nil, err + } + + ciphertext := gcm.Seal(nil, nonce, content, nil) + + // Prepare ASN.1 Encrypted Content Info + paramSeq := aesGCMParameters{ + Nonce: nonce, + ICVLen: gcm.Overhead(), + } + + paramBytes, err := asn1.Marshal(paramSeq) + if err != nil { + return nil, nil, err + } + + eci := encryptedContentInfo{ + ContentType: OIDData, + ContentEncryptionAlgorithm: pkix.AlgorithmIdentifier{ + Algorithm: algID, + Parameters: asn1.RawValue{ + Tag: asn1.TagSequence, + Bytes: paramBytes, + }, + }, + EncryptedContent: marshalEncryptedContent(ciphertext), + } + + return key, &eci, nil +} + +func encryptDESCBC(content []byte, key []byte) ([]byte, *encryptedContentInfo, error) { + if key == nil { + // Create DES key + key = make([]byte, 8) + + _, err := rand.Read(key) + if err != nil { + return nil, nil, err + } + } + + // Create CBC IV + iv := make([]byte, des.BlockSize) + _, err := rand.Read(iv) + if err != nil { + return nil, nil, err + } + + // Encrypt padded content + block, err := des.NewCipher(key) + if err != nil { + return nil, nil, err + } + mode := cipher.NewCBCEncrypter(block, iv) + plaintext, err := pad(content, mode.BlockSize()) + if err != nil { + return nil, nil, err + } + cyphertext := make([]byte, len(plaintext)) + mode.CryptBlocks(cyphertext, plaintext) + + // Prepare ASN.1 Encrypted Content Info + eci := encryptedContentInfo{ + ContentType: OIDData, + ContentEncryptionAlgorithm: pkix.AlgorithmIdentifier{ + Algorithm: OIDEncryptionAlgorithmDESCBC, + Parameters: asn1.RawValue{Tag: 4, Bytes: iv}, + }, + EncryptedContent: marshalEncryptedContent(cyphertext), + } + + return key, &eci, nil +} + +func encryptAESCBC(content []byte, key []byte) ([]byte, *encryptedContentInfo, error) { + var keyLen int + var algID asn1.ObjectIdentifier + switch ContentEncryptionAlgorithm { + case EncryptionAlgorithmAES128CBC: + keyLen = 16 + algID = OIDEncryptionAlgorithmAES128CBC + case EncryptionAlgorithmAES256CBC: + keyLen = 32 + algID = OIDEncryptionAlgorithmAES256CBC + default: + return nil, nil, fmt.Errorf("invalid ContentEncryptionAlgorithm in encryptAESCBC: %d", ContentEncryptionAlgorithm) + } + + if key == nil { + // Create AES key + key = make([]byte, keyLen) + + _, err := rand.Read(key) + if err != nil { + return nil, nil, err + } + } + + // Create CBC IV + iv := make([]byte, aes.BlockSize) + _, err := rand.Read(iv) + if err != nil { + return nil, nil, err + } + + // Encrypt padded content + block, err := aes.NewCipher(key) + if err != nil { + return nil, nil, err + } + mode := cipher.NewCBCEncrypter(block, iv) + plaintext, err := pad(content, mode.BlockSize()) + if err != nil { + return nil, nil, err + } + cyphertext := make([]byte, len(plaintext)) + mode.CryptBlocks(cyphertext, plaintext) + + // Prepare ASN.1 Encrypted Content Info + eci := encryptedContentInfo{ + ContentType: OIDData, + ContentEncryptionAlgorithm: pkix.AlgorithmIdentifier{ + Algorithm: algID, + Parameters: asn1.RawValue{Tag: 4, Bytes: iv}, + }, + EncryptedContent: marshalEncryptedContent(cyphertext), + } + + return key, &eci, nil +} + +// Encrypt creates and returns an envelope data PKCS7 structure with encrypted +// recipient keys for each recipient public key. +// +// The algorithm used to perform encryption is determined by the current value +// of the global ContentEncryptionAlgorithm package variable. By default, the +// value is EncryptionAlgorithmDESCBC. To use a different algorithm, change the +// value before calling Encrypt(). For example: +// +// ContentEncryptionAlgorithm = EncryptionAlgorithmAES128GCM +// +// TODO(fullsailor): Add support for encrypting content with other algorithms +func Encrypt(content []byte, recipients []*x509.Certificate) ([]byte, error) { + var eci *encryptedContentInfo + var key []byte + var err error + + // Apply chosen symmetric encryption method + switch ContentEncryptionAlgorithm { + case EncryptionAlgorithmDESCBC: + key, eci, err = encryptDESCBC(content, nil) + case EncryptionAlgorithmAES128CBC: + fallthrough + case EncryptionAlgorithmAES256CBC: + key, eci, err = encryptAESCBC(content, nil) + case EncryptionAlgorithmAES128GCM: + fallthrough + case EncryptionAlgorithmAES256GCM: + key, eci, err = encryptAESGCM(content, nil) + + default: + return nil, ErrUnsupportedEncryptionAlgorithm + } + + if err != nil { + return nil, err + } + + // Prepare each recipient's encrypted cipher key + recipientInfos := make([]recipientInfo, len(recipients)) + for i, recipient := range recipients { + encrypted, err := encryptKey(key, recipient) + if err != nil { + return nil, err + } + ias, err := cert2issuerAndSerial(recipient) + if err != nil { + return nil, err + } + info := recipientInfo{ + Version: 0, + IssuerAndSerialNumber: ias, + KeyEncryptionAlgorithm: pkix.AlgorithmIdentifier{ + Algorithm: OIDEncryptionAlgorithmRSA, + }, + EncryptedKey: encrypted, + } + recipientInfos[i] = info + } + + // Prepare envelope content + envelope := envelopedData{ + EncryptedContentInfo: *eci, + Version: 0, + RecipientInfos: recipientInfos, + } + innerContent, err := asn1.Marshal(envelope) + if err != nil { + return nil, err + } + + // Prepare outer payload structure + wrapper := contentInfo{ + ContentType: OIDEnvelopedData, + Content: asn1.RawValue{Class: 2, Tag: 0, IsCompound: true, Bytes: innerContent}, + } + + return asn1.Marshal(wrapper) +} + +// EncryptUsingPSK creates and returns an encrypted data PKCS7 structure, +// encrypted using caller provided pre-shared secret. +func EncryptUsingPSK(content []byte, key []byte) ([]byte, error) { + var eci *encryptedContentInfo + var err error + + if key == nil { + return nil, ErrPSKNotProvided + } + + // Apply chosen symmetric encryption method + switch ContentEncryptionAlgorithm { + case EncryptionAlgorithmDESCBC: + _, eci, err = encryptDESCBC(content, key) + + case EncryptionAlgorithmAES128GCM: + fallthrough + case EncryptionAlgorithmAES256GCM: + _, eci, err = encryptAESGCM(content, key) + + default: + return nil, ErrUnsupportedEncryptionAlgorithm + } + + if err != nil { + return nil, err + } + + // Prepare encrypted-data content + ed := encryptedData{ + Version: 0, + EncryptedContentInfo: *eci, + } + innerContent, err := asn1.Marshal(ed) + if err != nil { + return nil, err + } + + // Prepare outer payload structure + wrapper := contentInfo{ + ContentType: OIDEncryptedData, + Content: asn1.RawValue{Class: 2, Tag: 0, IsCompound: true, Bytes: innerContent}, + } + + return asn1.Marshal(wrapper) +} + +func marshalEncryptedContent(content []byte) asn1.RawValue { + asn1Content, _ := asn1.Marshal(content) + return asn1.RawValue{Tag: 0, Class: 2, Bytes: asn1Content, IsCompound: true} +} + +func encryptKey(key []byte, recipient *x509.Certificate) ([]byte, error) { + if pub := recipient.PublicKey.(*rsa.PublicKey); pub != nil { + return rsa.EncryptPKCS1v15(rand.Reader, pub, key) + } + return nil, ErrUnsupportedAlgorithm +} + +func pad(data []byte, blocklen int) ([]byte, error) { + if blocklen < 1 { + return nil, fmt.Errorf("invalid blocklen %d", blocklen) + } + padlen := blocklen - (len(data) % blocklen) + if padlen == 0 { + padlen = blocklen + } + pad := bytes.Repeat([]byte{byte(padlen)}, padlen) + return append(data, pad...), nil +} diff --git a/scep/pkcs7/encrypt_test.go b/scep/pkcs7/encrypt_test.go new file mode 100644 index 00000000..c64381e2 --- /dev/null +++ b/scep/pkcs7/encrypt_test.go @@ -0,0 +1,102 @@ +package pkcs7 + +import ( + "bytes" + "crypto/x509" + "testing" +) + +func TestEncrypt(t *testing.T) { + modes := []int{ + EncryptionAlgorithmDESCBC, + EncryptionAlgorithmAES128CBC, + EncryptionAlgorithmAES256CBC, + EncryptionAlgorithmAES128GCM, + EncryptionAlgorithmAES256GCM, + } + sigalgs := []x509.SignatureAlgorithm{ + x509.SHA1WithRSA, + x509.SHA256WithRSA, + x509.SHA512WithRSA, + } + for _, mode := range modes { + for _, sigalg := range sigalgs { + ContentEncryptionAlgorithm = mode + + plaintext := []byte("Hello Secret World!") + cert, err := createTestCertificate(sigalg) + if err != nil { + t.Fatal(err) + } + encrypted, err := Encrypt(plaintext, []*x509.Certificate{cert.Certificate}) + if err != nil { + t.Fatal(err) + } + p7, err := Parse(encrypted) + if err != nil { + t.Fatalf("cannot Parse encrypted result: %s", err) + } + result, err := p7.Decrypt(cert.Certificate, *cert.PrivateKey) + if err != nil { + t.Fatalf("cannot Decrypt encrypted result: %s", err) + } + if !bytes.Equal(plaintext, result) { + t.Errorf("encrypted data does not match plaintext:\n\tExpected: %s\n\tActual: %s", plaintext, result) + } + } + } +} + +func TestEncryptUsingPSK(t *testing.T) { + modes := []int{ + EncryptionAlgorithmDESCBC, + EncryptionAlgorithmAES128GCM, + } + + for _, mode := range modes { + ContentEncryptionAlgorithm = mode + plaintext := []byte("Hello Secret World!") + var key []byte + + switch mode { + case EncryptionAlgorithmDESCBC: + key = []byte("64BitKey") + case EncryptionAlgorithmAES128GCM: + key = []byte("128BitKey4AESGCM") + } + ciphertext, err := EncryptUsingPSK(plaintext, key) + if err != nil { + t.Fatal(err) + } + + p7, _ := Parse(ciphertext) + result, err := p7.DecryptUsingPSK(key) + if err != nil { + t.Fatalf("cannot Decrypt encrypted result: %s", err) + } + if !bytes.Equal(plaintext, result) { + t.Errorf("encrypted data does not match plaintext:\n\tExpected: %s\n\tActual: %s", plaintext, result) + } + } +} + +func TestPad(t *testing.T) { + tests := []struct { + Original []byte + Expected []byte + BlockSize int + }{ + {[]byte{0x1, 0x2, 0x3, 0x10}, []byte{0x1, 0x2, 0x3, 0x10, 0x4, 0x4, 0x4, 0x4}, 8}, + {[]byte{0x1, 0x2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0}, []byte{0x1, 0x2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8}, 8}, + } + for _, test := range tests { + padded, err := pad(test.Original, test.BlockSize) + if err != nil { + t.Errorf("pad encountered error: %s", err) + continue + } + if !bytes.Equal(test.Expected, padded) { + t.Errorf("pad results mismatch:\n\tExpected: %X\n\tActual: %X", test.Expected, padded) + } + } +} diff --git a/scep/pkcs7/pkcs7.go b/scep/pkcs7/pkcs7.go new file mode 100644 index 00000000..ccc6cc6d --- /dev/null +++ b/scep/pkcs7/pkcs7.go @@ -0,0 +1,291 @@ +// Package pkcs7 implements parsing and generation of some PKCS#7 structures. +package pkcs7 + +import ( + "bytes" + "crypto" + "crypto/dsa" + "crypto/ecdsa" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "fmt" + "sort" + + _ "crypto/sha1" // for crypto.SHA1 +) + +// PKCS7 Represents a PKCS7 structure +type PKCS7 struct { + Content []byte + Certificates []*x509.Certificate + CRLs []pkix.CertificateList + Signers []signerInfo + raw interface{} +} + +type contentInfo struct { + ContentType asn1.ObjectIdentifier + Content asn1.RawValue `asn1:"explicit,optional,tag:0"` +} + +// ErrUnsupportedContentType is returned when a PKCS7 content is not supported. +// Currently only Data (1.2.840.113549.1.7.1), Signed Data (1.2.840.113549.1.7.2), +// and Enveloped Data are supported (1.2.840.113549.1.7.3) +var ErrUnsupportedContentType = errors.New("pkcs7: cannot parse data: unimplemented content type") + +type unsignedData []byte + +var ( + // Signed Data OIDs + OIDData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1} + OIDSignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2} + OIDEnvelopedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 3} + OIDEncryptedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 6} + OIDAttributeContentType = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3} + OIDAttributeMessageDigest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 4} + OIDAttributeSigningTime = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 5} + + // Digest Algorithms + OIDDigestAlgorithmSHA1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26} + OIDDigestAlgorithmSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1} + OIDDigestAlgorithmSHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2} + OIDDigestAlgorithmSHA512 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3} + + OIDDigestAlgorithmDSA = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 1} + OIDDigestAlgorithmDSASHA1 = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 3} + + OIDDigestAlgorithmECDSASHA1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1} + OIDDigestAlgorithmECDSASHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2} + OIDDigestAlgorithmECDSASHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3} + OIDDigestAlgorithmECDSASHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4} + + // Signature Algorithms + OIDEncryptionAlgorithmRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} + OIDEncryptionAlgorithmRSASHA1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5} + OIDEncryptionAlgorithmRSASHA256 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11} + OIDEncryptionAlgorithmRSASHA384 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12} + OIDEncryptionAlgorithmRSASHA512 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13} + + OIDEncryptionAlgorithmECDSAP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7} + OIDEncryptionAlgorithmECDSAP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34} + OIDEncryptionAlgorithmECDSAP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35} + + // Encryption Algorithms + OIDEncryptionAlgorithmDESCBC = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 7} + OIDEncryptionAlgorithmDESEDE3CBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 3, 7} + OIDEncryptionAlgorithmAES256CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 42} + OIDEncryptionAlgorithmAES128GCM = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 6} + OIDEncryptionAlgorithmAES128CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 2} + OIDEncryptionAlgorithmAES256GCM = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 46} +) + +func getHashForOID(oid asn1.ObjectIdentifier) (crypto.Hash, error) { + switch { + case oid.Equal(OIDDigestAlgorithmSHA1), oid.Equal(OIDDigestAlgorithmECDSASHA1), + oid.Equal(OIDDigestAlgorithmDSA), oid.Equal(OIDDigestAlgorithmDSASHA1), + oid.Equal(OIDEncryptionAlgorithmRSA): + return crypto.SHA1, nil + case oid.Equal(OIDDigestAlgorithmSHA256), oid.Equal(OIDDigestAlgorithmECDSASHA256): + return crypto.SHA256, nil + case oid.Equal(OIDDigestAlgorithmSHA384), oid.Equal(OIDDigestAlgorithmECDSASHA384): + return crypto.SHA384, nil + case oid.Equal(OIDDigestAlgorithmSHA512), oid.Equal(OIDDigestAlgorithmECDSASHA512): + return crypto.SHA512, nil + } + return crypto.Hash(0), ErrUnsupportedAlgorithm +} + +// getDigestOIDForSignatureAlgorithm takes an x509.SignatureAlgorithm +// and returns the corresponding OID digest algorithm +func getDigestOIDForSignatureAlgorithm(digestAlg x509.SignatureAlgorithm) (asn1.ObjectIdentifier, error) { + switch digestAlg { + case x509.SHA1WithRSA, x509.ECDSAWithSHA1: + return OIDDigestAlgorithmSHA1, nil + case x509.SHA256WithRSA, x509.ECDSAWithSHA256: + return OIDDigestAlgorithmSHA256, nil + case x509.SHA384WithRSA, x509.ECDSAWithSHA384: + return OIDDigestAlgorithmSHA384, nil + case x509.SHA512WithRSA, x509.ECDSAWithSHA512: + return OIDDigestAlgorithmSHA512, nil + } + return nil, fmt.Errorf("pkcs7: cannot convert hash to oid, unknown hash algorithm") +} + +// getOIDForEncryptionAlgorithm takes the private key type of the signer and +// the OID of a digest algorithm to return the appropriate signerInfo.DigestEncryptionAlgorithm +func getOIDForEncryptionAlgorithm(pkey crypto.PrivateKey, OIDDigestAlg asn1.ObjectIdentifier) (asn1.ObjectIdentifier, error) { + switch pkey.(type) { + case *rsa.PrivateKey: + switch { + default: + return OIDEncryptionAlgorithmRSA, nil + case OIDDigestAlg.Equal(OIDEncryptionAlgorithmRSA): + return OIDEncryptionAlgorithmRSA, nil + case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA1): + return OIDEncryptionAlgorithmRSASHA1, nil + case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA256): + return OIDEncryptionAlgorithmRSASHA256, nil + case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA384): + return OIDEncryptionAlgorithmRSASHA384, nil + case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA512): + return OIDEncryptionAlgorithmRSASHA512, nil + } + case *ecdsa.PrivateKey: + switch { + case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA1): + return OIDDigestAlgorithmECDSASHA1, nil + case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA256): + return OIDDigestAlgorithmECDSASHA256, nil + case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA384): + return OIDDigestAlgorithmECDSASHA384, nil + case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA512): + return OIDDigestAlgorithmECDSASHA512, nil + } + case *dsa.PrivateKey: + return OIDDigestAlgorithmDSA, nil + } + return nil, fmt.Errorf("pkcs7: cannot convert encryption algorithm to oid, unknown private key type %T", pkey) + +} + +// Parse decodes a DER encoded PKCS7 package +func Parse(data []byte) (p7 *PKCS7, err error) { + if len(data) == 0 { + return nil, errors.New("pkcs7: input data is empty") + } + var info contentInfo + der, err := ber2der(data) + if err != nil { + return nil, err + } + rest, err := asn1.Unmarshal(der, &info) + if len(rest) > 0 { + err = asn1.SyntaxError{Msg: "trailing data"} + return + } + if err != nil { + return + } + + // fmt.Printf("--> Content Type: %s", info.ContentType) + switch { + case info.ContentType.Equal(OIDSignedData): + return parseSignedData(info.Content.Bytes) + case info.ContentType.Equal(OIDEnvelopedData): + return parseEnvelopedData(info.Content.Bytes) + case info.ContentType.Equal(OIDEncryptedData): + return parseEncryptedData(info.Content.Bytes) + } + return nil, ErrUnsupportedContentType +} + +func parseEnvelopedData(data []byte) (*PKCS7, error) { + var ed envelopedData + if _, err := asn1.Unmarshal(data, &ed); err != nil { + return nil, err + } + return &PKCS7{ + raw: ed, + }, nil +} + +func parseEncryptedData(data []byte) (*PKCS7, error) { + var ed encryptedData + if _, err := asn1.Unmarshal(data, &ed); err != nil { + return nil, err + } + return &PKCS7{ + raw: ed, + }, nil +} + +func (raw rawCertificates) Parse() ([]*x509.Certificate, error) { + if len(raw.Raw) == 0 { + return nil, nil + } + + var val asn1.RawValue + if _, err := asn1.Unmarshal(raw.Raw, &val); err != nil { + return nil, err + } + + return x509.ParseCertificates(val.Bytes) +} + +func isCertMatchForIssuerAndSerial(cert *x509.Certificate, ias issuerAndSerial) bool { + return cert.SerialNumber.Cmp(ias.SerialNumber) == 0 && bytes.Equal(cert.RawIssuer, ias.IssuerName.FullBytes) +} + +// Attribute represents a key value pair attribute. Value must be marshalable byte +// `encoding/asn1` +type Attribute struct { + Type asn1.ObjectIdentifier + Value interface{} +} + +type attributes struct { + types []asn1.ObjectIdentifier + values []interface{} +} + +// Add adds the attribute, maintaining insertion order +func (attrs *attributes) Add(attrType asn1.ObjectIdentifier, value interface{}) { + attrs.types = append(attrs.types, attrType) + attrs.values = append(attrs.values, value) +} + +type sortableAttribute struct { + SortKey []byte + Attribute attribute +} + +type attributeSet []sortableAttribute + +func (sa attributeSet) Len() int { + return len(sa) +} + +func (sa attributeSet) Less(i, j int) bool { + return bytes.Compare(sa[i].SortKey, sa[j].SortKey) < 0 +} + +func (sa attributeSet) Swap(i, j int) { + sa[i], sa[j] = sa[j], sa[i] +} + +func (sa attributeSet) Attributes() []attribute { + attrs := make([]attribute, len(sa)) + for i, attr := range sa { + attrs[i] = attr.Attribute + } + return attrs +} + +func (attrs *attributes) ForMarshalling() ([]attribute, error) { + sortables := make(attributeSet, len(attrs.types)) + for i := range sortables { + attrType := attrs.types[i] + attrValue := attrs.values[i] + asn1Value, err := asn1.Marshal(attrValue) + if err != nil { + return nil, err + } + attr := attribute{ + Type: attrType, + Value: asn1.RawValue{Tag: 17, IsCompound: true, Bytes: asn1Value}, // 17 == SET tag + } + encoded, err := asn1.Marshal(attr) + if err != nil { + return nil, err + } + sortables[i] = sortableAttribute{ + SortKey: encoded, + Attribute: attr, + } + } + sort.Sort(sortables) + return sortables.Attributes(), nil +} diff --git a/scep/pkcs7/pkcs7_test.go b/scep/pkcs7/pkcs7_test.go new file mode 100644 index 00000000..e743192d --- /dev/null +++ b/scep/pkcs7/pkcs7_test.go @@ -0,0 +1,326 @@ +package pkcs7 + +import ( + "crypto" + "crypto/dsa" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "log" + "math/big" + "os" + "time" +) + +var test1024Key, test2048Key, test3072Key, test4096Key *rsa.PrivateKey + +func init() { + test1024Key = &rsa.PrivateKey{ + PublicKey: rsa.PublicKey{ + N: fromBase10("123024078101403810516614073341068864574068590522569345017786163424062310013967742924377390210586226651760719671658568413826602264886073432535341149584680111145880576802262550990305759285883150470245429547886689754596541046564560506544976611114898883158121012232676781340602508151730773214407220733898059285561"), + E: 65537, + }, + D: fromBase10("118892427340746627750435157989073921703209000249285930635312944544706203626114423392257295670807166199489096863209592887347935991101581502404113203993092422730000157893515953622392722273095289787303943046491132467130346663160540744582438810535626328230098940583296878135092036661410664695896115177534496784545"), + Primes: []*big.Int{ + fromBase10("12172745919282672373981903347443034348576729562395784527365032103134165674508405592530417723266847908118361582847315228810176708212888860333051929276459099"), + fromBase10("10106518193772789699356660087736308350857919389391620140340519320928952625438936098550728858345355053201610649202713962702543058578827268756755006576249339"), + }, + } + test1024Key.Precompute() + test2048Key = &rsa.PrivateKey{ + PublicKey: rsa.PublicKey{ + N: fromBase10("14314132931241006650998084889274020608918049032671858325988396851334124245188214251956198731333464217832226406088020736932173064754214329009979944037640912127943488972644697423190955557435910767690712778463524983667852819010259499695177313115447116110358524558307947613422897787329221478860907963827160223559690523660574329011927531289655711860504630573766609239332569210831325633840174683944553667352219670930408593321661375473885147973879086994006440025257225431977751512374815915392249179976902953721486040787792801849818254465486633791826766873076617116727073077821584676715609985777563958286637185868165868520557"), + E: 3, + }, + D: fromBase10("9542755287494004433998723259516013739278699355114572217325597900889416163458809501304132487555642811888150937392013824621448709836142886006653296025093941418628992648429798282127303704957273845127141852309016655778568546006839666463451542076964744073572349705538631742281931858219480985907271975884773482372966847639853897890615456605598071088189838676728836833012254065983259638538107719766738032720239892094196108713378822882383694456030043492571063441943847195939549773271694647657549658603365629458610273821292232646334717612674519997533901052790334279661754176490593041941863932308687197618671528035670452762731"), + Primes: []*big.Int{ + fromBase10("130903255182996722426771613606077755295583329135067340152947172868415809027537376306193179624298874215608270802054347609836776473930072411958753044562214537013874103802006369634761074377213995983876788718033850153719421695468704276694983032644416930879093914927146648402139231293035971427838068945045019075433"), + fromBase10("109348945610485453577574767652527472924289229538286649661240938988020367005475727988253438647560958573506159449538793540472829815903949343191091817779240101054552748665267574271163617694640513549693841337820602726596756351006149518830932261246698766355347898158548465400674856021497190430791824869615170301029"), + }, + } + test2048Key.Precompute() + test3072Key = &rsa.PrivateKey{ + PublicKey: rsa.PublicKey{ + N: fromBase10("4799422180968749215324244710281712119910779465109490663934897082847293004098645365195947978124390029272750644394844443980065532911010718425428791498896288210928474905407341584968381379157418577471272697781778686372450913810019702928839200328075568223462554606149618941566459398862673532997592879359280754226882565483298027678735544377401276021471356093819491755877827249763065753555051973844057308627201762456191918852016986546071426986328720794061622370410645440235373576002278045257207695462423797272017386006110722769072206022723167102083033531426777518054025826800254337147514768377949097720074878744769255210076910190151785807232805749219196645305822228090875616900385866236956058984170647782567907618713309775105943700661530312800231153745705977436176908325539234432407050398510090070342851489496464612052853185583222422124535243967989533830816012180864309784486694786581956050902756173889941244024888811572094961378021"), + E: 65537, + }, + D: fromBase10("4068124900056380177006532461065648259352178312499768312132802353620854992915205894105621345694615110794369150964768050224096623567443679436821868510233726084582567244003894477723706516831312989564775159596496449435830457803384416702014837685962523313266832032687145914871879794104404800823188153886925022171560391765913739346955738372354826804228989767120353182641396181570533678315099748218734875742705419933837638038793286534641711407564379950728858267828581787483317040753987167237461567332386718574803231955771633274184646232632371006762852623964054645811527580417392163873708539175349637050049959954373319861427407953413018816604365474462455009323937599275324390953644555294418021286807661559165324810415569396577697316798600308544755741549699523972971375304826663847015905713096287495342701286542193782001358775773848824496321550110946106870685499577993864871847542645561943034990484973293461948058147956373115641615329"), + Primes: []*big.Int{ + fromBase10("2378529069722721185825622840841310902793949682948530343491428052737890236476884657507685118578733560141370511507721598189068683665232991988491561624429938984370132428230072355214627085652359350722926394699707232921674771664421591347888367477300909202851476404132163673865768760147403525700174918450753162242834161458300343282159799476695001920226357456953682236859505243928716782707623075239350380352265954107362618991716602898266999700316937680986690964564264877"), + fromBase10("2017811025336026464312837780072272578817919741496395062543647660689775637351085991504709917848745137013798005682591633910555599626950744674459976829106750083386168859581016361317479081273480343110649405858059581933773354781034946787147300862495438979895430001323443224335618577322449133208754541656374335100929456885995320929464029817626916719434010943205170760536768893924932021302887114400922813817969176636993508191950649313115712159241971065134077636674146073"), + }, + } + test3072Key.Precompute() + test4096Key = &rsa.PrivateKey{ + PublicKey: rsa.PublicKey{ + N: fromBase10("633335480064287130853997429184971616419051348693342219741748040433588285601270210251206421401040394238592139790962887290698043839174341843721930134010306454716566698330215646704263665452264344664385995704186692432827662862845900348526672531755932642433662686500295989783595767573119607065791980381547677840410600100715146047382485989885183858757974681241303484641390718944520330953604501686666386926996348457928415093305041429178744778762826377713889019740060910363468343855830206640274442887621960581569183233822878661711798998132931623726434336448716605363514220760343097572198620479297583609779817750646169845195672483600293522186340560792255595411601450766002877850696008003794520089358819042318331840490155176019070646738739580486357084733208876620846449161909966690602374519398451042362690200166144326179405976024265116931974936425064291406950542193873313447617169603706868220189295654943247311295475722243471700112334609817776430552541319671117235957754556272646031356496763094955985615723596562217985372503002989591679252640940571608314743271809251568670314461039035793703429977801961867815257832671786542212589906513979094156334941265621017752516999186481477500481433634914622735206243841674973785078408289183000133399026553"), + E: 65537, + }, + D: fromBase10("439373650557744155078930178606343279553665694488479749802070836418412881168612407941793966086633543867614175621952769177088930851151267623886678906158545451731745754402575409204816390946376103491325109185445659065122640946673660760274557781540431107937331701243915001777636528502669576801704352961341634812275635811512806966908648671988644114352046582195051714797831307925775689566757438907578527366568747104508496278929566712224252103563340770696548181508180254674236716995730292431858611476396845443056967589437890065663497768422598977743046882539288481002449571403783500529740184608873520856954837631427724158592309018382711485601884461168736465751756282510065053161144027097169985941910909130083273691945578478173708396726266170473745329617793866669307716920992380350270584929908460462802627239204245339385636926433446418108504614031393494119344916828744888432279343816084433424594432427362258172264834429525166677273382617457205387388293888430391895615438030066428745187333897518037597413369705720436392869403948934993623418405908467147848576977008003556716087129242155836114780890054057743164411952731290520995017097151300091841286806603044227906213832083363876549637037625314539090155417589796428888619937329669464810549362433"), + Primes: []*big.Int{ + fromBase10("25745433817240673759910623230144796182285844101796353869339294232644316274580053211056707671663014355388701931204078502829809738396303142990312095225333440050808647355535878394534263839500592870406002873182360027755750148248672968563366185348499498613479490545488025779331426515670185366021612402246813511722553210128074701620113404560399242413747318161403908617342170447610792422053460359960010544593668037305465806912471260799852789913123044326555978680190904164976511331681163576833618899773550873682147782263100803907156362439021929408298804955194748640633152519828940133338948391986823456836070708197320166146761"), + fromBase10("24599914864909676687852658457515103765368967514652318497893275892114442089314173678877914038802355565271545910572804267918959612739009937926962653912943833939518967731764560204997062096919833970670512726396663920955497151415639902788974842698619579886297871162402643104696160155894685518587660015182381685605752989716946154299190561137541792784125356553411300817844325739404126956793095254412123887617931225840421856505925283322918693259047428656823141903489964287619982295891439430302405252447010728112098326033634688757933930065610737780413018498561434074501822951716586796047404555397992425143397497639322075233073"), + }, + } + test4096Key.Precompute() +} + +func fromBase10(base10 string) *big.Int { + i, ok := new(big.Int).SetString(base10, 10) + if !ok { + panic("bad number: " + base10) + } + return i +} + +type certKeyPair struct { + Certificate *x509.Certificate + PrivateKey *crypto.PrivateKey +} + +func createTestCertificate(sigAlg x509.SignatureAlgorithm) (certKeyPair, error) { + signer, err := createTestCertificateByIssuer("Eddard Stark", nil, sigAlg, true) + if err != nil { + return certKeyPair{}, err + } + pair, err := createTestCertificateByIssuer("Jon Snow", signer, sigAlg, false) + if err != nil { + return certKeyPair{}, err + } + return *pair, nil +} + +func createTestCertificateByIssuer(name string, issuer *certKeyPair, sigAlg x509.SignatureAlgorithm, isCA bool) (*certKeyPair, error) { + var ( + err error + priv crypto.PrivateKey + derCert []byte + issuerCert *x509.Certificate + issuerKey crypto.PrivateKey + ) + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 32) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, err + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: name, + Organization: []string{"Acme Co"}, + }, + NotBefore: time.Now().Add(-1 *time.Second), + NotAfter: time.Now().AddDate(1, 0, 0), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection}, + } + if issuer != nil { + issuerCert = issuer.Certificate + issuerKey = *issuer.PrivateKey + } + switch sigAlg { + case x509.SHA1WithRSA: + priv = test1024Key + switch issuerKey.(type) { + case *rsa.PrivateKey: + template.SignatureAlgorithm = x509.SHA1WithRSA + case *ecdsa.PrivateKey: + template.SignatureAlgorithm = x509.ECDSAWithSHA1 + case *dsa.PrivateKey: + template.SignatureAlgorithm = x509.DSAWithSHA1 + } + case x509.SHA256WithRSA: + priv = test2048Key + switch issuerKey.(type) { + case *rsa.PrivateKey: + template.SignatureAlgorithm = x509.SHA256WithRSA + case *ecdsa.PrivateKey: + template.SignatureAlgorithm = x509.ECDSAWithSHA256 + case *dsa.PrivateKey: + template.SignatureAlgorithm = x509.DSAWithSHA256 + } + case x509.SHA384WithRSA: + priv = test3072Key + switch issuerKey.(type) { + case *rsa.PrivateKey: + template.SignatureAlgorithm = x509.SHA384WithRSA + case *ecdsa.PrivateKey: + template.SignatureAlgorithm = x509.ECDSAWithSHA384 + case *dsa.PrivateKey: + template.SignatureAlgorithm = x509.DSAWithSHA256 + } + case x509.SHA512WithRSA: + priv = test4096Key + switch issuerKey.(type) { + case *rsa.PrivateKey: + template.SignatureAlgorithm = x509.SHA512WithRSA + case *ecdsa.PrivateKey: + template.SignatureAlgorithm = x509.ECDSAWithSHA512 + case *dsa.PrivateKey: + template.SignatureAlgorithm = x509.DSAWithSHA256 + } + case x509.ECDSAWithSHA1: + priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, err + } + switch issuerKey.(type) { + case *rsa.PrivateKey: + template.SignatureAlgorithm = x509.SHA1WithRSA + case *ecdsa.PrivateKey: + template.SignatureAlgorithm = x509.ECDSAWithSHA1 + case *dsa.PrivateKey: + template.SignatureAlgorithm = x509.DSAWithSHA1 + } + case x509.ECDSAWithSHA256: + priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, err + } + switch issuerKey.(type) { + case *rsa.PrivateKey: + template.SignatureAlgorithm = x509.SHA256WithRSA + case *ecdsa.PrivateKey: + template.SignatureAlgorithm = x509.ECDSAWithSHA256 + case *dsa.PrivateKey: + template.SignatureAlgorithm = x509.DSAWithSHA256 + } + case x509.ECDSAWithSHA384: + priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + if err != nil { + return nil, err + } + switch issuerKey.(type) { + case *rsa.PrivateKey: + template.SignatureAlgorithm = x509.SHA384WithRSA + case *ecdsa.PrivateKey: + template.SignatureAlgorithm = x509.ECDSAWithSHA384 + case *dsa.PrivateKey: + template.SignatureAlgorithm = x509.DSAWithSHA256 + } + case x509.ECDSAWithSHA512: + priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + return nil, err + } + switch issuerKey.(type) { + case *rsa.PrivateKey: + template.SignatureAlgorithm = x509.SHA512WithRSA + case *ecdsa.PrivateKey: + template.SignatureAlgorithm = x509.ECDSAWithSHA512 + case *dsa.PrivateKey: + template.SignatureAlgorithm = x509.DSAWithSHA256 + } + case x509.DSAWithSHA1: + var dsaPriv dsa.PrivateKey + params := &dsaPriv.Parameters + err = dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160) + if err != nil { + return nil, err + } + err = dsa.GenerateKey(&dsaPriv, rand.Reader) + if err != nil { + return nil, err + } + switch issuerKey.(type) { + case *rsa.PrivateKey: + template.SignatureAlgorithm = x509.SHA1WithRSA + case *ecdsa.PrivateKey: + template.SignatureAlgorithm = x509.ECDSAWithSHA1 + case *dsa.PrivateKey: + template.SignatureAlgorithm = x509.DSAWithSHA1 + } + priv = &dsaPriv + } + if isCA { + template.IsCA = true + template.KeyUsage |= x509.KeyUsageCertSign + template.BasicConstraintsValid = true + } + if issuer == nil { + // no issuer given,make this a self-signed root cert + issuerCert = &template + issuerKey = priv + } + + log.Println("creating cert", name, "issued by", issuerCert.Subject.CommonName, "with sigalg", sigAlg) + switch priv.(type) { + case *rsa.PrivateKey: + switch issuerKey.(type) { + case *rsa.PrivateKey: + derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*rsa.PrivateKey).Public(), issuerKey.(*rsa.PrivateKey)) + case *ecdsa.PrivateKey: + derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*rsa.PrivateKey).Public(), issuerKey.(*ecdsa.PrivateKey)) + case *dsa.PrivateKey: + derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*rsa.PrivateKey).Public(), issuerKey.(*dsa.PrivateKey)) + } + case *ecdsa.PrivateKey: + switch issuerKey.(type) { + case *rsa.PrivateKey: + derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*ecdsa.PrivateKey).Public(), issuerKey.(*rsa.PrivateKey)) + case *ecdsa.PrivateKey: + derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*ecdsa.PrivateKey).Public(), issuerKey.(*ecdsa.PrivateKey)) + case *dsa.PrivateKey: + derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*ecdsa.PrivateKey).Public(), issuerKey.(*dsa.PrivateKey)) + } + case *dsa.PrivateKey: + pub := &priv.(*dsa.PrivateKey).PublicKey + switch issuerKey := issuerKey.(type) { + case *rsa.PrivateKey: + derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, pub, issuerKey) + case *ecdsa.PrivateKey: + derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*dsa.PublicKey), issuerKey) + case *dsa.PrivateKey: + derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*dsa.PublicKey), issuerKey) + } + } + if err != nil { + return nil, err + } + if len(derCert) == 0 { + return nil, fmt.Errorf("no certificate created, probably due to wrong keys. types were %T and %T", priv, issuerKey) + } + cert, err := x509.ParseCertificate(derCert) + if err != nil { + return nil, err + } + pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) + return &certKeyPair{ + Certificate: cert, + PrivateKey: &priv, + }, nil +} + +type TestFixture struct { + Input []byte + Certificate *x509.Certificate + PrivateKey *rsa.PrivateKey +} + +func UnmarshalTestFixture(testPEMBlock string) TestFixture { + var result TestFixture + var derBlock *pem.Block + var pemBlock = []byte(testPEMBlock) + for { + derBlock, pemBlock = pem.Decode(pemBlock) + if derBlock == nil { + break + } + switch derBlock.Type { + case "PKCS7": + result.Input = derBlock.Bytes + case "CERTIFICATE": + result.Certificate, _ = x509.ParseCertificate(derBlock.Bytes) + case "PRIVATE KEY": + result.PrivateKey, _ = x509.ParsePKCS1PrivateKey(derBlock.Bytes) + } + } + + return result +} diff --git a/scep/pkcs7/sign.go b/scep/pkcs7/sign.go new file mode 100644 index 00000000..addd7638 --- /dev/null +++ b/scep/pkcs7/sign.go @@ -0,0 +1,429 @@ +package pkcs7 + +import ( + "bytes" + "crypto" + "crypto/dsa" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "fmt" + "math/big" + "time" +) + +// SignedData is an opaque data structure for creating signed data payloads +type SignedData struct { + sd signedData + certs []*x509.Certificate + data, messageDigest []byte + digestOid asn1.ObjectIdentifier + encryptionOid asn1.ObjectIdentifier +} + +// NewSignedData takes data and initializes a PKCS7 SignedData struct that is +// ready to be signed via AddSigner. The digest algorithm is set to SHA1 by default +// and can be changed by calling SetDigestAlgorithm. +func NewSignedData(data []byte) (*SignedData, error) { + content, err := asn1.Marshal(data) + if err != nil { + return nil, err + } + ci := contentInfo{ + ContentType: OIDData, + Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true}, + } + sd := signedData{ + ContentInfo: ci, + Version: 1, + } + return &SignedData{sd: sd, data: data, digestOid: OIDDigestAlgorithmSHA1}, nil +} + +// SignerInfoConfig are optional values to include when adding a signer +type SignerInfoConfig struct { + ExtraSignedAttributes []Attribute + ExtraUnsignedAttributes []Attribute +} + +type signedData struct { + Version int `asn1:"default:1"` + DigestAlgorithmIdentifiers []pkix.AlgorithmIdentifier `asn1:"set"` + ContentInfo contentInfo + Certificates rawCertificates `asn1:"optional,tag:0"` + CRLs []pkix.CertificateList `asn1:"optional,tag:1"` + SignerInfos []signerInfo `asn1:"set"` +} + +type signerInfo struct { + Version int `asn1:"default:1"` + IssuerAndSerialNumber issuerAndSerial + DigestAlgorithm pkix.AlgorithmIdentifier + AuthenticatedAttributes []attribute `asn1:"optional,omitempty,tag:0"` + DigestEncryptionAlgorithm pkix.AlgorithmIdentifier + EncryptedDigest []byte + UnauthenticatedAttributes []attribute `asn1:"optional,omitempty,tag:1"` +} + +type attribute struct { + Type asn1.ObjectIdentifier + Value asn1.RawValue `asn1:"set"` +} + +func marshalAttributes(attrs []attribute) ([]byte, error) { + encodedAttributes, err := asn1.Marshal(struct { + A []attribute `asn1:"set"` + }{A: attrs}) + if err != nil { + return nil, err + } + + // Remove the leading sequence octets + var raw asn1.RawValue + asn1.Unmarshal(encodedAttributes, &raw) + return raw.Bytes, nil +} + +type rawCertificates struct { + Raw asn1.RawContent +} + +type issuerAndSerial struct { + IssuerName asn1.RawValue + SerialNumber *big.Int +} + +// SetDigestAlgorithm sets the digest algorithm to be used in the signing process. +// +// This should be called before adding signers +func (sd *SignedData) SetDigestAlgorithm(d asn1.ObjectIdentifier) { + sd.digestOid = d +} + +// SetEncryptionAlgorithm sets the encryption algorithm to be used in the signing process. +// +// This should be called before adding signers +func (sd *SignedData) SetEncryptionAlgorithm(d asn1.ObjectIdentifier) { + sd.encryptionOid = d +} + +// AddSigner is a wrapper around AddSignerChain() that adds a signer without any parent. +func (sd *SignedData) AddSigner(ee *x509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error { + var parents []*x509.Certificate + return sd.AddSignerChain(ee, pkey, parents, config) +} + +// AddSignerChain signs attributes about the content and adds certificates +// and signers infos to the Signed Data. The certificate and private key +// of the end-entity signer are used to issue the signature, and any +// parent of that end-entity that need to be added to the list of +// certifications can be specified in the parents slice. +// +// The signature algorithm used to hash the data is the one of the end-entity +// certificate. +func (sd *SignedData) AddSignerChain(ee *x509.Certificate, pkey crypto.PrivateKey, parents []*x509.Certificate, config SignerInfoConfig) error { +// Following RFC 2315, 9.2 SignerInfo type, the distinguished name of +// the issuer of the end-entity signer is stored in the issuerAndSerialNumber +// section of the SignedData.SignerInfo, alongside the serial number of +// the end-entity. + var ias issuerAndSerial + ias.SerialNumber = ee.SerialNumber + if len(parents) == 0 { + // no parent, the issuer is the end-entity cert itself + ias.IssuerName = asn1.RawValue{FullBytes: ee.RawIssuer} + } else { + err := verifyPartialChain(ee, parents) + if err != nil { + return err + } + // the first parent is the issuer + ias.IssuerName = asn1.RawValue{FullBytes: parents[0].RawSubject} + } + sd.sd.DigestAlgorithmIdentifiers = append(sd.sd.DigestAlgorithmIdentifiers, + pkix.AlgorithmIdentifier{Algorithm: sd.digestOid}, + ) + hash, err := getHashForOID(sd.digestOid) + if err != nil { + return err + } + h := hash.New() + h.Write(sd.data) + sd.messageDigest = h.Sum(nil) + encryptionOid, err := getOIDForEncryptionAlgorithm(pkey, sd.digestOid) + if err != nil { + return err + } + attrs := &attributes{} + attrs.Add(OIDAttributeContentType, sd.sd.ContentInfo.ContentType) + attrs.Add(OIDAttributeMessageDigest, sd.messageDigest) + attrs.Add(OIDAttributeSigningTime, time.Now().UTC()) + for _, attr := range config.ExtraSignedAttributes { + attrs.Add(attr.Type, attr.Value) + } + finalAttrs, err := attrs.ForMarshalling() + if err != nil { + return err + } + unsignedAttrs := &attributes{} + for _, attr := range config.ExtraUnsignedAttributes { + unsignedAttrs.Add(attr.Type, attr.Value) + } + finalUnsignedAttrs, err := unsignedAttrs.ForMarshalling() + if err != nil { + return err + } + // create signature of signed attributes + signature, err := signAttributes(finalAttrs, pkey, hash) + if err != nil { + return err + } + signer := signerInfo{ + AuthenticatedAttributes: finalAttrs, + UnauthenticatedAttributes: finalUnsignedAttrs, + DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.digestOid}, + DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: encryptionOid}, + IssuerAndSerialNumber: ias, + EncryptedDigest: signature, + Version: 1, + } + sd.certs = append(sd.certs, ee) + if len(parents) > 0 { + sd.certs = append(sd.certs, parents...) + } + sd.sd.SignerInfos = append(sd.sd.SignerInfos, signer) + return nil +} + +// SignWithoutAttr issues a signature on the content of the pkcs7 SignedData. +// Unlike AddSigner/AddSignerChain, it calculates the digest on the data alone +// and does not include any signed attributes like timestamp and so on. +// +// This function is needed to sign old Android APKs, something you probably +// shouldn't do unless you're maintaining backward compatibility for old +// applications. +func (sd *SignedData) SignWithoutAttr(ee *x509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error { + var signature []byte + sd.sd.DigestAlgorithmIdentifiers = append(sd.sd.DigestAlgorithmIdentifiers, pkix.AlgorithmIdentifier{Algorithm: sd.digestOid}) + hash, err := getHashForOID(sd.digestOid) + if err != nil { + return err + } + h := hash.New() + h.Write(sd.data) + sd.messageDigest = h.Sum(nil) + switch pkey := pkey.(type) { + case *dsa.PrivateKey: + // dsa doesn't implement crypto.Signer so we make a special case + // https://github.com/golang/go/issues/27889 + r, s, err := dsa.Sign(rand.Reader, pkey, sd.messageDigest) + if err != nil { + return err + } + signature, err = asn1.Marshal(dsaSignature{r, s}) + if err != nil { + return err + } + default: + key, ok := pkey.(crypto.Signer) + if !ok { + return errors.New("pkcs7: private key does not implement crypto.Signer") + } + signature, err = key.Sign(rand.Reader, sd.messageDigest, hash) + if err != nil { + return err + } + } + var ias issuerAndSerial + ias.SerialNumber = ee.SerialNumber + // no parent, the issue is the end-entity cert itself + ias.IssuerName = asn1.RawValue{FullBytes: ee.RawIssuer} + if sd.encryptionOid == nil { + // if the encryption algorithm wasn't set by SetEncryptionAlgorithm, + // infer it from the digest algorithm + sd.encryptionOid, err = getOIDForEncryptionAlgorithm(pkey, sd.digestOid) + } + if err != nil { + return err + } + signer := signerInfo{ + DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.digestOid}, + DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.encryptionOid}, + IssuerAndSerialNumber: ias, + EncryptedDigest: signature, + Version: 1, + } + // create signature of signed attributes + sd.certs = append(sd.certs, ee) + sd.sd.SignerInfos = append(sd.sd.SignerInfos, signer) + return nil +} + +func (si *signerInfo) SetUnauthenticatedAttributes(extraUnsignedAttrs []Attribute) error { + unsignedAttrs := &attributes{} + for _, attr := range extraUnsignedAttrs { + unsignedAttrs.Add(attr.Type, attr.Value) + } + finalUnsignedAttrs, err := unsignedAttrs.ForMarshalling() + if err != nil { + return err + } + + si.UnauthenticatedAttributes = finalUnsignedAttrs + + return nil +} + +// AddCertificate adds the certificate to the payload. Useful for parent certificates +func (sd *SignedData) AddCertificate(cert *x509.Certificate) { + sd.certs = append(sd.certs, cert) +} + +// Detach removes content from the signed data struct to make it a detached signature. +// This must be called right before Finish() +func (sd *SignedData) Detach() { + sd.sd.ContentInfo = contentInfo{ContentType: OIDData} +} + +// GetSignedData returns the private Signed Data +func (sd *SignedData) GetSignedData() *signedData { + return &sd.sd +} + +// Finish marshals the content and its signers +func (sd *SignedData) Finish() ([]byte, error) { + sd.sd.Certificates = marshalCertificates(sd.certs) + inner, err := asn1.Marshal(sd.sd) + if err != nil { + return nil, err + } + outer := contentInfo{ + ContentType: OIDSignedData, + Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: inner, IsCompound: true}, + } + return asn1.Marshal(outer) +} + +// RemoveAuthenticatedAttributes removes authenticated attributes from signedData +// similar to OpenSSL's PKCS7_NOATTR or -noattr flags +func (sd *SignedData) RemoveAuthenticatedAttributes() { + for i := range sd.sd.SignerInfos { + sd.sd.SignerInfos[i].AuthenticatedAttributes = nil + } +} + +// RemoveUnauthenticatedAttributes removes unauthenticated attributes from signedData +func (sd *SignedData) RemoveUnauthenticatedAttributes() { + for i := range sd.sd.SignerInfos { + sd.sd.SignerInfos[i].UnauthenticatedAttributes = nil + } +} + +// verifyPartialChain checks that a given cert is issued by the first parent in the list, +// then continue down the path. It doesn't require the last parent to be a root CA, +// or to be trusted in any truststore. It simply verifies that the chain provided, albeit +// partial, makes sense. +func verifyPartialChain(cert *x509.Certificate, parents []*x509.Certificate) error { + if len(parents) == 0 { + return fmt.Errorf("pkcs7: zero parents provided to verify the signature of certificate %q", cert.Subject.CommonName) + } + err := cert.CheckSignatureFrom(parents[0]) + if err != nil { + return fmt.Errorf("pkcs7: certificate signature from parent is invalid: %v", err) + } + if len(parents) == 1 { + // there is no more parent to check, return + return nil + } + return verifyPartialChain(parents[0], parents[1:]) +} + +func cert2issuerAndSerial(cert *x509.Certificate) (issuerAndSerial, error) { + var ias issuerAndSerial + // The issuer RDNSequence has to match exactly the sequence in the certificate + // We cannot use cert.Issuer.ToRDNSequence() here since it mangles the sequence + ias.IssuerName = asn1.RawValue{FullBytes: cert.RawIssuer} + ias.SerialNumber = cert.SerialNumber + + return ias, nil +} + +// signs the DER encoded form of the attributes with the private key +func signAttributes(attrs []attribute, pkey crypto.PrivateKey, digestAlg crypto.Hash) ([]byte, error) { + attrBytes, err := marshalAttributes(attrs) + if err != nil { + return nil, err + } + h := digestAlg.New() + h.Write(attrBytes) + hash := h.Sum(nil) + + // dsa doesn't implement crypto.Signer so we make a special case + // https://github.com/golang/go/issues/27889 + switch pkey := pkey.(type) { + case *dsa.PrivateKey: + r, s, err := dsa.Sign(rand.Reader, pkey, hash) + if err != nil { + return nil, err + } + return asn1.Marshal(dsaSignature{r, s}) + } + + key, ok := pkey.(crypto.Signer) + if !ok { + return nil, errors.New("pkcs7: private key does not implement crypto.Signer") + } + return key.Sign(rand.Reader, hash, digestAlg) +} + +type dsaSignature struct { + R, S *big.Int +} + +// concats and wraps the certificates in the RawValue structure +func marshalCertificates(certs []*x509.Certificate) rawCertificates { + var buf bytes.Buffer + for _, cert := range certs { + buf.Write(cert.Raw) + } + rawCerts, _ := marshalCertificateBytes(buf.Bytes()) + return rawCerts +} + +// Even though, the tag & length are stripped out during marshalling the +// RawContent, we have to encode it into the RawContent. If its missing, +// then `asn1.Marshal()` will strip out the certificate wrapper instead. +func marshalCertificateBytes(certs []byte) (rawCertificates, error) { + var val = asn1.RawValue{Bytes: certs, Class: 2, Tag: 0, IsCompound: true} + b, err := asn1.Marshal(val) + if err != nil { + return rawCertificates{}, err + } + return rawCertificates{Raw: b}, nil +} + +// DegenerateCertificate creates a signed data structure containing only the +// provided certificate or certificate chain. +func DegenerateCertificate(cert []byte) ([]byte, error) { + rawCert, err := marshalCertificateBytes(cert) + if err != nil { + return nil, err + } + emptyContent := contentInfo{ContentType: OIDData} + sd := signedData{ + Version: 1, + ContentInfo: emptyContent, + Certificates: rawCert, + CRLs: []pkix.CertificateList{}, + } + content, err := asn1.Marshal(sd) + if err != nil { + return nil, err + } + signedContent := contentInfo{ + ContentType: OIDSignedData, + Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true}, + } + return asn1.Marshal(signedContent) +} diff --git a/scep/pkcs7/sign_test.go b/scep/pkcs7/sign_test.go new file mode 100644 index 00000000..0ba6324d --- /dev/null +++ b/scep/pkcs7/sign_test.go @@ -0,0 +1,266 @@ +package pkcs7 + +import ( + "bytes" + "crypto/dsa" + "crypto/x509" + "encoding/asn1" + "encoding/pem" + "fmt" + "io/ioutil" + "log" + "math/big" + "os" + "os/exec" + "testing" +) + +func TestSign(t *testing.T) { + content := []byte("Hello World") + sigalgs := []x509.SignatureAlgorithm{ + x509.SHA1WithRSA, + x509.SHA256WithRSA, + x509.SHA512WithRSA, + x509.ECDSAWithSHA1, + x509.ECDSAWithSHA256, + x509.ECDSAWithSHA384, + x509.ECDSAWithSHA512, + } + for _, sigalgroot := range sigalgs { + rootCert, err := createTestCertificateByIssuer("PKCS7 Test Root CA", nil, sigalgroot, true) + if err != nil { + t.Fatalf("test %s: cannot generate root cert: %s", sigalgroot, err) + } + truststore := x509.NewCertPool() + truststore.AddCert(rootCert.Certificate) + for _, sigalginter := range sigalgs { + interCert, err := createTestCertificateByIssuer("PKCS7 Test Intermediate Cert", rootCert, sigalginter, true) + if err != nil { + t.Fatalf("test %s/%s: cannot generate intermediate cert: %s", sigalgroot, sigalginter, err) + } + var parents []*x509.Certificate + parents = append(parents, interCert.Certificate) + for _, sigalgsigner := range sigalgs { + signerCert, err := createTestCertificateByIssuer("PKCS7 Test Signer Cert", interCert, sigalgsigner, false) + if err != nil { + t.Fatalf("test %s/%s/%s: cannot generate signer cert: %s", sigalgroot, sigalginter, sigalgsigner, err) + } + for _, testDetach := range []bool{false, true} { + log.Printf("test %s/%s/%s detached %t\n", sigalgroot, sigalginter, sigalgsigner, testDetach) + toBeSigned, err := NewSignedData(content) + if err != nil { + t.Fatalf("test %s/%s/%s: cannot initialize signed data: %s", sigalgroot, sigalginter, sigalgsigner, err) + } + + // Set the digest to match the end entity cert + signerDigest, _ := getDigestOIDForSignatureAlgorithm(signerCert.Certificate.SignatureAlgorithm) + toBeSigned.SetDigestAlgorithm(signerDigest) + + if err := toBeSigned.AddSignerChain(signerCert.Certificate, *signerCert.PrivateKey, parents, SignerInfoConfig{}); err != nil { + t.Fatalf("test %s/%s/%s: cannot add signer: %s", sigalgroot, sigalginter, sigalgsigner, err) + } + if testDetach { + toBeSigned.Detach() + } + signed, err := toBeSigned.Finish() + if err != nil { + t.Fatalf("test %s/%s/%s: cannot finish signing data: %s", sigalgroot, sigalginter, sigalgsigner, err) + } + pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: signed}) + p7, err := Parse(signed) + if err != nil { + t.Fatalf("test %s/%s/%s: cannot parse signed data: %s", sigalgroot, sigalginter, sigalgsigner, err) + } + if testDetach { + p7.Content = content + } + if !bytes.Equal(content, p7.Content) { + t.Errorf("test %s/%s/%s: content was not found in the parsed data:\n\tExpected: %s\n\tActual: %s", sigalgroot, sigalginter, sigalgsigner, content, p7.Content) + } + if err := p7.VerifyWithChain(truststore); err != nil { + t.Errorf("test %s/%s/%s: cannot verify signed data: %s", sigalgroot, sigalginter, sigalgsigner, err) + } + if !signerDigest.Equal(p7.Signers[0].DigestAlgorithm.Algorithm) { + t.Errorf("test %s/%s/%s: expected digest algorithm %q but got %q", + sigalgroot, sigalginter, sigalgsigner, signerDigest, p7.Signers[0].DigestAlgorithm.Algorithm) + } + } + } + } + } +} + +func TestDSASignAndVerifyWithOpenSSL(t *testing.T) { + content := []byte("Hello World") + // write the content to a temp file + tmpContentFile, err := ioutil.TempFile("", "TestDSASignAndVerifyWithOpenSSL_content") + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(tmpContentFile.Name(), content, 0755) + + block, _ := pem.Decode([]byte(dsaPublicCert)) + if block == nil { + t.Fatal("failed to parse certificate PEM") + } + signerCert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + t.Fatal("failed to parse certificate: " + err.Error()) + } + + // write the signer cert to a temp file + tmpSignerCertFile, err := ioutil.TempFile("", "TestDSASignAndVerifyWithOpenSSL_signer") + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(tmpSignerCertFile.Name(), dsaPublicCert, 0755) + + priv := dsa.PrivateKey{ + PublicKey: dsa.PublicKey{Parameters: dsa.Parameters{P: fromHex("fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80b6512669455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801d346ff26660b76b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150f04fb83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c7"), + Q: fromHex("9760508F15230BCCB292B982A2EB840BF0581CF5"), + G: fromHex("F7E1A085D69B3DDECBBCAB5C36B857B97994AFBBFA3AEA82F9574C0B3D0782675159578EBAD4594FE67107108180B449167123E84C281613B7CF09328CC8A6E13C167A8B547C8D28E0A3AE1E2BB3A675916EA37F0BFA213562F1FB627A01243BCCA4F1BEA8519089A883DFE15AE59F06928B665E807B552564014C3BFECF492A"), + }, + }, + X: fromHex("7D6E1A3DD4019FD809669D8AB8DA73807CEF7EC1"), + } + toBeSigned, err := NewSignedData(content) + if err != nil { + t.Fatalf("test case: cannot initialize signed data: %s", err) + } + if err := toBeSigned.SignWithoutAttr(signerCert, &priv, SignerInfoConfig{}); err != nil { + t.Fatalf("Cannot add signer: %s", err) + } + toBeSigned.Detach() + signed, err := toBeSigned.Finish() + if err != nil { + t.Fatalf("test case: cannot finish signing data: %s", err) + } + + // write the signature to a temp file + tmpSignatureFile, err := ioutil.TempFile("", "TestDSASignAndVerifyWithOpenSSL_signature") + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(tmpSignatureFile.Name(), pem.EncodeToMemory(&pem.Block{Type: "PKCS7", Bytes: signed}), 0755) + + // call openssl to verify the signature on the content using the root + opensslCMD := exec.Command("openssl", "smime", "-verify", "-noverify", + "-in", tmpSignatureFile.Name(), "-inform", "PEM", + "-content", tmpContentFile.Name()) + out, err := opensslCMD.CombinedOutput() + if err != nil { + t.Fatalf("test case: openssl command failed with %s: %s", err, out) + } + os.Remove(tmpSignatureFile.Name()) // clean up + os.Remove(tmpContentFile.Name()) // clean up + os.Remove(tmpSignerCertFile.Name()) // clean up +} + +func ExampleSignedData() { + // generate a signing cert or load a key pair + cert, err := createTestCertificate(x509.SHA256WithRSA) + if err != nil { + fmt.Printf("Cannot create test certificates: %s", err) + } + + // Initialize a SignedData struct with content to be signed + signedData, err := NewSignedData([]byte("Example data to be signed")) + if err != nil { + fmt.Printf("Cannot initialize signed data: %s", err) + } + + // Add the signing cert and private key + if err := signedData.AddSigner(cert.Certificate, cert.PrivateKey, SignerInfoConfig{}); err != nil { + fmt.Printf("Cannot add signer: %s", err) + } + + // Call Detach() is you want to remove content from the signature + // and generate an S/MIME detached signature + signedData.Detach() + + // Finish() to obtain the signature bytes + detachedSignature, err := signedData.Finish() + if err != nil { + fmt.Printf("Cannot finish signing data: %s", err) + } + pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: detachedSignature}) +} + +func TestUnmarshalSignedAttribute(t *testing.T) { + cert, err := createTestCertificate(x509.SHA512WithRSA) + if err != nil { + t.Fatal(err) + } + content := []byte("Hello World") + toBeSigned, err := NewSignedData(content) + if err != nil { + t.Fatalf("Cannot initialize signed data: %s", err) + } + oidTest := asn1.ObjectIdentifier{2, 3, 4, 5, 6, 7} + testValue := "TestValue" + if err := toBeSigned.AddSigner(cert.Certificate, *cert.PrivateKey, SignerInfoConfig{ + ExtraSignedAttributes: []Attribute{Attribute{Type: oidTest, Value: testValue}}, + }); err != nil { + t.Fatalf("Cannot add signer: %s", err) + } + signed, err := toBeSigned.Finish() + if err != nil { + t.Fatalf("Cannot finish signing data: %s", err) + } + p7, err := Parse(signed) + if err != nil { + t.Fatalf("Cannot parse signed data: %v", err) + } + var actual string + err = p7.UnmarshalSignedAttribute(oidTest, &actual) + if err != nil { + t.Fatalf("Cannot unmarshal test value: %s", err) + } + if testValue != actual { + t.Errorf("Attribute does not match test value\n\tExpected: %s\n\tActual: %s", testValue, actual) + } +} + +func TestDegenerateCertificate(t *testing.T) { + cert, err := createTestCertificate(x509.SHA1WithRSA) + if err != nil { + t.Fatal(err) + } + deg, err := DegenerateCertificate(cert.Certificate.Raw) + if err != nil { + t.Fatal(err) + } + testOpenSSLParse(t, deg) + pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: deg}) +} + +// writes the cert to a temporary file and tests that openssl can read it. +func testOpenSSLParse(t *testing.T, certBytes []byte) { + tmpCertFile, err := ioutil.TempFile("", "testCertificate") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpCertFile.Name()) // clean up + + if _, err := tmpCertFile.Write(certBytes); err != nil { + t.Fatal(err) + } + + opensslCMD := exec.Command("openssl", "pkcs7", "-inform", "der", "-in", tmpCertFile.Name()) + _, err = opensslCMD.Output() + if err != nil { + t.Fatal(err) + } + + if err := tmpCertFile.Close(); err != nil { + t.Fatal(err) + } + +} +func fromHex(s string) *big.Int { + result, ok := new(big.Int).SetString(s, 16) + if !ok { + panic(s) + } + return result +} diff --git a/scep/pkcs7/verify.go b/scep/pkcs7/verify.go new file mode 100644 index 00000000..c8ead236 --- /dev/null +++ b/scep/pkcs7/verify.go @@ -0,0 +1,264 @@ +package pkcs7 + +import ( + "crypto/subtle" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "fmt" + "time" +) + +// Verify is a wrapper around VerifyWithChain() that initializes an empty +// trust store, effectively disabling certificate verification when validating +// a signature. +func (p7 *PKCS7) Verify() (err error) { + return p7.VerifyWithChain(nil) +} + +// VerifyWithChain checks the signatures of a PKCS7 object. +// If truststore is not nil, it also verifies the chain of trust of the end-entity +// signer cert to one of the root in the truststore. +func (p7 *PKCS7) VerifyWithChain(truststore *x509.CertPool) (err error) { + if len(p7.Signers) == 0 { + return errors.New("pkcs7: Message has no signers") + } + for _, signer := range p7.Signers { + if err := verifySignature(p7, signer, truststore); err != nil { + return err + } + } + return nil +} + +func verifySignature(p7 *PKCS7, signer signerInfo, truststore *x509.CertPool) (err error) { + signedData := p7.Content + ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber) + if ee == nil { + return errors.New("pkcs7: No certificate for signer") + } + signingTime := time.Now().UTC() + if len(signer.AuthenticatedAttributes) > 0 { + // TODO(fullsailor): First check the content type match + var digest []byte + err := unmarshalAttribute(signer.AuthenticatedAttributes, OIDAttributeMessageDigest, &digest) + if err != nil { + return err + } + hash, err := getHashForOID(signer.DigestAlgorithm.Algorithm) + if err != nil { + return err + } + h := hash.New() + h.Write(p7.Content) + computed := h.Sum(nil) + if subtle.ConstantTimeCompare(digest, computed) != 1 { + return &MessageDigestMismatchError{ + ExpectedDigest: digest, + ActualDigest: computed, + } + } + signedData, err = marshalAttributes(signer.AuthenticatedAttributes) + if err != nil { + return err + } + err = unmarshalAttribute(signer.AuthenticatedAttributes, OIDAttributeSigningTime, &signingTime) + if err == nil { + // signing time found, performing validity check + if signingTime.After(ee.NotAfter) || signingTime.Before(ee.NotBefore) { + return fmt.Errorf("pkcs7: signing time %q is outside of certificate validity %q to %q", + signingTime.Format(time.RFC3339), + ee.NotBefore.Format(time.RFC3339), + ee.NotBefore.Format(time.RFC3339)) + } + } + } + if truststore != nil { + _, err = verifyCertChain(ee, p7.Certificates, truststore, signingTime) + if err != nil { + return err + } + } + sigalg, err := getSignatureAlgorithm(signer.DigestEncryptionAlgorithm, signer.DigestAlgorithm) + if err != nil { + return err + } + return ee.CheckSignature(sigalg, signedData, signer.EncryptedDigest) +} + +// GetOnlySigner returns an x509.Certificate for the first signer of the signed +// data payload. If there are more or less than one signer, nil is returned +func (p7 *PKCS7) GetOnlySigner() *x509.Certificate { + if len(p7.Signers) != 1 { + return nil + } + signer := p7.Signers[0] + return getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber) +} + +// UnmarshalSignedAttribute decodes a single attribute from the signer info +func (p7 *PKCS7) UnmarshalSignedAttribute(attributeType asn1.ObjectIdentifier, out interface{}) error { + sd, ok := p7.raw.(signedData) + if !ok { + return errors.New("pkcs7: payload is not signedData content") + } + if len(sd.SignerInfos) < 1 { + return errors.New("pkcs7: payload has no signers") + } + attributes := sd.SignerInfos[0].AuthenticatedAttributes + return unmarshalAttribute(attributes, attributeType, out) +} + +func parseSignedData(data []byte) (*PKCS7, error) { + var sd signedData + asn1.Unmarshal(data, &sd) + certs, err := sd.Certificates.Parse() + if err != nil { + return nil, err + } + // fmt.Printf("--> Signed Data Version %d\n", sd.Version) + + var compound asn1.RawValue + var content unsignedData + + // The Content.Bytes maybe empty on PKI responses. + if len(sd.ContentInfo.Content.Bytes) > 0 { + if _, err := asn1.Unmarshal(sd.ContentInfo.Content.Bytes, &compound); err != nil { + return nil, err + } + } + // Compound octet string + if compound.IsCompound { + if compound.Tag == 4 { + if _, err = asn1.Unmarshal(compound.Bytes, &content); err != nil { + return nil, err + } + } else { + content = compound.Bytes + } + } else { + // assuming this is tag 04 + content = compound.Bytes + } + return &PKCS7{ + Content: content, + Certificates: certs, + CRLs: sd.CRLs, + Signers: sd.SignerInfos, + raw: sd}, nil +} + +// verifyCertChain takes an end-entity certs, a list of potential intermediates and a +// truststore, and built all potential chains between the EE and a trusted root. +// +// When verifying chains that may have expired, currentTime can be set to a past date +// to allow the verification to pass. If unset, currentTime is set to the current UTC time. +func verifyCertChain(ee *x509.Certificate, certs []*x509.Certificate, truststore *x509.CertPool, currentTime time.Time) (chains [][]*x509.Certificate, err error) { + intermediates := x509.NewCertPool() + for _, intermediate := range certs { + intermediates.AddCert(intermediate) + } + verifyOptions := x509.VerifyOptions{ + Roots: truststore, + Intermediates: intermediates, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, + CurrentTime: currentTime, + } + chains, err = ee.Verify(verifyOptions) + if err != nil { + return chains, fmt.Errorf("pkcs7: failed to verify certificate chain: %v", err) + } + return +} + +// MessageDigestMismatchError is returned when the signer data digest does not +// match the computed digest for the contained content +type MessageDigestMismatchError struct { + ExpectedDigest []byte + ActualDigest []byte +} + +func (err *MessageDigestMismatchError) Error() string { + return fmt.Sprintf("pkcs7: Message digest mismatch\n\tExpected: %X\n\tActual : %X", err.ExpectedDigest, err.ActualDigest) +} + +func getSignatureAlgorithm(digestEncryption, digest pkix.AlgorithmIdentifier) (x509.SignatureAlgorithm, error) { + switch { + case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA1): + return x509.ECDSAWithSHA1, nil + case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA256): + return x509.ECDSAWithSHA256, nil + case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA384): + return x509.ECDSAWithSHA384, nil + case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA512): + return x509.ECDSAWithSHA512, nil + case digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSA), + digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA1), + digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA256), + digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA384), + digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA512): + switch { + case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1): + return x509.SHA1WithRSA, nil + case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256): + return x509.SHA256WithRSA, nil + case digest.Algorithm.Equal(OIDDigestAlgorithmSHA384): + return x509.SHA384WithRSA, nil + case digest.Algorithm.Equal(OIDDigestAlgorithmSHA512): + return x509.SHA512WithRSA, nil + default: + return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q", + digest.Algorithm.String(), digestEncryption.Algorithm.String()) + } + case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmDSA), + digestEncryption.Algorithm.Equal(OIDDigestAlgorithmDSASHA1): + switch { + case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1): + return x509.DSAWithSHA1, nil + case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256): + return x509.DSAWithSHA256, nil + default: + return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q", + digest.Algorithm.String(), digestEncryption.Algorithm.String()) + } + case digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP256), + digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP384), + digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP521): + switch { + case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1): + return x509.ECDSAWithSHA1, nil + case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256): + return x509.ECDSAWithSHA256, nil + case digest.Algorithm.Equal(OIDDigestAlgorithmSHA384): + return x509.ECDSAWithSHA384, nil + case digest.Algorithm.Equal(OIDDigestAlgorithmSHA512): + return x509.ECDSAWithSHA512, nil + default: + return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q", + digest.Algorithm.String(), digestEncryption.Algorithm.String()) + } + default: + return -1, fmt.Errorf("pkcs7: unsupported algorithm %q", + digestEncryption.Algorithm.String()) + } +} + +func getCertFromCertsByIssuerAndSerial(certs []*x509.Certificate, ias issuerAndSerial) *x509.Certificate { + for _, cert := range certs { + if isCertMatchForIssuerAndSerial(cert, ias) { + return cert + } + } + return nil +} + +func unmarshalAttribute(attrs []attribute, attributeType asn1.ObjectIdentifier, out interface{}) error { + for _, attr := range attrs { + if attr.Type.Equal(attributeType) { + _, err := asn1.Unmarshal(attr.Value.Bytes, out) + return err + } + } + return errors.New("pkcs7: attribute type not in attributes") +} diff --git a/scep/pkcs7/verify_test.go b/scep/pkcs7/verify_test.go new file mode 100644 index 00000000..f80943b2 --- /dev/null +++ b/scep/pkcs7/verify_test.go @@ -0,0 +1,713 @@ +package pkcs7 + +import ( + "bytes" + "crypto/ecdsa" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "fmt" + "io/ioutil" + "os" + "os/exec" + "testing" + "time" +) + +func TestVerify(t *testing.T) { + fixture := UnmarshalTestFixture(SignedTestFixture) + p7, err := Parse(fixture.Input) + if err != nil { + t.Errorf("Parse encountered unexpected error: %v", err) + } + + if err := p7.Verify(); err != nil { + t.Errorf("Verify failed with error: %v", err) + } + expected := []byte("We the People") + if !bytes.Equal(p7.Content, expected) { + t.Errorf("Signed content does not match.\n\tExpected:%s\n\tActual:%s", expected, p7.Content) + + } +} + +var SignedTestFixture = ` +-----BEGIN PKCS7----- +MIIDVgYJKoZIhvcNAQcCoIIDRzCCA0MCAQExCTAHBgUrDgMCGjAcBgkqhkiG9w0B +BwGgDwQNV2UgdGhlIFBlb3BsZaCCAdkwggHVMIIBQKADAgECAgRpuDctMAsGCSqG +SIb3DQEBCzApMRAwDgYDVQQKEwdBY21lIENvMRUwEwYDVQQDEwxFZGRhcmQgU3Rh +cmswHhcNMTUwNTA2MDQyNDQ4WhcNMTYwNTA2MDQyNDQ4WjAlMRAwDgYDVQQKEwdB +Y21lIENvMREwDwYDVQQDEwhKb24gU25vdzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEAqr+tTF4mZP5rMwlXp1y+crRtFpuLXF1zvBZiYMfIvAHwo1ta8E1IcyEP +J1jIiKMcwbzeo6kAmZzIJRCTezq9jwXUsKbQTvcfOH9HmjUmXBRWFXZYoQs/OaaF +a45deHmwEeMQkuSWEtYiVKKZXtJOtflKIT3MryJEDiiItMkdybUCAwEAAaMSMBAw +DgYDVR0PAQH/BAQDAgCgMAsGCSqGSIb3DQEBCwOBgQDK1EweZWRL+f7Z+J0kVzY8 +zXptcBaV4Lf5wGZJLJVUgp33bpLNpT3yadS++XQJ+cvtW3wADQzBSTMduyOF8Zf+ +L7TjjrQ2+F2HbNbKUhBQKudxTfv9dJHdKbD+ngCCdQJYkIy2YexsoNG0C8nQkggy +axZd/J69xDVx6pui3Sj8sDGCATYwggEyAgEBMDEwKTEQMA4GA1UEChMHQWNtZSBD +bzEVMBMGA1UEAxMMRWRkYXJkIFN0YXJrAgRpuDctMAcGBSsOAwIaoGEwGAYJKoZI +hvcNAQkDMQsGCSqGSIb3DQEHATAgBgkqhkiG9w0BCQUxExcRMTUwNTA2MDAyNDQ4 +LTA0MDAwIwYJKoZIhvcNAQkEMRYEFG9D7gcTh9zfKiYNJ1lgB0yTh4sZMAsGCSqG +SIb3DQEBAQSBgFF3sGDU9PtXty/QMtpcFa35vvIOqmWQAIZt93XAskQOnBq4OloX +iL9Ct7t1m4pzjRm0o9nDkbaSLZe7HKASHdCqijroScGlI8M+alJ8drHSFv6ZIjnM +FIwIf0B2Lko6nh9/6mUXq7tbbIHa3Gd1JUVire/QFFtmgRXMbXYk8SIS +-----END PKCS7----- +-----BEGIN CERTIFICATE----- +MIIB1TCCAUCgAwIBAgIEabg3LTALBgkqhkiG9w0BAQswKTEQMA4GA1UEChMHQWNt +ZSBDbzEVMBMGA1UEAxMMRWRkYXJkIFN0YXJrMB4XDTE1MDUwNjA0MjQ0OFoXDTE2 +MDUwNjA0MjQ0OFowJTEQMA4GA1UEChMHQWNtZSBDbzERMA8GA1UEAxMISm9uIFNu +b3cwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKq/rUxeJmT+azMJV6dcvnK0 +bRabi1xdc7wWYmDHyLwB8KNbWvBNSHMhDydYyIijHMG83qOpAJmcyCUQk3s6vY8F +1LCm0E73Hzh/R5o1JlwUVhV2WKELPzmmhWuOXXh5sBHjEJLklhLWIlSimV7STrX5 +SiE9zK8iRA4oiLTJHcm1AgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIAoDALBgkqhkiG +9w0BAQsDgYEAytRMHmVkS/n+2fidJFc2PM16bXAWleC3+cBmSSyVVIKd926SzaU9 +8mnUvvl0CfnL7Vt8AA0MwUkzHbsjhfGX/i+04460Nvhdh2zWylIQUCrncU37/XSR +3Smw/p4AgnUCWJCMtmHsbKDRtAvJ0JIIMmsWXfyevcQ1ceqbot0o/LA= +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIICXgIBAAKBgQCqv61MXiZk/mszCVenXL5ytG0Wm4tcXXO8FmJgx8i8AfCjW1rw +TUhzIQ8nWMiIoxzBvN6jqQCZnMglEJN7Or2PBdSwptBO9x84f0eaNSZcFFYVdlih +Cz85poVrjl14ebAR4xCS5JYS1iJUople0k61+UohPcyvIkQOKIi0yR3JtQIDAQAB +AoGBAIPLCR9N+IKxodq11lNXEaUFwMHXc1zqwP8no+2hpz3+nVfplqqubEJ4/PJY +5AgbJoIfnxVhyBXJXu7E+aD/OPneKZrgp58YvHKgGvvPyJg2gpC/1Fh0vQB0HNpI +1ZzIZUl8ZTUtVgtnCBUOh5JGI4bFokAqrT//Uvcfd+idgxqBAkEA1ZbP/Kseld14 +qbWmgmU5GCVxsZRxgR1j4lG3UVjH36KXMtRTm1atAam1uw3OEGa6Y3ANjpU52FaB +Hep5rkk4FQJBAMynMo1L1uiN5GP+KYLEF5kKRxK+FLjXR0ywnMh+gpGcZDcOae+J ++t1gLoWBIESH/Xt639T7smuSfrZSA9V0EyECQA8cvZiWDvLxmaEAXkipmtGPjKzQ +4PsOtkuEFqFl07aKDYKmLUg3aMROWrJidqsIabWxbvQgsNgSvs38EiH3wkUCQQCg +ndxb7piVXb9RBwm3OoU2tE1BlXMX+sVXmAkEhd2dwDsaxrI3sHf1xGXem5AimQRF +JBOFyaCnMotGNioSHY5hAkEAxyXcNixQ2RpLXJTQZtwnbk0XDcbgB+fBgXnv/4f3 +BCvcu85DqJeJyQv44Oe1qsXEX9BfcQIOVaoep35RPlKi9g== +-----END PRIVATE KEY-----` + +func TestVerifyEC2(t *testing.T) { + fixture := UnmarshalTestFixture(EC2IdentityDocumentFixture) + p7, err := Parse(fixture.Input) + if err != nil { + t.Errorf("Parse encountered unexpected error: %v", err) + } + p7.Certificates = []*x509.Certificate{fixture.Certificate} + if err := p7.Verify(); err != nil { + t.Errorf("Verify failed with error: %v", err) + } +} + +var EC2IdentityDocumentFixture = ` +-----BEGIN PKCS7----- +MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAaCA +JIAEggGmewogICJwcml2YXRlSXAiIDogIjE3Mi4zMC4wLjI1MiIsCiAgImRldnBh +eVByb2R1Y3RDb2RlcyIgOiBudWxsLAogICJhdmFpbGFiaWxpdHlab25lIiA6ICJ1 +cy1lYXN0LTFhIiwKICAidmVyc2lvbiIgOiAiMjAxMC0wOC0zMSIsCiAgImluc3Rh +bmNlSWQiIDogImktZjc5ZmU1NmMiLAogICJiaWxsaW5nUHJvZHVjdHMiIDogbnVs +bCwKICAiaW5zdGFuY2VUeXBlIiA6ICJ0Mi5taWNybyIsCiAgImFjY291bnRJZCIg +OiAiMTIxNjU5MDE0MzM0IiwKICAiaW1hZ2VJZCIgOiAiYW1pLWZjZTNjNjk2IiwK +ICAicGVuZGluZ1RpbWUiIDogIjIwMTYtMDQtMDhUMDM6MDE6MzhaIiwKICAiYXJj +aGl0ZWN0dXJlIiA6ICJ4ODZfNjQiLAogICJrZXJuZWxJZCIgOiBudWxsLAogICJy +YW1kaXNrSWQiIDogbnVsbCwKICAicmVnaW9uIiA6ICJ1cy1lYXN0LTEiCn0AAAAA +AAAxggEYMIIBFAIBATBpMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5n +dG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2Vi +IFNlcnZpY2VzIExMQwIJAJa6SNnlXhpnMAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0B +CQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xNjA0MDgwMzAxNDRaMCMG +CSqGSIb3DQEJBDEWBBTuUc28eBXmImAautC+wOjqcFCBVjAJBgcqhkjOOAQDBC8w +LQIVAKA54NxGHWWCz5InboDmY/GHs33nAhQ6O/ZI86NwjA9Vz3RNMUJrUPU5tAAA +AAAAAA== +-----END PKCS7----- +-----BEGIN CERTIFICATE----- +MIIC7TCCAq0CCQCWukjZ5V4aZzAJBgcqhkjOOAQDMFwxCzAJBgNVBAYTAlVTMRkw +FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYD +VQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0xMjAxMDUxMjU2MTJaFw0z +ODAxMDUxMjU2MTJaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9u +IFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNl +cnZpY2VzIExMQzCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQCjkvcS2bb1VQ4yt/5e +ih5OO6kK/n1Lzllr7D8ZwtQP8fOEpp5E2ng+D6Ud1Z1gYipr58Kj3nssSNpI6bX3 +VyIQzK7wLclnd/YozqNNmgIyZecN7EglK9ITHJLP+x8FtUpt3QbyYXJdmVMegN6P +hviYt5JH/nYl4hh3Pa1HJdskgQIVALVJ3ER11+Ko4tP6nwvHwh6+ERYRAoGBAI1j +k+tkqMVHuAFcvAGKocTgsjJem6/5qomzJuKDmbJNu9Qxw3rAotXau8Qe+MBcJl/U +hhy1KHVpCGl9fueQ2s6IL0CaO/buycU1CiYQk40KNHCcHfNiZbdlx1E9rpUp7bnF +lRa2v1ntMX3caRVDdbtPEWmdxSCYsYFDk4mZrOLBA4GEAAKBgEbmeve5f8LIE/Gf +MNmP9CM5eovQOGx5ho8WqD+aTebs+k2tn92BBPqeZqpWRa5P/+jrdKml1qx4llHW +MXrs3IgIb6+hUIB+S8dz8/mmO0bpr76RoZVCXYab2CZedFut7qc3WUH9+EUAH5mw +vSeDCOUMYQR7R9LINYwouHIziqQYMAkGByqGSM44BAMDLwAwLAIUWXBlk40xTwSw +7HX32MxXYruse9ACFBNGmdX2ZBrVNGrN9N2f6ROk0k9K +-----END CERTIFICATE-----` + +func TestVerifyAppStore(t *testing.T) { + fixture := UnmarshalTestFixture(AppStoreReceiptFixture) + p7, err := Parse(fixture.Input) + if err != nil { + t.Errorf("Parse encountered unexpected error: %v", err) + } + if err := p7.Verify(); err != nil { + t.Errorf("Verify failed with error: %v", err) + } +} + +var AppStoreReceiptFixture = ` +-----BEGIN PKCS7----- +MIITtgYJKoZIhvcNAQcCoIITpzCCE6MCAQExCzAJBgUrDgMCGgUAMIIDVwYJKoZI +hvcNAQcBoIIDSASCA0QxggNAMAoCAQgCAQEEAhYAMAoCARQCAQEEAgwAMAsCAQEC +AQEEAwIBADALAgEDAgEBBAMMATEwCwIBCwIBAQQDAgEAMAsCAQ8CAQEEAwIBADAL +AgEQAgEBBAMCAQAwCwIBGQIBAQQDAgEDMAwCAQoCAQEEBBYCNCswDAIBDgIBAQQE +AgIAjTANAgENAgEBBAUCAwFgvTANAgETAgEBBAUMAzEuMDAOAgEJAgEBBAYCBFAy +NDcwGAIBAgIBAQQQDA5jb20uemhpaHUudGVzdDAYAgEEAgECBBCS+ZODNMHwT1Nz +gWYDXyWZMBsCAQACAQEEEwwRUHJvZHVjdGlvblNhbmRib3gwHAIBBQIBAQQU4nRh +YCEZx70Flzv7hvJRjJZckYIwHgIBDAIBAQQWFhQyMDE2LTA3LTIzVDA2OjIxOjEx +WjAeAgESAgEBBBYWFDIwMTMtMDgtMDFUMDc6MDA6MDBaMD0CAQYCAQEENbR21I+a +8+byMXo3NPRoDWQmSXQF2EcCeBoD4GaL//ZCRETp9rGFPSg1KekCP7Kr9HAqw09m +MEICAQcCAQEEOlVJozYYBdugybShbiiMsejDMNeCbZq6CrzGBwW6GBy+DGWxJI91 +Y3ouXN4TZUhuVvLvN1b0m5T3ggQwggFaAgERAgEBBIIBUDGCAUwwCwICBqwCAQEE +AhYAMAsCAgatAgEBBAIMADALAgIGsAIBAQQCFgAwCwICBrICAQEEAgwAMAsCAgaz +AgEBBAIMADALAgIGtAIBAQQCDAAwCwICBrUCAQEEAgwAMAsCAga2AgEBBAIMADAM +AgIGpQIBAQQDAgEBMAwCAgarAgEBBAMCAQEwDAICBq4CAQEEAwIBADAMAgIGrwIB +AQQDAgEAMAwCAgaxAgEBBAMCAQAwGwICBqcCAQEEEgwQMTAwMDAwMDIyNTMyNTkw +MTAbAgIGqQIBAQQSDBAxMDAwMDAwMjI1MzI1OTAxMB8CAgaoAgEBBBYWFDIwMTYt +MDctMjNUMDY6MjE6MTFaMB8CAgaqAgEBBBYWFDIwMTYtMDctMjNUMDY6MjE6MTFa +MCACAgamAgEBBBcMFWNvbS56aGlodS50ZXN0LnRlc3RfMaCCDmUwggV8MIIEZKAD +AgECAggO61eH554JjTANBgkqhkiG9w0BAQUFADCBljELMAkGA1UEBhMCVVMxEzAR +BgNVBAoMCkFwcGxlIEluYy4xLDAqBgNVBAsMI0FwcGxlIFdvcmxkd2lkZSBEZXZl +bG9wZXIgUmVsYXRpb25zMUQwQgYDVQQDDDtBcHBsZSBXb3JsZHdpZGUgRGV2ZWxv +cGVyIFJlbGF0aW9ucyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNTExMTMw +MjE1MDlaFw0yMzAyMDcyMTQ4NDdaMIGJMTcwNQYDVQQDDC5NYWMgQXBwIFN0b3Jl +IGFuZCBpVHVuZXMgU3RvcmUgUmVjZWlwdCBTaWduaW5nMSwwKgYDVQQLDCNBcHBs +ZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczETMBEGA1UECgwKQXBwbGUg +SW5jLjELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQClz4H9JaKBW9aH7SPaMxyO4iPApcQmyz3Gn+xKDVWG/6QC15fKOVRtfX+yVBid +xCxScY5ke4LOibpJ1gjltIhxzz9bRi7GxB24A6lYogQ+IXjV27fQjhKNg0xbKmg3 +k8LyvR7E0qEMSlhSqxLj7d0fmBWQNS3CzBLKjUiB91h4VGvojDE2H0oGDEdU8zeQ +uLKSiX1fpIVK4cCc4Lqku4KXY/Qrk8H9Pm/KwfU8qY9SGsAlCnYO3v6Z/v/Ca/Vb +XqxzUUkIVonMQ5DMjoEC0KCXtlyxoWlph5AQaCYmObgdEHOwCl3Fc9DfdjvYLdmI +HuPsB8/ijtDT+iZVge/iA0kjAgMBAAGjggHXMIIB0zA/BggrBgEFBQcBAQQzMDEw +LwYIKwYBBQUHMAGGI2h0dHA6Ly9vY3NwLmFwcGxlLmNvbS9vY3NwMDMtd3dkcjA0 +MB0GA1UdDgQWBBSRpJz8xHa3n6CK9E31jzZd7SsEhTAMBgNVHRMBAf8EAjAAMB8G +A1UdIwQYMBaAFIgnFwmpthhgi+zruvZHWcVSVKO3MIIBHgYDVR0gBIIBFTCCAREw +ggENBgoqhkiG92NkBQYBMIH+MIHDBggrBgEFBQcCAjCBtgyBs1JlbGlhbmNlIG9u +IHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5j +ZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25k +aXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0 +aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMDYGCCsGAQUFBwIBFipodHRwOi8vd3d3 +LmFwcGxlLmNvbS9jZXJ0aWZpY2F0ZWF1dGhvcml0eS8wDgYDVR0PAQH/BAQDAgeA +MBAGCiqGSIb3Y2QGCwEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQANphvTLj3jWysH +bkKWbNPojEMwgl/gXNGNvr0PvRr8JZLbjIXDgFnf4+LXLgUUrA3btrj+/DUufMut +F2uOfx/kd7mxZ5W0E16mGYZ2+FogledjjA9z/Ojtxh+umfhlSFyg4Cg6wBA3Lbmg +BDkfc7nIBf3y3n8aKipuKwH8oCBc2et9J6Yz+PWY4L5E27FMZ/xuCk/J4gao0pfz +p45rUaJahHVl0RYEYuPBX/UIqc9o2ZIAycGMs/iNAGS6WGDAfK+PdcppuVsq1h1o +bphC9UynNxmbzDscehlD86Ntv0hgBgw2kivs3hi1EdotI9CO/KBpnBcbnoB7OUdF +MGEvxxOoMIIEIjCCAwqgAwIBAgIIAd68xDltoBAwDQYJKoZIhvcNAQEFBQAwYjEL +MAkGA1UEBhMCVVMxEzARBgNVBAoTCkFwcGxlIEluYy4xJjAkBgNVBAsTHUFwcGxl +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRYwFAYDVQQDEw1BcHBsZSBSb290IENB +MB4XDTEzMDIwNzIxNDg0N1oXDTIzMDIwNzIxNDg0N1owgZYxCzAJBgNVBAYTAlVT +MRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3JsZHdpZGUg +RGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3aWRlIERl +dmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKOFSmy1aqyCQ5SOmM7uxfuH8mkbw0 +U3rOfGOAYXdkXqUHI7Y5/lAtFVZYcC1+xG7BSoU+L/DehBqhV8mvexj/avoVEkkV +CBmsqtsqMu2WY2hSFT2Miuy/axiV4AOsAX2XBWfODoWVN2rtCbauZ81RZJ/GXNG8 +V25nNYB2NqSHgW44j9grFU57Jdhav06DwY3Sk9UacbVgnJ0zTlX5ElgMhrgWDcHl +d0WNUEi6Ky3klIXh6MSdxmilsKP8Z35wugJZS3dCkTm59c3hTO/AO0iMpuUhXf1q +arunFjVg0uat80YpyejDi+l5wGphZxWy8P3laLxiX27Pmd3vG2P+kmWrAgMBAAGj +gaYwgaMwHQYDVR0OBBYEFIgnFwmpthhgi+zruvZHWcVSVKO3MA8GA1UdEwEB/wQF +MAMBAf8wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wLgYDVR0fBCcw +JTAjoCGgH4YdaHR0cDovL2NybC5hcHBsZS5jb20vcm9vdC5jcmwwDgYDVR0PAQH/ +BAQDAgGGMBAGCiqGSIb3Y2QGAgEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQBPz+9Z +viz1smwvj+4ThzLoBTWobot9yWkMudkXvHcs1Gfi/ZptOllc34MBvbKuKmFysa/N +w0Uwj6ODDc4dR7Txk4qjdJukw5hyhzs+r0ULklS5MruQGFNrCk4QttkdUGwhgAqJ +TleMa1s8Pab93vcNIx0LSiaHP7qRkkykGRIZbVf1eliHe2iK5IaMSuviSRSqpd1V +AKmuu0swruGgsbwpgOYJd+W+NKIByn/c4grmO7i77LpilfMFY0GCzQ87HUyVpNur ++cmV6U/kTecmmYHpvPm0KdIBembhLoz2IYrF+Hjhga6/05Cdqa3zr/04GpZnMBxR +pVzscYqCtGwPDBUfMIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQsw +CQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0Ew +HhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzET +MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne ++Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjcz +y8QPTc4UadHJGXL1XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQ +Z48ItCD3y6wsIG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCS +C7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINB +hzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIB +djAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9Bp +R5R2Cf70a40uQKb3R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/ +CF4wggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcC +ARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCB +thqBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFz +c3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJk +IHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5 +IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3 +DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizU +sZAS2L70c5vu0mQPy3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJ +fBdAVhEedNO3iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr +1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltk +wGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIq +xw8dtk2cXmPIS4AXUKqK1drk/NAJBzewdXUhMYIByzCCAccCAQEwgaMwgZYxCzAJ +BgNVBAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBX +b3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29y +bGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkCCA7rV4fnngmNMAkGBSsOAwIaBQAwDQYJKoZIhvcNAQEBBQAEggEAasPtnide +NWyfUtewW9OSgcQA8pW+5tWMR0469cBPZR84uJa0gyfmPspySvbNOAwnrwzZHYLa +ujOxZLip4DUw4F5s3QwUa3y4BXpF4J+NSn9XNvxNtnT/GcEQtCuFwgJ0o3F0ilhv +MTHrwiwyx/vr+uNDqlORK8lfK+1qNp+A/kzh8eszMrn4JSeTh9ZYxLHE56WkTQGD +VZXl0gKgxSOmDrcp1eQxdlymzrPv9U60wUJ0bkPfrU9qZj3mJrmrkQk61JTe3j6/ +QfjfFBG9JG2mUmYQP1KQ3SypGHzDW8vngvsGu//tNU0NFfOqQu4bYU4VpQl0nPtD +4B85NkrgvQsWAQ== +-----END PKCS7-----` + +func TestVerifyApkEcdsa(t *testing.T) { + fixture := UnmarshalTestFixture(ApkEcdsaFixture) + p7, err := Parse(fixture.Input) + if err != nil { + t.Errorf("Parse encountered unexpected error: %v", err) + } + p7.Content, err = base64.StdEncoding.DecodeString(ApkEcdsaContent) + if err != nil { + t.Errorf("Failed to decode base64 signature file: %v", err) + } + if err := p7.Verify(); err != nil { + t.Errorf("Verify failed with error: %v", err) + } +} + +var ApkEcdsaFixture = `-----BEGIN PKCS7----- +MIIDAgYJKoZIhvcNAQcCoIIC8zCCAu8CAQExDzANBglghkgBZQMEAgMFADALBgkq +hkiG9w0BBwGgggH3MIIB8zCCAVSgAwIBAgIJAOxXdFsvm3YiMAoGCCqGSM49BAME +MBIxEDAOBgNVBAMMB2VjLXA1MjEwHhcNMTYwMzMxMTUzMTIyWhcNNDMwODE3MTUz +MTIyWjASMRAwDgYDVQQDDAdlYy1wNTIxMIGbMBAGByqGSM49AgEGBSuBBAAjA4GG +AAQAYX95sSjPEQqgyLD04tNUyq9y/w8seblOpfqa/Amx6H4GFdrjGXX0YTfXKr9G +hAyIyQSnNrIg0zDlWQUbBPRW4CYBLFOg1pUn1NBhKFD4NtO1KWvYtNOYDegFjRCP +B0p+fEXDbq8QFDYvlh+NZUJ16+ih8XNIf1C29xuLEqN6oKOnAvajUDBOMB0GA1Ud +DgQWBBT/Ra3kz60gQ7tYk3byZckcLabt8TAfBgNVHSMEGDAWgBT/Ra3kz60gQ7tY +k3byZckcLabt8TAMBgNVHRMEBTADAQH/MAoGCCqGSM49BAMEA4GMADCBiAJCAP39 +hYLsWk2H84oEw+HJqGGjexhqeD3vSO1mWhopripE/81oy3yV10puYoJe11xDSfcD +j2VfNCHazuXO3kSxGA/1AkIBLUJxp/WYbYzhBGKr6lcxczKI/wuMfkZ6vL+0EMJV +A/2uEoeqvnl7BsdkicyaOBNEADijuVdaPPIWzKClt9OaVxExgdAwgc0CAQEwHzAS +MRAwDgYDVQQDDAdlYy1wNTIxAgkA7Fd0Wy+bdiIwDQYJYIZIAWUDBAIDBQAwCgYI +KoZIzj0EAwQEgYswgYgCQgD1pVSNo7qTm9A6tpt3SU2yRa+xpJAnUbpZ+Gu36B71 +JnQBUzRgTGevniqHpyagi7b2zjWh1uvfz9FfrITUwGMddgJCAPjiBRcl7rKpxmZn +V1MvcJOX41xRSJu1wmBiYXqaJarL+gQ/Wl7RYsMtqLjmNColvLaHNxCaWOO/8nAE +Hg0OMA60 +-----END PKCS7-----` + +var ApkEcdsaContent = `U2lnbmF0dXJlLVZlcnNpb246IDEuMA0KU0hBLTUxMi1EaWdlc3QtTWFuaWZlc3Q6IFAvVDRqSWtTMjQvNzFxeFE2WW1MeEtNdkRPUUF0WjUxR090dFRzUU9yemhHRQ0KIEpaUGVpWUtyUzZYY090bStYaWlFVC9uS2tYdWVtUVBwZ2RBRzFKUzFnPT0NCkNyZWF0ZWQtQnk6IDEuMCAoQW5kcm9pZCBTaWduQXBrKQ0KDQpOYW1lOiBBbmRyb2lkTWFuaWZlc3QueG1sDQpTSEEtNTEyLURpZ2VzdDogcm9NbWVQZmllYUNQSjFJK2VzMVpsYis0anB2UXowNHZqRWVpL2U0dkN1ald0VVVWSHEzMkNXDQogMUxsOHZiZGMzMCtRc1FlN29ibld4dmhLdXN2K3c1a2c9PQ0KDQpOYW1lOiByZXNvdXJjZXMuYXJzYw0KU0hBLTUxMi1EaWdlc3Q6IG5aYW1aUzlPZTRBRW41cEZaaCtoQ1JFT3krb1N6a3hHdU5YZU0wUFF6WGVBVlVQV3hSVzFPYQ0KIGVLbThRbXdmTmhhaS9HOEcwRUhIbHZEQWdlcy9HUGtBPT0NCg0KTmFtZTogY2xhc3Nlcy5kZXgNClNIQS01MTItRGlnZXN0OiBlbWlDQld2bkVSb0g2N2lCa3EwcUgrdm5tMkpaZDlMWUNEV051N3RNYzJ3bTRtV0dYSUVpWmcNCiBWZkVPV083MFRlZnFjUVhldkNtN2hQMnRpT0U3Y0w5UT09DQoNCg==` + +func TestVerifyFirefoxAddon(t *testing.T) { + fixture := UnmarshalTestFixture(FirefoxAddonFixture) + p7, err := Parse(fixture.Input) + if err != nil { + t.Errorf("Parse encountered unexpected error: %v", err) + } + p7.Content = FirefoxAddonContent + certPool := x509.NewCertPool() + certPool.AppendCertsFromPEM(FirefoxAddonRootCert) + if err := p7.VerifyWithChain(certPool); err != nil { + t.Errorf("Verify failed with error: %v", err) + } + // Verify the certificate chain to make sure the identified root + // is the one we expect + ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, p7.Signers[0].IssuerAndSerialNumber) + if ee == nil { + t.Errorf("No end-entity certificate found for signer") + } + signingTime, _ := time.Parse(time.RFC3339, "2017-02-23 09:06:16-05:00") + chains, err := verifyCertChain(ee, p7.Certificates, certPool, signingTime) + if err != nil { + t.Error(err) + } + if len(chains) != 1 { + t.Errorf("Expected to find one chain, but found %d", len(chains)) + } + if len(chains[0]) != 3 { + t.Errorf("Expected to find three certificates in chain, but found %d", len(chains[0])) + } + if chains[0][0].Subject.CommonName != "tabscope@xuldev.org" { + t.Errorf("Expected to find EE certificate with subject 'tabscope@xuldev.org', but found '%s'", chains[0][0].Subject.CommonName) + } + if chains[0][1].Subject.CommonName != "production-signing-ca.addons.mozilla.org" { + t.Errorf("Expected to find intermediate certificate with subject 'production-signing-ca.addons.mozilla.org', but found '%s'", chains[0][1].Subject.CommonName) + } + if chains[0][2].Subject.CommonName != "root-ca-production-amo" { + t.Errorf("Expected to find root certificate with subject 'root-ca-production-amo', but found '%s'", chains[0][2].Subject.CommonName) + } +} + +var FirefoxAddonContent = []byte(`Signature-Version: 1.0 +MD5-Digest-Manifest: KjRavc6/KNpuT1QLcB/Gsg== +SHA1-Digest-Manifest: 5Md5nUg+U7hQ/UfzV+xGKWOruVI= + +`) + +var FirefoxAddonFixture = ` +-----BEGIN PKCS7----- +MIIQTAYJKoZIhvcNAQcCoIIQPTCCEDkCAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3 +DQEHAaCCDL0wggW6MIIDoqADAgECAgYBVpobWVwwDQYJKoZIhvcNAQELBQAwgcUx +CzAJBgNVBAYTAlVTMRwwGgYDVQQKExNNb3ppbGxhIENvcnBvcmF0aW9uMS8wLQYD +VQQLEyZNb3ppbGxhIEFNTyBQcm9kdWN0aW9uIFNpZ25pbmcgU2VydmljZTExMC8G +A1UEAxMocHJvZHVjdGlvbi1zaWduaW5nLWNhLmFkZG9ucy5tb3ppbGxhLm9yZzE0 +MDIGCSqGSIb3DQEJARYlc2VydmljZXMtb3BzK2FkZG9uc2lnbmluZ0Btb3ppbGxh +LmNvbTAeFw0xNjA4MTcyMDA0NThaFw0yMTA4MTYyMDA0NThaMHYxEzARBgNVBAsT +ClByb2R1Y3Rpb24xCzAJBgNVBAYTAlVTMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3 +MQ8wDQYDVQQKEwZBZGRvbnMxCzAJBgNVBAgTAkNBMRwwGgYDVQQDFBN0YWJzY29w +ZUB4dWxkZXYub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAv6e0 +mPD8dt4J8HTNNq4ODns2DV6Weh1hllCIFvOeu1u3UrR03st0BMY8OXYwr/NvRVjg +bA8gRySWAL+XqLzbhtXNeNegAoxrF+3mYY5rJjsLj/FGI6P6OXjngqwgm9VTBl7m +jh/KXBSwYoUcavJo6cmk8sCFwoblyQiv+tsWaUCOI6zMzubNtIS+GFvET9y/VZMP +j6mk8O10wBgJF5MMtA19va3qXy7aCZ7DnZp1l3equd/L6t324TtXoqx6xWQKo6TM +I0mcTlKvm6TKegTGBCyGn3JRARoIJv4AW1qqgyaHXf9EoY2pKT8Avkri5++NuSJ6 +jtO4k/diBA2MZU20U0KGffYZNTxKDqd6XtI6y1tJPd/OWRFyU+mHntkcm9sar7L3 +nPKujHRox2re10ec1WBnJE3PjlAoesNjxzp+xs2mGGc8DX9NuWn+1uK9xmgGIIMl +OFfyQ4s0G6hKp5goFcrFZxmexu0ZahOs8vZf8xDBW7yR1zToQElOXHvrscM386os +kOF9IxQZfcCoPuNQVg1haCONNkx0oau3RQQlOSAZtC79b+rBjQ5JYfjRLYAworf2 +xQaprCh33TD1dTBrvzEbCGszgkN53Vqh5TFBjbU/NyldOkGvK8Xf6WhT5u+aftnV +lbuE2McAg6x1AlloUZq6PNTBpz7zypcIISnQ+y8CAwEAATANBgkqhkiG9w0BAQsF +AAOCAgEAIBoo2+OEYNCgP/IbUj9azaf/lde1q4AK/uTMoUeS5WcrXd8aqA0Y1qV7 +xUALgDQAExXgqcOMGu4mPMaoZDgwGI4Tj7XPJQq5Z5zYxpRf/Wtzae33T9BF6QPW +v5xiRYuol+FbEtqRHZqxDWtIrd1MWBy3wjO3pLPdzDM9jWh+HLxdGWThJszaZp3T +CqsOx+l9W0Q7qM5ioZpHStgXDfhw38Lg++kLnzcX9MqsjYyezdwE4krqW6hK3+4S +0LZE4dTgsy8JULkyAF3HrPWEXESnD7c4mx6owZe+BNDK5hsVM/obAqH7sJq/igbM +5N1l832p/ws8l5xKOr3qBWSzWn6u7ExvqG6Ckh0foJOVXvzGqvrXcoiBGV8S9Z7c +DghUvMt6b0pZ0ildRCHfTUz7eG3g4MhfbjupR7b+L9FWEJhcd/H0dxpw7SKYha/n +ePuRL7MXmbW8WLMqO/ImxzL8TPOB3pUg3nITfubV6gpPBmn+0nwbqYUmggJuwgvK +I2GpN2Ny6EErZy17EEgyhJygJZMj+UzQjC781xxsl3ljpYEqqwgRLIZBSBUD5dXj +XBuU24w162SeSyHZzkBbuv6lr52pqoZyFrG29DCHECgO9ZmNWgSpiWSkh+vExAG7 +wNs0y61t2HUG+BCMGPQ9sOzouyTfrnLVAWwzswGftFYQfoIBeJIwggb7MIIE46AD +AgECAgMQAAIwDQYJKoZIhvcNAQEMBQAwfTELMAkGA1UEBhMCVVMxHDAaBgNVBAoT +E01vemlsbGEgQ29ycG9yYXRpb24xLzAtBgNVBAsTJk1vemlsbGEgQU1PIFByb2R1 +Y3Rpb24gU2lnbmluZyBTZXJ2aWNlMR8wHQYDVQQDExZyb290LWNhLXByb2R1Y3Rp +b24tYW1vMB4XDTE1MDMxNzIzNTI0MloXDTI1MDMxNDIzNTI0MlowgcUxCzAJBgNV +BAYTAlVTMRwwGgYDVQQKExNNb3ppbGxhIENvcnBvcmF0aW9uMS8wLQYDVQQLEyZN +b3ppbGxhIEFNTyBQcm9kdWN0aW9uIFNpZ25pbmcgU2VydmljZTExMC8GA1UEAxMo +cHJvZHVjdGlvbi1zaWduaW5nLWNhLmFkZG9ucy5tb3ppbGxhLm9yZzE0MDIGCSqG +SIb3DQEJARYlc2VydmljZXMtb3BzK2FkZG9uc2lnbmluZ0Btb3ppbGxhLmNvbTCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMLMM9m2HBLhCiO9mhljpehT +hxpzlCnxluzDZ51I/H7MvBbIvZBm9zSpHdffubSsak2qYE69d+ebTa/CK83WIosM +24/2Qp7n/GGaPJcCC4Y3JkrCsgA8+wV2MbFlKSv+qMdvI/sE3BPYDMCjVPMhHmIP +XaPWd42OoHpI8R3GGUtVnR3Hm76pa2+v6TwgeMiO8om+ogGufiyv6FNMZ5NuY1Z9 +aLNEvehnAzSfddQyki+6FJd7XkgZbP7pb1Kl8yYgiy4piBerJ9H09uPehffE3Ell +3cApQL3+0kjaUX4scMjuNQDMKziRZkYgJAM+qA9WA5Jn77AjerQBWQeEev1PWHYh +0IDlgS/a0bjKmVjNZYG6adrY/R5/whzWGFCIE1UfhPm6PdN0557qvF838C2RFHsI +KzV6KQf0chMjpa02tPaIctjVhnDQZZNKm2ZfLOt9kQ57Is/e6KxH7pYMit46+s99 +lYM7ZquvWbK19b1Ili/6S1BxSzd3wztgfN5jGsc+jCCYLm+AcVtfNKc8cFZHXKrB +CwhGmdbWDSBCicZNA7FKJpO3oIx26VPF2XUldA/T5Mh/POGLilK3t9m9qbjEyDp1 +EwoBToOR/aMrdnNYvSWp0g/GHMzSfJjjXyAqrZY2itam/IJd8r9FoRAzevPt/zTX +BET3INoiCDGRH0XrxUYtAgMGVTejggE5MIIBNTAMBgNVHRMEBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUdHxf +FKXipZLjs20GqIUdNkQXH4gwgagGA1UdIwSBoDCBnYAUs7zqWHSr4W54KrKrnCMe +qGMsl7ehgYGkfzB9MQswCQYDVQQGEwJVUzEcMBoGA1UEChMTTW96aWxsYSBDb3Jw +b3JhdGlvbjEvMC0GA1UECxMmTW96aWxsYSBBTU8gUHJvZHVjdGlvbiBTaWduaW5n +IFNlcnZpY2UxHzAdBgNVBAMTFnJvb3QtY2EtcHJvZHVjdGlvbi1hbW+CAQEwMwYJ +YIZIAYb4QgEEBCYWJGh0dHA6Ly9hZGRvbnMubW96aWxsYS5vcmcvY2EvY3JsLnBl +bTANBgkqhkiG9w0BAQwFAAOCAgEArde/fdjb7TE0eH7Ij7xU4JbcSyhY3cQhVYCw +Fg+Q/2pj+NAfazcjUuLWA0Y/YZs9HOx6j+ZAqO4C/xfMP4RDs9IypxvzHDU6SXgD +RK6uOKtS07HXLcXgFUBvJEQhbT/h5+IQOA4/GcpCshfD6iyiBBi+IocR+tnKPCuZ +T3m1t60Eja/MkPKG/Gx8vSodHvlTTsJ2GzjUEANveCZOnlAdp9fjTvFZny9qqnbg +sfVbuTqKndbCFW5QLXfkna6jBqMrY0+CpMYY2oJ5gwpHbE/7hhukjxGCTcpv7r/O +M53bb/DZnybDlLLepacljvz7DBA1O1FFtEhf9MR+vyvmBpniAyKQhqG2hsVGurE1 +nBcE+oteZWar2lMp6+etDAb9DRC+jZv0aEQs2o/qQwyD8AGquLgBsJq5Jz3gGxzn +4r3vGu2lV8VdzIm0C8sOFSWTmTZxQmJbF8xSsQBojnsvEah4DPER+eAt6qKolaWe +s4drJQjzFyC7HJn2VqalpCwbe9CdMB7eRqzeP6GujJBi80/gx0pAysUtuKKpH5IJ +WbXAOszfrjb3CaHafYZDnwPoOfj74ogFzjt2f54jwnU+ET/byfjZ7J8SLH316C1V +HrvFXcTzyMV4aRluVPjPg9x1G58hMIbeuT4GpwQUNdJ9uL8t65v0XwG2t6Y7jpRO +sFVxBtgxggNXMIIDUwIBATCB0DCBxTELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE01v +emlsbGEgQ29ycG9yYXRpb24xLzAtBgNVBAsTJk1vemlsbGEgQU1PIFByb2R1Y3Rp +b24gU2lnbmluZyBTZXJ2aWNlMTEwLwYDVQQDEyhwcm9kdWN0aW9uLXNpZ25pbmct +Y2EuYWRkb25zLm1vemlsbGEub3JnMTQwMgYJKoZIhvcNAQkBFiVzZXJ2aWNlcy1v +cHMrYWRkb25zaWduaW5nQG1vemlsbGEuY29tAgYBVpobWVwwCQYFKw4DAhoFAKBd +MBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE2MDgx +NzIwMDQ1OFowIwYJKoZIhvcNAQkEMRYEFAxlGvNFSx+Jqj70haE8b7UZk+2GMA0G +CSqGSIb3DQEBAQUABIICADsDlrucYRgwq9o2QSsO6X6cRa5Zu6w+1n07PTIyc1zn +Pi1cgkkWZ0kZBHDrJ5CY33yRQPl6I1tHXaq7SkOSdOppKhpUmBiKZxQRAZR21QHk +R3v1XS+st/o0N+0btv3YoplUifLIwtH89oolxqlStChELu7FuOBretdhx/z12ytA +EhIIS53o/XjDL7XKJbQA02vzOtOC/Eq6p8BI7F3y6pvtmJIRkeGv+u6ssJa6g5q8 +74w8hHXaH94Z9+hDPqjNWlsXJHgPdAKiEjzDz9oLkvDyX4Pd8JMK5ILskirpG+hj +Q8jkTc5oYwyuSlBAUTGxW6ZbuOrtfVZvOVtRL/ixuiFiVlJ+JOQOxrtK19ukamsI +iacFlbLgiA7w0HCtm2DsT9aL67/1e4rJ0lv0MjnQYUMmKQy7g0Gd3+nQPU9pn+Lf +Z/UmSNWiJ8Csc/seDMyzT6jrzcGPfoSVaUowH0wGrI9If1snwcr+mMg7dWRGf1fm +y/dcVSzed0ax4LqDmike1EshU+51cKWWlnhyNHK4KH+0fNsBQ0c6clrFpGx9MPmV +YXie6C+LWkh5x12RU0sJt/SmSZV6q9VliIkX+yY3jBrC/pKgRahtcIyq46Da1E6K +lc15Euur3NfGow+nott0Z8XutpYdK/2vBKcIh9JOdkd+oe6pcIP6hnhHRp53wqmG +-----END PKCS7-----` + +var FirefoxAddonRootCert = []byte(` +-----BEGIN CERTIFICATE----- +MIIGYTCCBEmgAwIBAgIBATANBgkqhkiG9w0BAQwFADB9MQswCQYDVQQGEwJVUzEc +MBoGA1UEChMTTW96aWxsYSBDb3Jwb3JhdGlvbjEvMC0GA1UECxMmTW96aWxsYSBB +TU8gUHJvZHVjdGlvbiBTaWduaW5nIFNlcnZpY2UxHzAdBgNVBAMTFnJvb3QtY2Et +cHJvZHVjdGlvbi1hbW8wHhcNMTUwMzE3MjI1MzU3WhcNMjUwMzE0MjI1MzU3WjB9 +MQswCQYDVQQGEwJVUzEcMBoGA1UEChMTTW96aWxsYSBDb3Jwb3JhdGlvbjEvMC0G +A1UECxMmTW96aWxsYSBBTU8gUHJvZHVjdGlvbiBTaWduaW5nIFNlcnZpY2UxHzAd +BgNVBAMTFnJvb3QtY2EtcHJvZHVjdGlvbi1hbW8wggIgMA0GCSqGSIb3DQEBAQUA +A4ICDQAwggIIAoICAQC0u2HXXbrwy36+MPeKf5jgoASMfMNz7mJWBecJgvlTf4hH +JbLzMPsIUauzI9GEpLfHdZ6wzSyFOb4AM+D1mxAWhuZJ3MDAJOf3B1Rs6QorHrl8 +qqlNtPGqepnpNJcLo7JsSqqE3NUm72MgqIHRgTRsqUs+7LIPGe7262U+N/T0LPYV +Le4rZ2RDHoaZhYY7a9+49mHOI/g2YFB+9yZjE+XdplT2kBgA4P8db7i7I0tIi4b0 +B0N6y9MhL+CRZJyxdFe2wBykJX14LsheKsM1azHjZO56SKNrW8VAJTLkpRxCmsiT +r08fnPyDKmaeZ0BtsugicdipcZpXriIGmsZbI12q5yuwjSELdkDV6Uajo2n+2ws5 +uXrP342X71WiWhC/dF5dz1LKtjBdmUkxaQMOP/uhtXEKBrZo1ounDRQx1j7+SkQ4 +BEwjB3SEtr7XDWGOcOIkoJZWPACfBLC3PJCBWjTAyBlud0C5n3Cy9regAAnOIqI1 +t16GU2laRh7elJ7gPRNgQgwLXeZcFxw6wvyiEcmCjOEQ6PM8UQjthOsKlszMhlKw +vjyOGDoztkqSBy/v+Asx7OW2Q7rlVfKarL0mREZdSMfoy3zTgtMVCM0vhNl6zcvf +5HNNopoEdg5yuXo2chZ1p1J+q86b0G5yJRMeT2+iOVY2EQ37tHrqUURncCy4uwIB +A6OB7TCB6jAMBgNVHRMEBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAWBgNVHSUBAf8E +DDAKBggrBgEFBQcDAzCBkgYDVR0jBIGKMIGHoYGBpH8wfTELMAkGA1UEBhMCVVMx +HDAaBgNVBAoTE01vemlsbGEgQ29ycG9yYXRpb24xLzAtBgNVBAsTJk1vemlsbGEg +QU1PIFByb2R1Y3Rpb24gU2lnbmluZyBTZXJ2aWNlMR8wHQYDVQQDExZyb290LWNh +LXByb2R1Y3Rpb24tYW1vggEBMB0GA1UdDgQWBBSzvOpYdKvhbngqsqucIx6oYyyX +tzANBgkqhkiG9w0BAQwFAAOCAgEAaNSRYAaECAePQFyfk12kl8UPLh8hBNidP2H6 +KT6O0vCVBjxmMrwr8Aqz6NL+TgdPmGRPDDLPDpDJTdWzdj7khAjxqWYhutACTew5 +eWEaAzyErbKQl+duKvtThhV2p6F6YHJ2vutu4KIciOMKB8dslIqIQr90IX2Usljq +8Ttdyf+GhUmazqLtoB0GOuESEqT4unX6X7vSGu1oLV20t7t5eCnMMYD67ZBn0YIU +/cm/+pan66hHrja+NeDGF8wabJxdqKItCS3p3GN1zUGuJKrLykxqbOp/21byAGog +Z1amhz6NHUcfE6jki7sM7LHjPostU5ZWs3PEfVVgha9fZUhOrIDsyXEpCWVa3481 +LlAq3GiUMKZ5DVRh9/Nvm4NwrTfB3QkQQJCwfXvO9pwnPKtISYkZUqhEqvXk5nBg +QCkDSLDjXTx39naBBGIVIqBtKKuVTla9enngdq692xX/CgO6QJVrwpqdGjebj5P8 +5fNZPABzTezG3Uls5Vp+4iIWVAEDkK23cUj3c/HhE+Oo7kxfUeu5Y1ZV3qr61+6t +ZARKjbu1TuYQHf0fs+GwID8zeLc2zJL7UzcHFwwQ6Nda9OJN4uPAuC/BKaIpxCLL +26b24/tRam4SJjqpiq20lynhUrmTtt6hbG3E1Hpy3bmkt2DYnuMFwEx2gfXNcnbT +wNuvFqc= +-----END CERTIFICATE-----`) + +// sign a document with openssl and verify the signature with pkcs7. +// this uses a chain of root, intermediate and signer cert, where the +// intermediate is added to the certs but the root isn't. +func TestSignWithOpenSSLAndVerify(t *testing.T) { + content := []byte(` +A ship in port is safe, +but that's not what ships are built for. +-- Grace Hopper`) + // write the content to a temp file + tmpContentFile, err := ioutil.TempFile("", "TestSignWithOpenSSLAndVerify_content") + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(tmpContentFile.Name(), content, 0755) + sigalgs := []x509.SignatureAlgorithm{ + x509.SHA1WithRSA, + x509.SHA256WithRSA, + x509.SHA512WithRSA, + x509.ECDSAWithSHA1, + x509.ECDSAWithSHA256, + x509.ECDSAWithSHA384, + x509.ECDSAWithSHA512, + } + for _, sigalgroot := range sigalgs { + rootCert, err := createTestCertificateByIssuer("PKCS7 Test Root CA", nil, sigalgroot, true) + if err != nil { + t.Fatalf("test %s: cannot generate root cert: %s", sigalgroot, err) + } + truststore := x509.NewCertPool() + truststore.AddCert(rootCert.Certificate) + for _, sigalginter := range sigalgs { + interCert, err := createTestCertificateByIssuer("PKCS7 Test Intermediate Cert", rootCert, sigalginter, true) + if err != nil { + t.Fatalf("test %s/%s: cannot generate intermediate cert: %s", sigalgroot, sigalginter, err) + } + // write the intermediate cert to a temp file + tmpInterCertFile, err := ioutil.TempFile("", "TestSignWithOpenSSLAndVerify_intermediate") + if err != nil { + t.Fatal(err) + } + fd, err := os.OpenFile(tmpInterCertFile.Name(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) + if err != nil { + t.Fatal(err) + } + pem.Encode(fd, &pem.Block{Type: "CERTIFICATE", Bytes: interCert.Certificate.Raw}) + fd.Close() + for _, sigalgsigner := range sigalgs { + signerCert, err := createTestCertificateByIssuer("PKCS7 Test Signer Cert", interCert, sigalgsigner, false) + if err != nil { + t.Fatalf("test %s/%s/%s: cannot generate signer cert: %s", sigalgroot, sigalginter, sigalgsigner, err) + } + + // write the signer cert to a temp file + tmpSignerCertFile, err := ioutil.TempFile("", "TestSignWithOpenSSLAndVerify_signer") + if err != nil { + t.Fatal(err) + } + fd, err = os.OpenFile(tmpSignerCertFile.Name(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) + if err != nil { + t.Fatal(err) + } + pem.Encode(fd, &pem.Block{Type: "CERTIFICATE", Bytes: signerCert.Certificate.Raw}) + fd.Close() + + // write the signer key to a temp file + tmpSignerKeyFile, err := ioutil.TempFile("", "TestSignWithOpenSSLAndVerify_key") + if err != nil { + t.Fatal(err) + } + fd, err = os.OpenFile(tmpSignerKeyFile.Name(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) + if err != nil { + t.Fatal(err) + } + var derKey []byte + priv := *signerCert.PrivateKey + switch priv := priv.(type) { + case *rsa.PrivateKey: + derKey = x509.MarshalPKCS1PrivateKey(priv) + pem.Encode(fd, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: derKey}) + case *ecdsa.PrivateKey: + derKey, err = x509.MarshalECPrivateKey(priv) + if err != nil { + t.Fatal(err) + } + pem.Encode(fd, &pem.Block{Type: "EC PRIVATE KEY", Bytes: derKey}) + } + fd.Close() + + // write the root cert to a temp file + tmpSignedFile, err := ioutil.TempFile("", "TestSignWithOpenSSLAndVerify_signature") + if err != nil { + t.Fatal(err) + } + // call openssl to sign the content + opensslCMD := exec.Command("openssl", "smime", "-sign", "-nodetach", + "-in", tmpContentFile.Name(), "-out", tmpSignedFile.Name(), + "-signer", tmpSignerCertFile.Name(), "-inkey", tmpSignerKeyFile.Name(), + "-certfile", tmpInterCertFile.Name(), "-outform", "PEM") + out, err := opensslCMD.CombinedOutput() + if err != nil { + t.Fatalf("test %s/%s/%s: openssl command failed with %s: %s", sigalgroot, sigalginter, sigalgsigner, err, out) + } + + // verify the signed content + pemSignature, err := ioutil.ReadFile(tmpSignedFile.Name()) + if err != nil { + t.Fatal(err) + } + derBlock, _ := pem.Decode(pemSignature) + if derBlock == nil { + break + } + p7, err := Parse(derBlock.Bytes) + if err != nil { + t.Fatalf("Parse encountered unexpected error: %v", err) + } + if err := p7.VerifyWithChain(truststore); err != nil { + t.Fatalf("Verify failed with error: %v", err) + } + // Verify the certificate chain to make sure the identified root + // is the one we expect + ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, p7.Signers[0].IssuerAndSerialNumber) + if ee == nil { + t.Fatalf("No end-entity certificate found for signer") + } + chains, err := verifyCertChain(ee, p7.Certificates, truststore, time.Now()) + if err != nil { + t.Fatal(err) + } + if len(chains) != 1 { + t.Fatalf("Expected to find one chain, but found %d", len(chains)) + } + if len(chains[0]) != 3 { + t.Fatalf("Expected to find three certificates in chain, but found %d", len(chains[0])) + } + if chains[0][0].Subject.CommonName != "PKCS7 Test Signer Cert" { + t.Fatalf("Expected to find EE certificate with subject 'PKCS7 Test Signer Cert', but found '%s'", chains[0][0].Subject.CommonName) + } + if chains[0][1].Subject.CommonName != "PKCS7 Test Intermediate Cert" { + t.Fatalf("Expected to find intermediate certificate with subject 'PKCS7 Test Intermediate Cert', but found '%s'", chains[0][1].Subject.CommonName) + } + if chains[0][2].Subject.CommonName != "PKCS7 Test Root CA" { + t.Fatalf("Expected to find root certificate with subject 'PKCS7 Test Root CA', but found '%s'", chains[0][2].Subject.CommonName) + } + os.Remove(tmpSignerCertFile.Name()) // clean up + os.Remove(tmpSignerKeyFile.Name()) // clean up + } + os.Remove(tmpInterCertFile.Name()) // clean up + } + } + os.Remove(tmpContentFile.Name()) // clean up +} + +func TestDSASignWithOpenSSLAndVerify(t *testing.T) { + content := []byte(` +A ship in port is safe, +but that's not what ships are built for. +-- Grace Hopper`) + // write the content to a temp file + tmpContentFile, err := ioutil.TempFile("", "TestDSASignWithOpenSSLAndVerify_content") + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(tmpContentFile.Name(), content, 0755) + + // write the signer cert to a temp file + tmpSignerCertFile, err := ioutil.TempFile("", "TestDSASignWithOpenSSLAndVerify_signer") + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(tmpSignerCertFile.Name(), dsaPublicCert, 0755) + + // write the signer key to a temp file + tmpSignerKeyFile, err := ioutil.TempFile("", "TestDSASignWithOpenSSLAndVerify_key") + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(tmpSignerKeyFile.Name(), dsaPrivateKey, 0755) + + tmpSignedFile, err := ioutil.TempFile("", "TestDSASignWithOpenSSLAndVerify_signature") + if err != nil { + t.Fatal(err) + } + // call openssl to sign the content + opensslCMD := exec.Command("openssl", "smime", "-sign", "-nodetach", "-md", "sha1", + "-in", tmpContentFile.Name(), "-out", tmpSignedFile.Name(), + "-signer", tmpSignerCertFile.Name(), "-inkey", tmpSignerKeyFile.Name(), + "-certfile", tmpSignerCertFile.Name(), "-outform", "PEM") + out, err := opensslCMD.CombinedOutput() + if err != nil { + t.Fatalf("openssl command failed with %s: %s", err, out) + } + + // verify the signed content + pemSignature, err := ioutil.ReadFile(tmpSignedFile.Name()) + if err != nil { + t.Fatal(err) + } + fmt.Printf("%s\n", pemSignature) + derBlock, _ := pem.Decode(pemSignature) + if derBlock == nil { + t.Fatalf("failed to read DER block from signature PEM %s", tmpSignedFile.Name()) + } + p7, err := Parse(derBlock.Bytes) + if err != nil { + t.Fatalf("Parse encountered unexpected error: %v", err) + } + if err := p7.Verify(); err != nil { + t.Fatalf("Verify failed with error: %v", err) + } + os.Remove(tmpSignerCertFile.Name()) // clean up + os.Remove(tmpSignerKeyFile.Name()) // clean up + os.Remove(tmpContentFile.Name()) // clean up +} + +var dsaPrivateKey = []byte(`-----BEGIN PRIVATE KEY----- +MIIBSwIBADCCASwGByqGSM44BAEwggEfAoGBAP1/U4EddRIpUt9KnC7s5Of2EbdS +PO9EAMMeP4C2USZpRV1AIlH7WT2NWPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVCl +pJ+f6AR7ECLCT7up1/63xhv4O1fnxqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith +1yrv8iIDGZ3RSAHHAhUAl2BQjxUjC8yykrmCouuEC/BYHPUCgYEA9+GghdabPd7L +vKtcNrhXuXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3 +zwkyjMim4TwWeotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImo +g9/hWuWfBpKLZl6Ae1UlZAFMO/7PSSoEFgIUfW4aPdQBn9gJZp2KuNpzgHzvfsE= +-----END PRIVATE KEY-----`) + +var dsaPublicCert = []byte(`-----BEGIN CERTIFICATE----- +MIIDOjCCAvWgAwIBAgIEPCY/UDANBglghkgBZQMEAwIFADBsMRAwDgYDVQQGEwdV +bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD +VQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3du +MB4XDTE4MTAyMjEzNDMwN1oXDTQ2MDMwOTEzNDMwN1owbDEQMA4GA1UEBhMHVW5r +bm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UE +ChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCC +AbgwggEsBgcqhkjOOAQBMIIBHwKBgQD9f1OBHXUSKVLfSpwu7OTn9hG3UjzvRADD +Hj+AtlEmaUVdQCJR+1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gE +exAiwk+7qdf+t8Yb+DtX58aophUPBPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/Ii +Axmd0UgBxwIVAJdgUI8VIwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4 +V7l5lK+7+jrqgvlXTAs9B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozI +puE8FnqLVHyNKOCjrh4rs6Z1kW6jfwv6ITVi8ftiegEkO8yk8b6oUZCJqIPf4Vrl +nwaSi2ZegHtVJWQBTDv+z0kqA4GFAAKBgQDCriMPbEVBoRK4SOUeFwg7+VRf4TTp +rcOQC9IVVoCjXzuWEGrp3ZI7YWJSpFnSch4lk29RH8O0HpI/NOzKnOBtnKr782pt +1k/bJVMH9EaLd6MKnAVjrCDMYBB0MhebZ8QHY2elZZCWoqDYAcIDOsEx+m4NLErT +ypPnjS5M0jm1PKMhMB8wHQYDVR0OBBYEFC0Yt5XdM0Kc95IX8NQ8XRssGPx7MA0G +CWCGSAFlAwQDAgUAAzAAMC0CFQCIgQtrZZ9hdZG1ROhR5hc8nYEmbgIUAIlgC688 +qzy/7yePTlhlpj+ahMM= +-----END CERTIFICATE-----`) From 2a249d20deb77a6aea307fd0f8457a92cae005e9 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Feb 2021 00:32:21 +0100 Subject: [PATCH 29/89] Refactor initialization of SCEP authority --- authority/authority.go | 45 ++++++ authority/provisioner/scep.go | 4 +- cas/softcas/softcas_test.go | 4 + go.mod | 2 +- go.sum | 26 +++ kms/apiv1/options.go | 1 + kms/apiv1/requests.go | 11 ++ kms/awskms/awskms.go | 6 + kms/cloudkms/cloudkms.go | 6 + kms/pkcs11/pkcs11.go | 5 + kms/softkms/softkms.go | 36 +++++ kms/sshagentkms/sshagentkms.go | 6 + kms/yubikey/yubikey.go | 6 + scep/api/api.go | 47 +++--- scep/authority.go | 279 ++++++++++++++++++++++++++++----- scep/scep.go | 54 +++++++ scep/service.go | 9 ++ 17 files changed, 479 insertions(+), 68 deletions(-) create mode 100644 scep/scep.go create mode 100644 scep/service.go diff --git a/authority/authority.go b/authority/authority.go index 5652c71a..e753aeae 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -12,6 +12,7 @@ import ( "github.com/smallstep/certificates/cas" "github.com/smallstep/certificates/linkedca" + "github.com/smallstep/certificates/scep" "github.com/pkg/errors" "github.com/smallstep/certificates/authority/admin" @@ -46,6 +47,9 @@ type Authority struct { federatedX509Certs []*x509.Certificate certificates *sync.Map + // SCEP CA + scepService *scep.Service + // SSH CA sshCAUserCertSignKey ssh.Signer sshCAHostCertSignKey ssh.Signer @@ -309,6 +313,38 @@ func (a *Authority) init() error { } } + // TODO: decide if this is a good approach for providing the SCEP functionality + if a.scepService == nil { + var options casapi.Options + if a.config.AuthorityConfig.Options != nil { + options = *a.config.AuthorityConfig.Options + } + + // Read intermediate and create X509 signer for default CAS. + if options.Is(casapi.SoftCAS) { + options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert) + if err != nil { + return err + } + options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ + SigningKey: a.config.IntermediateKey, + Password: []byte(a.config.Password), + }) + if err != nil { + return err + } + options.Decrypter, err = a.keyManager.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ + DecryptionKey: a.config.IntermediateKey, + Password: []byte(a.config.Password), + }) + } + + a.scepService = &scep.Service{ + Signer: options.Signer, + Decrypter: options.Decrypter, + } + } + // Read root certificates and store them in the certificates map. if len(a.rootX509Certs) == 0 { a.rootX509Certs = make([]*x509.Certificate, len(a.config.Root)) @@ -529,3 +565,12 @@ func (a *Authority) CloseForReload() { log.Printf("error closing the key manager: %v", err) } } + +// GetSCEPService returns the configured SCEP Service +// TODO: this function is intended to exist temporarily +// in order to make SCEP work more easily. It can be +// made more correct by using the right interfaces/abstractions +// after it works as expected. +func (a *Authority) GetSCEPService() scep.Service { + return *a.scepService +} diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 30b4a1b2..6cdfa69f 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -55,7 +55,7 @@ func (s *SCEP) DefaultTLSCertDuration() time.Duration { return s.claimer.DefaultTLSCertDuration() } -// Init initializes and validates the fields of a JWK type. +// Init initializes and validates the fields of a SCEP type. func (s *SCEP) Init(config Config) (err error) { switch { @@ -70,6 +70,8 @@ func (s *SCEP) Init(config Config) (err error) { return err } + // TODO: add other, SCEP specific, options? + return err } diff --git a/cas/softcas/softcas_test.go b/cas/softcas/softcas_test.go index 0a50a990..092a0337 100644 --- a/cas/softcas/softcas_test.go +++ b/cas/softcas/softcas_test.go @@ -110,6 +110,10 @@ func (m *mockKeyManager) CreateSigner(req *kmsapi.CreateSignerRequest) (crypto.S return signer, m.errCreatesigner } +func (m *mockKeyManager) CreateDecrypter(req *kmsapi.CreateDecrypterRequest) (crypto.Decrypter, error) { + return nil, nil +} + func (m *mockKeyManager) Close() error { return m.errClose } diff --git a/go.mod b/go.mod index fe0c7f87..ea867f8f 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/google/uuid v1.1.2 github.com/googleapis/gax-go/v2 v2.0.5 github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect - github.com/micromdm/scep v1.0.0 + github.com/micromdm/scep v1.0.1-0.20210219005251-6027e7654b23 github.com/newrelic/go-agent v2.15.0+incompatible github.com/pkg/errors v0.9.1 github.com/rs/xid v1.2.1 diff --git a/go.sum b/go.sum index 77e0b0cb..934bcfb6 100644 --- a/go.sum +++ b/go.sum @@ -62,6 +62,7 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/aws/aws-sdk-go v1.30.29 h1:NXNqBS9hjOCpDL8SyCyl38gZX3LLLunKOJc5E7vJ8P0= github.com/aws/aws-sdk-go v1.30.29/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -116,10 +117,16 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.4.0 h1:KeVK+Emj3c3S4eRztFuzbFYb2BAgf2jmwDwyXEri7Lo= +github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-piv/piv-go v1.7.0 h1:rfjdFdASfGV5KLJhSjgpGJ5lzVZVtRWn8ovy/H9HQ/U= github.com/go-piv/piv-go v1.7.0/go.mod h1:ON2WvQncm7dIkCQ7kYJs+nc3V4jHGfrrJnSF8HKy7Gk= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.6.0 h1:MmJCxYVKTJ0SplGKqFVX3SBnmaUhODHZrrFF6jMbpZk= +github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -185,6 +192,9 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.4.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda/go.mod h1:MyndkAZd5rUMdNogn35MWXBX1UiBigrU8eTj8DoAC2c= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -211,6 +221,12 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +<<<<<<< HEAD +======= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +>>>>>>> 7ad90d1 (Refactor initialization of SCEP authority) github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -232,6 +248,11 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +<<<<<<< HEAD +======= +github.com/micromdm/scep v1.0.1-0.20210219005251-6027e7654b23 h1:QACkVsQ7Qx4PuPDFL2OFD5u4OnYT0TkReWk9IVqaGHA= +github.com/micromdm/scep v1.0.1-0.20210219005251-6027e7654b23/go.mod h1:a82oNZyGV8jj9Do7dh8EkA90+esBls0CZHR6B85Qda8= +>>>>>>> 7ad90d1 (Refactor initialization of SCEP authority) github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f h1:eVB9ELsoq5ouItQBr5Tj334bhPJG/MX+m7rTchmzVUQ= github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/micromdm/scep v1.0.0 h1:ai//kcZnxZPq1YE/MatiE2bIRD94KOAwZRpN1fhVQXY= @@ -248,6 +269,7 @@ github.com/newrelic/go-agent v2.15.0+incompatible h1:IB0Fy+dClpBq9aEoIrLyQXzU34J github.com/newrelic/go-agent v2.15.0+incompatible/go.mod h1:a8Fv1b/fYhFSReoTU6HDkTYIMZeSVNffmoS726Y0LzQ= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -314,6 +336,8 @@ github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -371,6 +395,7 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20170726083632-f5079bd7f6f7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -417,6 +442,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170728174421-0f826bdd13b5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/kms/apiv1/options.go b/kms/apiv1/options.go index 705f3633..0e6f32df 100644 --- a/kms/apiv1/options.go +++ b/kms/apiv1/options.go @@ -13,6 +13,7 @@ type KeyManager interface { GetPublicKey(req *GetPublicKeyRequest) (crypto.PublicKey, error) CreateKey(req *CreateKeyRequest) (*CreateKeyResponse, error) CreateSigner(req *CreateSignerRequest) (crypto.Signer, error) + CreateDecrypter(req *CreateDecrypterRequest) (crypto.Decrypter, error) // TODO: split into separate interface? Close() error } diff --git a/kms/apiv1/requests.go b/kms/apiv1/requests.go index e58c4546..31097040 100644 --- a/kms/apiv1/requests.go +++ b/kms/apiv1/requests.go @@ -133,6 +133,17 @@ type CreateSignerRequest struct { Password []byte } +// CreateDecrypterRequest is the parameter used in the kms.Decrypt method. +type CreateDecrypterRequest struct { + Decrypter crypto.Decrypter + DecryptionKey string + DecryptionKeyPEM []byte + TokenLabel string + PublicKey string + PublicKeyPEM []byte + Password []byte +} + // LoadCertificateRequest is the parameter used in the LoadCertificate method of // a CertificateManager. type LoadCertificateRequest struct { diff --git a/kms/awskms/awskms.go b/kms/awskms/awskms.go index da392989..00d7d0b3 100644 --- a/kms/awskms/awskms.go +++ b/kms/awskms/awskms.go @@ -3,6 +3,7 @@ package awskms import ( "context" "crypto" + "fmt" "net/url" "strings" "time" @@ -221,6 +222,11 @@ func (k *KMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error return NewSigner(k.service, req.SigningKey) } +// CreateDecrypter creates a new crypto.decrypter backed by AWS KMS +func (k *KMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { + return nil, fmt.Errorf("not implemented yet") +} + // Close closes the connection of the KMS client. func (k *KMS) Close() error { return nil diff --git a/kms/cloudkms/cloudkms.go b/kms/cloudkms/cloudkms.go index cfbf8235..ace12a1a 100644 --- a/kms/cloudkms/cloudkms.go +++ b/kms/cloudkms/cloudkms.go @@ -3,6 +3,7 @@ package cloudkms import ( "context" "crypto" + "fmt" "log" "strings" "time" @@ -284,6 +285,11 @@ func (k *CloudKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKe return pk, nil } +// CreateDecrypter creates a new crypto.Decrypter backed by Google Cloud KMS +func (k *CloudKMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { + return nil, fmt.Errorf("not implemented yet") +} + // getPublicKeyWithRetries retries the request if the error is // FailedPrecondition, caused because the key is in the PENDING_GENERATION // status. diff --git a/kms/pkcs11/pkcs11.go b/kms/pkcs11/pkcs11.go index 47c298a5..8c1497e8 100644 --- a/kms/pkcs11/pkcs11.go +++ b/kms/pkcs11/pkcs11.go @@ -352,3 +352,8 @@ func findCertificate(ctx P11, rawuri string) (*x509.Certificate, error) { } return cert, nil } + +// CreateDecrypter creates a new crypto.Decrypter backed by PKCS11 +func (k *PKCS11) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { + return nil, fmt.Errorf("not implemented yet") +} diff --git a/kms/softkms/softkms.go b/kms/softkms/softkms.go index 23b50849..a2f43c31 100644 --- a/kms/softkms/softkms.go +++ b/kms/softkms/softkms.go @@ -145,3 +145,39 @@ func (k *SoftKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey return nil, errors.Errorf("unsupported public key type %T", v) } } + +// CreateDecrypter creates a new crypto.Decrypter backed by disk/software +func (k *SoftKMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { + + var opts []pemutil.Options + if req.Password != nil { + opts = append(opts, pemutil.WithPassword(req.Password)) + } + + switch { + case req.Decrypter != nil: + return req.Decrypter, nil + case len(req.DecryptionKeyPEM) != 0: + v, err := pemutil.ParseKey(req.DecryptionKeyPEM, opts...) + if err != nil { + return nil, err + } + decrypter, ok := v.(crypto.Decrypter) + if !ok { + return nil, errors.New("decryptorKeyPEM is not a crypto.Decrypter") + } + return decrypter, nil + case req.DecryptionKey != "": + v, err := pemutil.Read(req.DecryptionKey, opts...) + if err != nil { + return nil, err + } + decrypter, ok := v.(crypto.Decrypter) + if !ok { + return nil, errors.New("decryptionKey is not a crypto.Decrypter") + } + return decrypter, nil + default: + return nil, errors.New("failed to load softKMS: please define decryptionKeyPEM or decryptionKey") + } +} diff --git a/kms/sshagentkms/sshagentkms.go b/kms/sshagentkms/sshagentkms.go index b3627a08..9c8a7866 100644 --- a/kms/sshagentkms/sshagentkms.go +++ b/kms/sshagentkms/sshagentkms.go @@ -7,6 +7,7 @@ import ( "crypto/ed25519" "crypto/rsa" "crypto/x509" + "fmt" "io" "net" "os" @@ -204,3 +205,8 @@ func (k *SSHAgentKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.Publi return nil, errors.Errorf("unsupported public key type %T", v) } } + +// CreateDecrypter creates a crypto.Decrypter backed by ssh-agent +func (k *SSHAgentKMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { + return nil, fmt.Errorf("not implemented yet") +} diff --git a/kms/yubikey/yubikey.go b/kms/yubikey/yubikey.go index 2dde244a..b78ff5e5 100644 --- a/kms/yubikey/yubikey.go +++ b/kms/yubikey/yubikey.go @@ -7,6 +7,7 @@ import ( "crypto" "crypto/x509" "encoding/hex" + "fmt" "net/url" "strings" @@ -189,6 +190,11 @@ func (k *YubiKey) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, e return signer, nil } +// CreateDecrypter creates a new crypto.Decrypter backed by a YubiKey +func (k *YubiKey) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { + return nil, fmt.Errorf("not implemented yet") +} + // Close releases the connection to the YubiKey. func (k *YubiKey) Close() error { return errors.Wrap(k.yk.Close(), "error closing yubikey") diff --git a/scep/api/api.go b/scep/api/api.go index 5a4cb5b2..ec959519 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -28,6 +28,8 @@ const ( opnGetCACert = "GetCACert" opnGetCACaps = "GetCACaps" opnPKIOperation = "PKIOperation" + + // TODO: add other (more optional) operations and handling ) // SCEPRequest is a SCEP server request. @@ -98,6 +100,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { } func (h *Handler) Post(w http.ResponseWriter, r *http.Request) { + scepRequest, err := decodeSCEPRequest(r) if err != nil { fmt.Println(err) @@ -252,27 +255,22 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque return err } - certs, err := h.Auth.GetCACertificates() - if err != nil { + pkimsg := &scep.PKIMessage{ + TransactionID: msg.TransactionID, + MessageType: msg.MessageType, + SenderNonce: msg.SenderNonce, + Raw: msg.Raw, + } + + if err := h.Auth.DecryptPKIEnvelope(pkimsg); err != nil { return err } - // TODO: instead of getting the key to decrypt, add a decrypt function to the auth; less leaky - key, err := h.Auth.GetSigningKey() - if err != nil { - return err - } - - ca := certs[0] - if err := msg.DecryptPKIEnvelope(ca, key); err != nil { - return err - } - - if msg.MessageType == microscep.PKCSReq { + if pkimsg.MessageType == microscep.PKCSReq { // TODO: CSR validation, like challenge password } - csr := msg.CSRReqMessage.CSR + csr := pkimsg.CSRReqMessage.CSR id, err := createKeyIdentifier(csr.PublicKey) if err != nil { return err @@ -282,6 +280,7 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque days := 40 + // TODO: use information from provisioner, like claims template := &x509.Certificate{ SerialNumber: serial, Subject: csr.Subject, @@ -296,16 +295,16 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque EmailAddresses: csr.EmailAddresses, } - certRep, err := msg.SignCSR(ca, key, template) + certRep, err := h.Auth.SignCSR(pkimsg, template) if err != nil { return err } - //cert := certRep.CertRepMessage.Certificate - //name := certName(cert) + // //cert := certRep.CertRepMessage.Certificate + // //name := certName(cert) - // TODO: check if CN already exists, if renewal is allowed and if existing should be revoked; fail if not - // TODO: store the new cert for CN locally; should go into the DB + // // TODO: check if CN already exists, if renewal is allowed and if existing should be revoked; fail if not + // // TODO: store the new cert for CN locally; should go into the DB scepResponse.Data = certRep.Raw @@ -321,7 +320,7 @@ func certName(cert *x509.Certificate) string { return string(cert.Signature) } -// createKeyIdentifier create an identifier for public keys +// createKeyIdentifier creates an identifier for public keys // according to the first method in RFC5280 section 4.2.1.2. func createKeyIdentifier(pub crypto.PublicKey) ([]byte, error) { @@ -390,9 +389,9 @@ func ProvisionerFromContext(ctx context.Context) (scep.Provisioner, error) { if val == nil { return nil, errors.New("provisioner expected in request context") } - pval, ok := val.(scep.Provisioner) - if !ok || pval == nil { + p, ok := val.(scep.Provisioner) + if !ok || p == nil { return nil, errors.New("provisioner in context is not a SCEP provisioner") } - return pval, nil + return p, nil } diff --git a/scep/authority.go b/scep/authority.go index 27817b0f..244a6f92 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -1,17 +1,26 @@ package scep import ( - "crypto/rsa" + "bytes" "crypto/x509" - "encoding/pem" "errors" - "io/ioutil" + "fmt" + "math/big" + "math/rand" "github.com/smallstep/certificates/authority/provisioner" database "github.com/smallstep/certificates/db" + "go.step.sm/crypto/pemutil" "github.com/smallstep/nosql" + + microx509util "github.com/micromdm/scep/crypto/x509util" + microscep "github.com/micromdm/scep/scep" + + "github.com/smallstep/certificates/scep/pkcs7" + + "go.step.sm/crypto/x509util" ) // Interface is the SCEP authority interface. @@ -41,7 +50,10 @@ type Interface interface { // GetLinkExplicit(linkType Link, provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string GetCACertificates() ([]*x509.Certificate, error) - GetSigningKey() (*rsa.PrivateKey, error) + //GetSigningKey() (*rsa.PrivateKey, error) + + DecryptPKIEnvelope(*PKIMessage) error + SignCSR(msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) } // Authority is the layer that handles all SCEP interactions. @@ -54,17 +66,16 @@ type Authority struct { // dir *directory intermediateCertificate *x509.Certificate - intermediateKey *rsa.PrivateKey - - //signer crypto.Signer + service Service signAuth SignAuthority } // AuthorityOptions required to create a new SCEP Authority. type AuthorityOptions struct { IntermediateCertificatePath string - IntermediateKeyPath string + + Service Service // Backdate Backdate provisioner.Duration @@ -79,7 +90,7 @@ type AuthorityOptions struct { Prefix string } -// SignAuthority is the interface implemented by a CA authority. +// SignAuthority is the interface for a signing authority type SignAuthority interface { Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) LoadProvisionerByID(string) (provisioner.Interface, error) @@ -87,6 +98,7 @@ type SignAuthority interface { // New returns a new Authority that implements the SCEP interface. func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { + if _, ok := ops.DB.(*database.SimpleDB); !ok { // TODO: see ACME implementation } @@ -100,42 +112,17 @@ func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { return nil, err } - intermediateKey, err := readPrivateKey(ops.IntermediateKeyPath) - if err != nil { - return nil, err - } - return &Authority{ backdate: ops.Backdate, db: ops.DB, prefix: ops.Prefix, dns: ops.DNS, intermediateCertificate: certificateChain[0], - intermediateKey: intermediateKey, + service: ops.Service, signAuth: signAuth, }, nil } -func readPrivateKey(path string) (*rsa.PrivateKey, error) { - - keyBytes, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - - block, _ := pem.Decode([]byte(keyBytes)) - if block == nil { - return nil, nil - } - - key, err := x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - return nil, err - } - - return key, nil -} - // LoadProvisionerByID calls out to the SignAuthority interface to load a // provisioner by ID. func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error) { @@ -145,6 +132,20 @@ func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error // GetCACertificates returns the certificate (chain) for the CA func (a *Authority) GetCACertificates() ([]*x509.Certificate, error) { + // TODO: this should return: the "SCEP Server (RA)" certificate, the issuing CA up to and excl. the root + // Some clients do need the root certificate however; also see: https://github.com/openxpki/openxpki/issues/73 + // + // This means we might need to think about if we should use the current intermediate CA + // certificate as the "SCEP Server (RA)" certificate. It might be better to have a distinct + // RA certificate, with a corresponding rsa.PrivateKey, just for SCEP usage, which is signed by + // the intermediate CA. Will need to look how we can provide this nicely within step-ca. + // + // This might also mean that we might want to use a distinct instance of KMS for doing the key operations, + // so that we can use RSA just for SCEP. + // + // Using an RA does not seem to exist in https://tools.ietf.org/html/rfc8894, but is mentioned in + // https://tools.ietf.org/id/draft-nourse-scep-21.html. Will continue using the CA directly for now. + if a.intermediateCertificate == nil { return nil, errors.New("no intermediate certificate available in SCEP authority") } @@ -152,16 +153,210 @@ func (a *Authority) GetCACertificates() ([]*x509.Certificate, error) { return []*x509.Certificate{a.intermediateCertificate}, nil } -// GetSigningKey returns the RSA private key for the CA -// TODO: we likely should provide utility functions for decrypting and -// signing instead of providing the signing key directly -func (a *Authority) GetSigningKey() (*rsa.PrivateKey, error) { +// DecryptPKIEnvelope decrypts an enveloped message +func (a *Authority) DecryptPKIEnvelope(msg *PKIMessage) error { - if a.intermediateKey == nil { - return nil, errors.New("no intermediate key available in SCEP authority") + data := msg.Raw + + p7, err := pkcs7.Parse(data) + if err != nil { + return err } - return a.intermediateKey, nil + var tID microscep.TransactionID + if err := p7.UnmarshalSignedAttribute(oidSCEPtransactionID, &tID); err != nil { + return err + } + + var msgType microscep.MessageType + if err := p7.UnmarshalSignedAttribute(oidSCEPmessageType, &msgType); err != nil { + return err + } + + msg.p7 = p7 + + p7c, err := pkcs7.Parse(p7.Content) + if err != nil { + return err + } + + envelope, err := p7c.Decrypt(a.intermediateCertificate, a.service.Decrypter) + if err != nil { + return err + } + + msg.pkiEnvelope = envelope + + switch msg.MessageType { + case microscep.CertRep: + certs, err := microscep.CACerts(msg.pkiEnvelope) + if err != nil { + return err + } + msg.CertRepMessage.Certificate = certs[0] // TODO: check correctness of this + return nil + case microscep.PKCSReq, microscep.UpdateReq, microscep.RenewalReq: + csr, err := x509.ParseCertificateRequest(msg.pkiEnvelope) + if err != nil { + return fmt.Errorf("parse CSR from pkiEnvelope") + } + // check for challengePassword + cp, err := microx509util.ParseChallengePassword(msg.pkiEnvelope) + if err != nil { + return fmt.Errorf("scep: parse challenge password in pkiEnvelope") + } + msg.CSRReqMessage = µscep.CSRReqMessage{ + RawDecrypted: msg.pkiEnvelope, + CSR: csr, + ChallengePassword: cp, + } + //msg.Certificate = p7.Certificates[0] // TODO: check if this is necessary to add (again) + return nil + case microscep.GetCRL, microscep.GetCert, microscep.CertPoll: + return fmt.Errorf("not implemented") //errNotImplemented + } + + return nil +} + +// SignCSR creates an x509.Certificate based on a template and Cert Authority credentials +// returns a new PKIMessage with CertRep data +//func (msg *PKIMessage) SignCSR(crtAuth *x509.Certificate, keyAuth *rsa.PrivateKey, template *x509.Certificate) (*PKIMessage, error) { +func (a *Authority) SignCSR(msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) { + + // check if CSRReqMessage has already been decrypted + if msg.CSRReqMessage.CSR == nil { + if err := a.DecryptPKIEnvelope(msg); err != nil { + return nil, err + } + } + + csr := msg.CSRReqMessage.CSR + + // Template data + data := x509util.NewTemplateData() + data.SetCommonName(csr.Subject.CommonName) + //data.Set(x509util.SANsKey, sans) + + // templateOptions, err := provisioner.TemplateOptions(p.GetOptions(), data) + // if err != nil { + // return nil, ServerInternalErr(errors.Wrapf(err, "error creating template options from ACME provisioner")) + // } + // signOps = append(signOps, templateOptions) + + // // Create and store a new certificate. + // certChain, err := auth.Sign(csr, provisioner.SignOptions{ + // NotBefore: provisioner.NewTimeDuration(o.NotBefore), + // NotAfter: provisioner.NewTimeDuration(o.NotAfter), + // }, signOps...) + // if err != nil { + // return nil, ServerInternalErr(errors.Wrapf(err, "error generating certificate for order %s", o.ID)) + // } + + // TODO: proper options + signOps := provisioner.SignOptions{} + signOps2 := []provisioner.SignOption{} + + certs, err := a.signAuth.Sign(csr, signOps, signOps2...) + if err != nil { + return nil, err + } + + cert := certs[0] + + // fmt.Println("CERT") + // fmt.Println(cert) + // fmt.Println(fmt.Sprintf("%T", cert)) + // fmt.Println(cert.Issuer) + // fmt.Println(cert.Subject) + + serial := big.NewInt(int64(rand.Int63())) // TODO: serial logic? + cert.SerialNumber = serial + + // create a degenerate cert structure + deg, err := DegenerateCertificates([]*x509.Certificate{cert}) + if err != nil { + return nil, err + } + + e7, err := pkcs7.Encrypt(deg, msg.p7.Certificates) + if err != nil { + return nil, err + } + + // PKIMessageAttributes to be signed + config := pkcs7.SignerInfoConfig{ + ExtraSignedAttributes: []pkcs7.Attribute{ + { + Type: oidSCEPtransactionID, + Value: msg.TransactionID, + }, + { + Type: oidSCEPpkiStatus, + Value: microscep.SUCCESS, + }, + { + Type: oidSCEPmessageType, + Value: microscep.CertRep, + }, + { + Type: oidSCEPrecipientNonce, + Value: msg.SenderNonce, + }, + }, + } + + signedData, err := pkcs7.NewSignedData(e7) + if err != nil { + return nil, err + } + + // add the certificate into the signed data type + // this cert must be added before the signedData because the recipient will expect it + // as the first certificate in the array + signedData.AddCertificate(cert) + + authCert := a.intermediateCertificate + + // sign the attributes + if err := signedData.AddSigner(authCert, a.service.Signer, config); err != nil { + return nil, err + } + + certRepBytes, err := signedData.Finish() + if err != nil { + return nil, err + } + + cr := &CertRepMessage{ + PKIStatus: microscep.SUCCESS, + RecipientNonce: microscep.RecipientNonce(msg.SenderNonce), + Certificate: cert, + degenerate: deg, + } + + // create a CertRep message from the original + crepMsg := &PKIMessage{ + Raw: certRepBytes, + TransactionID: msg.TransactionID, + MessageType: microscep.CertRep, + CertRepMessage: cr, + } + + return crepMsg, nil +} + +// DegenerateCertificates creates degenerate certificates pkcs#7 type +func DegenerateCertificates(certs []*x509.Certificate) ([]byte, error) { + var buf bytes.Buffer + for _, cert := range certs { + buf.Write(cert.Raw) + } + degenerate, err := pkcs7.DegenerateCertificate(buf.Bytes()) + if err != nil { + return nil, err + } + return degenerate, nil } // Interface guards diff --git a/scep/scep.go b/scep/scep.go new file mode 100644 index 00000000..bc46cce7 --- /dev/null +++ b/scep/scep.go @@ -0,0 +1,54 @@ +package scep + +import ( + "crypto/x509" + "encoding/asn1" + + microscep "github.com/micromdm/scep/scep" + + "github.com/smallstep/certificates/scep/pkcs7" +) + +// SCEP OIDs +var ( + oidSCEPmessageType = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 2} + oidSCEPpkiStatus = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 3} + oidSCEPfailInfo = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 4} + oidSCEPsenderNonce = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 5} + oidSCEPrecipientNonce = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 6} + oidSCEPtransactionID = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 7} + oidChallengePassword = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 7} +) + +// PKIMessage defines the possible SCEP message types +type PKIMessage struct { + microscep.TransactionID + microscep.MessageType + microscep.SenderNonce + *microscep.CSRReqMessage + + *CertRepMessage + + // DER Encoded PKIMessage + Raw []byte + + // parsed + p7 *pkcs7.PKCS7 + + // decrypted enveloped content + pkiEnvelope []byte + + // Used to sign message + Recipients []*x509.Certificate +} + +// CertRepMessage is a type of PKIMessage +type CertRepMessage struct { + microscep.PKIStatus + microscep.RecipientNonce + microscep.FailInfo + + Certificate *x509.Certificate + + degenerate []byte +} diff --git a/scep/service.go b/scep/service.go new file mode 100644 index 00000000..1d743dd6 --- /dev/null +++ b/scep/service.go @@ -0,0 +1,9 @@ +package scep + +import "crypto" + +// Service is a (temporary?) wrapper for signer/decrypters +type Service struct { + Signer crypto.Signer + Decrypter crypto.Decrypter +} From 80026e10162bb0c61d9847e5492f19c4cf11af66 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Feb 2021 00:55:37 +0100 Subject: [PATCH 30/89] Remove the copy of mozilla/pkcs7 Apparently the existing library works out of the box, after all. We'll have to see how it works out continuing forward. --- scep/authority.go | 4 +- scep/pkcs7/.gitignore | 24 -- scep/pkcs7/.travis.yml | 10 - scep/pkcs7/LICENSE | 22 -- scep/pkcs7/Makefile | 20 -- scep/pkcs7/README.md | 69 ---- scep/pkcs7/ber.go | 251 ------------- scep/pkcs7/ber_test.go | 62 ---- scep/pkcs7/decrypt.go | 177 --------- scep/pkcs7/decrypt_test.go | 60 ---- scep/pkcs7/encrypt.go | 399 --------------------- scep/pkcs7/encrypt_test.go | 102 ------ scep/pkcs7/pkcs7.go | 291 --------------- scep/pkcs7/pkcs7_test.go | 326 ----------------- scep/pkcs7/sign.go | 429 ---------------------- scep/pkcs7/sign_test.go | 266 -------------- scep/pkcs7/verify.go | 264 -------------- scep/pkcs7/verify_test.go | 713 ------------------------------------- scep/scep.go | 4 +- 19 files changed, 6 insertions(+), 3487 deletions(-) delete mode 100644 scep/pkcs7/.gitignore delete mode 100644 scep/pkcs7/.travis.yml delete mode 100644 scep/pkcs7/LICENSE delete mode 100644 scep/pkcs7/Makefile delete mode 100644 scep/pkcs7/README.md delete mode 100644 scep/pkcs7/ber.go delete mode 100644 scep/pkcs7/ber_test.go delete mode 100644 scep/pkcs7/decrypt.go delete mode 100644 scep/pkcs7/decrypt_test.go delete mode 100644 scep/pkcs7/encrypt.go delete mode 100644 scep/pkcs7/encrypt_test.go delete mode 100644 scep/pkcs7/pkcs7.go delete mode 100644 scep/pkcs7/pkcs7_test.go delete mode 100644 scep/pkcs7/sign.go delete mode 100644 scep/pkcs7/sign_test.go delete mode 100644 scep/pkcs7/verify.go delete mode 100644 scep/pkcs7/verify_test.go diff --git a/scep/authority.go b/scep/authority.go index 244a6f92..864ecbba 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -18,7 +18,9 @@ import ( microx509util "github.com/micromdm/scep/crypto/x509util" microscep "github.com/micromdm/scep/scep" - "github.com/smallstep/certificates/scep/pkcs7" + //"github.com/smallstep/certificates/scep/pkcs7" + + "go.mozilla.org/pkcs7" "go.step.sm/crypto/x509util" ) diff --git a/scep/pkcs7/.gitignore b/scep/pkcs7/.gitignore deleted file mode 100644 index daf913b1..00000000 --- a/scep/pkcs7/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test -*.prof diff --git a/scep/pkcs7/.travis.yml b/scep/pkcs7/.travis.yml deleted file mode 100644 index eac4c176..00000000 --- a/scep/pkcs7/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: go -go: - - "1.11" - - "1.12" - - "1.13" - - tip -before_install: - - make gettools -script: - - make diff --git a/scep/pkcs7/LICENSE b/scep/pkcs7/LICENSE deleted file mode 100644 index 75f32090..00000000 --- a/scep/pkcs7/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015 Andrew Smith - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - diff --git a/scep/pkcs7/Makefile b/scep/pkcs7/Makefile deleted file mode 100644 index 47c73b86..00000000 --- a/scep/pkcs7/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -all: vet staticcheck test - -test: - go test -covermode=count -coverprofile=coverage.out . - -showcoverage: test - go tool cover -html=coverage.out - -vet: - go vet . - -lint: - golint . - -staticcheck: - staticcheck . - -gettools: - go get -u honnef.co/go/tools/... - go get -u golang.org/x/lint/golint diff --git a/scep/pkcs7/README.md b/scep/pkcs7/README.md deleted file mode 100644 index bf37059c..00000000 --- a/scep/pkcs7/README.md +++ /dev/null @@ -1,69 +0,0 @@ -# pkcs7 - -[![GoDoc](https://godoc.org/go.mozilla.org/pkcs7?status.svg)](https://godoc.org/go.mozilla.org/pkcs7) -[![Build Status](https://travis-ci.org/mozilla-services/pkcs7.svg?branch=master)](https://travis-ci.org/mozilla-services/pkcs7) - -pkcs7 implements parsing and creating signed and enveloped messages. - -```go -package main - -import ( - "bytes" - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "fmt" - "os" - - "go.mozilla.org/pkcs7" -) - -func SignAndDetach(content []byte, cert *x509.Certificate, privkey *rsa.PrivateKey) (signed []byte, err error) { - toBeSigned, err := NewSignedData(content) - if err != nil { - err = fmt.Errorf("Cannot initialize signed data: %s", err) - return - } - if err = toBeSigned.AddSigner(cert, privkey, SignerInfoConfig{}); err != nil { - err = fmt.Errorf("Cannot add signer: %s", err) - return - } - - // Detach signature, omit if you want an embedded signature - toBeSigned.Detach() - - signed, err = toBeSigned.Finish() - if err != nil { - err = fmt.Errorf("Cannot finish signing data: %s", err) - return - } - - // Verify the signature - pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: signed}) - p7, err := pkcs7.Parse(signed) - if err != nil { - err = fmt.Errorf("Cannot parse our signed data: %s", err) - return - } - - // since the signature was detached, reattach the content here - p7.Content = content - - if bytes.Compare(content, p7.Content) != 0 { - err = fmt.Errorf("Our content was not in the parsed data:\n\tExpected: %s\n\tActual: %s", content, p7.Content) - return - } - if err = p7.Verify(); err != nil { - err = fmt.Errorf("Cannot verify our signed data: %s", err) - return - } - - return signed, nil -} -``` - - - -## Credits -This is a fork of [fullsailor/pkcs7](https://github.com/fullsailor/pkcs7) diff --git a/scep/pkcs7/ber.go b/scep/pkcs7/ber.go deleted file mode 100644 index 58525673..00000000 --- a/scep/pkcs7/ber.go +++ /dev/null @@ -1,251 +0,0 @@ -package pkcs7 - -import ( - "bytes" - "errors" -) - -var encodeIndent = 0 - -type asn1Object interface { - EncodeTo(writer *bytes.Buffer) error -} - -type asn1Structured struct { - tagBytes []byte - content []asn1Object -} - -func (s asn1Structured) EncodeTo(out *bytes.Buffer) error { - //fmt.Printf("%s--> tag: % X\n", strings.Repeat("| ", encodeIndent), s.tagBytes) - encodeIndent++ - inner := new(bytes.Buffer) - for _, obj := range s.content { - err := obj.EncodeTo(inner) - if err != nil { - return err - } - } - encodeIndent-- - out.Write(s.tagBytes) - encodeLength(out, inner.Len()) - out.Write(inner.Bytes()) - return nil -} - -type asn1Primitive struct { - tagBytes []byte - length int - content []byte -} - -func (p asn1Primitive) EncodeTo(out *bytes.Buffer) error { - _, err := out.Write(p.tagBytes) - if err != nil { - return err - } - if err = encodeLength(out, p.length); err != nil { - return err - } - //fmt.Printf("%s--> tag: % X length: %d\n", strings.Repeat("| ", encodeIndent), p.tagBytes, p.length) - //fmt.Printf("%s--> content length: %d\n", strings.Repeat("| ", encodeIndent), len(p.content)) - out.Write(p.content) - - return nil -} - -func ber2der(ber []byte) ([]byte, error) { - if len(ber) == 0 { - return nil, errors.New("ber2der: input ber is empty") - } - //fmt.Printf("--> ber2der: Transcoding %d bytes\n", len(ber)) - out := new(bytes.Buffer) - - obj, _, err := readObject(ber, 0) - if err != nil { - return nil, err - } - obj.EncodeTo(out) - - // if offset < len(ber) { - // return nil, fmt.Errorf("ber2der: Content longer than expected. Got %d, expected %d", offset, len(ber)) - //} - - return out.Bytes(), nil -} - -// encodes lengths that are longer than 127 into string of bytes -func marshalLongLength(out *bytes.Buffer, i int) (err error) { - n := lengthLength(i) - - for ; n > 0; n-- { - err = out.WriteByte(byte(i >> uint((n-1)*8))) - if err != nil { - return - } - } - - return nil -} - -// computes the byte length of an encoded length value -func lengthLength(i int) (numBytes int) { - numBytes = 1 - for i > 255 { - numBytes++ - i >>= 8 - } - return -} - -// encodes the length in DER format -// If the length fits in 7 bits, the value is encoded directly. -// -// Otherwise, the number of bytes to encode the length is first determined. -// This number is likely to be 4 or less for a 32bit length. This number is -// added to 0x80. The length is encoded in big endian encoding follow after -// -// Examples: -// length | byte 1 | bytes n -// 0 | 0x00 | - -// 120 | 0x78 | - -// 200 | 0x81 | 0xC8 -// 500 | 0x82 | 0x01 0xF4 -// -func encodeLength(out *bytes.Buffer, length int) (err error) { - if length >= 128 { - l := lengthLength(length) - err = out.WriteByte(0x80 | byte(l)) - if err != nil { - return - } - err = marshalLongLength(out, length) - if err != nil { - return - } - } else { - err = out.WriteByte(byte(length)) - if err != nil { - return - } - } - return -} - -func readObject(ber []byte, offset int) (asn1Object, int, error) { - berLen := len(ber) - if offset >= berLen { - return nil, 0, errors.New("ber2der: offset is after end of ber data") - } - tagStart := offset - b := ber[offset] - offset++ - if offset >= berLen { - return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") - } - tag := b & 0x1F // last 5 bits - if tag == 0x1F { - tag = 0 - for ber[offset] >= 0x80 { - tag = tag*128 + ber[offset] - 0x80 - offset++ - if offset > berLen { - return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") - } - } - // jvehent 20170227: this doesn't appear to be used anywhere... - //tag = tag*128 + ber[offset] - 0x80 - offset++ - if offset > berLen { - return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") - } - } - tagEnd := offset - - kind := b & 0x20 - if kind == 0 { - debugprint("--> Primitive\n") - } else { - debugprint("--> Constructed\n") - } - // read length - var length int - l := ber[offset] - offset++ - if offset > berLen { - return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") - } - hack := 0 - if l > 0x80 { - numberOfBytes := (int)(l & 0x7F) - if numberOfBytes > 4 { // int is only guaranteed to be 32bit - return nil, 0, errors.New("ber2der: BER tag length too long") - } - if numberOfBytes == 4 && (int)(ber[offset]) > 0x7F { - return nil, 0, errors.New("ber2der: BER tag length is negative") - } - if (int)(ber[offset]) == 0x0 { - return nil, 0, errors.New("ber2der: BER tag length has leading zero") - } - debugprint("--> (compute length) indicator byte: %x\n", l) - debugprint("--> (compute length) length bytes: % X\n", ber[offset:offset+numberOfBytes]) - for i := 0; i < numberOfBytes; i++ { - length = length*256 + (int)(ber[offset]) - offset++ - if offset > berLen { - return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") - } - } - } else if l == 0x80 { - // find length by searching content - markerIndex := bytes.LastIndex(ber[offset:], []byte{0x0, 0x0}) - if markerIndex == -1 { - return nil, 0, errors.New("ber2der: Invalid BER format") - } - length = markerIndex - hack = 2 - debugprint("--> (compute length) marker found at offset: %d\n", markerIndex+offset) - } else { - length = (int)(l) - } - if length < 0 { - return nil, 0, errors.New("ber2der: invalid negative value found in BER tag length") - } - //fmt.Printf("--> length : %d\n", length) - contentEnd := offset + length - if contentEnd > len(ber) { - return nil, 0, errors.New("ber2der: BER tag length is more than available data") - } - debugprint("--> content start : %d\n", offset) - debugprint("--> content end : %d\n", contentEnd) - debugprint("--> content : % X\n", ber[offset:contentEnd]) - var obj asn1Object - if kind == 0 { - obj = asn1Primitive{ - tagBytes: ber[tagStart:tagEnd], - length: length, - content: ber[offset:contentEnd], - } - } else { - var subObjects []asn1Object - for offset < contentEnd { - var subObj asn1Object - var err error - subObj, offset, err = readObject(ber[:contentEnd], offset) - if err != nil { - return nil, 0, err - } - subObjects = append(subObjects, subObj) - } - obj = asn1Structured{ - tagBytes: ber[tagStart:tagEnd], - content: subObjects, - } - } - - return obj, contentEnd + hack, nil -} - -func debugprint(format string, a ...interface{}) { - //fmt.Printf(format, a) -} diff --git a/scep/pkcs7/ber_test.go b/scep/pkcs7/ber_test.go deleted file mode 100644 index fcb4b6a2..00000000 --- a/scep/pkcs7/ber_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package pkcs7 - -import ( - "bytes" - "encoding/asn1" - "strings" - "testing" -) - -func TestBer2Der(t *testing.T) { - // indefinite length fixture - ber := []byte{0x30, 0x80, 0x02, 0x01, 0x01, 0x00, 0x00} - expected := []byte{0x30, 0x03, 0x02, 0x01, 0x01} - der, err := ber2der(ber) - if err != nil { - t.Fatalf("ber2der failed with error: %v", err) - } - if !bytes.Equal(der, expected) { - t.Errorf("ber2der result did not match.\n\tExpected: % X\n\tActual: % X", expected, der) - } - - if der2, err := ber2der(der); err != nil { - t.Errorf("ber2der on DER bytes failed with error: %v", err) - } else { - if !bytes.Equal(der, der2) { - t.Error("ber2der is not idempotent") - } - } - var thing struct { - Number int - } - rest, err := asn1.Unmarshal(der, &thing) - if err != nil { - t.Errorf("Cannot parse resulting DER because: %v", err) - } else if len(rest) > 0 { - t.Errorf("Resulting DER has trailing data: % X", rest) - } -} - -func TestBer2Der_Negatives(t *testing.T) { - fixtures := []struct { - Input []byte - ErrorContains string - }{ - {[]byte{0x30, 0x85}, "tag length too long"}, - {[]byte{0x30, 0x84, 0x80, 0x0, 0x0, 0x0}, "length is negative"}, - {[]byte{0x30, 0x82, 0x0, 0x1}, "length has leading zero"}, - {[]byte{0x30, 0x80, 0x1, 0x2}, "Invalid BER format"}, - {[]byte{0x30, 0x03, 0x01, 0x02}, "length is more than available data"}, - {[]byte{0x30}, "end of ber data reached"}, - } - - for _, fixture := range fixtures { - _, err := ber2der(fixture.Input) - if err == nil { - t.Errorf("No error thrown. Expected: %s", fixture.ErrorContains) - } - if !strings.Contains(err.Error(), fixture.ErrorContains) { - t.Errorf("Unexpected error thrown.\n\tExpected: /%s/\n\tActual: %s", fixture.ErrorContains, err.Error()) - } - } -} diff --git a/scep/pkcs7/decrypt.go b/scep/pkcs7/decrypt.go deleted file mode 100644 index 0d088d62..00000000 --- a/scep/pkcs7/decrypt.go +++ /dev/null @@ -1,177 +0,0 @@ -package pkcs7 - -import ( - "bytes" - "crypto" - "crypto/aes" - "crypto/cipher" - "crypto/des" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "encoding/asn1" - "errors" - "fmt" -) - -// ErrUnsupportedAlgorithm tells you when our quick dev assumptions have failed -var ErrUnsupportedAlgorithm = errors.New("pkcs7: cannot decrypt data: only RSA, DES, DES-EDE3, AES-256-CBC and AES-128-GCM supported") - -// ErrNotEncryptedContent is returned when attempting to Decrypt data that is not encrypted data -var ErrNotEncryptedContent = errors.New("pkcs7: content data is a decryptable data type") - -// Decrypt decrypts encrypted content info for recipient cert and private key -func (p7 *PKCS7) Decrypt(cert *x509.Certificate, pkey crypto.PrivateKey) ([]byte, error) { - data, ok := p7.raw.(envelopedData) - if !ok { - return nil, ErrNotEncryptedContent - } - recipient := selectRecipientForCertificate(data.RecipientInfos, cert) - if recipient.EncryptedKey == nil { - return nil, errors.New("pkcs7: no enveloped recipient for provided certificate") - } - switch pkey := pkey.(type) { - case *rsa.PrivateKey: - var contentKey []byte - contentKey, err := rsa.DecryptPKCS1v15(rand.Reader, pkey, recipient.EncryptedKey) - if err != nil { - return nil, err - } - return data.EncryptedContentInfo.decrypt(contentKey) - } - return nil, ErrUnsupportedAlgorithm -} - -// DecryptUsingPSK decrypts encrypted data using caller provided -// pre-shared secret -func (p7 *PKCS7) DecryptUsingPSK(key []byte) ([]byte, error) { - data, ok := p7.raw.(encryptedData) - if !ok { - return nil, ErrNotEncryptedContent - } - return data.EncryptedContentInfo.decrypt(key) -} - -func (eci encryptedContentInfo) decrypt(key []byte) ([]byte, error) { - alg := eci.ContentEncryptionAlgorithm.Algorithm - if !alg.Equal(OIDEncryptionAlgorithmDESCBC) && - !alg.Equal(OIDEncryptionAlgorithmDESEDE3CBC) && - !alg.Equal(OIDEncryptionAlgorithmAES256CBC) && - !alg.Equal(OIDEncryptionAlgorithmAES128CBC) && - !alg.Equal(OIDEncryptionAlgorithmAES128GCM) && - !alg.Equal(OIDEncryptionAlgorithmAES256GCM) { - fmt.Printf("Unsupported Content Encryption Algorithm: %s\n", alg) - return nil, ErrUnsupportedAlgorithm - } - - // EncryptedContent can either be constructed of multple OCTET STRINGs - // or _be_ a tagged OCTET STRING - var cyphertext []byte - if eci.EncryptedContent.IsCompound { - // Complex case to concat all of the children OCTET STRINGs - var buf bytes.Buffer - cypherbytes := eci.EncryptedContent.Bytes - for { - var part []byte - cypherbytes, _ = asn1.Unmarshal(cypherbytes, &part) - buf.Write(part) - if cypherbytes == nil { - break - } - } - cyphertext = buf.Bytes() - } else { - // Simple case, the bytes _are_ the cyphertext - cyphertext = eci.EncryptedContent.Bytes - } - - var block cipher.Block - var err error - - switch { - case alg.Equal(OIDEncryptionAlgorithmDESCBC): - block, err = des.NewCipher(key) - case alg.Equal(OIDEncryptionAlgorithmDESEDE3CBC): - block, err = des.NewTripleDESCipher(key) - case alg.Equal(OIDEncryptionAlgorithmAES256CBC), alg.Equal(OIDEncryptionAlgorithmAES256GCM): - fallthrough - case alg.Equal(OIDEncryptionAlgorithmAES128GCM), alg.Equal(OIDEncryptionAlgorithmAES128CBC): - block, err = aes.NewCipher(key) - } - - if err != nil { - return nil, err - } - - if alg.Equal(OIDEncryptionAlgorithmAES128GCM) || alg.Equal(OIDEncryptionAlgorithmAES256GCM) { - params := aesGCMParameters{} - paramBytes := eci.ContentEncryptionAlgorithm.Parameters.Bytes - - _, err := asn1.Unmarshal(paramBytes, ¶ms) - if err != nil { - return nil, err - } - - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - - if len(params.Nonce) != gcm.NonceSize() { - return nil, errors.New("pkcs7: encryption algorithm parameters are incorrect") - } - if params.ICVLen != gcm.Overhead() { - return nil, errors.New("pkcs7: encryption algorithm parameters are incorrect") - } - - plaintext, err := gcm.Open(nil, params.Nonce, cyphertext, nil) - if err != nil { - return nil, err - } - - return plaintext, nil - } - - iv := eci.ContentEncryptionAlgorithm.Parameters.Bytes - if len(iv) != block.BlockSize() { - return nil, errors.New("pkcs7: encryption algorithm parameters are malformed") - } - mode := cipher.NewCBCDecrypter(block, iv) - plaintext := make([]byte, len(cyphertext)) - mode.CryptBlocks(plaintext, cyphertext) - if plaintext, err = unpad(plaintext, mode.BlockSize()); err != nil { - return nil, err - } - return plaintext, nil -} - -func unpad(data []byte, blocklen int) ([]byte, error) { - if blocklen < 1 { - return nil, fmt.Errorf("invalid blocklen %d", blocklen) - } - if len(data)%blocklen != 0 || len(data) == 0 { - return nil, fmt.Errorf("invalid data len %d", len(data)) - } - - // the last byte is the length of padding - padlen := int(data[len(data)-1]) - - // check padding integrity, all bytes should be the same - pad := data[len(data)-padlen:] - for _, padbyte := range pad { - if padbyte != byte(padlen) { - return nil, errors.New("invalid padding") - } - } - - return data[:len(data)-padlen], nil -} - -func selectRecipientForCertificate(recipients []recipientInfo, cert *x509.Certificate) recipientInfo { - for _, recp := range recipients { - if isCertMatchForIssuerAndSerial(cert, recp.IssuerAndSerialNumber) { - return recp - } - } - return recipientInfo{} -} diff --git a/scep/pkcs7/decrypt_test.go b/scep/pkcs7/decrypt_test.go deleted file mode 100644 index 06cc0f80..00000000 --- a/scep/pkcs7/decrypt_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package pkcs7 - -import ( - "bytes" - "testing" -) - -func TestDecrypt(t *testing.T) { - fixture := UnmarshalTestFixture(EncryptedTestFixture) - p7, err := Parse(fixture.Input) - if err != nil { - t.Fatal(err) - } - content, err := p7.Decrypt(fixture.Certificate, fixture.PrivateKey) - if err != nil { - t.Errorf("Cannot Decrypt with error: %v", err) - } - expected := []byte("This is a test") - if !bytes.Equal(content, expected) { - t.Errorf("Decrypted result does not match.\n\tExpected:%s\n\tActual:%s", expected, content) - } -} - -// Content is "This is a test" -var EncryptedTestFixture = ` ------BEGIN PKCS7----- -MIIBFwYJKoZIhvcNAQcDoIIBCDCCAQQCAQAxgcowgccCAQAwMjApMRAwDgYDVQQK -EwdBY21lIENvMRUwEwYDVQQDEwxFZGRhcmQgU3RhcmsCBQDL+CvWMAsGCSqGSIb3 -DQEBAQSBgKyP/5WlRTZD3dWMrLOX6QRNDrXEkQjhmToRwFZdY3LgUh25ZU0S/q4G -dHPV21Fv9lQD+q7l3vfeHw8M6Z1PKi9sHMVfxAkQpvaI96DTIT3YHtuLC1w3geCO -8eFWTq2qS4WChSuS/yhYosjA1kTkE0eLnVZcGw0z/WVuEZznkdyIMDIGCSqGSIb3 -DQEHATARBgUrDgMCBwQImpKsUyMPpQigEgQQRcWWrCRXqpD5Njs0GkJl+g== ------END PKCS7----- ------BEGIN CERTIFICATE----- -MIIB1jCCAUGgAwIBAgIFAMv4K9YwCwYJKoZIhvcNAQELMCkxEDAOBgNVBAoTB0Fj -bWUgQ28xFTATBgNVBAMTDEVkZGFyZCBTdGFyazAeFw0xNTA1MDYwMzU2NDBaFw0x -NjA1MDYwMzU2NDBaMCUxEDAOBgNVBAoTB0FjbWUgQ28xETAPBgNVBAMTCEpvbiBT -bm93MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDK6NU0R0eiCYVquU4RcjKc -LzGfx0aa1lMr2TnLQUSeLFZHFxsyyMXXuMPig3HK4A7SGFHupO+/1H/sL4xpH5zg -8+Zg2r8xnnney7abxcuv0uATWSIeKlNnb1ZO1BAxFnESc3GtyOCr2dUwZHX5mRVP -+Zxp2ni5qHNraf3wE2VPIQIDAQABoxIwEDAOBgNVHQ8BAf8EBAMCAKAwCwYJKoZI -hvcNAQELA4GBAIr2F7wsqmEU/J/kLyrCgEVXgaV/sKZq4pPNnzS0tBYk8fkV3V18 -sBJyHKRLL/wFZASvzDcVGCplXyMdAOCyfd8jO3F9Ac/xdlz10RrHJT75hNu3a7/n -9KNwKhfN4A1CQv2x372oGjRhCW5bHNCWx4PIVeNzCyq/KZhyY9sxHE6f ------END CERTIFICATE----- ------BEGIN PRIVATE KEY----- -MIICXgIBAAKBgQDK6NU0R0eiCYVquU4RcjKcLzGfx0aa1lMr2TnLQUSeLFZHFxsy -yMXXuMPig3HK4A7SGFHupO+/1H/sL4xpH5zg8+Zg2r8xnnney7abxcuv0uATWSIe -KlNnb1ZO1BAxFnESc3GtyOCr2dUwZHX5mRVP+Zxp2ni5qHNraf3wE2VPIQIDAQAB -AoGBALyvnSt7KUquDen7nXQtvJBudnf9KFPt//OjkdHHxNZNpoF/JCSqfQeoYkeu -MdAVYNLQGMiRifzZz4dDhA9xfUAuy7lcGQcMCxEQ1dwwuFaYkawbS0Tvy2PFlq2d -H5/HeDXU4EDJ3BZg0eYj2Bnkt1sJI35UKQSxblQ0MY2q0uFBAkEA5MMOogkgUx1C -67S1tFqMUSM8D0mZB0O5vOJZC5Gtt2Urju6vywge2ArExWRXlM2qGl8afFy2SgSv -Xk5eybcEiQJBAOMRwwbEoW5NYHuFFbSJyWll4n71CYuWuQOCzehDPyTb80WFZGLV -i91kFIjeERyq88eDE5xVB3ZuRiXqaShO/9kCQQCKOEkpInaDgZSjskZvuJ47kByD -6CYsO4GIXQMMeHML8ncFH7bb6AYq5ybJVb2NTU7QLFJmfeYuhvIm+xdOreRxAkEA -o5FC5Jg2FUfFzZSDmyZ6IONUsdF/i78KDV5nRv1R+hI6/oRlWNCtTNBv/lvBBd6b -dseUE9QoaQZsn5lpILEvmQJAZ0B+Or1rAYjnbjnUhdVZoy9kC4Zov+4UH3N/BtSy -KJRWUR0wTWfZBPZ5hAYZjTBEAFULaYCXlQKsODSp0M1aQA== ------END PRIVATE KEY-----` diff --git a/scep/pkcs7/encrypt.go b/scep/pkcs7/encrypt.go deleted file mode 100644 index da57ae64..00000000 --- a/scep/pkcs7/encrypt.go +++ /dev/null @@ -1,399 +0,0 @@ -package pkcs7 - -import ( - "bytes" - "crypto/aes" - "crypto/cipher" - "crypto/des" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "errors" - "fmt" -) - -type envelopedData struct { - Version int - RecipientInfos []recipientInfo `asn1:"set"` - EncryptedContentInfo encryptedContentInfo -} - -type encryptedData struct { - Version int - EncryptedContentInfo encryptedContentInfo -} - -type recipientInfo struct { - Version int - IssuerAndSerialNumber issuerAndSerial - KeyEncryptionAlgorithm pkix.AlgorithmIdentifier - EncryptedKey []byte -} - -type encryptedContentInfo struct { - ContentType asn1.ObjectIdentifier - ContentEncryptionAlgorithm pkix.AlgorithmIdentifier - EncryptedContent asn1.RawValue `asn1:"tag:0,optional,explicit"` -} - -const ( - // EncryptionAlgorithmDESCBC is the DES CBC encryption algorithm - EncryptionAlgorithmDESCBC = iota - - // EncryptionAlgorithmAES128CBC is the AES 128 bits with CBC encryption algorithm - // Avoid this algorithm unless required for interoperability; use AES GCM instead. - EncryptionAlgorithmAES128CBC - - // EncryptionAlgorithmAES256CBC is the AES 256 bits with CBC encryption algorithm - // Avoid this algorithm unless required for interoperability; use AES GCM instead. - EncryptionAlgorithmAES256CBC - - // EncryptionAlgorithmAES128GCM is the AES 128 bits with GCM encryption algorithm - EncryptionAlgorithmAES128GCM - - // EncryptionAlgorithmAES256GCM is the AES 256 bits with GCM encryption algorithm - EncryptionAlgorithmAES256GCM -) - -// ContentEncryptionAlgorithm determines the algorithm used to encrypt the -// plaintext message. Change the value of this variable to change which -// algorithm is used in the Encrypt() function. -var ContentEncryptionAlgorithm = EncryptionAlgorithmDESCBC - -// ErrUnsupportedEncryptionAlgorithm is returned when attempting to encrypt -// content with an unsupported algorithm. -var ErrUnsupportedEncryptionAlgorithm = errors.New("pkcs7: cannot encrypt content: only DES-CBC, AES-CBC, and AES-GCM supported") - -// ErrPSKNotProvided is returned when attempting to encrypt -// using a PSK without actually providing the PSK. -var ErrPSKNotProvided = errors.New("pkcs7: cannot encrypt content: PSK not provided") - -const nonceSize = 12 - -type aesGCMParameters struct { - Nonce []byte `asn1:"tag:4"` - ICVLen int -} - -func encryptAESGCM(content []byte, key []byte) ([]byte, *encryptedContentInfo, error) { - var keyLen int - var algID asn1.ObjectIdentifier - switch ContentEncryptionAlgorithm { - case EncryptionAlgorithmAES128GCM: - keyLen = 16 - algID = OIDEncryptionAlgorithmAES128GCM - case EncryptionAlgorithmAES256GCM: - keyLen = 32 - algID = OIDEncryptionAlgorithmAES256GCM - default: - return nil, nil, fmt.Errorf("invalid ContentEncryptionAlgorithm in encryptAESGCM: %d", ContentEncryptionAlgorithm) - } - if key == nil { - // Create AES key - key = make([]byte, keyLen) - - _, err := rand.Read(key) - if err != nil { - return nil, nil, err - } - } - - // Create nonce - nonce := make([]byte, nonceSize) - - _, err := rand.Read(nonce) - if err != nil { - return nil, nil, err - } - - // Encrypt content - block, err := aes.NewCipher(key) - if err != nil { - return nil, nil, err - } - - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, nil, err - } - - ciphertext := gcm.Seal(nil, nonce, content, nil) - - // Prepare ASN.1 Encrypted Content Info - paramSeq := aesGCMParameters{ - Nonce: nonce, - ICVLen: gcm.Overhead(), - } - - paramBytes, err := asn1.Marshal(paramSeq) - if err != nil { - return nil, nil, err - } - - eci := encryptedContentInfo{ - ContentType: OIDData, - ContentEncryptionAlgorithm: pkix.AlgorithmIdentifier{ - Algorithm: algID, - Parameters: asn1.RawValue{ - Tag: asn1.TagSequence, - Bytes: paramBytes, - }, - }, - EncryptedContent: marshalEncryptedContent(ciphertext), - } - - return key, &eci, nil -} - -func encryptDESCBC(content []byte, key []byte) ([]byte, *encryptedContentInfo, error) { - if key == nil { - // Create DES key - key = make([]byte, 8) - - _, err := rand.Read(key) - if err != nil { - return nil, nil, err - } - } - - // Create CBC IV - iv := make([]byte, des.BlockSize) - _, err := rand.Read(iv) - if err != nil { - return nil, nil, err - } - - // Encrypt padded content - block, err := des.NewCipher(key) - if err != nil { - return nil, nil, err - } - mode := cipher.NewCBCEncrypter(block, iv) - plaintext, err := pad(content, mode.BlockSize()) - if err != nil { - return nil, nil, err - } - cyphertext := make([]byte, len(plaintext)) - mode.CryptBlocks(cyphertext, plaintext) - - // Prepare ASN.1 Encrypted Content Info - eci := encryptedContentInfo{ - ContentType: OIDData, - ContentEncryptionAlgorithm: pkix.AlgorithmIdentifier{ - Algorithm: OIDEncryptionAlgorithmDESCBC, - Parameters: asn1.RawValue{Tag: 4, Bytes: iv}, - }, - EncryptedContent: marshalEncryptedContent(cyphertext), - } - - return key, &eci, nil -} - -func encryptAESCBC(content []byte, key []byte) ([]byte, *encryptedContentInfo, error) { - var keyLen int - var algID asn1.ObjectIdentifier - switch ContentEncryptionAlgorithm { - case EncryptionAlgorithmAES128CBC: - keyLen = 16 - algID = OIDEncryptionAlgorithmAES128CBC - case EncryptionAlgorithmAES256CBC: - keyLen = 32 - algID = OIDEncryptionAlgorithmAES256CBC - default: - return nil, nil, fmt.Errorf("invalid ContentEncryptionAlgorithm in encryptAESCBC: %d", ContentEncryptionAlgorithm) - } - - if key == nil { - // Create AES key - key = make([]byte, keyLen) - - _, err := rand.Read(key) - if err != nil { - return nil, nil, err - } - } - - // Create CBC IV - iv := make([]byte, aes.BlockSize) - _, err := rand.Read(iv) - if err != nil { - return nil, nil, err - } - - // Encrypt padded content - block, err := aes.NewCipher(key) - if err != nil { - return nil, nil, err - } - mode := cipher.NewCBCEncrypter(block, iv) - plaintext, err := pad(content, mode.BlockSize()) - if err != nil { - return nil, nil, err - } - cyphertext := make([]byte, len(plaintext)) - mode.CryptBlocks(cyphertext, plaintext) - - // Prepare ASN.1 Encrypted Content Info - eci := encryptedContentInfo{ - ContentType: OIDData, - ContentEncryptionAlgorithm: pkix.AlgorithmIdentifier{ - Algorithm: algID, - Parameters: asn1.RawValue{Tag: 4, Bytes: iv}, - }, - EncryptedContent: marshalEncryptedContent(cyphertext), - } - - return key, &eci, nil -} - -// Encrypt creates and returns an envelope data PKCS7 structure with encrypted -// recipient keys for each recipient public key. -// -// The algorithm used to perform encryption is determined by the current value -// of the global ContentEncryptionAlgorithm package variable. By default, the -// value is EncryptionAlgorithmDESCBC. To use a different algorithm, change the -// value before calling Encrypt(). For example: -// -// ContentEncryptionAlgorithm = EncryptionAlgorithmAES128GCM -// -// TODO(fullsailor): Add support for encrypting content with other algorithms -func Encrypt(content []byte, recipients []*x509.Certificate) ([]byte, error) { - var eci *encryptedContentInfo - var key []byte - var err error - - // Apply chosen symmetric encryption method - switch ContentEncryptionAlgorithm { - case EncryptionAlgorithmDESCBC: - key, eci, err = encryptDESCBC(content, nil) - case EncryptionAlgorithmAES128CBC: - fallthrough - case EncryptionAlgorithmAES256CBC: - key, eci, err = encryptAESCBC(content, nil) - case EncryptionAlgorithmAES128GCM: - fallthrough - case EncryptionAlgorithmAES256GCM: - key, eci, err = encryptAESGCM(content, nil) - - default: - return nil, ErrUnsupportedEncryptionAlgorithm - } - - if err != nil { - return nil, err - } - - // Prepare each recipient's encrypted cipher key - recipientInfos := make([]recipientInfo, len(recipients)) - for i, recipient := range recipients { - encrypted, err := encryptKey(key, recipient) - if err != nil { - return nil, err - } - ias, err := cert2issuerAndSerial(recipient) - if err != nil { - return nil, err - } - info := recipientInfo{ - Version: 0, - IssuerAndSerialNumber: ias, - KeyEncryptionAlgorithm: pkix.AlgorithmIdentifier{ - Algorithm: OIDEncryptionAlgorithmRSA, - }, - EncryptedKey: encrypted, - } - recipientInfos[i] = info - } - - // Prepare envelope content - envelope := envelopedData{ - EncryptedContentInfo: *eci, - Version: 0, - RecipientInfos: recipientInfos, - } - innerContent, err := asn1.Marshal(envelope) - if err != nil { - return nil, err - } - - // Prepare outer payload structure - wrapper := contentInfo{ - ContentType: OIDEnvelopedData, - Content: asn1.RawValue{Class: 2, Tag: 0, IsCompound: true, Bytes: innerContent}, - } - - return asn1.Marshal(wrapper) -} - -// EncryptUsingPSK creates and returns an encrypted data PKCS7 structure, -// encrypted using caller provided pre-shared secret. -func EncryptUsingPSK(content []byte, key []byte) ([]byte, error) { - var eci *encryptedContentInfo - var err error - - if key == nil { - return nil, ErrPSKNotProvided - } - - // Apply chosen symmetric encryption method - switch ContentEncryptionAlgorithm { - case EncryptionAlgorithmDESCBC: - _, eci, err = encryptDESCBC(content, key) - - case EncryptionAlgorithmAES128GCM: - fallthrough - case EncryptionAlgorithmAES256GCM: - _, eci, err = encryptAESGCM(content, key) - - default: - return nil, ErrUnsupportedEncryptionAlgorithm - } - - if err != nil { - return nil, err - } - - // Prepare encrypted-data content - ed := encryptedData{ - Version: 0, - EncryptedContentInfo: *eci, - } - innerContent, err := asn1.Marshal(ed) - if err != nil { - return nil, err - } - - // Prepare outer payload structure - wrapper := contentInfo{ - ContentType: OIDEncryptedData, - Content: asn1.RawValue{Class: 2, Tag: 0, IsCompound: true, Bytes: innerContent}, - } - - return asn1.Marshal(wrapper) -} - -func marshalEncryptedContent(content []byte) asn1.RawValue { - asn1Content, _ := asn1.Marshal(content) - return asn1.RawValue{Tag: 0, Class: 2, Bytes: asn1Content, IsCompound: true} -} - -func encryptKey(key []byte, recipient *x509.Certificate) ([]byte, error) { - if pub := recipient.PublicKey.(*rsa.PublicKey); pub != nil { - return rsa.EncryptPKCS1v15(rand.Reader, pub, key) - } - return nil, ErrUnsupportedAlgorithm -} - -func pad(data []byte, blocklen int) ([]byte, error) { - if blocklen < 1 { - return nil, fmt.Errorf("invalid blocklen %d", blocklen) - } - padlen := blocklen - (len(data) % blocklen) - if padlen == 0 { - padlen = blocklen - } - pad := bytes.Repeat([]byte{byte(padlen)}, padlen) - return append(data, pad...), nil -} diff --git a/scep/pkcs7/encrypt_test.go b/scep/pkcs7/encrypt_test.go deleted file mode 100644 index c64381e2..00000000 --- a/scep/pkcs7/encrypt_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package pkcs7 - -import ( - "bytes" - "crypto/x509" - "testing" -) - -func TestEncrypt(t *testing.T) { - modes := []int{ - EncryptionAlgorithmDESCBC, - EncryptionAlgorithmAES128CBC, - EncryptionAlgorithmAES256CBC, - EncryptionAlgorithmAES128GCM, - EncryptionAlgorithmAES256GCM, - } - sigalgs := []x509.SignatureAlgorithm{ - x509.SHA1WithRSA, - x509.SHA256WithRSA, - x509.SHA512WithRSA, - } - for _, mode := range modes { - for _, sigalg := range sigalgs { - ContentEncryptionAlgorithm = mode - - plaintext := []byte("Hello Secret World!") - cert, err := createTestCertificate(sigalg) - if err != nil { - t.Fatal(err) - } - encrypted, err := Encrypt(plaintext, []*x509.Certificate{cert.Certificate}) - if err != nil { - t.Fatal(err) - } - p7, err := Parse(encrypted) - if err != nil { - t.Fatalf("cannot Parse encrypted result: %s", err) - } - result, err := p7.Decrypt(cert.Certificate, *cert.PrivateKey) - if err != nil { - t.Fatalf("cannot Decrypt encrypted result: %s", err) - } - if !bytes.Equal(plaintext, result) { - t.Errorf("encrypted data does not match plaintext:\n\tExpected: %s\n\tActual: %s", plaintext, result) - } - } - } -} - -func TestEncryptUsingPSK(t *testing.T) { - modes := []int{ - EncryptionAlgorithmDESCBC, - EncryptionAlgorithmAES128GCM, - } - - for _, mode := range modes { - ContentEncryptionAlgorithm = mode - plaintext := []byte("Hello Secret World!") - var key []byte - - switch mode { - case EncryptionAlgorithmDESCBC: - key = []byte("64BitKey") - case EncryptionAlgorithmAES128GCM: - key = []byte("128BitKey4AESGCM") - } - ciphertext, err := EncryptUsingPSK(plaintext, key) - if err != nil { - t.Fatal(err) - } - - p7, _ := Parse(ciphertext) - result, err := p7.DecryptUsingPSK(key) - if err != nil { - t.Fatalf("cannot Decrypt encrypted result: %s", err) - } - if !bytes.Equal(plaintext, result) { - t.Errorf("encrypted data does not match plaintext:\n\tExpected: %s\n\tActual: %s", plaintext, result) - } - } -} - -func TestPad(t *testing.T) { - tests := []struct { - Original []byte - Expected []byte - BlockSize int - }{ - {[]byte{0x1, 0x2, 0x3, 0x10}, []byte{0x1, 0x2, 0x3, 0x10, 0x4, 0x4, 0x4, 0x4}, 8}, - {[]byte{0x1, 0x2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0}, []byte{0x1, 0x2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8}, 8}, - } - for _, test := range tests { - padded, err := pad(test.Original, test.BlockSize) - if err != nil { - t.Errorf("pad encountered error: %s", err) - continue - } - if !bytes.Equal(test.Expected, padded) { - t.Errorf("pad results mismatch:\n\tExpected: %X\n\tActual: %X", test.Expected, padded) - } - } -} diff --git a/scep/pkcs7/pkcs7.go b/scep/pkcs7/pkcs7.go deleted file mode 100644 index ccc6cc6d..00000000 --- a/scep/pkcs7/pkcs7.go +++ /dev/null @@ -1,291 +0,0 @@ -// Package pkcs7 implements parsing and generation of some PKCS#7 structures. -package pkcs7 - -import ( - "bytes" - "crypto" - "crypto/dsa" - "crypto/ecdsa" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "errors" - "fmt" - "sort" - - _ "crypto/sha1" // for crypto.SHA1 -) - -// PKCS7 Represents a PKCS7 structure -type PKCS7 struct { - Content []byte - Certificates []*x509.Certificate - CRLs []pkix.CertificateList - Signers []signerInfo - raw interface{} -} - -type contentInfo struct { - ContentType asn1.ObjectIdentifier - Content asn1.RawValue `asn1:"explicit,optional,tag:0"` -} - -// ErrUnsupportedContentType is returned when a PKCS7 content is not supported. -// Currently only Data (1.2.840.113549.1.7.1), Signed Data (1.2.840.113549.1.7.2), -// and Enveloped Data are supported (1.2.840.113549.1.7.3) -var ErrUnsupportedContentType = errors.New("pkcs7: cannot parse data: unimplemented content type") - -type unsignedData []byte - -var ( - // Signed Data OIDs - OIDData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1} - OIDSignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2} - OIDEnvelopedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 3} - OIDEncryptedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 6} - OIDAttributeContentType = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3} - OIDAttributeMessageDigest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 4} - OIDAttributeSigningTime = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 5} - - // Digest Algorithms - OIDDigestAlgorithmSHA1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26} - OIDDigestAlgorithmSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1} - OIDDigestAlgorithmSHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2} - OIDDigestAlgorithmSHA512 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3} - - OIDDigestAlgorithmDSA = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 1} - OIDDigestAlgorithmDSASHA1 = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 3} - - OIDDigestAlgorithmECDSASHA1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1} - OIDDigestAlgorithmECDSASHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2} - OIDDigestAlgorithmECDSASHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3} - OIDDigestAlgorithmECDSASHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4} - - // Signature Algorithms - OIDEncryptionAlgorithmRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} - OIDEncryptionAlgorithmRSASHA1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5} - OIDEncryptionAlgorithmRSASHA256 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11} - OIDEncryptionAlgorithmRSASHA384 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12} - OIDEncryptionAlgorithmRSASHA512 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13} - - OIDEncryptionAlgorithmECDSAP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7} - OIDEncryptionAlgorithmECDSAP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34} - OIDEncryptionAlgorithmECDSAP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35} - - // Encryption Algorithms - OIDEncryptionAlgorithmDESCBC = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 7} - OIDEncryptionAlgorithmDESEDE3CBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 3, 7} - OIDEncryptionAlgorithmAES256CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 42} - OIDEncryptionAlgorithmAES128GCM = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 6} - OIDEncryptionAlgorithmAES128CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 2} - OIDEncryptionAlgorithmAES256GCM = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 46} -) - -func getHashForOID(oid asn1.ObjectIdentifier) (crypto.Hash, error) { - switch { - case oid.Equal(OIDDigestAlgorithmSHA1), oid.Equal(OIDDigestAlgorithmECDSASHA1), - oid.Equal(OIDDigestAlgorithmDSA), oid.Equal(OIDDigestAlgorithmDSASHA1), - oid.Equal(OIDEncryptionAlgorithmRSA): - return crypto.SHA1, nil - case oid.Equal(OIDDigestAlgorithmSHA256), oid.Equal(OIDDigestAlgorithmECDSASHA256): - return crypto.SHA256, nil - case oid.Equal(OIDDigestAlgorithmSHA384), oid.Equal(OIDDigestAlgorithmECDSASHA384): - return crypto.SHA384, nil - case oid.Equal(OIDDigestAlgorithmSHA512), oid.Equal(OIDDigestAlgorithmECDSASHA512): - return crypto.SHA512, nil - } - return crypto.Hash(0), ErrUnsupportedAlgorithm -} - -// getDigestOIDForSignatureAlgorithm takes an x509.SignatureAlgorithm -// and returns the corresponding OID digest algorithm -func getDigestOIDForSignatureAlgorithm(digestAlg x509.SignatureAlgorithm) (asn1.ObjectIdentifier, error) { - switch digestAlg { - case x509.SHA1WithRSA, x509.ECDSAWithSHA1: - return OIDDigestAlgorithmSHA1, nil - case x509.SHA256WithRSA, x509.ECDSAWithSHA256: - return OIDDigestAlgorithmSHA256, nil - case x509.SHA384WithRSA, x509.ECDSAWithSHA384: - return OIDDigestAlgorithmSHA384, nil - case x509.SHA512WithRSA, x509.ECDSAWithSHA512: - return OIDDigestAlgorithmSHA512, nil - } - return nil, fmt.Errorf("pkcs7: cannot convert hash to oid, unknown hash algorithm") -} - -// getOIDForEncryptionAlgorithm takes the private key type of the signer and -// the OID of a digest algorithm to return the appropriate signerInfo.DigestEncryptionAlgorithm -func getOIDForEncryptionAlgorithm(pkey crypto.PrivateKey, OIDDigestAlg asn1.ObjectIdentifier) (asn1.ObjectIdentifier, error) { - switch pkey.(type) { - case *rsa.PrivateKey: - switch { - default: - return OIDEncryptionAlgorithmRSA, nil - case OIDDigestAlg.Equal(OIDEncryptionAlgorithmRSA): - return OIDEncryptionAlgorithmRSA, nil - case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA1): - return OIDEncryptionAlgorithmRSASHA1, nil - case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA256): - return OIDEncryptionAlgorithmRSASHA256, nil - case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA384): - return OIDEncryptionAlgorithmRSASHA384, nil - case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA512): - return OIDEncryptionAlgorithmRSASHA512, nil - } - case *ecdsa.PrivateKey: - switch { - case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA1): - return OIDDigestAlgorithmECDSASHA1, nil - case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA256): - return OIDDigestAlgorithmECDSASHA256, nil - case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA384): - return OIDDigestAlgorithmECDSASHA384, nil - case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA512): - return OIDDigestAlgorithmECDSASHA512, nil - } - case *dsa.PrivateKey: - return OIDDigestAlgorithmDSA, nil - } - return nil, fmt.Errorf("pkcs7: cannot convert encryption algorithm to oid, unknown private key type %T", pkey) - -} - -// Parse decodes a DER encoded PKCS7 package -func Parse(data []byte) (p7 *PKCS7, err error) { - if len(data) == 0 { - return nil, errors.New("pkcs7: input data is empty") - } - var info contentInfo - der, err := ber2der(data) - if err != nil { - return nil, err - } - rest, err := asn1.Unmarshal(der, &info) - if len(rest) > 0 { - err = asn1.SyntaxError{Msg: "trailing data"} - return - } - if err != nil { - return - } - - // fmt.Printf("--> Content Type: %s", info.ContentType) - switch { - case info.ContentType.Equal(OIDSignedData): - return parseSignedData(info.Content.Bytes) - case info.ContentType.Equal(OIDEnvelopedData): - return parseEnvelopedData(info.Content.Bytes) - case info.ContentType.Equal(OIDEncryptedData): - return parseEncryptedData(info.Content.Bytes) - } - return nil, ErrUnsupportedContentType -} - -func parseEnvelopedData(data []byte) (*PKCS7, error) { - var ed envelopedData - if _, err := asn1.Unmarshal(data, &ed); err != nil { - return nil, err - } - return &PKCS7{ - raw: ed, - }, nil -} - -func parseEncryptedData(data []byte) (*PKCS7, error) { - var ed encryptedData - if _, err := asn1.Unmarshal(data, &ed); err != nil { - return nil, err - } - return &PKCS7{ - raw: ed, - }, nil -} - -func (raw rawCertificates) Parse() ([]*x509.Certificate, error) { - if len(raw.Raw) == 0 { - return nil, nil - } - - var val asn1.RawValue - if _, err := asn1.Unmarshal(raw.Raw, &val); err != nil { - return nil, err - } - - return x509.ParseCertificates(val.Bytes) -} - -func isCertMatchForIssuerAndSerial(cert *x509.Certificate, ias issuerAndSerial) bool { - return cert.SerialNumber.Cmp(ias.SerialNumber) == 0 && bytes.Equal(cert.RawIssuer, ias.IssuerName.FullBytes) -} - -// Attribute represents a key value pair attribute. Value must be marshalable byte -// `encoding/asn1` -type Attribute struct { - Type asn1.ObjectIdentifier - Value interface{} -} - -type attributes struct { - types []asn1.ObjectIdentifier - values []interface{} -} - -// Add adds the attribute, maintaining insertion order -func (attrs *attributes) Add(attrType asn1.ObjectIdentifier, value interface{}) { - attrs.types = append(attrs.types, attrType) - attrs.values = append(attrs.values, value) -} - -type sortableAttribute struct { - SortKey []byte - Attribute attribute -} - -type attributeSet []sortableAttribute - -func (sa attributeSet) Len() int { - return len(sa) -} - -func (sa attributeSet) Less(i, j int) bool { - return bytes.Compare(sa[i].SortKey, sa[j].SortKey) < 0 -} - -func (sa attributeSet) Swap(i, j int) { - sa[i], sa[j] = sa[j], sa[i] -} - -func (sa attributeSet) Attributes() []attribute { - attrs := make([]attribute, len(sa)) - for i, attr := range sa { - attrs[i] = attr.Attribute - } - return attrs -} - -func (attrs *attributes) ForMarshalling() ([]attribute, error) { - sortables := make(attributeSet, len(attrs.types)) - for i := range sortables { - attrType := attrs.types[i] - attrValue := attrs.values[i] - asn1Value, err := asn1.Marshal(attrValue) - if err != nil { - return nil, err - } - attr := attribute{ - Type: attrType, - Value: asn1.RawValue{Tag: 17, IsCompound: true, Bytes: asn1Value}, // 17 == SET tag - } - encoded, err := asn1.Marshal(attr) - if err != nil { - return nil, err - } - sortables[i] = sortableAttribute{ - SortKey: encoded, - Attribute: attr, - } - } - sort.Sort(sortables) - return sortables.Attributes(), nil -} diff --git a/scep/pkcs7/pkcs7_test.go b/scep/pkcs7/pkcs7_test.go deleted file mode 100644 index e743192d..00000000 --- a/scep/pkcs7/pkcs7_test.go +++ /dev/null @@ -1,326 +0,0 @@ -package pkcs7 - -import ( - "crypto" - "crypto/dsa" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "fmt" - "log" - "math/big" - "os" - "time" -) - -var test1024Key, test2048Key, test3072Key, test4096Key *rsa.PrivateKey - -func init() { - test1024Key = &rsa.PrivateKey{ - PublicKey: rsa.PublicKey{ - N: fromBase10("123024078101403810516614073341068864574068590522569345017786163424062310013967742924377390210586226651760719671658568413826602264886073432535341149584680111145880576802262550990305759285883150470245429547886689754596541046564560506544976611114898883158121012232676781340602508151730773214407220733898059285561"), - E: 65537, - }, - D: fromBase10("118892427340746627750435157989073921703209000249285930635312944544706203626114423392257295670807166199489096863209592887347935991101581502404113203993092422730000157893515953622392722273095289787303943046491132467130346663160540744582438810535626328230098940583296878135092036661410664695896115177534496784545"), - Primes: []*big.Int{ - fromBase10("12172745919282672373981903347443034348576729562395784527365032103134165674508405592530417723266847908118361582847315228810176708212888860333051929276459099"), - fromBase10("10106518193772789699356660087736308350857919389391620140340519320928952625438936098550728858345355053201610649202713962702543058578827268756755006576249339"), - }, - } - test1024Key.Precompute() - test2048Key = &rsa.PrivateKey{ - PublicKey: rsa.PublicKey{ - N: fromBase10("14314132931241006650998084889274020608918049032671858325988396851334124245188214251956198731333464217832226406088020736932173064754214329009979944037640912127943488972644697423190955557435910767690712778463524983667852819010259499695177313115447116110358524558307947613422897787329221478860907963827160223559690523660574329011927531289655711860504630573766609239332569210831325633840174683944553667352219670930408593321661375473885147973879086994006440025257225431977751512374815915392249179976902953721486040787792801849818254465486633791826766873076617116727073077821584676715609985777563958286637185868165868520557"), - E: 3, - }, - D: fromBase10("9542755287494004433998723259516013739278699355114572217325597900889416163458809501304132487555642811888150937392013824621448709836142886006653296025093941418628992648429798282127303704957273845127141852309016655778568546006839666463451542076964744073572349705538631742281931858219480985907271975884773482372966847639853897890615456605598071088189838676728836833012254065983259638538107719766738032720239892094196108713378822882383694456030043492571063441943847195939549773271694647657549658603365629458610273821292232646334717612674519997533901052790334279661754176490593041941863932308687197618671528035670452762731"), - Primes: []*big.Int{ - fromBase10("130903255182996722426771613606077755295583329135067340152947172868415809027537376306193179624298874215608270802054347609836776473930072411958753044562214537013874103802006369634761074377213995983876788718033850153719421695468704276694983032644416930879093914927146648402139231293035971427838068945045019075433"), - fromBase10("109348945610485453577574767652527472924289229538286649661240938988020367005475727988253438647560958573506159449538793540472829815903949343191091817779240101054552748665267574271163617694640513549693841337820602726596756351006149518830932261246698766355347898158548465400674856021497190430791824869615170301029"), - }, - } - test2048Key.Precompute() - test3072Key = &rsa.PrivateKey{ - PublicKey: rsa.PublicKey{ - N: fromBase10("4799422180968749215324244710281712119910779465109490663934897082847293004098645365195947978124390029272750644394844443980065532911010718425428791498896288210928474905407341584968381379157418577471272697781778686372450913810019702928839200328075568223462554606149618941566459398862673532997592879359280754226882565483298027678735544377401276021471356093819491755877827249763065753555051973844057308627201762456191918852016986546071426986328720794061622370410645440235373576002278045257207695462423797272017386006110722769072206022723167102083033531426777518054025826800254337147514768377949097720074878744769255210076910190151785807232805749219196645305822228090875616900385866236956058984170647782567907618713309775105943700661530312800231153745705977436176908325539234432407050398510090070342851489496464612052853185583222422124535243967989533830816012180864309784486694786581956050902756173889941244024888811572094961378021"), - E: 65537, - }, - D: fromBase10("4068124900056380177006532461065648259352178312499768312132802353620854992915205894105621345694615110794369150964768050224096623567443679436821868510233726084582567244003894477723706516831312989564775159596496449435830457803384416702014837685962523313266832032687145914871879794104404800823188153886925022171560391765913739346955738372354826804228989767120353182641396181570533678315099748218734875742705419933837638038793286534641711407564379950728858267828581787483317040753987167237461567332386718574803231955771633274184646232632371006762852623964054645811527580417392163873708539175349637050049959954373319861427407953413018816604365474462455009323937599275324390953644555294418021286807661559165324810415569396577697316798600308544755741549699523972971375304826663847015905713096287495342701286542193782001358775773848824496321550110946106870685499577993864871847542645561943034990484973293461948058147956373115641615329"), - Primes: []*big.Int{ - fromBase10("2378529069722721185825622840841310902793949682948530343491428052737890236476884657507685118578733560141370511507721598189068683665232991988491561624429938984370132428230072355214627085652359350722926394699707232921674771664421591347888367477300909202851476404132163673865768760147403525700174918450753162242834161458300343282159799476695001920226357456953682236859505243928716782707623075239350380352265954107362618991716602898266999700316937680986690964564264877"), - fromBase10("2017811025336026464312837780072272578817919741496395062543647660689775637351085991504709917848745137013798005682591633910555599626950744674459976829106750083386168859581016361317479081273480343110649405858059581933773354781034946787147300862495438979895430001323443224335618577322449133208754541656374335100929456885995320929464029817626916719434010943205170760536768893924932021302887114400922813817969176636993508191950649313115712159241971065134077636674146073"), - }, - } - test3072Key.Precompute() - test4096Key = &rsa.PrivateKey{ - PublicKey: rsa.PublicKey{ - N: fromBase10("633335480064287130853997429184971616419051348693342219741748040433588285601270210251206421401040394238592139790962887290698043839174341843721930134010306454716566698330215646704263665452264344664385995704186692432827662862845900348526672531755932642433662686500295989783595767573119607065791980381547677840410600100715146047382485989885183858757974681241303484641390718944520330953604501686666386926996348457928415093305041429178744778762826377713889019740060910363468343855830206640274442887621960581569183233822878661711798998132931623726434336448716605363514220760343097572198620479297583609779817750646169845195672483600293522186340560792255595411601450766002877850696008003794520089358819042318331840490155176019070646738739580486357084733208876620846449161909966690602374519398451042362690200166144326179405976024265116931974936425064291406950542193873313447617169603706868220189295654943247311295475722243471700112334609817776430552541319671117235957754556272646031356496763094955985615723596562217985372503002989591679252640940571608314743271809251568670314461039035793703429977801961867815257832671786542212589906513979094156334941265621017752516999186481477500481433634914622735206243841674973785078408289183000133399026553"), - E: 65537, - }, - D: fromBase10("439373650557744155078930178606343279553665694488479749802070836418412881168612407941793966086633543867614175621952769177088930851151267623886678906158545451731745754402575409204816390946376103491325109185445659065122640946673660760274557781540431107937331701243915001777636528502669576801704352961341634812275635811512806966908648671988644114352046582195051714797831307925775689566757438907578527366568747104508496278929566712224252103563340770696548181508180254674236716995730292431858611476396845443056967589437890065663497768422598977743046882539288481002449571403783500529740184608873520856954837631427724158592309018382711485601884461168736465751756282510065053161144027097169985941910909130083273691945578478173708396726266170473745329617793866669307716920992380350270584929908460462802627239204245339385636926433446418108504614031393494119344916828744888432279343816084433424594432427362258172264834429525166677273382617457205387388293888430391895615438030066428745187333897518037597413369705720436392869403948934993623418405908467147848576977008003556716087129242155836114780890054057743164411952731290520995017097151300091841286806603044227906213832083363876549637037625314539090155417589796428888619937329669464810549362433"), - Primes: []*big.Int{ - fromBase10("25745433817240673759910623230144796182285844101796353869339294232644316274580053211056707671663014355388701931204078502829809738396303142990312095225333440050808647355535878394534263839500592870406002873182360027755750148248672968563366185348499498613479490545488025779331426515670185366021612402246813511722553210128074701620113404560399242413747318161403908617342170447610792422053460359960010544593668037305465806912471260799852789913123044326555978680190904164976511331681163576833618899773550873682147782263100803907156362439021929408298804955194748640633152519828940133338948391986823456836070708197320166146761"), - fromBase10("24599914864909676687852658457515103765368967514652318497893275892114442089314173678877914038802355565271545910572804267918959612739009937926962653912943833939518967731764560204997062096919833970670512726396663920955497151415639902788974842698619579886297871162402643104696160155894685518587660015182381685605752989716946154299190561137541792784125356553411300817844325739404126956793095254412123887617931225840421856505925283322918693259047428656823141903489964287619982295891439430302405252447010728112098326033634688757933930065610737780413018498561434074501822951716586796047404555397992425143397497639322075233073"), - }, - } - test4096Key.Precompute() -} - -func fromBase10(base10 string) *big.Int { - i, ok := new(big.Int).SetString(base10, 10) - if !ok { - panic("bad number: " + base10) - } - return i -} - -type certKeyPair struct { - Certificate *x509.Certificate - PrivateKey *crypto.PrivateKey -} - -func createTestCertificate(sigAlg x509.SignatureAlgorithm) (certKeyPair, error) { - signer, err := createTestCertificateByIssuer("Eddard Stark", nil, sigAlg, true) - if err != nil { - return certKeyPair{}, err - } - pair, err := createTestCertificateByIssuer("Jon Snow", signer, sigAlg, false) - if err != nil { - return certKeyPair{}, err - } - return *pair, nil -} - -func createTestCertificateByIssuer(name string, issuer *certKeyPair, sigAlg x509.SignatureAlgorithm, isCA bool) (*certKeyPair, error) { - var ( - err error - priv crypto.PrivateKey - derCert []byte - issuerCert *x509.Certificate - issuerKey crypto.PrivateKey - ) - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 32) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - return nil, err - } - - template := x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ - CommonName: name, - Organization: []string{"Acme Co"}, - }, - NotBefore: time.Now().Add(-1 *time.Second), - NotAfter: time.Now().AddDate(1, 0, 0), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection}, - } - if issuer != nil { - issuerCert = issuer.Certificate - issuerKey = *issuer.PrivateKey - } - switch sigAlg { - case x509.SHA1WithRSA: - priv = test1024Key - switch issuerKey.(type) { - case *rsa.PrivateKey: - template.SignatureAlgorithm = x509.SHA1WithRSA - case *ecdsa.PrivateKey: - template.SignatureAlgorithm = x509.ECDSAWithSHA1 - case *dsa.PrivateKey: - template.SignatureAlgorithm = x509.DSAWithSHA1 - } - case x509.SHA256WithRSA: - priv = test2048Key - switch issuerKey.(type) { - case *rsa.PrivateKey: - template.SignatureAlgorithm = x509.SHA256WithRSA - case *ecdsa.PrivateKey: - template.SignatureAlgorithm = x509.ECDSAWithSHA256 - case *dsa.PrivateKey: - template.SignatureAlgorithm = x509.DSAWithSHA256 - } - case x509.SHA384WithRSA: - priv = test3072Key - switch issuerKey.(type) { - case *rsa.PrivateKey: - template.SignatureAlgorithm = x509.SHA384WithRSA - case *ecdsa.PrivateKey: - template.SignatureAlgorithm = x509.ECDSAWithSHA384 - case *dsa.PrivateKey: - template.SignatureAlgorithm = x509.DSAWithSHA256 - } - case x509.SHA512WithRSA: - priv = test4096Key - switch issuerKey.(type) { - case *rsa.PrivateKey: - template.SignatureAlgorithm = x509.SHA512WithRSA - case *ecdsa.PrivateKey: - template.SignatureAlgorithm = x509.ECDSAWithSHA512 - case *dsa.PrivateKey: - template.SignatureAlgorithm = x509.DSAWithSHA256 - } - case x509.ECDSAWithSHA1: - priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - return nil, err - } - switch issuerKey.(type) { - case *rsa.PrivateKey: - template.SignatureAlgorithm = x509.SHA1WithRSA - case *ecdsa.PrivateKey: - template.SignatureAlgorithm = x509.ECDSAWithSHA1 - case *dsa.PrivateKey: - template.SignatureAlgorithm = x509.DSAWithSHA1 - } - case x509.ECDSAWithSHA256: - priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - return nil, err - } - switch issuerKey.(type) { - case *rsa.PrivateKey: - template.SignatureAlgorithm = x509.SHA256WithRSA - case *ecdsa.PrivateKey: - template.SignatureAlgorithm = x509.ECDSAWithSHA256 - case *dsa.PrivateKey: - template.SignatureAlgorithm = x509.DSAWithSHA256 - } - case x509.ECDSAWithSHA384: - priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) - if err != nil { - return nil, err - } - switch issuerKey.(type) { - case *rsa.PrivateKey: - template.SignatureAlgorithm = x509.SHA384WithRSA - case *ecdsa.PrivateKey: - template.SignatureAlgorithm = x509.ECDSAWithSHA384 - case *dsa.PrivateKey: - template.SignatureAlgorithm = x509.DSAWithSHA256 - } - case x509.ECDSAWithSHA512: - priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader) - if err != nil { - return nil, err - } - switch issuerKey.(type) { - case *rsa.PrivateKey: - template.SignatureAlgorithm = x509.SHA512WithRSA - case *ecdsa.PrivateKey: - template.SignatureAlgorithm = x509.ECDSAWithSHA512 - case *dsa.PrivateKey: - template.SignatureAlgorithm = x509.DSAWithSHA256 - } - case x509.DSAWithSHA1: - var dsaPriv dsa.PrivateKey - params := &dsaPriv.Parameters - err = dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160) - if err != nil { - return nil, err - } - err = dsa.GenerateKey(&dsaPriv, rand.Reader) - if err != nil { - return nil, err - } - switch issuerKey.(type) { - case *rsa.PrivateKey: - template.SignatureAlgorithm = x509.SHA1WithRSA - case *ecdsa.PrivateKey: - template.SignatureAlgorithm = x509.ECDSAWithSHA1 - case *dsa.PrivateKey: - template.SignatureAlgorithm = x509.DSAWithSHA1 - } - priv = &dsaPriv - } - if isCA { - template.IsCA = true - template.KeyUsage |= x509.KeyUsageCertSign - template.BasicConstraintsValid = true - } - if issuer == nil { - // no issuer given,make this a self-signed root cert - issuerCert = &template - issuerKey = priv - } - - log.Println("creating cert", name, "issued by", issuerCert.Subject.CommonName, "with sigalg", sigAlg) - switch priv.(type) { - case *rsa.PrivateKey: - switch issuerKey.(type) { - case *rsa.PrivateKey: - derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*rsa.PrivateKey).Public(), issuerKey.(*rsa.PrivateKey)) - case *ecdsa.PrivateKey: - derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*rsa.PrivateKey).Public(), issuerKey.(*ecdsa.PrivateKey)) - case *dsa.PrivateKey: - derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*rsa.PrivateKey).Public(), issuerKey.(*dsa.PrivateKey)) - } - case *ecdsa.PrivateKey: - switch issuerKey.(type) { - case *rsa.PrivateKey: - derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*ecdsa.PrivateKey).Public(), issuerKey.(*rsa.PrivateKey)) - case *ecdsa.PrivateKey: - derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*ecdsa.PrivateKey).Public(), issuerKey.(*ecdsa.PrivateKey)) - case *dsa.PrivateKey: - derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*ecdsa.PrivateKey).Public(), issuerKey.(*dsa.PrivateKey)) - } - case *dsa.PrivateKey: - pub := &priv.(*dsa.PrivateKey).PublicKey - switch issuerKey := issuerKey.(type) { - case *rsa.PrivateKey: - derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, pub, issuerKey) - case *ecdsa.PrivateKey: - derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*dsa.PublicKey), issuerKey) - case *dsa.PrivateKey: - derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*dsa.PublicKey), issuerKey) - } - } - if err != nil { - return nil, err - } - if len(derCert) == 0 { - return nil, fmt.Errorf("no certificate created, probably due to wrong keys. types were %T and %T", priv, issuerKey) - } - cert, err := x509.ParseCertificate(derCert) - if err != nil { - return nil, err - } - pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) - return &certKeyPair{ - Certificate: cert, - PrivateKey: &priv, - }, nil -} - -type TestFixture struct { - Input []byte - Certificate *x509.Certificate - PrivateKey *rsa.PrivateKey -} - -func UnmarshalTestFixture(testPEMBlock string) TestFixture { - var result TestFixture - var derBlock *pem.Block - var pemBlock = []byte(testPEMBlock) - for { - derBlock, pemBlock = pem.Decode(pemBlock) - if derBlock == nil { - break - } - switch derBlock.Type { - case "PKCS7": - result.Input = derBlock.Bytes - case "CERTIFICATE": - result.Certificate, _ = x509.ParseCertificate(derBlock.Bytes) - case "PRIVATE KEY": - result.PrivateKey, _ = x509.ParsePKCS1PrivateKey(derBlock.Bytes) - } - } - - return result -} diff --git a/scep/pkcs7/sign.go b/scep/pkcs7/sign.go deleted file mode 100644 index addd7638..00000000 --- a/scep/pkcs7/sign.go +++ /dev/null @@ -1,429 +0,0 @@ -package pkcs7 - -import ( - "bytes" - "crypto" - "crypto/dsa" - "crypto/rand" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "errors" - "fmt" - "math/big" - "time" -) - -// SignedData is an opaque data structure for creating signed data payloads -type SignedData struct { - sd signedData - certs []*x509.Certificate - data, messageDigest []byte - digestOid asn1.ObjectIdentifier - encryptionOid asn1.ObjectIdentifier -} - -// NewSignedData takes data and initializes a PKCS7 SignedData struct that is -// ready to be signed via AddSigner. The digest algorithm is set to SHA1 by default -// and can be changed by calling SetDigestAlgorithm. -func NewSignedData(data []byte) (*SignedData, error) { - content, err := asn1.Marshal(data) - if err != nil { - return nil, err - } - ci := contentInfo{ - ContentType: OIDData, - Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true}, - } - sd := signedData{ - ContentInfo: ci, - Version: 1, - } - return &SignedData{sd: sd, data: data, digestOid: OIDDigestAlgorithmSHA1}, nil -} - -// SignerInfoConfig are optional values to include when adding a signer -type SignerInfoConfig struct { - ExtraSignedAttributes []Attribute - ExtraUnsignedAttributes []Attribute -} - -type signedData struct { - Version int `asn1:"default:1"` - DigestAlgorithmIdentifiers []pkix.AlgorithmIdentifier `asn1:"set"` - ContentInfo contentInfo - Certificates rawCertificates `asn1:"optional,tag:0"` - CRLs []pkix.CertificateList `asn1:"optional,tag:1"` - SignerInfos []signerInfo `asn1:"set"` -} - -type signerInfo struct { - Version int `asn1:"default:1"` - IssuerAndSerialNumber issuerAndSerial - DigestAlgorithm pkix.AlgorithmIdentifier - AuthenticatedAttributes []attribute `asn1:"optional,omitempty,tag:0"` - DigestEncryptionAlgorithm pkix.AlgorithmIdentifier - EncryptedDigest []byte - UnauthenticatedAttributes []attribute `asn1:"optional,omitempty,tag:1"` -} - -type attribute struct { - Type asn1.ObjectIdentifier - Value asn1.RawValue `asn1:"set"` -} - -func marshalAttributes(attrs []attribute) ([]byte, error) { - encodedAttributes, err := asn1.Marshal(struct { - A []attribute `asn1:"set"` - }{A: attrs}) - if err != nil { - return nil, err - } - - // Remove the leading sequence octets - var raw asn1.RawValue - asn1.Unmarshal(encodedAttributes, &raw) - return raw.Bytes, nil -} - -type rawCertificates struct { - Raw asn1.RawContent -} - -type issuerAndSerial struct { - IssuerName asn1.RawValue - SerialNumber *big.Int -} - -// SetDigestAlgorithm sets the digest algorithm to be used in the signing process. -// -// This should be called before adding signers -func (sd *SignedData) SetDigestAlgorithm(d asn1.ObjectIdentifier) { - sd.digestOid = d -} - -// SetEncryptionAlgorithm sets the encryption algorithm to be used in the signing process. -// -// This should be called before adding signers -func (sd *SignedData) SetEncryptionAlgorithm(d asn1.ObjectIdentifier) { - sd.encryptionOid = d -} - -// AddSigner is a wrapper around AddSignerChain() that adds a signer without any parent. -func (sd *SignedData) AddSigner(ee *x509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error { - var parents []*x509.Certificate - return sd.AddSignerChain(ee, pkey, parents, config) -} - -// AddSignerChain signs attributes about the content and adds certificates -// and signers infos to the Signed Data. The certificate and private key -// of the end-entity signer are used to issue the signature, and any -// parent of that end-entity that need to be added to the list of -// certifications can be specified in the parents slice. -// -// The signature algorithm used to hash the data is the one of the end-entity -// certificate. -func (sd *SignedData) AddSignerChain(ee *x509.Certificate, pkey crypto.PrivateKey, parents []*x509.Certificate, config SignerInfoConfig) error { -// Following RFC 2315, 9.2 SignerInfo type, the distinguished name of -// the issuer of the end-entity signer is stored in the issuerAndSerialNumber -// section of the SignedData.SignerInfo, alongside the serial number of -// the end-entity. - var ias issuerAndSerial - ias.SerialNumber = ee.SerialNumber - if len(parents) == 0 { - // no parent, the issuer is the end-entity cert itself - ias.IssuerName = asn1.RawValue{FullBytes: ee.RawIssuer} - } else { - err := verifyPartialChain(ee, parents) - if err != nil { - return err - } - // the first parent is the issuer - ias.IssuerName = asn1.RawValue{FullBytes: parents[0].RawSubject} - } - sd.sd.DigestAlgorithmIdentifiers = append(sd.sd.DigestAlgorithmIdentifiers, - pkix.AlgorithmIdentifier{Algorithm: sd.digestOid}, - ) - hash, err := getHashForOID(sd.digestOid) - if err != nil { - return err - } - h := hash.New() - h.Write(sd.data) - sd.messageDigest = h.Sum(nil) - encryptionOid, err := getOIDForEncryptionAlgorithm(pkey, sd.digestOid) - if err != nil { - return err - } - attrs := &attributes{} - attrs.Add(OIDAttributeContentType, sd.sd.ContentInfo.ContentType) - attrs.Add(OIDAttributeMessageDigest, sd.messageDigest) - attrs.Add(OIDAttributeSigningTime, time.Now().UTC()) - for _, attr := range config.ExtraSignedAttributes { - attrs.Add(attr.Type, attr.Value) - } - finalAttrs, err := attrs.ForMarshalling() - if err != nil { - return err - } - unsignedAttrs := &attributes{} - for _, attr := range config.ExtraUnsignedAttributes { - unsignedAttrs.Add(attr.Type, attr.Value) - } - finalUnsignedAttrs, err := unsignedAttrs.ForMarshalling() - if err != nil { - return err - } - // create signature of signed attributes - signature, err := signAttributes(finalAttrs, pkey, hash) - if err != nil { - return err - } - signer := signerInfo{ - AuthenticatedAttributes: finalAttrs, - UnauthenticatedAttributes: finalUnsignedAttrs, - DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.digestOid}, - DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: encryptionOid}, - IssuerAndSerialNumber: ias, - EncryptedDigest: signature, - Version: 1, - } - sd.certs = append(sd.certs, ee) - if len(parents) > 0 { - sd.certs = append(sd.certs, parents...) - } - sd.sd.SignerInfos = append(sd.sd.SignerInfos, signer) - return nil -} - -// SignWithoutAttr issues a signature on the content of the pkcs7 SignedData. -// Unlike AddSigner/AddSignerChain, it calculates the digest on the data alone -// and does not include any signed attributes like timestamp and so on. -// -// This function is needed to sign old Android APKs, something you probably -// shouldn't do unless you're maintaining backward compatibility for old -// applications. -func (sd *SignedData) SignWithoutAttr(ee *x509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error { - var signature []byte - sd.sd.DigestAlgorithmIdentifiers = append(sd.sd.DigestAlgorithmIdentifiers, pkix.AlgorithmIdentifier{Algorithm: sd.digestOid}) - hash, err := getHashForOID(sd.digestOid) - if err != nil { - return err - } - h := hash.New() - h.Write(sd.data) - sd.messageDigest = h.Sum(nil) - switch pkey := pkey.(type) { - case *dsa.PrivateKey: - // dsa doesn't implement crypto.Signer so we make a special case - // https://github.com/golang/go/issues/27889 - r, s, err := dsa.Sign(rand.Reader, pkey, sd.messageDigest) - if err != nil { - return err - } - signature, err = asn1.Marshal(dsaSignature{r, s}) - if err != nil { - return err - } - default: - key, ok := pkey.(crypto.Signer) - if !ok { - return errors.New("pkcs7: private key does not implement crypto.Signer") - } - signature, err = key.Sign(rand.Reader, sd.messageDigest, hash) - if err != nil { - return err - } - } - var ias issuerAndSerial - ias.SerialNumber = ee.SerialNumber - // no parent, the issue is the end-entity cert itself - ias.IssuerName = asn1.RawValue{FullBytes: ee.RawIssuer} - if sd.encryptionOid == nil { - // if the encryption algorithm wasn't set by SetEncryptionAlgorithm, - // infer it from the digest algorithm - sd.encryptionOid, err = getOIDForEncryptionAlgorithm(pkey, sd.digestOid) - } - if err != nil { - return err - } - signer := signerInfo{ - DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.digestOid}, - DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.encryptionOid}, - IssuerAndSerialNumber: ias, - EncryptedDigest: signature, - Version: 1, - } - // create signature of signed attributes - sd.certs = append(sd.certs, ee) - sd.sd.SignerInfos = append(sd.sd.SignerInfos, signer) - return nil -} - -func (si *signerInfo) SetUnauthenticatedAttributes(extraUnsignedAttrs []Attribute) error { - unsignedAttrs := &attributes{} - for _, attr := range extraUnsignedAttrs { - unsignedAttrs.Add(attr.Type, attr.Value) - } - finalUnsignedAttrs, err := unsignedAttrs.ForMarshalling() - if err != nil { - return err - } - - si.UnauthenticatedAttributes = finalUnsignedAttrs - - return nil -} - -// AddCertificate adds the certificate to the payload. Useful for parent certificates -func (sd *SignedData) AddCertificate(cert *x509.Certificate) { - sd.certs = append(sd.certs, cert) -} - -// Detach removes content from the signed data struct to make it a detached signature. -// This must be called right before Finish() -func (sd *SignedData) Detach() { - sd.sd.ContentInfo = contentInfo{ContentType: OIDData} -} - -// GetSignedData returns the private Signed Data -func (sd *SignedData) GetSignedData() *signedData { - return &sd.sd -} - -// Finish marshals the content and its signers -func (sd *SignedData) Finish() ([]byte, error) { - sd.sd.Certificates = marshalCertificates(sd.certs) - inner, err := asn1.Marshal(sd.sd) - if err != nil { - return nil, err - } - outer := contentInfo{ - ContentType: OIDSignedData, - Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: inner, IsCompound: true}, - } - return asn1.Marshal(outer) -} - -// RemoveAuthenticatedAttributes removes authenticated attributes from signedData -// similar to OpenSSL's PKCS7_NOATTR or -noattr flags -func (sd *SignedData) RemoveAuthenticatedAttributes() { - for i := range sd.sd.SignerInfos { - sd.sd.SignerInfos[i].AuthenticatedAttributes = nil - } -} - -// RemoveUnauthenticatedAttributes removes unauthenticated attributes from signedData -func (sd *SignedData) RemoveUnauthenticatedAttributes() { - for i := range sd.sd.SignerInfos { - sd.sd.SignerInfos[i].UnauthenticatedAttributes = nil - } -} - -// verifyPartialChain checks that a given cert is issued by the first parent in the list, -// then continue down the path. It doesn't require the last parent to be a root CA, -// or to be trusted in any truststore. It simply verifies that the chain provided, albeit -// partial, makes sense. -func verifyPartialChain(cert *x509.Certificate, parents []*x509.Certificate) error { - if len(parents) == 0 { - return fmt.Errorf("pkcs7: zero parents provided to verify the signature of certificate %q", cert.Subject.CommonName) - } - err := cert.CheckSignatureFrom(parents[0]) - if err != nil { - return fmt.Errorf("pkcs7: certificate signature from parent is invalid: %v", err) - } - if len(parents) == 1 { - // there is no more parent to check, return - return nil - } - return verifyPartialChain(parents[0], parents[1:]) -} - -func cert2issuerAndSerial(cert *x509.Certificate) (issuerAndSerial, error) { - var ias issuerAndSerial - // The issuer RDNSequence has to match exactly the sequence in the certificate - // We cannot use cert.Issuer.ToRDNSequence() here since it mangles the sequence - ias.IssuerName = asn1.RawValue{FullBytes: cert.RawIssuer} - ias.SerialNumber = cert.SerialNumber - - return ias, nil -} - -// signs the DER encoded form of the attributes with the private key -func signAttributes(attrs []attribute, pkey crypto.PrivateKey, digestAlg crypto.Hash) ([]byte, error) { - attrBytes, err := marshalAttributes(attrs) - if err != nil { - return nil, err - } - h := digestAlg.New() - h.Write(attrBytes) - hash := h.Sum(nil) - - // dsa doesn't implement crypto.Signer so we make a special case - // https://github.com/golang/go/issues/27889 - switch pkey := pkey.(type) { - case *dsa.PrivateKey: - r, s, err := dsa.Sign(rand.Reader, pkey, hash) - if err != nil { - return nil, err - } - return asn1.Marshal(dsaSignature{r, s}) - } - - key, ok := pkey.(crypto.Signer) - if !ok { - return nil, errors.New("pkcs7: private key does not implement crypto.Signer") - } - return key.Sign(rand.Reader, hash, digestAlg) -} - -type dsaSignature struct { - R, S *big.Int -} - -// concats and wraps the certificates in the RawValue structure -func marshalCertificates(certs []*x509.Certificate) rawCertificates { - var buf bytes.Buffer - for _, cert := range certs { - buf.Write(cert.Raw) - } - rawCerts, _ := marshalCertificateBytes(buf.Bytes()) - return rawCerts -} - -// Even though, the tag & length are stripped out during marshalling the -// RawContent, we have to encode it into the RawContent. If its missing, -// then `asn1.Marshal()` will strip out the certificate wrapper instead. -func marshalCertificateBytes(certs []byte) (rawCertificates, error) { - var val = asn1.RawValue{Bytes: certs, Class: 2, Tag: 0, IsCompound: true} - b, err := asn1.Marshal(val) - if err != nil { - return rawCertificates{}, err - } - return rawCertificates{Raw: b}, nil -} - -// DegenerateCertificate creates a signed data structure containing only the -// provided certificate or certificate chain. -func DegenerateCertificate(cert []byte) ([]byte, error) { - rawCert, err := marshalCertificateBytes(cert) - if err != nil { - return nil, err - } - emptyContent := contentInfo{ContentType: OIDData} - sd := signedData{ - Version: 1, - ContentInfo: emptyContent, - Certificates: rawCert, - CRLs: []pkix.CertificateList{}, - } - content, err := asn1.Marshal(sd) - if err != nil { - return nil, err - } - signedContent := contentInfo{ - ContentType: OIDSignedData, - Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true}, - } - return asn1.Marshal(signedContent) -} diff --git a/scep/pkcs7/sign_test.go b/scep/pkcs7/sign_test.go deleted file mode 100644 index 0ba6324d..00000000 --- a/scep/pkcs7/sign_test.go +++ /dev/null @@ -1,266 +0,0 @@ -package pkcs7 - -import ( - "bytes" - "crypto/dsa" - "crypto/x509" - "encoding/asn1" - "encoding/pem" - "fmt" - "io/ioutil" - "log" - "math/big" - "os" - "os/exec" - "testing" -) - -func TestSign(t *testing.T) { - content := []byte("Hello World") - sigalgs := []x509.SignatureAlgorithm{ - x509.SHA1WithRSA, - x509.SHA256WithRSA, - x509.SHA512WithRSA, - x509.ECDSAWithSHA1, - x509.ECDSAWithSHA256, - x509.ECDSAWithSHA384, - x509.ECDSAWithSHA512, - } - for _, sigalgroot := range sigalgs { - rootCert, err := createTestCertificateByIssuer("PKCS7 Test Root CA", nil, sigalgroot, true) - if err != nil { - t.Fatalf("test %s: cannot generate root cert: %s", sigalgroot, err) - } - truststore := x509.NewCertPool() - truststore.AddCert(rootCert.Certificate) - for _, sigalginter := range sigalgs { - interCert, err := createTestCertificateByIssuer("PKCS7 Test Intermediate Cert", rootCert, sigalginter, true) - if err != nil { - t.Fatalf("test %s/%s: cannot generate intermediate cert: %s", sigalgroot, sigalginter, err) - } - var parents []*x509.Certificate - parents = append(parents, interCert.Certificate) - for _, sigalgsigner := range sigalgs { - signerCert, err := createTestCertificateByIssuer("PKCS7 Test Signer Cert", interCert, sigalgsigner, false) - if err != nil { - t.Fatalf("test %s/%s/%s: cannot generate signer cert: %s", sigalgroot, sigalginter, sigalgsigner, err) - } - for _, testDetach := range []bool{false, true} { - log.Printf("test %s/%s/%s detached %t\n", sigalgroot, sigalginter, sigalgsigner, testDetach) - toBeSigned, err := NewSignedData(content) - if err != nil { - t.Fatalf("test %s/%s/%s: cannot initialize signed data: %s", sigalgroot, sigalginter, sigalgsigner, err) - } - - // Set the digest to match the end entity cert - signerDigest, _ := getDigestOIDForSignatureAlgorithm(signerCert.Certificate.SignatureAlgorithm) - toBeSigned.SetDigestAlgorithm(signerDigest) - - if err := toBeSigned.AddSignerChain(signerCert.Certificate, *signerCert.PrivateKey, parents, SignerInfoConfig{}); err != nil { - t.Fatalf("test %s/%s/%s: cannot add signer: %s", sigalgroot, sigalginter, sigalgsigner, err) - } - if testDetach { - toBeSigned.Detach() - } - signed, err := toBeSigned.Finish() - if err != nil { - t.Fatalf("test %s/%s/%s: cannot finish signing data: %s", sigalgroot, sigalginter, sigalgsigner, err) - } - pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: signed}) - p7, err := Parse(signed) - if err != nil { - t.Fatalf("test %s/%s/%s: cannot parse signed data: %s", sigalgroot, sigalginter, sigalgsigner, err) - } - if testDetach { - p7.Content = content - } - if !bytes.Equal(content, p7.Content) { - t.Errorf("test %s/%s/%s: content was not found in the parsed data:\n\tExpected: %s\n\tActual: %s", sigalgroot, sigalginter, sigalgsigner, content, p7.Content) - } - if err := p7.VerifyWithChain(truststore); err != nil { - t.Errorf("test %s/%s/%s: cannot verify signed data: %s", sigalgroot, sigalginter, sigalgsigner, err) - } - if !signerDigest.Equal(p7.Signers[0].DigestAlgorithm.Algorithm) { - t.Errorf("test %s/%s/%s: expected digest algorithm %q but got %q", - sigalgroot, sigalginter, sigalgsigner, signerDigest, p7.Signers[0].DigestAlgorithm.Algorithm) - } - } - } - } - } -} - -func TestDSASignAndVerifyWithOpenSSL(t *testing.T) { - content := []byte("Hello World") - // write the content to a temp file - tmpContentFile, err := ioutil.TempFile("", "TestDSASignAndVerifyWithOpenSSL_content") - if err != nil { - t.Fatal(err) - } - ioutil.WriteFile(tmpContentFile.Name(), content, 0755) - - block, _ := pem.Decode([]byte(dsaPublicCert)) - if block == nil { - t.Fatal("failed to parse certificate PEM") - } - signerCert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - t.Fatal("failed to parse certificate: " + err.Error()) - } - - // write the signer cert to a temp file - tmpSignerCertFile, err := ioutil.TempFile("", "TestDSASignAndVerifyWithOpenSSL_signer") - if err != nil { - t.Fatal(err) - } - ioutil.WriteFile(tmpSignerCertFile.Name(), dsaPublicCert, 0755) - - priv := dsa.PrivateKey{ - PublicKey: dsa.PublicKey{Parameters: dsa.Parameters{P: fromHex("fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80b6512669455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801d346ff26660b76b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150f04fb83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c7"), - Q: fromHex("9760508F15230BCCB292B982A2EB840BF0581CF5"), - G: fromHex("F7E1A085D69B3DDECBBCAB5C36B857B97994AFBBFA3AEA82F9574C0B3D0782675159578EBAD4594FE67107108180B449167123E84C281613B7CF09328CC8A6E13C167A8B547C8D28E0A3AE1E2BB3A675916EA37F0BFA213562F1FB627A01243BCCA4F1BEA8519089A883DFE15AE59F06928B665E807B552564014C3BFECF492A"), - }, - }, - X: fromHex("7D6E1A3DD4019FD809669D8AB8DA73807CEF7EC1"), - } - toBeSigned, err := NewSignedData(content) - if err != nil { - t.Fatalf("test case: cannot initialize signed data: %s", err) - } - if err := toBeSigned.SignWithoutAttr(signerCert, &priv, SignerInfoConfig{}); err != nil { - t.Fatalf("Cannot add signer: %s", err) - } - toBeSigned.Detach() - signed, err := toBeSigned.Finish() - if err != nil { - t.Fatalf("test case: cannot finish signing data: %s", err) - } - - // write the signature to a temp file - tmpSignatureFile, err := ioutil.TempFile("", "TestDSASignAndVerifyWithOpenSSL_signature") - if err != nil { - t.Fatal(err) - } - ioutil.WriteFile(tmpSignatureFile.Name(), pem.EncodeToMemory(&pem.Block{Type: "PKCS7", Bytes: signed}), 0755) - - // call openssl to verify the signature on the content using the root - opensslCMD := exec.Command("openssl", "smime", "-verify", "-noverify", - "-in", tmpSignatureFile.Name(), "-inform", "PEM", - "-content", tmpContentFile.Name()) - out, err := opensslCMD.CombinedOutput() - if err != nil { - t.Fatalf("test case: openssl command failed with %s: %s", err, out) - } - os.Remove(tmpSignatureFile.Name()) // clean up - os.Remove(tmpContentFile.Name()) // clean up - os.Remove(tmpSignerCertFile.Name()) // clean up -} - -func ExampleSignedData() { - // generate a signing cert or load a key pair - cert, err := createTestCertificate(x509.SHA256WithRSA) - if err != nil { - fmt.Printf("Cannot create test certificates: %s", err) - } - - // Initialize a SignedData struct with content to be signed - signedData, err := NewSignedData([]byte("Example data to be signed")) - if err != nil { - fmt.Printf("Cannot initialize signed data: %s", err) - } - - // Add the signing cert and private key - if err := signedData.AddSigner(cert.Certificate, cert.PrivateKey, SignerInfoConfig{}); err != nil { - fmt.Printf("Cannot add signer: %s", err) - } - - // Call Detach() is you want to remove content from the signature - // and generate an S/MIME detached signature - signedData.Detach() - - // Finish() to obtain the signature bytes - detachedSignature, err := signedData.Finish() - if err != nil { - fmt.Printf("Cannot finish signing data: %s", err) - } - pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: detachedSignature}) -} - -func TestUnmarshalSignedAttribute(t *testing.T) { - cert, err := createTestCertificate(x509.SHA512WithRSA) - if err != nil { - t.Fatal(err) - } - content := []byte("Hello World") - toBeSigned, err := NewSignedData(content) - if err != nil { - t.Fatalf("Cannot initialize signed data: %s", err) - } - oidTest := asn1.ObjectIdentifier{2, 3, 4, 5, 6, 7} - testValue := "TestValue" - if err := toBeSigned.AddSigner(cert.Certificate, *cert.PrivateKey, SignerInfoConfig{ - ExtraSignedAttributes: []Attribute{Attribute{Type: oidTest, Value: testValue}}, - }); err != nil { - t.Fatalf("Cannot add signer: %s", err) - } - signed, err := toBeSigned.Finish() - if err != nil { - t.Fatalf("Cannot finish signing data: %s", err) - } - p7, err := Parse(signed) - if err != nil { - t.Fatalf("Cannot parse signed data: %v", err) - } - var actual string - err = p7.UnmarshalSignedAttribute(oidTest, &actual) - if err != nil { - t.Fatalf("Cannot unmarshal test value: %s", err) - } - if testValue != actual { - t.Errorf("Attribute does not match test value\n\tExpected: %s\n\tActual: %s", testValue, actual) - } -} - -func TestDegenerateCertificate(t *testing.T) { - cert, err := createTestCertificate(x509.SHA1WithRSA) - if err != nil { - t.Fatal(err) - } - deg, err := DegenerateCertificate(cert.Certificate.Raw) - if err != nil { - t.Fatal(err) - } - testOpenSSLParse(t, deg) - pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: deg}) -} - -// writes the cert to a temporary file and tests that openssl can read it. -func testOpenSSLParse(t *testing.T, certBytes []byte) { - tmpCertFile, err := ioutil.TempFile("", "testCertificate") - if err != nil { - t.Fatal(err) - } - defer os.Remove(tmpCertFile.Name()) // clean up - - if _, err := tmpCertFile.Write(certBytes); err != nil { - t.Fatal(err) - } - - opensslCMD := exec.Command("openssl", "pkcs7", "-inform", "der", "-in", tmpCertFile.Name()) - _, err = opensslCMD.Output() - if err != nil { - t.Fatal(err) - } - - if err := tmpCertFile.Close(); err != nil { - t.Fatal(err) - } - -} -func fromHex(s string) *big.Int { - result, ok := new(big.Int).SetString(s, 16) - if !ok { - panic(s) - } - return result -} diff --git a/scep/pkcs7/verify.go b/scep/pkcs7/verify.go deleted file mode 100644 index c8ead236..00000000 --- a/scep/pkcs7/verify.go +++ /dev/null @@ -1,264 +0,0 @@ -package pkcs7 - -import ( - "crypto/subtle" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "errors" - "fmt" - "time" -) - -// Verify is a wrapper around VerifyWithChain() that initializes an empty -// trust store, effectively disabling certificate verification when validating -// a signature. -func (p7 *PKCS7) Verify() (err error) { - return p7.VerifyWithChain(nil) -} - -// VerifyWithChain checks the signatures of a PKCS7 object. -// If truststore is not nil, it also verifies the chain of trust of the end-entity -// signer cert to one of the root in the truststore. -func (p7 *PKCS7) VerifyWithChain(truststore *x509.CertPool) (err error) { - if len(p7.Signers) == 0 { - return errors.New("pkcs7: Message has no signers") - } - for _, signer := range p7.Signers { - if err := verifySignature(p7, signer, truststore); err != nil { - return err - } - } - return nil -} - -func verifySignature(p7 *PKCS7, signer signerInfo, truststore *x509.CertPool) (err error) { - signedData := p7.Content - ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber) - if ee == nil { - return errors.New("pkcs7: No certificate for signer") - } - signingTime := time.Now().UTC() - if len(signer.AuthenticatedAttributes) > 0 { - // TODO(fullsailor): First check the content type match - var digest []byte - err := unmarshalAttribute(signer.AuthenticatedAttributes, OIDAttributeMessageDigest, &digest) - if err != nil { - return err - } - hash, err := getHashForOID(signer.DigestAlgorithm.Algorithm) - if err != nil { - return err - } - h := hash.New() - h.Write(p7.Content) - computed := h.Sum(nil) - if subtle.ConstantTimeCompare(digest, computed) != 1 { - return &MessageDigestMismatchError{ - ExpectedDigest: digest, - ActualDigest: computed, - } - } - signedData, err = marshalAttributes(signer.AuthenticatedAttributes) - if err != nil { - return err - } - err = unmarshalAttribute(signer.AuthenticatedAttributes, OIDAttributeSigningTime, &signingTime) - if err == nil { - // signing time found, performing validity check - if signingTime.After(ee.NotAfter) || signingTime.Before(ee.NotBefore) { - return fmt.Errorf("pkcs7: signing time %q is outside of certificate validity %q to %q", - signingTime.Format(time.RFC3339), - ee.NotBefore.Format(time.RFC3339), - ee.NotBefore.Format(time.RFC3339)) - } - } - } - if truststore != nil { - _, err = verifyCertChain(ee, p7.Certificates, truststore, signingTime) - if err != nil { - return err - } - } - sigalg, err := getSignatureAlgorithm(signer.DigestEncryptionAlgorithm, signer.DigestAlgorithm) - if err != nil { - return err - } - return ee.CheckSignature(sigalg, signedData, signer.EncryptedDigest) -} - -// GetOnlySigner returns an x509.Certificate for the first signer of the signed -// data payload. If there are more or less than one signer, nil is returned -func (p7 *PKCS7) GetOnlySigner() *x509.Certificate { - if len(p7.Signers) != 1 { - return nil - } - signer := p7.Signers[0] - return getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber) -} - -// UnmarshalSignedAttribute decodes a single attribute from the signer info -func (p7 *PKCS7) UnmarshalSignedAttribute(attributeType asn1.ObjectIdentifier, out interface{}) error { - sd, ok := p7.raw.(signedData) - if !ok { - return errors.New("pkcs7: payload is not signedData content") - } - if len(sd.SignerInfos) < 1 { - return errors.New("pkcs7: payload has no signers") - } - attributes := sd.SignerInfos[0].AuthenticatedAttributes - return unmarshalAttribute(attributes, attributeType, out) -} - -func parseSignedData(data []byte) (*PKCS7, error) { - var sd signedData - asn1.Unmarshal(data, &sd) - certs, err := sd.Certificates.Parse() - if err != nil { - return nil, err - } - // fmt.Printf("--> Signed Data Version %d\n", sd.Version) - - var compound asn1.RawValue - var content unsignedData - - // The Content.Bytes maybe empty on PKI responses. - if len(sd.ContentInfo.Content.Bytes) > 0 { - if _, err := asn1.Unmarshal(sd.ContentInfo.Content.Bytes, &compound); err != nil { - return nil, err - } - } - // Compound octet string - if compound.IsCompound { - if compound.Tag == 4 { - if _, err = asn1.Unmarshal(compound.Bytes, &content); err != nil { - return nil, err - } - } else { - content = compound.Bytes - } - } else { - // assuming this is tag 04 - content = compound.Bytes - } - return &PKCS7{ - Content: content, - Certificates: certs, - CRLs: sd.CRLs, - Signers: sd.SignerInfos, - raw: sd}, nil -} - -// verifyCertChain takes an end-entity certs, a list of potential intermediates and a -// truststore, and built all potential chains between the EE and a trusted root. -// -// When verifying chains that may have expired, currentTime can be set to a past date -// to allow the verification to pass. If unset, currentTime is set to the current UTC time. -func verifyCertChain(ee *x509.Certificate, certs []*x509.Certificate, truststore *x509.CertPool, currentTime time.Time) (chains [][]*x509.Certificate, err error) { - intermediates := x509.NewCertPool() - for _, intermediate := range certs { - intermediates.AddCert(intermediate) - } - verifyOptions := x509.VerifyOptions{ - Roots: truststore, - Intermediates: intermediates, - KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, - CurrentTime: currentTime, - } - chains, err = ee.Verify(verifyOptions) - if err != nil { - return chains, fmt.Errorf("pkcs7: failed to verify certificate chain: %v", err) - } - return -} - -// MessageDigestMismatchError is returned when the signer data digest does not -// match the computed digest for the contained content -type MessageDigestMismatchError struct { - ExpectedDigest []byte - ActualDigest []byte -} - -func (err *MessageDigestMismatchError) Error() string { - return fmt.Sprintf("pkcs7: Message digest mismatch\n\tExpected: %X\n\tActual : %X", err.ExpectedDigest, err.ActualDigest) -} - -func getSignatureAlgorithm(digestEncryption, digest pkix.AlgorithmIdentifier) (x509.SignatureAlgorithm, error) { - switch { - case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA1): - return x509.ECDSAWithSHA1, nil - case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA256): - return x509.ECDSAWithSHA256, nil - case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA384): - return x509.ECDSAWithSHA384, nil - case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA512): - return x509.ECDSAWithSHA512, nil - case digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSA), - digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA1), - digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA256), - digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA384), - digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA512): - switch { - case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1): - return x509.SHA1WithRSA, nil - case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256): - return x509.SHA256WithRSA, nil - case digest.Algorithm.Equal(OIDDigestAlgorithmSHA384): - return x509.SHA384WithRSA, nil - case digest.Algorithm.Equal(OIDDigestAlgorithmSHA512): - return x509.SHA512WithRSA, nil - default: - return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q", - digest.Algorithm.String(), digestEncryption.Algorithm.String()) - } - case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmDSA), - digestEncryption.Algorithm.Equal(OIDDigestAlgorithmDSASHA1): - switch { - case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1): - return x509.DSAWithSHA1, nil - case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256): - return x509.DSAWithSHA256, nil - default: - return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q", - digest.Algorithm.String(), digestEncryption.Algorithm.String()) - } - case digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP256), - digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP384), - digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP521): - switch { - case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1): - return x509.ECDSAWithSHA1, nil - case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256): - return x509.ECDSAWithSHA256, nil - case digest.Algorithm.Equal(OIDDigestAlgorithmSHA384): - return x509.ECDSAWithSHA384, nil - case digest.Algorithm.Equal(OIDDigestAlgorithmSHA512): - return x509.ECDSAWithSHA512, nil - default: - return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q", - digest.Algorithm.String(), digestEncryption.Algorithm.String()) - } - default: - return -1, fmt.Errorf("pkcs7: unsupported algorithm %q", - digestEncryption.Algorithm.String()) - } -} - -func getCertFromCertsByIssuerAndSerial(certs []*x509.Certificate, ias issuerAndSerial) *x509.Certificate { - for _, cert := range certs { - if isCertMatchForIssuerAndSerial(cert, ias) { - return cert - } - } - return nil -} - -func unmarshalAttribute(attrs []attribute, attributeType asn1.ObjectIdentifier, out interface{}) error { - for _, attr := range attrs { - if attr.Type.Equal(attributeType) { - _, err := asn1.Unmarshal(attr.Value.Bytes, out) - return err - } - } - return errors.New("pkcs7: attribute type not in attributes") -} diff --git a/scep/pkcs7/verify_test.go b/scep/pkcs7/verify_test.go deleted file mode 100644 index f80943b2..00000000 --- a/scep/pkcs7/verify_test.go +++ /dev/null @@ -1,713 +0,0 @@ -package pkcs7 - -import ( - "bytes" - "crypto/ecdsa" - "crypto/rsa" - "crypto/x509" - "encoding/base64" - "encoding/pem" - "fmt" - "io/ioutil" - "os" - "os/exec" - "testing" - "time" -) - -func TestVerify(t *testing.T) { - fixture := UnmarshalTestFixture(SignedTestFixture) - p7, err := Parse(fixture.Input) - if err != nil { - t.Errorf("Parse encountered unexpected error: %v", err) - } - - if err := p7.Verify(); err != nil { - t.Errorf("Verify failed with error: %v", err) - } - expected := []byte("We the People") - if !bytes.Equal(p7.Content, expected) { - t.Errorf("Signed content does not match.\n\tExpected:%s\n\tActual:%s", expected, p7.Content) - - } -} - -var SignedTestFixture = ` ------BEGIN PKCS7----- -MIIDVgYJKoZIhvcNAQcCoIIDRzCCA0MCAQExCTAHBgUrDgMCGjAcBgkqhkiG9w0B -BwGgDwQNV2UgdGhlIFBlb3BsZaCCAdkwggHVMIIBQKADAgECAgRpuDctMAsGCSqG -SIb3DQEBCzApMRAwDgYDVQQKEwdBY21lIENvMRUwEwYDVQQDEwxFZGRhcmQgU3Rh -cmswHhcNMTUwNTA2MDQyNDQ4WhcNMTYwNTA2MDQyNDQ4WjAlMRAwDgYDVQQKEwdB -Y21lIENvMREwDwYDVQQDEwhKb24gU25vdzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw -gYkCgYEAqr+tTF4mZP5rMwlXp1y+crRtFpuLXF1zvBZiYMfIvAHwo1ta8E1IcyEP -J1jIiKMcwbzeo6kAmZzIJRCTezq9jwXUsKbQTvcfOH9HmjUmXBRWFXZYoQs/OaaF -a45deHmwEeMQkuSWEtYiVKKZXtJOtflKIT3MryJEDiiItMkdybUCAwEAAaMSMBAw -DgYDVR0PAQH/BAQDAgCgMAsGCSqGSIb3DQEBCwOBgQDK1EweZWRL+f7Z+J0kVzY8 -zXptcBaV4Lf5wGZJLJVUgp33bpLNpT3yadS++XQJ+cvtW3wADQzBSTMduyOF8Zf+ -L7TjjrQ2+F2HbNbKUhBQKudxTfv9dJHdKbD+ngCCdQJYkIy2YexsoNG0C8nQkggy -axZd/J69xDVx6pui3Sj8sDGCATYwggEyAgEBMDEwKTEQMA4GA1UEChMHQWNtZSBD -bzEVMBMGA1UEAxMMRWRkYXJkIFN0YXJrAgRpuDctMAcGBSsOAwIaoGEwGAYJKoZI -hvcNAQkDMQsGCSqGSIb3DQEHATAgBgkqhkiG9w0BCQUxExcRMTUwNTA2MDAyNDQ4 -LTA0MDAwIwYJKoZIhvcNAQkEMRYEFG9D7gcTh9zfKiYNJ1lgB0yTh4sZMAsGCSqG -SIb3DQEBAQSBgFF3sGDU9PtXty/QMtpcFa35vvIOqmWQAIZt93XAskQOnBq4OloX -iL9Ct7t1m4pzjRm0o9nDkbaSLZe7HKASHdCqijroScGlI8M+alJ8drHSFv6ZIjnM -FIwIf0B2Lko6nh9/6mUXq7tbbIHa3Gd1JUVire/QFFtmgRXMbXYk8SIS ------END PKCS7----- ------BEGIN CERTIFICATE----- -MIIB1TCCAUCgAwIBAgIEabg3LTALBgkqhkiG9w0BAQswKTEQMA4GA1UEChMHQWNt -ZSBDbzEVMBMGA1UEAxMMRWRkYXJkIFN0YXJrMB4XDTE1MDUwNjA0MjQ0OFoXDTE2 -MDUwNjA0MjQ0OFowJTEQMA4GA1UEChMHQWNtZSBDbzERMA8GA1UEAxMISm9uIFNu -b3cwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKq/rUxeJmT+azMJV6dcvnK0 -bRabi1xdc7wWYmDHyLwB8KNbWvBNSHMhDydYyIijHMG83qOpAJmcyCUQk3s6vY8F -1LCm0E73Hzh/R5o1JlwUVhV2WKELPzmmhWuOXXh5sBHjEJLklhLWIlSimV7STrX5 -SiE9zK8iRA4oiLTJHcm1AgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIAoDALBgkqhkiG -9w0BAQsDgYEAytRMHmVkS/n+2fidJFc2PM16bXAWleC3+cBmSSyVVIKd926SzaU9 -8mnUvvl0CfnL7Vt8AA0MwUkzHbsjhfGX/i+04460Nvhdh2zWylIQUCrncU37/XSR -3Smw/p4AgnUCWJCMtmHsbKDRtAvJ0JIIMmsWXfyevcQ1ceqbot0o/LA= ------END CERTIFICATE----- ------BEGIN PRIVATE KEY----- -MIICXgIBAAKBgQCqv61MXiZk/mszCVenXL5ytG0Wm4tcXXO8FmJgx8i8AfCjW1rw -TUhzIQ8nWMiIoxzBvN6jqQCZnMglEJN7Or2PBdSwptBO9x84f0eaNSZcFFYVdlih -Cz85poVrjl14ebAR4xCS5JYS1iJUople0k61+UohPcyvIkQOKIi0yR3JtQIDAQAB -AoGBAIPLCR9N+IKxodq11lNXEaUFwMHXc1zqwP8no+2hpz3+nVfplqqubEJ4/PJY -5AgbJoIfnxVhyBXJXu7E+aD/OPneKZrgp58YvHKgGvvPyJg2gpC/1Fh0vQB0HNpI -1ZzIZUl8ZTUtVgtnCBUOh5JGI4bFokAqrT//Uvcfd+idgxqBAkEA1ZbP/Kseld14 -qbWmgmU5GCVxsZRxgR1j4lG3UVjH36KXMtRTm1atAam1uw3OEGa6Y3ANjpU52FaB -Hep5rkk4FQJBAMynMo1L1uiN5GP+KYLEF5kKRxK+FLjXR0ywnMh+gpGcZDcOae+J -+t1gLoWBIESH/Xt639T7smuSfrZSA9V0EyECQA8cvZiWDvLxmaEAXkipmtGPjKzQ -4PsOtkuEFqFl07aKDYKmLUg3aMROWrJidqsIabWxbvQgsNgSvs38EiH3wkUCQQCg -ndxb7piVXb9RBwm3OoU2tE1BlXMX+sVXmAkEhd2dwDsaxrI3sHf1xGXem5AimQRF -JBOFyaCnMotGNioSHY5hAkEAxyXcNixQ2RpLXJTQZtwnbk0XDcbgB+fBgXnv/4f3 -BCvcu85DqJeJyQv44Oe1qsXEX9BfcQIOVaoep35RPlKi9g== ------END PRIVATE KEY-----` - -func TestVerifyEC2(t *testing.T) { - fixture := UnmarshalTestFixture(EC2IdentityDocumentFixture) - p7, err := Parse(fixture.Input) - if err != nil { - t.Errorf("Parse encountered unexpected error: %v", err) - } - p7.Certificates = []*x509.Certificate{fixture.Certificate} - if err := p7.Verify(); err != nil { - t.Errorf("Verify failed with error: %v", err) - } -} - -var EC2IdentityDocumentFixture = ` ------BEGIN PKCS7----- -MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAaCA -JIAEggGmewogICJwcml2YXRlSXAiIDogIjE3Mi4zMC4wLjI1MiIsCiAgImRldnBh -eVByb2R1Y3RDb2RlcyIgOiBudWxsLAogICJhdmFpbGFiaWxpdHlab25lIiA6ICJ1 -cy1lYXN0LTFhIiwKICAidmVyc2lvbiIgOiAiMjAxMC0wOC0zMSIsCiAgImluc3Rh -bmNlSWQiIDogImktZjc5ZmU1NmMiLAogICJiaWxsaW5nUHJvZHVjdHMiIDogbnVs -bCwKICAiaW5zdGFuY2VUeXBlIiA6ICJ0Mi5taWNybyIsCiAgImFjY291bnRJZCIg -OiAiMTIxNjU5MDE0MzM0IiwKICAiaW1hZ2VJZCIgOiAiYW1pLWZjZTNjNjk2IiwK -ICAicGVuZGluZ1RpbWUiIDogIjIwMTYtMDQtMDhUMDM6MDE6MzhaIiwKICAiYXJj -aGl0ZWN0dXJlIiA6ICJ4ODZfNjQiLAogICJrZXJuZWxJZCIgOiBudWxsLAogICJy -YW1kaXNrSWQiIDogbnVsbCwKICAicmVnaW9uIiA6ICJ1cy1lYXN0LTEiCn0AAAAA -AAAxggEYMIIBFAIBATBpMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5n -dG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2Vi -IFNlcnZpY2VzIExMQwIJAJa6SNnlXhpnMAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0B -CQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xNjA0MDgwMzAxNDRaMCMG -CSqGSIb3DQEJBDEWBBTuUc28eBXmImAautC+wOjqcFCBVjAJBgcqhkjOOAQDBC8w -LQIVAKA54NxGHWWCz5InboDmY/GHs33nAhQ6O/ZI86NwjA9Vz3RNMUJrUPU5tAAA -AAAAAA== ------END PKCS7----- ------BEGIN CERTIFICATE----- -MIIC7TCCAq0CCQCWukjZ5V4aZzAJBgcqhkjOOAQDMFwxCzAJBgNVBAYTAlVTMRkw -FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYD -VQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0xMjAxMDUxMjU2MTJaFw0z -ODAxMDUxMjU2MTJaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9u -IFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNl -cnZpY2VzIExMQzCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQCjkvcS2bb1VQ4yt/5e -ih5OO6kK/n1Lzllr7D8ZwtQP8fOEpp5E2ng+D6Ud1Z1gYipr58Kj3nssSNpI6bX3 -VyIQzK7wLclnd/YozqNNmgIyZecN7EglK9ITHJLP+x8FtUpt3QbyYXJdmVMegN6P -hviYt5JH/nYl4hh3Pa1HJdskgQIVALVJ3ER11+Ko4tP6nwvHwh6+ERYRAoGBAI1j -k+tkqMVHuAFcvAGKocTgsjJem6/5qomzJuKDmbJNu9Qxw3rAotXau8Qe+MBcJl/U -hhy1KHVpCGl9fueQ2s6IL0CaO/buycU1CiYQk40KNHCcHfNiZbdlx1E9rpUp7bnF -lRa2v1ntMX3caRVDdbtPEWmdxSCYsYFDk4mZrOLBA4GEAAKBgEbmeve5f8LIE/Gf -MNmP9CM5eovQOGx5ho8WqD+aTebs+k2tn92BBPqeZqpWRa5P/+jrdKml1qx4llHW -MXrs3IgIb6+hUIB+S8dz8/mmO0bpr76RoZVCXYab2CZedFut7qc3WUH9+EUAH5mw -vSeDCOUMYQR7R9LINYwouHIziqQYMAkGByqGSM44BAMDLwAwLAIUWXBlk40xTwSw -7HX32MxXYruse9ACFBNGmdX2ZBrVNGrN9N2f6ROk0k9K ------END CERTIFICATE-----` - -func TestVerifyAppStore(t *testing.T) { - fixture := UnmarshalTestFixture(AppStoreReceiptFixture) - p7, err := Parse(fixture.Input) - if err != nil { - t.Errorf("Parse encountered unexpected error: %v", err) - } - if err := p7.Verify(); err != nil { - t.Errorf("Verify failed with error: %v", err) - } -} - -var AppStoreReceiptFixture = ` ------BEGIN PKCS7----- -MIITtgYJKoZIhvcNAQcCoIITpzCCE6MCAQExCzAJBgUrDgMCGgUAMIIDVwYJKoZI -hvcNAQcBoIIDSASCA0QxggNAMAoCAQgCAQEEAhYAMAoCARQCAQEEAgwAMAsCAQEC -AQEEAwIBADALAgEDAgEBBAMMATEwCwIBCwIBAQQDAgEAMAsCAQ8CAQEEAwIBADAL -AgEQAgEBBAMCAQAwCwIBGQIBAQQDAgEDMAwCAQoCAQEEBBYCNCswDAIBDgIBAQQE -AgIAjTANAgENAgEBBAUCAwFgvTANAgETAgEBBAUMAzEuMDAOAgEJAgEBBAYCBFAy -NDcwGAIBAgIBAQQQDA5jb20uemhpaHUudGVzdDAYAgEEAgECBBCS+ZODNMHwT1Nz -gWYDXyWZMBsCAQACAQEEEwwRUHJvZHVjdGlvblNhbmRib3gwHAIBBQIBAQQU4nRh -YCEZx70Flzv7hvJRjJZckYIwHgIBDAIBAQQWFhQyMDE2LTA3LTIzVDA2OjIxOjEx -WjAeAgESAgEBBBYWFDIwMTMtMDgtMDFUMDc6MDA6MDBaMD0CAQYCAQEENbR21I+a -8+byMXo3NPRoDWQmSXQF2EcCeBoD4GaL//ZCRETp9rGFPSg1KekCP7Kr9HAqw09m -MEICAQcCAQEEOlVJozYYBdugybShbiiMsejDMNeCbZq6CrzGBwW6GBy+DGWxJI91 -Y3ouXN4TZUhuVvLvN1b0m5T3ggQwggFaAgERAgEBBIIBUDGCAUwwCwICBqwCAQEE -AhYAMAsCAgatAgEBBAIMADALAgIGsAIBAQQCFgAwCwICBrICAQEEAgwAMAsCAgaz -AgEBBAIMADALAgIGtAIBAQQCDAAwCwICBrUCAQEEAgwAMAsCAga2AgEBBAIMADAM -AgIGpQIBAQQDAgEBMAwCAgarAgEBBAMCAQEwDAICBq4CAQEEAwIBADAMAgIGrwIB -AQQDAgEAMAwCAgaxAgEBBAMCAQAwGwICBqcCAQEEEgwQMTAwMDAwMDIyNTMyNTkw -MTAbAgIGqQIBAQQSDBAxMDAwMDAwMjI1MzI1OTAxMB8CAgaoAgEBBBYWFDIwMTYt -MDctMjNUMDY6MjE6MTFaMB8CAgaqAgEBBBYWFDIwMTYtMDctMjNUMDY6MjE6MTFa -MCACAgamAgEBBBcMFWNvbS56aGlodS50ZXN0LnRlc3RfMaCCDmUwggV8MIIEZKAD -AgECAggO61eH554JjTANBgkqhkiG9w0BAQUFADCBljELMAkGA1UEBhMCVVMxEzAR -BgNVBAoMCkFwcGxlIEluYy4xLDAqBgNVBAsMI0FwcGxlIFdvcmxkd2lkZSBEZXZl -bG9wZXIgUmVsYXRpb25zMUQwQgYDVQQDDDtBcHBsZSBXb3JsZHdpZGUgRGV2ZWxv -cGVyIFJlbGF0aW9ucyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNTExMTMw -MjE1MDlaFw0yMzAyMDcyMTQ4NDdaMIGJMTcwNQYDVQQDDC5NYWMgQXBwIFN0b3Jl -IGFuZCBpVHVuZXMgU3RvcmUgUmVjZWlwdCBTaWduaW5nMSwwKgYDVQQLDCNBcHBs -ZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczETMBEGA1UECgwKQXBwbGUg -SW5jLjELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQClz4H9JaKBW9aH7SPaMxyO4iPApcQmyz3Gn+xKDVWG/6QC15fKOVRtfX+yVBid -xCxScY5ke4LOibpJ1gjltIhxzz9bRi7GxB24A6lYogQ+IXjV27fQjhKNg0xbKmg3 -k8LyvR7E0qEMSlhSqxLj7d0fmBWQNS3CzBLKjUiB91h4VGvojDE2H0oGDEdU8zeQ -uLKSiX1fpIVK4cCc4Lqku4KXY/Qrk8H9Pm/KwfU8qY9SGsAlCnYO3v6Z/v/Ca/Vb -XqxzUUkIVonMQ5DMjoEC0KCXtlyxoWlph5AQaCYmObgdEHOwCl3Fc9DfdjvYLdmI -HuPsB8/ijtDT+iZVge/iA0kjAgMBAAGjggHXMIIB0zA/BggrBgEFBQcBAQQzMDEw -LwYIKwYBBQUHMAGGI2h0dHA6Ly9vY3NwLmFwcGxlLmNvbS9vY3NwMDMtd3dkcjA0 -MB0GA1UdDgQWBBSRpJz8xHa3n6CK9E31jzZd7SsEhTAMBgNVHRMBAf8EAjAAMB8G -A1UdIwQYMBaAFIgnFwmpthhgi+zruvZHWcVSVKO3MIIBHgYDVR0gBIIBFTCCAREw -ggENBgoqhkiG92NkBQYBMIH+MIHDBggrBgEFBQcCAjCBtgyBs1JlbGlhbmNlIG9u -IHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5j -ZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25k -aXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0 -aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMDYGCCsGAQUFBwIBFipodHRwOi8vd3d3 -LmFwcGxlLmNvbS9jZXJ0aWZpY2F0ZWF1dGhvcml0eS8wDgYDVR0PAQH/BAQDAgeA -MBAGCiqGSIb3Y2QGCwEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQANphvTLj3jWysH -bkKWbNPojEMwgl/gXNGNvr0PvRr8JZLbjIXDgFnf4+LXLgUUrA3btrj+/DUufMut -F2uOfx/kd7mxZ5W0E16mGYZ2+FogledjjA9z/Ojtxh+umfhlSFyg4Cg6wBA3Lbmg -BDkfc7nIBf3y3n8aKipuKwH8oCBc2et9J6Yz+PWY4L5E27FMZ/xuCk/J4gao0pfz -p45rUaJahHVl0RYEYuPBX/UIqc9o2ZIAycGMs/iNAGS6WGDAfK+PdcppuVsq1h1o -bphC9UynNxmbzDscehlD86Ntv0hgBgw2kivs3hi1EdotI9CO/KBpnBcbnoB7OUdF -MGEvxxOoMIIEIjCCAwqgAwIBAgIIAd68xDltoBAwDQYJKoZIhvcNAQEFBQAwYjEL -MAkGA1UEBhMCVVMxEzARBgNVBAoTCkFwcGxlIEluYy4xJjAkBgNVBAsTHUFwcGxl -IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRYwFAYDVQQDEw1BcHBsZSBSb290IENB -MB4XDTEzMDIwNzIxNDg0N1oXDTIzMDIwNzIxNDg0N1owgZYxCzAJBgNVBAYTAlVT -MRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3JsZHdpZGUg -RGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3aWRlIERl -dmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKOFSmy1aqyCQ5SOmM7uxfuH8mkbw0 -U3rOfGOAYXdkXqUHI7Y5/lAtFVZYcC1+xG7BSoU+L/DehBqhV8mvexj/avoVEkkV -CBmsqtsqMu2WY2hSFT2Miuy/axiV4AOsAX2XBWfODoWVN2rtCbauZ81RZJ/GXNG8 -V25nNYB2NqSHgW44j9grFU57Jdhav06DwY3Sk9UacbVgnJ0zTlX5ElgMhrgWDcHl -d0WNUEi6Ky3klIXh6MSdxmilsKP8Z35wugJZS3dCkTm59c3hTO/AO0iMpuUhXf1q -arunFjVg0uat80YpyejDi+l5wGphZxWy8P3laLxiX27Pmd3vG2P+kmWrAgMBAAGj -gaYwgaMwHQYDVR0OBBYEFIgnFwmpthhgi+zruvZHWcVSVKO3MA8GA1UdEwEB/wQF -MAMBAf8wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wLgYDVR0fBCcw -JTAjoCGgH4YdaHR0cDovL2NybC5hcHBsZS5jb20vcm9vdC5jcmwwDgYDVR0PAQH/ -BAQDAgGGMBAGCiqGSIb3Y2QGAgEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQBPz+9Z -viz1smwvj+4ThzLoBTWobot9yWkMudkXvHcs1Gfi/ZptOllc34MBvbKuKmFysa/N -w0Uwj6ODDc4dR7Txk4qjdJukw5hyhzs+r0ULklS5MruQGFNrCk4QttkdUGwhgAqJ -TleMa1s8Pab93vcNIx0LSiaHP7qRkkykGRIZbVf1eliHe2iK5IaMSuviSRSqpd1V -AKmuu0swruGgsbwpgOYJd+W+NKIByn/c4grmO7i77LpilfMFY0GCzQ87HUyVpNur -+cmV6U/kTecmmYHpvPm0KdIBembhLoz2IYrF+Hjhga6/05Cdqa3zr/04GpZnMBxR -pVzscYqCtGwPDBUfMIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQsw -CQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUg -Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0Ew -HhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzET -MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv -biBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne -+Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjcz -y8QPTc4UadHJGXL1XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQ -Z48ItCD3y6wsIG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCS -C7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINB -hzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIB -djAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9Bp -R5R2Cf70a40uQKb3R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/ -CF4wggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcC -ARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCB -thqBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFz -c3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJk -IHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5 -IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3 -DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizU -sZAS2L70c5vu0mQPy3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJ -fBdAVhEedNO3iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr -1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltk -wGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIq -xw8dtk2cXmPIS4AXUKqK1drk/NAJBzewdXUhMYIByzCCAccCAQEwgaMwgZYxCzAJ -BgNVBAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBX -b3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29y -bGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3Jp -dHkCCA7rV4fnngmNMAkGBSsOAwIaBQAwDQYJKoZIhvcNAQEBBQAEggEAasPtnide -NWyfUtewW9OSgcQA8pW+5tWMR0469cBPZR84uJa0gyfmPspySvbNOAwnrwzZHYLa -ujOxZLip4DUw4F5s3QwUa3y4BXpF4J+NSn9XNvxNtnT/GcEQtCuFwgJ0o3F0ilhv -MTHrwiwyx/vr+uNDqlORK8lfK+1qNp+A/kzh8eszMrn4JSeTh9ZYxLHE56WkTQGD -VZXl0gKgxSOmDrcp1eQxdlymzrPv9U60wUJ0bkPfrU9qZj3mJrmrkQk61JTe3j6/ -QfjfFBG9JG2mUmYQP1KQ3SypGHzDW8vngvsGu//tNU0NFfOqQu4bYU4VpQl0nPtD -4B85NkrgvQsWAQ== ------END PKCS7-----` - -func TestVerifyApkEcdsa(t *testing.T) { - fixture := UnmarshalTestFixture(ApkEcdsaFixture) - p7, err := Parse(fixture.Input) - if err != nil { - t.Errorf("Parse encountered unexpected error: %v", err) - } - p7.Content, err = base64.StdEncoding.DecodeString(ApkEcdsaContent) - if err != nil { - t.Errorf("Failed to decode base64 signature file: %v", err) - } - if err := p7.Verify(); err != nil { - t.Errorf("Verify failed with error: %v", err) - } -} - -var ApkEcdsaFixture = `-----BEGIN PKCS7----- -MIIDAgYJKoZIhvcNAQcCoIIC8zCCAu8CAQExDzANBglghkgBZQMEAgMFADALBgkq -hkiG9w0BBwGgggH3MIIB8zCCAVSgAwIBAgIJAOxXdFsvm3YiMAoGCCqGSM49BAME -MBIxEDAOBgNVBAMMB2VjLXA1MjEwHhcNMTYwMzMxMTUzMTIyWhcNNDMwODE3MTUz -MTIyWjASMRAwDgYDVQQDDAdlYy1wNTIxMIGbMBAGByqGSM49AgEGBSuBBAAjA4GG -AAQAYX95sSjPEQqgyLD04tNUyq9y/w8seblOpfqa/Amx6H4GFdrjGXX0YTfXKr9G -hAyIyQSnNrIg0zDlWQUbBPRW4CYBLFOg1pUn1NBhKFD4NtO1KWvYtNOYDegFjRCP -B0p+fEXDbq8QFDYvlh+NZUJ16+ih8XNIf1C29xuLEqN6oKOnAvajUDBOMB0GA1Ud -DgQWBBT/Ra3kz60gQ7tYk3byZckcLabt8TAfBgNVHSMEGDAWgBT/Ra3kz60gQ7tY -k3byZckcLabt8TAMBgNVHRMEBTADAQH/MAoGCCqGSM49BAMEA4GMADCBiAJCAP39 -hYLsWk2H84oEw+HJqGGjexhqeD3vSO1mWhopripE/81oy3yV10puYoJe11xDSfcD -j2VfNCHazuXO3kSxGA/1AkIBLUJxp/WYbYzhBGKr6lcxczKI/wuMfkZ6vL+0EMJV -A/2uEoeqvnl7BsdkicyaOBNEADijuVdaPPIWzKClt9OaVxExgdAwgc0CAQEwHzAS -MRAwDgYDVQQDDAdlYy1wNTIxAgkA7Fd0Wy+bdiIwDQYJYIZIAWUDBAIDBQAwCgYI -KoZIzj0EAwQEgYswgYgCQgD1pVSNo7qTm9A6tpt3SU2yRa+xpJAnUbpZ+Gu36B71 -JnQBUzRgTGevniqHpyagi7b2zjWh1uvfz9FfrITUwGMddgJCAPjiBRcl7rKpxmZn -V1MvcJOX41xRSJu1wmBiYXqaJarL+gQ/Wl7RYsMtqLjmNColvLaHNxCaWOO/8nAE -Hg0OMA60 ------END PKCS7-----` - -var ApkEcdsaContent = `U2lnbmF0dXJlLVZlcnNpb246IDEuMA0KU0hBLTUxMi1EaWdlc3QtTWFuaWZlc3Q6IFAvVDRqSWtTMjQvNzFxeFE2WW1MeEtNdkRPUUF0WjUxR090dFRzUU9yemhHRQ0KIEpaUGVpWUtyUzZYY090bStYaWlFVC9uS2tYdWVtUVBwZ2RBRzFKUzFnPT0NCkNyZWF0ZWQtQnk6IDEuMCAoQW5kcm9pZCBTaWduQXBrKQ0KDQpOYW1lOiBBbmRyb2lkTWFuaWZlc3QueG1sDQpTSEEtNTEyLURpZ2VzdDogcm9NbWVQZmllYUNQSjFJK2VzMVpsYis0anB2UXowNHZqRWVpL2U0dkN1ald0VVVWSHEzMkNXDQogMUxsOHZiZGMzMCtRc1FlN29ibld4dmhLdXN2K3c1a2c9PQ0KDQpOYW1lOiByZXNvdXJjZXMuYXJzYw0KU0hBLTUxMi1EaWdlc3Q6IG5aYW1aUzlPZTRBRW41cEZaaCtoQ1JFT3krb1N6a3hHdU5YZU0wUFF6WGVBVlVQV3hSVzFPYQ0KIGVLbThRbXdmTmhhaS9HOEcwRUhIbHZEQWdlcy9HUGtBPT0NCg0KTmFtZTogY2xhc3Nlcy5kZXgNClNIQS01MTItRGlnZXN0OiBlbWlDQld2bkVSb0g2N2lCa3EwcUgrdm5tMkpaZDlMWUNEV051N3RNYzJ3bTRtV0dYSUVpWmcNCiBWZkVPV083MFRlZnFjUVhldkNtN2hQMnRpT0U3Y0w5UT09DQoNCg==` - -func TestVerifyFirefoxAddon(t *testing.T) { - fixture := UnmarshalTestFixture(FirefoxAddonFixture) - p7, err := Parse(fixture.Input) - if err != nil { - t.Errorf("Parse encountered unexpected error: %v", err) - } - p7.Content = FirefoxAddonContent - certPool := x509.NewCertPool() - certPool.AppendCertsFromPEM(FirefoxAddonRootCert) - if err := p7.VerifyWithChain(certPool); err != nil { - t.Errorf("Verify failed with error: %v", err) - } - // Verify the certificate chain to make sure the identified root - // is the one we expect - ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, p7.Signers[0].IssuerAndSerialNumber) - if ee == nil { - t.Errorf("No end-entity certificate found for signer") - } - signingTime, _ := time.Parse(time.RFC3339, "2017-02-23 09:06:16-05:00") - chains, err := verifyCertChain(ee, p7.Certificates, certPool, signingTime) - if err != nil { - t.Error(err) - } - if len(chains) != 1 { - t.Errorf("Expected to find one chain, but found %d", len(chains)) - } - if len(chains[0]) != 3 { - t.Errorf("Expected to find three certificates in chain, but found %d", len(chains[0])) - } - if chains[0][0].Subject.CommonName != "tabscope@xuldev.org" { - t.Errorf("Expected to find EE certificate with subject 'tabscope@xuldev.org', but found '%s'", chains[0][0].Subject.CommonName) - } - if chains[0][1].Subject.CommonName != "production-signing-ca.addons.mozilla.org" { - t.Errorf("Expected to find intermediate certificate with subject 'production-signing-ca.addons.mozilla.org', but found '%s'", chains[0][1].Subject.CommonName) - } - if chains[0][2].Subject.CommonName != "root-ca-production-amo" { - t.Errorf("Expected to find root certificate with subject 'root-ca-production-amo', but found '%s'", chains[0][2].Subject.CommonName) - } -} - -var FirefoxAddonContent = []byte(`Signature-Version: 1.0 -MD5-Digest-Manifest: KjRavc6/KNpuT1QLcB/Gsg== -SHA1-Digest-Manifest: 5Md5nUg+U7hQ/UfzV+xGKWOruVI= - -`) - -var FirefoxAddonFixture = ` ------BEGIN PKCS7----- -MIIQTAYJKoZIhvcNAQcCoIIQPTCCEDkCAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3 -DQEHAaCCDL0wggW6MIIDoqADAgECAgYBVpobWVwwDQYJKoZIhvcNAQELBQAwgcUx -CzAJBgNVBAYTAlVTMRwwGgYDVQQKExNNb3ppbGxhIENvcnBvcmF0aW9uMS8wLQYD -VQQLEyZNb3ppbGxhIEFNTyBQcm9kdWN0aW9uIFNpZ25pbmcgU2VydmljZTExMC8G -A1UEAxMocHJvZHVjdGlvbi1zaWduaW5nLWNhLmFkZG9ucy5tb3ppbGxhLm9yZzE0 -MDIGCSqGSIb3DQEJARYlc2VydmljZXMtb3BzK2FkZG9uc2lnbmluZ0Btb3ppbGxh -LmNvbTAeFw0xNjA4MTcyMDA0NThaFw0yMTA4MTYyMDA0NThaMHYxEzARBgNVBAsT -ClByb2R1Y3Rpb24xCzAJBgNVBAYTAlVTMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3 -MQ8wDQYDVQQKEwZBZGRvbnMxCzAJBgNVBAgTAkNBMRwwGgYDVQQDFBN0YWJzY29w -ZUB4dWxkZXYub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAv6e0 -mPD8dt4J8HTNNq4ODns2DV6Weh1hllCIFvOeu1u3UrR03st0BMY8OXYwr/NvRVjg -bA8gRySWAL+XqLzbhtXNeNegAoxrF+3mYY5rJjsLj/FGI6P6OXjngqwgm9VTBl7m -jh/KXBSwYoUcavJo6cmk8sCFwoblyQiv+tsWaUCOI6zMzubNtIS+GFvET9y/VZMP -j6mk8O10wBgJF5MMtA19va3qXy7aCZ7DnZp1l3equd/L6t324TtXoqx6xWQKo6TM -I0mcTlKvm6TKegTGBCyGn3JRARoIJv4AW1qqgyaHXf9EoY2pKT8Avkri5++NuSJ6 -jtO4k/diBA2MZU20U0KGffYZNTxKDqd6XtI6y1tJPd/OWRFyU+mHntkcm9sar7L3 -nPKujHRox2re10ec1WBnJE3PjlAoesNjxzp+xs2mGGc8DX9NuWn+1uK9xmgGIIMl -OFfyQ4s0G6hKp5goFcrFZxmexu0ZahOs8vZf8xDBW7yR1zToQElOXHvrscM386os -kOF9IxQZfcCoPuNQVg1haCONNkx0oau3RQQlOSAZtC79b+rBjQ5JYfjRLYAworf2 -xQaprCh33TD1dTBrvzEbCGszgkN53Vqh5TFBjbU/NyldOkGvK8Xf6WhT5u+aftnV -lbuE2McAg6x1AlloUZq6PNTBpz7zypcIISnQ+y8CAwEAATANBgkqhkiG9w0BAQsF -AAOCAgEAIBoo2+OEYNCgP/IbUj9azaf/lde1q4AK/uTMoUeS5WcrXd8aqA0Y1qV7 -xUALgDQAExXgqcOMGu4mPMaoZDgwGI4Tj7XPJQq5Z5zYxpRf/Wtzae33T9BF6QPW -v5xiRYuol+FbEtqRHZqxDWtIrd1MWBy3wjO3pLPdzDM9jWh+HLxdGWThJszaZp3T -CqsOx+l9W0Q7qM5ioZpHStgXDfhw38Lg++kLnzcX9MqsjYyezdwE4krqW6hK3+4S -0LZE4dTgsy8JULkyAF3HrPWEXESnD7c4mx6owZe+BNDK5hsVM/obAqH7sJq/igbM -5N1l832p/ws8l5xKOr3qBWSzWn6u7ExvqG6Ckh0foJOVXvzGqvrXcoiBGV8S9Z7c -DghUvMt6b0pZ0ildRCHfTUz7eG3g4MhfbjupR7b+L9FWEJhcd/H0dxpw7SKYha/n -ePuRL7MXmbW8WLMqO/ImxzL8TPOB3pUg3nITfubV6gpPBmn+0nwbqYUmggJuwgvK -I2GpN2Ny6EErZy17EEgyhJygJZMj+UzQjC781xxsl3ljpYEqqwgRLIZBSBUD5dXj -XBuU24w162SeSyHZzkBbuv6lr52pqoZyFrG29DCHECgO9ZmNWgSpiWSkh+vExAG7 -wNs0y61t2HUG+BCMGPQ9sOzouyTfrnLVAWwzswGftFYQfoIBeJIwggb7MIIE46AD -AgECAgMQAAIwDQYJKoZIhvcNAQEMBQAwfTELMAkGA1UEBhMCVVMxHDAaBgNVBAoT -E01vemlsbGEgQ29ycG9yYXRpb24xLzAtBgNVBAsTJk1vemlsbGEgQU1PIFByb2R1 -Y3Rpb24gU2lnbmluZyBTZXJ2aWNlMR8wHQYDVQQDExZyb290LWNhLXByb2R1Y3Rp -b24tYW1vMB4XDTE1MDMxNzIzNTI0MloXDTI1MDMxNDIzNTI0MlowgcUxCzAJBgNV -BAYTAlVTMRwwGgYDVQQKExNNb3ppbGxhIENvcnBvcmF0aW9uMS8wLQYDVQQLEyZN -b3ppbGxhIEFNTyBQcm9kdWN0aW9uIFNpZ25pbmcgU2VydmljZTExMC8GA1UEAxMo -cHJvZHVjdGlvbi1zaWduaW5nLWNhLmFkZG9ucy5tb3ppbGxhLm9yZzE0MDIGCSqG -SIb3DQEJARYlc2VydmljZXMtb3BzK2FkZG9uc2lnbmluZ0Btb3ppbGxhLmNvbTCC -AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMLMM9m2HBLhCiO9mhljpehT -hxpzlCnxluzDZ51I/H7MvBbIvZBm9zSpHdffubSsak2qYE69d+ebTa/CK83WIosM -24/2Qp7n/GGaPJcCC4Y3JkrCsgA8+wV2MbFlKSv+qMdvI/sE3BPYDMCjVPMhHmIP -XaPWd42OoHpI8R3GGUtVnR3Hm76pa2+v6TwgeMiO8om+ogGufiyv6FNMZ5NuY1Z9 -aLNEvehnAzSfddQyki+6FJd7XkgZbP7pb1Kl8yYgiy4piBerJ9H09uPehffE3Ell -3cApQL3+0kjaUX4scMjuNQDMKziRZkYgJAM+qA9WA5Jn77AjerQBWQeEev1PWHYh -0IDlgS/a0bjKmVjNZYG6adrY/R5/whzWGFCIE1UfhPm6PdN0557qvF838C2RFHsI -KzV6KQf0chMjpa02tPaIctjVhnDQZZNKm2ZfLOt9kQ57Is/e6KxH7pYMit46+s99 -lYM7ZquvWbK19b1Ili/6S1BxSzd3wztgfN5jGsc+jCCYLm+AcVtfNKc8cFZHXKrB -CwhGmdbWDSBCicZNA7FKJpO3oIx26VPF2XUldA/T5Mh/POGLilK3t9m9qbjEyDp1 -EwoBToOR/aMrdnNYvSWp0g/GHMzSfJjjXyAqrZY2itam/IJd8r9FoRAzevPt/zTX -BET3INoiCDGRH0XrxUYtAgMGVTejggE5MIIBNTAMBgNVHRMEBTADAQH/MA4GA1Ud -DwEB/wQEAwIBBjAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUdHxf -FKXipZLjs20GqIUdNkQXH4gwgagGA1UdIwSBoDCBnYAUs7zqWHSr4W54KrKrnCMe -qGMsl7ehgYGkfzB9MQswCQYDVQQGEwJVUzEcMBoGA1UEChMTTW96aWxsYSBDb3Jw -b3JhdGlvbjEvMC0GA1UECxMmTW96aWxsYSBBTU8gUHJvZHVjdGlvbiBTaWduaW5n -IFNlcnZpY2UxHzAdBgNVBAMTFnJvb3QtY2EtcHJvZHVjdGlvbi1hbW+CAQEwMwYJ -YIZIAYb4QgEEBCYWJGh0dHA6Ly9hZGRvbnMubW96aWxsYS5vcmcvY2EvY3JsLnBl -bTANBgkqhkiG9w0BAQwFAAOCAgEArde/fdjb7TE0eH7Ij7xU4JbcSyhY3cQhVYCw -Fg+Q/2pj+NAfazcjUuLWA0Y/YZs9HOx6j+ZAqO4C/xfMP4RDs9IypxvzHDU6SXgD -RK6uOKtS07HXLcXgFUBvJEQhbT/h5+IQOA4/GcpCshfD6iyiBBi+IocR+tnKPCuZ -T3m1t60Eja/MkPKG/Gx8vSodHvlTTsJ2GzjUEANveCZOnlAdp9fjTvFZny9qqnbg -sfVbuTqKndbCFW5QLXfkna6jBqMrY0+CpMYY2oJ5gwpHbE/7hhukjxGCTcpv7r/O -M53bb/DZnybDlLLepacljvz7DBA1O1FFtEhf9MR+vyvmBpniAyKQhqG2hsVGurE1 -nBcE+oteZWar2lMp6+etDAb9DRC+jZv0aEQs2o/qQwyD8AGquLgBsJq5Jz3gGxzn -4r3vGu2lV8VdzIm0C8sOFSWTmTZxQmJbF8xSsQBojnsvEah4DPER+eAt6qKolaWe -s4drJQjzFyC7HJn2VqalpCwbe9CdMB7eRqzeP6GujJBi80/gx0pAysUtuKKpH5IJ -WbXAOszfrjb3CaHafYZDnwPoOfj74ogFzjt2f54jwnU+ET/byfjZ7J8SLH316C1V -HrvFXcTzyMV4aRluVPjPg9x1G58hMIbeuT4GpwQUNdJ9uL8t65v0XwG2t6Y7jpRO -sFVxBtgxggNXMIIDUwIBATCB0DCBxTELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE01v -emlsbGEgQ29ycG9yYXRpb24xLzAtBgNVBAsTJk1vemlsbGEgQU1PIFByb2R1Y3Rp -b24gU2lnbmluZyBTZXJ2aWNlMTEwLwYDVQQDEyhwcm9kdWN0aW9uLXNpZ25pbmct -Y2EuYWRkb25zLm1vemlsbGEub3JnMTQwMgYJKoZIhvcNAQkBFiVzZXJ2aWNlcy1v -cHMrYWRkb25zaWduaW5nQG1vemlsbGEuY29tAgYBVpobWVwwCQYFKw4DAhoFAKBd -MBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE2MDgx -NzIwMDQ1OFowIwYJKoZIhvcNAQkEMRYEFAxlGvNFSx+Jqj70haE8b7UZk+2GMA0G -CSqGSIb3DQEBAQUABIICADsDlrucYRgwq9o2QSsO6X6cRa5Zu6w+1n07PTIyc1zn -Pi1cgkkWZ0kZBHDrJ5CY33yRQPl6I1tHXaq7SkOSdOppKhpUmBiKZxQRAZR21QHk -R3v1XS+st/o0N+0btv3YoplUifLIwtH89oolxqlStChELu7FuOBretdhx/z12ytA -EhIIS53o/XjDL7XKJbQA02vzOtOC/Eq6p8BI7F3y6pvtmJIRkeGv+u6ssJa6g5q8 -74w8hHXaH94Z9+hDPqjNWlsXJHgPdAKiEjzDz9oLkvDyX4Pd8JMK5ILskirpG+hj -Q8jkTc5oYwyuSlBAUTGxW6ZbuOrtfVZvOVtRL/ixuiFiVlJ+JOQOxrtK19ukamsI -iacFlbLgiA7w0HCtm2DsT9aL67/1e4rJ0lv0MjnQYUMmKQy7g0Gd3+nQPU9pn+Lf -Z/UmSNWiJ8Csc/seDMyzT6jrzcGPfoSVaUowH0wGrI9If1snwcr+mMg7dWRGf1fm -y/dcVSzed0ax4LqDmike1EshU+51cKWWlnhyNHK4KH+0fNsBQ0c6clrFpGx9MPmV -YXie6C+LWkh5x12RU0sJt/SmSZV6q9VliIkX+yY3jBrC/pKgRahtcIyq46Da1E6K -lc15Euur3NfGow+nott0Z8XutpYdK/2vBKcIh9JOdkd+oe6pcIP6hnhHRp53wqmG ------END PKCS7-----` - -var FirefoxAddonRootCert = []byte(` ------BEGIN CERTIFICATE----- -MIIGYTCCBEmgAwIBAgIBATANBgkqhkiG9w0BAQwFADB9MQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTTW96aWxsYSBDb3Jwb3JhdGlvbjEvMC0GA1UECxMmTW96aWxsYSBB -TU8gUHJvZHVjdGlvbiBTaWduaW5nIFNlcnZpY2UxHzAdBgNVBAMTFnJvb3QtY2Et -cHJvZHVjdGlvbi1hbW8wHhcNMTUwMzE3MjI1MzU3WhcNMjUwMzE0MjI1MzU3WjB9 -MQswCQYDVQQGEwJVUzEcMBoGA1UEChMTTW96aWxsYSBDb3Jwb3JhdGlvbjEvMC0G -A1UECxMmTW96aWxsYSBBTU8gUHJvZHVjdGlvbiBTaWduaW5nIFNlcnZpY2UxHzAd -BgNVBAMTFnJvb3QtY2EtcHJvZHVjdGlvbi1hbW8wggIgMA0GCSqGSIb3DQEBAQUA -A4ICDQAwggIIAoICAQC0u2HXXbrwy36+MPeKf5jgoASMfMNz7mJWBecJgvlTf4hH -JbLzMPsIUauzI9GEpLfHdZ6wzSyFOb4AM+D1mxAWhuZJ3MDAJOf3B1Rs6QorHrl8 -qqlNtPGqepnpNJcLo7JsSqqE3NUm72MgqIHRgTRsqUs+7LIPGe7262U+N/T0LPYV -Le4rZ2RDHoaZhYY7a9+49mHOI/g2YFB+9yZjE+XdplT2kBgA4P8db7i7I0tIi4b0 -B0N6y9MhL+CRZJyxdFe2wBykJX14LsheKsM1azHjZO56SKNrW8VAJTLkpRxCmsiT -r08fnPyDKmaeZ0BtsugicdipcZpXriIGmsZbI12q5yuwjSELdkDV6Uajo2n+2ws5 -uXrP342X71WiWhC/dF5dz1LKtjBdmUkxaQMOP/uhtXEKBrZo1ounDRQx1j7+SkQ4 -BEwjB3SEtr7XDWGOcOIkoJZWPACfBLC3PJCBWjTAyBlud0C5n3Cy9regAAnOIqI1 -t16GU2laRh7elJ7gPRNgQgwLXeZcFxw6wvyiEcmCjOEQ6PM8UQjthOsKlszMhlKw -vjyOGDoztkqSBy/v+Asx7OW2Q7rlVfKarL0mREZdSMfoy3zTgtMVCM0vhNl6zcvf -5HNNopoEdg5yuXo2chZ1p1J+q86b0G5yJRMeT2+iOVY2EQ37tHrqUURncCy4uwIB -A6OB7TCB6jAMBgNVHRMEBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAWBgNVHSUBAf8E -DDAKBggrBgEFBQcDAzCBkgYDVR0jBIGKMIGHoYGBpH8wfTELMAkGA1UEBhMCVVMx -HDAaBgNVBAoTE01vemlsbGEgQ29ycG9yYXRpb24xLzAtBgNVBAsTJk1vemlsbGEg -QU1PIFByb2R1Y3Rpb24gU2lnbmluZyBTZXJ2aWNlMR8wHQYDVQQDExZyb290LWNh -LXByb2R1Y3Rpb24tYW1vggEBMB0GA1UdDgQWBBSzvOpYdKvhbngqsqucIx6oYyyX -tzANBgkqhkiG9w0BAQwFAAOCAgEAaNSRYAaECAePQFyfk12kl8UPLh8hBNidP2H6 -KT6O0vCVBjxmMrwr8Aqz6NL+TgdPmGRPDDLPDpDJTdWzdj7khAjxqWYhutACTew5 -eWEaAzyErbKQl+duKvtThhV2p6F6YHJ2vutu4KIciOMKB8dslIqIQr90IX2Usljq -8Ttdyf+GhUmazqLtoB0GOuESEqT4unX6X7vSGu1oLV20t7t5eCnMMYD67ZBn0YIU -/cm/+pan66hHrja+NeDGF8wabJxdqKItCS3p3GN1zUGuJKrLykxqbOp/21byAGog -Z1amhz6NHUcfE6jki7sM7LHjPostU5ZWs3PEfVVgha9fZUhOrIDsyXEpCWVa3481 -LlAq3GiUMKZ5DVRh9/Nvm4NwrTfB3QkQQJCwfXvO9pwnPKtISYkZUqhEqvXk5nBg -QCkDSLDjXTx39naBBGIVIqBtKKuVTla9enngdq692xX/CgO6QJVrwpqdGjebj5P8 -5fNZPABzTezG3Uls5Vp+4iIWVAEDkK23cUj3c/HhE+Oo7kxfUeu5Y1ZV3qr61+6t -ZARKjbu1TuYQHf0fs+GwID8zeLc2zJL7UzcHFwwQ6Nda9OJN4uPAuC/BKaIpxCLL -26b24/tRam4SJjqpiq20lynhUrmTtt6hbG3E1Hpy3bmkt2DYnuMFwEx2gfXNcnbT -wNuvFqc= ------END CERTIFICATE-----`) - -// sign a document with openssl and verify the signature with pkcs7. -// this uses a chain of root, intermediate and signer cert, where the -// intermediate is added to the certs but the root isn't. -func TestSignWithOpenSSLAndVerify(t *testing.T) { - content := []byte(` -A ship in port is safe, -but that's not what ships are built for. --- Grace Hopper`) - // write the content to a temp file - tmpContentFile, err := ioutil.TempFile("", "TestSignWithOpenSSLAndVerify_content") - if err != nil { - t.Fatal(err) - } - ioutil.WriteFile(tmpContentFile.Name(), content, 0755) - sigalgs := []x509.SignatureAlgorithm{ - x509.SHA1WithRSA, - x509.SHA256WithRSA, - x509.SHA512WithRSA, - x509.ECDSAWithSHA1, - x509.ECDSAWithSHA256, - x509.ECDSAWithSHA384, - x509.ECDSAWithSHA512, - } - for _, sigalgroot := range sigalgs { - rootCert, err := createTestCertificateByIssuer("PKCS7 Test Root CA", nil, sigalgroot, true) - if err != nil { - t.Fatalf("test %s: cannot generate root cert: %s", sigalgroot, err) - } - truststore := x509.NewCertPool() - truststore.AddCert(rootCert.Certificate) - for _, sigalginter := range sigalgs { - interCert, err := createTestCertificateByIssuer("PKCS7 Test Intermediate Cert", rootCert, sigalginter, true) - if err != nil { - t.Fatalf("test %s/%s: cannot generate intermediate cert: %s", sigalgroot, sigalginter, err) - } - // write the intermediate cert to a temp file - tmpInterCertFile, err := ioutil.TempFile("", "TestSignWithOpenSSLAndVerify_intermediate") - if err != nil { - t.Fatal(err) - } - fd, err := os.OpenFile(tmpInterCertFile.Name(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) - if err != nil { - t.Fatal(err) - } - pem.Encode(fd, &pem.Block{Type: "CERTIFICATE", Bytes: interCert.Certificate.Raw}) - fd.Close() - for _, sigalgsigner := range sigalgs { - signerCert, err := createTestCertificateByIssuer("PKCS7 Test Signer Cert", interCert, sigalgsigner, false) - if err != nil { - t.Fatalf("test %s/%s/%s: cannot generate signer cert: %s", sigalgroot, sigalginter, sigalgsigner, err) - } - - // write the signer cert to a temp file - tmpSignerCertFile, err := ioutil.TempFile("", "TestSignWithOpenSSLAndVerify_signer") - if err != nil { - t.Fatal(err) - } - fd, err = os.OpenFile(tmpSignerCertFile.Name(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) - if err != nil { - t.Fatal(err) - } - pem.Encode(fd, &pem.Block{Type: "CERTIFICATE", Bytes: signerCert.Certificate.Raw}) - fd.Close() - - // write the signer key to a temp file - tmpSignerKeyFile, err := ioutil.TempFile("", "TestSignWithOpenSSLAndVerify_key") - if err != nil { - t.Fatal(err) - } - fd, err = os.OpenFile(tmpSignerKeyFile.Name(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) - if err != nil { - t.Fatal(err) - } - var derKey []byte - priv := *signerCert.PrivateKey - switch priv := priv.(type) { - case *rsa.PrivateKey: - derKey = x509.MarshalPKCS1PrivateKey(priv) - pem.Encode(fd, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: derKey}) - case *ecdsa.PrivateKey: - derKey, err = x509.MarshalECPrivateKey(priv) - if err != nil { - t.Fatal(err) - } - pem.Encode(fd, &pem.Block{Type: "EC PRIVATE KEY", Bytes: derKey}) - } - fd.Close() - - // write the root cert to a temp file - tmpSignedFile, err := ioutil.TempFile("", "TestSignWithOpenSSLAndVerify_signature") - if err != nil { - t.Fatal(err) - } - // call openssl to sign the content - opensslCMD := exec.Command("openssl", "smime", "-sign", "-nodetach", - "-in", tmpContentFile.Name(), "-out", tmpSignedFile.Name(), - "-signer", tmpSignerCertFile.Name(), "-inkey", tmpSignerKeyFile.Name(), - "-certfile", tmpInterCertFile.Name(), "-outform", "PEM") - out, err := opensslCMD.CombinedOutput() - if err != nil { - t.Fatalf("test %s/%s/%s: openssl command failed with %s: %s", sigalgroot, sigalginter, sigalgsigner, err, out) - } - - // verify the signed content - pemSignature, err := ioutil.ReadFile(tmpSignedFile.Name()) - if err != nil { - t.Fatal(err) - } - derBlock, _ := pem.Decode(pemSignature) - if derBlock == nil { - break - } - p7, err := Parse(derBlock.Bytes) - if err != nil { - t.Fatalf("Parse encountered unexpected error: %v", err) - } - if err := p7.VerifyWithChain(truststore); err != nil { - t.Fatalf("Verify failed with error: %v", err) - } - // Verify the certificate chain to make sure the identified root - // is the one we expect - ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, p7.Signers[0].IssuerAndSerialNumber) - if ee == nil { - t.Fatalf("No end-entity certificate found for signer") - } - chains, err := verifyCertChain(ee, p7.Certificates, truststore, time.Now()) - if err != nil { - t.Fatal(err) - } - if len(chains) != 1 { - t.Fatalf("Expected to find one chain, but found %d", len(chains)) - } - if len(chains[0]) != 3 { - t.Fatalf("Expected to find three certificates in chain, but found %d", len(chains[0])) - } - if chains[0][0].Subject.CommonName != "PKCS7 Test Signer Cert" { - t.Fatalf("Expected to find EE certificate with subject 'PKCS7 Test Signer Cert', but found '%s'", chains[0][0].Subject.CommonName) - } - if chains[0][1].Subject.CommonName != "PKCS7 Test Intermediate Cert" { - t.Fatalf("Expected to find intermediate certificate with subject 'PKCS7 Test Intermediate Cert', but found '%s'", chains[0][1].Subject.CommonName) - } - if chains[0][2].Subject.CommonName != "PKCS7 Test Root CA" { - t.Fatalf("Expected to find root certificate with subject 'PKCS7 Test Root CA', but found '%s'", chains[0][2].Subject.CommonName) - } - os.Remove(tmpSignerCertFile.Name()) // clean up - os.Remove(tmpSignerKeyFile.Name()) // clean up - } - os.Remove(tmpInterCertFile.Name()) // clean up - } - } - os.Remove(tmpContentFile.Name()) // clean up -} - -func TestDSASignWithOpenSSLAndVerify(t *testing.T) { - content := []byte(` -A ship in port is safe, -but that's not what ships are built for. --- Grace Hopper`) - // write the content to a temp file - tmpContentFile, err := ioutil.TempFile("", "TestDSASignWithOpenSSLAndVerify_content") - if err != nil { - t.Fatal(err) - } - ioutil.WriteFile(tmpContentFile.Name(), content, 0755) - - // write the signer cert to a temp file - tmpSignerCertFile, err := ioutil.TempFile("", "TestDSASignWithOpenSSLAndVerify_signer") - if err != nil { - t.Fatal(err) - } - ioutil.WriteFile(tmpSignerCertFile.Name(), dsaPublicCert, 0755) - - // write the signer key to a temp file - tmpSignerKeyFile, err := ioutil.TempFile("", "TestDSASignWithOpenSSLAndVerify_key") - if err != nil { - t.Fatal(err) - } - ioutil.WriteFile(tmpSignerKeyFile.Name(), dsaPrivateKey, 0755) - - tmpSignedFile, err := ioutil.TempFile("", "TestDSASignWithOpenSSLAndVerify_signature") - if err != nil { - t.Fatal(err) - } - // call openssl to sign the content - opensslCMD := exec.Command("openssl", "smime", "-sign", "-nodetach", "-md", "sha1", - "-in", tmpContentFile.Name(), "-out", tmpSignedFile.Name(), - "-signer", tmpSignerCertFile.Name(), "-inkey", tmpSignerKeyFile.Name(), - "-certfile", tmpSignerCertFile.Name(), "-outform", "PEM") - out, err := opensslCMD.CombinedOutput() - if err != nil { - t.Fatalf("openssl command failed with %s: %s", err, out) - } - - // verify the signed content - pemSignature, err := ioutil.ReadFile(tmpSignedFile.Name()) - if err != nil { - t.Fatal(err) - } - fmt.Printf("%s\n", pemSignature) - derBlock, _ := pem.Decode(pemSignature) - if derBlock == nil { - t.Fatalf("failed to read DER block from signature PEM %s", tmpSignedFile.Name()) - } - p7, err := Parse(derBlock.Bytes) - if err != nil { - t.Fatalf("Parse encountered unexpected error: %v", err) - } - if err := p7.Verify(); err != nil { - t.Fatalf("Verify failed with error: %v", err) - } - os.Remove(tmpSignerCertFile.Name()) // clean up - os.Remove(tmpSignerKeyFile.Name()) // clean up - os.Remove(tmpContentFile.Name()) // clean up -} - -var dsaPrivateKey = []byte(`-----BEGIN PRIVATE KEY----- -MIIBSwIBADCCASwGByqGSM44BAEwggEfAoGBAP1/U4EddRIpUt9KnC7s5Of2EbdS -PO9EAMMeP4C2USZpRV1AIlH7WT2NWPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVCl -pJ+f6AR7ECLCT7up1/63xhv4O1fnxqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith -1yrv8iIDGZ3RSAHHAhUAl2BQjxUjC8yykrmCouuEC/BYHPUCgYEA9+GghdabPd7L -vKtcNrhXuXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3 -zwkyjMim4TwWeotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImo -g9/hWuWfBpKLZl6Ae1UlZAFMO/7PSSoEFgIUfW4aPdQBn9gJZp2KuNpzgHzvfsE= ------END PRIVATE KEY-----`) - -var dsaPublicCert = []byte(`-----BEGIN CERTIFICATE----- -MIIDOjCCAvWgAwIBAgIEPCY/UDANBglghkgBZQMEAwIFADBsMRAwDgYDVQQGEwdV -bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD -VQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3du -MB4XDTE4MTAyMjEzNDMwN1oXDTQ2MDMwOTEzNDMwN1owbDEQMA4GA1UEBhMHVW5r -bm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UE -ChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCC -AbgwggEsBgcqhkjOOAQBMIIBHwKBgQD9f1OBHXUSKVLfSpwu7OTn9hG3UjzvRADD -Hj+AtlEmaUVdQCJR+1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gE -exAiwk+7qdf+t8Yb+DtX58aophUPBPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/Ii -Axmd0UgBxwIVAJdgUI8VIwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4 -V7l5lK+7+jrqgvlXTAs9B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozI -puE8FnqLVHyNKOCjrh4rs6Z1kW6jfwv6ITVi8ftiegEkO8yk8b6oUZCJqIPf4Vrl -nwaSi2ZegHtVJWQBTDv+z0kqA4GFAAKBgQDCriMPbEVBoRK4SOUeFwg7+VRf4TTp -rcOQC9IVVoCjXzuWEGrp3ZI7YWJSpFnSch4lk29RH8O0HpI/NOzKnOBtnKr782pt -1k/bJVMH9EaLd6MKnAVjrCDMYBB0MhebZ8QHY2elZZCWoqDYAcIDOsEx+m4NLErT -ypPnjS5M0jm1PKMhMB8wHQYDVR0OBBYEFC0Yt5XdM0Kc95IX8NQ8XRssGPx7MA0G -CWCGSAFlAwQDAgUAAzAAMC0CFQCIgQtrZZ9hdZG1ROhR5hc8nYEmbgIUAIlgC688 -qzy/7yePTlhlpj+ahMM= ------END CERTIFICATE-----`) diff --git a/scep/scep.go b/scep/scep.go index bc46cce7..6a636aee 100644 --- a/scep/scep.go +++ b/scep/scep.go @@ -6,7 +6,9 @@ import ( microscep "github.com/micromdm/scep/scep" - "github.com/smallstep/certificates/scep/pkcs7" + //"github.com/smallstep/certificates/scep/pkcs7" + + "go.mozilla.org/pkcs7" ) // SCEP OIDs From 812e1c72181cdf8d49ff7125a304198897623baa Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Feb 2021 12:32:43 +0100 Subject: [PATCH 31/89] Add handling of options --- scep/api/api.go | 35 ++++++++++++----------------------- scep/authority.go | 40 +++++++++++++++++++++++----------------- scep/common.go | 22 ++++++++++++++++++++++ 3 files changed, 57 insertions(+), 40 deletions(-) create mode 100644 scep/common.go diff --git a/scep/api/api.go b/scep/api/api.go index ec959519..c73bb53a 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -235,12 +235,12 @@ func (h *Handler) GetCACert(w http.ResponseWriter, r *http.Request, scepResponse func (h *Handler) GetCACaps(w http.ResponseWriter, r *http.Request, scepResponse SCEPResponse) error { - ctx := r.Context() + //ctx := r.Context() - _, err := ProvisionerFromContext(ctx) - if err != nil { - return err - } + // _, err := ProvisionerFromContext(ctx) + // if err != nil { + // return err + // } // TODO: get the actual capabilities from provisioner config scepResponse.Data = formatCapabilities(defaultCapabilities) @@ -250,6 +250,8 @@ func (h *Handler) GetCACaps(w http.ResponseWriter, r *http.Request, scepResponse func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepRequest SCEPRequest, scepResponse SCEPResponse) error { + ctx := r.Context() + msg, err := microscep.ParsePKIMessage(scepRequest.Message) if err != nil { return err @@ -262,7 +264,7 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque Raw: msg.Raw, } - if err := h.Auth.DecryptPKIEnvelope(pkimsg); err != nil { + if err := h.Auth.DecryptPKIEnvelope(ctx, pkimsg); err != nil { return err } @@ -271,7 +273,7 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque } csr := pkimsg.CSRReqMessage.CSR - id, err := createKeyIdentifier(csr.PublicKey) + subjectKeyID, err := createKeyIdentifier(csr.PublicKey) if err != nil { return err } @@ -286,16 +288,17 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque Subject: csr.Subject, NotBefore: time.Now().Add(-600).UTC(), NotAfter: time.Now().AddDate(0, 0, days).UTC(), - SubjectKeyId: id, + SubjectKeyId: subjectKeyID, KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{ x509.ExtKeyUsageClientAuth, }, SignatureAlgorithm: csr.SignatureAlgorithm, EmailAddresses: csr.EmailAddresses, + DNSNames: csr.DNSNames, } - certRep, err := h.Auth.SignCSR(pkimsg, template) + certRep, err := h.Auth.SignCSR(ctx, pkimsg, template) if err != nil { return err } @@ -381,17 +384,3 @@ func contentHeader(operation string, certNum int) string { return "text/plain" } } - -// ProvisionerFromContext searches the context for a provisioner. Returns the -// provisioner or an error. -func ProvisionerFromContext(ctx context.Context) (scep.Provisioner, error) { - val := ctx.Value(acme.ProvisionerContextKey) - if val == nil { - return nil, errors.New("provisioner expected in request context") - } - p, ok := val.(scep.Provisioner) - if !ok || p == nil { - return nil, errors.New("provisioner in context is not a SCEP provisioner") - } - return p, nil -} diff --git a/scep/authority.go b/scep/authority.go index 864ecbba..cb191d34 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -2,6 +2,7 @@ package scep import ( "bytes" + "context" "crypto/x509" "errors" "fmt" @@ -54,8 +55,8 @@ type Interface interface { GetCACertificates() ([]*x509.Certificate, error) //GetSigningKey() (*rsa.PrivateKey, error) - DecryptPKIEnvelope(*PKIMessage) error - SignCSR(msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) + DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error + SignCSR(ctx context.Context, msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) } // Authority is the layer that handles all SCEP interactions. @@ -156,7 +157,7 @@ func (a *Authority) GetCACertificates() ([]*x509.Certificate, error) { } // DecryptPKIEnvelope decrypts an enveloped message -func (a *Authority) DecryptPKIEnvelope(msg *PKIMessage) error { +func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error { data := msg.Raw @@ -221,14 +222,19 @@ func (a *Authority) DecryptPKIEnvelope(msg *PKIMessage) error { return nil } -// SignCSR creates an x509.Certificate based on a template and Cert Authority credentials +// SignCSR creates an x509.Certificate based on a CSR template and Cert Authority credentials // returns a new PKIMessage with CertRep data //func (msg *PKIMessage) SignCSR(crtAuth *x509.Certificate, keyAuth *rsa.PrivateKey, template *x509.Certificate) (*PKIMessage, error) { -func (a *Authority) SignCSR(msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) { +func (a *Authority) SignCSR(ctx context.Context, msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) { + + p, err := ProvisionerFromContext(ctx) + if err != nil { + return nil, err + } // check if CSRReqMessage has already been decrypted if msg.CSRReqMessage.CSR == nil { - if err := a.DecryptPKIEnvelope(msg); err != nil { + if err := a.DecryptPKIEnvelope(ctx, msg); err != nil { return nil, err } } @@ -238,13 +244,17 @@ func (a *Authority) SignCSR(msg *PKIMessage, template *x509.Certificate) (*PKIMe // Template data data := x509util.NewTemplateData() data.SetCommonName(csr.Subject.CommonName) - //data.Set(x509util.SANsKey, sans) + data.SetSANs(csr.DNSNames) - // templateOptions, err := provisioner.TemplateOptions(p.GetOptions(), data) - // if err != nil { - // return nil, ServerInternalErr(errors.Wrapf(err, "error creating template options from ACME provisioner")) - // } - // signOps = append(signOps, templateOptions) + // TODO: proper options + opts := provisioner.SignOptions{} + signOps := []provisioner.SignOption{} + + templateOptions, err := provisioner.TemplateOptions(p.GetOptions(), data) + if err != nil { + return nil, fmt.Errorf("error creating template options from SCEP provisioner") + } + signOps = append(signOps, templateOptions) // // Create and store a new certificate. // certChain, err := auth.Sign(csr, provisioner.SignOptions{ @@ -255,11 +265,7 @@ func (a *Authority) SignCSR(msg *PKIMessage, template *x509.Certificate) (*PKIMe // return nil, ServerInternalErr(errors.Wrapf(err, "error generating certificate for order %s", o.ID)) // } - // TODO: proper options - signOps := provisioner.SignOptions{} - signOps2 := []provisioner.SignOption{} - - certs, err := a.signAuth.Sign(csr, signOps, signOps2...) + certs, err := a.signAuth.Sign(csr, opts, signOps...) if err != nil { return nil, err } diff --git a/scep/common.go b/scep/common.go new file mode 100644 index 00000000..123c8d82 --- /dev/null +++ b/scep/common.go @@ -0,0 +1,22 @@ +package scep + +import ( + "context" + "errors" + + "github.com/smallstep/certificates/acme" +) + +// ProvisionerFromContext searches the context for a SCEP provisioner. +// Returns the provisioner or an error. +func ProvisionerFromContext(ctx context.Context) (Provisioner, error) { + val := ctx.Value(acme.ProvisionerContextKey) + if val == nil { + return nil, errors.New("provisioner expected in request context") + } + p, ok := val.(Provisioner) + if !ok || p == nil { + return nil, errors.New("provisioner in context is not a SCEP provisioner") + } + return p, nil +} From da65f46d0f99f08bf1b0e83649fd184672e1b0bb Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Feb 2021 14:00:47 +0100 Subject: [PATCH 32/89] Add AuthorizeSign method to SCEP authority --- authority/provisioner/scep.go | 18 ++++++++++++- scep/authority.go | 48 +++++++++++++++++------------------ scep/provisioner.go | 3 ++- 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 6cdfa69f..10414b5e 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -1,6 +1,7 @@ package provisioner import ( + "context" "time" "github.com/pkg/errors" @@ -13,7 +14,7 @@ type SCEP struct { Type string `json:"type"` Name string `json:"name"` - // ForceCN bool `json:"forceCN,omitempty"` + ForceCN bool `json:"forceCN,omitempty"` Options *Options `json:"options,omitempty"` Claims *Claims `json:"claims,omitempty"` claimer *Claimer @@ -75,6 +76,21 @@ func (s *SCEP) Init(config Config) (err error) { return err } +// AuthorizeSign does not do any validation, because all validation is handled +// in the SCEP protocol. This method returns a list of modifiers / constraints +// on the resulting certificate. +func (s *SCEP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) { + return []SignOption{ + // modifiers / withOptions + newProvisionerExtensionOption(TypeSCEP, s.Name, ""), + newForceCNOption(s.ForceCN), + profileDefaultDuration(s.claimer.DefaultTLSCertDuration()), + // validators + defaultPublicKeyValidator{}, + newValidityValidator(s.claimer.MinTLSCertDuration(), s.claimer.MaxTLSCertDuration()), + }, nil +} + // Interface guards var ( _ Interface = (*SCEP)(nil) diff --git a/scep/authority.go b/scep/authority.go index cb191d34..c9a2cdb8 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -6,8 +6,6 @@ import ( "crypto/x509" "errors" "fmt" - "math/big" - "math/rand" "github.com/smallstep/certificates/authority/provisioner" database "github.com/smallstep/certificates/db" @@ -53,8 +51,6 @@ type Interface interface { // GetLinkExplicit(linkType Link, provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string GetCACertificates() ([]*x509.Certificate, error) - //GetSigningKey() (*rsa.PrivateKey, error) - DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error SignCSR(ctx context.Context, msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) } @@ -201,12 +197,12 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err case microscep.PKCSReq, microscep.UpdateReq, microscep.RenewalReq: csr, err := x509.ParseCertificateRequest(msg.pkiEnvelope) if err != nil { - return fmt.Errorf("parse CSR from pkiEnvelope") + return fmt.Errorf("parse CSR from pkiEnvelope: %w", err) } // check for challengePassword cp, err := microx509util.ParseChallengePassword(msg.pkiEnvelope) if err != nil { - return fmt.Errorf("scep: parse challenge password in pkiEnvelope") + return fmt.Errorf("scep: parse challenge password in pkiEnvelope: %w", err) } msg.CSRReqMessage = µscep.CSRReqMessage{ RawDecrypted: msg.pkiEnvelope, @@ -227,6 +223,11 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err //func (msg *PKIMessage) SignCSR(crtAuth *x509.Certificate, keyAuth *rsa.PrivateKey, template *x509.Certificate) (*PKIMessage, error) { func (a *Authority) SignCSR(ctx context.Context, msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) { + // TODO: intermediate storage of the request? In SCEP it's possible to request a csr/certificate + // to be signed, which can be performed asynchronously / out-of-band. In that case a client can + // poll for the status. It seems to be similar as what can happen in ACME, so might want to model + // the implementation after the one in the ACME authority. Requires storage, etc. + p, err := ProvisionerFromContext(ctx) if err != nil { return nil, err @@ -245,32 +246,32 @@ func (a *Authority) SignCSR(ctx context.Context, msg *PKIMessage, template *x509 data := x509util.NewTemplateData() data.SetCommonName(csr.Subject.CommonName) data.SetSANs(csr.DNSNames) + data.SetCertificateRequest(csr) - // TODO: proper options - opts := provisioner.SignOptions{} - signOps := []provisioner.SignOption{} + // Get authorizations from the SCEP provisioner. + ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod) + signOps, err := p.AuthorizeSign(ctx, "") + if err != nil { + return nil, fmt.Errorf("error retrieving authorization options from SCEP provisioner: %w", err) + } + + opts := provisioner.SignOptions{ + // NotBefore: provisioner.NewTimeDuration(o.NotBefore), + // NotAfter: provisioner.NewTimeDuration(o.NotAfter), + } templateOptions, err := provisioner.TemplateOptions(p.GetOptions(), data) if err != nil { - return nil, fmt.Errorf("error creating template options from SCEP provisioner") + return nil, fmt.Errorf("error creating template options from SCEP provisioner: %w", err) } signOps = append(signOps, templateOptions) - // // Create and store a new certificate. - // certChain, err := auth.Sign(csr, provisioner.SignOptions{ - // NotBefore: provisioner.NewTimeDuration(o.NotBefore), - // NotAfter: provisioner.NewTimeDuration(o.NotAfter), - // }, signOps...) - // if err != nil { - // return nil, ServerInternalErr(errors.Wrapf(err, "error generating certificate for order %s", o.ID)) - // } - - certs, err := a.signAuth.Sign(csr, opts, signOps...) + certChain, err := a.signAuth.Sign(csr, opts, signOps...) if err != nil { - return nil, err + return nil, fmt.Errorf("error generating certificate for order %w", err) } - cert := certs[0] + cert := certChain[0] // fmt.Println("CERT") // fmt.Println(cert) @@ -278,9 +279,6 @@ func (a *Authority) SignCSR(ctx context.Context, msg *PKIMessage, template *x509 // fmt.Println(cert.Issuer) // fmt.Println(cert.Subject) - serial := big.NewInt(int64(rand.Int63())) // TODO: serial logic? - cert.SerialNumber = serial - // create a degenerate cert structure deg, err := DegenerateCertificates([]*x509.Certificate{cert}) if err != nil { diff --git a/scep/provisioner.go b/scep/provisioner.go index d543d453..64a787d4 100644 --- a/scep/provisioner.go +++ b/scep/provisioner.go @@ -1,6 +1,7 @@ package scep import ( + "context" "time" "github.com/smallstep/certificates/authority/provisioner" @@ -9,7 +10,7 @@ import ( // Provisioner is an interface that implements a subset of the provisioner.Interface -- // only those methods required by the SCEP api/authority. type Provisioner interface { - // AuthorizeSign(ctx context.Context, token string) ([]provisioner.SignOption, error) + AuthorizeSign(ctx context.Context, token string) ([]provisioner.SignOption, error) GetName() string DefaultTLSCertDuration() time.Duration GetOptions() *provisioner.Options From 30d3a26c20f14e3940f3551e0fe0afe55916e7da Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Feb 2021 18:07:50 +0100 Subject: [PATCH 33/89] Remove x509 template from API --- scep/api/api.go | 64 ++++++++--------------------------------------- scep/authority.go | 48 ++++++++++++++++++++++++++++++++--- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/scep/api/api.go b/scep/api/api.go index c73bb53a..16278075 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -2,19 +2,14 @@ package api import ( "context" - "crypto" - "crypto/sha1" "crypto/x509" "encoding/base64" "errors" "fmt" "io" "io/ioutil" - "math/big" - "math/rand" "net/http" "strings" - "time" "github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/api" @@ -252,53 +247,29 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque ctx := r.Context() - msg, err := microscep.ParsePKIMessage(scepRequest.Message) + microMsg, err := microscep.ParsePKIMessage(scepRequest.Message) if err != nil { return err } - pkimsg := &scep.PKIMessage{ - TransactionID: msg.TransactionID, - MessageType: msg.MessageType, - SenderNonce: msg.SenderNonce, - Raw: msg.Raw, + msg := &scep.PKIMessage{ + TransactionID: microMsg.TransactionID, + MessageType: microMsg.MessageType, + SenderNonce: microMsg.SenderNonce, + Raw: microMsg.Raw, } - if err := h.Auth.DecryptPKIEnvelope(ctx, pkimsg); err != nil { + if err := h.Auth.DecryptPKIEnvelope(ctx, msg); err != nil { return err } - if pkimsg.MessageType == microscep.PKCSReq { + if msg.MessageType == microscep.PKCSReq { // TODO: CSR validation, like challenge password } - csr := pkimsg.CSRReqMessage.CSR - subjectKeyID, err := createKeyIdentifier(csr.PublicKey) - if err != nil { - return err - } + csr := msg.CSRReqMessage.CSR - serial := big.NewInt(int64(rand.Int63())) // TODO: serial logic? - - days := 40 - - // TODO: use information from provisioner, like claims - template := &x509.Certificate{ - SerialNumber: serial, - Subject: csr.Subject, - NotBefore: time.Now().Add(-600).UTC(), - NotAfter: time.Now().AddDate(0, 0, days).UTC(), - SubjectKeyId: subjectKeyID, - KeyUsage: x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{ - x509.ExtKeyUsageClientAuth, - }, - SignatureAlgorithm: csr.SignatureAlgorithm, - EmailAddresses: csr.EmailAddresses, - DNSNames: csr.DNSNames, - } - - certRep, err := h.Auth.SignCSR(ctx, pkimsg, template) + certRep, err := h.Auth.SignCSR(ctx, csr, msg) if err != nil { return err } @@ -323,20 +294,6 @@ func certName(cert *x509.Certificate) string { return string(cert.Signature) } -// createKeyIdentifier creates an identifier for public keys -// according to the first method in RFC5280 section 4.2.1.2. -func createKeyIdentifier(pub crypto.PublicKey) ([]byte, error) { - - keyBytes, err := x509.MarshalPKIXPublicKey(pub) - if err != nil { - return nil, err - } - - id := sha1.Sum(keyBytes) - - return id[:], nil -} - func formatCapabilities(caps []string) []byte { return []byte(strings.Join(caps, "\r\n")) } @@ -354,6 +311,7 @@ func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) error { var ( // TODO: check the default capabilities; https://tools.ietf.org/html/rfc8894#section-3.5.2 + // TODO: move capabilities to Authority or Provisioner, so that they can be configured? defaultCapabilities = []string{ "Renewal", "SHA-1", diff --git a/scep/authority.go b/scep/authority.go index c9a2cdb8..013550a6 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -3,6 +3,8 @@ package scep import ( "bytes" "context" + "crypto" + "crypto/sha1" "crypto/x509" "errors" "fmt" @@ -52,7 +54,7 @@ type Interface interface { GetCACertificates() ([]*x509.Certificate, error) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error - SignCSR(ctx context.Context, msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) + SignCSR(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage) (*PKIMessage, error) } // Authority is the layer that handles all SCEP interactions. @@ -221,7 +223,8 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err // SignCSR creates an x509.Certificate based on a CSR template and Cert Authority credentials // returns a new PKIMessage with CertRep data //func (msg *PKIMessage) SignCSR(crtAuth *x509.Certificate, keyAuth *rsa.PrivateKey, template *x509.Certificate) (*PKIMessage, error) { -func (a *Authority) SignCSR(ctx context.Context, msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) { +//func (a *Authority) SignCSR(ctx context.Context, msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) { +func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage) (*PKIMessage, error) { // TODO: intermediate storage of the request? In SCEP it's possible to request a csr/certificate // to be signed, which can be performed asynchronously / out-of-band. In that case a client can @@ -238,9 +241,32 @@ func (a *Authority) SignCSR(ctx context.Context, msg *PKIMessage, template *x509 if err := a.DecryptPKIEnvelope(ctx, msg); err != nil { return nil, err } + csr = msg.CSRReqMessage.CSR } - csr := msg.CSRReqMessage.CSR + // subjectKeyID, err := createKeyIdentifier(csr.PublicKey) + // if err != nil { + // return nil, err + // } + + // serial := big.NewInt(int64(rand.Int63())) // TODO: serial logic? + // days := 40 // TODO: days + + // // TODO: use information from provisioner, like claims + // template := &x509.Certificate{ + // SerialNumber: serial, + // Subject: csr.Subject, + // NotBefore: time.Now().Add(-600).UTC(), + // NotAfter: time.Now().AddDate(0, 0, days).UTC(), + // SubjectKeyId: subjectKeyID, + // KeyUsage: x509.KeyUsageDigitalSignature, + // ExtKeyUsage: []x509.ExtKeyUsage{ + // x509.ExtKeyUsageClientAuth, + // }, + // SignatureAlgorithm: csr.SignatureAlgorithm, + // EmailAddresses: csr.EmailAddresses, + // DNSNames: csr.DNSNames, + // } // Template data data := x509util.NewTemplateData() @@ -278,6 +304,8 @@ func (a *Authority) SignCSR(ctx context.Context, msg *PKIMessage, template *x509 // fmt.Println(fmt.Sprintf("%T", cert)) // fmt.Println(cert.Issuer) // fmt.Println(cert.Subject) + // fmt.Println(cert.SerialNumber) + // fmt.Println(string(cert.SubjectKeyId)) // create a degenerate cert structure deg, err := DegenerateCertificates([]*x509.Certificate{cert}) @@ -365,6 +393,20 @@ func DegenerateCertificates(certs []*x509.Certificate) ([]byte, error) { return degenerate, nil } +// createKeyIdentifier creates an identifier for public keys +// according to the first method in RFC5280 section 4.2.1.2. +func createKeyIdentifier(pub crypto.PublicKey) ([]byte, error) { + + keyBytes, err := x509.MarshalPKIXPublicKey(pub) + if err != nil { + return nil, err + } + + id := sha1.Sum(keyBytes) + + return id[:], nil +} + // Interface guards var ( _ Interface = (*Authority)(nil) From a191319da9c8f36249bf343e6e048a0bec88f78c Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Sat, 27 Feb 2021 00:34:50 +0100 Subject: [PATCH 34/89] Improve SCEP API logic and error handling --- api/errors.go | 3 + scep/api/api.go | 215 +++++++++++++++++++++++++----------------------- scep/common.go | 11 ++- scep/errors.go | 19 +++++ 4 files changed, 145 insertions(+), 103 deletions(-) create mode 100644 scep/errors.go diff --git a/api/errors.go b/api/errors.go index 085d05cf..f9bcb199 100644 --- a/api/errors.go +++ b/api/errors.go @@ -11,6 +11,7 @@ import ( "github.com/smallstep/certificates/authority/mgmt" "github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/logging" + "github.com/smallstep/certificates/scep" ) // WriteError writes to w a JSON representation of the given error. @@ -22,6 +23,8 @@ func WriteError(w http.ResponseWriter, err error) { case *mgmt.Error: mgmt.WriteError(w, k) return + case *scep.Error: + w.Header().Set("Content-Type", "text/plain") default: w.Header().Set("Content-Type", "application/json") } diff --git a/scep/api/api.go b/scep/api/api.go index 16278075..f2d11fb7 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -11,7 +11,6 @@ import ( "net/http" "strings" - "github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/scep" @@ -27,6 +26,30 @@ const ( // TODO: add other (more optional) operations and handling ) +const maxPayloadSize = 2 << 20 + +type nextHTTP = func(http.ResponseWriter, *http.Request) + +var ( + // TODO: check the default capabilities; https://tools.ietf.org/html/rfc8894#section-3.5.2 + // TODO: move capabilities to Authority or Provisioner, so that they can be configured? + defaultCapabilities = []string{ + "Renewal", + "SHA-1", + "SHA-256", + "AES", + "DES3", + "SCEPStandard", + "POSTPKIOperation", + } +) + +const ( + certChainHeader = "application/x-x509-ca-ra-cert" + leafHeader = "application/x-x509-ca-cert" + pkiOpHeader = "application/x-pki-message" +) + // SCEPRequest is a SCEP server request. type SCEPRequest struct { Operation string @@ -35,10 +58,10 @@ type SCEPRequest struct { // SCEPResponse is a SCEP server response. type SCEPResponse struct { - Operation string - CACertNum int - Data []byte - Err error + Operation string + CACertNum int + Data []byte + Certificate *x509.Certificate } // Handler is the SCEP request handler. @@ -64,60 +87,65 @@ func (h *Handler) Route(r api.Router) { } +// Get handles all SCEP GET requests func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { - scepRequest, err := decodeSCEPRequest(r) + request, err := decodeSCEPRequest(r) if err != nil { - fmt.Println(err) - fmt.Println("not a scep get request") - w.WriteHeader(500) + writeError(w, fmt.Errorf("not a scep get request: %w", err)) + return } - scepResponse := SCEPResponse{Operation: scepRequest.Operation} + ctx := r.Context() + var response SCEPResponse - switch scepRequest.Operation { + switch request.Operation { case opnGetCACert: - err := h.GetCACert(w, r, scepResponse) - if err != nil { - fmt.Println(err) - } - + response, err = h.GetCACert(ctx) case opnGetCACaps: - err := h.GetCACaps(w, r, scepResponse) - if err != nil { - fmt.Println(err) - } + response, err = h.GetCACaps(ctx) case opnPKIOperation: - + // TODO: implement the GET for PKI operation default: - + err = fmt.Errorf("unknown operation: %s", request.Operation) } + + if err != nil { + writeError(w, fmt.Errorf("get request failed: %w", err)) + return + } + + writeSCEPResponse(w, response) } +// Post handles all SCEP POST requests func (h *Handler) Post(w http.ResponseWriter, r *http.Request) { - scepRequest, err := decodeSCEPRequest(r) + request, err := decodeSCEPRequest(r) if err != nil { - fmt.Println(err) - fmt.Println("not a scep post request") - w.WriteHeader(500) + writeError(w, fmt.Errorf("not a scep post request: %w", err)) + return } - scepResponse := SCEPResponse{Operation: scepRequest.Operation} + ctx := r.Context() + var response SCEPResponse - switch scepRequest.Operation { + switch request.Operation { case opnPKIOperation: - err := h.PKIOperation(w, r, scepRequest, scepResponse) - if err != nil { - fmt.Println(err) - } + response, err = h.PKIOperation(ctx, request) default: - + err = fmt.Errorf("unknown operation: %s", request.Operation) } -} + if err != nil { + writeError(w, fmt.Errorf("post request failed: %w", err)) + return + } -const maxPayloadSize = 2 << 20 + api.LogCertificate(w, response.Certificate) + + writeSCEPResponse(w, response) +} func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) { @@ -169,8 +197,6 @@ func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) { } } -type nextHTTP = func(http.ResponseWriter, *http.Request) - // lookupProvisioner loads the provisioner associated with the request. // Responds 404 if the provisioner does not exist. func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP { @@ -189,67 +215,69 @@ func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP { p, err := h.Auth.LoadProvisionerByID("scep/" + provisionerID) if err != nil { - api.WriteError(w, err) + writeError(w, err) return } - scepProvisioner, ok := p.(*provisioner.SCEP) + provisioner, ok := p.(*provisioner.SCEP) if !ok { - api.WriteError(w, errors.New("provisioner must be of type SCEP")) + writeError(w, errors.New("provisioner must be of type SCEP")) return } ctx := r.Context() - ctx = context.WithValue(ctx, acme.ProvisionerContextKey, scep.Provisioner(scepProvisioner)) + ctx = context.WithValue(ctx, scep.ProvisionerContextKey, scep.Provisioner(provisioner)) next(w, r.WithContext(ctx)) } } -func (h *Handler) GetCACert(w http.ResponseWriter, r *http.Request, scepResponse SCEPResponse) error { +// GetCACert returns the CA certificates in a SCEP response +func (h *Handler) GetCACert(ctx context.Context) (SCEPResponse, error) { certs, err := h.Auth.GetCACertificates() if err != nil { - return err + return SCEPResponse{}, err } if len(certs) == 0 { - scepResponse.CACertNum = 0 - scepResponse.Err = errors.New("missing CA Cert") - } else if len(certs) == 1 { - scepResponse.Data = certs[0].Raw - scepResponse.CACertNum = 1 - } else { - data, err := microscep.DegenerateCertificates(certs) - scepResponse.CACertNum = len(certs) - scepResponse.Data = data - scepResponse.Err = err + return SCEPResponse{}, errors.New("missing CA cert") } - return writeSCEPResponse(w, scepResponse) + response := SCEPResponse{Operation: opnGetCACert} + response.CACertNum = len(certs) + + if len(certs) == 1 { + response.Data = certs[0].Raw + } else { + data, err := microscep.DegenerateCertificates(certs) + if err != nil { + return SCEPResponse{}, err + } + response.Data = data + } + + return response, nil } -func (h *Handler) GetCACaps(w http.ResponseWriter, r *http.Request, scepResponse SCEPResponse) error { +// GetCACaps returns the CA capabilities in a SCEP response +func (h *Handler) GetCACaps(ctx context.Context) (SCEPResponse, error) { - //ctx := r.Context() - - // _, err := ProvisionerFromContext(ctx) - // if err != nil { - // return err - // } + response := SCEPResponse{Operation: opnGetCACaps} // TODO: get the actual capabilities from provisioner config - scepResponse.Data = formatCapabilities(defaultCapabilities) + response.Data = formatCapabilities(defaultCapabilities) - return writeSCEPResponse(w, scepResponse) + return response, nil } -func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepRequest SCEPRequest, scepResponse SCEPResponse) error { +// PKIOperation performs PKI operations and returns a SCEP response +func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPResponse, error) { - ctx := r.Context() + response := SCEPResponse{Operation: opnPKIOperation} - microMsg, err := microscep.ParsePKIMessage(scepRequest.Message) + microMsg, err := microscep.ParsePKIMessage(request.Message) if err != nil { - return err + return SCEPResponse{}, err } msg := &scep.PKIMessage{ @@ -260,7 +288,7 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque } if err := h.Auth.DecryptPKIEnvelope(ctx, msg); err != nil { - return err + return SCEPResponse{}, err } if msg.MessageType == microscep.PKCSReq { @@ -271,7 +299,7 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque certRep, err := h.Auth.SignCSR(ctx, csr, msg) if err != nil { - return err + return SCEPResponse{}, err } // //cert := certRep.CertRepMessage.Certificate @@ -280,11 +308,10 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque // // TODO: check if CN already exists, if renewal is allowed and if existing should be revoked; fail if not // // TODO: store the new cert for CN locally; should go into the DB - scepResponse.Data = certRep.Raw + response.Data = certRep.Raw + response.Certificate = certRep.Certificate - api.LogCertificate(w, certRep.Certificate) - - return writeSCEPResponse(w, scepResponse) + return response, nil } func certName(cert *x509.Certificate) string { @@ -299,40 +326,26 @@ func formatCapabilities(caps []string) []byte { } // writeSCEPResponse writes a SCEP response back to the SCEP client. -func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) error { - if response.Err != nil { - http.Error(w, response.Err.Error(), http.StatusInternalServerError) - return nil +func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) { + w.Header().Set("Content-Type", contentHeader(response)) + _, err := w.Write(response.Data) + if err != nil { + writeError(w, fmt.Errorf("error when writing scep response: %w", err)) // This could end up as an error again } - w.Header().Set("Content-Type", contentHeader(response.Operation, response.CACertNum)) - w.Write(response.Data) - return nil } -var ( - // TODO: check the default capabilities; https://tools.ietf.org/html/rfc8894#section-3.5.2 - // TODO: move capabilities to Authority or Provisioner, so that they can be configured? - defaultCapabilities = []string{ - "Renewal", - "SHA-1", - "SHA-256", - "AES", - "DES3", - "SCEPStandard", - "POSTPKIOperation", +func writeError(w http.ResponseWriter, err error) { + scepError := &scep.Error{ + Err: fmt.Errorf("post request failed: %w", err), + Status: http.StatusInternalServerError, // TODO: make this a param? } -) + api.WriteError(w, scepError) +} -const ( - certChainHeader = "application/x-x509-ca-ra-cert" - leafHeader = "application/x-x509-ca-cert" - pkiOpHeader = "application/x-pki-message" -) - -func contentHeader(operation string, certNum int) string { - switch operation { +func contentHeader(r SCEPResponse) string { + switch r.Operation { case opnGetCACert: - if certNum > 1 { + if r.CACertNum > 1 { return certChainHeader } return leafHeader diff --git a/scep/common.go b/scep/common.go index 123c8d82..ca87841f 100644 --- a/scep/common.go +++ b/scep/common.go @@ -3,14 +3,21 @@ package scep import ( "context" "errors" +) - "github.com/smallstep/certificates/acme" +// ContextKey is the key type for storing and searching for SCEP request +// essentials in the context of a request. +type ContextKey string + +const ( + // ProvisionerContextKey provisioner key + ProvisionerContextKey = ContextKey("provisioner") ) // ProvisionerFromContext searches the context for a SCEP provisioner. // Returns the provisioner or an error. func ProvisionerFromContext(ctx context.Context) (Provisioner, error) { - val := ctx.Value(acme.ProvisionerContextKey) + val := ctx.Value(ProvisionerContextKey) if val == nil { return nil, errors.New("provisioner expected in request context") } diff --git a/scep/errors.go b/scep/errors.go new file mode 100644 index 00000000..52fff8ae --- /dev/null +++ b/scep/errors.go @@ -0,0 +1,19 @@ +package scep + +// Error is an SCEP error type +type Error struct { + // Type ProbType + // Detail string + Err error + Status int + // Sub []*Error + // Identifier *Identifier +} + +// Error implements the error interface. +func (e *Error) Error() string { + // if e.Err == nil { + // return e.Detail + // } + return e.Err.Error() +} From 5df60c5a9b4466e5250a0b5e34896377d0172b26 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 5 Mar 2021 12:40:42 +0100 Subject: [PATCH 35/89] Add support for multiple SCEP provisioners Similarly to how ACME suppors multiple provisioners, it's now possible to load the right provisioner based on the URL. --- ca/ca.go | 2 +- scep/api/api.go | 41 +++++++++++++++++++---------------- scep/authority.go | 55 ++++++++++++++++++++++++++++++++++++++++++++--- scep/errors.go | 6 +++--- 4 files changed, 78 insertions(+), 26 deletions(-) diff --git a/ca/ca.go b/ca/ca.go index fa71abd6..a60346ee 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -212,7 +212,7 @@ func (ca *CA) Init(config *config.Config) (*CA, error) { }) } - // helpful routine for logging all routes // + // helpful routine for logging all routes //dumpRoutes(mux) // Add monitoring if configured diff --git a/scep/api/api.go b/scep/api/api.go index f2d11fb7..4df5d6a1 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -9,8 +9,10 @@ import ( "io" "io/ioutil" "net/http" + "net/url" "strings" + "github.com/go-chi/chi" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/scep" @@ -76,14 +78,10 @@ func New(scepAuth scep.Interface) api.RouterHandler { // Route traffic and implement the Router interface. func (h *Handler) Route(r api.Router) { - //getLink := h.Auth.GetLinkExplicit - //fmt.Println(getLink) + getLink := h.Auth.GetLinkExplicit - //r.MethodFunc("GET", "/bla", h.baseURLFromRequest(h.lookupProvisioner(nil))) - //r.MethodFunc("GET", getLink(acme.NewNonceLink, "{provisionerID}", false, nil), h.baseURLFromRequest(h.lookupProvisioner(h.addNonce(h.GetNonce)))) - - r.MethodFunc(http.MethodGet, "/", h.lookupProvisioner(h.Get)) - r.MethodFunc(http.MethodPost, "/", h.lookupProvisioner(h.Post)) + r.MethodFunc(http.MethodGet, getLink("{provisionerID}", false, nil), h.lookupProvisioner(h.Get)) + r.MethodFunc(http.MethodPost, getLink("{provisionerID}", false, nil), h.lookupProvisioner(h.Post)) } @@ -202,16 +200,12 @@ func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) { func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP { return func(w http.ResponseWriter, r *http.Request) { - // name := chi.URLParam(r, "provisionerID") - // provisionerID, err := url.PathUnescape(name) - // if err != nil { - // api.WriteError(w, fmt.Errorf("error url unescaping provisioner id '%s'", name)) - // return - // } - - // TODO: make this configurable; and we might want to look at being able to provide multiple, - // like the ACME one? The below assumes a SCEP provider (scep/) called "scep1" exists. - provisionerID := "scep1" + name := chi.URLParam(r, "provisionerID") + provisionerID, err := url.PathUnescape(name) + if err != nil { + api.WriteError(w, fmt.Errorf("error url unescaping provisioner id '%s'", name)) + return + } p, err := h.Auth.LoadProvisionerByID("scep/" + provisionerID) if err != nil { @@ -275,6 +269,8 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe response := SCEPResponse{Operation: opnPKIOperation} + fmt.Println("BEFORE PARSING") + microMsg, err := microscep.ParsePKIMessage(request.Message) if err != nil { return SCEPResponse{}, err @@ -287,7 +283,12 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe Raw: microMsg.Raw, } + fmt.Println("len raw:", len(microMsg.Raw)) + + fmt.Println("AFTER PARSING") + if err := h.Auth.DecryptPKIEnvelope(ctx, msg); err != nil { + fmt.Println("ERROR IN DECRYPTPKIENVELOPE") return SCEPResponse{}, err } @@ -311,6 +312,8 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe response.Data = certRep.Raw response.Certificate = certRep.Certificate + fmt.Println("HERE!!!") + return response, nil } @@ -336,8 +339,8 @@ func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) { func writeError(w http.ResponseWriter, err error) { scepError := &scep.Error{ - Err: fmt.Errorf("post request failed: %w", err), - Status: http.StatusInternalServerError, // TODO: make this a param? + Message: err.Error(), + Status: http.StatusInternalServerError, // TODO: make this a param? } api.WriteError(w, scepError) } diff --git a/scep/authority.go b/scep/authority.go index 013550a6..e5d1ea48 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -8,6 +8,7 @@ import ( "crypto/x509" "errors" "fmt" + "net/url" "github.com/smallstep/certificates/authority/provisioner" database "github.com/smallstep/certificates/db" @@ -55,6 +56,8 @@ type Interface interface { GetCACertificates() ([]*x509.Certificate, error) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error SignCSR(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage) (*PKIMessage, error) + + GetLinkExplicit(provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string } // Authority is the layer that handles all SCEP interactions. @@ -130,6 +133,44 @@ func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error return a.signAuth.LoadProvisionerByID(id) } +// GetLinkExplicit returns the requested link from the directory. +func (a *Authority) GetLinkExplicit(provName string, abs bool, baseURL *url.URL, inputs ...string) string { + // TODO: taken from ACME; move it to directory (if we need a directory in SCEP)? + return a.getLinkExplicit(provName, abs, baseURL, inputs...) +} + +// getLinkExplicit returns an absolute or partial path to the given resource and a base +// URL dynamically obtained from the request for which the link is being calculated. +func (a *Authority) getLinkExplicit(provisionerName string, abs bool, baseURL *url.URL, inputs ...string) string { + + // TODO: do we need to provide a way to provide a different suffix/base? + // Like "/cgi-bin/pkiclient.exe"? Or would it be enough to have that as the name? + link := fmt.Sprintf("/%s", provisionerName) + + if abs { + // Copy the baseURL value from the pointer. https://github.com/golang/go/issues/38351 + u := url.URL{} + if baseURL != nil { + u = *baseURL + } + + // If no Scheme is set, then default to https. + if u.Scheme == "" { + u.Scheme = "https" + } + + // If no Host is set, then use the default (first DNS attr in the ca.json). + if u.Host == "" { + u.Host = a.dns + } + + u.Path = a.prefix + link + return u.String() + } + + return link +} + // GetCACertificates returns the certificate (chain) for the CA func (a *Authority) GetCACertificates() ([]*x509.Certificate, error) { @@ -164,6 +205,8 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err return err } + fmt.Println("len content:", len(p7.Content)) + var tID microscep.TransactionID if err := p7.UnmarshalSignedAttribute(oidSCEPtransactionID, &tID); err != nil { return err @@ -176,11 +219,17 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err msg.p7 = p7 + //p7c, err := pkcs7.Parse(p7.Content) p7c, err := pkcs7.Parse(p7.Content) if err != nil { return err } + fmt.Println(tID) + fmt.Println(msgType) + + fmt.Println("len p7c content:", len(p7c.Content)) + envelope, err := p7c.Decrypt(a.intermediateCertificate, a.service.Decrypter) if err != nil { return err @@ -308,7 +357,7 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m // fmt.Println(string(cert.SubjectKeyId)) // create a degenerate cert structure - deg, err := DegenerateCertificates([]*x509.Certificate{cert}) + deg, err := degenerateCertificates([]*x509.Certificate{cert}) if err != nil { return nil, err } @@ -380,8 +429,8 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m return crepMsg, nil } -// DegenerateCertificates creates degenerate certificates pkcs#7 type -func DegenerateCertificates(certs []*x509.Certificate) ([]byte, error) { +// degenerateCertificates creates degenerate certificates pkcs#7 type +func degenerateCertificates(certs []*x509.Certificate) ([]byte, error) { var buf bytes.Buffer for _, cert := range certs { buf.Write(cert.Raw) diff --git a/scep/errors.go b/scep/errors.go index 52fff8ae..8454e16d 100644 --- a/scep/errors.go +++ b/scep/errors.go @@ -4,8 +4,8 @@ package scep type Error struct { // Type ProbType // Detail string - Err error - Status int + Message string `json:"message"` + Status int `json:"-"` // Sub []*Error // Identifier *Identifier } @@ -15,5 +15,5 @@ func (e *Error) Error() string { // if e.Err == nil { // return e.Detail // } - return e.Err.Error() + return e.Message } From 75cd3ab0ac503f64271362dc5da7d370649a8adc Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Sat, 6 Mar 2021 22:35:41 +0100 Subject: [PATCH 36/89] Change to a fixed fork of go.mozilla.org/pkcs7 Hopefully this will be a temporary change until the fix is merged in the upstream module. --- go.mod | 7 +++++-- go.sum | 2 ++ scep/api/api.go | 13 ++----------- scep/authority.go | 7 ------- 4 files changed, 9 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index ea867f8f..5ccc3aa8 100644 --- a/go.mod +++ b/go.mod @@ -21,10 +21,11 @@ require ( github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 github.com/smallstep/nosql v0.3.6 github.com/urfave/cli v1.22.4 + go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 go.step.sm/cli-utils v0.2.0 go.step.sm/crypto v0.8.3 - golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a - golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 + golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 + golang.org/x/net v0.0.0-20210119194325-5f4716e94777 google.golang.org/api v0.33.0 google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154 google.golang.org/grpc v1.32.0 @@ -34,3 +35,5 @@ require ( // replace github.com/smallstep/nosql => ../nosql // replace go.step.sm/crypto => ../crypto + +replace go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 => github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568 diff --git a/go.sum b/go.sum index 934bcfb6..4883af5e 100644 --- a/go.sum +++ b/go.sum @@ -267,6 +267,8 @@ github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/I github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/newrelic/go-agent v2.15.0+incompatible h1:IB0Fy+dClpBq9aEoIrLyQXzU34JyI1xVTanPLB/+jvU= github.com/newrelic/go-agent v2.15.0+incompatible/go.mod h1:a8Fv1b/fYhFSReoTU6HDkTYIMZeSVNffmoS726Y0LzQ= +github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568 h1:+MPqEswjYiS0S1FCTg8MIhMBMzxiVQ94rooFwvPPiWk= +github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/scep/api/api.go b/scep/api/api.go index 4df5d6a1..fc134a95 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -82,7 +82,6 @@ func (h *Handler) Route(r api.Router) { r.MethodFunc(http.MethodGet, getLink("{provisionerID}", false, nil), h.lookupProvisioner(h.Get)) r.MethodFunc(http.MethodPost, getLink("{provisionerID}", false, nil), h.lookupProvisioner(h.Post)) - } // Get handles all SCEP GET requests @@ -103,7 +102,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { case opnGetCACaps: response, err = h.GetCACaps(ctx) case opnPKIOperation: - // TODO: implement the GET for PKI operation + // TODO: implement the GET for PKI operation? Default CACAPS doesn't specify this is in use, though default: err = fmt.Errorf("unknown operation: %s", request.Operation) } @@ -170,6 +169,7 @@ func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) { if _, ok := query["message"]; ok { message = query.Get("message") } + // TODO: verify this; it seems like it should be StdEncoding instead of URLEncoding decodedMessage, err := base64.URLEncoding.DecodeString(message) if err != nil { return SCEPRequest{}, err @@ -269,8 +269,6 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe response := SCEPResponse{Operation: opnPKIOperation} - fmt.Println("BEFORE PARSING") - microMsg, err := microscep.ParsePKIMessage(request.Message) if err != nil { return SCEPResponse{}, err @@ -283,12 +281,7 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe Raw: microMsg.Raw, } - fmt.Println("len raw:", len(microMsg.Raw)) - - fmt.Println("AFTER PARSING") - if err := h.Auth.DecryptPKIEnvelope(ctx, msg); err != nil { - fmt.Println("ERROR IN DECRYPTPKIENVELOPE") return SCEPResponse{}, err } @@ -312,8 +305,6 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe response.Data = certRep.Raw response.Certificate = certRep.Certificate - fmt.Println("HERE!!!") - return response, nil } diff --git a/scep/authority.go b/scep/authority.go index e5d1ea48..a1d47700 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -205,8 +205,6 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err return err } - fmt.Println("len content:", len(p7.Content)) - var tID microscep.TransactionID if err := p7.UnmarshalSignedAttribute(oidSCEPtransactionID, &tID); err != nil { return err @@ -225,11 +223,6 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err return err } - fmt.Println(tID) - fmt.Println(msgType) - - fmt.Println("len p7c content:", len(p7c.Content)) - envelope, err := p7c.Decrypt(a.intermediateCertificate, a.service.Decrypter) if err != nil { return err From 017e56c9fb29a0b3d55096c57e7b60de085503ee Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Sat, 6 Mar 2021 23:24:49 +0100 Subject: [PATCH 37/89] Remove some duplicate and unnecessary logic --- scep/api/api.go | 9 +++++++++ scep/authority.go | 25 ++----------------------- scep/scep.go | 2 +- 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/scep/api/api.go b/scep/api/api.go index fc134a95..09ffddd0 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -16,6 +16,7 @@ import ( "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/scep" + "go.mozilla.org/pkcs7" microscep "github.com/micromdm/scep/scep" ) @@ -269,16 +270,24 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe response := SCEPResponse{Operation: opnPKIOperation} + // parse the message using microscep implementation microMsg, err := microscep.ParsePKIMessage(request.Message) if err != nil { return SCEPResponse{}, err } + p7, err := pkcs7.Parse(microMsg.Raw) + if err != nil { + return SCEPResponse{}, err + } + + // copy over properties to our internal PKIMessage msg := &scep.PKIMessage{ TransactionID: microMsg.TransactionID, MessageType: microMsg.MessageType, SenderNonce: microMsg.SenderNonce, Raw: microMsg.Raw, + P7: p7, } if err := h.Auth.DecryptPKIEnvelope(ctx, msg); err != nil { diff --git a/scep/authority.go b/scep/authority.go index a1d47700..a61c093a 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -198,27 +198,7 @@ func (a *Authority) GetCACertificates() ([]*x509.Certificate, error) { // DecryptPKIEnvelope decrypts an enveloped message func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error { - data := msg.Raw - - p7, err := pkcs7.Parse(data) - if err != nil { - return err - } - - var tID microscep.TransactionID - if err := p7.UnmarshalSignedAttribute(oidSCEPtransactionID, &tID); err != nil { - return err - } - - var msgType microscep.MessageType - if err := p7.UnmarshalSignedAttribute(oidSCEPmessageType, &msgType); err != nil { - return err - } - - msg.p7 = p7 - - //p7c, err := pkcs7.Parse(p7.Content) - p7c, err := pkcs7.Parse(p7.Content) + p7c, err := pkcs7.Parse(msg.P7.Content) if err != nil { return err } @@ -253,7 +233,6 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err CSR: csr, ChallengePassword: cp, } - //msg.Certificate = p7.Certificates[0] // TODO: check if this is necessary to add (again) return nil case microscep.GetCRL, microscep.GetCert, microscep.CertPoll: return fmt.Errorf("not implemented") //errNotImplemented @@ -355,7 +334,7 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m return nil, err } - e7, err := pkcs7.Encrypt(deg, msg.p7.Certificates) + e7, err := pkcs7.Encrypt(deg, msg.P7.Certificates) if err != nil { return nil, err } diff --git a/scep/scep.go b/scep/scep.go index 6a636aee..7fc4c261 100644 --- a/scep/scep.go +++ b/scep/scep.go @@ -35,7 +35,7 @@ type PKIMessage struct { Raw []byte // parsed - p7 *pkcs7.PKCS7 + P7 *pkcs7.PKCS7 // decrypted enveloped content pkiEnvelope []byte From 3b86550dbfffb136ad9280f3f89e1f723c9f8e45 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Sun, 7 Mar 2021 00:30:37 +0100 Subject: [PATCH 38/89] Add support for challenge password --- authority/provisioner/scep.go | 16 +++++++++++----- scep/api/api.go | 29 +++++++++++++++++++++-------- scep/authority.go | 20 ++++++++++++++++++++ scep/provisioner.go | 1 + 4 files changed, 53 insertions(+), 13 deletions(-) diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 10414b5e..031241c4 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -14,10 +14,11 @@ type SCEP struct { Type string `json:"type"` Name string `json:"name"` - ForceCN bool `json:"forceCN,omitempty"` - Options *Options `json:"options,omitempty"` - Claims *Claims `json:"claims,omitempty"` - claimer *Claimer + ForceCN bool `json:"forceCN,omitempty"` + ChallengePassword string `json:"challenge,omitempty"` + Options *Options `json:"options,omitempty"` + Claims *Claims `json:"claims,omitempty"` + claimer *Claimer } // GetID returns the provisioner unique identifier. @@ -76,7 +77,7 @@ func (s *SCEP) Init(config Config) (err error) { return err } -// AuthorizeSign does not do any validation, because all validation is handled +// AuthorizeSign does not do any verification, because all verification is handled // in the SCEP protocol. This method returns a list of modifiers / constraints // on the resulting certificate. func (s *SCEP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) { @@ -91,6 +92,11 @@ func (s *SCEP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e }, nil } +// GetChallengePassword returns the challenge password +func (s *SCEP) GetChallengePassword() string { + return s.ChallengePassword +} + // Interface guards var ( _ Interface = (*SCEP)(nil) diff --git a/scep/api/api.go b/scep/api/api.go index 09ffddd0..3f49f7a3 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -80,7 +80,6 @@ func New(scepAuth scep.Interface) api.RouterHandler { // Route traffic and implement the Router interface. func (h *Handler) Route(r api.Router) { getLink := h.Auth.GetLinkExplicit - r.MethodFunc(http.MethodGet, getLink("{provisionerID}", false, nil), h.lookupProvisioner(h.Get)) r.MethodFunc(http.MethodPost, getLink("{provisionerID}", false, nil), h.lookupProvisioner(h.Post)) } @@ -140,6 +139,8 @@ func (h *Handler) Post(w http.ResponseWriter, r *http.Request) { return } + // TODO: fix cases in which we get here and there's no certificate (i.e. wrong password, waiting for cert, etc) + // We should generate an appropriate response and it should be signed api.LogCertificate(w, response.Certificate) writeSCEPResponse(w, response) @@ -238,8 +239,10 @@ func (h *Handler) GetCACert(ctx context.Context) (SCEPResponse, error) { return SCEPResponse{}, errors.New("missing CA cert") } - response := SCEPResponse{Operation: opnGetCACert} - response.CACertNum = len(certs) + response := SCEPResponse{ + Operation: opnGetCACert, + CACertNum: len(certs), + } if len(certs) == 1 { response.Data = certs[0].Raw @@ -268,8 +271,6 @@ func (h *Handler) GetCACaps(ctx context.Context) (SCEPResponse, error) { // PKIOperation performs PKI operations and returns a SCEP response func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPResponse, error) { - response := SCEPResponse{Operation: opnPKIOperation} - // parse the message using microscep implementation microMsg, err := microscep.ParsePKIMessage(request.Message) if err != nil { @@ -295,7 +296,15 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe } if msg.MessageType == microscep.PKCSReq { - // TODO: CSR validation, like challenge password + + challengeMatches, err := h.Auth.MatchChallengePassword(ctx, msg.CSRReqMessage.ChallengePassword) + if err != nil { + return SCEPResponse{}, err + } + + if !challengeMatches { + return SCEPResponse{}, errors.New("wrong password provided") + } } csr := msg.CSRReqMessage.CSR @@ -311,8 +320,11 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe // // TODO: check if CN already exists, if renewal is allowed and if existing should be revoked; fail if not // // TODO: store the new cert for CN locally; should go into the DB - response.Data = certRep.Raw - response.Certificate = certRep.Certificate + response := SCEPResponse{ + Operation: opnPKIOperation, + Data: certRep.Raw, + Certificate: certRep.Certificate, + } return response, nil } @@ -338,6 +350,7 @@ func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) { } func writeError(w http.ResponseWriter, err error) { + // TODO: this probably needs to use SCEP specific errors (i.e. failInfo) scepError := &scep.Error{ Message: err.Error(), Status: http.StatusInternalServerError, // TODO: make this a param? diff --git a/scep/authority.go b/scep/authority.go index a61c093a..a2b3b8b9 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -56,6 +56,7 @@ type Interface interface { GetCACertificates() ([]*x509.Certificate, error) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error SignCSR(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage) (*PKIMessage, error) + MatchChallengePassword(ctx context.Context, password string) (bool, error) GetLinkExplicit(provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string } @@ -401,6 +402,25 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m return crepMsg, nil } +// MatchChallengePassword verifies a SCEP challenge password +func (a *Authority) MatchChallengePassword(ctx context.Context, password string) (bool, error) { + + p, err := ProvisionerFromContext(ctx) + if err != nil { + return false, err + } + + if p.GetChallengePassword() == password { + return true, nil + } + + // TODO: support dynamic challenges, i.e. a list of challenges instead of one? + // That's probably a bit harder to configure, though; likely requires some data store + // that can be interacted with more easily, via some internal API, for example. + + return false, nil +} + // degenerateCertificates creates degenerate certificates pkcs#7 type func degenerateCertificates(certs []*x509.Certificate) ([]byte, error) { var buf bytes.Buffer diff --git a/scep/provisioner.go b/scep/provisioner.go index 64a787d4..5c665a1f 100644 --- a/scep/provisioner.go +++ b/scep/provisioner.go @@ -14,4 +14,5 @@ type Provisioner interface { GetName() string DefaultTLSCertDuration() time.Duration GetOptions() *provisioner.Options + GetChallengePassword() string } From 4fe7179b95682f1fdd157fc92a274c09567901b0 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Sun, 7 Mar 2021 00:50:00 +0100 Subject: [PATCH 39/89] Add support for configuring capabilities (cacaps) --- authority/provisioner/scep.go | 6 +++++ scep/api/api.go | 22 ++++-------------- scep/authority.go | 43 +++++++++++++++++++++++++++++++++-- scep/provisioner.go | 1 + 4 files changed, 53 insertions(+), 19 deletions(-) diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 031241c4..49d057ff 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -16,6 +16,7 @@ type SCEP struct { ForceCN bool `json:"forceCN,omitempty"` ChallengePassword string `json:"challenge,omitempty"` + Capabilities []string `json:"capabilities,omitempty"` Options *Options `json:"options,omitempty"` Claims *Claims `json:"claims,omitempty"` claimer *Claimer @@ -97,6 +98,11 @@ func (s *SCEP) GetChallengePassword() string { return s.ChallengePassword } +// GetCapabilities returns the CA capabilities +func (s *SCEP) GetCapabilities() []string { + return s.Capabilities +} + // Interface guards var ( _ Interface = (*SCEP)(nil) diff --git a/scep/api/api.go b/scep/api/api.go index 3f49f7a3..f6294f06 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -33,20 +33,6 @@ const maxPayloadSize = 2 << 20 type nextHTTP = func(http.ResponseWriter, *http.Request) -var ( - // TODO: check the default capabilities; https://tools.ietf.org/html/rfc8894#section-3.5.2 - // TODO: move capabilities to Authority or Provisioner, so that they can be configured? - defaultCapabilities = []string{ - "Renewal", - "SHA-1", - "SHA-256", - "AES", - "DES3", - "SCEPStandard", - "POSTPKIOperation", - } -) - const ( certChainHeader = "application/x-x509-ca-ra-cert" leafHeader = "application/x-x509-ca-cert" @@ -260,10 +246,12 @@ func (h *Handler) GetCACert(ctx context.Context) (SCEPResponse, error) { // GetCACaps returns the CA capabilities in a SCEP response func (h *Handler) GetCACaps(ctx context.Context) (SCEPResponse, error) { - response := SCEPResponse{Operation: opnGetCACaps} + caps := h.Auth.GetCACaps(ctx) - // TODO: get the actual capabilities from provisioner config - response.Data = formatCapabilities(defaultCapabilities) + response := SCEPResponse{ + Operation: opnGetCACaps, + Data: formatCapabilities(caps), + } return response, nil } diff --git a/scep/authority.go b/scep/authority.go index a2b3b8b9..f8e5a76f 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -57,6 +57,7 @@ type Interface interface { DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error SignCSR(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage) (*PKIMessage, error) MatchChallengePassword(ctx context.Context, password string) (bool, error) + GetCACaps(ctx context.Context) []string GetLinkExplicit(provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string } @@ -128,6 +129,19 @@ func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { }, nil } +var ( + // TODO: check the default capabilities; https://tools.ietf.org/html/rfc8894#section-3.5.2 + defaultCapabilities = []string{ + "Renewal", + "SHA-1", + "SHA-256", + "AES", + "DES3", + "SCEPStandard", + "POSTPKIOperation", + } +) + // LoadProvisionerByID calls out to the SignAuthority interface to load a // provisioner by ID. func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error) { @@ -155,9 +169,9 @@ func (a *Authority) getLinkExplicit(provisionerName string, abs bool, baseURL *u u = *baseURL } - // If no Scheme is set, then default to https. + // If no Scheme is set, then default to http (in case of SCEP) if u.Scheme == "" { - u.Scheme = "https" + u.Scheme = "http" } // If no Host is set, then use the default (first DNS attr in the ca.json). @@ -188,6 +202,9 @@ func (a *Authority) GetCACertificates() ([]*x509.Certificate, error) { // // Using an RA does not seem to exist in https://tools.ietf.org/html/rfc8894, but is mentioned in // https://tools.ietf.org/id/draft-nourse-scep-21.html. Will continue using the CA directly for now. + // + // The certificate to use should probably depend on the (configured) Provisioner and may + // use a distinct certificate, apart from the intermediate. if a.intermediateCertificate == nil { return nil, errors.New("no intermediate certificate available in SCEP authority") @@ -421,6 +438,28 @@ func (a *Authority) MatchChallengePassword(ctx context.Context, password string) return false, nil } +// GetCACaps returns the CA capabilities +func (a *Authority) GetCACaps(ctx context.Context) []string { + + p, err := ProvisionerFromContext(ctx) + if err != nil { + return defaultCapabilities + } + + caps := p.GetCapabilities() + if len(caps) == 0 { + return defaultCapabilities + } + + // TODO: validate the caps? Ensure they are the right format according to RFC? + // TODO: ensure that the capabilities are actually "enforced"/"verified" in code too: + // check that only parts of the spec are used in the implementation belonging to the capabilities. + // For example for renewals, which we could disable in the provisioner, should then also + // not be reported in cacaps operation. + + return caps +} + // degenerateCertificates creates degenerate certificates pkcs#7 type func degenerateCertificates(certs []*x509.Certificate) ([]byte, error) { var buf bytes.Buffer diff --git a/scep/provisioner.go b/scep/provisioner.go index 5c665a1f..e1b7f8b1 100644 --- a/scep/provisioner.go +++ b/scep/provisioner.go @@ -15,4 +15,5 @@ type Provisioner interface { DefaultTLSCertDuration() time.Duration GetOptions() *provisioner.Options GetChallengePassword() string + GetCapabilities() []string } From f0050e5ca98be94f3c2c614d08fd83f498ee6812 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 10 Mar 2021 21:13:05 +0100 Subject: [PATCH 40/89] Add signed failure responses --- scep/api/api.go | 44 +++++++++++++++++++--------- scep/authority.go | 73 +++++++++++++++++++++++++++++++++++++++++++++++ scep/scep.go | 13 +++++++++ 3 files changed, 117 insertions(+), 13 deletions(-) diff --git a/scep/api/api.go b/scep/api/api.go index f6294f06..ced6fa05 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -34,9 +34,9 @@ const maxPayloadSize = 2 << 20 type nextHTTP = func(http.ResponseWriter, *http.Request) const ( - certChainHeader = "application/x-x509-ca-ra-cert" - leafHeader = "application/x-x509-ca-cert" - pkiOpHeader = "application/x-pki-message" + certChainHeader = "application/x-x509-ca-ra-cert" + leafHeader = "application/x-x509-ca-cert" + pkiOperationHeader = "application/x-pki-message" ) // SCEPRequest is a SCEP server request. @@ -125,10 +125,6 @@ func (h *Handler) Post(w http.ResponseWriter, r *http.Request) { return } - // TODO: fix cases in which we get here and there's no certificate (i.e. wrong password, waiting for cert, etc) - // We should generate an appropriate response and it should be signed - api.LogCertificate(w, response.Certificate) - writeSCEPResponse(w, response) } @@ -262,9 +258,13 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe // parse the message using microscep implementation microMsg, err := microscep.ParsePKIMessage(request.Message) if err != nil { + // return the error, because we can't use the msg for creating a CertRep return SCEPResponse{}, err } + // this is essentially doing the same as microscep.ParsePKIMessage, but + // gives us access to the p7 itself in scep.PKIMessage. Essentially a small + // wrapper for the microscep implementation. p7, err := pkcs7.Parse(microMsg.Raw) if err != nil { return SCEPResponse{}, err @@ -283,23 +283,25 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe return SCEPResponse{}, err } + // NOTE: at this point we have sufficient information for returning nicely signed CertReps + csr := msg.CSRReqMessage.CSR + if msg.MessageType == microscep.PKCSReq { challengeMatches, err := h.Auth.MatchChallengePassword(ctx, msg.CSRReqMessage.ChallengePassword) if err != nil { - return SCEPResponse{}, err + return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, "error when checking password") } if !challengeMatches { - return SCEPResponse{}, errors.New("wrong password provided") + // TODO: can this be returned safely to the client? In the end, if the password was correct, that gains a bit of info too. + return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, "wrong password provided") } } - csr := msg.CSRReqMessage.CSR - certRep, err := h.Auth.SignCSR(ctx, csr, msg) if err != nil { - return SCEPResponse{}, err + return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, "error when signing new certificate") } // //cert := certRep.CertRepMessage.Certificate @@ -330,6 +332,11 @@ func formatCapabilities(caps []string) []byte { // writeSCEPResponse writes a SCEP response back to the SCEP client. func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) { + + if response.Certificate != nil { + api.LogCertificate(w, response.Certificate) + } + w.Header().Set("Content-Type", contentHeader(response)) _, err := w.Write(response.Data) if err != nil { @@ -346,6 +353,17 @@ func writeError(w http.ResponseWriter, err error) { api.WriteError(w, scepError) } +func (h *Handler) createFailureResponse(ctx context.Context, csr *x509.CertificateRequest, msg *scep.PKIMessage, info microscep.FailInfo, infoText string) (SCEPResponse, error) { + certRepMsg, err := h.Auth.CreateFailureResponse(ctx, csr, msg, scep.FailInfoName(info), infoText) + if err != nil { + return SCEPResponse{}, err + } + return SCEPResponse{ + Operation: opnPKIOperation, + Data: certRepMsg.Raw, + }, nil +} + func contentHeader(r SCEPResponse) string { switch r.Operation { case opnGetCACert: @@ -354,7 +372,7 @@ func contentHeader(r SCEPResponse) string { } return leafHeader case opnPKIOperation: - return pkiOpHeader + return pkiOperationHeader default: return "text/plain" } diff --git a/scep/authority.go b/scep/authority.go index f8e5a76f..e277a5af 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -56,6 +56,7 @@ type Interface interface { GetCACertificates() ([]*x509.Certificate, error) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error SignCSR(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage) (*PKIMessage, error) + CreateFailureResponse(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage, info FailInfoName, infoText string) (*PKIMessage, error) MatchChallengePassword(ctx context.Context, password string) (bool, error) GetCACaps(ctx context.Context) []string @@ -376,6 +377,10 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m Type: oidSCEPrecipientNonce, Value: msg.SenderNonce, }, + pkcs7.Attribute{ + Type: oidSCEPsenderNonce, + Value: msg.SenderNonce, + }, }, } @@ -419,6 +424,74 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m return crepMsg, nil } +// CreateFailureResponse creates an appropriately signed reply for PKI operations +func (a *Authority) CreateFailureResponse(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage, info FailInfoName, infoText string) (*PKIMessage, error) { + + config := pkcs7.SignerInfoConfig{ + ExtraSignedAttributes: []pkcs7.Attribute{ + pkcs7.Attribute{ + Type: oidSCEPtransactionID, + Value: msg.TransactionID, + }, + pkcs7.Attribute{ + Type: oidSCEPpkiStatus, + Value: microscep.FAILURE, + }, + pkcs7.Attribute{ + Type: oidSCEPfailInfo, + Value: info, + }, + pkcs7.Attribute{ + Type: oidSCEPfailInfoText, + Value: infoText, + }, + pkcs7.Attribute{ + Type: oidSCEPmessageType, + Value: microscep.CertRep, + }, + pkcs7.Attribute{ + Type: oidSCEPsenderNonce, + Value: msg.SenderNonce, + }, + pkcs7.Attribute{ + Type: oidSCEPrecipientNonce, + Value: msg.SenderNonce, + }, + }, + } + + signedData, err := pkcs7.NewSignedData(nil) + if err != nil { + return nil, err + } + + // sign the attributes + if err := signedData.AddSigner(a.intermediateCertificate, a.service.Signer, config); err != nil { + return nil, err + } + + certRepBytes, err := signedData.Finish() + if err != nil { + return nil, err + } + + cr := &CertRepMessage{ + PKIStatus: microscep.FAILURE, + FailInfo: microscep.BadRequest, + RecipientNonce: microscep.RecipientNonce(msg.SenderNonce), + } + + // create a CertRep message from the original + crepMsg := &PKIMessage{ + Raw: certRepBytes, + TransactionID: msg.TransactionID, + MessageType: microscep.CertRep, + CertRepMessage: cr, + } + + return crepMsg, nil +} + // MatchChallengePassword verifies a SCEP challenge password func (a *Authority) MatchChallengePassword(ctx context.Context, password string) (bool, error) { diff --git a/scep/scep.go b/scep/scep.go index 7fc4c261..0c25ec4c 100644 --- a/scep/scep.go +++ b/scep/scep.go @@ -11,6 +11,18 @@ import ( "go.mozilla.org/pkcs7" ) +// FailInfoName models the name/value of failInfo +type FailInfoName microscep.FailInfo + +// FailInfo models a failInfo object consisting of a +// name/identifier and a failInfoText, the latter of +// which can be more descriptive and is intended to be +// read by humans. +type FailInfo struct { + Name FailInfoName + Text string +} + // SCEP OIDs var ( oidSCEPmessageType = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 2} @@ -20,6 +32,7 @@ var ( oidSCEPrecipientNonce = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 6} oidSCEPtransactionID = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 7} oidChallengePassword = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 7} + oidSCEPfailInfoText = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 24} ) // PKIMessage defines the possible SCEP message types From aa2ce0a2a553a1a9078882749eba2f84bb9521a2 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 10 Mar 2021 22:20:02 +0100 Subject: [PATCH 41/89] Store new certificates in database --- scep/api/api.go | 8 +--- scep/authority.go | 96 +++++++++++++++------------------------------ scep/certificate.go | 80 +++++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 71 deletions(-) create mode 100644 scep/certificate.go diff --git a/scep/api/api.go b/scep/api/api.go index ced6fa05..13aeec21 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -299,17 +299,13 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe } } + // TODO: check if CN already exists, if renewal is allowed and if existing should be revoked; fail if not + certRep, err := h.Auth.SignCSR(ctx, csr, msg) if err != nil { return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, "error when signing new certificate") } - // //cert := certRep.CertRepMessage.Certificate - // //name := certName(cert) - - // // TODO: check if CN already exists, if renewal is allowed and if existing should be revoked; fail if not - // // TODO: store the new cert for CN locally; should go into the DB - response := SCEPResponse{ Operation: opnPKIOperation, Data: certRep.Raw, diff --git a/scep/authority.go b/scep/authority.go index e277a5af..157854f1 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -27,31 +27,14 @@ import ( "go.step.sm/crypto/x509util" ) +var ( + certTable = []byte("scep_certs") +) + // Interface is the SCEP authority interface. type Interface interface { - // GetDirectory(ctx context.Context) (*Directory, error) - // NewNonce() (string, error) - // UseNonce(string) error - - // DeactivateAccount(ctx context.Context, accID string) (*Account, error) - // GetAccount(ctx context.Context, accID string) (*Account, error) - // GetAccountByKey(ctx context.Context, key *jose.JSONWebKey) (*Account, error) - // NewAccount(ctx context.Context, ao AccountOptions) (*Account, error) - // UpdateAccount(context.Context, string, []string) (*Account, error) - - // GetAuthz(ctx context.Context, accID string, authzID string) (*Authz, error) - // ValidateChallenge(ctx context.Context, accID string, chID string, key *jose.JSONWebKey) (*Challenge, error) - - // FinalizeOrder(ctx context.Context, accID string, orderID string, csr *x509.CertificateRequest) (*Order, error) - // GetOrder(ctx context.Context, accID string, orderID string) (*Order, error) - // GetOrdersByAccount(ctx context.Context, accID string) ([]string, error) - // NewOrder(ctx context.Context, oo OrderOptions) (*Order, error) - - // GetCertificate(string, string) ([]byte, error) - LoadProvisionerByID(string) (provisioner.Interface, error) - // GetLink(ctx context.Context, linkType Link, absoluteLink bool, inputs ...string) string - // GetLinkExplicit(linkType Link, provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string + GetLinkExplicit(provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string GetCACertificates() ([]*x509.Certificate, error) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error @@ -59,8 +42,6 @@ type Interface interface { CreateFailureResponse(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage, info FailInfoName, infoText string) (*PKIMessage, error) MatchChallengePassword(ctx context.Context, password string) (bool, error) GetCACaps(ctx context.Context) []string - - GetLinkExplicit(provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string } // Authority is the layer that handles all SCEP interactions. @@ -107,7 +88,14 @@ type SignAuthority interface { func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { if _, ok := ops.DB.(*database.SimpleDB); !ok { - // TODO: see ACME implementation + // If it's not a SimpleDB then go ahead and bootstrap the DB with the + // necessary SCEP tables. SimpleDB should ONLY be used for testing. + tables := [][]byte{certTable} + for _, b := range tables { + if err := ops.DB.CreateTable(b); err != nil { + return nil, fmt.Errorf("%w: error creating table %s", err, string(b)) + } + } } // TODO: the below is a bit similar as what happens in the core Authority class, which @@ -284,30 +272,6 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m csr = msg.CSRReqMessage.CSR } - // subjectKeyID, err := createKeyIdentifier(csr.PublicKey) - // if err != nil { - // return nil, err - // } - - // serial := big.NewInt(int64(rand.Int63())) // TODO: serial logic? - // days := 40 // TODO: days - - // // TODO: use information from provisioner, like claims - // template := &x509.Certificate{ - // SerialNumber: serial, - // Subject: csr.Subject, - // NotBefore: time.Now().Add(-600).UTC(), - // NotAfter: time.Now().AddDate(0, 0, days).UTC(), - // SubjectKeyId: subjectKeyID, - // KeyUsage: x509.KeyUsageDigitalSignature, - // ExtKeyUsage: []x509.ExtKeyUsage{ - // x509.ExtKeyUsageClientAuth, - // }, - // SignatureAlgorithm: csr.SignatureAlgorithm, - // EmailAddresses: csr.EmailAddresses, - // DNSNames: csr.DNSNames, - // } - // Template data data := x509util.NewTemplateData() data.SetCommonName(csr.Subject.CommonName) @@ -339,14 +303,6 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m cert := certChain[0] - // fmt.Println("CERT") - // fmt.Println(cert) - // fmt.Println(fmt.Sprintf("%T", cert)) - // fmt.Println(cert.Issuer) - // fmt.Println(cert.Subject) - // fmt.Println(cert.SerialNumber) - // fmt.Println(string(cert.SubjectKeyId)) - // create a degenerate cert structure deg, err := degenerateCertificates([]*x509.Certificate{cert}) if err != nil { @@ -377,7 +333,7 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m Type: oidSCEPrecipientNonce, Value: msg.SenderNonce, }, - pkcs7.Attribute{ + { Type: oidSCEPsenderNonce, Value: msg.SenderNonce, }, @@ -421,6 +377,16 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m CertRepMessage: cr, } + // TODO: save more data? + _, err = newCert(a.db, CertOptions{ + Leaf: certChain[0], + Intermediates: certChain[1:], + }) + if err != nil { + fmt.Println(err) + return nil, err + } + return crepMsg, nil } @@ -429,31 +395,31 @@ func (a *Authority) CreateFailureResponse(ctx context.Context, csr *x509.Certifi config := pkcs7.SignerInfoConfig{ ExtraSignedAttributes: []pkcs7.Attribute{ - pkcs7.Attribute{ + { Type: oidSCEPtransactionID, Value: msg.TransactionID, }, - pkcs7.Attribute{ + { Type: oidSCEPpkiStatus, Value: microscep.FAILURE, }, - pkcs7.Attribute{ + { Type: oidSCEPfailInfo, Value: info, }, - pkcs7.Attribute{ + { Type: oidSCEPfailInfoText, Value: infoText, }, - pkcs7.Attribute{ + { Type: oidSCEPmessageType, Value: microscep.CertRep, }, - pkcs7.Attribute{ + { Type: oidSCEPsenderNonce, Value: msg.SenderNonce, }, - pkcs7.Attribute{ + { Type: oidSCEPrecipientNonce, Value: msg.SenderNonce, }, diff --git a/scep/certificate.go b/scep/certificate.go new file mode 100644 index 00000000..fe48d29e --- /dev/null +++ b/scep/certificate.go @@ -0,0 +1,80 @@ +package scep + +import ( + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + "time" + + "github.com/smallstep/nosql" +) + +type certificate struct { + ID string `json:"id"` + Created time.Time `json:"created"` + Leaf []byte `json:"leaf"` + Intermediates []byte `json:"intermediates"` +} + +// CertOptions options with which to create and store a cert object. +type CertOptions struct { + Leaf *x509.Certificate + Intermediates []*x509.Certificate +} + +func newCert(db nosql.DB, ops CertOptions) (*certificate, error) { + + // TODO: according to the RFC this should be IssuerAndSerialNumber, + // but sscep seems to use just the serial number for getcert + + id := ops.Leaf.SerialNumber.String() + + leaf := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: ops.Leaf.Raw, + }) + var intermediates []byte + for _, cert := range ops.Intermediates { + intermediates = append(intermediates, pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: cert.Raw, + })...) + } + + cert := &certificate{ + ID: id, + Leaf: leaf, + Intermediates: intermediates, + Created: time.Now().UTC(), + } + certB, err := json.Marshal(cert) + if err != nil { + return nil, fmt.Errorf("%w: error marshaling certificate", err) + } + + _, swapped, err := db.CmpAndSwap(certTable, []byte(id), nil, certB) + switch { + case err != nil: + return nil, fmt.Errorf("%w: error storing certificate", err) + case !swapped: + return nil, fmt.Errorf("error storing certificate; " + + "value has changed since last read") + default: + return cert, nil + } +} + +func getCert(db nosql.DB, id string) (*certificate, error) { + b, err := db.Get(certTable, []byte(id)) + if nosql.IsErrNotFound(err) { + return nil, fmt.Errorf("certificate %s not found", id) + } else if err != nil { + return nil, fmt.Errorf("error loading certificate") + } + var cert certificate + if err := json.Unmarshal(b, &cert); err != nil { + return nil, fmt.Errorf("%w: error unmarshaling certificate", err) + } + return &cert, nil +} From e7cb80f88015d23e387571de9f932e7fd0e04146 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 10 Mar 2021 22:39:20 +0100 Subject: [PATCH 42/89] Fix linter issues --- authority/authority.go | 3 +++ scep/api/api.go | 7 ------- scep/authority.go | 16 ---------------- scep/certificate.go | 26 +++++++++++++------------- scep/scep.go | 3 ++- 5 files changed, 18 insertions(+), 37 deletions(-) diff --git a/authority/authority.go b/authority/authority.go index e753aeae..f9f723a4 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -337,6 +337,9 @@ func (a *Authority) init() error { DecryptionKey: a.config.IntermediateKey, Password: []byte(a.config.Password), }) + if err != nil { + return err + } } a.scepService = &scep.Service{ diff --git a/scep/api/api.go b/scep/api/api.go index 13aeec21..a9e4d840 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -315,13 +315,6 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe return response, nil } -func certName(cert *x509.Certificate) string { - if cert.Subject.CommonName != "" { - return cert.Subject.CommonName - } - return string(cert.Signature) -} - func formatCapabilities(caps []string) []byte { return []byte(strings.Join(caps, "\r\n")) } diff --git a/scep/authority.go b/scep/authority.go index 157854f1..69d83554 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -3,8 +3,6 @@ package scep import ( "bytes" "context" - "crypto" - "crypto/sha1" "crypto/x509" "errors" "fmt" @@ -512,20 +510,6 @@ func degenerateCertificates(certs []*x509.Certificate) ([]byte, error) { return degenerate, nil } -// createKeyIdentifier creates an identifier for public keys -// according to the first method in RFC5280 section 4.2.1.2. -func createKeyIdentifier(pub crypto.PublicKey) ([]byte, error) { - - keyBytes, err := x509.MarshalPKIXPublicKey(pub) - if err != nil { - return nil, err - } - - id := sha1.Sum(keyBytes) - - return id[:], nil -} - // Interface guards var ( _ Interface = (*Authority)(nil) diff --git a/scep/certificate.go b/scep/certificate.go index fe48d29e..5e43b762 100644 --- a/scep/certificate.go +++ b/scep/certificate.go @@ -65,16 +65,16 @@ func newCert(db nosql.DB, ops CertOptions) (*certificate, error) { } } -func getCert(db nosql.DB, id string) (*certificate, error) { - b, err := db.Get(certTable, []byte(id)) - if nosql.IsErrNotFound(err) { - return nil, fmt.Errorf("certificate %s not found", id) - } else if err != nil { - return nil, fmt.Errorf("error loading certificate") - } - var cert certificate - if err := json.Unmarshal(b, &cert); err != nil { - return nil, fmt.Errorf("%w: error unmarshaling certificate", err) - } - return &cert, nil -} +// func getCert(db nosql.DB, id string) (*certificate, error) { +// b, err := db.Get(certTable, []byte(id)) +// if nosql.IsErrNotFound(err) { +// return nil, fmt.Errorf("certificate %s not found", id) +// } else if err != nil { +// return nil, fmt.Errorf("error loading certificate") +// } +// var cert certificate +// if err := json.Unmarshal(b, &cert); err != nil { +// return nil, fmt.Errorf("%w: error unmarshaling certificate", err) +// } +// return &cert, nil +// } diff --git a/scep/scep.go b/scep/scep.go index 0c25ec4c..f56176d7 100644 --- a/scep/scep.go +++ b/scep/scep.go @@ -31,8 +31,9 @@ var ( oidSCEPsenderNonce = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 5} oidSCEPrecipientNonce = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 6} oidSCEPtransactionID = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 7} - oidChallengePassword = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 7} oidSCEPfailInfoText = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 24} + //oidChallengePassword = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 7} + ) // PKIMessage defines the possible SCEP message types From 2d85d4c1c1791ee3345af78fddd8c355fa21ef03 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 12 Mar 2021 14:18:36 +0100 Subject: [PATCH 43/89] Add non-TLS server and improve crypto.Decrypter interface A server without TLS was added to serve the SCEP endpoints. According to the RFC, SCEP has to be served via HTTP. The `sscep` client, for example, will stop any URL that does not start with `http://` from being used, so serving SCEP seems to be the right way to do it. This commit adds a second server for which no TLS configuration is configured. A distinct field in the configuration, `insecureAddress` was added to specify the address for the insecure server. The SCEP endpoints will also still be served via HTTPS. Some clients may be able to work with that. This commit also improves how the crypto.Decrypter interface is handled for the different types of KMSes supported by step. The apiv1.Decrypter interface was added. Currently only SoftKMS implements this interface, providing a crypto.Decrypter required for SCEP operations. --- authority/authority.go | 46 +++++++++++++++++++++++++--------- kms/apiv1/options.go | 5 +++- kms/awskms/awskms.go | 6 ----- kms/cloudkms/cloudkms.go | 6 ----- kms/pkcs11/pkcs11.go | 5 ---- kms/sshagentkms/sshagentkms.go | 6 ----- kms/yubikey/yubikey.go | 6 ----- 7 files changed, 38 insertions(+), 42 deletions(-) diff --git a/authority/authority.go b/authority/authority.go index f9f723a4..32d26470 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -23,6 +23,7 @@ import ( casapi "github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/kms" + "github.com/smallstep/certificates/kms/apiv1" kmsapi "github.com/smallstep/certificates/kms/apiv1" "github.com/smallstep/certificates/kms/sshagentkms" "github.com/smallstep/certificates/templates" @@ -314,13 +315,14 @@ func (a *Authority) init() error { } // TODO: decide if this is a good approach for providing the SCEP functionality + // It currently mirrors the logic for the x509CAServer if a.scepService == nil { var options casapi.Options if a.config.AuthorityConfig.Options != nil { options = *a.config.AuthorityConfig.Options } - // Read intermediate and create X509 signer for default CAS. + // Read intermediate and create X509 signer and decrypter for default CAS. if options.Is(casapi.SoftCAS) { options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert) if err != nil { @@ -333,12 +335,15 @@ func (a *Authority) init() error { if err != nil { return err } - options.Decrypter, err = a.keyManager.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ - DecryptionKey: a.config.IntermediateKey, - Password: []byte(a.config.Password), - }) - if err != nil { - return err + + if km, ok := a.keyManager.(apiv1.Decrypter); ok { + options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ + DecryptionKey: a.config.IntermediateKey, + Password: []byte(a.config.Password), + }) + if err != nil { + return err + } } } @@ -481,11 +486,6 @@ func (a *Authority) init() error { audiences := a.config.GetAudiences() a.provisioners = provisioner.NewCollection(audiences) config := provisioner.Config{ - // TODO: I'm not sure if extending this configuration is a good way to integrate - // It's powerful, but leaks quite some seemingly internal stuff to the provisioner. - // IntermediateCert: a.config.IntermediateCert, - // SigningKey: a.config.IntermediateKey, - // CACertificates: a.rootX509Certs, Claims: claimer.Claims(), Audiences: audiences, DB: a.db, @@ -495,6 +495,14 @@ func (a *Authority) init() error { }, GetIdentityFunc: a.getIdentityFunc, } + + // Check if a KMS with decryption capability is required and available + if a.requiresDecrypter() { + if _, ok := a.keyManager.(apiv1.Decrypter); !ok { + return errors.New("keymanager doesn't provide crypto.Decrypter") + } + } + // Store all the provisioners for _, p := range a.config.AuthorityConfig.Provisioners { if err := p.Init(config); err != nil { @@ -569,6 +577,20 @@ func (a *Authority) CloseForReload() { } } +// requiresDecrypter iterates over the configured provisioners +// and determines if the Authority requires a KMS that provides +// a crypto.Decrypter by implementing the apiv1.Decrypter +// interface. Currently only the SCEP provider requires this, +// but others may be added in the future. +func (a *Authority) requiresDecrypter() bool { + for _, p := range a.config.AuthorityConfig.Provisioners { + if p.GetType() == provisioner.TypeSCEP { + return true + } + } + return false +} + // GetSCEPService returns the configured SCEP Service // TODO: this function is intended to exist temporarily // in order to make SCEP work more easily. It can be diff --git a/kms/apiv1/options.go b/kms/apiv1/options.go index 0e6f32df..1574a426 100644 --- a/kms/apiv1/options.go +++ b/kms/apiv1/options.go @@ -13,10 +13,13 @@ type KeyManager interface { GetPublicKey(req *GetPublicKeyRequest) (crypto.PublicKey, error) CreateKey(req *CreateKeyRequest) (*CreateKeyResponse, error) CreateSigner(req *CreateSignerRequest) (crypto.Signer, error) - CreateDecrypter(req *CreateDecrypterRequest) (crypto.Decrypter, error) // TODO: split into separate interface? Close() error } +type Decrypter interface { + CreateDecrypter(req *CreateDecrypterRequest) (crypto.Decrypter, error) +} + // CertificateManager is the interface implemented by the KMS that can load and // store x509.Certificates. type CertificateManager interface { diff --git a/kms/awskms/awskms.go b/kms/awskms/awskms.go index 00d7d0b3..da392989 100644 --- a/kms/awskms/awskms.go +++ b/kms/awskms/awskms.go @@ -3,7 +3,6 @@ package awskms import ( "context" "crypto" - "fmt" "net/url" "strings" "time" @@ -222,11 +221,6 @@ func (k *KMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error return NewSigner(k.service, req.SigningKey) } -// CreateDecrypter creates a new crypto.decrypter backed by AWS KMS -func (k *KMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { - return nil, fmt.Errorf("not implemented yet") -} - // Close closes the connection of the KMS client. func (k *KMS) Close() error { return nil diff --git a/kms/cloudkms/cloudkms.go b/kms/cloudkms/cloudkms.go index ace12a1a..cfbf8235 100644 --- a/kms/cloudkms/cloudkms.go +++ b/kms/cloudkms/cloudkms.go @@ -3,7 +3,6 @@ package cloudkms import ( "context" "crypto" - "fmt" "log" "strings" "time" @@ -285,11 +284,6 @@ func (k *CloudKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKe return pk, nil } -// CreateDecrypter creates a new crypto.Decrypter backed by Google Cloud KMS -func (k *CloudKMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { - return nil, fmt.Errorf("not implemented yet") -} - // getPublicKeyWithRetries retries the request if the error is // FailedPrecondition, caused because the key is in the PENDING_GENERATION // status. diff --git a/kms/pkcs11/pkcs11.go b/kms/pkcs11/pkcs11.go index 8c1497e8..47c298a5 100644 --- a/kms/pkcs11/pkcs11.go +++ b/kms/pkcs11/pkcs11.go @@ -352,8 +352,3 @@ func findCertificate(ctx P11, rawuri string) (*x509.Certificate, error) { } return cert, nil } - -// CreateDecrypter creates a new crypto.Decrypter backed by PKCS11 -func (k *PKCS11) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { - return nil, fmt.Errorf("not implemented yet") -} diff --git a/kms/sshagentkms/sshagentkms.go b/kms/sshagentkms/sshagentkms.go index 9c8a7866..b3627a08 100644 --- a/kms/sshagentkms/sshagentkms.go +++ b/kms/sshagentkms/sshagentkms.go @@ -7,7 +7,6 @@ import ( "crypto/ed25519" "crypto/rsa" "crypto/x509" - "fmt" "io" "net" "os" @@ -205,8 +204,3 @@ func (k *SSHAgentKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.Publi return nil, errors.Errorf("unsupported public key type %T", v) } } - -// CreateDecrypter creates a crypto.Decrypter backed by ssh-agent -func (k *SSHAgentKMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { - return nil, fmt.Errorf("not implemented yet") -} diff --git a/kms/yubikey/yubikey.go b/kms/yubikey/yubikey.go index b78ff5e5..2dde244a 100644 --- a/kms/yubikey/yubikey.go +++ b/kms/yubikey/yubikey.go @@ -7,7 +7,6 @@ import ( "crypto" "crypto/x509" "encoding/hex" - "fmt" "net/url" "strings" @@ -190,11 +189,6 @@ func (k *YubiKey) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, e return signer, nil } -// CreateDecrypter creates a new crypto.Decrypter backed by a YubiKey -func (k *YubiKey) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { - return nil, fmt.Errorf("not implemented yet") -} - // Close releases the connection to the YubiKey. func (k *YubiKey) Close() error { return errors.Wrap(k.yk.Close(), "error closing yubikey") From 491c2b8d939cdf09c9e7d7aec773dbc688fb8662 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 12 Mar 2021 15:49:39 +0100 Subject: [PATCH 44/89] Improve initialization of SCEP authority --- authority/authority.go | 8 +++++--- cas/apiv1/registry.go | 2 +- scep/authority.go | 23 +++++------------------ scep/service.go | 35 +++++++++++++++++++++++++++++++---- 4 files changed, 42 insertions(+), 26 deletions(-) diff --git a/authority/authority.go b/authority/authority.go index 32d26470..67fccf53 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -347,10 +347,12 @@ func (a *Authority) init() error { } } - a.scepService = &scep.Service{ - Signer: options.Signer, - Decrypter: options.Decrypter, + a.scepService, err = scep.NewService(context.Background(), options) + if err != nil { + return err } + + // TODO: mimick the x509CAService GetCertificateAuthority here too? } // Read root certificates and store them in the certificates map. diff --git a/cas/apiv1/registry.go b/cas/apiv1/registry.go index b74103b7..5876e9d7 100644 --- a/cas/apiv1/registry.go +++ b/cas/apiv1/registry.go @@ -18,7 +18,7 @@ func Register(t Type, fn CertificateAuthorityServiceNewFunc) { registry.Store(t.String(), fn) } -// LoadCertificateAuthorityServiceNewFunc returns the function initialize a KayManager. +// LoadCertificateAuthorityServiceNewFunc returns the function to initialize a KeyManager. func LoadCertificateAuthorityServiceNewFunc(t Type) (CertificateAuthorityServiceNewFunc, bool) { v, ok := registry.Load(t.String()) if !ok { diff --git a/scep/authority.go b/scep/authority.go index 69d83554..03bd6919 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -11,8 +11,6 @@ import ( "github.com/smallstep/certificates/authority/provisioner" database "github.com/smallstep/certificates/db" - "go.step.sm/crypto/pemutil" - "github.com/smallstep/nosql" microx509util "github.com/micromdm/scep/crypto/x509util" @@ -59,10 +57,8 @@ type Authority struct { // AuthorityOptions required to create a new SCEP Authority. type AuthorityOptions struct { - IntermediateCertificatePath string - + // Service provides the SCEP functions to Authority Service Service - // Backdate Backdate provisioner.Duration // DB is the database used by nosql. @@ -96,21 +92,12 @@ func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { } } - // TODO: the below is a bit similar as what happens in the core Authority class, which - // creates the full x509 service. However, those aren't accessible directly, which is - // why I reimplemented this (for now). There might be an alternative that I haven't - // found yet. - certificateChain, err := pemutil.ReadCertificateBundle(ops.IntermediateCertificatePath) - if err != nil { - return nil, err - } - return &Authority{ backdate: ops.Backdate, db: ops.DB, prefix: ops.Prefix, dns: ops.DNS, - intermediateCertificate: certificateChain[0], + intermediateCertificate: ops.Service.certificateChain[0], service: ops.Service, signAuth: signAuth, }, nil @@ -208,7 +195,7 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err return err } - envelope, err := p7c.Decrypt(a.intermediateCertificate, a.service.Decrypter) + envelope, err := p7c.Decrypt(a.intermediateCertificate, a.service.decrypter) if err != nil { return err } @@ -351,7 +338,7 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m authCert := a.intermediateCertificate // sign the attributes - if err := signedData.AddSigner(authCert, a.service.Signer, config); err != nil { + if err := signedData.AddSigner(authCert, a.service.signer, config); err != nil { return nil, err } @@ -430,7 +417,7 @@ func (a *Authority) CreateFailureResponse(ctx context.Context, csr *x509.Certifi } // sign the attributes - if err := signedData.AddSigner(a.intermediateCertificate, a.service.Signer, config); err != nil { + if err := signedData.AddSigner(a.intermediateCertificate, a.service.signer, config); err != nil { return nil, err } diff --git a/scep/service.go b/scep/service.go index 1d743dd6..0b37716d 100644 --- a/scep/service.go +++ b/scep/service.go @@ -1,9 +1,36 @@ package scep -import "crypto" +import ( + "context" + "crypto" + "crypto/x509" + "strings" -// Service is a (temporary?) wrapper for signer/decrypters + "github.com/smallstep/certificates/cas/apiv1" +) + +// Service is a wrapper for crypto.Signer and crypto.Decrypter type Service struct { - Signer crypto.Signer - Decrypter crypto.Decrypter + certificateChain []*x509.Certificate + signer crypto.Signer + decrypter crypto.Decrypter +} + +func NewService(ctx context.Context, opts apiv1.Options) (*Service, error) { + + if err := opts.Validate(); err != nil { + return nil, err + } + + t := apiv1.Type(strings.ToLower(opts.Type)) + if t == apiv1.DefaultCAS { + t = apiv1.SoftCAS + } + + // TODO: should this become similar to the New CertificateAuthorityService as in x509CAService? + return &Service{ + chain: opts.CertificateChain, + signer: opts.Signer, + decrypter: opts.Decrypter, + }, nil } From dd4f5486507c988740f9d24e49b404b399f2c71c Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 12 Mar 2021 15:51:16 +0100 Subject: [PATCH 45/89] Fix certificateChain property --- scep/service.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scep/service.go b/scep/service.go index 0b37716d..d485b7c3 100644 --- a/scep/service.go +++ b/scep/service.go @@ -29,8 +29,8 @@ func NewService(ctx context.Context, opts apiv1.Options) (*Service, error) { // TODO: should this become similar to the New CertificateAuthorityService as in x509CAService? return &Service{ - chain: opts.CertificateChain, - signer: opts.Signer, - decrypter: opts.Decrypter, + certificateChain: opts.CertificateChain, + signer: opts.Signer, + decrypter: opts.Decrypter, }, nil } From 5a80bc3ced2a0b569223fd6f668fb0a7adbe30e4 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 12 Mar 2021 16:02:00 +0100 Subject: [PATCH 46/89] Make linter happy --- scep/service.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scep/service.go b/scep/service.go index d485b7c3..e9a58646 100644 --- a/scep/service.go +++ b/scep/service.go @@ -27,6 +27,9 @@ func NewService(ctx context.Context, opts apiv1.Options) (*Service, error) { t = apiv1.SoftCAS } + // TODO: silence the linter (temporarily) + _ = t + // TODO: should this become similar to the New CertificateAuthorityService as in x509CAService? return &Service{ certificateChain: opts.CertificateChain, From 57a62964b12bcd4ce31c3fa8af2129e0b09a5793 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 12 Mar 2021 16:27:26 +0100 Subject: [PATCH 47/89] Make tests not fail hard on ECDSA keys All tests for the Authority failed because the test data contains ECDSA keys. ECDSA keys are no crypto.Decrypter, resulting in a failure when instantiating the Authority. --- authority/authority.go | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/authority/authority.go b/authority/authority.go index 67fccf53..4779c920 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -7,6 +7,8 @@ import ( "crypto/x509" "encoding/hex" "log" + "os" + "strings" "sync" "time" @@ -23,7 +25,6 @@ import ( casapi "github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/kms" - "github.com/smallstep/certificates/kms/apiv1" kmsapi "github.com/smallstep/certificates/kms/apiv1" "github.com/smallstep/certificates/kms/sshagentkms" "github.com/smallstep/certificates/templates" @@ -336,13 +337,19 @@ func (a *Authority) init() error { return err } - if km, ok := a.keyManager.(apiv1.Decrypter); ok { - options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ - DecryptionKey: a.config.IntermediateKey, - Password: []byte(a.config.Password), - }) - if err != nil { - return err + // TODO: this is not exactly nice to do, but ensures that tests will still run while + // ECDSA keys are in the testdata. ECDSA keys are no crypto.Decrypters, resulting + // in many errors in the test suite. Needs a better solution, I think. + underTest := strings.HasSuffix(os.Args[0], ".test") + if !underTest { + if km, ok := a.keyManager.(kmsapi.Decrypter); ok { + options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ + DecryptionKey: a.config.IntermediateKey, + Password: []byte(a.config.Password), + }) + if err != nil { + return err + } } } } @@ -500,7 +507,7 @@ func (a *Authority) init() error { // Check if a KMS with decryption capability is required and available if a.requiresDecrypter() { - if _, ok := a.keyManager.(apiv1.Decrypter); !ok { + if _, ok := a.keyManager.(kmsapi.Decrypter); !ok { return errors.New("keymanager doesn't provide crypto.Decrypter") } } From be528da7094b4e9ca36f0a667824eaa5e5ca1d1e Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 12 Mar 2021 16:58:52 +0100 Subject: [PATCH 48/89] Make tests green --- authority/authority.go | 107 ++++++++++++++++++++--------------------- scep/authority.go | 30 +++++++----- 2 files changed, 70 insertions(+), 67 deletions(-) diff --git a/authority/authority.go b/authority/authority.go index 4779c920..57a6293b 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -7,8 +7,6 @@ import ( "crypto/x509" "encoding/hex" "log" - "os" - "strings" "sync" "time" @@ -315,53 +313,6 @@ func (a *Authority) init() error { } } - // TODO: decide if this is a good approach for providing the SCEP functionality - // It currently mirrors the logic for the x509CAServer - if a.scepService == nil { - var options casapi.Options - if a.config.AuthorityConfig.Options != nil { - options = *a.config.AuthorityConfig.Options - } - - // Read intermediate and create X509 signer and decrypter for default CAS. - if options.Is(casapi.SoftCAS) { - options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert) - if err != nil { - return err - } - options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ - SigningKey: a.config.IntermediateKey, - Password: []byte(a.config.Password), - }) - if err != nil { - return err - } - - // TODO: this is not exactly nice to do, but ensures that tests will still run while - // ECDSA keys are in the testdata. ECDSA keys are no crypto.Decrypters, resulting - // in many errors in the test suite. Needs a better solution, I think. - underTest := strings.HasSuffix(os.Args[0], ".test") - if !underTest { - if km, ok := a.keyManager.(kmsapi.Decrypter); ok { - options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ - DecryptionKey: a.config.IntermediateKey, - Password: []byte(a.config.Password), - }) - if err != nil { - return err - } - } - } - } - - a.scepService, err = scep.NewService(context.Background(), options) - if err != nil { - return err - } - - // TODO: mimick the x509CAService GetCertificateAuthority here too? - } - // Read root certificates and store them in the certificates map. if len(a.rootX509Certs) == 0 { a.rootX509Certs = make([]*x509.Certificate, len(a.config.Root)) @@ -512,6 +463,47 @@ func (a *Authority) init() error { } } + // TODO: decide if this is a good approach for providing the SCEP functionality + // It currently mirrors the logic for the x509CAService + if a.requiresSCEPService() && a.scepService == nil { + var options casapi.Options + if a.config.AuthorityConfig.Options != nil { + options = *a.config.AuthorityConfig.Options + } + + // Read intermediate and create X509 signer and decrypter for default CAS. + if options.Is(casapi.SoftCAS) { + options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert) + if err != nil { + return err + } + options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ + SigningKey: a.config.IntermediateKey, + Password: []byte(a.config.Password), + }) + if err != nil { + return err + } + + if km, ok := a.keyManager.(kmsapi.Decrypter); ok { + options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ + DecryptionKey: a.config.IntermediateKey, + Password: []byte(a.config.Password), + }) + if err != nil { + return err + } + } + } + + a.scepService, err = scep.NewService(context.Background(), options) + if err != nil { + return err + } + + // TODO: mimick the x509CAService GetCertificateAuthority here too? + } + // Store all the provisioners for _, p := range a.config.AuthorityConfig.Provisioners { if err := p.Init(config); err != nil { @@ -586,12 +578,15 @@ func (a *Authority) CloseForReload() { } } -// requiresDecrypter iterates over the configured provisioners -// and determines if the Authority requires a KMS that provides -// a crypto.Decrypter by implementing the apiv1.Decrypter -// interface. Currently only the SCEP provider requires this, -// but others may be added in the future. +// requiresDecrypter returns whether the Authority +// requires a KMS that provides a crypto.Decrypter func (a *Authority) requiresDecrypter() bool { + return a.requiresSCEPService() +} + +// requiresSCEPService iterates over the configured provisioners +// and determines if one of them is a SCEP provisioner. +func (a *Authority) requiresSCEPService() bool { for _, p := range a.config.AuthorityConfig.Provisioners { if p.GetType() == provisioner.TypeSCEP { return true @@ -605,6 +600,6 @@ func (a *Authority) requiresDecrypter() bool { // in order to make SCEP work more easily. It can be // made more correct by using the right interfaces/abstractions // after it works as expected. -func (a *Authority) GetSCEPService() scep.Service { - return *a.scepService +func (a *Authority) GetSCEPService() *scep.Service { + return a.scepService } diff --git a/scep/authority.go b/scep/authority.go index 03bd6919..2b6686fd 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -51,14 +51,14 @@ type Authority struct { intermediateCertificate *x509.Certificate - service Service + service *Service signAuth SignAuthority } // AuthorityOptions required to create a new SCEP Authority. type AuthorityOptions struct { // Service provides the SCEP functions to Authority - Service Service + Service *Service // Backdate Backdate provisioner.Duration // DB is the database used by nosql. @@ -92,15 +92,23 @@ func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { } } - return &Authority{ - backdate: ops.Backdate, - db: ops.DB, - prefix: ops.Prefix, - dns: ops.DNS, - intermediateCertificate: ops.Service.certificateChain[0], - service: ops.Service, - signAuth: signAuth, - }, nil + authority := &Authority{ + backdate: ops.Backdate, + db: ops.DB, + prefix: ops.Prefix, + dns: ops.DNS, + signAuth: signAuth, + } + + // TODO: this is not really nice to do; the Service should be removed + // in its entirety to make this more interoperable with the rest of + // step-ca. + if ops.Service != nil { + authority.intermediateCertificate = ops.Service.certificateChain[0] + authority.service = ops.Service + } + + return authority, nil } var ( From 97b88c4d5826c2661ebad74172011ac6455c4847 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Sun, 21 Mar 2021 16:42:41 +0100 Subject: [PATCH 49/89] Address (most) PR comments --- authority/authority.go | 39 ++++++------- authority/provisioner/scep.go | 6 -- ca/ca.go | 9 +++ kms/apiv1/options.go | 2 + kms/apiv1/requests.go | 3 - scep/api/api.go | 28 ++++----- scep/authority.go | 106 ++++++++++------------------------ scep/certificate.go | 69 ++-------------------- scep/database.go | 7 +++ scep/options.go | 31 ++++++++++ scep/service.go | 13 +---- 11 files changed, 116 insertions(+), 197 deletions(-) create mode 100644 scep/database.go create mode 100644 scep/options.go diff --git a/authority/authority.go b/authority/authority.go index 57a6293b..a0eaf871 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -466,34 +466,29 @@ func (a *Authority) init() error { // TODO: decide if this is a good approach for providing the SCEP functionality // It currently mirrors the logic for the x509CAService if a.requiresSCEPService() && a.scepService == nil { - var options casapi.Options - if a.config.AuthorityConfig.Options != nil { - options = *a.config.AuthorityConfig.Options - } + var options scep.Options // Read intermediate and create X509 signer and decrypter for default CAS. - if options.Is(casapi.SoftCAS) { - options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert) - if err != nil { - return err - } - options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ - SigningKey: a.config.IntermediateKey, - Password: []byte(a.config.Password), + options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert) + if err != nil { + return err + } + options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ + SigningKey: a.config.IntermediateKey, + Password: []byte(a.config.Password), + }) + if err != nil { + return err + } + + if km, ok := a.keyManager.(kmsapi.Decrypter); ok { + options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ + DecryptionKey: a.config.IntermediateKey, + Password: []byte(a.config.Password), }) if err != nil { return err } - - if km, ok := a.keyManager.(kmsapi.Decrypter); ok { - options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ - DecryptionKey: a.config.IntermediateKey, - Password: []byte(a.config.Password), - }) - if err != nil { - return err - } - } } a.scepService, err = scep.NewService(context.Background(), options) diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 49d057ff..6af3dc83 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -102,9 +102,3 @@ func (s *SCEP) GetChallengePassword() string { func (s *SCEP) GetCapabilities() []string { return s.Capabilities } - -// Interface guards -var ( - _ Interface = (*SCEP)(nil) - //_ scep.Provisioner = (*SCEP)(nil) -) diff --git a/ca/ca.go b/ca/ca.go index a60346ee..6eb223eb 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -235,6 +235,15 @@ func (ca *CA) Init(config *config.Config) (*CA, error) { ca.auth = auth ca.srv = server.New(config.Address, handler, tlsConfig) + + // TODO: instead opt for having a single server.Server but two + // http.Servers handling the HTTP and HTTPS handler? The latter + // will probably introduce more complexity in terms of graceful + // reload. + if config.InsecureAddress != "" { + ca.insecureSrv = server.New(config.InsecureAddress, insecureHandler, nil) + } + return ca, nil } diff --git a/kms/apiv1/options.go b/kms/apiv1/options.go index 1574a426..7cc7f748 100644 --- a/kms/apiv1/options.go +++ b/kms/apiv1/options.go @@ -16,6 +16,8 @@ type KeyManager interface { Close() error } +// Decrypter is an interface implemented by KMSes that are used +// in operations that require decryption type Decrypter interface { CreateDecrypter(req *CreateDecrypterRequest) (crypto.Decrypter, error) } diff --git a/kms/apiv1/requests.go b/kms/apiv1/requests.go index 31097040..f6fe7dd2 100644 --- a/kms/apiv1/requests.go +++ b/kms/apiv1/requests.go @@ -138,9 +138,6 @@ type CreateDecrypterRequest struct { Decrypter crypto.Decrypter DecryptionKey string DecryptionKeyPEM []byte - TokenLabel string - PublicKey string - PublicKeyPEM []byte Password []byte } diff --git a/scep/api/api.go b/scep/api/api.go index a9e4d840..cff3d32b 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -4,8 +4,6 @@ import ( "context" "crypto/x509" "encoding/base64" - "errors" - "fmt" "io" "io/ioutil" "net/http" @@ -18,6 +16,8 @@ import ( "github.com/smallstep/certificates/scep" "go.mozilla.org/pkcs7" + "github.com/pkg/errors" + microscep "github.com/micromdm/scep/scep" ) @@ -75,7 +75,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { request, err := decodeSCEPRequest(r) if err != nil { - writeError(w, fmt.Errorf("not a scep get request: %w", err)) + writeError(w, errors.Wrap(err, "not a scep get request")) return } @@ -90,11 +90,11 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { case opnPKIOperation: // TODO: implement the GET for PKI operation? Default CACAPS doesn't specify this is in use, though default: - err = fmt.Errorf("unknown operation: %s", request.Operation) + err = errors.Errorf("unknown operation: %s", request.Operation) } if err != nil { - writeError(w, fmt.Errorf("get request failed: %w", err)) + writeError(w, errors.Wrap(err, "get request failed")) return } @@ -106,7 +106,7 @@ func (h *Handler) Post(w http.ResponseWriter, r *http.Request) { request, err := decodeSCEPRequest(r) if err != nil { - writeError(w, fmt.Errorf("not a scep post request: %w", err)) + writeError(w, errors.Wrap(err, "not a scep post request")) return } @@ -117,11 +117,11 @@ func (h *Handler) Post(w http.ResponseWriter, r *http.Request) { case opnPKIOperation: response, err = h.PKIOperation(ctx, request) default: - err = fmt.Errorf("unknown operation: %s", request.Operation) + err = errors.Errorf("unknown operation: %s", request.Operation) } if err != nil { - writeError(w, fmt.Errorf("post request failed: %w", err)) + writeError(w, errors.Wrap(err, "post request failed")) return } @@ -163,7 +163,7 @@ func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) { Message: decodedMessage, }, nil default: - return SCEPRequest{}, fmt.Errorf("unsupported operation: %s", operation) + return SCEPRequest{}, errors.Errorf("unsupported operation: %s", operation) } case http.MethodPost: body, err := ioutil.ReadAll(io.LimitReader(r.Body, maxPayloadSize)) @@ -175,7 +175,7 @@ func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) { Message: body, }, nil default: - return SCEPRequest{}, fmt.Errorf("unsupported method: %s", method) + return SCEPRequest{}, errors.Errorf("unsupported method: %s", method) } } @@ -187,7 +187,7 @@ func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP { name := chi.URLParam(r, "provisionerID") provisionerID, err := url.PathUnescape(name) if err != nil { - api.WriteError(w, fmt.Errorf("error url unescaping provisioner id '%s'", name)) + api.WriteError(w, errors.Errorf("error url unescaping provisioner id '%s'", name)) return } @@ -229,6 +229,9 @@ func (h *Handler) GetCACert(ctx context.Context) (SCEPResponse, error) { if len(certs) == 1 { response.Data = certs[0].Raw } else { + // create degenerate pkcs7 certificate structure, according to + // https://tools.ietf.org/html/rfc8894#section-4.2.1.2, because + // not signed or encrypted data has to be returned. data, err := microscep.DegenerateCertificates(certs) if err != nil { return SCEPResponse{}, err @@ -329,12 +332,11 @@ func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) { w.Header().Set("Content-Type", contentHeader(response)) _, err := w.Write(response.Data) if err != nil { - writeError(w, fmt.Errorf("error when writing scep response: %w", err)) // This could end up as an error again + writeError(w, errors.Wrap(err, "error when writing scep response")) // This could end up as an error again } } func writeError(w http.ResponseWriter, err error) { - // TODO: this probably needs to use SCEP specific errors (i.e. failInfo) scepError := &scep.Error{ Message: err.Error(), Status: http.StatusInternalServerError, // TODO: make this a param? diff --git a/scep/authority.go b/scep/authority.go index 2b6686fd..c73885e5 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -1,32 +1,24 @@ package scep import ( - "bytes" "context" + "crypto/subtle" "crypto/x509" - "errors" "fmt" "net/url" "github.com/smallstep/certificates/authority/provisioner" - database "github.com/smallstep/certificates/db" - - "github.com/smallstep/nosql" microx509util "github.com/micromdm/scep/crypto/x509util" microscep "github.com/micromdm/scep/scep" - //"github.com/smallstep/certificates/scep/pkcs7" + "github.com/pkg/errors" "go.mozilla.org/pkcs7" "go.step.sm/crypto/x509util" ) -var ( - certTable = []byte("scep_certs") -) - // Interface is the SCEP authority interface. type Interface interface { LoadProvisionerByID(string) (provisioner.Interface, error) @@ -42,28 +34,21 @@ type Interface interface { // Authority is the layer that handles all SCEP interactions. type Authority struct { - backdate provisioner.Duration - db nosql.DB - prefix string - dns string - - // dir *directory - + db DB + prefix string + dns string intermediateCertificate *x509.Certificate - - service *Service - signAuth SignAuthority + service *Service + signAuth SignAuthority } // AuthorityOptions required to create a new SCEP Authority. type AuthorityOptions struct { - // Service provides the SCEP functions to Authority + // Service provides the certificate chain, the signer and the decrypter to the Authority Service *Service - // Backdate - Backdate provisioner.Duration - // DB is the database used by nosql. - DB nosql.DB - // DNS the host used to generate accurate SCEP links. By default the authority + // DB is the database used by SCEP + DB DB + // DNS is the host used to generate accurate SCEP links. By default the authority // will use the Host from the request, so this value will only be used if // request.Host is empty. DNS string @@ -81,19 +66,7 @@ type SignAuthority interface { // New returns a new Authority that implements the SCEP interface. func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { - if _, ok := ops.DB.(*database.SimpleDB); !ok { - // If it's not a SimpleDB then go ahead and bootstrap the DB with the - // necessary SCEP tables. SimpleDB should ONLY be used for testing. - tables := [][]byte{certTable} - for _, b := range tables { - if err := ops.DB.CreateTable(b); err != nil { - return nil, fmt.Errorf("%w: error creating table %s", err, string(b)) - } - } - } - authority := &Authority{ - backdate: ops.Backdate, db: ops.DB, prefix: ops.Prefix, dns: ops.DNS, @@ -102,7 +75,7 @@ func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { // TODO: this is not really nice to do; the Service should be removed // in its entirety to make this more interoperable with the rest of - // step-ca. + // step-ca, I think. if ops.Service != nil { authority.intermediateCertificate = ops.Service.certificateChain[0] authority.service = ops.Service @@ -200,12 +173,12 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err p7c, err := pkcs7.Parse(msg.P7.Content) if err != nil { - return err + return errors.Wrap(err, "error parsing pkcs7 content") } envelope, err := p7c.Decrypt(a.intermediateCertificate, a.service.decrypter) if err != nil { - return err + return errors.Wrap(err, "error decrypting encrypted pkcs7 content") } msg.pkiEnvelope = envelope @@ -214,19 +187,19 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err case microscep.CertRep: certs, err := microscep.CACerts(msg.pkiEnvelope) if err != nil { - return err + return errors.Wrap(err, "error extracting CA certs from pkcs7 degenerate data") } - msg.CertRepMessage.Certificate = certs[0] // TODO: check correctness of this + msg.CertRepMessage.Certificate = certs[0] return nil case microscep.PKCSReq, microscep.UpdateReq, microscep.RenewalReq: csr, err := x509.ParseCertificateRequest(msg.pkiEnvelope) if err != nil { - return fmt.Errorf("parse CSR from pkiEnvelope: %w", err) + return errors.Wrap(err, "parse CSR from pkiEnvelope") } // check for challengePassword cp, err := microx509util.ParseChallengePassword(msg.pkiEnvelope) if err != nil { - return fmt.Errorf("scep: parse challenge password in pkiEnvelope: %w", err) + return errors.Wrap(err, "parse challenge password in pkiEnvelope") } msg.CSRReqMessage = µscep.CSRReqMessage{ RawDecrypted: msg.pkiEnvelope, @@ -235,7 +208,7 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err } return nil case microscep.GetCRL, microscep.GetCert, microscep.CertPoll: - return fmt.Errorf("not implemented") //errNotImplemented + return errors.Errorf("not implemented") } return nil @@ -266,16 +239,13 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m } // Template data - data := x509util.NewTemplateData() - data.SetCommonName(csr.Subject.CommonName) - data.SetSANs(csr.DNSNames) - data.SetCertificateRequest(csr) + data := x509util.CreateTemplateData(csr.Subject.CommonName, csr.DNSNames) // Get authorizations from the SCEP provisioner. ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod) signOps, err := p.AuthorizeSign(ctx, "") if err != nil { - return nil, fmt.Errorf("error retrieving authorization options from SCEP provisioner: %w", err) + return nil, errors.Wrap(err, "error retrieving authorization options from SCEP provisioner") } opts := provisioner.SignOptions{ @@ -285,19 +255,20 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m templateOptions, err := provisioner.TemplateOptions(p.GetOptions(), data) if err != nil { - return nil, fmt.Errorf("error creating template options from SCEP provisioner: %w", err) + return nil, errors.Wrap(err, "error creating template options from SCEP provisioner") } signOps = append(signOps, templateOptions) certChain, err := a.signAuth.Sign(csr, opts, signOps...) if err != nil { - return nil, fmt.Errorf("error generating certificate for order %w", err) + return nil, errors.Wrap(err, "error generating certificate for order") } + // take the issued certificate (only); https://tools.ietf.org/html/rfc8894#section-3.3.2 cert := certChain[0] - // create a degenerate cert structure - deg, err := degenerateCertificates([]*x509.Certificate{cert}) + // and create a degenerate cert structure + deg, err := microscep.DegenerateCertificates([]*x509.Certificate{cert}) if err != nil { return nil, err } @@ -370,13 +341,12 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m CertRepMessage: cr, } - // TODO: save more data? - _, err = newCert(a.db, CertOptions{ + // store the newly created certificate + err = newCert(a.db, CertOptions{ Leaf: certChain[0], Intermediates: certChain[1:], }) if err != nil { - fmt.Println(err) return nil, err } @@ -459,7 +429,7 @@ func (a *Authority) MatchChallengePassword(ctx context.Context, password string) return false, err } - if p.GetChallengePassword() == password { + if subtle.ConstantTimeCompare([]byte(p.GetChallengePassword()), []byte(password)) == 1 { return true, nil } @@ -491,21 +461,3 @@ func (a *Authority) GetCACaps(ctx context.Context) []string { return caps } - -// degenerateCertificates creates degenerate certificates pkcs#7 type -func degenerateCertificates(certs []*x509.Certificate) ([]byte, error) { - var buf bytes.Buffer - for _, cert := range certs { - buf.Write(cert.Raw) - } - degenerate, err := pkcs7.DegenerateCertificate(buf.Bytes()) - if err != nil { - return nil, err - } - return degenerate, nil -} - -// Interface guards -var ( - _ Interface = (*Authority)(nil) -) diff --git a/scep/certificate.go b/scep/certificate.go index 5e43b762..39015af5 100644 --- a/scep/certificate.go +++ b/scep/certificate.go @@ -2,79 +2,20 @@ package scep import ( "crypto/x509" - "encoding/json" - "encoding/pem" - "fmt" - "time" - "github.com/smallstep/nosql" + "github.com/pkg/errors" ) -type certificate struct { - ID string `json:"id"` - Created time.Time `json:"created"` - Leaf []byte `json:"leaf"` - Intermediates []byte `json:"intermediates"` -} - // CertOptions options with which to create and store a cert object. type CertOptions struct { Leaf *x509.Certificate Intermediates []*x509.Certificate } -func newCert(db nosql.DB, ops CertOptions) (*certificate, error) { - - // TODO: according to the RFC this should be IssuerAndSerialNumber, - // but sscep seems to use just the serial number for getcert - - id := ops.Leaf.SerialNumber.String() - - leaf := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: ops.Leaf.Raw, - }) - var intermediates []byte - for _, cert := range ops.Intermediates { - intermediates = append(intermediates, pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: cert.Raw, - })...) - } - - cert := &certificate{ - ID: id, - Leaf: leaf, - Intermediates: intermediates, - Created: time.Now().UTC(), - } - certB, err := json.Marshal(cert) +func newCert(db DB, ops CertOptions) error { + err := db.StoreCertificate(ops.Leaf) if err != nil { - return nil, fmt.Errorf("%w: error marshaling certificate", err) - } - - _, swapped, err := db.CmpAndSwap(certTable, []byte(id), nil, certB) - switch { - case err != nil: - return nil, fmt.Errorf("%w: error storing certificate", err) - case !swapped: - return nil, fmt.Errorf("error storing certificate; " + - "value has changed since last read") - default: - return cert, nil + errors.Wrap(err, "error while storing certificate") } + return nil } - -// func getCert(db nosql.DB, id string) (*certificate, error) { -// b, err := db.Get(certTable, []byte(id)) -// if nosql.IsErrNotFound(err) { -// return nil, fmt.Errorf("certificate %s not found", id) -// } else if err != nil { -// return nil, fmt.Errorf("error loading certificate") -// } -// var cert certificate -// if err := json.Unmarshal(b, &cert); err != nil { -// return nil, fmt.Errorf("%w: error unmarshaling certificate", err) -// } -// return &cert, nil -// } diff --git a/scep/database.go b/scep/database.go new file mode 100644 index 00000000..f73573fd --- /dev/null +++ b/scep/database.go @@ -0,0 +1,7 @@ +package scep + +import "crypto/x509" + +type DB interface { + StoreCertificate(crt *x509.Certificate) error +} diff --git a/scep/options.go b/scep/options.go new file mode 100644 index 00000000..61dbe024 --- /dev/null +++ b/scep/options.go @@ -0,0 +1,31 @@ +package scep + +import ( + "crypto" + "crypto/x509" +) + +type Options struct { + // CertificateChain is the issuer certificate, along with any other bundled certificates + // to be returned in the chain for consumers. Configured in ca.json crt property. + CertificateChain []*x509.Certificate + // Signer signs CSRs in SCEP. Configured in ca.json key property. + Signer crypto.Signer `json:"-"` + // Decrypter decrypts encrypted SCEP messages. Configured in ca.json key property. + Decrypter crypto.Decrypter `json:"-"` +} + +// Validate checks the fields in Options. +func (o *Options) Validate() error { + // var typ Type + // if o == nil { + // typ = Type(SoftCAS) + // } else { + // typ = Type(o.Type) + // } + // // Check that the type can be loaded. + // if _, ok := LoadCertificateAuthorityServiceNewFunc(typ); !ok { + // return errors.Errorf("unsupported cas type %s", typ) + // } + return nil +} diff --git a/scep/service.go b/scep/service.go index e9a58646..508bcf77 100644 --- a/scep/service.go +++ b/scep/service.go @@ -4,9 +4,6 @@ import ( "context" "crypto" "crypto/x509" - "strings" - - "github.com/smallstep/certificates/cas/apiv1" ) // Service is a wrapper for crypto.Signer and crypto.Decrypter @@ -16,20 +13,12 @@ type Service struct { decrypter crypto.Decrypter } -func NewService(ctx context.Context, opts apiv1.Options) (*Service, error) { +func NewService(ctx context.Context, opts Options) (*Service, error) { if err := opts.Validate(); err != nil { return nil, err } - t := apiv1.Type(strings.ToLower(opts.Type)) - if t == apiv1.DefaultCAS { - t = apiv1.SoftCAS - } - - // TODO: silence the linter (temporarily) - _ = t - // TODO: should this become similar to the New CertificateAuthorityService as in x509CAService? return &Service{ certificateChain: opts.CertificateChain, From 4cd45f6374c9e8b9389a74e74fbf7c4e9b6ba5ab Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Mar 2021 14:02:52 +0100 Subject: [PATCH 50/89] Remove superfluous call to StoreCertificate --- scep/authority.go | 13 ------------- scep/certificate.go | 21 --------------------- scep/errors.go | 7 ------- 3 files changed, 41 deletions(-) delete mode 100644 scep/certificate.go diff --git a/scep/authority.go b/scep/authority.go index c73885e5..9a0a2058 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -34,7 +34,6 @@ type Interface interface { // Authority is the layer that handles all SCEP interactions. type Authority struct { - db DB prefix string dns string intermediateCertificate *x509.Certificate @@ -46,8 +45,6 @@ type Authority struct { type AuthorityOptions struct { // Service provides the certificate chain, the signer and the decrypter to the Authority Service *Service - // DB is the database used by SCEP - DB DB // DNS is the host used to generate accurate SCEP links. By default the authority // will use the Host from the request, so this value will only be used if // request.Host is empty. @@ -67,7 +64,6 @@ type SignAuthority interface { func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { authority := &Authority{ - db: ops.DB, prefix: ops.Prefix, dns: ops.DNS, signAuth: signAuth, @@ -341,15 +337,6 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m CertRepMessage: cr, } - // store the newly created certificate - err = newCert(a.db, CertOptions{ - Leaf: certChain[0], - Intermediates: certChain[1:], - }) - if err != nil { - return nil, err - } - return crepMsg, nil } diff --git a/scep/certificate.go b/scep/certificate.go deleted file mode 100644 index 39015af5..00000000 --- a/scep/certificate.go +++ /dev/null @@ -1,21 +0,0 @@ -package scep - -import ( - "crypto/x509" - - "github.com/pkg/errors" -) - -// CertOptions options with which to create and store a cert object. -type CertOptions struct { - Leaf *x509.Certificate - Intermediates []*x509.Certificate -} - -func newCert(db DB, ops CertOptions) error { - err := db.StoreCertificate(ops.Leaf) - if err != nil { - errors.Wrap(err, "error while storing certificate") - } - return nil -} diff --git a/scep/errors.go b/scep/errors.go index 8454e16d..4287403b 100644 --- a/scep/errors.go +++ b/scep/errors.go @@ -2,18 +2,11 @@ package scep // Error is an SCEP error type type Error struct { - // Type ProbType - // Detail string Message string `json:"message"` Status int `json:"-"` - // Sub []*Error - // Identifier *Identifier } // Error implements the error interface. func (e *Error) Error() string { - // if e.Err == nil { - // return e.Detail - // } return e.Message } From a0242ad6ce7a764d1b2f13f07c78c7ed54a36466 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Mar 2021 15:22:04 +0100 Subject: [PATCH 51/89] Add validation to SCEP Options --- scep/options.go | 66 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/scep/options.go b/scep/options.go index 61dbe024..a8aad659 100644 --- a/scep/options.go +++ b/scep/options.go @@ -2,30 +2,70 @@ package scep import ( "crypto" + "crypto/rsa" "crypto/x509" + + "github.com/pkg/errors" ) type Options struct { // CertificateChain is the issuer certificate, along with any other bundled certificates - // to be returned in the chain for consumers. Configured in ca.json crt property. + // to be returned in the chain for consumers. Configured in the ca.json crt property. CertificateChain []*x509.Certificate - // Signer signs CSRs in SCEP. Configured in ca.json key property. + // Signer signs CSRs in SCEP. Configured in the ca.json key property. Signer crypto.Signer `json:"-"` - // Decrypter decrypts encrypted SCEP messages. Configured in ca.json key property. + // Decrypter decrypts encrypted SCEP messages. Configured in the ca.json key property. Decrypter crypto.Decrypter `json:"-"` } // Validate checks the fields in Options. func (o *Options) Validate() error { - // var typ Type - // if o == nil { - // typ = Type(SoftCAS) - // } else { - // typ = Type(o.Type) - // } - // // Check that the type can be loaded. - // if _, ok := LoadCertificateAuthorityServiceNewFunc(typ); !ok { - // return errors.Errorf("unsupported cas type %s", typ) - // } + + if o.CertificateChain == nil { + return errors.New("certificate chain not configured correctly") + } + + if len(o.CertificateChain) < 1 { + return errors.New("certificate chain should at least have one certificate") + } + + // According to the RFC: https://tools.ietf.org/html/rfc8894#section-3.1, SCEP + // can be used with something different than RSA, but requires the encryption + // to be performed using the challenge password. An older version of specification + // states that only RSA is supported: https://tools.ietf.org/html/draft-nourse-scep-23#section-2.1.1 + // Other algorithms than RSA do not seem to be supported in certnanny/sscep, but it might work + // in micromdm/scep. Currently only RSA is allowed, but it might be an option + // to try other algorithms in the future. + intermediate := o.CertificateChain[0] + if intermediate.PublicKeyAlgorithm != x509.RSA { + return errors.New("only the RSA algorithm is (currently) supported") + } + + // TODO: add checks for key usage? + + signerPublicKey, ok := o.Signer.Public().(*rsa.PublicKey) + if !ok { + return errors.New("only RSA public keys are (currently) supported as signers") + } + + // check if the intermediate ca certificate has the same public key as the signer. + // According to the RFC it seems valid to have different keys for the intermediate + // and the CA signing new certificates, so this might change in the future. + if !signerPublicKey.Equal(intermediate.PublicKey) { + return errors.New("mismatch between certificate chain and signer public keys") + } + + decrypterPublicKey, ok := o.Decrypter.Public().(*rsa.PublicKey) + if !ok { + return errors.New("only RSA public keys are (currently) supported as decrypters") + } + + // check if intermedate public key is the same as the decrypter public key. + // In certnanny/sscep it's mentioned that the signing key can be different + // from the decrypting (and encrypting) key. Currently that's not supported. + if !decrypterPublicKey.Equal(intermediate.PublicKey) { + return errors.New("mismatch between certificate chain and decrypter public keys") + } + return nil } From bcacd2f4da407bd781c1af50230cd3aace7e54bd Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Mar 2021 15:24:27 +0100 Subject: [PATCH 52/89] Fix typo --- scep/options.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scep/options.go b/scep/options.go index a8aad659..752b309a 100644 --- a/scep/options.go +++ b/scep/options.go @@ -60,7 +60,7 @@ func (o *Options) Validate() error { return errors.New("only RSA public keys are (currently) supported as decrypters") } - // check if intermedate public key is the same as the decrypter public key. + // check if intermediate public key is the same as the decrypter public key. // In certnanny/sscep it's mentioned that the signing key can be different // from the decrypting (and encrypting) key. Currently that's not supported. if !decrypterPublicKey.Equal(intermediate.PublicKey) { From 13fe7a01213150a10c7b2d305b26a9b007db528b Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Mar 2021 15:44:45 +0100 Subject: [PATCH 53/89] Make serving SCEP endpoints optional Only when a SCEP provisioner is enabled, the SCEP endpoints will now be available. The SCEP endpoints will be served on an "insecure" server, without TLS, only when an additional "insecureAddress" and a SCEP provisioner are configured for the CA. --- authority/authority.go | 2 ++ ca/ca.go | 14 ++++++++------ scep/scep.go | 1 - 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/authority/authority.go b/authority/authority.go index a0eaf871..b5924061 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -575,6 +575,8 @@ func (a *Authority) CloseForReload() { // requiresDecrypter returns whether the Authority // requires a KMS that provides a crypto.Decrypter +// Currently this is only required when SCEP is +// enabled. func (a *Authority) requiresDecrypter() bool { return a.requiresSCEPService() } diff --git a/ca/ca.go b/ca/ca.go index 6eb223eb..4998b45b 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -118,6 +118,7 @@ func (ca *CA) Init(config *config.Config) (*CA, error) { if err != nil { return nil, err } + ca.auth = auth tlsConfig, err := ca.getTLSConfig(auth) if err != nil { @@ -233,14 +234,15 @@ func (ca *CA) Init(config *config.Config) (*CA, error) { handler = logger.Middleware(handler) } - ca.auth = auth ca.srv = server.New(config.Address, handler, tlsConfig) - // TODO: instead opt for having a single server.Server but two - // http.Servers handling the HTTP and HTTPS handler? The latter - // will probably introduce more complexity in terms of graceful - // reload. - if config.InsecureAddress != "" { + // only start the insecure server if the insecure address is configured + // and, currently, also only when it should serve SCEP endpoints. + if ca.shouldServeSCEPEndpoints() && config.InsecureAddress != "" { + // TODO: instead opt for having a single server.Server but two + // http.Servers handling the HTTP and HTTPS handler? The latter + // will probably introduce more complexity in terms of graceful + // reload. ca.insecureSrv = server.New(config.InsecureAddress, insecureHandler, nil) } diff --git a/scep/scep.go b/scep/scep.go index f56176d7..3323ac1d 100644 --- a/scep/scep.go +++ b/scep/scep.go @@ -33,7 +33,6 @@ var ( oidSCEPtransactionID = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 7} oidSCEPfailInfoText = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 24} //oidChallengePassword = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 7} - ) // PKIMessage defines the possible SCEP message types From 1cd0cb99f638fc20c6db494b935965ffbc6c353c Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Mar 2021 16:11:35 +0100 Subject: [PATCH 54/89] Add more template data --- scep/authority.go | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/scep/authority.go b/scep/authority.go index 9a0a2058..3443eb51 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -4,7 +4,6 @@ import ( "context" "crypto/subtle" "crypto/x509" - "fmt" "net/url" "github.com/smallstep/certificates/authority/provisioner" @@ -109,9 +108,9 @@ func (a *Authority) GetLinkExplicit(provName string, abs bool, baseURL *url.URL, // URL dynamically obtained from the request for which the link is being calculated. func (a *Authority) getLinkExplicit(provisionerName string, abs bool, baseURL *url.URL, inputs ...string) string { - // TODO: do we need to provide a way to provide a different suffix/base? + // TODO: do we need to provide a way to provide a different suffix? // Like "/cgi-bin/pkiclient.exe"? Or would it be enough to have that as the name? - link := fmt.Sprintf("/%s", provisionerName) + link := "/" + provisionerName if abs { // Copy the baseURL value from the pointer. https://github.com/golang/go/issues/38351 @@ -235,7 +234,31 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m } // Template data - data := x509util.CreateTemplateData(csr.Subject.CommonName, csr.DNSNames) + sans := []string{} + sans = append(sans, csr.DNSNames...) + sans = append(sans, csr.EmailAddresses...) + for _, v := range csr.IPAddresses { + sans = append(sans, v.String()) + } + for _, v := range csr.URIs { + sans = append(sans, v.String()) + } + if len(sans) == 0 { + sans = append(sans, csr.Subject.CommonName) + } + data := x509util.CreateTemplateData(csr.Subject.CommonName, sans) + data.SetCertificateRequest(csr) + data.SetSubject(x509util.Subject{ + Country: csr.Subject.Country, + Organization: csr.Subject.Organization, + OrganizationalUnit: csr.Subject.OrganizationalUnit, + Locality: csr.Subject.Locality, + Province: csr.Subject.Province, + StreetAddress: csr.Subject.StreetAddress, + PostalCode: csr.Subject.PostalCode, + SerialNumber: csr.Subject.SerialNumber, + CommonName: csr.Subject.CommonName, + }) // Get authorizations from the SCEP provisioner. ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod) From 03c472359c65a93fb1cf5c329d4b8495398d1088 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Mar 2021 16:21:02 +0100 Subject: [PATCH 55/89] Add sync.WaitGroup for proper error handling in Run() --- ca/ca.go | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/ca/ca.go b/ca/ca.go index 4998b45b..1882e8a6 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" "reflect" + "sync" "github.com/go-chi/chi" "github.com/pkg/errors" @@ -251,7 +252,29 @@ func (ca *CA) Init(config *config.Config) (*CA, error) { // Run starts the CA calling to the server ListenAndServe method. func (ca *CA) Run() error { - return ca.srv.ListenAndServe() + var wg sync.WaitGroup + errors := make(chan error, 1) + + if ca.insecureSrv != nil { + wg.Add(1) + go func() { + defer wg.Done() + errors <- ca.insecureSrv.ListenAndServe() + }() + } + + wg.Add(1) + go func() { + defer wg.Done() + errors <- ca.srv.ListenAndServe() + }() + + // wait till error occurs; ensures the servers keep listening + err := <-errors + + wg.Wait() + + return err } // Stop stops the CA calling to the server Shutdown method. From 66a67ed691948de8aa7cc90a23bc0e102e85263a Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Mar 2021 22:04:18 +0100 Subject: [PATCH 56/89] Update to v2.0.0 of github.com/micromdm/scep --- go.mod | 3 +- go.sum | 252 ++++++++++++++++++++++++++++++++++++++++++++++ scep/api/api.go | 2 +- scep/authority.go | 4 +- scep/scep.go | 2 +- 5 files changed, 258 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 5ccc3aa8..8a2fe4a1 100644 --- a/go.mod +++ b/go.mod @@ -8,12 +8,13 @@ require ( github.com/ThalesIgnite/crypto11 v1.2.4 github.com/aws/aws-sdk-go v1.30.29 github.com/go-chi/chi v4.0.2+incompatible + github.com/go-kit/kit v0.10.0 // indirect github.com/go-piv/piv-go v1.7.0 github.com/golang/mock v1.4.4 github.com/google/uuid v1.1.2 github.com/googleapis/gax-go/v2 v2.0.5 github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect - github.com/micromdm/scep v1.0.1-0.20210219005251-6027e7654b23 + github.com/micromdm/scep/v2 v2.0.0 github.com/newrelic/go-agent v2.15.0+incompatible github.com/pkg/errors v0.9.1 github.com/rs/xid v1.2.1 diff --git a/go.sum b/go.sum index 4883af5e..80b154e1 100644 --- a/go.sum +++ b/go.sum @@ -47,6 +47,7 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk= @@ -55,38 +56,69 @@ github.com/Masterminds/sprig/v3 v3.1.0 h1:j7GpgZ7PdFqNsmncycTHsLmVPf5/3wJtlgW9TN github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/ThalesIgnite/crypto11 v1.2.4 h1:3MebRK/U0mA2SmSthXAIZAdUA9w8+ZuKem2O6HuR1f8= github.com/ThalesIgnite/crypto11 v1.2.4/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.30.29 h1:NXNqBS9hjOCpDL8SyCyl38gZX3LLLunKOJc5E7vJ8P0= github.com/aws/aws-sdk-go v1.30.29/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible h1:bXhRBIXoTm9BYHS3gE0TtQuyNZyeEMux2sDi4oo5YOo= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -97,19 +129,30 @@ github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20201003150343-5d1bab4fc658/go.mod h github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd h1:KoJOtZf+6wpQaDTuOWGuo61GxcPBIfhwRxRTaTWGCTc= github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd/go.mod h1:YylP9MpCYGVZQrly/j/diqcdUetCRRePeBB0c2VGXsA= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4 h1:rEvIZUSZ3fx39WIi3JkQqQBitGwpELBIYWeBVh6wn+E= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs= github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= @@ -119,16 +162,31 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.4.0 h1:KeVK+Emj3c3S4eRztFuzbFYb2BAgf2jmwDwyXEri7Lo= github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-piv/piv-go v1.7.0 h1:rfjdFdASfGV5KLJhSjgpGJ5lzVZVtRWn8ovy/H9HQ/U= github.com/go-piv/piv-go v1.7.0/go.mod h1:ON2WvQncm7dIkCQ7kYJs+nc3V4jHGfrrJnSF8HKy7Gk= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.6.0 h1:MmJCxYVKTJ0SplGKqFVX3SBnmaUhODHZrrFF6jMbpZk= github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= @@ -156,6 +214,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw= github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -171,6 +230,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs= @@ -186,22 +246,51 @@ github.com/google/pprof v0.0.0-20201009210932-67992a1a5a35 h1:WL9iUw2tSwvaCb3++2 github.com/google/pprof v0.0.0-20201009210932-67992a1a5a35/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.4.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda/go.mod h1:MyndkAZd5rUMdNogn35MWXBX1UiBigrU8eTj8DoAC2c= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -209,13 +298,22 @@ github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -234,9 +332,12 @@ github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/manifoldco/promptui v0.8.0 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo= @@ -244,11 +345,13 @@ github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEX github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= <<<<<<< HEAD +<<<<<<< HEAD ======= github.com/micromdm/scep v1.0.1-0.20210219005251-6027e7654b23 h1:QACkVsQ7Qx4PuPDFL2OFD5u4OnYT0TkReWk9IVqaGHA= github.com/micromdm/scep v1.0.1-0.20210219005251-6027e7654b23/go.mod h1:a82oNZyGV8jj9Do7dh8EkA90+esBls0CZHR6B85Qda8= @@ -257,28 +360,95 @@ github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f h1:eVB9ELsoq5ouItQB github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/micromdm/scep v1.0.0 h1:ai//kcZnxZPq1YE/MatiE2bIRD94KOAwZRpN1fhVQXY= github.com/micromdm/scep v1.0.0/go.mod h1:CID2SixSr5FvoauZdAFUSpQkn5MAuSy9oyURMGOJbag= +======= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/micromdm/scep/v2 v2.0.0 h1:cRzcY0S5QX+0+J+7YC4P2uZSnfMup8S8zJu/bLFgOkA= +github.com/micromdm/scep/v2 v2.0.0/go.mod h1:ouaDs5tcjOjdHD/h8BGaQsWE87MUnQ/wMTMgfMMIpPc= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f h1:eVB9ELsoq5ouItQBr5Tj334bhPJG/MX+m7rTchmzVUQ= +github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +>>>>>>> c3d9cef (Update to v2.0.0 of github.com/micromdm/scep) github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/newrelic/go-agent v2.15.0+incompatible h1:IB0Fy+dClpBq9aEoIrLyQXzU34JyI1xVTanPLB/+jvU= github.com/newrelic/go-agent v2.15.0+incompatible/go.mod h1:a8Fv1b/fYhFSReoTU6HDkTYIMZeSVNffmoS726Y0LzQ= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568 h1:+MPqEswjYiS0S1FCTg8MIhMBMzxiVQ94rooFwvPPiWk= github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= @@ -287,10 +457,14 @@ github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNue github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189 h1:CmSpbxmewNQbzqztaY0bke1qzHhyNyC29wYgh17Gxfo= github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189/go.mod h1:UUwuHEJ9zkkPDxspIHOa59PUeSkGFljESGzbxntLmIg= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5/go.mod h1:TC9A4+RjIOS+HyTH7wG17/gSqVv95uDw2J64dQZx7RE= @@ -298,6 +472,10 @@ github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= github.com/smallstep/nosql v0.3.6 h1:cq6a3NwjFJxkVlWU1T4qGskcfEXr0fO1WqQrraDO1Po= github.com/smallstep/nosql v0.3.6/go.mod h1:h1zC/Z54uNHc8euquLED4qJNCrMHd3nytA141ZZh4qQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -306,14 +484,19 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -324,11 +507,15 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg= github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -336,10 +523,17 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +<<<<<<< HEAD go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +======= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +>>>>>>> c3d9cef (Update to v2.0.0 of github.com/micromdm/scep) go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -350,12 +544,27 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.step.sm/cli-utils v0.2.0 h1:hpVu9+6dpv/7/Bd8nGJFc3V+gQ+TciSJRTu9TavDUQ4= go.step.sm/cli-utils v0.2.0/go.mod h1:+t4qCp5NO+080DdGkJxEh3xL5S4TcYC2JTPLMM72b6Y= go.step.sm/crypto v0.6.1/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0= +<<<<<<< HEAD go.step.sm/crypto v0.8.3 h1:TO/OPlaUrYXhs8srGEFNyL6OWVQvRmEPCUONNnQUuEM= go.step.sm/crypto v0.8.3/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0= +======= +go.step.sm/crypto v0.8.0 h1:S4qBPyy3hR7KWLybSkHB0H14pwFfYkom4RZ96JzmXig= +go.step.sm/crypto v0.8.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +>>>>>>> c3d9cef (Update to v2.0.0 of github.com/micromdm/scep) golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -400,16 +609,24 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170726083632-f5079bd7f6f7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -445,7 +662,13 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170728174421-0f826bdd13b5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -459,9 +682,11 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -490,16 +715,20 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -509,6 +738,8 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -516,6 +747,7 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -542,6 +774,7 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -561,6 +794,7 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.33.0 h1:+gL0XvACeMIvpwLZ5rQZzLn5cwOsgg8dIcfJ2SYfBVw= google.golang.org/api v0.33.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= @@ -572,6 +806,7 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -599,10 +834,15 @@ google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154 h1:bFFRpT+e8JJVY7lMMfvezL1ZIwqiwmPl2bsE2yx4HqM= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -625,17 +865,27 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -650,3 +900,5 @@ rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/scep/api/api.go b/scep/api/api.go index cff3d32b..c19c090f 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -18,7 +18,7 @@ import ( "github.com/pkg/errors" - microscep "github.com/micromdm/scep/scep" + microscep "github.com/micromdm/scep/v2/scep" ) const ( diff --git a/scep/authority.go b/scep/authority.go index 3443eb51..34913a84 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -8,8 +8,8 @@ import ( "github.com/smallstep/certificates/authority/provisioner" - microx509util "github.com/micromdm/scep/crypto/x509util" - microscep "github.com/micromdm/scep/scep" + microx509util "github.com/micromdm/scep/v2/cryptoutil/x509util" + microscep "github.com/micromdm/scep/v2/scep" "github.com/pkg/errors" diff --git a/scep/scep.go b/scep/scep.go index 3323ac1d..afabf368 100644 --- a/scep/scep.go +++ b/scep/scep.go @@ -4,7 +4,7 @@ import ( "crypto/x509" "encoding/asn1" - microscep "github.com/micromdm/scep/scep" + microscep "github.com/micromdm/scep/v2/scep" //"github.com/smallstep/certificates/scep/pkcs7" From fa100a513823bafe925c77649f0df90f80f8f3fc Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 16 Apr 2021 14:09:34 +0200 Subject: [PATCH 57/89] Mask challenge password after it has been read --- authority/provisioner/scep.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 6af3dc83..7f3cce8f 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -20,6 +20,8 @@ type SCEP struct { Options *Options `json:"options,omitempty"` Claims *Claims `json:"claims,omitempty"` claimer *Claimer + + secretChallengePassword string } // GetID returns the provisioner unique identifier. @@ -73,6 +75,10 @@ func (s *SCEP) Init(config Config) (err error) { return err } + // Mask the actual challenge value, so it won't be marshalled + s.secretChallengePassword = s.ChallengePassword + s.ChallengePassword = "*** redacted ***" + // TODO: add other, SCEP specific, options? return err @@ -95,7 +101,7 @@ func (s *SCEP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e // GetChallengePassword returns the challenge password func (s *SCEP) GetChallengePassword() string { - return s.ChallengePassword + return s.secretChallengePassword } // GetCapabilities returns the CA capabilities From 4168449935dfe630e294855909e3a59786a2c504 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 16 Apr 2021 15:49:33 +0200 Subject: [PATCH 58/89] Fix typo --- authority/provisioner/scep.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 7f3cce8f..49f53860 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -75,7 +75,7 @@ func (s *SCEP) Init(config Config) (err error) { return err } - // Mask the actual challenge value, so it won't be marshalled + // Mask the actual challenge value, so it won't be marshaled s.secretChallengePassword = s.ChallengePassword s.ChallengePassword = "*** redacted ***" From 2beea1aa89ecbc4a9c6a300d8ffbada98faf0d4d Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Thu, 6 May 2021 22:56:28 +0200 Subject: [PATCH 59/89] Add configuration option for specifying the minimum public key length Instead of using the defaultPublicKeyValidator a new validator called publicKeyMinimumLengthValidator has been implemented that uses a configurable minimum length for public keys in CSRs. It's also an option to alter the defaultPublicKeyValidator to also take a parameter, but that would touch quite some lines of code. This might be a viable option after merging SCEP support. --- authority/provisioner/scep.go | 20 +++++++++++++---- authority/provisioner/sign_options.go | 31 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 49f53860..a5825b9b 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -2,6 +2,7 @@ package provisioner import ( "context" + "fmt" "time" "github.com/pkg/errors" @@ -17,9 +18,11 @@ type SCEP struct { ForceCN bool `json:"forceCN,omitempty"` ChallengePassword string `json:"challenge,omitempty"` Capabilities []string `json:"capabilities,omitempty"` - Options *Options `json:"options,omitempty"` - Claims *Claims `json:"claims,omitempty"` - claimer *Claimer + // MinimumPublicKeyLength is the minimum length for public keys in CSRs + MinimumPublicKeyLength int `json:"minimumPublicKeyLength,omitempty"` + Options *Options `json:"options,omitempty"` + Claims *Claims `json:"claims,omitempty"` + claimer *Claimer secretChallengePassword string } @@ -79,6 +82,15 @@ func (s *SCEP) Init(config Config) (err error) { s.secretChallengePassword = s.ChallengePassword s.ChallengePassword = "*** redacted ***" + // Default to 2048 bits minimum public key length (for CSRs) if not set + if s.MinimumPublicKeyLength == 0 { + s.MinimumPublicKeyLength = 2048 + } + + if s.MinimumPublicKeyLength%8 != 0 { + return fmt.Errorf("only minimum public keys exactly divisible by 8 are supported; %d is not exactly divisibly by 8", s.MinimumPublicKeyLength) + } + // TODO: add other, SCEP specific, options? return err @@ -94,7 +106,7 @@ func (s *SCEP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e newForceCNOption(s.ForceCN), profileDefaultDuration(s.claimer.DefaultTLSCertDuration()), // validators - defaultPublicKeyValidator{}, + newPublicKeyMinimumLengthValidator(s.MinimumPublicKeyLength), newValidityValidator(s.claimer.MinTLSCertDuration(), s.claimer.MaxTLSCertDuration()), }, nil } diff --git a/authority/provisioner/sign_options.go b/authority/provisioner/sign_options.go index 3b52d497..20b6f8c0 100644 --- a/authority/provisioner/sign_options.go +++ b/authority/provisioner/sign_options.go @@ -8,6 +8,7 @@ import ( "crypto/x509/pkix" "encoding/asn1" "encoding/json" + "fmt" "net" "net/url" "reflect" @@ -117,6 +118,36 @@ func (v defaultPublicKeyValidator) Valid(req *x509.CertificateRequest) error { return nil } +// publicKeyMinimumLengthValidator validates the length (in bits) of the public key +// of a certificate request is at least a certain length +type publicKeyMinimumLengthValidator struct { + length int +} + +// newPublicKeyMinimumLengthValidator creates a new publicKeyMinimumLengthValidator +// with the given length as its minimum value +// TODO: change the defaultPublicKeyValidator to have a configurable length instead? +func newPublicKeyMinimumLengthValidator(length int) publicKeyMinimumLengthValidator { + return publicKeyMinimumLengthValidator{ + length: length, + } +} + +// Valid checks that certificate request common name matches the one configured. +func (v publicKeyMinimumLengthValidator) Valid(req *x509.CertificateRequest) error { + switch k := req.PublicKey.(type) { + case *rsa.PublicKey: + minimumLengthInBytes := v.length / 8 + if k.Size() < minimumLengthInBytes { + return fmt.Errorf("rsa key in CSR must be at least %d bits (%d bytes)", v.length, minimumLengthInBytes) + } + case *ecdsa.PublicKey, ed25519.PublicKey: + default: + return errors.Errorf("unrecognized public key of type '%T' in CSR", k) + } + return nil +} + // commonNameValidator validates the common name of a certificate request. type commonNameValidator string From d46a4eaca4a33c49bc5468a0dda6419a4cbdfbff Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 7 May 2021 00:22:06 +0200 Subject: [PATCH 60/89] Change fmt to errors package for formatting errors --- authority/provisioner/scep.go | 3 +-- authority/provisioner/sign_options.go | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index a5825b9b..bedf1733 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -2,7 +2,6 @@ package provisioner import ( "context" - "fmt" "time" "github.com/pkg/errors" @@ -88,7 +87,7 @@ func (s *SCEP) Init(config Config) (err error) { } if s.MinimumPublicKeyLength%8 != 0 { - return fmt.Errorf("only minimum public keys exactly divisible by 8 are supported; %d is not exactly divisibly by 8", s.MinimumPublicKeyLength) + return errors.Errorf("only minimum public keys exactly divisible by 8 are supported; %d is not exactly divisibly by 8", s.MinimumPublicKeyLength) } // TODO: add other, SCEP specific, options? diff --git a/authority/provisioner/sign_options.go b/authority/provisioner/sign_options.go index 20b6f8c0..764916b6 100644 --- a/authority/provisioner/sign_options.go +++ b/authority/provisioner/sign_options.go @@ -8,7 +8,6 @@ import ( "crypto/x509/pkix" "encoding/asn1" "encoding/json" - "fmt" "net" "net/url" "reflect" @@ -139,7 +138,7 @@ func (v publicKeyMinimumLengthValidator) Valid(req *x509.CertificateRequest) err case *rsa.PublicKey: minimumLengthInBytes := v.length / 8 if k.Size() < minimumLengthInBytes { - return fmt.Errorf("rsa key in CSR must be at least %d bits (%d bytes)", v.length, minimumLengthInBytes) + return errors.Errorf("rsa key in CSR must be at least %d bits (%d bytes)", v.length, minimumLengthInBytes) } case *ecdsa.PublicKey, ed25519.PublicKey: default: From 382b6f977cb4b3ba9ea407c6b554000efb972f70 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 7 May 2021 00:23:09 +0200 Subject: [PATCH 61/89] Improve error logging --- scep/api/api.go | 28 +++++++++++++++++----------- scep/authority.go | 2 +- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/scep/api/api.go b/scep/api/api.go index c19c090f..e64eef83 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -51,6 +51,7 @@ type SCEPResponse struct { CACertNum int Data []byte Certificate *x509.Certificate + Error error } // Handler is the SCEP request handler. @@ -75,7 +76,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { request, err := decodeSCEPRequest(r) if err != nil { - writeError(w, errors.Wrap(err, "not a scep get request")) + writeError(w, errors.Wrap(err, "invalid scep get request")) return } @@ -94,7 +95,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { } if err != nil { - writeError(w, errors.Wrap(err, "get request failed")) + writeError(w, errors.Wrap(err, "scep get request failed")) return } @@ -106,7 +107,7 @@ func (h *Handler) Post(w http.ResponseWriter, r *http.Request) { request, err := decodeSCEPRequest(r) if err != nil { - writeError(w, errors.Wrap(err, "not a scep post request")) + writeError(w, errors.Wrap(err, "invalid scep post request")) return } @@ -121,7 +122,7 @@ func (h *Handler) Post(w http.ResponseWriter, r *http.Request) { } if err != nil { - writeError(w, errors.Wrap(err, "post request failed")) + writeError(w, errors.Wrap(err, "scep post request failed")) return } @@ -193,13 +194,13 @@ func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP { p, err := h.Auth.LoadProvisionerByID("scep/" + provisionerID) if err != nil { - writeError(w, err) + api.WriteError(w, err) return } provisioner, ok := p.(*provisioner.SCEP) if !ok { - writeError(w, errors.New("provisioner must be of type SCEP")) + api.WriteError(w, errors.New("provisioner must be of type SCEP")) return } @@ -293,12 +294,12 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe challengeMatches, err := h.Auth.MatchChallengePassword(ctx, msg.CSRReqMessage.ChallengePassword) if err != nil { - return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, "error when checking password") + return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, errors.New("error when checking password")) } if !challengeMatches { // TODO: can this be returned safely to the client? In the end, if the password was correct, that gains a bit of info too. - return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, "wrong password provided") + return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, errors.New("wrong password provided")) } } @@ -306,7 +307,7 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe certRep, err := h.Auth.SignCSR(ctx, csr, msg) if err != nil { - return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, "error when signing new certificate") + return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, errors.Wrap(err, "error when signing new certificate")) } response := SCEPResponse{ @@ -325,6 +326,10 @@ func formatCapabilities(caps []string) []byte { // writeSCEPResponse writes a SCEP response back to the SCEP client. func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) { + if response.Error != nil { + api.LogError(w, response.Error) + } + if response.Certificate != nil { api.LogCertificate(w, response.Certificate) } @@ -344,14 +349,15 @@ func writeError(w http.ResponseWriter, err error) { api.WriteError(w, scepError) } -func (h *Handler) createFailureResponse(ctx context.Context, csr *x509.CertificateRequest, msg *scep.PKIMessage, info microscep.FailInfo, infoText string) (SCEPResponse, error) { - certRepMsg, err := h.Auth.CreateFailureResponse(ctx, csr, msg, scep.FailInfoName(info), infoText) +func (h *Handler) createFailureResponse(ctx context.Context, csr *x509.CertificateRequest, msg *scep.PKIMessage, info microscep.FailInfo, failError error) (SCEPResponse, error) { + certRepMsg, err := h.Auth.CreateFailureResponse(ctx, csr, msg, scep.FailInfoName(info), failError.Error()) if err != nil { return SCEPResponse{}, err } return SCEPResponse{ Operation: opnPKIOperation, Data: certRepMsg.Raw, + Error: failError, }, nil } diff --git a/scep/authority.go b/scep/authority.go index 34913a84..47d1d41c 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -416,7 +416,7 @@ func (a *Authority) CreateFailureResponse(ctx context.Context, csr *x509.Certifi cr := &CertRepMessage{ PKIStatus: microscep.FAILURE, - FailInfo: microscep.BadRequest, + FailInfo: microscep.FailInfo(info), RecipientNonce: microscep.RecipientNonce(msg.SenderNonce), } From a64974c1796d938efb71c9221a1b58bbb5e0722e Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 7 May 2021 00:31:34 +0200 Subject: [PATCH 62/89] Fix small typo in divisible --- authority/provisioner/scep.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index bedf1733..7673ecc2 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -87,7 +87,7 @@ func (s *SCEP) Init(config Config) (err error) { } if s.MinimumPublicKeyLength%8 != 0 { - return errors.Errorf("only minimum public keys exactly divisible by 8 are supported; %d is not exactly divisibly by 8", s.MinimumPublicKeyLength) + return errors.Errorf("only minimum public keys exactly divisible by 8 are supported; %d is not exactly divisible by 8", s.MinimumPublicKeyLength) } // TODO: add other, SCEP specific, options? From 74d8bdc298a8578baa63be3bb83165107190c613 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 7 May 2021 15:32:07 +0200 Subject: [PATCH 63/89] Add tests for CreateDecrypter --- kms/softkms/softkms_test.go | 69 +++++++++++++++++++++++++++++++ kms/softkms/testdata/rsa.priv.pem | 30 ++++++++++++++ kms/softkms/testdata/rsa.pub.pem | 9 ++++ 3 files changed, 108 insertions(+) create mode 100644 kms/softkms/testdata/rsa.priv.pem create mode 100644 kms/softkms/testdata/rsa.pub.pem diff --git a/kms/softkms/softkms_test.go b/kms/softkms/softkms_test.go index 607a5a51..9e293b07 100644 --- a/kms/softkms/softkms_test.go +++ b/kms/softkms/softkms_test.go @@ -310,3 +310,72 @@ func Test_generateKey(t *testing.T) { }) } } + +func TestSoftKMS_CreateDecrypter(t *testing.T) { + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatal(err) + } + pemBlock, err := pemutil.Serialize(privateKey) + if err != nil { + t.Fatal(err) + } + pemBlockPassword, err := pemutil.Serialize(privateKey, pemutil.WithPassword([]byte("pass"))) + if err != nil { + t.Fatal(err) + } + ecdsaPK, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + ecdsaPemBlock, err := pemutil.Serialize(ecdsaPK) + if err != nil { + t.Fatal(err) + } + b, err := ioutil.ReadFile("testdata/rsa.priv.pem") + if err != nil { + t.Fatal(err) + } + block, _ := pem.Decode(b) + block.Bytes, err = x509.DecryptPEMBlock(block, []byte("pass")) //nolint + if err != nil { + t.Fatal(err) + } + keyFromFile, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + t.Fatal(err) + } + type args struct { + req *apiv1.CreateDecrypterRequest + } + tests := []struct { + name string + args args + want crypto.Decrypter + wantErr bool + }{ + {"decrypter", args{&apiv1.CreateDecrypterRequest{Decrypter: privateKey}}, privateKey, false}, + {"file", args{&apiv1.CreateDecrypterRequest{DecryptionKey: "testdata/rsa.priv.pem", Password: []byte("pass")}}, keyFromFile, false}, + {"pem", args{&apiv1.CreateDecrypterRequest{DecryptionKeyPEM: pem.EncodeToMemory(pemBlock)}}, privateKey, false}, + {"pem password", args{&apiv1.CreateDecrypterRequest{DecryptionKeyPEM: pem.EncodeToMemory(pemBlockPassword), Password: []byte("pass")}}, privateKey, false}, + {"fail none", args{&apiv1.CreateDecrypterRequest{}}, nil, true}, + {"fail missing", args{&apiv1.CreateDecrypterRequest{DecryptionKey: "testdata/missing"}}, nil, true}, + {"fail bad pem", args{&apiv1.CreateDecrypterRequest{DecryptionKeyPEM: []byte("bad pem")}}, nil, true}, + {"fail bad password", args{&apiv1.CreateDecrypterRequest{DecryptionKeyPEM: pem.EncodeToMemory(pemBlockPassword), Password: []byte("bad-pass")}}, nil, true}, + {"fail not a decrypter (ecdsa key)", args{&apiv1.CreateDecrypterRequest{DecryptionKeyPEM: pem.EncodeToMemory(ecdsaPemBlock)}}, nil, true}, + {"fail not a decrypter from file", args{&apiv1.CreateDecrypterRequest{DecryptionKey: "testdata/rsa.pub.pem"}}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + k := &SoftKMS{} + got, err := k.CreateDecrypter(tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("SoftKMS.CreateDecrypter(), error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SoftKMS.CreateDecrypter() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/kms/softkms/testdata/rsa.priv.pem b/kms/softkms/testdata/rsa.priv.pem new file mode 100644 index 00000000..497ec8d2 --- /dev/null +++ b/kms/softkms/testdata/rsa.priv.pem @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,dff7bfd0e0163a4cd7ade8f68b966699 + +jtmOhr2zo244Oq2fVsShZAUoQZ1gi6Iwc4i0sReU66XP9CFkdvJasAfkjQGrbCEy +m2+r7W6aH+L3j/4sXcJe8h4UVnnC4DHCozmtqqFCq7cFS4TiVpco26wEVH5WLm7Y +3Ew/pL0k24E+Ycf+yV5c1tQXRlmsKubjwzrZtGZP2yn3Dxsu97mzOXAfx7r+DIKI +5a4S3m1/yXw76tt6Iho9h4huA25UUDHKUQvOGd5gmOKqJRV9djoyu85ODbmz5nt0 +pB2EzdHOrefgd0rcQQPI1uFBWqASJxTn+uS7ZBP4rlCcs932lI1mPerMh1ujo51F +3aibrwhKE6kaJyOOnUbvyBnaiTb5i4WwTqx/jfsOsggXQb3UlxgDph48VXw8O2jF +CQmle+TR8yr1A14/Dno5Dd4cqPv6AmWWU2zolvLxKQixFcvjsyQYCDajWWRPkOgj +RTKXDqL1mpjrlDqcSXzemCWk6FzqdUQhimhFgARDRfRwwDeWQN5ua4a3gnem/cpA +ZS8J45H0ZC/CxGPfp+qx75n5a875+n4VMmCZerXPzEIj1CzS7D6BVAXTHJaNIB6S +0WNfQnftp09O2l6iXBE+MHt5bVxqt46+vgcceSu7Gsb3ZfD79vnQ7tR+wb+xmHKk +8rVcMrB+kDRXVguH/a3zUGYAEnb6hPkIJywJVD4G65oM+D9D67Mdka8wIMK48doV +my8a0MfT/9AidR6XJVxIkHlPsPzlxirm/NKF7oSlzurcvYcPAYnHYLW2uB8dyidq +1zB+3rxbSYCVqrhqzN4prydGvkIE3/+AJyIGn7uGSTSSyF6BC9APXQaHplRGKwLz +efOIMoEwXJ1DIcKmk9GB65xxrZxMu3Cclcbc4PgY4370G0PfCHuUQNQL2RUWCQn0 +aax+qDiFg1LsLRaI75OaLJ+uKs6rRfytQMmFGqK/b6iVbktiYWMtrDJDo4OUTtZ6 +LBBySH7sAFgI3IIxct2Fwg8X1J4kfHr9jWTLjMEIE2o8cyqvSQ8rdwA25MxRcn75 +DGqSlGE6Sx0XhWCVUiZidVRSYGKmOmH9yw8cjKm17qL23t8Gwns4Xunl7V6YlTCG +BPw5f1jWCQ94TwvUSuHMPYoXlYwRoe+jfDAzp2AQwXqvWX5Qno5PKz9gQ5iYacZ/ +k82fyPbk2XLDkPnaNJKnyiIc252O0WffUlX6Rlv3aF8ZgVvWfZbuHEK6g1W+IKSA +pXAQ+iZBl+fjs/wT0yZSNTB0P1InD9Ve536L94gxXoeMr6F0Eouk3J2R9qdFp0Av +31xylRKSmzUf87/sRxjy3FzSTjIal77y1euJoAEU/nShmNrAZ6B8wnlvHfVwbgmt +xWqxYIi/j/C8Led9uhEhX2WjPsO7ckGA41Tw6hZk/5hr4jmPoZQKHf9OauJFujMh +ybPRQ6SGZJaYQAgpEGHSHFm8lwf5/DcezdSMdzqAKBWJBv6MediMuS60wcJ0Tebk +rdLkNE4bsxfc889BkXBrSqfd+Auu5RcF/kF44gLL7oj4ojQyV44vLZbC4+liGThT +bhayYGV64hsY+zL03u5wVfF1Y+33/uc8o/0JjbfuW5AIdikVES/jnKKFXSTMNL69 +-----END RSA PRIVATE KEY----- diff --git a/kms/softkms/testdata/rsa.pub.pem b/kms/softkms/testdata/rsa.pub.pem new file mode 100644 index 00000000..4bf5de9b --- /dev/null +++ b/kms/softkms/testdata/rsa.pub.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn2Oh7/uWB5RH40la1a43 +IRaLZ8EnJVw5DCKE3BUre8xflVY2wTIS7XHcY0fEGprtq7hzFKors9AIGGn2yGrf +bZX2I+1g+RtQ6cLL6koeLuhRDqCuae0lZPulWc5ixBmM9mpl4ARRcpQFldxFRhis +xUaHMx8VqdZjFSDc5CJHYYK1n2G5DyuzJCk6yOfyMpwxizZJF4IUyqV7zKmZv1z9 +/Xd8X0ag7jRdaTBpupJ1WLaq7LlvyB4nr47JXXkLFbRIL1F/gTcPtg0tdEZiKnxs +VLKwOs3VjhEorUwhmVxr4NnNX/0tuOY1FJ0mx5jKLAevqLVwK2JIg/f3h7JcNxDy +tQIDAQAB +-----END PUBLIC KEY----- From 7e82bd6ef394490c48dfb77527003b688fbd375f Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Thu, 20 May 2021 21:31:52 +0200 Subject: [PATCH 64/89] Add setup for Authority tests --- authority/authority_test.go | 148 +++++++++++++++++++++++ authority/testdata/scep/intermediate.crt | 15 +++ authority/testdata/scep/intermediate.key | 30 +++++ authority/testdata/scep/root.crt | 10 ++ authority/testdata/scep/root.key | 8 ++ 5 files changed, 211 insertions(+) create mode 100644 authority/testdata/scep/intermediate.crt create mode 100644 authority/testdata/scep/intermediate.key create mode 100644 authority/testdata/scep/root.crt create mode 100644 authority/testdata/scep/root.key diff --git a/authority/authority_test.go b/authority/authority_test.go index e6625d6a..618e7939 100644 --- a/authority/authority_test.go +++ b/authority/authority_test.go @@ -6,6 +6,7 @@ import ( "crypto/sha256" "crypto/x509" "encoding/hex" + "fmt" "io/ioutil" "net" "reflect" @@ -320,3 +321,150 @@ func TestAuthority_CloseForReload(t *testing.T) { }) } } + +func testScepAuthority(t *testing.T, opts ...Option) *Authority { + + p := provisioner.List{ + &provisioner.SCEP{ + Name: "scep1", + Type: "SCEP", + }, + } + c := &Config{ + Address: "127.0.0.1:8443", + InsecureAddress: "127.0.0.1:8080", + Root: []string{"testdata/scep/root.crt"}, + IntermediateCert: "testdata/scep/intermediate.crt", + IntermediateKey: "testdata/scep/intermediate.key", + DNSNames: []string{"example.com"}, + Password: "pass", + AuthorityConfig: &AuthConfig{ + Provisioners: p, + }, + } + a, err := New(c, opts...) + assert.FatalError(t, err) + return a +} + +func TestAuthority_GetSCEPService(t *testing.T) { + auth := testScepAuthority(t) + fmt.Println(auth) + + p := provisioner.List{ + &provisioner.SCEP{ + Name: "scep1", + Type: "SCEP", + }, + } + + type fields struct { + config *Config + // keyManager kms.KeyManager + // provisioners *provisioner.Collection + // db db.AuthDB + // templates *templates.Templates + // x509CAService cas.CertificateAuthorityService + // rootX509Certs []*x509.Certificate + // federatedX509Certs []*x509.Certificate + // certificates *sync.Map + // scepService *scep.Service + // sshCAUserCertSignKey ssh.Signer + // sshCAHostCertSignKey ssh.Signer + // sshCAUserCerts []ssh.PublicKey + // sshCAHostCerts []ssh.PublicKey + // sshCAUserFederatedCerts []ssh.PublicKey + // sshCAHostFederatedCerts []ssh.PublicKey + // initOnce bool + // startTime time.Time + // sshBastionFunc func(ctx context.Context, user, hostname string) (*Bastion, error) + // sshCheckHostFunc func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, error) + // sshGetHostsFunc func(ctx context.Context, cert *x509.Certificate) ([]Host, error) + // getIdentityFunc provisioner.GetIdentityFunc + } + tests := []struct { + name string + fields fields + wantService bool + wantErr bool + }{ + { + name: "ok", + fields: fields{ + config: &Config{ + Address: "127.0.0.1:8443", + InsecureAddress: "127.0.0.1:8080", + Root: []string{"testdata/scep/root.crt"}, + IntermediateCert: "testdata/scep/intermediate.crt", + IntermediateKey: "testdata/scep/intermediate.key", + DNSNames: []string{"example.com"}, + Password: "pass", + AuthorityConfig: &AuthConfig{ + Provisioners: p, + }, + }, + }, + wantService: true, + wantErr: false, + }, + { + name: "wrong password", + fields: fields{ + config: &Config{ + Address: "127.0.0.1:8443", + InsecureAddress: "127.0.0.1:8080", + Root: []string{"testdata/scep/root.crt"}, + IntermediateCert: "testdata/scep/intermediate.crt", + IntermediateKey: "testdata/scep/intermediate.key", + DNSNames: []string{"example.com"}, + Password: "wrongpass", + AuthorityConfig: &AuthConfig{ + Provisioners: p, + }, + }, + }, + wantService: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // a := &Authority{ + // config: tt.fields.config, + // keyManager: tt.fields.keyManager, + // provisioners: tt.fields.provisioners, + // db: tt.fields.db, + // templates: tt.fields.templates, + // x509CAService: tt.fields.x509CAService, + // rootX509Certs: tt.fields.rootX509Certs, + // federatedX509Certs: tt.fields.federatedX509Certs, + // certificates: tt.fields.certificates, + // scepService: tt.fields.scepService, + // sshCAUserCertSignKey: tt.fields.sshCAUserCertSignKey, + // sshCAHostCertSignKey: tt.fields.sshCAHostCertSignKey, + // sshCAUserCerts: tt.fields.sshCAUserCerts, + // sshCAHostCerts: tt.fields.sshCAHostCerts, + // sshCAUserFederatedCerts: tt.fields.sshCAUserFederatedCerts, + // sshCAHostFederatedCerts: tt.fields.sshCAHostFederatedCerts, + // initOnce: tt.fields.initOnce, + // startTime: tt.fields.startTime, + // sshBastionFunc: tt.fields.sshBastionFunc, + // sshCheckHostFunc: tt.fields.sshCheckHostFunc, + // sshGetHostsFunc: tt.fields.sshGetHostsFunc, + // getIdentityFunc: tt.fields.getIdentityFunc, + // } + a, err := New(tt.fields.config) + fmt.Println(err) + fmt.Println(a) + if (err != nil) != tt.wantErr { + t.Errorf("Authority.New(), error = %v, wantErr %v", err, tt.wantErr) + return + } + if tt.wantService { + if got := a.GetSCEPService(); (got != nil) != tt.wantService { + t.Errorf("Authority.GetSCEPService() = %v, wantService %v", got, tt.wantService) + } + } + }) + } +} diff --git a/authority/testdata/scep/intermediate.crt b/authority/testdata/scep/intermediate.crt new file mode 100644 index 00000000..42ac4867 --- /dev/null +++ b/authority/testdata/scep/intermediate.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICZTCCAgugAwIBAgIQDPpOQXW7OLMFNR/+iOUdQjAKBggqhkjOPQQDAjAXMRUw +EwYDVQQDEwxzY2VwdGVzdHJvb3QwHhcNMjEwNTA3MTUyMjU2WhcNMzEwNTA1MTUy +MjU2WjAfMR0wGwYDVQQDExRzY2VwdGVzdGludGVybWVkaWF0ZTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJTw49z9/MeZ/YeRO89ylMV3HnYpw52/Vs2G +NsgYZRKiPz2RjixUp1iWRPoDONdlEOIAo0TALNOqz4EqJHB+FpBPBA1ZfwG/PlP/ +eWFubNXLXIhZPSQOiHmL4dIw0FS/VFGZm1eqc9JPG/V2G6UaKvOa8+W9/nhi4eeL ++/9nTwG4cTav9ltaVxQ55kcoJtMcvouYQ4oPSZ6yNuVYbFAoaqZnJqNQhxDvKsFH +lHmvl28FAVM+otmEQNTm91uPwXuVusxEGn9N/d7M4iojCiMGg0S3luBS8IrGRI1Y +bSKZvGsFnqUjHh2cLL1lqqo5+QvhvP9ut6+g8QGoq8NTc2yCRy8CAwEAAaNmMGQw +DgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGfO +jTNTKTAyra+rAd/NL2ydarSFMB8GA1UdIwQYMBaAFKJr1p5QRfkHzewG3YEhPAtv +FQNrMAoGCCqGSM49BAMCA0gAMEUCIEYK76FN9a/hWkMZcQ+NXyzGtfW+bnwsX3oN +wT6jfyO0AiEAojTeSwf/H2l/E1lvsWJfNr8nOokWz+ZsbmMm5PU0Y+g= +-----END CERTIFICATE----- diff --git a/authority/testdata/scep/intermediate.key b/authority/testdata/scep/intermediate.key new file mode 100644 index 00000000..ae564056 --- /dev/null +++ b/authority/testdata/scep/intermediate.key @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,a54ae9388ce050f0a479a258d105fbb7 + +VkJp9kKZQ7O9Gy9orvXaO+klt4Lrqp9oSABSBy8yFcc3neniLixqcyZZ4+CC/OG2 +TGTm4TiB9RBucrUyPwoxBraWbtTLHvS4nfPwr2feSTKoHDhSIr4Z1VMDF8PWiOSg +vD3iYs5F1lz78hcB/SNdSZ2jm0ze84DFC2E49agWeiFLwezcLhXKQ2HHRJ6PmJv7 +IYB7+aLw8cUis/eJquWv7vrmlnshXBXLOrDekNq/mGhdpUmguDNEGX/3yT+8QYRv +yeCqLVWcfkQ7KkXAeet0tVPNGQQF0+yS80Hv2/LBcskhL467qa79Xm+QPbBbhsEB +aa4rettMLEdxk3IB1dgXdWhdJ4zBD+RFjczJbQlZRfmPb8sR20V/xp3x9i+SLqKp +seVoNF+LhLhEwJdMF23t2KpuiOShzC60ApjALN6/O2/XGCl0KQ+NzucX+wpirS6z +d2XfEYpsUaUFEFraOwfGXxLmluRtS6Q3+0+NPgwVQuH7EE7KuoTDUoSrUG4OFjaq +CeUeZv1IVf0sYqZQVRiMxxdoFBKUSgcaR1gzzLZgHeoZCGP0PewmZDfJMQ5rWe0D +zYYIKXUg8+oytHsz+5pQ277psXsl7iApZu56s6w3rD45w/zBeEyBhyL5JMBP8Y6y +7ReaUGsoFu3WEvrMcOsN+0Vag/SdQsvEH0PGA/ltlrlhaHKq+4t/ZwP6WxUmnaVV +JNtTWB8IqxtO0zbwK1owxjrO7t42K2isSryg/y2sQb4wgokoOzg1PqEaM8PIUvjl +qkGhwrOz4lNNQ9b6Hgy81DpnXnJkRNY7B5yKi62TCc6K/DHrFs0fHKb9Qxac5KKf +paasGWuEC5IP0lUyn81BmAVlfByBvnGmYiDmmGXLmfsyqtGFL9fpOl1Txq3/URfT +f705lzeUt9r2BT5FJtV5lkTntRzjpi5QeRiJsvfXA7nCPZj2hoLWgIm/D/HRgfVR +PIX1M7nxefRgES+T6UJNsBbGjSTgEVIPqVnyWs0JUyg4+KQ5VMU8g8SGA0dtnJyF +9JrZHy2OA/AYt/c96vJj4WdFvqw3kodIKOipBbKjBBGokaOTsLADFEYgOr51BfvO +QmxGZoXsRpD4sBOAwW039Ka5uCfuBETa+XQPtlHailaRZLlK9cZaDlzQr/K9jAgM +qOmZIKr3L8YPK3mQV+mWVYchPXTf+UyTFiWIt30z1JlyrTw1H+h62pV9f1QXDB6P +FIlfWHUK2mohWqzBnv4zFRBTVUnUDC9ONT+cVLh0cvlbRt2yy2ZgR4+d6IGH6mRH +VLgWAFpS3KS1/4NfwWRBaMvIBfqfXCzXSqVJsq7RlBSW/EBwe9TDXhcTzOLHjx4E +vdp+hqyXT62cTd7oWe78BBw3xOgpQwQ8bUdhye0kXMLNpU9j70pA7CjLVoVsdzH6 +n1EG7Mz/5NmXLy7LP8RuVU90mNQzNu8PFWtfjZ/jr3/OxoOc0Wx6mFykXkZbxKXI +xOlaOnUHKnEmsCLnZUkIxEqwKo+RYWBRtKxYsS8x8TLXyFGEfHidI75ulZM7eAS8 +jWtVNKbPIyal+nQMpqa/lKW6fiGGUVp0u2x3Pnd8luRCs2htBmXSB7W7mJ2SMCui +-----END RSA PRIVATE KEY----- diff --git a/authority/testdata/scep/root.crt b/authority/testdata/scep/root.crt new file mode 100644 index 00000000..58f9820d --- /dev/null +++ b/authority/testdata/scep/root.crt @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBczCCARigAwIBAgIRAImbSwfqrrI6p72t0b9f6l4wCgYIKoZIzj0EAwIwFzEV +MBMGA1UEAxMMc2NlcHRlc3Ryb290MB4XDTIxMDUwNzE1MjEzMFoXDTMxMDUwNTE1 +MjEzMFowFzEVMBMGA1UEAxMMc2NlcHRlc3Ryb290MFkwEwYHKoZIzj0CAQYIKoZI +zj0DAQcDQgAE3fyAgJsDICrnXhhoxHKmXMHLoW0EM9bYiBmx1xRyol0Qa3SZMW43 +rtTykqVP3HUA3rIrLdX106s9IFcA3eIYiaNFMEMwDgYDVR0PAQH/BAQDAgEGMBIG +A1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFKJr1p5QRfkHzewG3YEhPAtvFQNr +MAoGCCqGSM49BAMCA0kAMEYCIQDlXU695zKmSSfVPaPbM2cx7OlKr2n6NSyifatH +9zDITwIhAJUbbHzRJVgscxx+VSMqC2TkFvug6ryNu6kQIKNRwolr +-----END CERTIFICATE----- diff --git a/authority/testdata/scep/root.key b/authority/testdata/scep/root.key new file mode 100644 index 00000000..7dd4582b --- /dev/null +++ b/authority/testdata/scep/root.key @@ -0,0 +1,8 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,0ea78864d21de199d3a737e4337589c2 + +ZD3ggzw3eDYJp8NovTWgTxk6MagLutgU2UfwbYliAl7wKvVyzwkPytwRkyAXPBM6 +jMfiAdq6wY2wEpc8OSfrvAXrGuYqlCakDhdMaFDPcS3K29VLl4BaO2X2Rfk55nBd +ASBNREKVb+hg2HV22DO7r6t+EYXTSD6iO7EB90bvKdE= +-----END EC PRIVATE KEY----- From 6d9710c88d9798727da91545dcd54bc4d6b4c6ef Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 28 May 2021 16:40:46 +0200 Subject: [PATCH 65/89] Add initial support for ACME IP validation --- acme/api/order.go | 31 +++++--- acme/api/order_test.go | 113 ++++++++++++++++++++++++++ acme/order.go | 176 +++++++++++++++++++++++++++++++++-------- acme/order_test.go | 77 ++++++++++++++++++ 4 files changed, 354 insertions(+), 43 deletions(-) diff --git a/acme/api/order.go b/acme/api/order.go index 9d410173..b0b9cb43 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -28,7 +28,7 @@ func (n *NewOrderRequest) Validate() error { return acme.NewError(acme.ErrorMalformedType, "identifiers list cannot be empty") } for _, id := range n.Identifiers { - if id.Type != "dns" { + if !(id.Type == "dns" || id.Type == "ip") { return acme.NewError(acme.ErrorMalformedType, "identifier type unsupported: %s", id.Type) } } @@ -149,15 +149,9 @@ func (h *Handler) newAuthorization(ctx context.Context, az *acme.Authorization) } } - var ( - err error - chTypes = []string{"dns-01"} - ) - // HTTP and TLS challenges can only be used for identifiers without wildcards. - if !az.Wildcard { - chTypes = append(chTypes, []string{"http-01", "tls-alpn-01"}...) - } + chTypes := challengeTypes(az) + var err error az.Token, err = randutil.Alphanumeric(32) if err != nil { return acme.WrapErrorISE(err, "error generating random alphanumeric ID") @@ -275,3 +269,22 @@ func (h *Handler) FinalizeOrder(w http.ResponseWriter, r *http.Request) { w.Header().Set("Location", h.linker.GetLink(ctx, OrderLinkType, o.ID)) api.JSON(w, o) } + +// challengeTypes determines the types of challenges that should be used +// for the ACME authorization request. +func challengeTypes(az *acme.Authorization) []string { + chTypes := []string{} + + // DNS challenge can not be used for identifiers with type IP + if az.Identifier.Type != "ip" { + chTypes = append(chTypes, "dns-01") // TODO: make these types consts/enum? + } + + // HTTP and TLS challenges can only be used for identifiers without wildcards. + if !az.Wildcard { + //chTypes = append(chTypes, []string{"http-01", "tls-alpn-01"}...) + chTypes = append(chTypes, []string{"http-01"}...) // TODO: fix tls-alpn-01 + } + + return chTypes +} diff --git a/acme/api/order_test.go b/acme/api/order_test.go index 300aa61b..6782de75 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -10,6 +10,7 @@ import ( "io/ioutil" "net/http/httptest" "net/url" + "reflect" "testing" "time" @@ -60,6 +61,68 @@ func TestNewOrderRequest_Validate(t *testing.T) { naf: naf, } }, + "ok/ipv4": func(t *testing.T) test { + nbf := time.Now().UTC().Add(time.Minute) + naf := time.Now().UTC().Add(5 * time.Minute) + return test{ + nor: &NewOrderRequest{ + Identifiers: []acme.Identifier{ + {Type: "ip", Value: "192.168.42.42"}, + }, + NotAfter: naf, + NotBefore: nbf, + }, + nbf: nbf, + naf: naf, + } + }, + "ok/ipv6": func(t *testing.T) test { + nbf := time.Now().UTC().Add(time.Minute) + naf := time.Now().UTC().Add(5 * time.Minute) + return test{ + nor: &NewOrderRequest{ + Identifiers: []acme.Identifier{ + {Type: "ip", Value: "2001:db8::1"}, + }, + NotAfter: naf, + NotBefore: nbf, + }, + nbf: nbf, + naf: naf, + } + }, + "ok/mixed-dns-and-ipv4": func(t *testing.T) test { // TODO: verify that this is allowed and what we want to be possible (in Validate()) + nbf := time.Now().UTC().Add(time.Minute) + naf := time.Now().UTC().Add(5 * time.Minute) + return test{ + nor: &NewOrderRequest{ + Identifiers: []acme.Identifier{ + {Type: "dns", Value: "example.com"}, + {Type: "ip", Value: "192.168.42.42"}, + }, + NotAfter: naf, + NotBefore: nbf, + }, + nbf: nbf, + naf: naf, + } + }, + "ok/mixed-ipv4-and-ipv6": func(t *testing.T) test { + nbf := time.Now().UTC().Add(time.Minute) + naf := time.Now().UTC().Add(5 * time.Minute) + return test{ + nor: &NewOrderRequest{ + Identifiers: []acme.Identifier{ + {Type: "ip", Value: "192.168.42.42"}, + {Type: "ip", Value: "2001:db8::1"}, + }, + NotAfter: naf, + NotBefore: nbf, + }, + nbf: nbf, + naf: naf, + } + }, } for name, run := range tests { tc := run(t) @@ -1581,3 +1644,53 @@ func TestHandler_FinalizeOrder(t *testing.T) { }) } } + +func TestHandler_challengeTypes(t *testing.T) { + type args struct { + az *acme.Authorization + } + tests := []struct { + name string + args args + want []string + }{ + { + name: "ok/dns", + args: args{ + az: &acme.Authorization{ + Identifier: acme.Identifier{Type: "dns", Value: "example.com"}, + Wildcard: false, + }, + }, + want: []string{"dns-01", "http-01", "tls-alpn-01"}, + }, + { + name: "ok/wildcard", + args: args{ + az: &acme.Authorization{ + Identifier: acme.Identifier{Type: "dns", Value: "*.example.com"}, + Wildcard: true, + }, + }, + want: []string{"dns-01"}, + }, + { + name: "ok/ip", + args: args{ + az: &acme.Authorization{ + Identifier: acme.Identifier{Type: "ip", Value: "192.168.42.42"}, + Wildcard: false, + }, + }, + want: []string{"http-01", "tls-alpn-01"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + if got := challengeTypes(tt.args.az); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Handler.challengeTypes() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/acme/order.go b/acme/order.go index a003fe9a..fdab182f 100644 --- a/acme/order.go +++ b/acme/order.go @@ -1,9 +1,11 @@ package acme import ( + "bytes" "context" "crypto/x509" "encoding/json" + "net" "sort" "strings" "time" @@ -131,41 +133,13 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques return NewErrorISE("unexpected status %s for order %s", o.Status, o.ID) } - // RFC8555: The CSR MUST indicate the exact same set of requested - // identifiers as the initial newOrder request. Identifiers of type "dns" - // MUST appear either in the commonName portion of the requested subject - // name or in an extensionRequest attribute [RFC2985] requesting a - // subjectAltName extension, or both. - if csr.Subject.CommonName != "" { - csr.DNSNames = append(csr.DNSNames, csr.Subject.CommonName) - } - csr.DNSNames = uniqueSortedLowerNames(csr.DNSNames) - orderNames := make([]string, len(o.Identifiers)) - for i, n := range o.Identifiers { - orderNames[i] = n.Value - } - orderNames = uniqueSortedLowerNames(orderNames) + // canonicalize the CSR to allow for comparison + csr = canonicalize(csr) - // Validate identifier names against CSR alternative names. - // - // Note that with certificate templates we are not going to check for the - // absence of other SANs as they will only be set if the templates allows - // them. - if len(csr.DNSNames) != len(orderNames) { - return NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+ - "CSR names = %v, Order names = %v", csr.DNSNames, orderNames) - } - - sans := make([]x509util.SubjectAlternativeName, len(csr.DNSNames)) - for i := range csr.DNSNames { - if csr.DNSNames[i] != orderNames[i] { - return NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+ - "CSR names = %v, Order names = %v", csr.DNSNames, orderNames) - } - sans[i] = x509util.SubjectAlternativeName{ - Type: x509util.DNSType, - Value: csr.DNSNames[i], - } + // retrieve the requested SANs for the Order + sans, err := o.sans(csr) + if err != nil { + return WrapErrorISE(err, "error determining SANs for the CSR") } // Get authorizations from the ACME provisioner. @@ -213,6 +187,120 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques return nil } +func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativeName, error) { + + var sans []x509util.SubjectAlternativeName + + // order the DNS names and IP addresses, so that they can be compared against the canonicalized CSR + orderNames := make([]string, len(o.Identifiers)) + orderIPs := make([]net.IP, len(o.Identifiers)) + for i, n := range o.Identifiers { + switch n.Type { + case "dns": + orderNames[i] = n.Value + case "ip": + orderIPs[i] = net.ParseIP(n.Value) // NOTE: this assumes are all valid IPs or will result in nil entries + default: + return sans, NewErrorISE("unsupported identifier type in order: %s", n.Type) + } + } + orderNames = uniqueSortedLowerNames(orderNames) + orderIPs = uniqueSortedIPs(orderIPs) + + // TODO: check whether this order was requested with identifier-type IP, + // if so, handle it as an IP order; not as a DNSName order, so the logic + // for verifying the contents MAY not be necessary. + // TODO: limit what IP addresses can be used? Only private? Only certain ranges + // based on configuration? Public vs. private range? That logic should be configurable somewhere. + // TODO: how to handler orders that have DNSNames AND IPs? I guess it could + // happen in cases where there are multiple "identifiers" to order a cert for + // and http or tls-alpn-1 is used (NOT DNS, because that can't be used for IPs). + // TODO: ensure that DNSNames indeed MUST NEVER have an IP + // TODO: only allow IP based identifier based on configuration? + // TODO: validation of the input (if IP; should be valid IPv4/v6) + + // Determine if DNS names or IPs should be processed. + // At this time, orders in which DNS names and IPs are mixed are not supported. // TODO: ensure that's OK and/or should we support more, RFC-wise + shouldProcessIPAddresses := len(csr.DNSNames) == 0 && len(orderIPs) != 0 // TODO: verify that this logic is OK and sufficient + if shouldProcessIPAddresses { + // Validate identifier IPs against CSR alternative names (IPs). + if len(csr.IPAddresses) != len(orderIPs) { + return sans, NewError(ErrorBadCSRType, "CSR IPs do not match identifiers exactly: "+ + "CSR IPs = %v, Order IPs = %v", csr.IPAddresses, orderIPs) + } + + sans = make([]x509util.SubjectAlternativeName, len(csr.IPAddresses)) + for i := range csr.IPAddresses { + if !ipsAreEqual(csr.IPAddresses[i], orderIPs[i]) { + return sans, NewError(ErrorBadCSRType, "CSR IPs do not match identifiers exactly: "+ + "CSR IPs = %v, Order IPs = %v", csr.IPAddresses, orderIPs) + } + sans[i] = x509util.SubjectAlternativeName{ + Type: x509util.IPType, + Value: csr.IPAddresses[i].String(), + } + } + } else { + // Validate identifier names against CSR alternative names. + // + // Note that with certificate templates we are not going to check for the + // absence of other SANs as they will only be set if the templates allows + // them. + if len(csr.DNSNames) != len(orderNames) { + return sans, NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+ + "CSR names = %v, Order names = %v", csr.DNSNames, orderNames) + } + + sans = make([]x509util.SubjectAlternativeName, len(csr.DNSNames)) + for i := range csr.DNSNames { + if csr.DNSNames[i] != orderNames[i] { + return sans, NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+ + "CSR names = %v, Order names = %v", csr.DNSNames, orderNames) + } + sans[i] = x509util.SubjectAlternativeName{ + Type: x509util.DNSType, + Value: csr.DNSNames[i], + } + } + } + + return sans, nil +} + +func canonicalize(csr *x509.CertificateRequest) (canonicalized *x509.CertificateRequest) { + + // for clarity only; we're operating on the same object by pointer + canonicalized = csr + + // RFC8555: The CSR MUST indicate the exact same set of requested + // identifiers as the initial newOrder request. Identifiers of type "dns" + // MUST appear either in the commonName portion of the requested subject + // name or in an extensionRequest attribute [RFC2985] requesting a + // subjectAltName extension, or both. + if csr.Subject.CommonName != "" { + canonicalized.DNSNames = append(csr.DNSNames, csr.Subject.CommonName) + } + canonicalized.DNSNames = uniqueSortedLowerNames(csr.DNSNames) + canonicalized.IPAddresses = uniqueSortedIPs(csr.IPAddresses) // TODO: sorting and setting this value MAY result in different values in CSR (and probably also ending up in cert); is that behavior wanted? + + return canonicalized +} + +// ipsAreEqual compares IPs to be equal. IPv6 representations of IPv4 +// adresses are NOT considered equal to the IPv4 address in this case. +// Both IPs should be the same version AND equal to each other. +func ipsAreEqual(x, y net.IP) bool { + if isIPv4(x) && isIPv4(y) { + return x.Equal(y) + } + return x.Equal(y) +} + +// isIPv4 returns if an IP is IPv4 or not. +func isIPv4(ip net.IP) bool { + return ip.To4() != nil +} + // uniqueSortedLowerNames returns the set of all unique names in the input after all // of them are lowercased. The returned names will be in their lowercased form // and sorted alphabetically. @@ -228,3 +316,23 @@ func uniqueSortedLowerNames(names []string) (unique []string) { sort.Strings(unique) return } + +// uniqueSortedIPs returns the set of all unique net.IPs in the input. They +// are sorted by their bytes (octet) representation. +func uniqueSortedIPs(ips []net.IP) (unique []net.IP) { + type entry struct { + ip net.IP + } + ipEntryMap := make(map[string]entry, len(ips)) + for _, ip := range ips { + ipEntryMap[ip.String()] = entry{ip: ip} + } + unique = make([]net.IP, 0, len(ipEntryMap)) + for _, entry := range ipEntryMap { + unique = append(unique, entry.ip) + } + sort.Slice(unique, func(i, j int) bool { + return bytes.Compare(unique[i], unique[j]) < 0 + }) + return +} diff --git a/acme/order_test.go b/acme/order_test.go index 993a92f2..c0461dc6 100644 --- a/acme/order_test.go +++ b/acme/order_test.go @@ -5,6 +5,8 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/json" + "net" + "reflect" "testing" "time" @@ -737,3 +739,78 @@ func TestOrder_Finalize(t *testing.T) { }) } } + +func Test_uniqueSortedIPs(t *testing.T) { + type args struct { + ips []net.IP + } + tests := []struct { + name string + args args + wantUnique []net.IP + }{ + { + name: "ok/empty", + args: args{ + ips: []net.IP{}, + }, + wantUnique: []net.IP{}, + }, + { + name: "ok/single-ipv4", + args: args{ + ips: []net.IP{net.ParseIP("192.168.42.42")}, + }, + wantUnique: []net.IP{net.ParseIP("192.168.42.42")}, + }, + { + name: "ok/multiple-ipv4", + args: args{ + ips: []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.42.10"), net.ParseIP("192.168.42.1")}, + }, + wantUnique: []net.IP{net.ParseIP("192.168.42.1"), net.ParseIP("192.168.42.10"), net.ParseIP("192.168.42.42")}, + }, + { + name: "ok/unique-ipv4", + args: args{ + ips: []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.42.42")}, + }, + wantUnique: []net.IP{net.ParseIP("192.168.42.42")}, + }, + { + name: "ok/single-ipv6", + args: args{ + ips: []net.IP{net.ParseIP("2001:db8::30")}, + }, + wantUnique: []net.IP{net.ParseIP("2001:db8::30")}, + }, + { + name: "ok/multiple-ipv6", + args: args{ + ips: []net.IP{net.ParseIP("2001:db8::30"), net.ParseIP("2001:db8::20"), net.ParseIP("2001:db8::10")}, + }, + wantUnique: []net.IP{net.ParseIP("2001:db8::10"), net.ParseIP("2001:db8::20"), net.ParseIP("2001:db8::30")}, + }, + { + name: "ok/unique-ipv6", + args: args{ + ips: []net.IP{net.ParseIP("2001:db8::1"), net.ParseIP("2001:db8::1")}, + }, + wantUnique: []net.IP{net.ParseIP("2001:db8::1")}, + }, + { + name: "ok/mixed-ipv4-and-ipv6", + args: args{ + ips: []net.IP{net.ParseIP("2001:db8::1"), net.ParseIP("2001:db8::1"), net.ParseIP("192.168.42.42"), net.ParseIP("192.168.42.42")}, + }, + wantUnique: []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("2001:db8::1")}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotUnique := uniqueSortedIPs(tt.args.ips); !reflect.DeepEqual(gotUnique, tt.wantUnique) { + t.Errorf("uniqueSortedIPs() = %v, want %v", gotUnique, tt.wantUnique) + } + }) + } +} From 3e36522329aeef0009547c50d30e0f54e0081d89 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Sat, 29 May 2021 00:19:14 +0200 Subject: [PATCH 66/89] Add preliminary support for TLS-ALPN-01 challenge for IP identifiers --- acme/api/order.go | 7 +++---- acme/challenge.go | 24 +++++++++++++++++++++--- acme/order.go | 9 ++------- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/acme/api/order.go b/acme/api/order.go index b0b9cb43..78a4f9c6 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -275,15 +275,14 @@ func (h *Handler) FinalizeOrder(w http.ResponseWriter, r *http.Request) { func challengeTypes(az *acme.Authorization) []string { chTypes := []string{} - // DNS challenge can not be used for identifiers with type IP - if az.Identifier.Type != "ip" { + // DNS challenge can only be used for identifiers with type dns + if az.Identifier.Type == "dns" { chTypes = append(chTypes, "dns-01") // TODO: make these types consts/enum? } // HTTP and TLS challenges can only be used for identifiers without wildcards. if !az.Wildcard { - //chTypes = append(chTypes, []string{"http-01", "tls-alpn-01"}...) - chTypes = append(chTypes, []string{"http-01"}...) // TODO: fix tls-alpn-01 + chTypes = append(chTypes, []string{"http-01", "tls-alpn-01"}...) } return chTypes diff --git a/acme/challenge.go b/acme/challenge.go index 1059e437..90960fc4 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -119,8 +119,14 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON hostPort := net.JoinHostPort(ch.Value, "443") + fmt.Println(hostPort) + fmt.Println(fmt.Sprintf("%#+v", config)) + + time.Sleep(5 * time.Second) // TODO: remove this; client seems to take a while to start serving; the server does not seem to retry the conn + conn, err := vo.TLSDial("tcp", hostPort, config) if err != nil { + fmt.Println(err) return storeError(ctx, db, ch, false, WrapError(ErrorConnectionType, err, "error doing TLS dial for %s", hostPort)) } @@ -129,6 +135,9 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON cs := conn.ConnectionState() certs := cs.PeerCertificates + fmt.Println(fmt.Sprintf("%#+v", cs)) + fmt.Println(fmt.Sprintf("%#+v", certs)) + if len(certs) == 0 { return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, "%s challenge for %s resulted in no certificates", ch.Type, ch.Value)) @@ -140,10 +149,19 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON } leafCert := certs[0] + fmt.Println(fmt.Sprintf("%#+v", leafCert)) - if len(leafCert.DNSNames) != 1 || !strings.EqualFold(leafCert.DNSNames[0], ch.Value) { - return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, - "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single DNS name, %v", ch.Value)) + // if no DNS names present, look for IP address and verify that exactly one exists + if len(leafCert.DNSNames) == 0 { + if len(leafCert.IPAddresses) != 1 || !strings.EqualFold(leafCert.IPAddresses[0].String(), ch.Value) { + return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, + "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single IP address, %v", ch.Value)) + } + } else { + if len(leafCert.DNSNames) != 1 || !strings.EqualFold(leafCert.DNSNames[0], ch.Value) { + return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, + "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single DNS name, %v", ch.Value)) + } } idPeAcmeIdentifier := asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31} diff --git a/acme/order.go b/acme/order.go index fdab182f..e9e161f9 100644 --- a/acme/order.go +++ b/acme/order.go @@ -207,17 +207,12 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ orderNames = uniqueSortedLowerNames(orderNames) orderIPs = uniqueSortedIPs(orderIPs) - // TODO: check whether this order was requested with identifier-type IP, - // if so, handle it as an IP order; not as a DNSName order, so the logic - // for verifying the contents MAY not be necessary. // TODO: limit what IP addresses can be used? Only private? Only certain ranges // based on configuration? Public vs. private range? That logic should be configurable somewhere. - // TODO: how to handler orders that have DNSNames AND IPs? I guess it could - // happen in cases where there are multiple "identifiers" to order a cert for - // and http or tls-alpn-1 is used (NOT DNS, because that can't be used for IPs). // TODO: ensure that DNSNames indeed MUST NEVER have an IP // TODO: only allow IP based identifier based on configuration? - // TODO: validation of the input (if IP; should be valid IPv4/v6) + // TODO: validation of the input (if IP; should be valid IPv4/v6; Incoming request should have Host header set / ALPN IN-ADDR.ARPA) + // TODO: limit the IP address identifier to a single IP address? RFC _can_ be read like that, but there can be multiple identifiers, of course // Determine if DNS names or IPs should be processed. // At this time, orders in which DNS names and IPs are mixed are not supported. // TODO: ensure that's OK and/or should we support more, RFC-wise From 6486e6016bebf596879da34774c7b37e06e41a37 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Sat, 29 May 2021 00:37:22 +0200 Subject: [PATCH 67/89] Make logic for which challenge types to use clearer --- acme/api/order.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/acme/api/order.go b/acme/api/order.go index 78a4f9c6..7d43c2d9 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -273,16 +273,19 @@ func (h *Handler) FinalizeOrder(w http.ResponseWriter, r *http.Request) { // challengeTypes determines the types of challenges that should be used // for the ACME authorization request. func challengeTypes(az *acme.Authorization) []string { - chTypes := []string{} + var chTypes []string - // DNS challenge can only be used for identifiers with type dns - if az.Identifier.Type == "dns" { - chTypes = append(chTypes, "dns-01") // TODO: make these types consts/enum? - } - - // HTTP and TLS challenges can only be used for identifiers without wildcards. - if !az.Wildcard { - chTypes = append(chTypes, []string{"http-01", "tls-alpn-01"}...) + switch az.Identifier.Type { + case "ip": // TODO: make these types consts/enum? + chTypes = []string{"http-01", "tls-alpn-01"} + case "dns": + chTypes = []string{"dns-01"} + // HTTP and TLS challenges can only be used for identifiers without wildcards. + if !az.Wildcard { + chTypes = append(chTypes, []string{"http-01", "tls-alpn-01"}...) + } + default: + chTypes = []string{} } return chTypes From a0e92f8e99268fcb26f919fae973ae9af3c3b4c8 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Thu, 3 Jun 2021 22:02:13 +0200 Subject: [PATCH 68/89] Verify IP identifier contains valid IP --- acme/api/order.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/acme/api/order.go b/acme/api/order.go index 7d43c2d9..e1ac1aa1 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -5,6 +5,7 @@ import ( "crypto/x509" "encoding/base64" "encoding/json" + "net" "net/http" "strings" "time" @@ -31,6 +32,9 @@ func (n *NewOrderRequest) Validate() error { if !(id.Type == "dns" || id.Type == "ip") { return acme.NewError(acme.ErrorMalformedType, "identifier type unsupported: %s", id.Type) } + if id.Type == "ip" && net.ParseIP(id.Value) == nil { + return acme.NewError(acme.ErrorMalformedType, "%s is not a valid IP address", id.Value) + } } return nil } @@ -85,6 +89,7 @@ func (h *Handler) NewOrder(w http.ResponseWriter, r *http.Request) { "failed to unmarshal new-order request payload")) return } + if err := nor.Validate(); err != nil { api.WriteError(w, err) return From af615db6b51f4fd912e55957b82ae1b02addd3c6 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Thu, 3 Jun 2021 22:03:21 +0200 Subject: [PATCH 69/89] Support DNS and IPs as SANs in single Order --- acme/order.go | 91 ++++++++++++++++++++++++--------------------------- 1 file changed, 43 insertions(+), 48 deletions(-) diff --git a/acme/order.go b/acme/order.go index e9e161f9..e1d77ece 100644 --- a/acme/order.go +++ b/acme/order.go @@ -199,7 +199,7 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ case "dns": orderNames[i] = n.Value case "ip": - orderIPs[i] = net.ParseIP(n.Value) // NOTE: this assumes are all valid IPs or will result in nil entries + orderIPs[i] = net.ParseIP(n.Value) // NOTE: this assumes are all valid IPs at this time; or will result in nil entries default: return sans, NewErrorISE("unsupported identifier type in order: %s", n.Type) } @@ -207,55 +207,49 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ orderNames = uniqueSortedLowerNames(orderNames) orderIPs = uniqueSortedIPs(orderIPs) - // TODO: limit what IP addresses can be used? Only private? Only certain ranges - // based on configuration? Public vs. private range? That logic should be configurable somewhere. - // TODO: ensure that DNSNames indeed MUST NEVER have an IP - // TODO: only allow IP based identifier based on configuration? - // TODO: validation of the input (if IP; should be valid IPv4/v6; Incoming request should have Host header set / ALPN IN-ADDR.ARPA) - // TODO: limit the IP address identifier to a single IP address? RFC _can_ be read like that, but there can be multiple identifiers, of course + totalNumberOfSANs := len(csr.DNSNames) + len(csr.IPAddresses) + sans = make([]x509util.SubjectAlternativeName, totalNumberOfSANs) - // Determine if DNS names or IPs should be processed. - // At this time, orders in which DNS names and IPs are mixed are not supported. // TODO: ensure that's OK and/or should we support more, RFC-wise - shouldProcessIPAddresses := len(csr.DNSNames) == 0 && len(orderIPs) != 0 // TODO: verify that this logic is OK and sufficient - if shouldProcessIPAddresses { - // Validate identifier IPs against CSR alternative names (IPs). - if len(csr.IPAddresses) != len(orderIPs) { + // TODO: limit what IP addresses can be used? Only private? Only certain ranges (i.e. only allow the specific ranges by default, configuration for all?) + // TODO: can DNS already be limited to a certain domain? That would probably be nice to have too, but maybe not as part of this PR + // TODO: if it seems not too big of a change, make consts/enums out of the stringly typed identifiers (challenge types, identifier types) + // based on configuration? Public vs. private range? That logic should be configurable somewhere. + // TODO: only allow IP based identifier based on configuration? Some additional configuration and validation on the provisioner for this case. + + // Validate identifier names against CSR alternative names. + // + // Note that with certificate templates we are not going to check for the + // absence of other SANs as they will only be set if the templates allows + // them. + if len(csr.IPAddresses) != len(orderIPs) { + return sans, NewError(ErrorBadCSRType, "number of CSR IPs do not match identifiers exactly: "+ + "CSR IPs = %v, Order IPs = %v", csr.IPAddresses, orderIPs) + } + + for i := range csr.IPAddresses { + if !ipsAreEqual(csr.IPAddresses[i], orderIPs[i]) { return sans, NewError(ErrorBadCSRType, "CSR IPs do not match identifiers exactly: "+ "CSR IPs = %v, Order IPs = %v", csr.IPAddresses, orderIPs) } - - sans = make([]x509util.SubjectAlternativeName, len(csr.IPAddresses)) - for i := range csr.IPAddresses { - if !ipsAreEqual(csr.IPAddresses[i], orderIPs[i]) { - return sans, NewError(ErrorBadCSRType, "CSR IPs do not match identifiers exactly: "+ - "CSR IPs = %v, Order IPs = %v", csr.IPAddresses, orderIPs) - } - sans[i] = x509util.SubjectAlternativeName{ - Type: x509util.IPType, - Value: csr.IPAddresses[i].String(), - } + sans[i] = x509util.SubjectAlternativeName{ + Type: x509util.IPType, + Value: csr.IPAddresses[i].String(), } - } else { - // Validate identifier names against CSR alternative names. - // - // Note that with certificate templates we are not going to check for the - // absence of other SANs as they will only be set if the templates allows - // them. - if len(csr.DNSNames) != len(orderNames) { + } + + if len(csr.DNSNames) != len(orderNames) { + return sans, NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+ + "CSR names = %v, Order names = %v", csr.DNSNames, orderNames) + } + + for i := range csr.DNSNames { + if csr.DNSNames[i] != orderNames[i] { return sans, NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+ "CSR names = %v, Order names = %v", csr.DNSNames, orderNames) } - - sans = make([]x509util.SubjectAlternativeName, len(csr.DNSNames)) - for i := range csr.DNSNames { - if csr.DNSNames[i] != orderNames[i] { - return sans, NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+ - "CSR names = %v, Order names = %v", csr.DNSNames, orderNames) - } - sans[i] = x509util.SubjectAlternativeName{ - Type: x509util.DNSType, - Value: csr.DNSNames[i], - } + sans[i] = x509util.SubjectAlternativeName{ + Type: x509util.DNSType, + Value: csr.DNSNames[i], } } @@ -276,7 +270,7 @@ func canonicalize(csr *x509.CertificateRequest) (canonicalized *x509.Certificate canonicalized.DNSNames = append(csr.DNSNames, csr.Subject.CommonName) } canonicalized.DNSNames = uniqueSortedLowerNames(csr.DNSNames) - canonicalized.IPAddresses = uniqueSortedIPs(csr.IPAddresses) // TODO: sorting and setting this value MAY result in different values in CSR (and probably also ending up in cert); is that behavior wanted? + canonicalized.IPAddresses = uniqueSortedIPs(csr.IPAddresses) return canonicalized } @@ -285,15 +279,16 @@ func canonicalize(csr *x509.CertificateRequest) (canonicalized *x509.Certificate // adresses are NOT considered equal to the IPv4 address in this case. // Both IPs should be the same version AND equal to each other. func ipsAreEqual(x, y net.IP) bool { - if isIPv4(x) && isIPv4(y) { + if matchAddrFamily(x, y) { return x.Equal(y) } - return x.Equal(y) + return false } -// isIPv4 returns if an IP is IPv4 or not. -func isIPv4(ip net.IP) bool { - return ip.To4() != nil +// matchAddrFamily returns if two IPs are both IPv4 OR IPv6 +// Implementation taken and adapted from https://golang.org/src/net/ip.go +func matchAddrFamily(x net.IP, y net.IP) bool { + return x.To4() != nil && y.To4() != nil || x.To16() != nil && x.To4() == nil && y.To16() != nil && y.To4() == nil } // uniqueSortedLowerNames returns the set of all unique names in the input after all From 76dcf542d42f66531aeb1dec13ddb8340a9c52ab Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Thu, 3 Jun 2021 22:45:24 +0200 Subject: [PATCH 70/89] Fix mixed DNS and IP SANs in Order --- acme/api/order.go | 2 +- acme/order.go | 65 +++++++++++++++++++++++++++++++---------------- 2 files changed, 44 insertions(+), 23 deletions(-) diff --git a/acme/api/order.go b/acme/api/order.go index e1ac1aa1..936e9573 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -33,7 +33,7 @@ func (n *NewOrderRequest) Validate() error { return acme.NewError(acme.ErrorMalformedType, "identifier type unsupported: %s", id.Type) } if id.Type == "ip" && net.ParseIP(id.Value) == nil { - return acme.NewError(acme.ErrorMalformedType, "%s is not a valid IP address", id.Value) + return acme.NewError(acme.ErrorMalformedType, "invalid IP address: %s", id.Value) } } return nil diff --git a/acme/order.go b/acme/order.go index e1d77ece..add90e1a 100644 --- a/acme/order.go +++ b/acme/order.go @@ -192,14 +192,17 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ var sans []x509util.SubjectAlternativeName // order the DNS names and IP addresses, so that they can be compared against the canonicalized CSR - orderNames := make([]string, len(o.Identifiers)) - orderIPs := make([]net.IP, len(o.Identifiers)) - for i, n := range o.Identifiers { + orderNames := make([]string, numberOfIdentifierType("dns", o.Identifiers)) + orderIPs := make([]net.IP, numberOfIdentifierType("ip", o.Identifiers)) + indexDNS, indexIP := 0, 0 + for _, n := range o.Identifiers { switch n.Type { case "dns": - orderNames[i] = n.Value + orderNames[indexDNS] = n.Value + indexDNS++ case "ip": - orderIPs[i] = net.ParseIP(n.Value) // NOTE: this assumes are all valid IPs at this time; or will result in nil entries + orderIPs[indexIP] = net.ParseIP(n.Value) // NOTE: this assumes are all valid IPs at this time; or will result in nil entries + indexIP++ default: return sans, NewErrorISE("unsupported identifier type in order: %s", n.Type) } @@ -209,6 +212,7 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ totalNumberOfSANs := len(csr.DNSNames) + len(csr.IPAddresses) sans = make([]x509util.SubjectAlternativeName, totalNumberOfSANs) + index := 0 // TODO: limit what IP addresses can be used? Only private? Only certain ranges (i.e. only allow the specific ranges by default, configuration for all?) // TODO: can DNS already be limited to a certain domain? That would probably be nice to have too, but maybe not as part of this PR @@ -221,22 +225,6 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ // Note that with certificate templates we are not going to check for the // absence of other SANs as they will only be set if the templates allows // them. - if len(csr.IPAddresses) != len(orderIPs) { - return sans, NewError(ErrorBadCSRType, "number of CSR IPs do not match identifiers exactly: "+ - "CSR IPs = %v, Order IPs = %v", csr.IPAddresses, orderIPs) - } - - for i := range csr.IPAddresses { - if !ipsAreEqual(csr.IPAddresses[i], orderIPs[i]) { - return sans, NewError(ErrorBadCSRType, "CSR IPs do not match identifiers exactly: "+ - "CSR IPs = %v, Order IPs = %v", csr.IPAddresses, orderIPs) - } - sans[i] = x509util.SubjectAlternativeName{ - Type: x509util.IPType, - Value: csr.IPAddresses[i].String(), - } - } - if len(csr.DNSNames) != len(orderNames) { return sans, NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+ "CSR names = %v, Order names = %v", csr.DNSNames, orderNames) @@ -247,15 +235,48 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ return sans, NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+ "CSR names = %v, Order names = %v", csr.DNSNames, orderNames) } - sans[i] = x509util.SubjectAlternativeName{ + sans[index] = x509util.SubjectAlternativeName{ Type: x509util.DNSType, Value: csr.DNSNames[i], } + index++ + } + + if len(csr.IPAddresses) != len(orderIPs) { + return sans, NewError(ErrorBadCSRType, "number of CSR IPs do not match identifiers exactly: "+ + "CSR IPs = %v, Order IPs = %v", csr.IPAddresses, orderIPs) + } + + for i := range csr.IPAddresses { + if !ipsAreEqual(csr.IPAddresses[i], orderIPs[i]) { + return sans, NewError(ErrorBadCSRType, "CSR IPs do not match identifiers exactly: "+ + "CSR IPs = %v, Order IPs = %v", csr.IPAddresses, orderIPs) + } + sans[index] = x509util.SubjectAlternativeName{ + Type: x509util.IPType, + Value: csr.IPAddresses[i].String(), + } + index++ } return sans, nil } +// numberOfIdentifierType returns the number of Identifiers that +// are of type typ. +func numberOfIdentifierType(typ string, ids []Identifier) int { + c := 0 + for _, id := range ids { + if id.Type == typ { + c++ + } + } + return c +} + +// canonicalize canonicalizes a CSR so that it can be compared against an Order +// NOTE: this effectively changes the order of SANs in the CSR, which may be OK, +// but may not be expected. func canonicalize(csr *x509.CertificateRequest) (canonicalized *x509.CertificateRequest) { // for clarity only; we're operating on the same object by pointer From 2f40011da87cacdd73a51bd7ae9c915fa40f567d Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 4 Jun 2021 00:01:43 +0200 Subject: [PATCH 71/89] Add support for TLS-ALPN-01 challenge --- acme/challenge.go | 67 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/acme/challenge.go b/acme/challenge.go index 90960fc4..d541bed2 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -107,23 +107,30 @@ func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWeb } func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, vo *ValidateChallengeOptions) error { + + var serverName string + + // RFC8738 states that, if HostName is IP, it should be the ARPA + // address https://datatracker.ietf.org/doc/html/rfc8738#section-6. + // It also references TLS Extensions [RFC6066]. + if ip := net.ParseIP(ch.Value); ip != nil { + serverName = reverseAddr(ip) + } else { + serverName = ch.Value + } + config := &tls.Config{ NextProtos: []string{"acme-tls/1"}, // https://tools.ietf.org/html/rfc8737#section-4 // ACME servers that implement "acme-tls/1" MUST only negotiate TLS 1.2 // [RFC5246] or higher when connecting to clients for validation. MinVersion: tls.VersionTLS12, - ServerName: ch.Value, + ServerName: serverName, InsecureSkipVerify: true, // we expect a self-signed challenge certificate } hostPort := net.JoinHostPort(ch.Value, "443") - fmt.Println(hostPort) - fmt.Println(fmt.Sprintf("%#+v", config)) - - time.Sleep(5 * time.Second) // TODO: remove this; client seems to take a while to start serving; the server does not seem to retry the conn - conn, err := vo.TLSDial("tcp", hostPort, config) if err != nil { fmt.Println(err) @@ -135,9 +142,6 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON cs := conn.ConnectionState() certs := cs.PeerCertificates - fmt.Println(fmt.Sprintf("%#+v", cs)) - fmt.Println(fmt.Sprintf("%#+v", certs)) - if len(certs) == 0 { return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, "%s challenge for %s resulted in no certificates", ch.Type, ch.Value)) @@ -149,7 +153,6 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON } leafCert := certs[0] - fmt.Println(fmt.Sprintf("%#+v", leafCert)) // if no DNS names present, look for IP address and verify that exactly one exists if len(leafCert.DNSNames) == 0 { @@ -262,6 +265,50 @@ func dns01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebK return nil } +// reverseaddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP +// address addr suitable for rDNS (PTR) record lookup or an error if it fails +// to parse the IP address. +// Implementation taken and adapted from https://golang.org/src/net/dnsclient.go?s=780:834#L20 +func reverseAddr(ip net.IP) (arpa string) { + if ip.To4() != nil { + return uitoa(uint(ip[15])) + "." + uitoa(uint(ip[14])) + "." + uitoa(uint(ip[13])) + "." + uitoa(uint(ip[12])) + ".in-addr.arpa." + } + // Must be IPv6 + buf := make([]byte, 0, len(ip)*4+len("ip6.arpa.")) + // Add it, in reverse, to the buffer + for i := len(ip) - 1; i >= 0; i-- { + v := ip[i] + buf = append(buf, hexit[v&0xF], + '.', + hexit[v>>4], + '.') + } + // Append "ip6.arpa." and return (buf already has the final .) + buf = append(buf, "ip6.arpa."...) + return string(buf) +} + +// Convert unsigned integer to decimal string. +// Implementation taken from https://golang.org/src/net/parse.go +func uitoa(val uint) string { + if val == 0 { // avoid string allocation + return "0" + } + var buf [20]byte // big enough for 64bit value base 10 + i := len(buf) - 1 + for val >= 10 { + q := val / 10 + buf[i] = byte('0' + val - q*10) + i-- + val = q + } + // val < 10 + buf[i] = byte('0' + val) + return string(buf[i:]) +} + +const hexit = "0123456789abcdef" + // KeyAuthorization creates the ACME key authorization value from a token // and a jwk. func KeyAuthorization(token string, jwk *jose.JSONWebKey) (string, error) { From a6405e98a9939519ebc8012d263aed1ff1113b1b Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 4 Jun 2021 00:06:15 +0200 Subject: [PATCH 72/89] Remove fmt. --- acme/challenge.go | 1 - acme/order.go | 1 - 2 files changed, 2 deletions(-) diff --git a/acme/challenge.go b/acme/challenge.go index d541bed2..105fcbc8 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -133,7 +133,6 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON conn, err := vo.TLSDial("tcp", hostPort, config) if err != nil { - fmt.Println(err) return storeError(ctx, db, ch, false, WrapError(ErrorConnectionType, err, "error doing TLS dial for %s", hostPort)) } diff --git a/acme/order.go b/acme/order.go index add90e1a..b11d51c7 100644 --- a/acme/order.go +++ b/acme/order.go @@ -217,7 +217,6 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ // TODO: limit what IP addresses can be used? Only private? Only certain ranges (i.e. only allow the specific ranges by default, configuration for all?) // TODO: can DNS already be limited to a certain domain? That would probably be nice to have too, but maybe not as part of this PR // TODO: if it seems not too big of a change, make consts/enums out of the stringly typed identifiers (challenge types, identifier types) - // based on configuration? Public vs. private range? That logic should be configurable somewhere. // TODO: only allow IP based identifier based on configuration? Some additional configuration and validation on the provisioner for this case. // Validate identifier names against CSR alternative names. From 0c79914d0d417fcc4df97663e2e4acb6162108bf Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 4 Jun 2021 00:12:49 +0200 Subject: [PATCH 73/89] Improve check for single IP in TLS-ALPN-01 challenge --- acme/challenge.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/acme/challenge.go b/acme/challenge.go index 105fcbc8..eabdaaed 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -108,12 +108,12 @@ func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWeb func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, vo *ValidateChallengeOptions) error { - var serverName string - // RFC8738 states that, if HostName is IP, it should be the ARPA // address https://datatracker.ietf.org/doc/html/rfc8738#section-6. // It also references TLS Extensions [RFC6066]. - if ip := net.ParseIP(ch.Value); ip != nil { + var serverName string + ip := net.ParseIP(ch.Value) + if ip != nil { serverName = reverseAddr(ip) } else { serverName = ch.Value @@ -155,7 +155,7 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON // if no DNS names present, look for IP address and verify that exactly one exists if len(leafCert.DNSNames) == 0 { - if len(leafCert.IPAddresses) != 1 || !strings.EqualFold(leafCert.IPAddresses[0].String(), ch.Value) { + if len(leafCert.IPAddresses) != 1 || !leafCert.IPAddresses[0].Equal(ip) { return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single IP address, %v", ch.Value)) } From af4803b8b883c39322cc440cfd512de3b45281ab Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 4 Jun 2021 08:42:24 +0200 Subject: [PATCH 74/89] Fix tests --- acme/challenge.go | 4 ++-- acme/challenge_test.go | 10 +++++----- acme/order.go | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/acme/challenge.go b/acme/challenge.go index eabdaaed..cb15a188 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -157,12 +157,12 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON if len(leafCert.DNSNames) == 0 { if len(leafCert.IPAddresses) != 1 || !leafCert.IPAddresses[0].Equal(ip) { return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, - "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single IP address, %v", ch.Value)) + "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single IP address or DNS name, %v", ch.Value)) } } else { if len(leafCert.DNSNames) != 1 || !strings.EqualFold(leafCert.DNSNames[0], ch.Value) { return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, - "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single DNS name, %v", ch.Value)) + "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single IP address or DNS name, %v", ch.Value)) } } diff --git a/acme/challenge_test.go b/acme/challenge_test.go index 14287945..8e03a414 100644 --- a/acme/challenge_test.go +++ b/acme/challenge_test.go @@ -1544,7 +1544,7 @@ func TestTLSALPN01Validate(t *testing.T) { err: NewErrorISE("failure saving error to acme challenge: force"), } }, - "ok/no-names-error": func(t *testing.T) test { + "ok/no-names-nor-ips-error": func(t *testing.T) test { ch := makeTLSCh() jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) @@ -1573,7 +1573,7 @@ func TestTLSALPN01Validate(t *testing.T) { assert.Equals(t, updch.Type, ch.Type) assert.Equals(t, updch.Value, ch.Value) - err := NewError(ErrorRejectedIdentifierType, "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single DNS name, %v", ch.Value) + err := NewError(ErrorRejectedIdentifierType, "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single IP address or DNS name, %v", ch.Value) assert.HasPrefix(t, updch.Error.Err.Error(), err.Err.Error()) assert.Equals(t, updch.Error.Type, err.Type) @@ -1616,7 +1616,7 @@ func TestTLSALPN01Validate(t *testing.T) { assert.Equals(t, updch.Type, ch.Type) assert.Equals(t, updch.Value, ch.Value) - err := NewError(ErrorRejectedIdentifierType, "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single DNS name, %v", ch.Value) + err := NewError(ErrorRejectedIdentifierType, "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single IP address or DNS name, %v", ch.Value) assert.HasPrefix(t, updch.Error.Err.Error(), err.Err.Error()) assert.Equals(t, updch.Error.Type, err.Type) @@ -1660,7 +1660,7 @@ func TestTLSALPN01Validate(t *testing.T) { assert.Equals(t, updch.Type, ch.Type) assert.Equals(t, updch.Value, ch.Value) - err := NewError(ErrorRejectedIdentifierType, "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single DNS name, %v", ch.Value) + err := NewError(ErrorRejectedIdentifierType, "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single IP address or DNS name, %v", ch.Value) assert.HasPrefix(t, updch.Error.Err.Error(), err.Err.Error()) assert.Equals(t, updch.Error.Type, err.Type) @@ -1703,7 +1703,7 @@ func TestTLSALPN01Validate(t *testing.T) { assert.Equals(t, updch.Type, ch.Type) assert.Equals(t, updch.Value, ch.Value) - err := NewError(ErrorRejectedIdentifierType, "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single DNS name, %v", ch.Value) + err := NewError(ErrorRejectedIdentifierType, "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single IP address or DNS name, %v", ch.Value) assert.HasPrefix(t, updch.Error.Err.Error(), err.Err.Error()) assert.Equals(t, updch.Error.Type, err.Type) diff --git a/acme/order.go b/acme/order.go index b11d51c7..73d5e636 100644 --- a/acme/order.go +++ b/acme/order.go @@ -139,7 +139,7 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques // retrieve the requested SANs for the Order sans, err := o.sans(csr) if err != nil { - return WrapErrorISE(err, "error determining SANs for the CSR") + return err } // Get authorizations from the ACME provisioner. @@ -242,7 +242,7 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ } if len(csr.IPAddresses) != len(orderIPs) { - return sans, NewError(ErrorBadCSRType, "number of CSR IPs do not match identifiers exactly: "+ + return sans, NewError(ErrorBadCSRType, "CSR IPs do not match identifiers exactly: "+ "CSR IPs = %v, Order IPs = %v", csr.IPAddresses, orderIPs) } From 84ea8bd67aa18909ff6d78b7b46eb926cb584076 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 18 Jun 2021 12:03:46 +0200 Subject: [PATCH 75/89] Fix PR comments --- acme/api/order_test.go | 18 +++++++++++++++++- acme/order.go | 17 ++++++++++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/acme/api/order_test.go b/acme/api/order_test.go index 6782de75..a0096419 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -45,6 +45,22 @@ func TestNewOrderRequest_Validate(t *testing.T) { err: acme.NewError(acme.ErrorMalformedType, "identifier type unsupported: foo"), } }, + "fail/bad-ip": func(t *testing.T) test { + nbf := time.Now().UTC().Add(time.Minute) + naf := time.Now().UTC().Add(5 * time.Minute) + return test{ + nor: &NewOrderRequest{ + Identifiers: []acme.Identifier{ + {Type: "ip", Value: "192.168.42.1000"}, + }, + NotAfter: naf, + NotBefore: nbf, + }, + nbf: nbf, + naf: naf, + err: acme.NewError(acme.ErrorMalformedType, "invalid IP address: %s", "192.168.42.1000"), + } + }, "ok": func(t *testing.T) test { nbf := time.Now().UTC().Add(time.Minute) naf := time.Now().UTC().Add(5 * time.Minute) @@ -91,7 +107,7 @@ func TestNewOrderRequest_Validate(t *testing.T) { naf: naf, } }, - "ok/mixed-dns-and-ipv4": func(t *testing.T) test { // TODO: verify that this is allowed and what we want to be possible (in Validate()) + "ok/mixed-dns-and-ipv4": func(t *testing.T) test { nbf := time.Now().UTC().Add(time.Minute) naf := time.Now().UTC().Add(5 * time.Minute) return test{ diff --git a/acme/order.go b/acme/order.go index 73d5e636..86b3c43a 100644 --- a/acme/order.go +++ b/acme/order.go @@ -14,10 +14,17 @@ import ( "go.step.sm/crypto/x509util" ) +type IdentifierType string + +const ( + IP IdentifierType = "ip" + DNS IdentifierType = "dns" +) + // Identifier encodes the type that an order pertains to. type Identifier struct { - Type string `json:"type"` - Value string `json:"value"` + Type IdentifierType `json:"type"` + Value string `json:"value"` } // Order contains order metadata for the ACME protocol order type. @@ -222,7 +229,7 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ // Validate identifier names against CSR alternative names. // // Note that with certificate templates we are not going to check for the - // absence of other SANs as they will only be set if the templates allows + // absence of other SANs as they will only be set if the template allows // them. if len(csr.DNSNames) != len(orderNames) { return sans, NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+ @@ -263,7 +270,7 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ // numberOfIdentifierType returns the number of Identifiers that // are of type typ. -func numberOfIdentifierType(typ string, ids []Identifier) int { +func numberOfIdentifierType(typ IdentifierType, ids []Identifier) int { c := 0 for _, id := range ids { if id.Type == typ { @@ -305,7 +312,7 @@ func ipsAreEqual(x, y net.IP) bool { return false } -// matchAddrFamily returns if two IPs are both IPv4 OR IPv6 +// matchAddrFamily returns true if two IPs are both IPv4 OR IPv6 // Implementation taken and adapted from https://golang.org/src/net/ip.go func matchAddrFamily(x net.IP, y net.IP) bool { return x.To4() != nil && y.To4() != nil || x.To16() != nil && x.To4() == nil && y.To16() != nil && y.To4() == nil From 523ae96749cdd187d6228646d6d7f6397da4a0da Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 18 Jun 2021 12:39:36 +0200 Subject: [PATCH 76/89] Change identifier and challenge types to consts --- acme/api/handler_test.go | 10 +++--- acme/api/order.go | 20 ++++++------ acme/api/order_test.go | 66 +++++++++++++++++++------------------- acme/challenge.go | 34 ++++++++++++-------- acme/db/nosql/challenge.go | 18 +++++------ 5 files changed, 78 insertions(+), 70 deletions(-) diff --git a/acme/api/handler_test.go b/acme/api/handler_test.go index 5501479d..f354bbac 100644 --- a/acme/api/handler_test.go +++ b/acme/api/handler_test.go @@ -574,13 +574,13 @@ func TestHandler_GetChallenge(t *testing.T) { assert.Equals(t, azID, "authzID") return &acme.Challenge{ Status: acme.StatusPending, - Type: "http-01", + Type: acme.HTTP01, AccountID: "accID", }, nil }, MockUpdateChallenge: func(ctx context.Context, ch *acme.Challenge) error { assert.Equals(t, ch.Status, acme.StatusPending) - assert.Equals(t, ch.Type, "http-01") + assert.Equals(t, ch.Type, acme.HTTP01) assert.Equals(t, ch.AccountID, "accID") assert.Equals(t, ch.AuthorizationID, "authzID") assert.HasSuffix(t, ch.Error.Type, acme.ErrorConnectionType.String()) @@ -616,13 +616,13 @@ func TestHandler_GetChallenge(t *testing.T) { return &acme.Challenge{ ID: "chID", Status: acme.StatusPending, - Type: "http-01", + Type: acme.HTTP01, AccountID: "accID", }, nil }, MockUpdateChallenge: func(ctx context.Context, ch *acme.Challenge) error { assert.Equals(t, ch.Status, acme.StatusPending) - assert.Equals(t, ch.Type, "http-01") + assert.Equals(t, ch.Type, acme.HTTP01) assert.Equals(t, ch.AccountID, "accID") assert.Equals(t, ch.AuthorizationID, "authzID") assert.HasSuffix(t, ch.Error.Type, acme.ErrorConnectionType.String()) @@ -633,7 +633,7 @@ func TestHandler_GetChallenge(t *testing.T) { ID: "chID", Status: acme.StatusPending, AuthorizationID: "authzID", - Type: "http-01", + Type: acme.HTTP01, AccountID: "accID", URL: url, Error: acme.NewError(acme.ErrorConnectionType, "force"), diff --git a/acme/api/order.go b/acme/api/order.go index 936e9573..9cf2c1eb 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -29,10 +29,10 @@ func (n *NewOrderRequest) Validate() error { return acme.NewError(acme.ErrorMalformedType, "identifiers list cannot be empty") } for _, id := range n.Identifiers { - if !(id.Type == "dns" || id.Type == "ip") { + if !(id.Type == acme.DNS || id.Type == acme.IP) { return acme.NewError(acme.ErrorMalformedType, "identifier type unsupported: %s", id.Type) } - if id.Type == "ip" && net.ParseIP(id.Value) == nil { + if id.Type == acme.IP && net.ParseIP(id.Value) == nil { return acme.NewError(acme.ErrorMalformedType, "invalid IP address: %s", id.Value) } } @@ -277,20 +277,20 @@ func (h *Handler) FinalizeOrder(w http.ResponseWriter, r *http.Request) { // challengeTypes determines the types of challenges that should be used // for the ACME authorization request. -func challengeTypes(az *acme.Authorization) []string { - var chTypes []string +func challengeTypes(az *acme.Authorization) []acme.ChallengeType { + var chTypes []acme.ChallengeType switch az.Identifier.Type { - case "ip": // TODO: make these types consts/enum? - chTypes = []string{"http-01", "tls-alpn-01"} - case "dns": - chTypes = []string{"dns-01"} + case acme.IP: + chTypes = []acme.ChallengeType{acme.HTTP01, acme.TLSALPN01} + case acme.DNS: + chTypes = []acme.ChallengeType{acme.DNS01} // HTTP and TLS challenges can only be used for identifiers without wildcards. if !az.Wildcard { - chTypes = append(chTypes, []string{"http-01", "tls-alpn-01"}...) + chTypes = append(chTypes, []acme.ChallengeType{acme.HTTP01, acme.TLSALPN01}...) } default: - chTypes = []string{} + chTypes = []acme.ChallengeType{} } return chTypes diff --git a/acme/api/order_test.go b/acme/api/order_test.go index a0096419..38c4c3f0 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -474,7 +474,7 @@ func TestHandler_newAuthorization(t *testing.T) { db: &acme.MockDB{ MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error { assert.Equals(t, ch.AccountID, az.AccountID) - assert.Equals(t, ch.Type, "dns-01") + assert.Equals(t, ch.Type, acme.DNS01) assert.Equals(t, ch.Token, az.Token) assert.Equals(t, ch.Status, acme.StatusPending) assert.Equals(t, ch.Value, az.Identifier.Value) @@ -503,15 +503,15 @@ func TestHandler_newAuthorization(t *testing.T) { switch count { case 0: ch.ID = "dns" - assert.Equals(t, ch.Type, "dns-01") + assert.Equals(t, ch.Type, acme.DNS01) ch1 = &ch case 1: ch.ID = "http" - assert.Equals(t, ch.Type, "http-01") + assert.Equals(t, ch.Type, acme.HTTP01) ch2 = &ch case 2: ch.ID = "tls" - assert.Equals(t, ch.Type, "tls-alpn-01") + assert.Equals(t, ch.Type, acme.TLSALPN01) ch3 = &ch default: assert.FatalError(t, errors.New("test logic error")) @@ -557,15 +557,15 @@ func TestHandler_newAuthorization(t *testing.T) { switch count { case 0: ch.ID = "dns" - assert.Equals(t, ch.Type, "dns-01") + assert.Equals(t, ch.Type, acme.DNS01) ch1 = &ch case 1: ch.ID = "http" - assert.Equals(t, ch.Type, "http-01") + assert.Equals(t, ch.Type, acme.HTTP01) ch2 = &ch case 2: ch.ID = "tls" - assert.Equals(t, ch.Type, "tls-alpn-01") + assert.Equals(t, ch.Type, acme.TLSALPN01) ch3 = &ch default: assert.FatalError(t, errors.New("test logic error")) @@ -607,7 +607,7 @@ func TestHandler_newAuthorization(t *testing.T) { db: &acme.MockDB{ MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error { ch.ID = "dns" - assert.Equals(t, ch.Type, "dns-01") + assert.Equals(t, ch.Type, acme.DNS01) assert.Equals(t, ch.AccountID, az.AccountID) assert.Equals(t, ch.Token, az.Token) assert.Equals(t, ch.Status, acme.StatusPending) @@ -774,7 +774,7 @@ func TestHandler_NewOrder(t *testing.T) { db: &acme.MockDB{ MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error { assert.Equals(t, ch.AccountID, "accID") - assert.Equals(t, ch.Type, "dns-01") + assert.Equals(t, ch.Type, acme.DNS01) assert.NotEquals(t, ch.Token, "") assert.Equals(t, ch.Status, acme.StatusPending) assert.Equals(t, ch.Value, "zap.internal") @@ -809,15 +809,15 @@ func TestHandler_NewOrder(t *testing.T) { switch count { case 0: ch.ID = "dns" - assert.Equals(t, ch.Type, "dns-01") + assert.Equals(t, ch.Type, acme.DNS01) ch1 = &ch case 1: ch.ID = "http" - assert.Equals(t, ch.Type, "http-01") + assert.Equals(t, ch.Type, acme.HTTP01) ch2 = &ch case 2: ch.ID = "tls" - assert.Equals(t, ch.Type, "tls-alpn-01") + assert.Equals(t, ch.Type, acme.TLSALPN01) ch3 = &ch default: assert.FatalError(t, errors.New("test logic error")) @@ -881,22 +881,22 @@ func TestHandler_NewOrder(t *testing.T) { switch chCount { case 0: ch.ID = "dns" - assert.Equals(t, ch.Type, "dns-01") + assert.Equals(t, ch.Type, acme.DNS01) assert.Equals(t, ch.Value, "zap.internal") ch1 = &ch case 1: ch.ID = "http" - assert.Equals(t, ch.Type, "http-01") + assert.Equals(t, ch.Type, acme.HTTP01) assert.Equals(t, ch.Value, "zap.internal") ch2 = &ch case 2: ch.ID = "tls" - assert.Equals(t, ch.Type, "tls-alpn-01") + assert.Equals(t, ch.Type, acme.TLSALPN01) assert.Equals(t, ch.Value, "zap.internal") ch3 = &ch case 3: ch.ID = "dns" - assert.Equals(t, ch.Type, "dns-01") + assert.Equals(t, ch.Type, acme.DNS01) assert.Equals(t, ch.Value, "zar.internal") ch4 = &ch default: @@ -921,7 +921,7 @@ func TestHandler_NewOrder(t *testing.T) { az.ID = "az2ID" az2ID = &az.ID assert.Equals(t, az.Identifier, acme.Identifier{ - Type: "dns", + Type: acme.DNS, Value: "zar.internal", }) assert.Equals(t, az.Wildcard, true) @@ -996,15 +996,15 @@ func TestHandler_NewOrder(t *testing.T) { switch count { case 0: ch.ID = "dns" - assert.Equals(t, ch.Type, "dns-01") + assert.Equals(t, ch.Type, acme.DNS01) ch1 = &ch case 1: ch.ID = "http" - assert.Equals(t, ch.Type, "http-01") + assert.Equals(t, ch.Type, acme.HTTP01) ch2 = &ch case 2: ch.ID = "tls" - assert.Equals(t, ch.Type, "tls-alpn-01") + assert.Equals(t, ch.Type, acme.TLSALPN01) ch3 = &ch default: assert.FatalError(t, errors.New("test logic error")) @@ -1088,15 +1088,15 @@ func TestHandler_NewOrder(t *testing.T) { switch count { case 0: ch.ID = "dns" - assert.Equals(t, ch.Type, "dns-01") + assert.Equals(t, ch.Type, acme.DNS01) ch1 = &ch case 1: ch.ID = "http" - assert.Equals(t, ch.Type, "http-01") + assert.Equals(t, ch.Type, acme.HTTP01) ch2 = &ch case 2: ch.ID = "tls" - assert.Equals(t, ch.Type, "tls-alpn-01") + assert.Equals(t, ch.Type, acme.TLSALPN01) ch3 = &ch default: assert.FatalError(t, errors.New("test logic error")) @@ -1179,15 +1179,15 @@ func TestHandler_NewOrder(t *testing.T) { switch count { case 0: ch.ID = "dns" - assert.Equals(t, ch.Type, "dns-01") + assert.Equals(t, ch.Type, acme.DNS01) ch1 = &ch case 1: ch.ID = "http" - assert.Equals(t, ch.Type, "http-01") + assert.Equals(t, ch.Type, acme.HTTP01) ch2 = &ch case 2: ch.ID = "tls" - assert.Equals(t, ch.Type, "tls-alpn-01") + assert.Equals(t, ch.Type, acme.TLSALPN01) ch3 = &ch default: assert.FatalError(t, errors.New("test logic error")) @@ -1271,15 +1271,15 @@ func TestHandler_NewOrder(t *testing.T) { switch count { case 0: ch.ID = "dns" - assert.Equals(t, ch.Type, "dns-01") + assert.Equals(t, ch.Type, acme.DNS01) ch1 = &ch case 1: ch.ID = "http" - assert.Equals(t, ch.Type, "http-01") + assert.Equals(t, ch.Type, acme.HTTP01) ch2 = &ch case 2: ch.ID = "tls" - assert.Equals(t, ch.Type, "tls-alpn-01") + assert.Equals(t, ch.Type, acme.TLSALPN01) ch3 = &ch default: assert.FatalError(t, errors.New("test logic error")) @@ -1668,7 +1668,7 @@ func TestHandler_challengeTypes(t *testing.T) { tests := []struct { name string args args - want []string + want []acme.ChallengeType }{ { name: "ok/dns", @@ -1678,7 +1678,7 @@ func TestHandler_challengeTypes(t *testing.T) { Wildcard: false, }, }, - want: []string{"dns-01", "http-01", "tls-alpn-01"}, + want: []acme.ChallengeType{acme.DNS01, acme.HTTP01, acme.TLSALPN01}, //[]string{"dns-01", "http-01", "tls-alpn-01"}, }, { name: "ok/wildcard", @@ -1688,7 +1688,7 @@ func TestHandler_challengeTypes(t *testing.T) { Wildcard: true, }, }, - want: []string{"dns-01"}, + want: []acme.ChallengeType{acme.DNS01}, }, { name: "ok/ip", @@ -1698,7 +1698,7 @@ func TestHandler_challengeTypes(t *testing.T) { Wildcard: false, }, }, - want: []string{"http-01", "tls-alpn-01"}, + want: []acme.ChallengeType{acme.HTTP01, acme.TLSALPN01}, }, } for _, tt := range tests { diff --git a/acme/challenge.go b/acme/challenge.go index cb15a188..706d12a1 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -21,18 +21,26 @@ import ( "go.step.sm/crypto/jose" ) +type ChallengeType string + +const ( + HTTP01 ChallengeType = "http-01" + DNS01 ChallengeType = "dns-01" + TLSALPN01 ChallengeType = "tls-alpn-01" +) + // Challenge represents an ACME response Challenge type. type Challenge struct { - ID string `json:"-"` - AccountID string `json:"-"` - AuthorizationID string `json:"-"` - Value string `json:"-"` - Type string `json:"type"` - Status Status `json:"status"` - Token string `json:"token"` - ValidatedAt string `json:"validated,omitempty"` - URL string `json:"url"` - Error *Error `json:"error,omitempty"` + ID string `json:"-"` + AccountID string `json:"-"` + AuthorizationID string `json:"-"` + Value string `json:"-"` + Type ChallengeType `json:"type"` + Status Status `json:"status"` + Token string `json:"token"` + ValidatedAt string `json:"validated,omitempty"` + URL string `json:"url"` + Error *Error `json:"error,omitempty"` } // ToLog enables response logging. @@ -54,11 +62,11 @@ func (ch *Challenge) Validate(ctx context.Context, db DB, jwk *jose.JSONWebKey, return nil } switch ch.Type { - case "http-01": + case HTTP01: return http01Validate(ctx, ch, db, jwk, vo) - case "dns-01": + case DNS01: return dns01Validate(ctx, ch, db, jwk, vo) - case "tls-alpn-01": + case TLSALPN01: return tlsalpn01Validate(ctx, ch, db, jwk, vo) default: return NewErrorISE("unexpected challenge type '%s'", ch.Type) diff --git a/acme/db/nosql/challenge.go b/acme/db/nosql/challenge.go index f3a3cfca..f84a6f4e 100644 --- a/acme/db/nosql/challenge.go +++ b/acme/db/nosql/challenge.go @@ -11,15 +11,15 @@ import ( ) type dbChallenge struct { - ID string `json:"id"` - AccountID string `json:"accountID"` - Type string `json:"type"` - Status acme.Status `json:"status"` - Token string `json:"token"` - Value string `json:"value"` - ValidatedAt string `json:"validatedAt"` - CreatedAt time.Time `json:"createdAt"` - Error *acme.Error `json:"error"` + ID string `json:"id"` + AccountID string `json:"accountID"` + Type acme.ChallengeType `json:"type"` + Status acme.Status `json:"status"` + Token string `json:"token"` + Value string `json:"value"` + ValidatedAt string `json:"validatedAt"` + CreatedAt time.Time `json:"createdAt"` + Error *acme.Error `json:"error"` } func (dbc *dbChallenge) clone() *dbChallenge { From f33bdee5e06770fd4b1c1aae0f856789444f1a39 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 18 Jun 2021 12:55:50 +0200 Subject: [PATCH 77/89] Fix linter issue S1025 --- api/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/api.go b/api/api.go index 6a0a7e8f..5cf9f593 100644 --- a/api/api.go +++ b/api/api.go @@ -417,7 +417,7 @@ func LogCertificate(w http.ResponseWriter, cert *x509.Certificate) { if len(val.CredentialID) > 0 { m["provisioner"] = fmt.Sprintf("%s (%s)", val.Name, val.CredentialID) } else { - m["provisioner"] = fmt.Sprintf("%s", val.Name) + m["provisioner"] = string(val.Name) } break } From db416a45aea466a8c7fde7fae4ef71c23e3d4c71 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 8 Jun 2021 18:02:54 -0700 Subject: [PATCH 78/89] Fix path for labeler. --- .github/workflows/labeler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index f0011406..72b01a92 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -11,4 +11,4 @@ jobs: - uses: actions/labeler@v3 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" - configuration-path: .github/needs-triage-labeler.yml + configuration-path: .github/labeler.yml From 218a2adb9fc5778678fff7b323ee4943e2d4d7a7 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 18 Jun 2021 16:09:48 +0200 Subject: [PATCH 79/89] Add tests for IP Order validations --- acme/order.go | 12 +- acme/order_test.go | 375 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 380 insertions(+), 7 deletions(-) diff --git a/acme/order.go b/acme/order.go index 86b3c43a..71884603 100644 --- a/acme/order.go +++ b/acme/order.go @@ -199,15 +199,15 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ var sans []x509util.SubjectAlternativeName // order the DNS names and IP addresses, so that they can be compared against the canonicalized CSR - orderNames := make([]string, numberOfIdentifierType("dns", o.Identifiers)) - orderIPs := make([]net.IP, numberOfIdentifierType("ip", o.Identifiers)) + orderNames := make([]string, numberOfIdentifierType(DNS, o.Identifiers)) + orderIPs := make([]net.IP, numberOfIdentifierType(IP, o.Identifiers)) indexDNS, indexIP := 0, 0 for _, n := range o.Identifiers { switch n.Type { - case "dns": + case DNS: orderNames[indexDNS] = n.Value indexDNS++ - case "ip": + case IP: orderIPs[indexIP] = net.ParseIP(n.Value) // NOTE: this assumes are all valid IPs at this time; or will result in nil entries indexIP++ default: @@ -303,8 +303,8 @@ func canonicalize(csr *x509.CertificateRequest) (canonicalized *x509.Certificate } // ipsAreEqual compares IPs to be equal. IPv6 representations of IPv4 -// adresses are NOT considered equal to the IPv4 address in this case. -// Both IPs should be the same version AND equal to each other. +// adresses are considered equal to the IPv4 address in this case. +// TODO: is this behavior OK to keep? func ipsAreEqual(x, y net.IP) bool { if matchAddrFamily(x, y) { return x.Equal(y) diff --git a/acme/order_test.go b/acme/order_test.go index c0461dc6..e1d3744b 100644 --- a/acme/order_test.go +++ b/acme/order_test.go @@ -393,6 +393,31 @@ func TestOrder_Finalize(t *testing.T) { "CSR names = %v, Order names = %v", []string{"foo.internal"}, orderNames), } }, + "fail/error-ips-length-mismatch": func(t *testing.T) test { + now := clock.Now() + o := &Order{ + ID: "oID", + AccountID: "accID", + Status: StatusReady, + ExpiresAt: now.Add(5 * time.Minute), + AuthorizationIDs: []string{"a", "b"}, + Identifiers: []Identifier{ + {Type: "ip", Value: "192.168.42.42"}, + {Type: "ip", Value: "192.168.43.42"}, + }, + } + orderIPs := []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.43.42")} + csr := &x509.CertificateRequest{ + IPAddresses: []net.IP{net.ParseIP("192.168.42.42")}, + } + + return test{ + o: o, + csr: csr, + err: NewError(ErrorBadCSRType, "CSR IPs do not match identifiers exactly: "+ + "CSR IPs = %v, Order IPs = %v", []net.IP{net.ParseIP("192.168.42.42")}, orderIPs), + } + }, "fail/error-names-mismatch": func(t *testing.T) test { now := clock.Now() o := &Order{ @@ -421,6 +446,31 @@ func TestOrder_Finalize(t *testing.T) { "CSR names = %v, Order names = %v", []string{"foo.internal", "zap.internal"}, orderNames), } }, + "fail/error-ips-mismatch": func(t *testing.T) test { + now := clock.Now() + o := &Order{ + ID: "oID", + AccountID: "accID", + Status: StatusReady, + ExpiresAt: now.Add(5 * time.Minute), + AuthorizationIDs: []string{"a", "b"}, + Identifiers: []Identifier{ + {Type: "ip", Value: "192.168.42.42"}, + {Type: "ip", Value: "192.168.43.42"}, + }, + } + orderIPs := []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.43.42")} + csr := &x509.CertificateRequest{ + IPAddresses: []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.42.32")}, + } + + return test{ + o: o, + csr: csr, + err: NewError(ErrorBadCSRType, "CSR IPs do not match identifiers exactly: "+ + "CSR IPs = %v, Order IPs = %v", []net.IP{net.ParseIP("192.168.42.32"), net.ParseIP("192.168.42.42")}, orderIPs), + } + }, "fail/error-provisioner-auth": func(t *testing.T) test { now := clock.Now() o := &Order{ @@ -652,7 +702,7 @@ func TestOrder_Finalize(t *testing.T) { err: NewErrorISE("error updating order oID: force"), } }, - "ok/new-cert": func(t *testing.T) test { + "ok/new-cert-dns": func(t *testing.T) test { now := clock.Now() o := &Order{ ID: "oID", @@ -676,6 +726,131 @@ func TestOrder_Finalize(t *testing.T) { bar := &x509.Certificate{Subject: pkix.Name{CommonName: "bar"}} baz := &x509.Certificate{Subject: pkix.Name{CommonName: "baz"}} + return test{ + o: o, + csr: csr, + prov: &MockProvisioner{ + MauthorizeSign: func(ctx context.Context, token string) ([]provisioner.SignOption, error) { + assert.Equals(t, token, "") + return nil, nil + }, + MgetOptions: func() *provisioner.Options { + return nil + }, + }, + ca: &mockSignAuth{ + sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + assert.Equals(t, _csr, csr) + return []*x509.Certificate{foo, bar, baz}, nil + }, + }, + db: &MockDB{ + MockCreateCertificate: func(ctx context.Context, cert *Certificate) error { + cert.ID = "certID" + assert.Equals(t, cert.AccountID, o.AccountID) + assert.Equals(t, cert.OrderID, o.ID) + assert.Equals(t, cert.Leaf, foo) + assert.Equals(t, cert.Intermediates, []*x509.Certificate{bar, baz}) + return nil + }, + MockUpdateOrder: func(ctx context.Context, updo *Order) error { + assert.Equals(t, updo.CertificateID, "certID") + assert.Equals(t, updo.Status, StatusValid) + assert.Equals(t, updo.ID, o.ID) + assert.Equals(t, updo.AccountID, o.AccountID) + assert.Equals(t, updo.ExpiresAt, o.ExpiresAt) + assert.Equals(t, updo.AuthorizationIDs, o.AuthorizationIDs) + assert.Equals(t, updo.Identifiers, o.Identifiers) + return nil + }, + }, + } + }, + "ok/new-cert-ip": func(t *testing.T) test { + now := clock.Now() + o := &Order{ + ID: "oID", + AccountID: "accID", + Status: StatusReady, + ExpiresAt: now.Add(5 * time.Minute), + AuthorizationIDs: []string{"a", "b"}, + Identifiers: []Identifier{ + {Type: "ip", Value: "192.168.42.42"}, + {Type: "ip", Value: "192.168.43.42"}, + }, + } + csr := &x509.CertificateRequest{ + IPAddresses: []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.43.42")}, // in case of IPs, no Common Name + } + + foo := &x509.Certificate{Subject: pkix.Name{CommonName: "foo"}} + bar := &x509.Certificate{Subject: pkix.Name{CommonName: "bar"}} + baz := &x509.Certificate{Subject: pkix.Name{CommonName: "baz"}} + + return test{ + o: o, + csr: csr, + prov: &MockProvisioner{ + MauthorizeSign: func(ctx context.Context, token string) ([]provisioner.SignOption, error) { + assert.Equals(t, token, "") + return nil, nil + }, + MgetOptions: func() *provisioner.Options { + return nil + }, + }, + ca: &mockSignAuth{ + sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + assert.Equals(t, _csr, csr) + return []*x509.Certificate{foo, bar, baz}, nil + }, + }, + db: &MockDB{ + MockCreateCertificate: func(ctx context.Context, cert *Certificate) error { + cert.ID = "certID" + assert.Equals(t, cert.AccountID, o.AccountID) + assert.Equals(t, cert.OrderID, o.ID) + assert.Equals(t, cert.Leaf, foo) + assert.Equals(t, cert.Intermediates, []*x509.Certificate{bar, baz}) + return nil + }, + MockUpdateOrder: func(ctx context.Context, updo *Order) error { + assert.Equals(t, updo.CertificateID, "certID") + assert.Equals(t, updo.Status, StatusValid) + assert.Equals(t, updo.ID, o.ID) + assert.Equals(t, updo.AccountID, o.AccountID) + assert.Equals(t, updo.ExpiresAt, o.ExpiresAt) + assert.Equals(t, updo.AuthorizationIDs, o.AuthorizationIDs) + assert.Equals(t, updo.Identifiers, o.Identifiers) + return nil + }, + }, + } + }, + "ok/new-cert-dns-and-ip": func(t *testing.T) test { + now := clock.Now() + o := &Order{ + ID: "oID", + AccountID: "accID", + Status: StatusReady, + ExpiresAt: now.Add(5 * time.Minute), + AuthorizationIDs: []string{"a", "b"}, + Identifiers: []Identifier{ + {Type: "dns", Value: "foo.internal"}, + {Type: "ip", Value: "192.168.42.42"}, + }, + } + csr := &x509.CertificateRequest{ + Subject: pkix.Name{ + CommonName: "foo.internal", + }, + IPAddresses: []net.IP{net.ParseIP("192.168.42.42")}, + } + + foo := &x509.Certificate{Subject: pkix.Name{CommonName: "foo"}} + bar := &x509.Certificate{Subject: pkix.Name{CommonName: "bar"}} + baz := &x509.Certificate{Subject: pkix.Name{CommonName: "baz"}} + return test{ o: o, csr: csr, @@ -814,3 +989,201 @@ func Test_uniqueSortedIPs(t *testing.T) { }) } } + +func Test_numberOfIdentifierType(t *testing.T) { + type args struct { + typ IdentifierType + ids []Identifier + } + tests := []struct { + name string + args args + want int + }{ + { + name: "ok/no-identifiers", + args: args{ + typ: DNS, + ids: []Identifier{}, + }, + want: 0, + }, + { + name: "ok/no-dns", + args: args{ + typ: DNS, + ids: []Identifier{ + { + Type: IP, + Value: "192.168.42.42", + }, + }, + }, + want: 0, + }, + { + name: "ok/no-ips", + args: args{ + typ: IP, + ids: []Identifier{ + { + Type: DNS, + Value: "example.com", + }, + }, + }, + want: 0, + }, + { + name: "ok/one-dns", + args: args{ + typ: DNS, + ids: []Identifier{ + { + Type: DNS, + Value: "example.com", + }, + { + Type: IP, + Value: "192.168.42.42", + }, + }, + }, + want: 1, + }, + { + name: "ok/one-ip", + args: args{ + typ: IP, + ids: []Identifier{ + { + Type: DNS, + Value: "example.com", + }, + { + Type: IP, + Value: "192.168.42.42", + }, + }, + }, + want: 1, + }, + { + name: "ok/more-dns", + args: args{ + typ: DNS, + ids: []Identifier{ + { + Type: DNS, + Value: "example.com", + }, + { + Type: DNS, + Value: "*.example.com", + }, + { + Type: IP, + Value: "192.168.42.42", + }, + }, + }, + want: 2, + }, + { + name: "ok/more-ips", + args: args{ + typ: IP, + ids: []Identifier{ + { + Type: DNS, + Value: "example.com", + }, + { + Type: IP, + Value: "192.168.42.42", + }, + { + Type: IP, + Value: "192.168.42.43", + }, + }, + }, + want: 2, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := numberOfIdentifierType(tt.args.typ, tt.args.ids); got != tt.want { + t.Errorf("numberOfIdentifierType() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_ipsAreEqual(t *testing.T) { + type args struct { + x net.IP + y net.IP + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "ok/ipv4", + args: args{ + x: net.ParseIP("192.168.42.42"), + y: net.ParseIP("192.168.42.42"), + }, + want: true, + }, + { + name: "ok/ipv6", + args: args{ + x: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), + y: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), + }, + want: true, + }, + { + name: "ok/ipv4-and-ipv6", + args: args{ + x: net.ParseIP("192.168.42.42"), + y: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), + }, + want: false, + }, + { + name: "ok/ipv4-mapped-to-ipv6", + args: args{ + x: net.ParseIP("192.168.42.42"), + y: net.ParseIP("::ffff:192.168.42.42"), // parsed to the same IPv4 by Go + }, + want: true, // TODO: is this behavior OK? + }, + { + name: "ok/invalid-ipv4-and-valid-ipv6", + args: args{ + x: net.ParseIP("192.168.42.1000"), + y: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), + }, + want: false, + }, + { + name: "ok/invalid-ipv4-and-invalid-ipv6", + args: args{ + x: net.ParseIP("192.168.42.1000"), + y: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:1000000"), + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ipsAreEqual(tt.args.x, tt.args.y); got != tt.want { + t.Errorf("ipsAreEqual() = %v, want %v", got, tt.want) + } + }) + } +} From 135e912ac8fe118fe1cf22dd49777b2813b9e7ba Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 18 Jun 2021 17:27:35 +0200 Subject: [PATCH 80/89] Improve coverage for TLS-ALPN-01 challenge --- acme/challenge.go | 31 ++++++----- acme/challenge_test.go | 117 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 14 deletions(-) diff --git a/acme/challenge.go b/acme/challenge.go index 706d12a1..fe643c85 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -115,25 +115,13 @@ func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWeb } func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, vo *ValidateChallengeOptions) error { - - // RFC8738 states that, if HostName is IP, it should be the ARPA - // address https://datatracker.ietf.org/doc/html/rfc8738#section-6. - // It also references TLS Extensions [RFC6066]. - var serverName string - ip := net.ParseIP(ch.Value) - if ip != nil { - serverName = reverseAddr(ip) - } else { - serverName = ch.Value - } - config := &tls.Config{ NextProtos: []string{"acme-tls/1"}, // https://tools.ietf.org/html/rfc8737#section-4 // ACME servers that implement "acme-tls/1" MUST only negotiate TLS 1.2 // [RFC5246] or higher when connecting to clients for validation. MinVersion: tls.VersionTLS12, - ServerName: serverName, + ServerName: serverName(ch), InsecureSkipVerify: true, // we expect a self-signed challenge certificate } @@ -163,7 +151,7 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON // if no DNS names present, look for IP address and verify that exactly one exists if len(leafCert.DNSNames) == 0 { - if len(leafCert.IPAddresses) != 1 || !leafCert.IPAddresses[0].Equal(ip) { + if len(leafCert.IPAddresses) != 1 || !leafCert.IPAddresses[0].Equal(net.ParseIP(ch.Value)) { return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single IP address or DNS name, %v", ch.Value)) } @@ -272,6 +260,21 @@ func dns01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebK return nil } +// serverName determines the SNI HostName to set based on an acme.Challenge +// for TLS-ALPN-01 challenges. RFC8738 states that, if HostName is an IP, it +// should be the ARPA address https://datatracker.ietf.org/doc/html/rfc8738#section-6. +// It also references TLS Extensions [RFC6066]. +func serverName(ch *Challenge) string { + var serverName string + ip := net.ParseIP(ch.Value) + if ip != nil { + serverName = reverseAddr(ip) + } else { + serverName = ch.Value + } + return serverName +} + // reverseaddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP // address addr suitable for rDNS (PTR) record lookup or an error if it fails // to parse the IP address. diff --git a/acme/challenge_test.go b/acme/challenge_test.go index 8e03a414..727e9ef3 100644 --- a/acme/challenge_test.go +++ b/acme/challenge_test.go @@ -2187,6 +2187,43 @@ func TestTLSALPN01Validate(t *testing.T) { srv, tlsDial := newTestTLSALPNServer(cert) srv.Start() + return test{ + ch: ch, + vo: &ValidateChallengeOptions{ + TLSDial: tlsDial, + }, + db: &MockDB{ + MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error { + assert.Equals(t, updch.ID, ch.ID) + assert.Equals(t, updch.Token, ch.Token) + assert.Equals(t, updch.Status, StatusValid) + assert.Equals(t, updch.Type, ch.Type) + assert.Equals(t, updch.Value, ch.Value) + assert.Equals(t, updch.Error, nil) + return nil + }, + }, + srv: srv, + jwk: jwk, + } + }, + "ok/ip": func(t *testing.T) test { + ch := makeTLSCh() + ch.Value = "127.0.0.1" + + jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) + assert.FatalError(t, err) + + expKeyAuth, err := KeyAuthorization(ch.Token, jwk) + assert.FatalError(t, err) + expKeyAuthHash := sha256.Sum256([]byte(expKeyAuth)) + + cert, err := newTLSALPNValidationCert(expKeyAuthHash[:], false, true, ch.Value) + assert.FatalError(t, err) + + srv, tlsDial := newTestTLSALPNServer(cert) + srv.Start() + return test{ ch: ch, vo: &ValidateChallengeOptions{ @@ -2234,4 +2271,84 @@ func TestTLSALPN01Validate(t *testing.T) { } }) } + t.Fail() +} + +func Test_reverseAddr(t *testing.T) { + type args struct { + ip net.IP + } + tests := []struct { + name string + args args + wantArpa string + }{ + { + name: "ok/ipv4", + args: args{ + ip: net.ParseIP("127.0.0.1"), + }, + wantArpa: "1.0.0.127.in-addr.arpa.", + }, + { + name: "ok/ipv6", + args: args{ + ip: net.ParseIP("2001:db8::567:89ab"), + }, + wantArpa: "b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotArpa := reverseAddr(tt.args.ip); gotArpa != tt.wantArpa { + t.Errorf("reverseAddr() = %v, want %v", gotArpa, tt.wantArpa) + } + }) + } +} + +func Test_serverName(t *testing.T) { + type args struct { + ch *Challenge + } + tests := []struct { + name string + args args + want string + }{ + { + name: "ok/dns", + args: args{ + ch: &Challenge{ + Value: "example.com", + }, + }, + want: "example.com", + }, + { + name: "ok/ipv4", + args: args{ + ch: &Challenge{ + Value: "127.0.0.1", + }, + }, + want: "1.0.0.127.in-addr.arpa.", + }, + { + name: "ok/ipv4", + args: args{ + ch: &Challenge{ + Value: "2001:db8::567:89ab", + }, + }, + want: "b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := serverName(tt.args.ch); got != tt.want { + t.Errorf("serverName() = %v, want %v", got, tt.want) + } + }) + } } From c514a187b287abb3ad94610c671a42b25f78696c Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 18 Jun 2021 17:37:56 +0200 Subject: [PATCH 81/89] Fix Fail() -_-b --- acme/challenge_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/acme/challenge_test.go b/acme/challenge_test.go index 727e9ef3..423951e8 100644 --- a/acme/challenge_test.go +++ b/acme/challenge_test.go @@ -2271,7 +2271,6 @@ func TestTLSALPN01Validate(t *testing.T) { } }) } - t.Fail() } func Test_reverseAddr(t *testing.T) { From 64c15fde7eb1358546ecf4f9692042ae65226424 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 25 Jun 2021 14:07:40 +0200 Subject: [PATCH 82/89] Add tests for canonicalize function --- acme/api/order_test.go | 3 +- acme/challenge.go | 2 +- acme/challenge_test.go | 2 +- acme/order.go | 3 - acme/order_test.go | 153 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 156 insertions(+), 7 deletions(-) diff --git a/acme/api/order_test.go b/acme/api/order_test.go index 38c4c3f0..afb23c3f 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -1678,7 +1678,7 @@ func TestHandler_challengeTypes(t *testing.T) { Wildcard: false, }, }, - want: []acme.ChallengeType{acme.DNS01, acme.HTTP01, acme.TLSALPN01}, //[]string{"dns-01", "http-01", "tls-alpn-01"}, + want: []acme.ChallengeType{acme.DNS01, acme.HTTP01, acme.TLSALPN01}, }, { name: "ok/wildcard", @@ -1703,7 +1703,6 @@ func TestHandler_challengeTypes(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := challengeTypes(tt.args.az); !reflect.DeepEqual(got, tt.want) { t.Errorf("Handler.challengeTypes() = %v, want %v", got, tt.want) } diff --git a/acme/challenge.go b/acme/challenge.go index fe643c85..1d5f0ec9 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -261,7 +261,7 @@ func dns01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebK } // serverName determines the SNI HostName to set based on an acme.Challenge -// for TLS-ALPN-01 challenges. RFC8738 states that, if HostName is an IP, it +// for TLS-ALPN-01 challenges RFC8738 states that, if HostName is an IP, it // should be the ARPA address https://datatracker.ietf.org/doc/html/rfc8738#section-6. // It also references TLS Extensions [RFC6066]. func serverName(ch *Challenge) string { diff --git a/acme/challenge_test.go b/acme/challenge_test.go index 423951e8..bb9a2507 100644 --- a/acme/challenge_test.go +++ b/acme/challenge_test.go @@ -2334,7 +2334,7 @@ func Test_serverName(t *testing.T) { want: "1.0.0.127.in-addr.arpa.", }, { - name: "ok/ipv4", + name: "ok/ipv6", args: args{ ch: &Challenge{ Value: "2001:db8::567:89ab", diff --git a/acme/order.go b/acme/order.go index 71884603..2a869e78 100644 --- a/acme/order.go +++ b/acme/order.go @@ -221,9 +221,6 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ sans = make([]x509util.SubjectAlternativeName, totalNumberOfSANs) index := 0 - // TODO: limit what IP addresses can be used? Only private? Only certain ranges (i.e. only allow the specific ranges by default, configuration for all?) - // TODO: can DNS already be limited to a certain domain? That would probably be nice to have too, but maybe not as part of this PR - // TODO: if it seems not too big of a change, make consts/enums out of the stringly typed identifiers (challenge types, identifier types) // TODO: only allow IP based identifier based on configuration? Some additional configuration and validation on the provisioner for this case. // Validate identifier names against CSR alternative names. diff --git a/acme/order_test.go b/acme/order_test.go index e1d3744b..67d01f6d 100644 --- a/acme/order_test.go +++ b/acme/order_test.go @@ -13,6 +13,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/assert" "github.com/smallstep/certificates/authority/provisioner" + "go.step.sm/crypto/x509util" ) func TestOrder_UpdateStatus(t *testing.T) { @@ -1187,3 +1188,155 @@ func Test_ipsAreEqual(t *testing.T) { }) } } + +func Test_canonicalize(t *testing.T) { + type args struct { + csr *x509.CertificateRequest + } + tests := []struct { + name string + args args + wantCanonicalized *x509.CertificateRequest + }{ + { + name: "ok/dns", + args: args{ + csr: &x509.CertificateRequest{ + DNSNames: []string{"www.example.com", "example.com"}, + }, + }, + wantCanonicalized: &x509.CertificateRequest{ + DNSNames: []string{"example.com", "www.example.com"}, + IPAddresses: []net.IP{}, + }, + }, + { + name: "ok/common-name", + args: args{ + csr: &x509.CertificateRequest{ + Subject: pkix.Name{ + CommonName: "example.com", + }, + DNSNames: []string{"www.example.com"}, + }, + }, + wantCanonicalized: &x509.CertificateRequest{ + Subject: pkix.Name{ + CommonName: "example.com", + }, + DNSNames: []string{"example.com", "www.example.com"}, + IPAddresses: []net.IP{}, + }, + }, + { + name: "ok/ipv4", + args: args{ + csr: &x509.CertificateRequest{ + IPAddresses: []net.IP{net.ParseIP("192.168.43.42"), net.ParseIP("192.168.42.42")}, + }, + }, + wantCanonicalized: &x509.CertificateRequest{ + DNSNames: []string{}, + IPAddresses: []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.43.42")}, + }, + }, + { + name: "ok/mixed", + args: args{ + csr: &x509.CertificateRequest{ + DNSNames: []string{"www.example.com", "example.com"}, + IPAddresses: []net.IP{net.ParseIP("192.168.43.42"), net.ParseIP("192.168.42.42")}, + }, + }, + wantCanonicalized: &x509.CertificateRequest{ + DNSNames: []string{"example.com", "www.example.com"}, + IPAddresses: []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.43.42")}, + }, + }, + { + name: "ok/mixed-common-name", + args: args{ + csr: &x509.CertificateRequest{ + Subject: pkix.Name{ + CommonName: "example.com", + }, + DNSNames: []string{"www.example.com"}, + IPAddresses: []net.IP{net.ParseIP("192.168.43.42"), net.ParseIP("192.168.42.42")}, + }, + }, + wantCanonicalized: &x509.CertificateRequest{ + Subject: pkix.Name{ + CommonName: "example.com", + }, + DNSNames: []string{"example.com", "www.example.com"}, + IPAddresses: []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.43.42")}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotCanonicalized := canonicalize(tt.args.csr); !reflect.DeepEqual(gotCanonicalized, tt.wantCanonicalized) { + t.Errorf("canonicalize() = %v, want %v", gotCanonicalized, tt.wantCanonicalized) + } + }) + } +} + +func TestOrder_sans(t *testing.T) { + type fields struct { + ID string + AccountID string + ProvisionerID string + Status Status + ExpiresAt time.Time + Identifiers []Identifier + NotBefore time.Time + NotAfter time.Time + Error *Error + AuthorizationIDs []string + AuthorizationURLs []string + FinalizeURL string + CertificateID string + CertificateURL string + } + type args struct { + csr *x509.CertificateRequest + } + tests := []struct { + name string + fields fields + args args + want []x509util.SubjectAlternativeName + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := &Order{ + ID: tt.fields.ID, + AccountID: tt.fields.AccountID, + ProvisionerID: tt.fields.ProvisionerID, + Status: tt.fields.Status, + ExpiresAt: tt.fields.ExpiresAt, + Identifiers: tt.fields.Identifiers, + NotBefore: tt.fields.NotBefore, + NotAfter: tt.fields.NotAfter, + Error: tt.fields.Error, + AuthorizationIDs: tt.fields.AuthorizationIDs, + AuthorizationURLs: tt.fields.AuthorizationURLs, + FinalizeURL: tt.fields.FinalizeURL, + CertificateID: tt.fields.CertificateID, + CertificateURL: tt.fields.CertificateURL, + } + got, err := o.sans(tt.args.csr) + if (err != nil) != tt.wantErr { + t.Errorf("Order.sans() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Order.sans() = %v, want %v", got, tt.want) + } + }) + } +} From a6d33b7d0656b004291f73524df8447fda40004f Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 25 Jun 2021 17:21:22 +0200 Subject: [PATCH 83/89] Add tests for sans() --- acme/order_test.go | 187 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 147 insertions(+), 40 deletions(-) diff --git a/acme/order_test.go b/acme/order_test.go index 67d01f6d..c0682fee 100644 --- a/acme/order_test.go +++ b/acme/order_test.go @@ -1284,54 +1284,161 @@ func Test_canonicalize(t *testing.T) { func TestOrder_sans(t *testing.T) { type fields struct { - ID string - AccountID string - ProvisionerID string - Status Status - ExpiresAt time.Time - Identifiers []Identifier - NotBefore time.Time - NotAfter time.Time - Error *Error - AuthorizationIDs []string - AuthorizationURLs []string - FinalizeURL string - CertificateID string - CertificateURL string - } - type args struct { - csr *x509.CertificateRequest + Identifiers []Identifier } tests := []struct { - name string - fields fields - args args - want []x509util.SubjectAlternativeName - wantErr bool + name string + fields fields + csr *x509.CertificateRequest + want []x509util.SubjectAlternativeName + err error }{ - // TODO: Add test cases. + { + name: "ok/dns", + fields: fields{ + Identifiers: []Identifier{ + {Type: "dns", Value: "example.com"}, + }, + }, + csr: &x509.CertificateRequest{ + Subject: pkix.Name{ + CommonName: "example.com", + }, + }, + want: []x509util.SubjectAlternativeName{ + {Type: "dns", Value: "example.com"}, + }, + err: nil, + }, + { + name: "fail/error-names-length-mismatch", + fields: fields{ + Identifiers: []Identifier{ + {Type: "dns", Value: "foo.internal"}, + {Type: "dns", Value: "bar.internal"}, + }, + }, + csr: &x509.CertificateRequest{ + Subject: pkix.Name{ + CommonName: "foo.internal", + }, + }, + want: []x509util.SubjectAlternativeName{}, + err: NewError(ErrorBadCSRType, "..."), + }, + { + name: "fail/error-names-mismatch", + fields: fields{ + Identifiers: []Identifier{ + {Type: "dns", Value: "foo.internal"}, + {Type: "dns", Value: "bar.internal"}, + }, + }, + csr: &x509.CertificateRequest{ + Subject: pkix.Name{ + CommonName: "foo.internal", + }, + DNSNames: []string{"zap.internal"}, + }, + want: []x509util.SubjectAlternativeName{}, + err: NewError(ErrorBadCSRType, "..."), + }, + { + name: "ok/ipv4", + fields: fields{ + Identifiers: []Identifier{ + {Type: "ip", Value: "192.168.43.42"}, + {Type: "ip", Value: "192.168.42.42"}, + }, + }, + csr: &x509.CertificateRequest{ + IPAddresses: []net.IP{net.ParseIP("192.168.43.42"), net.ParseIP("192.168.42.42")}, + }, + want: []x509util.SubjectAlternativeName{ + {Type: "ip", Value: "192.168.42.42"}, + {Type: "ip", Value: "192.168.43.42"}, + }, + err: nil, + }, + { + name: "fail/error-ips-length-mismatch", + fields: fields{ + Identifiers: []Identifier{ + {Type: "ip", Value: "192.168.42.42"}, + {Type: "ip", Value: "192.168.43.42"}, + }, + }, + csr: &x509.CertificateRequest{ + IPAddresses: []net.IP{net.ParseIP("192.168.42.42")}, + }, + want: []x509util.SubjectAlternativeName{}, + err: NewError(ErrorBadCSRType, "..."), + }, + { + name: "fail/error-ips-mismatch", + fields: fields{ + Identifiers: []Identifier{ + {Type: "ip", Value: "192.168.42.42"}, + {Type: "ip", Value: "192.168.43.42"}, + }, + }, + csr: &x509.CertificateRequest{ + IPAddresses: []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.42.32")}, + }, + want: []x509util.SubjectAlternativeName{}, + err: NewError(ErrorBadCSRType, "..."), + }, + { + name: "ok/mixed", + fields: fields{ + Identifiers: []Identifier{ + {Type: "dns", Value: "foo.internal"}, + {Type: "dns", Value: "bar.internal"}, + {Type: "ip", Value: "192.168.43.42"}, + {Type: "ip", Value: "192.168.42.42"}, + }, + }, + csr: &x509.CertificateRequest{ + Subject: pkix.Name{ + CommonName: "bar.internal", + }, + DNSNames: []string{"foo.internal"}, + IPAddresses: []net.IP{net.ParseIP("192.168.43.42"), net.ParseIP("192.168.42.42")}, + }, + want: []x509util.SubjectAlternativeName{ + {Type: "dns", Value: "bar.internal"}, + {Type: "dns", Value: "foo.internal"}, + {Type: "ip", Value: "192.168.42.42"}, + {Type: "ip", Value: "192.168.43.42"}, + }, + err: nil, + }, + { + name: "fail/unsupported-identifier-type", + fields: fields{ + Identifiers: []Identifier{ + {Type: "ipv4", Value: "192.168.42.42"}, + }, + }, + csr: &x509.CertificateRequest{ + IPAddresses: []net.IP{net.ParseIP("192.168.42.42")}, + }, + want: []x509util.SubjectAlternativeName{}, + err: NewError(ErrorServerInternalType, "..."), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { o := &Order{ - ID: tt.fields.ID, - AccountID: tt.fields.AccountID, - ProvisionerID: tt.fields.ProvisionerID, - Status: tt.fields.Status, - ExpiresAt: tt.fields.ExpiresAt, - Identifiers: tt.fields.Identifiers, - NotBefore: tt.fields.NotBefore, - NotAfter: tt.fields.NotAfter, - Error: tt.fields.Error, - AuthorizationIDs: tt.fields.AuthorizationIDs, - AuthorizationURLs: tt.fields.AuthorizationURLs, - FinalizeURL: tt.fields.FinalizeURL, - CertificateID: tt.fields.CertificateID, - CertificateURL: tt.fields.CertificateURL, + Identifiers: tt.fields.Identifiers, } - got, err := o.sans(tt.args.csr) - if (err != nil) != tt.wantErr { - t.Errorf("Order.sans() error = %v, wantErr %v", err, tt.wantErr) + canonicalizedCSR := canonicalize(tt.csr) + got, err := o.sans(canonicalizedCSR) + if err != nil && tt.err != nil { + if tt.err.Error() != err.Error() { + t.Errorf("Order.sans() error = %v, wantErr %v", err, tt.err) + return + } return } if !reflect.DeepEqual(got, tt.want) { From 87b72afa25b88f74a4fc9847f76eae4d2e988250 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Sat, 26 Jun 2021 00:13:44 +0200 Subject: [PATCH 84/89] Fix IP equality check and add more tests --- acme/order.go | 21 +++++++++------------ acme/order_test.go | 32 ++++++++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/acme/order.go b/acme/order.go index 2a869e78..9c823f78 100644 --- a/acme/order.go +++ b/acme/order.go @@ -299,20 +299,17 @@ func canonicalize(csr *x509.CertificateRequest) (canonicalized *x509.Certificate return canonicalized } -// ipsAreEqual compares IPs to be equal. IPv6 representations of IPv4 -// adresses are considered equal to the IPv4 address in this case. -// TODO: is this behavior OK to keep? +// ipsAreEqual compares IPs to be equal. Nil values (i.e. invalid IPs) are +// not considered equal. IPv6 representations of IPv4 addresses are +// considered equal to the IPv4 address in this implementation, which is +// standard Go behavior. An example is "::ffff:192.168.42.42", which +// is equal to "192.168.42.42". This is considered a known issue within +// step and is tracked here too: https://github.com/golang/go/issues/37921. func ipsAreEqual(x, y net.IP) bool { - if matchAddrFamily(x, y) { - return x.Equal(y) + if x == nil || y == nil { + return false } - return false -} - -// matchAddrFamily returns true if two IPs are both IPv4 OR IPv6 -// Implementation taken and adapted from https://golang.org/src/net/ip.go -func matchAddrFamily(x net.IP, y net.IP) bool { - return x.To4() != nil && y.To4() != nil || x.To16() != nil && x.To4() == nil && y.To16() != nil && y.To4() == nil + return x.Equal(y) } // uniqueSortedLowerNames returns the set of all unique names in the input after all diff --git a/acme/order_test.go b/acme/order_test.go index c0682fee..b9609cf9 100644 --- a/acme/order_test.go +++ b/acme/order_test.go @@ -1139,6 +1139,14 @@ func Test_ipsAreEqual(t *testing.T) { }, want: true, }, + { + name: "fail/ipv4", + args: args{ + x: net.ParseIP("192.168.42.42"), + y: net.ParseIP("192.168.42.43"), + }, + want: false, + }, { name: "ok/ipv6", args: args{ @@ -1148,7 +1156,15 @@ func Test_ipsAreEqual(t *testing.T) { want: true, }, { - name: "ok/ipv4-and-ipv6", + name: "fail/ipv6", + args: args{ + x: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), + y: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7335"), + }, + want: false, + }, + { + name: "fail/ipv4-and-ipv6", args: args{ x: net.ParseIP("192.168.42.42"), y: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), @@ -1161,10 +1177,10 @@ func Test_ipsAreEqual(t *testing.T) { x: net.ParseIP("192.168.42.42"), y: net.ParseIP("::ffff:192.168.42.42"), // parsed to the same IPv4 by Go }, - want: true, // TODO: is this behavior OK? + want: true, // we expect this to happen; a known issue in which ipv4 mapped ipv6 addresses are considered the same as their ipv4 counterpart }, { - name: "ok/invalid-ipv4-and-valid-ipv6", + name: "fail/invalid-ipv4-and-valid-ipv6", args: args{ x: net.ParseIP("192.168.42.1000"), y: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), @@ -1172,7 +1188,15 @@ func Test_ipsAreEqual(t *testing.T) { want: false, }, { - name: "ok/invalid-ipv4-and-invalid-ipv6", + name: "fail/valid-ipv4-and-invalid-ipv6", + args: args{ + x: net.ParseIP("192.168.42.42"), + y: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:733400"), + }, + want: false, + }, + { + name: "fail/invalid-ipv4-and-invalid-ipv6", args: args{ x: net.ParseIP("192.168.42.1000"), y: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:1000000"), From 8e4a4ecc1f79e4ccfecdbf4528d1599e483afda7 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Sat, 26 Jun 2021 00:48:40 +0200 Subject: [PATCH 85/89] Refactor tests for sans --- acme/order.go | 2 - acme/order_test.go | 158 ++++++++++++--------------------------------- 2 files changed, 43 insertions(+), 117 deletions(-) diff --git a/acme/order.go b/acme/order.go index 9c823f78..fd8956f7 100644 --- a/acme/order.go +++ b/acme/order.go @@ -221,8 +221,6 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ sans = make([]x509util.SubjectAlternativeName, totalNumberOfSANs) index := 0 - // TODO: only allow IP based identifier based on configuration? Some additional configuration and validation on the provisioner for this case. - // Validate identifier names against CSR alternative names. // // Note that with certificate templates we are not going to check for the diff --git a/acme/order_test.go b/acme/order_test.go index b9609cf9..b9a43e5d 100644 --- a/acme/order_test.go +++ b/acme/order_test.go @@ -367,111 +367,6 @@ func TestOrder_Finalize(t *testing.T) { err: NewErrorISE("unrecognized order status: %s", o.Status), } }, - "fail/error-names-length-mismatch": func(t *testing.T) test { - now := clock.Now() - o := &Order{ - ID: "oID", - AccountID: "accID", - Status: StatusReady, - ExpiresAt: now.Add(5 * time.Minute), - AuthorizationIDs: []string{"a", "b"}, - Identifiers: []Identifier{ - {Type: "dns", Value: "foo.internal"}, - {Type: "dns", Value: "bar.internal"}, - }, - } - orderNames := []string{"bar.internal", "foo.internal"} - csr := &x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: "foo.internal", - }, - } - - return test{ - o: o, - csr: csr, - err: NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+ - "CSR names = %v, Order names = %v", []string{"foo.internal"}, orderNames), - } - }, - "fail/error-ips-length-mismatch": func(t *testing.T) test { - now := clock.Now() - o := &Order{ - ID: "oID", - AccountID: "accID", - Status: StatusReady, - ExpiresAt: now.Add(5 * time.Minute), - AuthorizationIDs: []string{"a", "b"}, - Identifiers: []Identifier{ - {Type: "ip", Value: "192.168.42.42"}, - {Type: "ip", Value: "192.168.43.42"}, - }, - } - orderIPs := []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.43.42")} - csr := &x509.CertificateRequest{ - IPAddresses: []net.IP{net.ParseIP("192.168.42.42")}, - } - - return test{ - o: o, - csr: csr, - err: NewError(ErrorBadCSRType, "CSR IPs do not match identifiers exactly: "+ - "CSR IPs = %v, Order IPs = %v", []net.IP{net.ParseIP("192.168.42.42")}, orderIPs), - } - }, - "fail/error-names-mismatch": func(t *testing.T) test { - now := clock.Now() - o := &Order{ - ID: "oID", - AccountID: "accID", - Status: StatusReady, - ExpiresAt: now.Add(5 * time.Minute), - AuthorizationIDs: []string{"a", "b"}, - Identifiers: []Identifier{ - {Type: "dns", Value: "foo.internal"}, - {Type: "dns", Value: "bar.internal"}, - }, - } - orderNames := []string{"bar.internal", "foo.internal"} - csr := &x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: "foo.internal", - }, - DNSNames: []string{"zap.internal"}, - } - - return test{ - o: o, - csr: csr, - err: NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+ - "CSR names = %v, Order names = %v", []string{"foo.internal", "zap.internal"}, orderNames), - } - }, - "fail/error-ips-mismatch": func(t *testing.T) test { - now := clock.Now() - o := &Order{ - ID: "oID", - AccountID: "accID", - Status: StatusReady, - ExpiresAt: now.Add(5 * time.Minute), - AuthorizationIDs: []string{"a", "b"}, - Identifiers: []Identifier{ - {Type: "ip", Value: "192.168.42.42"}, - {Type: "ip", Value: "192.168.43.42"}, - }, - } - orderIPs := []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.43.42")} - csr := &x509.CertificateRequest{ - IPAddresses: []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.42.32")}, - } - - return test{ - o: o, - csr: csr, - err: NewError(ErrorBadCSRType, "CSR IPs do not match identifiers exactly: "+ - "CSR IPs = %v, Order IPs = %v", []net.IP{net.ParseIP("192.168.42.32"), net.ParseIP("192.168.42.42")}, orderIPs), - } - }, "fail/error-provisioner-auth": func(t *testing.T) test { now := clock.Now() o := &Order{ @@ -1315,7 +1210,7 @@ func TestOrder_sans(t *testing.T) { fields fields csr *x509.CertificateRequest want []x509util.SubjectAlternativeName - err error + err *Error }{ { name: "ok/dns", @@ -1348,7 +1243,8 @@ func TestOrder_sans(t *testing.T) { }, }, want: []x509util.SubjectAlternativeName{}, - err: NewError(ErrorBadCSRType, "..."), + err: NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+ + "CSR names = %v, Order names = %v", []string{"foo.internal"}, []string{"bar.internal", "foo.internal"}), }, { name: "fail/error-names-mismatch", @@ -1365,7 +1261,8 @@ func TestOrder_sans(t *testing.T) { DNSNames: []string{"zap.internal"}, }, want: []x509util.SubjectAlternativeName{}, - err: NewError(ErrorBadCSRType, "..."), + err: NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+ + "CSR names = %v, Order names = %v", []string{"foo.internal", "zap.internal"}, []string{"bar.internal", "foo.internal"}), }, { name: "ok/ipv4", @@ -1384,6 +1281,23 @@ func TestOrder_sans(t *testing.T) { }, err: nil, }, + { + name: "ok/ipv6", + fields: fields{ + Identifiers: []Identifier{ + {Type: "ip", Value: "2001:0db8:85a3::8a2e:0370:7335"}, + {Type: "ip", Value: "2001:0db8:85a3::8a2e:0370:7334"}, + }, + }, + csr: &x509.CertificateRequest{ + IPAddresses: []net.IP{net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7335"), net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")}, + }, + want: []x509util.SubjectAlternativeName{ + {Type: "ip", Value: "2001:db8:85a3::8a2e:370:7334"}, + {Type: "ip", Value: "2001:db8:85a3::8a2e:370:7335"}, + }, + err: nil, + }, { name: "fail/error-ips-length-mismatch", fields: fields{ @@ -1396,7 +1310,8 @@ func TestOrder_sans(t *testing.T) { IPAddresses: []net.IP{net.ParseIP("192.168.42.42")}, }, want: []x509util.SubjectAlternativeName{}, - err: NewError(ErrorBadCSRType, "..."), + err: NewError(ErrorBadCSRType, "CSR IPs do not match identifiers exactly: "+ + "CSR IPs = %v, Order IPs = %v", []net.IP{net.ParseIP("192.168.42.42")}, []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.43.42")}), }, { name: "fail/error-ips-mismatch", @@ -1410,7 +1325,8 @@ func TestOrder_sans(t *testing.T) { IPAddresses: []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.42.32")}, }, want: []x509util.SubjectAlternativeName{}, - err: NewError(ErrorBadCSRType, "..."), + err: NewError(ErrorBadCSRType, "CSR IPs do not match identifiers exactly: "+ + "CSR IPs = %v, Order IPs = %v", []net.IP{net.ParseIP("192.168.42.32"), net.ParseIP("192.168.42.42")}, []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.43.42")}), }, { name: "ok/mixed", @@ -1420,6 +1336,7 @@ func TestOrder_sans(t *testing.T) { {Type: "dns", Value: "bar.internal"}, {Type: "ip", Value: "192.168.43.42"}, {Type: "ip", Value: "192.168.42.42"}, + {Type: "ip", Value: "2001:0db8:85a3:0000:0000:8a2e:0370:7334"}, }, }, csr: &x509.CertificateRequest{ @@ -1427,13 +1344,14 @@ func TestOrder_sans(t *testing.T) { CommonName: "bar.internal", }, DNSNames: []string{"foo.internal"}, - IPAddresses: []net.IP{net.ParseIP("192.168.43.42"), net.ParseIP("192.168.42.42")}, + IPAddresses: []net.IP{net.ParseIP("192.168.43.42"), net.ParseIP("192.168.42.42"), net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")}, }, want: []x509util.SubjectAlternativeName{ {Type: "dns", Value: "bar.internal"}, {Type: "dns", Value: "foo.internal"}, {Type: "ip", Value: "192.168.42.42"}, {Type: "ip", Value: "192.168.43.42"}, + {Type: "ip", Value: "2001:db8:85a3::8a2e:370:7334"}, }, err: nil, }, @@ -1448,7 +1366,7 @@ func TestOrder_sans(t *testing.T) { IPAddresses: []net.IP{net.ParseIP("192.168.42.42")}, }, want: []x509util.SubjectAlternativeName{}, - err: NewError(ErrorServerInternalType, "..."), + err: NewError(ErrorServerInternalType, "unsupported identifier type in order: ipv4"), }, } for _, tt := range tests { @@ -1458,11 +1376,21 @@ func TestOrder_sans(t *testing.T) { } canonicalizedCSR := canonicalize(tt.csr) got, err := o.sans(canonicalizedCSR) - if err != nil && tt.err != nil { - if tt.err.Error() != err.Error() { - t.Errorf("Order.sans() error = %v, wantErr %v", err, tt.err) + if tt.err != nil { + if err == nil { + t.Errorf("Order.sans() = %v, want error; got none", got) return } + switch k := err.(type) { + case *Error: + assert.Equals(t, k.Type, tt.err.Type) + assert.Equals(t, k.Detail, tt.err.Detail) + assert.Equals(t, k.Status, tt.err.Status) + assert.Equals(t, k.Err.Error(), tt.err.Err.Error()) + assert.Equals(t, k.Detail, tt.err.Detail) + default: + assert.FatalError(t, errors.New("unexpected error type")) + } return } if !reflect.DeepEqual(got, tt.want) { From 9fdef647099dd91b53683bff49e205f16f743a57 Mon Sep 17 00:00:00 2001 From: max furman Date: Mon, 3 May 2021 12:48:20 -0700 Subject: [PATCH 86/89] Admin level API for provisioner mgmt v1 --- .golangci.yml | 4 +- acme/api/middleware.go | 8 +- acme/common.go | 2 +- acme/db/nosql/nosql.go | 7 +- acme/order_test.go | 14 +- api/api.go | 4 +- api/api_test.go | 16 +- api/errors.go | 6 +- api/utils.go | 36 + authority/{mgmt => admin}/api/admin.go | 82 +- authority/admin/api/handler.go | 41 + authority/admin/api/middleware.go | 66 + authority/admin/api/provisioner.go | 175 ++ authority/admin/collection.go | 180 -- authority/{mgmt => admin}/db.go | 47 +- authority/{mgmt => admin}/db/nosql/admin.go | 98 +- authority/admin/db/nosql/admin_test.go | 1121 +++++++++++ authority/{mgmt => admin}/db/nosql/nosql.go | 10 +- authority/admin/db/nosql/provisioner.go | 211 ++ authority/admin/db/nosql/provisioner_test.go | 1221 ++++++++++++ authority/{mgmt => admin}/errors.go | 25 +- authority/administrator/collection.go | 243 +++ authority/admins.go | 97 + authority/authority.go | 241 +-- authority/authority_test.go | 2 - authority/authorize.go | 125 +- authority/authorize_test.go | 2 +- authority/config.go | 11 +- authority/config/config.go | 32 +- authority/config/config_test.go | 66 +- authority/config/ssh_test.go | 73 + authority/config/tls_options.go | 12 + authority/mgmt/api/handler.go | 47 - authority/mgmt/api/provisioner.go | 199 -- authority/mgmt/config.go | 48 - authority/mgmt/db/nosql/provisioner.go | 219 --- authority/mgmt/provisioner.go | 116 -- authority/options.go | 4 +- authority/provisioner/acme.go | 2 +- authority/provisioner/acme_test.go | 4 +- authority/provisioner/aws.go | 12 +- authority/provisioner/azure.go | 4 +- authority/provisioner/claims.go | 39 +- authority/provisioner/collection.go | 89 +- authority/provisioner/collection_test.go | 25 +- authority/provisioner/gcp.go | 10 +- authority/provisioner/jwk.go | 4 +- authority/provisioner/jwk_test.go | 2 +- authority/provisioner/k8sSA.go | 8 +- authority/provisioner/k8sSA_test.go | 4 +- authority/provisioner/oidc.go | 4 +- authority/provisioner/scep.go | 14 +- authority/provisioner/sshpop.go | 2 +- authority/provisioner/x5c.go | 11 +- authority/provisioner/x5c_test.go | 8 +- authority/provisioners.go | 664 +++++-- authority/provisioners_test.go | 2 +- authority/ssh_test.go | 63 - authority/tls.go | 7 +- authority/tls_test.go | 4 +- ca/adminClient.go | 238 ++- ca/ca.go | 33 +- ca/client.go | 105 +- ca/testdata/ca.json | 4 +- cas/stepcas/x5c_issuer.go | 8 +- go.mod | 14 +- go.sum | 197 +- linkedca/admin.pb.go | 240 --- linkedca/admin.proto | 18 - linkedca/doc.go | 3 - linkedca/provisioners.go | 38 - linkedca/provisioners.pb.go | 1833 ------------------ linkedca/provisioners.proto | 133 -- scep/api/api.go | 10 +- scep/authority.go | 12 +- 75 files changed, 4906 insertions(+), 3873 deletions(-) rename authority/{mgmt => admin}/api/admin.go (50%) create mode 100644 authority/admin/api/handler.go create mode 100644 authority/admin/api/middleware.go create mode 100644 authority/admin/api/provisioner.go delete mode 100644 authority/admin/collection.go rename authority/{mgmt => admin}/db.go (72%) rename authority/{mgmt => admin}/db/nosql/admin.go (68%) create mode 100644 authority/admin/db/nosql/admin_test.go rename authority/{mgmt => admin}/db/nosql/nosql.go (81%) create mode 100644 authority/admin/db/nosql/provisioner.go create mode 100644 authority/admin/db/nosql/provisioner_test.go rename authority/{mgmt => admin}/errors.go (88%) create mode 100644 authority/administrator/collection.go create mode 100644 authority/admins.go create mode 100644 authority/config/ssh_test.go delete mode 100644 authority/mgmt/api/handler.go delete mode 100644 authority/mgmt/api/provisioner.go delete mode 100644 authority/mgmt/config.go delete mode 100644 authority/mgmt/db/nosql/provisioner.go delete mode 100644 authority/mgmt/provisioner.go delete mode 100644 linkedca/admin.pb.go delete mode 100644 linkedca/admin.proto delete mode 100644 linkedca/doc.go delete mode 100644 linkedca/provisioners.go delete mode 100644 linkedca/provisioners.pb.go delete mode 100644 linkedca/provisioners.proto diff --git a/.golangci.yml b/.golangci.yml index 1bab3ba3..92af7723 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -8,7 +8,7 @@ linters-settings: - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf - golint: + revive: min-confidence: 0 gocyclo: min-complexity: 10 @@ -44,7 +44,7 @@ linters: disable-all: true enable: - gofmt - - golint + - revive - govet - misspell - ineffassign diff --git a/acme/api/middleware.go b/acme/api/middleware.go index 50f7146f..b2244dd7 100644 --- a/acme/api/middleware.go +++ b/acme/api/middleware.go @@ -288,13 +288,13 @@ func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - name := chi.URLParam(r, "provisionerID") - provID, err := url.PathUnescape(name) + nameEscaped := chi.URLParam(r, "provisionerID") + name, err := url.PathUnescape(nameEscaped) if err != nil { - api.WriteError(w, acme.WrapErrorISE(err, "error url unescaping provisioner id '%s'", name)) + api.WriteError(w, acme.WrapErrorISE(err, "error url unescaping provisioner name '%s'", nameEscaped)) return } - p, err := h.ca.LoadProvisionerByID("acme/" + provID) + p, err := h.ca.LoadProvisionerByName(name) if err != nil { api.WriteError(w, err) return diff --git a/acme/common.go b/acme/common.go index 26552c61..f18907fe 100644 --- a/acme/common.go +++ b/acme/common.go @@ -11,7 +11,7 @@ import ( // CertificateAuthority is the interface implemented by a CA authority. type CertificateAuthority interface { Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) - LoadProvisionerByID(string) (provisioner.Interface, error) + LoadProvisionerByName(string) (provisioner.Interface, error) } // Clock that returns time in UTC rounded to seconds. diff --git a/acme/db/nosql/nosql.go b/acme/db/nosql/nosql.go index e2edd050..052f5729 100644 --- a/acme/db/nosql/nosql.go +++ b/acme/db/nosql/nosql.go @@ -23,12 +23,11 @@ var ( // DB is a struct that implements the AcmeDB interface. type DB struct { - db nosqlDB.DB - authorityID string + db nosqlDB.DB } // New configures and returns a new ACME DB backend implemented using a nosql DB. -func New(db nosqlDB.DB, authorityID string) (*DB, error) { +func New(db nosqlDB.DB) (*DB, error) { tables := [][]byte{accountTable, accountByKeyIDTable, authzTable, challengeTable, nonceTable, orderTable, ordersByAccountIDTable, certTable} for _, b := range tables { @@ -37,7 +36,7 @@ func New(db nosqlDB.DB, authorityID string) (*DB, error) { string(b)) } } - return &DB{db, authorityID}, nil + return &DB{db}, nil } // save writes the new data to the database, overwriting the old data if it diff --git a/acme/order_test.go b/acme/order_test.go index 993a92f2..849f0daa 100644 --- a/acme/order_test.go +++ b/acme/order_test.go @@ -261,10 +261,10 @@ func TestOrder_UpdateStatus(t *testing.T) { } type mockSignAuth struct { - sign func(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) - loadProvisionerByID func(string) (provisioner.Interface, error) - ret1, ret2 interface{} - err error + sign func(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) + loadProvisionerByName func(string) (provisioner.Interface, error) + ret1, ret2 interface{} + err error } func (m *mockSignAuth) Sign(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { @@ -276,9 +276,9 @@ func (m *mockSignAuth) Sign(csr *x509.CertificateRequest, signOpts provisioner.S return []*x509.Certificate{m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate)}, m.err } -func (m *mockSignAuth) LoadProvisionerByID(id string) (provisioner.Interface, error) { - if m.loadProvisionerByID != nil { - return m.loadProvisionerByID(id) +func (m *mockSignAuth) LoadProvisionerByName(name string) (provisioner.Interface, error) { + if m.loadProvisionerByName != nil { + return m.loadProvisionerByName(name) } return m.ret1.(provisioner.Interface), m.err } diff --git a/api/api.go b/api/api.go index 20ef7e14..b542f473 100644 --- a/api/api.go +++ b/api/api.go @@ -39,7 +39,7 @@ type Authority interface { Renew(peer *x509.Certificate) ([]*x509.Certificate, error) Rekey(peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) LoadProvisionerByCertificate(*x509.Certificate) (provisioner.Interface, error) - LoadProvisionerByID(string) (provisioner.Interface, error) + LoadProvisionerByName(string) (provisioner.Interface, error) GetProvisioners(cursor string, limit int) (provisioner.List, string, error) Revoke(context.Context, *authority.RevokeOptions) error GetEncryptedKey(kid string) (string, error) @@ -418,7 +418,7 @@ func LogCertificate(w http.ResponseWriter, cert *x509.Certificate) { if len(val.CredentialID) > 0 { m["provisioner"] = fmt.Sprintf("%s (%s)", val.Name, val.CredentialID) } else { - m["provisioner"] = fmt.Sprintf("%s", val.Name) + m["provisioner"] = string(val.Name) } break } diff --git a/api/api_test.go b/api/api_test.go index 62ef7740..33d2bae7 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -430,6 +430,7 @@ type mockProvisioner struct { ret1, ret2, ret3 interface{} err error getID func() string + getIDForToken func() string getTokenID func(string) (string, error) getName func() string getType func() provisioner.Type @@ -452,6 +453,13 @@ func (m *mockProvisioner) GetID() string { return m.ret1.(string) } +func (m *mockProvisioner) GetIDForToken() string { + if m.getIDForToken != nil { + return m.getIDForToken() + } + return m.ret1.(string) +} + func (m *mockProvisioner) GetTokenID(token string) (string, error) { if m.getTokenID != nil { return m.getTokenID(token) @@ -553,7 +561,7 @@ type mockAuthority struct { renew func(cert *x509.Certificate) ([]*x509.Certificate, error) rekey func(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) loadProvisionerByCertificate func(cert *x509.Certificate) (provisioner.Interface, error) - loadProvisionerByID func(provID string) (provisioner.Interface, error) + loadProvisionerByName func(name string) (provisioner.Interface, error) getProvisioners func(nextCursor string, limit int) (provisioner.List, string, error) revoke func(context.Context, *authority.RevokeOptions) error getEncryptedKey func(kid string) (string, error) @@ -633,9 +641,9 @@ func (m *mockAuthority) LoadProvisionerByCertificate(cert *x509.Certificate) (pr return m.ret1.(provisioner.Interface), m.err } -func (m *mockAuthority) LoadProvisionerByID(provID string) (provisioner.Interface, error) { - if m.loadProvisionerByID != nil { - return m.loadProvisionerByID(provID) +func (m *mockAuthority) LoadProvisionerByName(name string) (provisioner.Interface, error) { + if m.loadProvisionerByName != nil { + return m.loadProvisionerByName(name) } return m.ret1.(provisioner.Interface), m.err } diff --git a/api/errors.go b/api/errors.go index f9bcb199..db3bc3e2 100644 --- a/api/errors.go +++ b/api/errors.go @@ -8,7 +8,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/acme" - "github.com/smallstep/certificates/authority/mgmt" + "github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/scep" @@ -20,8 +20,8 @@ func WriteError(w http.ResponseWriter, err error) { case *acme.Error: acme.WriteError(w, k) return - case *mgmt.Error: - mgmt.WriteError(w, k) + case *admin.Error: + admin.WriteError(w, k) return case *scep.Error: w.Header().Set("Content-Type", "text/plain") diff --git a/api/utils.go b/api/utils.go index 0d87a065..bf45db53 100644 --- a/api/utils.go +++ b/api/utils.go @@ -3,11 +3,14 @@ package api import ( "encoding/json" "io" + "io/ioutil" "log" "net/http" "github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/logging" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" ) // EnableLogger is an interface that enables response logging for an object. @@ -64,6 +67,29 @@ func JSONStatus(w http.ResponseWriter, v interface{}, status int) { LogEnabledResponse(w, v) } +// ProtoJSON writes the passed value into the http.ResponseWriter. +func ProtoJSON(w http.ResponseWriter, m proto.Message) { + ProtoJSONStatus(w, m, http.StatusOK) +} + +// ProtoJSONStatus writes the given value into the http.ResponseWriter and the +// given status is written as the status code of the response. +func ProtoJSONStatus(w http.ResponseWriter, m proto.Message, status int) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + + b, err := protojson.Marshal(m) + if err != nil { + LogError(w, err) + return + } + if _, err := w.Write(b); err != nil { + LogError(w, err) + return + } + //LogEnabledResponse(w, v) +} + // ReadJSON reads JSON from the request body and stores it in the value // pointed by v. func ReadJSON(r io.Reader, v interface{}) error { @@ -72,3 +98,13 @@ func ReadJSON(r io.Reader, v interface{}) error { } return nil } + +// ReadProtoJSON reads JSON from the request body and stores it in the value +// pointed by v. +func ReadProtoJSON(r io.Reader, m proto.Message) error { + data, err := ioutil.ReadAll(r) + if err != nil { + return errs.Wrap(http.StatusBadRequest, err, "error reading request body") + } + return protojson.Unmarshal(data, m) +} diff --git a/authority/mgmt/api/admin.go b/authority/admin/api/admin.go similarity index 50% rename from authority/mgmt/api/admin.go rename to authority/admin/api/admin.go index 30c78555..d92af5b2 100644 --- a/authority/mgmt/api/admin.go +++ b/authority/admin/api/admin.go @@ -1,14 +1,12 @@ package api import ( - "fmt" "net/http" "github.com/go-chi/chi" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority/admin" - "github.com/smallstep/certificates/authority/mgmt" - "github.com/smallstep/certificates/linkedca" + "go.step.sm/linkedca" ) // CreateAdminRequest represents the body for a CreateAdmin request. @@ -19,11 +17,7 @@ type CreateAdminRequest struct { } // Validate validates a new-admin request body. -func (car *CreateAdminRequest) Validate(c *admin.Collection) error { - if _, ok := c.LoadBySubProv(car.Subject, car.Provisioner); ok { - return mgmt.NewError(mgmt.ErrorBadRequestType, - "admin with subject: '%s' and provisioner: '%s' already exists", car.Subject, car.Provisioner) - } +func (car *CreateAdminRequest) Validate() error { return nil } @@ -52,25 +46,29 @@ type DeleteResponse struct { func (h *Handler) GetAdmin(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") - adm, ok := h.auth.GetAdminCollection().LoadByID(id) + adm, ok := h.auth.LoadAdminByID(id) if !ok { - api.WriteError(w, mgmt.NewError(mgmt.ErrorNotFoundType, + api.WriteError(w, admin.NewError(admin.ErrorNotFoundType, "admin %s not found", id)) return } api.JSON(w, adm) } -// GetAdmins returns all admins associated with the authority. +// GetAdmins returns a segment of admins associated with the authority. func (h *Handler) GetAdmins(w http.ResponseWriter, r *http.Request) { cursor, limit, err := api.ParseCursor(r) if err != nil { - api.WriteError(w, mgmt.WrapError(mgmt.ErrorBadRequestType, err, + api.WriteError(w, admin.WrapError(admin.ErrorBadRequestType, err, "error parsing cursor and limit from query params")) return } - admins, nextCursor := h.auth.GetAdminCollection().Find(cursor, limit) + admins, nextCursor, err := h.auth.GetAdmins(cursor, limit) + if err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error retrieving paginated admins")) + return + } api.JSON(w, &GetAdminsResponse{ Admins: admins, NextCursor: nextCursor, @@ -79,91 +77,63 @@ func (h *Handler) GetAdmins(w http.ResponseWriter, r *http.Request) { // CreateAdmin creates a new admin. func (h *Handler) CreateAdmin(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var body CreateAdminRequest if err := api.ReadJSON(r.Body, &body); err != nil { - api.WriteError(w, mgmt.WrapError(mgmt.ErrorBadRequestType, err, "error reading request body")) + api.WriteError(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body")) return } - if err := body.Validate(h.auth.GetAdminCollection()); err != nil { + if err := body.Validate(); err != nil { api.WriteError(w, err) return } - p, ok := h.auth.GetProvisionerCollection().LoadByName(body.Provisioner) - if !ok { - api.WriteError(w, mgmt.NewError(mgmt.ErrorNotFoundType, "provisioner %s not found", body.Provisioner)) + p, err := h.auth.LoadProvisionerByName(body.Provisioner) + if err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner %s", body.Provisioner)) return } - adm := &linkedca.Admin{ ProvisionerId: p.GetID(), Subject: body.Subject, Type: body.Type, } - if err := h.db.CreateAdmin(ctx, adm); err != nil { - api.WriteError(w, mgmt.WrapErrorISE(err, "error creating admin")) + // Store to authority collection. + if err := h.auth.StoreAdmin(r.Context(), adm, p); err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error storing admin")) return } + api.JSON(w, adm) - if err := h.auth.ReloadAuthConfig(ctx); err != nil { - fmt.Printf("err = %+v\n", err) - } } // DeleteAdmin deletes admin. func (h *Handler) DeleteAdmin(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") - if h.auth.GetAdminCollection().SuperCount() == 1 { - api.WriteError(w, mgmt.NewError(mgmt.ErrorBadRequestType, "cannot remove the last super admin")) + if err := h.auth.RemoveAdmin(r.Context(), id); err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error deleting admin %s", id)) return } - ctx := r.Context() - 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(ctx); err != nil { - fmt.Printf("err = %+v\n", err) - } } // UpdateAdmin updates an existing admin. func (h *Handler) UpdateAdmin(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var body UpdateAdminRequest if err := api.ReadJSON(r.Body, &body); err != nil { - api.WriteError(w, mgmt.WrapError(mgmt.ErrorBadRequestType, err, "error reading request body")) + api.WriteError(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body")) return } id := chi.URLParam(r, "id") - adm, ok := h.auth.GetAdminCollection().LoadByID(id) - if !ok { - api.WriteError(w, mgmt.NewError(mgmt.ErrorNotFoundType, "admin %s not found", id)) - return - } - if adm.Type == body.Type { - api.WriteError(w, mgmt.NewError(mgmt.ErrorBadRequestType, "admin %s already has type %s", id, adm.Type)) + adm, err := h.auth.UpdateAdmin(r.Context(), id, &linkedca.Admin{Type: body.Type}) + if err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error updating admin %s", id)) return } - adm.Type = body.Type - - 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(ctx); err != nil { - fmt.Printf("err = %+v\n", err) - } } diff --git a/authority/admin/api/handler.go b/authority/admin/api/handler.go new file mode 100644 index 00000000..d88edfa1 --- /dev/null +++ b/authority/admin/api/handler.go @@ -0,0 +1,41 @@ +package api + +import ( + "github.com/smallstep/certificates/api" + "github.com/smallstep/certificates/authority" + "github.com/smallstep/certificates/authority/admin" +) + +// Handler is the ACME API request handler. +type Handler struct { + db admin.DB + auth *authority.Authority +} + +// NewHandler returns a new Authority Config Handler. +func NewHandler(auth *authority.Authority) api.RouterHandler { + h := &Handler{db: auth.GetAdminDatabase(), auth: auth} + + return h +} + +// Route traffic and implement the Router interface. +func (h *Handler) Route(r api.Router) { + authnz := func(next nextHTTP) nextHTTP { + return h.extractAuthorizeTokenAdmin(h.requireAPIEnabled(next)) + } + + // Provisioners + r.MethodFunc("GET", "/provisioners/{name}", authnz(h.GetProvisioner)) + r.MethodFunc("GET", "/provisioners", authnz(h.GetProvisioners)) + r.MethodFunc("POST", "/provisioners", authnz(h.CreateProvisioner)) + r.MethodFunc("PUT", "/provisioners/{name}", authnz(h.UpdateProvisioner)) + r.MethodFunc("DELETE", "/provisioners/{name}", authnz(h.DeleteProvisioner)) + + // Admins + r.MethodFunc("GET", "/admins/{id}", authnz(h.GetAdmin)) + r.MethodFunc("GET", "/admins", authnz(h.GetAdmins)) + r.MethodFunc("POST", "/admins", authnz(h.CreateAdmin)) + r.MethodFunc("PATCH", "/admins/{id}", authnz(h.UpdateAdmin)) + r.MethodFunc("DELETE", "/admins/{id}", authnz(h.DeleteAdmin)) +} diff --git a/authority/admin/api/middleware.go b/authority/admin/api/middleware.go new file mode 100644 index 00000000..137bd6f7 --- /dev/null +++ b/authority/admin/api/middleware.go @@ -0,0 +1,66 @@ +package api + +import ( + "context" + "net/http" + + "github.com/smallstep/certificates/api" + "github.com/smallstep/certificates/authority/admin" +) + +type nextHTTP = func(http.ResponseWriter, *http.Request) + +// requireAPIEnabled is a middleware that ensures the Administration API +// is enabled before servicing requests. +func (h *Handler) requireAPIEnabled(next nextHTTP) nextHTTP { + return func(w http.ResponseWriter, r *http.Request) { + if h.db == nil { + api.WriteError(w, admin.NewError(admin.ErrorNotImplementedType, + "administration API not enabled")) + return + } + next(w, r) + } +} + +// extractAuthorizeTokenAdmin is a middleware that extracts and caches the bearer token. +func (h *Handler) extractAuthorizeTokenAdmin(next nextHTTP) nextHTTP { + return func(w http.ResponseWriter, r *http.Request) { + tok := r.Header.Get("Authorization") + if len(tok) == 0 { + api.WriteError(w, admin.NewError(admin.ErrorUnauthorizedType, + "missing authorization header token")) + return + } + + adm, err := h.auth.AuthorizeAdminToken(r, tok) + if err != nil { + api.WriteError(w, err) + return + } + + ctx := context.WithValue(r.Context(), adminContextKey, adm) + next(w, r.WithContext(ctx)) + } +} + +// ContextKey is the key type for storing and searching for ACME request +// essentials in the context of a request. +type ContextKey string + +const ( + // adminContextKey account key + adminContextKey = ContextKey("admin") +) + +/* +// adminFromContext searches the context for the token. Returns the +// token or an error. +func adminFromContext(ctx context.Context) (*linkedca.Admin, error) { + val, ok := ctx.Value(adminContextKey).(*linkedca.Admin) + if !ok || val == nil { + return nil, admin.NewErrorISE("admin not in context") + } + return val, nil +} +*/ diff --git a/authority/admin/api/provisioner.go b/authority/admin/api/provisioner.go new file mode 100644 index 00000000..e63a4417 --- /dev/null +++ b/authority/admin/api/provisioner.go @@ -0,0 +1,175 @@ +package api + +import ( + "net/http" + + "github.com/go-chi/chi" + "github.com/smallstep/certificates/api" + "github.com/smallstep/certificates/authority" + "github.com/smallstep/certificates/authority/admin" + "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/errs" + "go.step.sm/linkedca" +) + +// GetProvisionersResponse is the type for GET /admin/provisioners responses. +type GetProvisionersResponse struct { + Provisioners provisioner.List `json:"provisioners"` + NextCursor string `json:"nextCursor"` +} + +// GetProvisioner returns the requested provisioner, or an error. +func (h *Handler) GetProvisioner(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + id := r.URL.Query().Get("id") + name := chi.URLParam(r, "name") + + var ( + p provisioner.Interface + err error + ) + if len(id) > 0 { + if p, err = h.auth.LoadProvisionerByID(id); err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner %s", id)) + return + } + } else { + if p, err = h.auth.LoadProvisionerByName(name); err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner %s", name)) + return + } + } + + prov, err := h.db.GetProvisioner(ctx, p.GetID()) + if err != nil { + api.WriteError(w, err) + return + } + api.ProtoJSON(w, prov) +} + +// GetProvisioners returns the given segment of provisioners associated with the authority. +func (h *Handler) GetProvisioners(w http.ResponseWriter, r *http.Request) { + cursor, limit, err := api.ParseCursor(r) + if err != nil { + api.WriteError(w, admin.WrapError(admin.ErrorBadRequestType, err, + "error parsing cursor & limit query params")) + return + } + + p, next, err := h.auth.GetProvisioners(cursor, limit) + if err != nil { + api.WriteError(w, errs.InternalServerErr(err)) + return + } + api.JSON(w, &GetProvisionersResponse{ + Provisioners: p, + NextCursor: next, + }) +} + +// CreateProvisioner creates a new prov. +func (h *Handler) CreateProvisioner(w http.ResponseWriter, r *http.Request) { + var prov = new(linkedca.Provisioner) + if err := api.ReadProtoJSON(r.Body, prov); err != nil { + api.WriteError(w, err) + return + } + + // TODO: Validate inputs + if err := authority.ValidateClaims(prov.Claims); err != nil { + api.WriteError(w, err) + return + } + + if err := h.auth.StoreProvisioner(r.Context(), prov); err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error storing provisioner %s", prov.Name)) + return + } + api.ProtoJSONStatus(w, prov, http.StatusCreated) +} + +// DeleteProvisioner deletes a provisioner. +func (h *Handler) DeleteProvisioner(w http.ResponseWriter, r *http.Request) { + id := r.URL.Query().Get("id") + name := chi.URLParam(r, "name") + + var ( + p provisioner.Interface + err error + ) + if len(id) > 0 { + if p, err = h.auth.LoadProvisionerByID(id); err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner %s", id)) + return + } + } else { + if p, err = h.auth.LoadProvisionerByName(name); err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner %s", name)) + return + } + } + + if err := h.auth.RemoveProvisioner(r.Context(), p.GetID()); err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error removing provisioner %s", p.GetName())) + return + } + + api.JSON(w, &DeleteResponse{Status: "ok"}) +} + +// UpdateProvisioner updates an existing prov. +func (h *Handler) UpdateProvisioner(w http.ResponseWriter, r *http.Request) { + var nu = new(linkedca.Provisioner) + if err := api.ReadProtoJSON(r.Body, nu); err != nil { + api.WriteError(w, err) + return + } + + name := chi.URLParam(r, "name") + _old, err := h.auth.LoadProvisionerByName(name) + if err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner from cached configuration '%s'", name)) + return + } + + old, err := h.db.GetProvisioner(r.Context(), _old.GetID()) + if err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner from db '%s'", _old.GetID())) + return + } + + if nu.Id != old.Id { + api.WriteError(w, admin.NewErrorISE("cannot change provisioner ID")) + return + } + if nu.Type != old.Type { + api.WriteError(w, admin.NewErrorISE("cannot change provisioner type")) + return + } + if nu.AuthorityId != old.AuthorityId { + api.WriteError(w, admin.NewErrorISE("cannot change provisioner authorityID")) + return + } + if !nu.CreatedAt.AsTime().Equal(old.CreatedAt.AsTime()) { + api.WriteError(w, admin.NewErrorISE("cannot change provisioner createdAt")) + return + } + if !nu.DeletedAt.AsTime().Equal(old.DeletedAt.AsTime()) { + api.WriteError(w, admin.NewErrorISE("cannot change provisioner deletedAt")) + return + } + + // TODO: Validate inputs + if err := authority.ValidateClaims(nu.Claims); err != nil { + api.WriteError(w, err) + return + } + + if err := h.auth.UpdateProvisioner(r.Context(), nu); err != nil { + api.WriteError(w, err) + return + } + api.ProtoJSONStatus(w, nu, http.StatusOK) +} diff --git a/authority/admin/collection.go b/authority/admin/collection.go deleted file mode 100644 index e9d41113..00000000 --- a/authority/admin/collection.go +++ /dev/null @@ -1,180 +0,0 @@ -package admin - -import ( - "crypto/sha1" - "encoding/binary" - "encoding/hex" - "fmt" - "sort" - "strings" - "sync" - - "github.com/pkg/errors" - "github.com/smallstep/certificates/authority/provisioner" - "github.com/smallstep/certificates/linkedca" -) - -// DefaultAdminLimit is the default limit for listing provisioners. -const DefaultAdminLimit = 20 - -// DefaultAdminMax is the maximum limit for listing provisioners. -const DefaultAdminMax = 100 - -type uidAdmin struct { - admin *linkedca.Admin - uid string -} - -type adminSlice []uidAdmin - -func (p adminSlice) Len() int { return len(p) } -func (p adminSlice) Less(i, j int) bool { return p[i].uid < p[j].uid } -func (p adminSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } - -// Collection is a memory map of admins. -type Collection struct { - byID *sync.Map - bySubProv *sync.Map - byProv *sync.Map - sorted adminSlice - provisioners *provisioner.Collection - superCount int - superCountByProvisioner map[string]int -} - -// NewCollection initializes a collection of provisioners. The given list of -// audiences are the audiences used by the JWT provisioner. -func NewCollection(provisioners *provisioner.Collection) *Collection { - return &Collection{ - byID: new(sync.Map), - byProv: new(sync.Map), - bySubProv: new(sync.Map), - superCountByProvisioner: map[string]int{}, - provisioners: provisioners, - } -} - -// LoadByID a admin by the ID. -func (c *Collection) LoadByID(id string) (*linkedca.Admin, bool) { - return loadAdmin(c.byID, id) -} - -func subProvNameHash(sub, provName string) string { - subHash := sha1.Sum([]byte(sub)) - provNameHash := sha1.Sum([]byte(provName)) - _res := sha1.Sum(append(subHash[:], provNameHash[:]...)) - return string(_res[:]) -} - -// LoadBySubProv a admin by the subject and provisioner name. -func (c *Collection) LoadBySubProv(sub, provName string) (*linkedca.Admin, bool) { - return loadAdmin(c.bySubProv, subProvNameHash(sub, provName)) -} - -// LoadByProvisioner a admin by the subject and provisioner name. -func (c *Collection) LoadByProvisioner(provName string) ([]*linkedca.Admin, bool) { - a, ok := c.byProv.Load(provName) - if !ok { - return nil, false - } - admins, ok := a.([]*linkedca.Admin) - if !ok { - return nil, false - } - return admins, true -} - -// Store adds an admin to the collection and enforces the uniqueness of -// admin IDs and amdin subject <-> provisioner name combos. -func (c *Collection) Store(adm *linkedca.Admin) error { - p, ok := c.provisioners.Load(adm.ProvisionerId) - if !ok { - return fmt.Errorf("provisioner %s not found", adm.ProvisionerId) - } - // Store admin always in byID. ID must be unique. - if _, loaded := c.byID.LoadOrStore(adm.Id, adm); loaded { - return errors.New("cannot add multiple admins with the same id") - } - - provName := p.GetName() - // Store admin always in bySubProv. Subject <-> ProvisionerName must be unique. - if _, loaded := c.bySubProv.LoadOrStore(subProvNameHash(adm.Subject, provName), adm); loaded { - c.byID.Delete(adm.Id) - return errors.New("cannot add multiple admins with the same subject and provisioner") - } - - if admins, ok := c.LoadByProvisioner(provName); ok { - c.byProv.Store(provName, append(admins, adm)) - c.superCountByProvisioner[provName]++ - } else { - c.byProv.Store(provName, []*linkedca.Admin{adm}) - c.superCountByProvisioner[provName] = 1 - } - c.superCount++ - - // Store sorted admins. - // Use the first 4 bytes (32bit) of the sum to insert the order - // Using big endian format to get the strings sorted: - // 0x00000000, 0x00000001, 0x00000002, ... - bi := make([]byte, 4) - _sum := sha1.Sum([]byte(adm.Id)) - sum := _sum[:] - binary.BigEndian.PutUint32(bi, uint32(c.sorted.Len())) - sum[0], sum[1], sum[2], sum[3] = bi[0], bi[1], bi[2], bi[3] - c.sorted = append(c.sorted, uidAdmin{ - admin: adm, - uid: hex.EncodeToString(sum), - }) - sort.Sort(c.sorted) - - return nil -} - -// SuperCount returns the total number of admins. -func (c *Collection) SuperCount() int { - return c.superCount -} - -// SuperCountByProvisioner returns the total number of admins. -func (c *Collection) SuperCountByProvisioner(provName string) int { - if cnt, ok := c.superCountByProvisioner[provName]; ok { - return cnt - } - return 0 -} - -// Find implements pagination on a list of sorted provisioners. -func (c *Collection) Find(cursor string, limit int) ([]*linkedca.Admin, string) { - switch { - case limit <= 0: - limit = DefaultAdminLimit - case limit > DefaultAdminMax: - limit = DefaultAdminMax - } - - n := c.sorted.Len() - cursor = fmt.Sprintf("%040s", cursor) - i := sort.Search(n, func(i int) bool { return c.sorted[i].uid >= cursor }) - - slice := []*linkedca.Admin{} - for ; i < n && len(slice) < limit; i++ { - slice = append(slice, c.sorted[i].admin) - } - - if i < n { - return slice, strings.TrimLeft(c.sorted[i].uid, "0") - } - return slice, "" -} - -func loadAdmin(m *sync.Map, key string) (*linkedca.Admin, bool) { - a, ok := m.Load(key) - if !ok { - return nil, false - } - adm, ok := a.(*linkedca.Admin) - if !ok { - return nil, false - } - return adm, true -} diff --git a/authority/mgmt/db.go b/authority/admin/db.go similarity index 72% rename from authority/mgmt/db.go rename to authority/admin/db.go index 64ed39a2..15fe6686 100644 --- a/authority/mgmt/db.go +++ b/authority/admin/db.go @@ -1,16 +1,59 @@ -package mgmt +package admin import ( "context" + "encoding/json" + "fmt" "github.com/pkg/errors" - "github.com/smallstep/certificates/linkedca" + "go.step.sm/linkedca" +) + +const ( + // DefaultAuthorityID is the default AuthorityID. This will be the ID + // of the first Authority created, as well as the default AuthorityID + // if one is not specified in the configuration. + DefaultAuthorityID = "00000000-0000-0000-0000-000000000000" ) // ErrNotFound is an error that should be used by the authority.DB interface to // indicate that an entity does not exist. var ErrNotFound = errors.New("not found") +// UnmarshalProvisionerDetails unmarshals details type to the specific provisioner details. +func UnmarshalProvisionerDetails(typ linkedca.Provisioner_Type, data []byte) (*linkedca.ProvisionerDetails, error) { + var v linkedca.ProvisionerDetails + switch typ { + case linkedca.Provisioner_JWK: + v.Data = new(linkedca.ProvisionerDetails_JWK) + case linkedca.Provisioner_OIDC: + v.Data = new(linkedca.ProvisionerDetails_OIDC) + case linkedca.Provisioner_GCP: + v.Data = new(linkedca.ProvisionerDetails_GCP) + case linkedca.Provisioner_AWS: + v.Data = new(linkedca.ProvisionerDetails_AWS) + case linkedca.Provisioner_AZURE: + v.Data = new(linkedca.ProvisionerDetails_Azure) + case linkedca.Provisioner_ACME: + v.Data = new(linkedca.ProvisionerDetails_ACME) + case linkedca.Provisioner_X5C: + v.Data = new(linkedca.ProvisionerDetails_X5C) + case linkedca.Provisioner_K8SSA: + v.Data = new(linkedca.ProvisionerDetails_K8SSA) + case linkedca.Provisioner_SSHPOP: + v.Data = new(linkedca.ProvisionerDetails_SSHPOP) + case linkedca.Provisioner_SCEP: + v.Data = new(linkedca.ProvisionerDetails_SCEP) + default: + return nil, fmt.Errorf("unsupported provisioner type %s", typ) + } + + if err := json.Unmarshal(data, v.Data); err != nil { + return nil, err + } + return &linkedca.ProvisionerDetails{Data: v.Data}, nil +} + // DB is the DB interface expected by the step-ca ACME API. type DB interface { CreateProvisioner(ctx context.Context, prov *linkedca.Provisioner) error diff --git a/authority/mgmt/db/nosql/admin.go b/authority/admin/db/nosql/admin.go similarity index 68% rename from authority/mgmt/db/nosql/admin.go rename to authority/admin/db/nosql/admin.go index 8fd0fea5..6bb6bdd1 100644 --- a/authority/mgmt/db/nosql/admin.go +++ b/authority/admin/db/nosql/admin.go @@ -6,9 +6,10 @@ import ( "time" "github.com/pkg/errors" - "github.com/smallstep/certificates/authority/mgmt" - "github.com/smallstep/certificates/linkedca" + "github.com/smallstep/certificates/authority/admin" "github.com/smallstep/nosql" + "go.step.sm/linkedca" + "google.golang.org/protobuf/types/known/timestamppb" ) // dbAdmin is the database representation of the Admin type. @@ -22,60 +23,66 @@ type dbAdmin struct { DeletedAt time.Time `json:"deletedAt"` } -func (dbp *dbAdmin) clone() *dbAdmin { - u := *dbp +func (dba *dbAdmin) convert() *linkedca.Admin { + return &linkedca.Admin{ + Id: dba.ID, + AuthorityId: dba.AuthorityID, + ProvisionerId: dba.ProvisionerID, + Subject: dba.Subject, + Type: dba.Type, + CreatedAt: timestamppb.New(dba.CreatedAt), + DeletedAt: timestamppb.New(dba.DeletedAt), + } +} + +func (dba *dbAdmin) clone() *dbAdmin { + u := *dba return &u } func (db *DB) getDBAdminBytes(ctx context.Context, id string) ([]byte, error) { - data, err := db.db.Get(authorityAdminsTable, []byte(id)) + data, err := db.db.Get(adminsTable, []byte(id)) if nosql.IsErrNotFound(err) { - return nil, mgmt.NewError(mgmt.ErrorNotFoundType, "admin %s not found", id) + return nil, admin.NewError(admin.ErrorNotFoundType, "admin %s not found", id) } else if err != nil { return nil, errors.Wrapf(err, "error loading admin %s", id) } return data, nil } +func (db *DB) unmarshalDBAdmin(data []byte, id string) (*dbAdmin, error) { + var dba = new(dbAdmin) + if err := json.Unmarshal(data, dba); err != nil { + return nil, errors.Wrapf(err, "error unmarshaling admin %s into dbAdmin", id) + } + if !dba.DeletedAt.IsZero() { + return nil, admin.NewError(admin.ErrorDeletedType, "admin %s is deleted", id) + } + if dba.AuthorityID != db.authorityID { + return nil, admin.NewError(admin.ErrorAuthorityMismatchType, + "admin %s is not owned by authority %s", dba.ID, db.authorityID) + } + return dba, nil +} + func (db *DB) getDBAdmin(ctx context.Context, id string) (*dbAdmin, error) { data, err := db.getDBAdminBytes(ctx, id) if err != nil { return nil, err } - dba, err := unmarshalDBAdmin(data, id) + dba, err := db.unmarshalDBAdmin(data, id) if err != nil { return nil, err } - if dba.AuthorityID != db.authorityID { - return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType, - "admin %s is not owned by authority %s", dba.ID, db.authorityID) - } return dba, nil } -func unmarshalDBAdmin(data []byte, id string) (*dbAdmin, error) { - var dba = new(dbAdmin) - if err := json.Unmarshal(data, dba); err != nil { - return nil, errors.Wrapf(err, "error unmarshaling admin %s into dbAdmin", id) - } - if !dba.DeletedAt.IsZero() { - return nil, mgmt.NewError(mgmt.ErrorDeletedType, "admin %s is deleted", id) - } - return dba, nil -} - -func unmarshalAdmin(data []byte, id string) (*linkedca.Admin, error) { - dba, err := unmarshalDBAdmin(data, id) +func (db *DB) unmarshalAdmin(data []byte, id string) (*linkedca.Admin, error) { + dba, err := db.unmarshalDBAdmin(data, id) if err != nil { return nil, err } - return &linkedca.Admin{ - Id: dba.ID, - AuthorityId: dba.AuthorityID, - ProvisionerId: dba.ProvisionerID, - Subject: dba.Subject, - Type: dba.Type, - }, nil + return dba.convert(), nil } // GetAdmin retrieves and unmarshals a admin from the database. @@ -84,14 +91,10 @@ func (db *DB) GetAdmin(ctx context.Context, id string) (*linkedca.Admin, error) if err != nil { return nil, err } - adm, err := unmarshalAdmin(data, id) + adm, err := db.unmarshalAdmin(data, id) if err != nil { return nil, err } - if adm.AuthorityId != db.authorityID { - return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType, - "admin %s is not owned by authority %s", adm.Id, db.authorityID) - } return adm, nil } @@ -100,15 +103,24 @@ func (db *DB) GetAdmin(ctx context.Context, id string) (*linkedca.Admin, error) // from the database. // TODO should we be paginating? func (db *DB) GetAdmins(ctx context.Context) ([]*linkedca.Admin, error) { - dbEntries, err := db.db.List(authorityAdminsTable) + dbEntries, err := db.db.List(adminsTable) if err != nil { return nil, errors.Wrap(err, "error loading admins") } var admins = []*linkedca.Admin{} for _, entry := range dbEntries { - adm, err := unmarshalAdmin(entry.Value, string(entry.Key)) + adm, err := db.unmarshalAdmin(entry.Value, string(entry.Key)) if err != nil { - return nil, err + switch k := err.(type) { + case *admin.Error: + if k.IsType(admin.ErrorDeletedType) || k.IsType(admin.ErrorAuthorityMismatchType) { + continue + } else { + return nil, err + } + default: + return nil, err + } } if adm.AuthorityId != db.authorityID { continue @@ -123,7 +135,7 @@ func (db *DB) CreateAdmin(ctx context.Context, adm *linkedca.Admin) error { var err error adm.Id, err = randID() if err != nil { - return mgmt.WrapErrorISE(err, "error generating random id for admin") + return admin.WrapErrorISE(err, "error generating random id for admin") } adm.AuthorityId = db.authorityID @@ -136,7 +148,7 @@ func (db *DB) CreateAdmin(ctx context.Context, adm *linkedca.Admin) error { CreatedAt: clock.Now(), } - return db.save(ctx, dba.ID, dba, nil, "admin", authorityAdminsTable) + return db.save(ctx, dba.ID, dba, nil, "admin", adminsTable) } // UpdateAdmin saves an updated admin to the database. @@ -149,7 +161,7 @@ func (db *DB) UpdateAdmin(ctx context.Context, adm *linkedca.Admin) error { nu := old.clone() nu.Type = adm.Type - return db.save(ctx, old.ID, nu, old, "admin", authorityAdminsTable) + return db.save(ctx, old.ID, nu, old, "admin", adminsTable) } // DeleteAdmin saves an updated admin to the database. @@ -162,5 +174,5 @@ func (db *DB) DeleteAdmin(ctx context.Context, id string) error { nu := old.clone() nu.DeletedAt = clock.Now() - return db.save(ctx, old.ID, nu, old, "admin", authorityAdminsTable) + return db.save(ctx, old.ID, nu, old, "admin", adminsTable) } diff --git a/authority/admin/db/nosql/admin_test.go b/authority/admin/db/nosql/admin_test.go new file mode 100644 index 00000000..092d72db --- /dev/null +++ b/authority/admin/db/nosql/admin_test.go @@ -0,0 +1,1121 @@ +package nosql + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/pkg/errors" + "github.com/smallstep/assert" + "github.com/smallstep/certificates/authority/admin" + "github.com/smallstep/certificates/db" + "github.com/smallstep/nosql" + "github.com/smallstep/nosql/database" + nosqldb "github.com/smallstep/nosql/database" + "go.step.sm/linkedca" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestDB_getDBAdminBytes(t *testing.T) { + adminID := "adminID" + type test struct { + db nosql.DB + err error + adminErr *admin.Error + } + var tests = map[string]func(t *testing.T) test{ + "fail/not-found": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return nil, nosqldb.ErrNotFound + }, + }, + adminErr: admin.NewError(admin.ErrorNotFoundType, "admin adminID not found"), + } + }, + "fail/db.Get-error": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return nil, errors.New("force") + }, + }, + err: errors.New("error loading admin adminID: force"), + } + }, + "ok": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return []byte("foo"), nil + }, + }, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db} + if b, err := db.getDBAdminBytes(context.Background(), adminID); err != nil { + switch k := err.(type) { + case *admin.Error: + if assert.NotNil(t, tc.adminErr) { + assert.Equals(t, k.Type, tc.adminErr.Type) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, k.Status, tc.adminErr.Status) + assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + } + default: + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } + } else { + if assert.Nil(t, tc.err) { + assert.Equals(t, string(b), "foo") + } + } + }) + } +} + +func TestDB_getDBAdmin(t *testing.T) { + adminID := "adminID" + type test struct { + db nosql.DB + err error + adminErr *admin.Error + dba *dbAdmin + } + var tests = map[string]func(t *testing.T) test{ + "fail/not-found": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return nil, nosqldb.ErrNotFound + }, + }, + adminErr: admin.NewError(admin.ErrorNotFoundType, "admin adminID not found"), + } + }, + "fail/db.Get-error": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return nil, errors.New("force") + }, + }, + err: errors.New("error loading admin adminID: force"), + } + }, + "fail/unmarshal-error": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return []byte("foo"), nil + }, + }, + err: errors.New("error unmarshaling admin adminID into dbAdmin"), + } + }, + "fail/deleted": func(t *testing.T) test { + now := clock.Now() + dba := &dbAdmin{ + ID: adminID, + AuthorityID: admin.DefaultAuthorityID, + ProvisionerID: "provID", + Subject: "max@smallstep.com", + Type: linkedca.Admin_SUPER_ADMIN, + CreatedAt: now, + DeletedAt: now, + } + b, err := json.Marshal(dba) + assert.FatalError(t, err) + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return b, nil + }, + }, + adminErr: admin.NewError(admin.ErrorDeletedType, "admin adminID is deleted"), + } + }, + "ok": func(t *testing.T) test { + now := clock.Now() + dba := &dbAdmin{ + ID: adminID, + AuthorityID: admin.DefaultAuthorityID, + ProvisionerID: "provID", + Subject: "max@smallstep.com", + Type: linkedca.Admin_SUPER_ADMIN, + CreatedAt: now, + } + b, err := json.Marshal(dba) + assert.FatalError(t, err) + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return b, nil + }, + }, + dba: dba, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} + if dba, err := db.getDBAdmin(context.Background(), adminID); err != nil { + switch k := err.(type) { + case *admin.Error: + if assert.NotNil(t, tc.adminErr) { + assert.Equals(t, k.Type, tc.adminErr.Type) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, k.Status, tc.adminErr.Status) + assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + } + default: + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } + } else { + if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) { + assert.Equals(t, dba.ID, adminID) + assert.Equals(t, dba.AuthorityID, tc.dba.AuthorityID) + assert.Equals(t, dba.ProvisionerID, tc.dba.ProvisionerID) + assert.Equals(t, dba.Subject, tc.dba.Subject) + assert.Equals(t, dba.Type, tc.dba.Type) + assert.Equals(t, dba.CreatedAt, tc.dba.CreatedAt) + assert.Fatal(t, dba.DeletedAt.IsZero()) + } + } + }) + } +} + +func TestDB_unmarshalDBAdmin(t *testing.T) { + adminID := "adminID" + type test struct { + in []byte + err error + adminErr *admin.Error + dba *dbAdmin + } + var tests = map[string]func(t *testing.T) test{ + "fail/unmarshal-error": func(t *testing.T) test { + return test{ + in: []byte("foo"), + err: errors.New("error unmarshaling admin adminID into dbAdmin"), + } + }, + "fail/deleted-error": func(t *testing.T) test { + dba := &dbAdmin{ + DeletedAt: time.Now(), + } + data, err := json.Marshal(dba) + assert.FatalError(t, err) + return test{ + in: data, + adminErr: admin.NewError(admin.ErrorDeletedType, "admin adminID is deleted"), + } + }, + "fail/authority-mismatch-error": func(t *testing.T) test { + dba := &dbAdmin{ + ID: adminID, + AuthorityID: "foo", + } + data, err := json.Marshal(dba) + assert.FatalError(t, err) + return test{ + in: data, + adminErr: admin.NewError(admin.ErrorAuthorityMismatchType, + "admin %s is not owned by authority %s", adminID, admin.DefaultAuthorityID), + } + }, + "ok": func(t *testing.T) test { + dba := &dbAdmin{ + ID: adminID, + Subject: "max@smallstep.com", + ProvisionerID: "provID", + AuthorityID: admin.DefaultAuthorityID, + Type: linkedca.Admin_SUPER_ADMIN, + CreatedAt: clock.Now(), + } + data, err := json.Marshal(dba) + assert.FatalError(t, err) + return test{ + in: data, + dba: dba, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{authorityID: admin.DefaultAuthorityID} + if dba, err := db.unmarshalDBAdmin(tc.in, adminID); err != nil { + switch k := err.(type) { + case *admin.Error: + if assert.NotNil(t, tc.adminErr) { + assert.Equals(t, k.Type, tc.adminErr.Type) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, k.Status, tc.adminErr.Status) + assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + } + default: + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } + } else { + if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) { + assert.Equals(t, dba.ID, adminID) + assert.Equals(t, dba.AuthorityID, tc.dba.AuthorityID) + assert.Equals(t, dba.ProvisionerID, tc.dba.ProvisionerID) + assert.Equals(t, dba.Subject, tc.dba.Subject) + assert.Equals(t, dba.Type, tc.dba.Type) + assert.Equals(t, dba.CreatedAt, tc.dba.CreatedAt) + assert.Fatal(t, dba.DeletedAt.IsZero()) + } + } + }) + } +} + +func TestDB_unmarshalAdmin(t *testing.T) { + adminID := "adminID" + type test struct { + in []byte + err error + adminErr *admin.Error + dba *dbAdmin + } + var tests = map[string]func(t *testing.T) test{ + "fail/unmarshal-error": func(t *testing.T) test { + return test{ + in: []byte("foo"), + err: errors.New("error unmarshaling admin adminID into dbAdmin"), + } + }, + "fail/deleted-error": func(t *testing.T) test { + dba := &dbAdmin{ + DeletedAt: time.Now(), + } + data, err := json.Marshal(dba) + assert.FatalError(t, err) + return test{ + in: data, + adminErr: admin.NewError(admin.ErrorDeletedType, "admin adminID is deleted"), + } + }, + "ok": func(t *testing.T) test { + dba := &dbAdmin{ + ID: adminID, + Subject: "max@smallstep.com", + ProvisionerID: "provID", + AuthorityID: admin.DefaultAuthorityID, + Type: linkedca.Admin_SUPER_ADMIN, + CreatedAt: clock.Now(), + } + data, err := json.Marshal(dba) + assert.FatalError(t, err) + return test{ + in: data, + dba: dba, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{authorityID: admin.DefaultAuthorityID} + if adm, err := db.unmarshalAdmin(tc.in, adminID); err != nil { + switch k := err.(type) { + case *admin.Error: + if assert.NotNil(t, tc.adminErr) { + assert.Equals(t, k.Type, tc.adminErr.Type) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, k.Status, tc.adminErr.Status) + assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + } + default: + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } + } else { + if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) { + assert.Equals(t, adm.Id, adminID) + assert.Equals(t, adm.AuthorityId, tc.dba.AuthorityID) + assert.Equals(t, adm.ProvisionerId, tc.dba.ProvisionerID) + assert.Equals(t, adm.Subject, tc.dba.Subject) + assert.Equals(t, adm.Type, tc.dba.Type) + assert.Equals(t, adm.CreatedAt, timestamppb.New(tc.dba.CreatedAt)) + assert.Equals(t, adm.DeletedAt, timestamppb.New(tc.dba.DeletedAt)) + } + } + }) + } +} + +func TestDB_GetAdmin(t *testing.T) { + adminID := "adminID" + type test struct { + db nosql.DB + err error + adminErr *admin.Error + dba *dbAdmin + } + var tests = map[string]func(t *testing.T) test{ + "fail/not-found": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return nil, nosqldb.ErrNotFound + }, + }, + adminErr: admin.NewError(admin.ErrorNotFoundType, "admin adminID not found"), + } + }, + "fail/db.Get-error": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return nil, errors.New("force") + }, + }, + err: errors.New("error loading admin adminID: force"), + } + }, + "fail/unmarshal-error": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return []byte("foo"), nil + }, + }, + err: errors.New("error unmarshaling admin adminID into dbAdmin"), + } + }, + "fail/deleted": func(t *testing.T) test { + dba := &dbAdmin{ + ID: adminID, + AuthorityID: admin.DefaultAuthorityID, + ProvisionerID: "provID", + Subject: "max@smallstep.com", + Type: linkedca.Admin_SUPER_ADMIN, + CreatedAt: clock.Now(), + DeletedAt: clock.Now(), + } + b, err := json.Marshal(dba) + assert.FatalError(t, err) + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return b, nil + }, + }, + dba: dba, + adminErr: admin.NewError(admin.ErrorDeletedType, "admin adminID is deleted"), + } + }, + "fail/authorityID-mismatch": func(t *testing.T) test { + dba := &dbAdmin{ + ID: adminID, + AuthorityID: "foo", + ProvisionerID: "provID", + Subject: "max@smallstep.com", + Type: linkedca.Admin_SUPER_ADMIN, + CreatedAt: clock.Now(), + } + b, err := json.Marshal(dba) + assert.FatalError(t, err) + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return b, nil + }, + }, + dba: dba, + adminErr: admin.NewError(admin.ErrorAuthorityMismatchType, + "admin %s is not owned by authority %s", dba.ID, admin.DefaultAuthorityID), + } + }, + "ok": func(t *testing.T) test { + dba := &dbAdmin{ + ID: adminID, + AuthorityID: admin.DefaultAuthorityID, + ProvisionerID: "provID", + Subject: "max@smallstep.com", + Type: linkedca.Admin_SUPER_ADMIN, + CreatedAt: clock.Now(), + } + b, err := json.Marshal(dba) + assert.FatalError(t, err) + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return b, nil + }, + }, + dba: dba, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} + if adm, err := db.GetAdmin(context.Background(), adminID); err != nil { + switch k := err.(type) { + case *admin.Error: + if assert.NotNil(t, tc.adminErr) { + assert.Equals(t, k.Type, tc.adminErr.Type) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, k.Status, tc.adminErr.Status) + assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + } + default: + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } + } else { + if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) { + assert.Equals(t, adm.Id, adminID) + assert.Equals(t, adm.AuthorityId, tc.dba.AuthorityID) + assert.Equals(t, adm.ProvisionerId, tc.dba.ProvisionerID) + assert.Equals(t, adm.Subject, tc.dba.Subject) + assert.Equals(t, adm.Type, tc.dba.Type) + assert.Equals(t, adm.CreatedAt, timestamppb.New(tc.dba.CreatedAt)) + assert.Equals(t, adm.DeletedAt, timestamppb.New(tc.dba.DeletedAt)) + } + } + }) + } +} + +func TestDB_DeleteAdmin(t *testing.T) { + adminID := "adminID" + type test struct { + db nosql.DB + err error + adminErr *admin.Error + } + var tests = map[string]func(t *testing.T) test{ + "fail/not-found": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return nil, nosqldb.ErrNotFound + }, + }, + adminErr: admin.NewError(admin.ErrorNotFoundType, "admin adminID not found"), + } + }, + "fail/db.Get-error": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return nil, errors.New("force") + }, + }, + err: errors.New("error loading admin adminID: force"), + } + }, + "fail/save-error": func(t *testing.T) test { + dba := &dbAdmin{ + ID: adminID, + AuthorityID: admin.DefaultAuthorityID, + ProvisionerID: "provID", + Subject: "max@smallstep.com", + Type: linkedca.Admin_SUPER_ADMIN, + CreatedAt: clock.Now(), + } + data, err := json.Marshal(dba) + assert.FatalError(t, err) + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return data, nil + }, + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + assert.Equals(t, string(old), string(data)) + + var _dba = new(dbAdmin) + assert.FatalError(t, json.Unmarshal(nu, _dba)) + + assert.Equals(t, _dba.ID, dba.ID) + assert.Equals(t, _dba.AuthorityID, dba.AuthorityID) + assert.Equals(t, _dba.ProvisionerID, dba.ProvisionerID) + assert.Equals(t, _dba.Subject, dba.Subject) + assert.Equals(t, _dba.Type, dba.Type) + assert.Equals(t, _dba.CreatedAt, dba.CreatedAt) + + assert.True(t, _dba.DeletedAt.Before(time.Now())) + assert.True(t, _dba.DeletedAt.After(time.Now().Add(-time.Minute))) + + return nil, false, errors.New("force") + }, + }, + err: errors.New("error saving authority admin: force"), + } + }, + "ok": func(t *testing.T) test { + dba := &dbAdmin{ + ID: adminID, + AuthorityID: admin.DefaultAuthorityID, + ProvisionerID: "provID", + Subject: "max@smallstep.com", + Type: linkedca.Admin_SUPER_ADMIN, + CreatedAt: clock.Now(), + } + data, err := json.Marshal(dba) + assert.FatalError(t, err) + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return data, nil + }, + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + assert.Equals(t, string(old), string(data)) + + var _dba = new(dbAdmin) + assert.FatalError(t, json.Unmarshal(nu, _dba)) + + assert.Equals(t, _dba.ID, dba.ID) + assert.Equals(t, _dba.AuthorityID, dba.AuthorityID) + assert.Equals(t, _dba.ProvisionerID, dba.ProvisionerID) + assert.Equals(t, _dba.Subject, dba.Subject) + assert.Equals(t, _dba.Type, dba.Type) + assert.Equals(t, _dba.CreatedAt, dba.CreatedAt) + + assert.True(t, _dba.DeletedAt.Before(time.Now())) + assert.True(t, _dba.DeletedAt.After(time.Now().Add(-time.Minute))) + + return nu, true, nil + }, + }, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} + if err := db.DeleteAdmin(context.Background(), adminID); err != nil { + switch k := err.(type) { + case *admin.Error: + if assert.NotNil(t, tc.adminErr) { + assert.Equals(t, k.Type, tc.adminErr.Type) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, k.Status, tc.adminErr.Status) + assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + } + default: + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } + } + }) + } +} + +func TestDB_UpdateAdmin(t *testing.T) { + adminID := "adminID" + type test struct { + db nosql.DB + err error + adminErr *admin.Error + adm *linkedca.Admin + } + var tests = map[string]func(t *testing.T) test{ + "fail/not-found": func(t *testing.T) test { + return test{ + adm: &linkedca.Admin{Id: adminID}, + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return nil, nosqldb.ErrNotFound + }, + }, + adminErr: admin.NewError(admin.ErrorNotFoundType, "admin adminID not found"), + } + }, + "fail/db.Get-error": func(t *testing.T) test { + return test{ + adm: &linkedca.Admin{Id: adminID}, + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return nil, errors.New("force") + }, + }, + err: errors.New("error loading admin adminID: force"), + } + }, + "fail/save-error": func(t *testing.T) test { + dba := &dbAdmin{ + ID: adminID, + AuthorityID: admin.DefaultAuthorityID, + ProvisionerID: "provID", + Subject: "max@smallstep.com", + Type: linkedca.Admin_SUPER_ADMIN, + CreatedAt: clock.Now(), + } + + upd := dba.convert() + upd.Type = linkedca.Admin_ADMIN + + data, err := json.Marshal(dba) + assert.FatalError(t, err) + return test{ + adm: upd, + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return data, nil + }, + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + assert.Equals(t, string(old), string(data)) + + var _dba = new(dbAdmin) + assert.FatalError(t, json.Unmarshal(nu, _dba)) + + assert.Equals(t, _dba.ID, dba.ID) + assert.Equals(t, _dba.AuthorityID, dba.AuthorityID) + assert.Equals(t, _dba.ProvisionerID, dba.ProvisionerID) + assert.Equals(t, _dba.Subject, dba.Subject) + assert.Equals(t, _dba.Type, linkedca.Admin_ADMIN) + assert.Equals(t, _dba.CreatedAt, dba.CreatedAt) + + return nil, false, errors.New("force") + }, + }, + err: errors.New("error saving authority admin: force"), + } + }, + "ok": func(t *testing.T) test { + dba := &dbAdmin{ + ID: adminID, + AuthorityID: admin.DefaultAuthorityID, + ProvisionerID: "provID", + Subject: "max@smallstep.com", + Type: linkedca.Admin_SUPER_ADMIN, + CreatedAt: clock.Now(), + } + + upd := dba.convert() + upd.Type = linkedca.Admin_ADMIN + + data, err := json.Marshal(dba) + assert.FatalError(t, err) + return test{ + adm: upd, + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + + return data, nil + }, + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, string(key), adminID) + assert.Equals(t, string(old), string(data)) + + var _dba = new(dbAdmin) + assert.FatalError(t, json.Unmarshal(nu, _dba)) + + assert.Equals(t, _dba.ID, dba.ID) + assert.Equals(t, _dba.AuthorityID, dba.AuthorityID) + assert.Equals(t, _dba.ProvisionerID, dba.ProvisionerID) + assert.Equals(t, _dba.Subject, dba.Subject) + assert.Equals(t, _dba.Type, linkedca.Admin_ADMIN) + assert.Equals(t, _dba.CreatedAt, dba.CreatedAt) + + return nu, true, nil + }, + }, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} + if err := db.UpdateAdmin(context.Background(), tc.adm); err != nil { + switch k := err.(type) { + case *admin.Error: + if assert.NotNil(t, tc.adminErr) { + assert.Equals(t, k.Type, tc.adminErr.Type) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, k.Status, tc.adminErr.Status) + assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + } + default: + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } + } + }) + } +} + +func TestDB_CreateAdmin(t *testing.T) { + type test struct { + db nosql.DB + err error + adminErr *admin.Error + adm *linkedca.Admin + } + var tests = map[string]func(t *testing.T) test{ + "fail/save-error": func(t *testing.T) test { + adm := &linkedca.Admin{ + AuthorityId: admin.DefaultAuthorityID, + ProvisionerId: "provID", + Subject: "max@smallstep.com", + Type: linkedca.Admin_ADMIN, + } + + return test{ + adm: adm, + db: &db.MockNoSQLDB{ + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, old, nil) + + var _dba = new(dbAdmin) + assert.FatalError(t, json.Unmarshal(nu, _dba)) + + assert.True(t, len(_dba.ID) > 0 && _dba.ID == string(key)) + assert.Equals(t, _dba.AuthorityID, adm.AuthorityId) + assert.Equals(t, _dba.ProvisionerID, adm.ProvisionerId) + assert.Equals(t, _dba.Subject, adm.Subject) + assert.Equals(t, _dba.Type, linkedca.Admin_ADMIN) + + assert.True(t, _dba.CreatedAt.Before(time.Now())) + assert.True(t, _dba.CreatedAt.After(time.Now().Add(-time.Minute))) + + return nil, false, errors.New("force") + }, + }, + err: errors.New("error saving authority admin: force"), + } + }, + "ok": func(t *testing.T) test { + adm := &linkedca.Admin{ + AuthorityId: admin.DefaultAuthorityID, + ProvisionerId: "provID", + Subject: "max@smallstep.com", + Type: linkedca.Admin_ADMIN, + } + + return test{ + adm: adm, + db: &db.MockNoSQLDB{ + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, bucket, adminsTable) + assert.Equals(t, old, nil) + + var _dba = new(dbAdmin) + assert.FatalError(t, json.Unmarshal(nu, _dba)) + + assert.True(t, len(_dba.ID) > 0 && _dba.ID == string(key)) + assert.Equals(t, _dba.AuthorityID, adm.AuthorityId) + assert.Equals(t, _dba.ProvisionerID, adm.ProvisionerId) + assert.Equals(t, _dba.Subject, adm.Subject) + assert.Equals(t, _dba.Type, linkedca.Admin_ADMIN) + + assert.True(t, _dba.CreatedAt.Before(time.Now())) + assert.True(t, _dba.CreatedAt.After(time.Now().Add(-time.Minute))) + + return nu, true, nil + }, + }, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} + if err := db.CreateAdmin(context.Background(), tc.adm); err != nil { + switch k := err.(type) { + case *admin.Error: + if assert.NotNil(t, tc.adminErr) { + assert.Equals(t, k.Type, tc.adminErr.Type) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, k.Status, tc.adminErr.Status) + assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + } + default: + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } + } + }) + } +} + +func TestDB_GetAdmins(t *testing.T) { + now := clock.Now() + fooAdmin := &dbAdmin{ + ID: "foo", + AuthorityID: admin.DefaultAuthorityID, + ProvisionerID: "provID", + Subject: "foo@smallstep.com", + Type: linkedca.Admin_SUPER_ADMIN, + CreatedAt: now, + } + foob, err := json.Marshal(fooAdmin) + assert.FatalError(t, err) + + barAdmin := &dbAdmin{ + ID: "bar", + AuthorityID: admin.DefaultAuthorityID, + ProvisionerID: "provID", + Subject: "bar@smallstep.com", + Type: linkedca.Admin_ADMIN, + CreatedAt: now, + DeletedAt: now, + } + barb, err := json.Marshal(barAdmin) + assert.FatalError(t, err) + + bazAdmin := &dbAdmin{ + ID: "baz", + AuthorityID: "bazzer", + ProvisionerID: "provID", + Subject: "baz@smallstep.com", + Type: linkedca.Admin_ADMIN, + CreatedAt: now, + } + bazb, err := json.Marshal(bazAdmin) + assert.FatalError(t, err) + + zapAdmin := &dbAdmin{ + ID: "zap", + AuthorityID: admin.DefaultAuthorityID, + ProvisionerID: "provID", + Subject: "zap@smallstep.com", + Type: linkedca.Admin_ADMIN, + CreatedAt: now, + } + zapb, err := json.Marshal(zapAdmin) + assert.FatalError(t, err) + type test struct { + db nosql.DB + err error + adminErr *admin.Error + verify func(*testing.T, []*linkedca.Admin) + } + var tests = map[string]func(t *testing.T) test{ + "fail/db.List-error": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MList: func(bucket []byte) ([]*database.Entry, error) { + assert.Equals(t, bucket, adminsTable) + + return nil, errors.New("force") + }, + }, + err: errors.New("error loading admins: force"), + } + }, + "fail/unmarshal-error": func(t *testing.T) test { + ret := []*database.Entry{ + {Bucket: adminsTable, Key: []byte("foo"), Value: foob}, + {Bucket: adminsTable, Key: []byte("bar"), Value: barb}, + {Bucket: adminsTable, Key: []byte("zap"), Value: []byte("zap")}, + } + return test{ + db: &db.MockNoSQLDB{ + MList: func(bucket []byte) ([]*database.Entry, error) { + assert.Equals(t, bucket, adminsTable) + + return ret, nil + }, + }, + err: errors.New("error unmarshaling admin zap into dbAdmin"), + } + }, + "ok/none": func(t *testing.T) test { + ret := []*database.Entry{} + return test{ + db: &db.MockNoSQLDB{ + MList: func(bucket []byte) ([]*database.Entry, error) { + assert.Equals(t, bucket, adminsTable) + + return ret, nil + }, + }, + verify: func(t *testing.T, admins []*linkedca.Admin) { + assert.Equals(t, len(admins), 0) + }, + } + }, + "ok/only-invalid": func(t *testing.T) test { + ret := []*database.Entry{ + {Bucket: adminsTable, Key: []byte("bar"), Value: barb}, + {Bucket: adminsTable, Key: []byte("baz"), Value: bazb}, + } + return test{ + db: &db.MockNoSQLDB{ + MList: func(bucket []byte) ([]*database.Entry, error) { + assert.Equals(t, bucket, adminsTable) + + return ret, nil + }, + }, + verify: func(t *testing.T, admins []*linkedca.Admin) { + assert.Equals(t, len(admins), 0) + }, + } + }, + "ok": func(t *testing.T) test { + ret := []*database.Entry{ + {Bucket: adminsTable, Key: []byte("foo"), Value: foob}, + {Bucket: adminsTable, Key: []byte("bar"), Value: barb}, + {Bucket: adminsTable, Key: []byte("baz"), Value: bazb}, + {Bucket: adminsTable, Key: []byte("zap"), Value: zapb}, + } + return test{ + db: &db.MockNoSQLDB{ + MList: func(bucket []byte) ([]*database.Entry, error) { + assert.Equals(t, bucket, adminsTable) + + return ret, nil + }, + }, + verify: func(t *testing.T, admins []*linkedca.Admin) { + assert.Equals(t, len(admins), 2) + + assert.Equals(t, admins[0].Id, fooAdmin.ID) + assert.Equals(t, admins[0].AuthorityId, fooAdmin.AuthorityID) + assert.Equals(t, admins[0].ProvisionerId, fooAdmin.ProvisionerID) + assert.Equals(t, admins[0].Subject, fooAdmin.Subject) + assert.Equals(t, admins[0].Type, fooAdmin.Type) + assert.Equals(t, admins[0].CreatedAt, timestamppb.New(fooAdmin.CreatedAt)) + assert.Equals(t, admins[0].DeletedAt, timestamppb.New(fooAdmin.DeletedAt)) + + assert.Equals(t, admins[1].Id, zapAdmin.ID) + assert.Equals(t, admins[1].AuthorityId, zapAdmin.AuthorityID) + assert.Equals(t, admins[1].ProvisionerId, zapAdmin.ProvisionerID) + assert.Equals(t, admins[1].Subject, zapAdmin.Subject) + assert.Equals(t, admins[1].Type, zapAdmin.Type) + assert.Equals(t, admins[1].CreatedAt, timestamppb.New(zapAdmin.CreatedAt)) + assert.Equals(t, admins[1].DeletedAt, timestamppb.New(zapAdmin.DeletedAt)) + }, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} + if admins, err := db.GetAdmins(context.Background()); err != nil { + switch k := err.(type) { + case *admin.Error: + if assert.NotNil(t, tc.adminErr) { + assert.Equals(t, k.Type, tc.adminErr.Type) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, k.Status, tc.adminErr.Status) + assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + } + default: + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } + } else { + if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) { + tc.verify(t, admins) + } + } + }) + } +} diff --git a/authority/mgmt/db/nosql/nosql.go b/authority/admin/db/nosql/nosql.go similarity index 81% rename from authority/mgmt/db/nosql/nosql.go rename to authority/admin/db/nosql/nosql.go index 47f79bde..2ce4297c 100644 --- a/authority/mgmt/db/nosql/nosql.go +++ b/authority/admin/db/nosql/nosql.go @@ -11,10 +11,8 @@ import ( ) var ( - authorityAdminsTable = []byte("authority_admins") - authorityConfigsTable = []byte("authority_configs") - authorityProvisionersTable = []byte("authority_provisioners") - authorityProvisionersNameIDIndexTable = []byte("authority_provisioners_name_id_index") + adminsTable = []byte("admins") + provisionersTable = []byte("provisioners") ) // DB is a struct that implements the AcmeDB interface. @@ -25,7 +23,7 @@ type DB struct { // New configures and returns a new Authority DB backend implemented using a nosql DB. func New(db nosqlDB.DB, authorityID string) (*DB, error) { - tables := [][]byte{authorityAdminsTable, authorityConfigsTable, authorityProvisionersTable, authorityProvisionersNameIDIndexTable} + tables := [][]byte{adminsTable, provisionersTable} for _, b := range tables { if err := db.CreateTable(b); err != nil { return nil, errors.Wrapf(err, "error creating table %s", @@ -71,8 +69,6 @@ func (db *DB) save(ctx context.Context, id string, nu interface{}, old interface } } -var idLen = 32 - func randID() (val string, err error) { val, err = randutil.UUIDv4() if err != nil { diff --git a/authority/admin/db/nosql/provisioner.go b/authority/admin/db/nosql/provisioner.go new file mode 100644 index 00000000..71d9c8d6 --- /dev/null +++ b/authority/admin/db/nosql/provisioner.go @@ -0,0 +1,211 @@ +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"` +} + +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), + }, 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 { + switch k := err.(type) { + case *admin.Error: + if k.IsType(admin.ErrorDeletedType) || k.IsType(admin.ErrorAuthorityMismatchType) { + continue + } else { + return nil, err + } + default: + 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(), + } + + 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 + + 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) +} diff --git a/authority/admin/db/nosql/provisioner_test.go b/authority/admin/db/nosql/provisioner_test.go new file mode 100644 index 00000000..95811f26 --- /dev/null +++ b/authority/admin/db/nosql/provisioner_test.go @@ -0,0 +1,1221 @@ +package nosql + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/pkg/errors" + "github.com/smallstep/assert" + "github.com/smallstep/certificates/authority/admin" + "github.com/smallstep/certificates/db" + "github.com/smallstep/nosql" + "github.com/smallstep/nosql/database" + nosqldb "github.com/smallstep/nosql/database" + "go.step.sm/linkedca" +) + +func TestDB_getDBProvisionerBytes(t *testing.T) { + provID := "provID" + type test struct { + db nosql.DB + err error + adminErr *admin.Error + } + var tests = map[string]func(t *testing.T) test{ + "fail/not-found": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return nil, nosqldb.ErrNotFound + }, + }, + adminErr: admin.NewError(admin.ErrorNotFoundType, "provisioner provID not found"), + } + }, + "fail/db.Get-error": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return nil, errors.New("force") + }, + }, + err: errors.New("error loading provisioner provID: force"), + } + }, + "ok": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return []byte("foo"), nil + }, + }, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db} + if b, err := db.getDBProvisionerBytes(context.Background(), provID); err != nil { + switch k := err.(type) { + case *admin.Error: + if assert.NotNil(t, tc.adminErr) { + assert.Equals(t, k.Type, tc.adminErr.Type) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, k.Status, tc.adminErr.Status) + assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + } + default: + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } + } else { + if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) { + assert.Equals(t, string(b), "foo") + } + } + }) + } +} + +func TestDB_getDBProvisioner(t *testing.T) { + provID := "provID" + type test struct { + db nosql.DB + err error + adminErr *admin.Error + dbp *dbProvisioner + } + var tests = map[string]func(t *testing.T) test{ + "fail/not-found": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return nil, nosqldb.ErrNotFound + }, + }, + adminErr: admin.NewError(admin.ErrorNotFoundType, "provisioner provID not found"), + } + }, + "fail/db.Get-error": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return nil, errors.New("force") + }, + }, + err: errors.New("error loading provisioner provID: force"), + } + }, + "fail/unmarshal-error": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return []byte("foo"), nil + }, + }, + err: errors.New("error unmarshaling provisioner provID into dbProvisioner"), + } + }, + "fail/deleted": func(t *testing.T) test { + now := clock.Now() + dbp := &dbProvisioner{ + ID: provID, + AuthorityID: admin.DefaultAuthorityID, + Type: linkedca.Provisioner_JWK, + Name: "provName", + CreatedAt: now, + DeletedAt: now, + } + b, err := json.Marshal(dbp) + assert.FatalError(t, err) + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return b, nil + }, + }, + adminErr: admin.NewError(admin.ErrorDeletedType, "provisioner provID is deleted"), + } + }, + "ok": func(t *testing.T) test { + now := clock.Now() + dbp := &dbProvisioner{ + ID: provID, + AuthorityID: admin.DefaultAuthorityID, + Type: linkedca.Provisioner_JWK, + Name: "provName", + CreatedAt: now, + } + b, err := json.Marshal(dbp) + assert.FatalError(t, err) + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return b, nil + }, + }, + dbp: dbp, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} + if dbp, err := db.getDBProvisioner(context.Background(), provID); err != nil { + switch k := err.(type) { + case *admin.Error: + if assert.NotNil(t, tc.adminErr) { + assert.Equals(t, k.Type, tc.adminErr.Type) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, k.Status, tc.adminErr.Status) + assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + } + default: + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } + } else { + if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) { + assert.Equals(t, dbp.ID, provID) + assert.Equals(t, dbp.AuthorityID, tc.dbp.AuthorityID) + assert.Equals(t, dbp.Type, tc.dbp.Type) + assert.Equals(t, dbp.Name, tc.dbp.Name) + assert.Equals(t, dbp.CreatedAt, tc.dbp.CreatedAt) + assert.Fatal(t, dbp.DeletedAt.IsZero()) + } + } + }) + } +} + +func TestDB_unmarshalDBProvisioner(t *testing.T) { + provID := "provID" + type test struct { + in []byte + err error + adminErr *admin.Error + dbp *dbProvisioner + } + var tests = map[string]func(t *testing.T) test{ + "fail/unmarshal-error": func(t *testing.T) test { + return test{ + in: []byte("foo"), + err: errors.New("error unmarshaling provisioner provID into dbProvisioner"), + } + }, + "fail/deleted-error": func(t *testing.T) test { + dbp := &dbProvisioner{ + DeletedAt: clock.Now(), + } + data, err := json.Marshal(dbp) + assert.FatalError(t, err) + return test{ + in: data, + adminErr: admin.NewError(admin.ErrorDeletedType, "provisioner %s is deleted", provID), + } + }, + "fail/authority-mismatch-error": func(t *testing.T) test { + dbp := &dbProvisioner{ + ID: provID, + AuthorityID: "foo", + } + data, err := json.Marshal(dbp) + assert.FatalError(t, err) + return test{ + in: data, + adminErr: admin.NewError(admin.ErrorAuthorityMismatchType, + "provisioner %s is not owned by authority %s", provID, admin.DefaultAuthorityID), + } + }, + "ok": func(t *testing.T) test { + dbp := &dbProvisioner{ + ID: provID, + AuthorityID: admin.DefaultAuthorityID, + Type: linkedca.Provisioner_JWK, + Name: "provName", + CreatedAt: clock.Now(), + } + data, err := json.Marshal(dbp) + assert.FatalError(t, err) + return test{ + in: data, + dbp: dbp, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{authorityID: admin.DefaultAuthorityID} + if dbp, err := db.unmarshalDBProvisioner(tc.in, provID); err != nil { + switch k := err.(type) { + case *admin.Error: + if assert.NotNil(t, tc.adminErr) { + assert.Equals(t, k.Type, tc.adminErr.Type) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, k.Status, tc.adminErr.Status) + assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + } + default: + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } + } else { + if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) { + assert.Equals(t, dbp.ID, provID) + assert.Equals(t, dbp.AuthorityID, tc.dbp.AuthorityID) + assert.Equals(t, dbp.Type, tc.dbp.Type) + assert.Equals(t, dbp.Name, tc.dbp.Name) + assert.Equals(t, dbp.Details, tc.dbp.Details) + assert.Equals(t, dbp.Claims, tc.dbp.Claims) + assert.Equals(t, dbp.X509Template, tc.dbp.X509Template) + assert.Equals(t, dbp.SSHTemplate, tc.dbp.SSHTemplate) + assert.Equals(t, dbp.CreatedAt, tc.dbp.CreatedAt) + assert.Fatal(t, dbp.DeletedAt.IsZero()) + } + } + }) + } +} + +func defaultDBP(t *testing.T) *dbProvisioner { + details := &linkedca.ProvisionerDetails_ACME{ + ACME: &linkedca.ACMEProvisioner{ + ForceCn: true, + }, + } + detailBytes, err := json.Marshal(details) + assert.FatalError(t, err) + + return &dbProvisioner{ + ID: "provID", + AuthorityID: admin.DefaultAuthorityID, + Type: linkedca.Provisioner_ACME, + Name: "provName", + Details: detailBytes, + Claims: &linkedca.Claims{ + DisableRenewal: true, + X509: &linkedca.X509Claims{ + Enabled: true, + Durations: &linkedca.Durations{ + Min: "5m", + Max: "12h", + Default: "6h", + }, + }, + Ssh: &linkedca.SSHClaims{ + Enabled: true, + UserDurations: &linkedca.Durations{ + Min: "5m", + Max: "12h", + Default: "6h", + }, + HostDurations: &linkedca.Durations{ + Min: "5m", + Max: "12h", + Default: "6h", + }, + }, + }, + X509Template: &linkedca.Template{ + Template: []byte("foo"), + Data: []byte("bar"), + }, + SSHTemplate: &linkedca.Template{ + Template: []byte("baz"), + Data: []byte("zap"), + }, + CreatedAt: clock.Now(), + } +} + +func TestDB_unmarshalProvisioner(t *testing.T) { + provID := "provID" + type test struct { + in []byte + err error + adminErr *admin.Error + dbp *dbProvisioner + } + var tests = map[string]func(t *testing.T) test{ + "fail/unmarshal-error": func(t *testing.T) test { + return test{ + in: []byte("foo"), + err: errors.New("error unmarshaling provisioner provID into dbProvisioner"), + } + }, + "fail/deleted-error": func(t *testing.T) test { + dbp := &dbProvisioner{ + DeletedAt: time.Now(), + } + data, err := json.Marshal(dbp) + assert.FatalError(t, err) + return test{ + in: data, + adminErr: admin.NewError(admin.ErrorDeletedType, "provisioner provID is deleted"), + } + }, + "ok": func(t *testing.T) test { + dbp := defaultDBP(t) + data, err := json.Marshal(dbp) + assert.FatalError(t, err) + return test{ + in: data, + dbp: dbp, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{authorityID: admin.DefaultAuthorityID} + if prov, err := db.unmarshalProvisioner(tc.in, provID); err != nil { + switch k := err.(type) { + case *admin.Error: + if assert.NotNil(t, tc.adminErr) { + assert.Equals(t, k.Type, tc.adminErr.Type) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, k.Status, tc.adminErr.Status) + assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + } + default: + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } + } else { + if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) { + assert.Equals(t, prov.Id, provID) + assert.Equals(t, prov.AuthorityId, tc.dbp.AuthorityID) + assert.Equals(t, prov.Type, tc.dbp.Type) + assert.Equals(t, prov.Name, tc.dbp.Name) + assert.Equals(t, prov.Claims, tc.dbp.Claims) + assert.Equals(t, prov.X509Template, tc.dbp.X509Template) + assert.Equals(t, prov.SshTemplate, tc.dbp.SSHTemplate) + + retDetailsBytes, err := json.Marshal(prov.Details.GetData()) + assert.FatalError(t, err) + assert.Equals(t, retDetailsBytes, tc.dbp.Details) + } + } + }) + } +} + +func TestDB_GetProvisioner(t *testing.T) { + provID := "provID" + type test struct { + db nosql.DB + err error + adminErr *admin.Error + dbp *dbProvisioner + } + var tests = map[string]func(t *testing.T) test{ + "fail/not-found": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return nil, nosqldb.ErrNotFound + }, + }, + adminErr: admin.NewError(admin.ErrorNotFoundType, "provisioner provID not found"), + } + }, + "fail/db.Get-error": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return nil, errors.New("force") + }, + }, + err: errors.New("error loading provisioner provID: force"), + } + }, + "fail/unmarshal-error": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return []byte("foo"), nil + }, + }, + err: errors.New("error unmarshaling provisioner provID into dbProvisioner"), + } + }, + "fail/deleted": func(t *testing.T) test { + dbp := defaultDBP(t) + dbp.DeletedAt = clock.Now() + b, err := json.Marshal(dbp) + assert.FatalError(t, err) + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return b, nil + }, + }, + dbp: dbp, + adminErr: admin.NewError(admin.ErrorDeletedType, "provisioner provID is deleted"), + } + }, + "fail/authorityID-mismatch": func(t *testing.T) test { + dbp := defaultDBP(t) + dbp.AuthorityID = "foo" + b, err := json.Marshal(dbp) + assert.FatalError(t, err) + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return b, nil + }, + }, + dbp: dbp, + adminErr: admin.NewError(admin.ErrorAuthorityMismatchType, + "provisioner %s is not owned by authority %s", dbp.ID, admin.DefaultAuthorityID), + } + }, + "ok": func(t *testing.T) test { + dbp := defaultDBP(t) + b, err := json.Marshal(dbp) + assert.FatalError(t, err) + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return b, nil + }, + }, + dbp: dbp, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} + if prov, err := db.GetProvisioner(context.Background(), provID); err != nil { + switch k := err.(type) { + case *admin.Error: + if assert.NotNil(t, tc.adminErr) { + assert.Equals(t, k.Type, tc.adminErr.Type) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, k.Status, tc.adminErr.Status) + assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + } + default: + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } + } else { + if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) { + assert.Equals(t, prov.Id, provID) + assert.Equals(t, prov.AuthorityId, tc.dbp.AuthorityID) + assert.Equals(t, prov.Type, tc.dbp.Type) + assert.Equals(t, prov.Name, tc.dbp.Name) + assert.Equals(t, prov.Claims, tc.dbp.Claims) + assert.Equals(t, prov.X509Template, tc.dbp.X509Template) + assert.Equals(t, prov.SshTemplate, tc.dbp.SSHTemplate) + + retDetailsBytes, err := json.Marshal(prov.Details.GetData()) + assert.FatalError(t, err) + assert.Equals(t, retDetailsBytes, tc.dbp.Details) + } + } + }) + } +} + +func TestDB_DeleteProvisioner(t *testing.T) { + provID := "provID" + type test struct { + db nosql.DB + err error + adminErr *admin.Error + } + var tests = map[string]func(t *testing.T) test{ + "fail/not-found": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return nil, nosqldb.ErrNotFound + }, + }, + adminErr: admin.NewError(admin.ErrorNotFoundType, "provisioner provID not found"), + } + }, + "fail/db.Get-error": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return nil, errors.New("force") + }, + }, + err: errors.New("error loading provisioner provID: force"), + } + }, + "fail/save-error": func(t *testing.T) test { + dbp := defaultDBP(t) + data, err := json.Marshal(dbp) + assert.FatalError(t, err) + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return data, nil + }, + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + assert.Equals(t, string(old), string(data)) + + var _dbp = new(dbProvisioner) + assert.FatalError(t, json.Unmarshal(nu, _dbp)) + + assert.Equals(t, _dbp.ID, provID) + assert.Equals(t, _dbp.AuthorityID, dbp.AuthorityID) + assert.Equals(t, _dbp.Type, dbp.Type) + assert.Equals(t, _dbp.Name, dbp.Name) + assert.Equals(t, _dbp.Claims, dbp.Claims) + assert.Equals(t, _dbp.X509Template, dbp.X509Template) + assert.Equals(t, _dbp.SSHTemplate, dbp.SSHTemplate) + assert.Equals(t, _dbp.CreatedAt, dbp.CreatedAt) + assert.Equals(t, _dbp.Details, dbp.Details) + + assert.True(t, _dbp.DeletedAt.Before(time.Now())) + assert.True(t, _dbp.DeletedAt.After(time.Now().Add(-time.Minute))) + + return nil, false, errors.New("force") + }, + }, + err: errors.New("error saving authority provisioner: force"), + } + }, + "ok": func(t *testing.T) test { + dbp := defaultDBP(t) + data, err := json.Marshal(dbp) + assert.FatalError(t, err) + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return data, nil + }, + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + assert.Equals(t, string(old), string(data)) + + var _dbp = new(dbProvisioner) + assert.FatalError(t, json.Unmarshal(nu, _dbp)) + + assert.Equals(t, _dbp.ID, provID) + assert.Equals(t, _dbp.AuthorityID, dbp.AuthorityID) + assert.Equals(t, _dbp.Type, dbp.Type) + assert.Equals(t, _dbp.Name, dbp.Name) + assert.Equals(t, _dbp.Claims, dbp.Claims) + assert.Equals(t, _dbp.X509Template, dbp.X509Template) + assert.Equals(t, _dbp.SSHTemplate, dbp.SSHTemplate) + assert.Equals(t, _dbp.CreatedAt, dbp.CreatedAt) + assert.Equals(t, _dbp.Details, dbp.Details) + + assert.True(t, _dbp.DeletedAt.Before(time.Now())) + assert.True(t, _dbp.DeletedAt.After(time.Now().Add(-time.Minute))) + + return nu, true, nil + }, + }, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} + if err := db.DeleteProvisioner(context.Background(), provID); err != nil { + switch k := err.(type) { + case *admin.Error: + if assert.NotNil(t, tc.adminErr) { + assert.Equals(t, k.Type, tc.adminErr.Type) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, k.Status, tc.adminErr.Status) + assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + } + default: + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } + } + }) + } +} + +func TestDB_GetProvisioners(t *testing.T) { + fooProv := defaultDBP(t) + fooProv.Name = "foo" + foob, err := json.Marshal(fooProv) + assert.FatalError(t, err) + + barProv := defaultDBP(t) + barProv.Name = "bar" + barProv.DeletedAt = clock.Now() + barb, err := json.Marshal(barProv) + assert.FatalError(t, err) + + bazProv := defaultDBP(t) + bazProv.Name = "baz" + bazProv.AuthorityID = "baz" + bazb, err := json.Marshal(bazProv) + assert.FatalError(t, err) + + zapProv := defaultDBP(t) + zapProv.Name = "zap" + zapb, err := json.Marshal(zapProv) + assert.FatalError(t, err) + + type test struct { + db nosql.DB + err error + adminErr *admin.Error + verify func(*testing.T, []*linkedca.Provisioner) + } + var tests = map[string]func(t *testing.T) test{ + "fail/db.List-error": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MList: func(bucket []byte) ([]*database.Entry, error) { + assert.Equals(t, bucket, provisionersTable) + + return nil, errors.New("force") + }, + }, + err: errors.New("error loading provisioners"), + } + }, + "fail/unmarshal-error": func(t *testing.T) test { + ret := []*database.Entry{ + {Bucket: provisionersTable, Key: []byte("foo"), Value: foob}, + {Bucket: provisionersTable, Key: []byte("bar"), Value: barb}, + {Bucket: provisionersTable, Key: []byte("zap"), Value: []byte("zap")}, + } + return test{ + db: &db.MockNoSQLDB{ + MList: func(bucket []byte) ([]*database.Entry, error) { + assert.Equals(t, bucket, provisionersTable) + + return ret, nil + }, + }, + err: errors.New("error unmarshaling provisioner zap into dbProvisioner"), + } + }, + "ok/none": func(t *testing.T) test { + ret := []*database.Entry{} + return test{ + db: &db.MockNoSQLDB{ + MList: func(bucket []byte) ([]*database.Entry, error) { + assert.Equals(t, bucket, provisionersTable) + + return ret, nil + }, + }, + verify: func(t *testing.T, provs []*linkedca.Provisioner) { + assert.Equals(t, len(provs), 0) + }, + } + }, + "ok/only-invalid": func(t *testing.T) test { + ret := []*database.Entry{ + {Bucket: provisionersTable, Key: []byte("bar"), Value: barb}, + {Bucket: provisionersTable, Key: []byte("baz"), Value: bazb}, + } + return test{ + db: &db.MockNoSQLDB{ + MList: func(bucket []byte) ([]*database.Entry, error) { + assert.Equals(t, bucket, provisionersTable) + + return ret, nil + }, + }, + verify: func(t *testing.T, provs []*linkedca.Provisioner) { + assert.Equals(t, len(provs), 0) + }, + } + }, + "ok": func(t *testing.T) test { + ret := []*database.Entry{ + {Bucket: provisionersTable, Key: []byte("foo"), Value: foob}, + {Bucket: provisionersTable, Key: []byte("bar"), Value: barb}, + {Bucket: provisionersTable, Key: []byte("baz"), Value: bazb}, + {Bucket: provisionersTable, Key: []byte("zap"), Value: zapb}, + } + return test{ + db: &db.MockNoSQLDB{ + MList: func(bucket []byte) ([]*database.Entry, error) { + assert.Equals(t, bucket, provisionersTable) + + return ret, nil + }, + }, + verify: func(t *testing.T, provs []*linkedca.Provisioner) { + assert.Equals(t, len(provs), 2) + + assert.Equals(t, provs[0].Id, fooProv.ID) + assert.Equals(t, provs[0].AuthorityId, fooProv.AuthorityID) + assert.Equals(t, provs[0].Type, fooProv.Type) + assert.Equals(t, provs[0].Name, fooProv.Name) + assert.Equals(t, provs[0].Claims, fooProv.Claims) + assert.Equals(t, provs[0].X509Template, fooProv.X509Template) + assert.Equals(t, provs[0].SshTemplate, fooProv.SSHTemplate) + + retDetailsBytes, err := json.Marshal(provs[0].Details.GetData()) + assert.FatalError(t, err) + assert.Equals(t, retDetailsBytes, fooProv.Details) + + assert.Equals(t, provs[1].Id, zapProv.ID) + assert.Equals(t, provs[1].AuthorityId, zapProv.AuthorityID) + assert.Equals(t, provs[1].Type, zapProv.Type) + assert.Equals(t, provs[1].Name, zapProv.Name) + assert.Equals(t, provs[1].Claims, zapProv.Claims) + assert.Equals(t, provs[1].X509Template, zapProv.X509Template) + assert.Equals(t, provs[1].SshTemplate, zapProv.SSHTemplate) + + retDetailsBytes, err = json.Marshal(provs[1].Details.GetData()) + assert.FatalError(t, err) + assert.Equals(t, retDetailsBytes, zapProv.Details) + }, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} + if provs, err := db.GetProvisioners(context.Background()); err != nil { + switch k := err.(type) { + case *admin.Error: + if assert.NotNil(t, tc.adminErr) { + assert.Equals(t, k.Type, tc.adminErr.Type) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, k.Status, tc.adminErr.Status) + assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + } + default: + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } + } else { + if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) { + tc.verify(t, provs) + } + } + }) + } +} + +func TestDB_CreateProvisioner(t *testing.T) { + type test struct { + db nosql.DB + err error + adminErr *admin.Error + prov *linkedca.Provisioner + } + var tests = map[string]func(t *testing.T) test{ + "fail/save-error": func(t *testing.T) test { + dbp := defaultDBP(t) + prov, err := dbp.convert2linkedca() + assert.FatalError(t, err) + + return test{ + prov: prov, + db: &db.MockNoSQLDB{ + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, old, nil) + + var _dbp = new(dbProvisioner) + assert.FatalError(t, json.Unmarshal(nu, _dbp)) + + assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key)) + assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId) + assert.Equals(t, _dbp.Type, prov.Type) + assert.Equals(t, _dbp.Name, prov.Name) + assert.Equals(t, _dbp.Claims, prov.Claims) + assert.Equals(t, _dbp.X509Template, prov.X509Template) + assert.Equals(t, _dbp.SSHTemplate, prov.SshTemplate) + + retDetailsBytes, err := json.Marshal(prov.Details.GetData()) + assert.FatalError(t, err) + assert.Equals(t, retDetailsBytes, _dbp.Details) + + assert.True(t, _dbp.DeletedAt.IsZero()) + assert.True(t, _dbp.CreatedAt.Before(time.Now())) + assert.True(t, _dbp.CreatedAt.After(time.Now().Add(-time.Minute))) + + return nil, false, errors.New("force") + }, + }, + adminErr: admin.NewErrorISE("error creating provisioner provName: error saving authority provisioner: force"), + } + }, + "ok": func(t *testing.T) test { + dbp := defaultDBP(t) + prov, err := dbp.convert2linkedca() + assert.FatalError(t, err) + + return test{ + prov: prov, + db: &db.MockNoSQLDB{ + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, old, nil) + + var _dbp = new(dbProvisioner) + assert.FatalError(t, json.Unmarshal(nu, _dbp)) + + assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key)) + assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId) + assert.Equals(t, _dbp.Type, prov.Type) + assert.Equals(t, _dbp.Name, prov.Name) + assert.Equals(t, _dbp.Claims, prov.Claims) + assert.Equals(t, _dbp.X509Template, prov.X509Template) + assert.Equals(t, _dbp.SSHTemplate, prov.SshTemplate) + + retDetailsBytes, err := json.Marshal(prov.Details.GetData()) + assert.FatalError(t, err) + assert.Equals(t, retDetailsBytes, _dbp.Details) + + assert.True(t, _dbp.DeletedAt.IsZero()) + assert.True(t, _dbp.CreatedAt.Before(time.Now())) + assert.True(t, _dbp.CreatedAt.After(time.Now().Add(-time.Minute))) + + return nu, true, nil + }, + }, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} + if err := db.CreateProvisioner(context.Background(), tc.prov); err != nil { + switch k := err.(type) { + case *admin.Error: + if assert.NotNil(t, tc.adminErr) { + assert.Equals(t, k.Type, tc.adminErr.Type) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, k.Status, tc.adminErr.Status) + assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + } + default: + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } + } + }) + } +} + +func TestDB_UpdateProvisioner(t *testing.T) { + provID := "provID" + type test struct { + db nosql.DB + err error + adminErr *admin.Error + prov *linkedca.Provisioner + } + var tests = map[string]func(t *testing.T) test{ + "fail/not-found": func(t *testing.T) test { + return test{ + prov: &linkedca.Provisioner{Id: provID}, + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return nil, nosqldb.ErrNotFound + }, + }, + adminErr: admin.NewError(admin.ErrorNotFoundType, "provisioner provID not found"), + } + }, + "fail/db.Get-error": func(t *testing.T) test { + return test{ + prov: &linkedca.Provisioner{Id: provID}, + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return nil, errors.New("force") + }, + }, + err: errors.New("error loading provisioner provID: force"), + } + }, + "fail/update-deleted": func(t *testing.T) test { + dbp := defaultDBP(t) + dbp.DeletedAt = clock.Now() + data, err := json.Marshal(dbp) + assert.FatalError(t, err) + return test{ + prov: &linkedca.Provisioner{Id: provID}, + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return data, nil + }, + }, + adminErr: admin.NewError(admin.ErrorDeletedType, "provisioner %s is deleted", provID), + } + }, + "fail/update-type-error": func(t *testing.T) test { + dbp := defaultDBP(t) + + upd, err := dbp.convert2linkedca() + assert.FatalError(t, err) + upd.Type = linkedca.Provisioner_JWK + + data, err := json.Marshal(dbp) + assert.FatalError(t, err) + return test{ + prov: upd, + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return data, nil + }, + }, + adminErr: admin.NewError(admin.ErrorBadRequestType, "cannot update provisioner type"), + } + }, + "fail/save-error": func(t *testing.T) test { + dbp := defaultDBP(t) + + prov, err := dbp.convert2linkedca() + assert.FatalError(t, err) + + data, err := json.Marshal(dbp) + assert.FatalError(t, err) + return test{ + prov: prov, + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return data, nil + }, + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + assert.Equals(t, string(old), string(data)) + + var _dbp = new(dbProvisioner) + assert.FatalError(t, json.Unmarshal(nu, _dbp)) + + assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key)) + assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId) + assert.Equals(t, _dbp.Type, prov.Type) + assert.Equals(t, _dbp.Name, prov.Name) + assert.Equals(t, _dbp.Claims, prov.Claims) + assert.Equals(t, _dbp.X509Template, prov.X509Template) + assert.Equals(t, _dbp.SSHTemplate, prov.SshTemplate) + + retDetailsBytes, err := json.Marshal(prov.Details.GetData()) + assert.FatalError(t, err) + assert.Equals(t, retDetailsBytes, _dbp.Details) + + assert.True(t, _dbp.DeletedAt.IsZero()) + assert.True(t, _dbp.CreatedAt.Before(time.Now())) + assert.True(t, _dbp.CreatedAt.After(time.Now().Add(-time.Minute))) + + return nil, false, errors.New("force") + }, + }, + err: errors.New("error saving authority provisioner: force"), + } + }, + "ok": func(t *testing.T) test { + dbp := defaultDBP(t) + + prov, err := dbp.convert2linkedca() + assert.FatalError(t, err) + + prov.Name = "new-name" + prov.Claims = &linkedca.Claims{ + DisableRenewal: true, + X509: &linkedca.X509Claims{ + Enabled: true, + Durations: &linkedca.Durations{ + Min: "10m", + Max: "8h", + Default: "4h", + }, + }, + Ssh: &linkedca.SSHClaims{ + Enabled: true, + UserDurations: &linkedca.Durations{ + Min: "7m", + Max: "11h", + Default: "5h", + }, + HostDurations: &linkedca.Durations{ + Min: "4m", + Max: "24h", + Default: "24h", + }, + }, + } + prov.X509Template = &linkedca.Template{ + Template: []byte("x"), + Data: []byte("y"), + } + prov.SshTemplate = &linkedca.Template{ + Template: []byte("z"), + Data: []byte("w"), + } + prov.Details = &linkedca.ProvisionerDetails{ + Data: &linkedca.ProvisionerDetails_ACME{ + ACME: &linkedca.ACMEProvisioner{ + ForceCn: false, + }, + }, + } + + data, err := json.Marshal(dbp) + assert.FatalError(t, err) + return test{ + prov: prov, + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + + return data, nil + }, + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, bucket, provisionersTable) + assert.Equals(t, string(key), provID) + assert.Equals(t, string(old), string(data)) + + var _dbp = new(dbProvisioner) + assert.FatalError(t, json.Unmarshal(nu, _dbp)) + + assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key)) + assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId) + assert.Equals(t, _dbp.Type, prov.Type) + assert.Equals(t, _dbp.Name, prov.Name) + assert.Equals(t, _dbp.Claims, prov.Claims) + assert.Equals(t, _dbp.X509Template, prov.X509Template) + assert.Equals(t, _dbp.SSHTemplate, prov.SshTemplate) + + retDetailsBytes, err := json.Marshal(prov.Details.GetData()) + assert.FatalError(t, err) + assert.Equals(t, retDetailsBytes, _dbp.Details) + + assert.True(t, _dbp.DeletedAt.IsZero()) + assert.True(t, _dbp.CreatedAt.Before(time.Now())) + assert.True(t, _dbp.CreatedAt.After(time.Now().Add(-time.Minute))) + + return nu, true, nil + }, + }, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} + if err := db.UpdateProvisioner(context.Background(), tc.prov); err != nil { + switch k := err.(type) { + case *admin.Error: + if assert.NotNil(t, tc.adminErr) { + assert.Equals(t, k.Type, tc.adminErr.Type) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, k.Status, tc.adminErr.Status) + assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, k.Detail, tc.adminErr.Detail) + } + default: + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } + } + }) + } +} diff --git a/authority/mgmt/errors.go b/authority/admin/errors.go similarity index 88% rename from authority/mgmt/errors.go rename to authority/admin/errors.go index 63c0652e..81ae6d6f 100644 --- a/authority/mgmt/errors.go +++ b/authority/admin/errors.go @@ -1,4 +1,4 @@ -package mgmt +package admin import ( "encoding/json" @@ -25,6 +25,10 @@ const ( ErrorDeletedType // ErrorBadRequestType bad request. ErrorBadRequestType + // ErrorNotImplementedType not implemented. + ErrorNotImplementedType + // ErrorUnauthorizedType internal server error. + ErrorUnauthorizedType // ErrorServerInternalType internal server error. ErrorServerInternalType ) @@ -41,6 +45,10 @@ func (ap ProblemType) String() string { return "deleted" case ErrorBadRequestType: return "badRequest" + case ErrorNotImplementedType: + return "notImplemented" + case ErrorUnauthorizedType: + return "unauthorized" case ErrorServerInternalType: return "internalServerError" default: @@ -75,12 +83,22 @@ var ( ErrorDeletedType: { typ: ErrorDeletedType.String(), details: "resource is deleted", - status: 403, + status: http.StatusUnauthorized, + }, + ErrorNotImplementedType: { + typ: ErrorNotImplementedType.String(), + details: "not implemented", + status: http.StatusNotImplemented, }, ErrorBadRequestType: { typ: ErrorBadRequestType.String(), details: "bad request", - status: 400, + status: http.StatusBadRequest, + }, + ErrorUnauthorizedType: { + typ: ErrorUnauthorizedType.String(), + details: "unauthorized", + status: http.StatusUnauthorized, }, ErrorServerInternalType: errorServerInternalMetadata, } @@ -199,7 +217,6 @@ func WriteError(w http.ResponseWriter, err *Error) { } } - fmt.Printf("err = %+v\n", err) if err := json.NewEncoder(w).Encode(err); err != nil { log.Println(err) } diff --git a/authority/administrator/collection.go b/authority/administrator/collection.go new file mode 100644 index 00000000..47d3a35d --- /dev/null +++ b/authority/administrator/collection.go @@ -0,0 +1,243 @@ +package administrator + +import ( + "sort" + "sync" + + "github.com/pkg/errors" + "github.com/smallstep/certificates/authority/admin" + "github.com/smallstep/certificates/authority/provisioner" + "go.step.sm/linkedca" +) + +// DefaultAdminLimit is the default limit for listing provisioners. +const DefaultAdminLimit = 20 + +// DefaultAdminMax is the maximum limit for listing provisioners. +const DefaultAdminMax = 100 + +type adminSlice []*linkedca.Admin + +func (p adminSlice) Len() int { return len(p) } +func (p adminSlice) Less(i, j int) bool { return p[i].Id < p[j].Id } +func (p adminSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +// Collection is a memory map of admins. +type Collection struct { + byID *sync.Map + bySubProv *sync.Map + byProv *sync.Map + sorted adminSlice + provisioners *provisioner.Collection + superCount int + superCountByProvisioner map[string]int +} + +// NewCollection initializes a collection of provisioners. The given list of +// audiences are the audiences used by the JWT provisioner. +func NewCollection(provisioners *provisioner.Collection) *Collection { + return &Collection{ + byID: new(sync.Map), + byProv: new(sync.Map), + bySubProv: new(sync.Map), + superCountByProvisioner: map[string]int{}, + provisioners: provisioners, + } +} + +// LoadByID a admin by the ID. +func (c *Collection) LoadByID(id string) (*linkedca.Admin, bool) { + return loadAdmin(c.byID, id) +} + +type subProv struct { + subject string + provisioner string +} + +func newSubProv(subject, provisioner string) subProv { + return subProv{subject, provisioner} +} + +// LoadBySubProv a admin by the subject and provisioner name. +func (c *Collection) LoadBySubProv(sub, provName string) (*linkedca.Admin, bool) { + return loadAdmin(c.bySubProv, newSubProv(sub, provName)) +} + +// LoadByProvisioner a admin by the subject and provisioner name. +func (c *Collection) LoadByProvisioner(provName string) ([]*linkedca.Admin, bool) { + val, ok := c.byProv.Load(provName) + if !ok { + return nil, false + } + admins, ok := val.([]*linkedca.Admin) + if !ok { + return nil, false + } + return admins, true +} + +// Store adds an admin to the collection and enforces the uniqueness of +// admin IDs and amdin subject <-> provisioner name combos. +func (c *Collection) Store(adm *linkedca.Admin, prov provisioner.Interface) error { + // Input validation. + if adm.ProvisionerId != prov.GetID() { + return admin.NewErrorISE("admin.provisionerId does not match provisioner argument") + } + + // Store admin always in byID. ID must be unique. + if _, loaded := c.byID.LoadOrStore(adm.Id, adm); loaded { + return errors.New("cannot add multiple admins with the same id") + } + + provName := prov.GetName() + // Store admin always in bySubProv. Subject <-> ProvisionerName must be unique. + if _, loaded := c.bySubProv.LoadOrStore(newSubProv(adm.Subject, provName), adm); loaded { + c.byID.Delete(adm.Id) + return errors.New("cannot add multiple admins with the same subject and provisioner") + } + + var isSuper = (adm.Type == linkedca.Admin_SUPER_ADMIN) + if admins, ok := c.LoadByProvisioner(provName); ok { + c.byProv.Store(provName, append(admins, adm)) + if isSuper { + c.superCountByProvisioner[provName]++ + } + } else { + c.byProv.Store(provName, []*linkedca.Admin{adm}) + if isSuper { + c.superCountByProvisioner[provName] = 1 + } + } + if isSuper { + c.superCount++ + } + + c.sorted = append(c.sorted, adm) + sort.Sort(c.sorted) + + return nil +} + +// Remove deletes an admin from all associated collections and lists. +func (c *Collection) Remove(id string) error { + adm, ok := c.LoadByID(id) + if !ok { + return admin.NewError(admin.ErrorNotFoundType, "admin %s not found", id) + } + if adm.Type == linkedca.Admin_SUPER_ADMIN && c.SuperCount() == 1 { + return admin.NewError(admin.ErrorBadRequestType, "cannot remove the last super admin") + } + prov, ok := c.provisioners.Load(adm.ProvisionerId) + if !ok { + return admin.NewError(admin.ErrorNotFoundType, + "provisioner %s for admin %s not found", adm.ProvisionerId, id) + } + provName := prov.GetName() + adminsByProv, ok := c.LoadByProvisioner(provName) + if !ok { + return admin.NewError(admin.ErrorNotFoundType, + "admins not found for provisioner %s", provName) + } + + // Find index in sorted list. + sortedIndex := sort.Search(c.sorted.Len(), func(i int) bool { return c.sorted[i].Id >= adm.Id }) + if c.sorted[sortedIndex].Id != adm.Id { + return admin.NewError(admin.ErrorNotFoundType, + "admin %s not found in sorted list", adm.Id) + } + + var found bool + for i, a := range adminsByProv { + if a.Id == adm.Id { + // Remove admin from list. https://stackoverflow.com/questions/37334119/how-to-delete-an-element-from-a-slice-in-golang + // Order does not matter. + adminsByProv[i] = adminsByProv[len(adminsByProv)-1] + c.byProv.Store(provName, adminsByProv[:len(adminsByProv)-1]) + found = true + } + } + if !found { + return admin.NewError(admin.ErrorNotFoundType, + "admin %s not found in adminsByProvisioner list", adm.Id) + } + + // Remove index in sorted list + copy(c.sorted[sortedIndex:], c.sorted[sortedIndex+1:]) // Shift a[i+1:] left one index. + c.sorted[len(c.sorted)-1] = nil // Erase last element (write zero value). + c.sorted = c.sorted[:len(c.sorted)-1] // Truncate slice. + + c.byID.Delete(adm.Id) + c.bySubProv.Delete(newSubProv(adm.Subject, provName)) + + if adm.Type == linkedca.Admin_SUPER_ADMIN { + c.superCount-- + c.superCountByProvisioner[provName]-- + } + return nil +} + +// Update updates the given admin in all related lists and collections. +func (c *Collection) Update(id string, nu *linkedca.Admin) (*linkedca.Admin, error) { + adm, ok := c.LoadByID(id) + if !ok { + return nil, admin.NewError(admin.ErrorNotFoundType, "admin %s not found", adm.Id) + } + if adm.Type == nu.Type { + return nil, admin.NewError(admin.ErrorBadRequestType, "admin %s already has type %s", id, adm.Type) + } + if adm.Type == linkedca.Admin_SUPER_ADMIN && c.SuperCount() == 1 { + return nil, admin.NewError(admin.ErrorBadRequestType, "cannot change role of last super admin") + } + + adm.Type = nu.Type + return adm, nil +} + +// SuperCount returns the total number of admins. +func (c *Collection) SuperCount() int { + return c.superCount +} + +// SuperCountByProvisioner returns the total number of admins. +func (c *Collection) SuperCountByProvisioner(provName string) int { + if cnt, ok := c.superCountByProvisioner[provName]; ok { + return cnt + } + return 0 +} + +// Find implements pagination on a list of sorted admins. +func (c *Collection) Find(cursor string, limit int) ([]*linkedca.Admin, string) { + switch { + case limit <= 0: + limit = DefaultAdminLimit + case limit > DefaultAdminMax: + limit = DefaultAdminMax + } + + n := c.sorted.Len() + i := sort.Search(n, func(i int) bool { return c.sorted[i].Id >= cursor }) + + slice := []*linkedca.Admin{} + for ; i < n && len(slice) < limit; i++ { + slice = append(slice, c.sorted[i]) + } + + if i < n { + return slice, c.sorted[i].Id + } + return slice, "" +} + +func loadAdmin(m *sync.Map, key interface{}) (*linkedca.Admin, bool) { + val, ok := m.Load(key) + if !ok { + return nil, false + } + adm, ok := val.(*linkedca.Admin) + if !ok { + return nil, false + } + return adm, true +} diff --git a/authority/admins.go b/authority/admins.go new file mode 100644 index 00000000..dcaf9b49 --- /dev/null +++ b/authority/admins.go @@ -0,0 +1,97 @@ +package authority + +import ( + "context" + + "github.com/smallstep/certificates/authority/admin" + "github.com/smallstep/certificates/authority/provisioner" + "go.step.sm/linkedca" +) + +// LoadAdminByID returns an *linkedca.Admin with the given ID. +func (a *Authority) LoadAdminByID(id string) (*linkedca.Admin, bool) { + a.adminMutex.RLock() + defer a.adminMutex.RUnlock() + return a.admins.LoadByID(id) +} + +// LoadAdminBySubProv returns an *linkedca.Admin with the given ID. +func (a *Authority) LoadAdminBySubProv(subject, provisioner string) (*linkedca.Admin, bool) { + a.adminMutex.RLock() + defer a.adminMutex.RUnlock() + return a.admins.LoadBySubProv(subject, provisioner) +} + +// GetAdmins returns a map listing each provisioner and the JWK Key Set +// with their public keys. +func (a *Authority) GetAdmins(cursor string, limit int) ([]*linkedca.Admin, string, error) { + a.adminMutex.RLock() + defer a.adminMutex.RUnlock() + admins, nextCursor := a.admins.Find(cursor, limit) + return admins, nextCursor, nil +} + +// StoreAdmin stores an *linkedca.Admin to the authority. +func (a *Authority) StoreAdmin(ctx context.Context, adm *linkedca.Admin, prov provisioner.Interface) error { + a.adminMutex.Lock() + defer a.adminMutex.Unlock() + + if adm.ProvisionerId != prov.GetID() { + return admin.NewErrorISE("admin.provisionerId does not match provisioner argument") + } + + if _, ok := a.admins.LoadBySubProv(adm.Subject, prov.GetName()); ok { + return admin.NewError(admin.ErrorBadRequestType, + "admin with subject %s and provisioner %s already exists", adm.Subject, prov.GetName()) + } + // Store to database -- this will set the ID. + if err := a.adminDB.CreateAdmin(ctx, adm); err != nil { + return admin.WrapErrorISE(err, "error creating admin") + } + if err := a.admins.Store(adm, prov); err != nil { + if err := a.reloadAdminResources(ctx); err != nil { + return admin.WrapErrorISE(err, "error reloading admin resources on failed admin store") + } + return admin.WrapErrorISE(err, "error storing admin in authority cache") + } + return nil +} + +// UpdateAdmin stores an *linkedca.Admin to the authority. +func (a *Authority) UpdateAdmin(ctx context.Context, id string, nu *linkedca.Admin) (*linkedca.Admin, error) { + a.adminMutex.Lock() + defer a.adminMutex.Unlock() + adm, err := a.admins.Update(id, nu) + if err != nil { + return nil, admin.WrapErrorISE(err, "error updating cached admin %s", id) + } + if err := a.adminDB.UpdateAdmin(ctx, adm); err != nil { + if err := a.reloadAdminResources(ctx); err != nil { + return nil, admin.WrapErrorISE(err, "error reloading admin resources on failed admin update") + } + return nil, admin.WrapErrorISE(err, "error updating admin %s", id) + } + return adm, nil +} + +// RemoveAdmin removes an *linkedca.Admin from the authority. +func (a *Authority) RemoveAdmin(ctx context.Context, id string) error { + a.adminMutex.Lock() + defer a.adminMutex.Unlock() + + return a.removeAdmin(ctx, id) +} + +// removeAdmin helper that assumes lock. +func (a *Authority) removeAdmin(ctx context.Context, id string) error { + if err := a.admins.Remove(id); err != nil { + return admin.WrapErrorISE(err, "error removing admin %s from authority cache", id) + } + if err := a.adminDB.DeleteAdmin(ctx, id); err != nil { + if err := a.reloadAdminResources(ctx); err != nil { + return admin.WrapErrorISE(err, "error reloading admin resources on failed admin remove") + } + return admin.WrapErrorISE(err, "error deleting admin %s", id) + } + return nil +} diff --git a/authority/authority.go b/authority/authority.go index b5924061..eec0c3e0 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -11,14 +11,14 @@ import ( "time" "github.com/smallstep/certificates/cas" - "github.com/smallstep/certificates/linkedca" "github.com/smallstep/certificates/scep" + "go.step.sm/linkedca" "github.com/pkg/errors" "github.com/smallstep/certificates/authority/admin" + adminDBNosql "github.com/smallstep/certificates/authority/admin/db/nosql" + "github.com/smallstep/certificates/authority/administrator" "github.com/smallstep/certificates/authority/config" - "github.com/smallstep/certificates/authority/mgmt" - authMgmtNosql "github.com/smallstep/certificates/authority/mgmt/db/nosql" "github.com/smallstep/certificates/authority/provisioner" casapi "github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/db" @@ -34,16 +34,17 @@ import ( // Authority implements the Certificate Authority internal interface. type Authority struct { config *config.Config - adminDB mgmt.DB keyManager kms.KeyManager provisioners *provisioner.Collection - admins *admin.Collection + admins *administrator.Collection db db.AuthDB + adminDB admin.DB templates *templates.Templates // X509 CA x509CAService cas.CertificateAuthorityService rootX509Certs []*x509.Certificate + rootX509CertPool *x509.CertPool federatedX509Certs []*x509.Certificate certificates *sync.Map @@ -67,6 +68,8 @@ type Authority struct { sshCheckHostFunc func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, error) sshGetHostsFunc func(ctx context.Context, cert *x509.Certificate) ([]config.Host, error) getIdentityFunc provisioner.GetIdentityFunc + + adminMutex sync.RWMutex } // New creates and initiates a new Authority type. @@ -134,62 +137,62 @@ func NewEmbedded(opts ...Option) (*Authority, error) { return a, nil } -// ReloadAuthConfig reloads dynamic fields of the AuthConfig. -func (a *Authority) ReloadAuthConfig(ctx context.Context) error { - provs, err := a.adminDB.GetProvisioners(ctx) - 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(ctx) - if err != nil { - return mgmt.WrapErrorISE(err, "error getting provisioners to initialize authority") +// reloadAdminResources reloads admins and provisioners from the DB. +func (a *Authority) reloadAdminResources(ctx context.Context) error { + var ( + provList provisioner.List + adminList []*linkedca.Admin + ) + if a.config.AuthorityConfig.EnableAdmin { + provs, err := a.adminDB.GetProvisioners(ctx) + if err != nil { + return admin.WrapErrorISE(err, "error getting provisioners to initialize authority") + } + provList, err = provisionerListToCertificates(provs) + if err != nil { + return admin.WrapErrorISE(err, "error converting provisioner list to certificates") + } + adminList, err = a.adminDB.GetAdmins(ctx) + if err != nil { + return admin.WrapErrorISE(err, "error getting admins to initialize authority") + } + } else { + provList = a.config.AuthorityConfig.Provisioners + adminList = a.config.AuthorityConfig.Admins } - // Merge global and configuration claims - claimer, err := provisioner.NewClaimer(a.config.AuthorityConfig.Claims, config.GlobalProvisionerClaims) + provisionerConfig, err := a.generateProvisionerConfig(ctx) if err != nil { - return err + return admin.WrapErrorISE(err, "error generating provisioner config") } - // 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(ctx) - if err != nil { - return err - } - // Initialize provisioners - audiences := a.config.GetAudiences() - a.provisioners = provisioner.NewCollection(audiences) - config := provisioner.Config{ - Claims: claimer.Claims(), - Audiences: audiences, - DB: a.db, - SSHKeys: &provisioner.SSHKeys{ - UserKeys: sshKeys.UserKeys, - HostKeys: sshKeys.HostKeys, - }, - GetIdentityFunc: a.getIdentityFunc, - } - // Store all the provisioners - for _, p := range a.config.AuthorityConfig.Provisioners { - if err := p.Init(config); err != nil { + + // Create provisioner collection. + provClxn := provisioner.NewCollection(provisionerConfig.Audiences) + for _, p := range provList { + if err := p.Init(*provisionerConfig); err != nil { return err } - if err := a.provisioners.Store(p); err != nil { + if err := provClxn.Store(p); err != nil { return err } } - // Store all the admins - a.admins = admin.NewCollection(a.provisioners) - for _, adm := range a.config.AuthorityConfig.Admins { - if err := a.admins.Store(adm); err != nil { + // Create admin collection. + adminClxn := administrator.NewCollection(provClxn) + for _, adm := range adminList { + p, ok := provClxn.Load(adm.ProvisionerId) + if !ok { + return admin.NewErrorISE("provisioner %s not found when loading admin %s", + adm.ProvisionerId, adm.Id) + } + if err := adminClxn.Store(adm, p); err != nil { return err } } + + a.config.AuthorityConfig.Provisioners = provList + a.provisioners = provClxn + a.config.AuthorityConfig.Admins = adminList + a.admins = adminClxn return nil } @@ -210,56 +213,6 @@ func (a *Authority) init() error { } } - 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 - } - } - - provs, err := a.adminDB.GetProvisioners(context.Background()) - if err != nil { - return mgmt.WrapErrorISE(err, "error getting provisioners to initialize authority") - } - if len(provs) == 0 { - // Create First Provisioner - prov, err := mgmt.CreateFirstProvisioner(context.Background(), a.adminDB, a.config.Password) - if err != nil { - return mgmt.WrapErrorISE(err, "error creating first provisioner") - } - certProv, err := provisionerToCertificates(prov) - if err != nil { - return mgmt.WrapErrorISE(err, "error converting provisioner to certificates type") - } - a.config.AuthorityConfig.Provisioners = []provisioner.Interface{certProv} - - // 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 { - a.config.AuthorityConfig.Provisioners, err = provisionerListToCertificates(provs) - if err != nil { - return mgmt.WrapErrorISE(err, "error converting provisioner list to certificates type") - } - 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. if a.keyManager == nil { var options kmsapi.Options @@ -329,6 +282,11 @@ func (a *Authority) init() error { a.certificates.Store(hex.EncodeToString(sum[:]), crt) } + a.rootX509CertPool = x509.NewCertPool() + for _, cert := range a.rootX509Certs { + a.rootX509CertPool.AddCert(cert) + } + // Read federated certificates and store them in the certificates map. if len(a.federatedX509Certs) == 0 { a.federatedX509Certs = make([]*x509.Certificate, len(a.config.FederatedRoots)) @@ -430,32 +388,6 @@ func (a *Authority) init() error { tmplVars.SSH.UserFederatedKeys = append(tmplVars.SSH.UserFederatedKeys, a.sshCAUserFederatedCerts[1:]...) } - // Merge global and configuration claims - claimer, err := provisioner.NewClaimer(a.config.AuthorityConfig.Claims, config.GlobalProvisionerClaims) - if err != nil { - return err - } - // 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()) - if err != nil { - return err - } - // Initialize provisioners - audiences := a.config.GetAudiences() - a.provisioners = provisioner.NewCollection(audiences) - config := provisioner.Config{ - Claims: claimer.Claims(), - Audiences: audiences, - DB: a.db, - SSHKeys: &provisioner.SSHKeys{ - UserKeys: sshKeys.UserKeys, - HostKeys: sshKeys.HostKeys, - }, - GetIdentityFunc: a.getIdentityFunc, - } - // Check if a KMS with decryption capability is required and available if a.requiresDecrypter() { if _, ok := a.keyManager.(kmsapi.Decrypter); !ok { @@ -499,21 +431,42 @@ func (a *Authority) init() error { // TODO: mimick the x509CAService GetCertificateAuthority here too? } - // Store all the provisioners - for _, p := range a.config.AuthorityConfig.Provisioners { - if err := p.Init(config); err != nil { - return err + if a.config.AuthorityConfig.EnableAdmin { + // 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 = adminDBNosql.New(a.db.(nosql.DB), admin.DefaultAuthorityID) + if err != nil { + return err + } } - if err := a.provisioners.Store(p); err != nil { - return err + + provs, err := a.adminDB.GetProvisioners(context.Background()) + if err != nil { + return admin.WrapErrorISE(err, "error loading provisioners to initialize authority") + } + if len(provs) == 0 { + // Create First Provisioner + prov, err := CreateFirstProvisioner(context.Background(), a.adminDB, a.config.Password) + if err != nil { + return admin.WrapErrorISE(err, "error creating first provisioner") + } + + // Create first admin + if err := a.adminDB.CreateAdmin(context.Background(), &linkedca.Admin{ + ProvisionerId: prov.Id, + Subject: "step", + Type: linkedca.Admin_SUPER_ADMIN, + }); err != nil { + return admin.WrapErrorISE(err, "error creating first admin") + } } } - // Store all the admins - a.admins = admin.NewCollection(a.provisioners) - for _, adm := range a.config.AuthorityConfig.Admins { - if err := a.admins.Store(adm); err != nil { - return err - } + + // Load Provisioners and Admins + if err := a.reloadAdminResources(context.Background()); err != nil { + return err } // Configure templates, currently only ssh templates are supported. @@ -543,21 +496,11 @@ func (a *Authority) GetDatabase() db.AuthDB { return a.db } -// GetAdminDatabase returns the mgmt database, if one exists. -func (a *Authority) GetAdminDatabase() mgmt.DB { +// GetAdminDatabase returns the admin database, if one exists. +func (a *Authority) GetAdminDatabase() admin.DB { return a.adminDB } -// GetAdminCollection returns the admin collection. -func (a *Authority) GetAdminCollection() *admin.Collection { - return a.admins -} - -// GetProvisionerCollection returns the admin collection. -func (a *Authority) GetProvisionerCollection() *provisioner.Collection { - return a.provisioners -} - // Shutdown safely shuts down any clients, databases, etc. held by the Authority. func (a *Authority) Shutdown() error { if err := a.keyManager.Close(); err != nil { diff --git a/authority/authority_test.go b/authority/authority_test.go index 618e7939..7604ec6b 100644 --- a/authority/authority_test.go +++ b/authority/authority_test.go @@ -454,8 +454,6 @@ func TestAuthority_GetSCEPService(t *testing.T) { // getIdentityFunc: tt.fields.getIdentityFunc, // } a, err := New(tt.fields.config) - fmt.Println(err) - fmt.Println(a) if (err != nil) != tt.wantErr { t.Errorf("Authority.New(), error = %v, wantErr %v", err, tt.wantErr) return diff --git a/authority/authorize.go b/authority/authorize.go index f84bd6f5..8d1f878a 100644 --- a/authority/authorize.go +++ b/authority/authorize.go @@ -7,10 +7,13 @@ import ( "encoding/hex" "net/http" "strings" + "time" + "github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/errs" "go.step.sm/crypto/jose" + "go.step.sm/linkedca" "golang.org/x/crypto/ssh" ) @@ -73,25 +76,121 @@ func (a *Authority) authorizeToken(ctx context.Context, token string) (provision // Store the token to protect against reuse unless it's skipped. // If we cannot get a token id from the provisioner, just hash the token. if !SkipTokenReuseFromContext(ctx) { - if reuseKey, err := p.GetTokenID(token); err == nil { - if reuseKey == "" { - sum := sha256.Sum256([]byte(token)) - reuseKey = strings.ToLower(hex.EncodeToString(sum[:])) - } - ok, err := a.db.UseToken(reuseKey, token) - if err != nil { - return nil, errs.Wrap(http.StatusInternalServerError, err, - "authority.authorizeToken: failed when attempting to store token") - } - if !ok { - return nil, errs.Unauthorized("authority.authorizeToken: token already used") - } + if err = a.UseToken(token, p); err != nil { + return nil, err } } return p, nil } +// AuthorizeAdminToken authorize an Admin token. +func (a *Authority) AuthorizeAdminToken(r *http.Request, token string) (*linkedca.Admin, error) { + jwt, err := jose.ParseSigned(token) + if err != nil { + return nil, admin.WrapError(admin.ErrorUnauthorizedType, err, "adminHandler.authorizeToken; error parsing x5c token") + } + + verifiedChains, err := jwt.Headers[0].Certificates(x509.VerifyOptions{ + Roots: a.rootX509CertPool, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + }) + if err != nil { + return nil, admin.WrapError(admin.ErrorUnauthorizedType, err, + "adminHandler.authorizeToken; error verifying x5c certificate chain in token") + } + leaf := verifiedChains[0][0] + + if leaf.KeyUsage&x509.KeyUsageDigitalSignature == 0 { + return nil, admin.NewError(admin.ErrorUnauthorizedType, "adminHandler.authorizeToken; certificate used to sign x5c token cannot be used for digital signature") + } + + // Using the leaf certificates key to validate the claims accomplishes two + // things: + // 1. Asserts that the private key used to sign the token corresponds + // to the public certificate in the `x5c` header of the token. + // 2. Asserts that the claims are valid - have not been tampered with. + var claims jose.Claims + if err = jwt.Claims(leaf.PublicKey, &claims); err != nil { + return nil, admin.WrapError(admin.ErrorUnauthorizedType, err, "adminHandler.authorizeToken; error parsing x5c claims") + } + + prov, err := a.LoadProvisionerByCertificate(leaf) + if err != nil { + return nil, err + } + + // Check that the token has not been used. + if err = a.UseToken(token, prov); err != nil { + return nil, admin.WrapError(admin.ErrorUnauthorizedType, err, "adminHandler.authorizeToken; error with reuse token") + } + + // According to "rfc7519 JSON Web Token" acceptable skew should be no + // more than a few minutes. + if err = claims.ValidateWithLeeway(jose.Expected{ + Issuer: prov.GetName(), + Time: time.Now().UTC(), + }, time.Minute); err != nil { + return nil, admin.WrapError(admin.ErrorUnauthorizedType, err, "x5c.authorizeToken; invalid x5c claims") + } + + // validate audience: path matches the current path + if r.URL.Path != claims.Audience[0] { + return nil, admin.NewError(admin.ErrorUnauthorizedType, + "x5c.authorizeToken; x5c token has invalid audience "+ + "claim (aud); expected %s, but got %s", r.URL.Path, claims.Audience) + } + + if claims.Subject == "" { + return nil, admin.NewError(admin.ErrorUnauthorizedType, + "x5c.authorizeToken; x5c token subject cannot be empty") + } + + var ( + ok bool + adm *linkedca.Admin + ) + adminFound := false + adminSANs := append([]string{leaf.Subject.CommonName}, leaf.DNSNames...) + adminSANs = append(adminSANs, leaf.EmailAddresses...) + for _, san := range adminSANs { + if adm, ok = a.LoadAdminBySubProv(san, claims.Issuer); ok { + adminFound = true + break + } + } + if !adminFound { + return nil, admin.NewError(admin.ErrorUnauthorizedType, + "adminHandler.authorizeToken; unable to load admin with subject(s) %s and provisioner '%s'", + adminSANs, claims.Issuer) + } + + if strings.HasPrefix(r.URL.Path, "/admin/admins") && (r.Method != "GET") && adm.Type != linkedca.Admin_SUPER_ADMIN { + return nil, admin.NewError(admin.ErrorUnauthorizedType, "must have super admin access to make this request") + } + + return adm, nil +} + +// UseToken stores the token to protect against reuse. +func (a *Authority) UseToken(token string, prov provisioner.Interface) error { + if reuseKey, err := prov.GetTokenID(token); err == nil { + if reuseKey == "" { + sum := sha256.Sum256([]byte(token)) + reuseKey = strings.ToLower(hex.EncodeToString(sum[:])) + } + ok, err := a.db.UseToken(reuseKey, token) + if err != nil { + return errs.Wrap(http.StatusInternalServerError, err, + "authority.authorizeToken: failed when attempting to store token") + } + if !ok { + return errs.Unauthorized("authority.authorizeToken: token already used") + } + } + return nil +} + // Authorize grabs the method from the context and authorizes the request by // validating the one-time-token. func (a *Authority) Authorize(ctx context.Context, token string) ([]provisioner.SignOption, error) { diff --git a/authority/authorize_test.go b/authority/authorize_test.go index f20e2976..f308ec28 100644 --- a/authority/authorize_test.go +++ b/authority/authorize_test.go @@ -822,7 +822,7 @@ func TestAuthority_authorizeRenew(t *testing.T) { return &authorizeTest{ auth: a, cert: renewDisabledCrt, - err: errors.New("authority.authorizeRenew: jwk.AuthorizeRenew; renew is disabled for jwk provisioner renew_disabled:IMi94WBNI6gP5cNHXlZYNUzvMjGdHyBRmFoo-lCEaqk"), + err: errors.New("authority.authorizeRenew: jwk.AuthorizeRenew; renew is disabled for jwk provisioner 'renew_disabled'"), code: http.StatusUnauthorized, } }, diff --git a/authority/config.go b/authority/config.go index 79bdc7ff..744ca5e7 100644 --- a/authority/config.go +++ b/authority/config.go @@ -5,17 +5,26 @@ import "github.com/smallstep/certificates/authority/config" // Config is an alias to support older APIs. type Config = config.Config +// LoadConfiguration is an alias to support older APIs. +var LoadConfiguration = config.LoadConfiguration + // AuthConfig is an alias to support older APIs. type AuthConfig = config.AuthConfig +// TLS + // ASN1DN is an alias to support older APIs. type ASN1DN = config.ASN1DN -// TLS +// DefaultTLSOptions is an alias to support older APIs. +var DefaultTLSOptions = config.DefaultTLSOptions // TLSOptions is an alias to support older APIs. type TLSOptions = config.TLSOptions +// CipherSuites is an alias to support older APIs. +type CipherSuites = config.CipherSuites + // SSH // SSHConfig is an alias to support older APIs. diff --git a/authority/config/config.go b/authority/config/config.go index 55041142..9ad1ff5f 100644 --- a/authority/config/config.go +++ b/authority/config/config.go @@ -12,8 +12,8 @@ import ( cas "github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/db" kms "github.com/smallstep/certificates/kms/apiv1" - "github.com/smallstep/certificates/linkedca" "github.com/smallstep/certificates/templates" + "go.step.sm/linkedca" ) const ( @@ -21,18 +21,6 @@ const ( ) var ( - // DefaultTLSOptions represents the default TLS version as well as the cipher - // suites used in the TLS certificates. - DefaultTLSOptions = TLSOptions{ - CipherSuites: CipherSuites{ - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - }, - MinVersion: 1.2, - MaxVersion: 1.2, - Renegotiation: false, - } // DefaultBackdate length of time to backdate certificates to avoid // clock skew validation issues. DefaultBackdate = time.Minute @@ -41,7 +29,7 @@ var ( // DefaultEnableSSHCA enable SSH CA features per provisioner or globally // for all provisioners. DefaultEnableSSHCA = false - // GlobalProvisionerClaims default claims for the Authority. Can be overriden + // GlobalProvisionerClaims default claims for the Authority. Can be overridden // by provisioner specific claims. GlobalProvisionerClaims = provisioner.Claims{ MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute}, // TLS certs @@ -65,6 +53,7 @@ type Config struct { IntermediateCert string `json:"crt"` IntermediateKey string `json:"key"` Address string `json:"address"` + InsecureAddress string `json:"insecureAddress"` DNSNames []string `json:"dnsNames"` KMS *kms.Options `json:"kms,omitempty"` SSH *SSHConfig `json:"ssh,omitempty"` @@ -80,13 +69,13 @@ type Config struct { // ASN1DN contains ASN1.DN attributes that are used in Subject and Issuer // x509 Certificate blocks. type ASN1DN struct { - Country string `json:"country,omitempty" step:"country"` - Organization string `json:"organization,omitempty" step:"organization"` - OrganizationalUnit string `json:"organizationalUnit,omitempty" step:"organizationalUnit"` - Locality string `json:"locality,omitempty" step:"locality"` - Province string `json:"province,omitempty" step:"province"` - StreetAddress string `json:"streetAddress,omitempty" step:"streetAddress"` - CommonName string `json:"commonName,omitempty" step:"commonName"` + Country string `json:"country,omitempty"` + Organization string `json:"organization,omitempty"` + OrganizationalUnit string `json:"organizationalUnit,omitempty"` + Locality string `json:"locality,omitempty"` + Province string `json:"province,omitempty"` + StreetAddress string `json:"streetAddress,omitempty"` + CommonName string `json:"commonName,omitempty"` } // AuthConfig represents the configuration options for the authority. An @@ -101,6 +90,7 @@ type AuthConfig struct { Claims *provisioner.Claims `json:"claims,omitempty"` DisableIssuedAtCheck bool `json:"disableIssuedAtCheck,omitempty"` Backdate *provisioner.Duration `json:"backdate,omitempty"` + EnableAdmin bool `json:"enableAdmin,omitempty"` } // init initializes the required fields in the AuthConfig if they are not diff --git a/authority/config/config_test.go b/authority/config/config_test.go index 735ac33e..a5b60513 100644 --- a/authority/config/config_test.go +++ b/authority/config/config_test.go @@ -8,12 +8,14 @@ import ( "github.com/smallstep/assert" "github.com/smallstep/certificates/authority/provisioner" "go.step.sm/crypto/jose" + + _ "github.com/smallstep/certificates/cas" ) func TestConfigValidate(t *testing.T) { - maxjwk, err := jose.ReadKey("testdata/secrets/max_pub.jwk") + maxjwk, err := jose.ReadKey("../testdata/secrets/max_pub.jwk") assert.FatalError(t, err) - clijwk, err := jose.ReadKey("testdata/secrets/step_cli_key_pub.jwk") + clijwk, err := jose.ReadKey("../testdata/secrets/step_cli_key_pub.jwk") assert.FatalError(t, err) ac := &AuthConfig{ Provisioners: provisioner.List{ @@ -39,9 +41,9 @@ func TestConfigValidate(t *testing.T) { "empty-address": func(t *testing.T) ConfigValidateTest { return ConfigValidateTest{ config: &Config{ - Root: []string{"testdata/secrets/root_ca.crt"}, - IntermediateCert: "testdata/secrets/intermediate_ca.crt", - IntermediateKey: "testdata/secrets/intermediate_ca_key", + Root: []string{"../testdata/secrets/root_ca.crt"}, + IntermediateCert: "../testdata/secrets/intermediate_ca.crt", + IntermediateKey: "../testdata/secrets/intermediate_ca_key", DNSNames: []string{"test.smallstep.com"}, Password: "pass", AuthorityConfig: ac, @@ -53,9 +55,9 @@ func TestConfigValidate(t *testing.T) { return ConfigValidateTest{ config: &Config{ Address: "127.0.0.1", - Root: []string{"testdata/secrets/root_ca.crt"}, - IntermediateCert: "testdata/secrets/intermediate_ca.crt", - IntermediateKey: "testdata/secrets/intermediate_ca_key", + Root: []string{"../testdata/secrets/root_ca.crt"}, + IntermediateCert: "../testdata/secrets/intermediate_ca.crt", + IntermediateKey: "../testdata/secrets/intermediate_ca_key", DNSNames: []string{"test.smallstep.com"}, Password: "pass", AuthorityConfig: ac, @@ -67,8 +69,8 @@ func TestConfigValidate(t *testing.T) { return ConfigValidateTest{ config: &Config{ Address: "127.0.0.1:443", - IntermediateCert: "testdata/secrets/intermediate_ca.crt", - IntermediateKey: "testdata/secrets/intermediate_ca_key", + IntermediateCert: "../testdata/secrets/intermediate_ca.crt", + IntermediateKey: "../testdata/secrets/intermediate_ca_key", DNSNames: []string{"test.smallstep.com"}, Password: "pass", AuthorityConfig: ac, @@ -80,8 +82,8 @@ func TestConfigValidate(t *testing.T) { return ConfigValidateTest{ config: &Config{ Address: "127.0.0.1:443", - Root: []string{"testdata/secrets/root_ca.crt"}, - IntermediateKey: "testdata/secrets/intermediate_ca_key", + Root: []string{"../testdata/secrets/root_ca.crt"}, + IntermediateKey: "../testdata/secrets/intermediate_ca_key", DNSNames: []string{"test.smallstep.com"}, Password: "pass", AuthorityConfig: ac, @@ -93,8 +95,8 @@ func TestConfigValidate(t *testing.T) { return ConfigValidateTest{ config: &Config{ Address: "127.0.0.1:443", - Root: []string{"testdata/secrets/root_ca.crt"}, - IntermediateCert: "testdata/secrets/intermediate_ca.crt", + Root: []string{"../testdata/secrets/root_ca.crt"}, + IntermediateCert: "../testdata/secrets/intermediate_ca.crt", DNSNames: []string{"test.smallstep.com"}, Password: "pass", AuthorityConfig: ac, @@ -106,9 +108,9 @@ func TestConfigValidate(t *testing.T) { return ConfigValidateTest{ config: &Config{ Address: "127.0.0.1:443", - Root: []string{"testdata/secrets/root_ca.crt"}, - IntermediateCert: "testdata/secrets/intermediate_ca.crt", - IntermediateKey: "testdata/secrets/intermediate_ca_key", + Root: []string{"../testdata/secrets/root_ca.crt"}, + IntermediateCert: "../testdata/secrets/intermediate_ca.crt", + IntermediateKey: "../testdata/secrets/intermediate_ca_key", Password: "pass", AuthorityConfig: ac, }, @@ -119,9 +121,9 @@ func TestConfigValidate(t *testing.T) { return ConfigValidateTest{ config: &Config{ Address: "127.0.0.1:443", - Root: []string{"testdata/secrets/root_ca.crt"}, - IntermediateCert: "testdata/secrets/intermediate_ca.crt", - IntermediateKey: "testdata/secrets/intermediate_ca_key", + Root: []string{"../testdata/secrets/root_ca.crt"}, + IntermediateCert: "../testdata/secrets/intermediate_ca.crt", + IntermediateKey: "../testdata/secrets/intermediate_ca_key", DNSNames: []string{"test.smallstep.com"}, Password: "pass", AuthorityConfig: ac, @@ -133,9 +135,9 @@ func TestConfigValidate(t *testing.T) { return ConfigValidateTest{ config: &Config{ Address: "127.0.0.1:443", - Root: []string{"testdata/secrets/root_ca.crt"}, - IntermediateCert: "testdata/secrets/intermediate_ca.crt", - IntermediateKey: "testdata/secrets/intermediate_ca_key", + Root: []string{"../testdata/secrets/root_ca.crt"}, + IntermediateCert: "../testdata/secrets/intermediate_ca.crt", + IntermediateKey: "../testdata/secrets/intermediate_ca_key", DNSNames: []string{"test.smallstep.com"}, Password: "pass", AuthorityConfig: ac, @@ -148,9 +150,9 @@ func TestConfigValidate(t *testing.T) { return ConfigValidateTest{ config: &Config{ Address: "127.0.0.1:443", - Root: []string{"testdata/secrets/root_ca.crt"}, - IntermediateCert: "testdata/secrets/intermediate_ca.crt", - IntermediateKey: "testdata/secrets/intermediate_ca_key", + Root: []string{"../testdata/secrets/root_ca.crt"}, + IntermediateCert: "../testdata/secrets/intermediate_ca.crt", + IntermediateKey: "../testdata/secrets/intermediate_ca_key", DNSNames: []string{"test.smallstep.com"}, Password: "pass", AuthorityConfig: ac, @@ -177,9 +179,9 @@ func TestConfigValidate(t *testing.T) { return ConfigValidateTest{ config: &Config{ Address: "127.0.0.1:443", - Root: []string{"testdata/secrets/root_ca.crt"}, - IntermediateCert: "testdata/secrets/intermediate_ca.crt", - IntermediateKey: "testdata/secrets/intermediate_ca_key", + Root: []string{"../testdata/secrets/root_ca.crt"}, + IntermediateCert: "../testdata/secrets/intermediate_ca.crt", + IntermediateKey: "../testdata/secrets/intermediate_ca_key", DNSNames: []string{"test.smallstep.com"}, Password: "pass", AuthorityConfig: ac, @@ -207,6 +209,8 @@ func TestConfigValidate(t *testing.T) { } } else { if assert.Nil(t, tc.err) { + fmt.Printf("tc.tls = %+v\n", tc.tls) + fmt.Printf("*tc.config.TLS = %+v\n", *tc.config.TLS) assert.Equals(t, *tc.config.TLS, tc.tls) } } @@ -224,9 +228,9 @@ func TestAuthConfigValidate(t *testing.T) { CommonName: "test", } - maxjwk, err := jose.ReadKey("testdata/secrets/max_pub.jwk") + maxjwk, err := jose.ReadKey("../testdata/secrets/max_pub.jwk") assert.FatalError(t, err) - clijwk, err := jose.ReadKey("testdata/secrets/step_cli_key_pub.jwk") + clijwk, err := jose.ReadKey("../testdata/secrets/step_cli_key_pub.jwk") assert.FatalError(t, err) p := provisioner.List{ &provisioner.JWK{ diff --git a/authority/config/ssh_test.go b/authority/config/ssh_test.go new file mode 100644 index 00000000..2c4c8eac --- /dev/null +++ b/authority/config/ssh_test.go @@ -0,0 +1,73 @@ +package config + +import ( + "reflect" + "testing" + + "github.com/smallstep/assert" + "go.step.sm/crypto/jose" + "golang.org/x/crypto/ssh" +) + +func TestSSHPublicKey_Validate(t *testing.T) { + key, err := jose.GenerateJWK("EC", "P-256", "", "sig", "", 0) + assert.FatalError(t, err) + + type fields struct { + Type string + Federated bool + Key jose.JSONWebKey + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + {"user", fields{"user", true, key.Public()}, false}, + {"host", fields{"host", false, key.Public()}, false}, + {"empty", fields{"", true, key.Public()}, true}, + {"badType", fields{"bad", false, key.Public()}, true}, + {"badKey", fields{"user", false, *key}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + k := &SSHPublicKey{ + Type: tt.fields.Type, + Federated: tt.fields.Federated, + Key: tt.fields.Key, + } + if err := k.Validate(); (err != nil) != tt.wantErr { + t.Errorf("SSHPublicKey.Validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestSSHPublicKey_PublicKey(t *testing.T) { + key, err := jose.GenerateJWK("EC", "P-256", "", "sig", "", 0) + assert.FatalError(t, err) + pub, err := ssh.NewPublicKey(key.Public().Key) + assert.FatalError(t, err) + + type fields struct { + publicKey ssh.PublicKey + } + tests := []struct { + name string + fields fields + want ssh.PublicKey + }{ + {"ok", fields{pub}, pub}, + {"nil", fields{nil}, nil}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + k := &SSHPublicKey{ + publicKey: tt.fields.publicKey, + } + if got := k.PublicKey(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("SSHPublicKey.PublicKey() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/authority/config/tls_options.go b/authority/config/tls_options.go index 5b0575d6..996b5834 100644 --- a/authority/config/tls_options.go +++ b/authority/config/tls_options.go @@ -34,6 +34,18 @@ var ( "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", } + // DefaultTLSOptions represents the default TLS version as well as the cipher + // suites used in the TLS certificates. + DefaultTLSOptions = TLSOptions{ + CipherSuites: CipherSuites{ + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + }, + MinVersion: 1.2, + MaxVersion: 1.2, + Renegotiation: false, + } ) // TLSVersion represents a TLS version number. diff --git a/authority/mgmt/api/handler.go b/authority/mgmt/api/handler.go deleted file mode 100644 index ad8b35ad..00000000 --- a/authority/mgmt/api/handler.go +++ /dev/null @@ -1,47 +0,0 @@ -package api - -import ( - "time" - - "github.com/smallstep/certificates/api" - "github.com/smallstep/certificates/authority" - "github.com/smallstep/certificates/authority/mgmt" -) - -// Clock that returns time in UTC rounded to seconds. -type Clock struct{} - -// Now returns the UTC time rounded to seconds. -func (c *Clock) Now() time.Time { - return time.Now().UTC().Truncate(time.Second) -} - -var clock Clock - -// Handler is the ACME API request handler. -type Handler struct { - db mgmt.DB - auth *authority.Authority -} - -// NewHandler returns a new Authority Config Handler. -func NewHandler(auth *authority.Authority) api.RouterHandler { - return &Handler{auth.GetAdminDatabase(), auth} -} - -// Route traffic and implement the Router interface. -func (h *Handler) Route(r api.Router) { - // Provisioners - r.MethodFunc("GET", "/provisioners/{name}", h.GetProvisioner) - r.MethodFunc("GET", "/provisioners", h.GetProvisioners) - r.MethodFunc("POST", "/provisioners", h.CreateProvisioner) - r.MethodFunc("PUT", "/provisioners/{name}", h.UpdateProvisioner) - r.MethodFunc("DELETE", "/provisioners/{name}", h.DeleteProvisioner) - - // Admins - r.MethodFunc("GET", "/admins/{id}", h.GetAdmin) - r.MethodFunc("GET", "/admins", h.GetAdmins) - r.MethodFunc("POST", "/admins", h.CreateAdmin) - r.MethodFunc("PATCH", "/admins/{id}", h.UpdateAdmin) - r.MethodFunc("DELETE", "/admins/{id}", h.DeleteAdmin) -} diff --git a/authority/mgmt/api/provisioner.go b/authority/mgmt/api/provisioner.go deleted file mode 100644 index 7964d36b..00000000 --- a/authority/mgmt/api/provisioner.go +++ /dev/null @@ -1,199 +0,0 @@ -package api - -import ( - "fmt" - "net/http" - - "github.com/go-chi/chi" - "github.com/smallstep/certificates/api" - "github.com/smallstep/certificates/authority/mgmt" - "github.com/smallstep/certificates/authority/provisioner" - "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 *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. -func (cpr *CreateProvisionerRequest) Validate(c *provisioner.Collection) error { - if _, ok := c.LoadByName(cpr.Name); ok { - return mgmt.NewError(mgmt.ErrorBadRequestType, "provisioner with name %s already exists", cpr.Name) - } - return nil -} - -// GetProvisionersResponse is the type for GET /admin/provisioners responses. -type GetProvisionersResponse struct { - Provisioners provisioner.List `json:"provisioners"` - NextCursor string `json:"nextCursor"` -} - -// UpdateProvisionerRequest represents the body for a UpdateProvisioner request. -type UpdateProvisionerRequest struct { - 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. -func (upr *UpdateProvisionerRequest) Validate(c *provisioner.Collection) error { - if _, ok := c.LoadByName(upr.Name); ok { - return mgmt.NewError(mgmt.ErrorBadRequestType, "provisioner with name %s already exists", upr.Name) - } - return nil -} - -// GetProvisioner returns the requested provisioner, or an error. -func (h *Handler) GetProvisioner(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - name := chi.URLParam(r, "name") - - p, ok := h.auth.GetProvisionerCollection().LoadByName(name) - if !ok { - api.WriteError(w, mgmt.NewError(mgmt.ErrorNotFoundType, "provisioner %s not found", name)) - return - } - - prov, err := h.db.GetProvisioner(ctx, p.GetID()) - if err != nil { - api.WriteError(w, err) - return - } - api.JSON(w, prov) -} - -// GetProvisioners returns all provisioners associated with the authority. -func (h *Handler) GetProvisioners(w http.ResponseWriter, r *http.Request) { - cursor, limit, err := api.ParseCursor(r) - if err != nil { - api.WriteError(w, mgmt.WrapError(mgmt.ErrorBadRequestType, err, - "error parsing cursor / limt query params")) - return - } - - p, next, err := h.auth.GetProvisioners(cursor, limit) - if err != nil { - api.WriteError(w, errs.InternalServerErr(err)) - return - } - api.JSON(w, &GetProvisionersResponse{ - Provisioners: p, - NextCursor: next, - }) -} - -// CreateProvisioner creates a new prov. -func (h *Handler) CreateProvisioner(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - var prov = new(linkedca.Provisioner) - if err := api.ReadJSON(r.Body, prov); err != nil { - api.WriteError(w, err) - return - } - - // TODO: validate - - // TODO: fix this - prov.Claims = mgmt.NewDefaultClaims() - - if err := h.db.CreateProvisioner(ctx, prov); err != nil { - api.WriteError(w, err) - return - } - api.JSONStatus(w, prov, http.StatusCreated) - - if err := h.auth.ReloadAuthConfig(ctx); err != nil { - fmt.Printf("err = %+v\n", err) - } -} - -// DeleteProvisioner deletes a provisioner. -func (h *Handler) DeleteProvisioner(w http.ResponseWriter, r *http.Request) { - name := chi.URLParam(r, "name") - - p, ok := h.auth.GetProvisionerCollection().LoadByName(name) - if !ok { - api.WriteError(w, mgmt.NewError(mgmt.ErrorNotFoundType, "provisioner %s not found", name)) - return - } - - c := h.auth.GetAdminCollection() - if c.SuperCount() == c.SuperCountByProvisioner(name) { - api.WriteError(w, mgmt.NewError(mgmt.ErrorBadRequestType, - "cannot remove provisioner %s because no super admins will remain", name)) - return - } - - ctx := r.Context() - if err := h.db.DeleteProvisioner(ctx, p.GetID()); err != nil { - api.WriteError(w, mgmt.WrapErrorISE(err, "error deleting provisioner %s", name)) - return - } - - // Delete all admins associated with the provisioner. - admins, ok := c.LoadByProvisioner(name) - if ok { - for _, adm := range admins { - 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 - } - } - } - - api.JSON(w, &DeleteResponse{Status: "ok"}) - - if err := h.auth.ReloadAuthConfig(ctx); err != nil { - fmt.Printf("err = %+v\n", err) - } -} - -// UpdateProvisioner updates an existing prov. -func (h *Handler) UpdateProvisioner(w http.ResponseWriter, r *http.Request) { - /* - ctx := r.Context() - id := chi.URLParam(r, "id") - - var body UpdateProvisionerRequest - if err := ReadJSON(r.Body, &body); err != nil { - api.WriteError(w, err) - return - } - if err := body.Validate(); err != nil { - api.WriteError(w, err) - return - } - if prov, err := h.db.GetProvisioner(ctx, id); err != nil { - api.WriteError(w, err) - return - } - - prov.Claims = body.Claims - prov.Details = body.Provisioner - prov.X509Template = body.X509Template - prov.SSHTemplate = body.SSHTemplate - prov.Status = body.Status - - if err := h.db.UpdateProvisioner(ctx, prov); err != nil { - api.WriteError(w, err) - return - } - api.JSON(w, prov) - */ -} diff --git a/authority/mgmt/config.go b/authority/mgmt/config.go deleted file mode 100644 index 3c322415..00000000 --- a/authority/mgmt/config.go +++ /dev/null @@ -1,48 +0,0 @@ -package mgmt - -const ( - // DefaultAuthorityID is the default AuthorityID. This will be the ID - // of the first Authority created, as well as the default AuthorityID - // if one is not specified in the configuration. - DefaultAuthorityID = "00000000-0000-0000-0000-000000000000" -) - -/* -func CreateAuthority(ctx context.Context, db DB, options ...AuthorityOption) (*AuthConfig, error) { - ac := NewDefaultAuthConfig() - - for _, o := range options { - if err := o(ac); err != nil { - return nil, err - } - } - - if err := db.CreateAuthConfig(ctx, ac); err != nil { - return nil, errors.Wrap(err, "error creating authConfig") - } - - // Generate default JWK provisioner. - - provOpts := []ProvisionerOption{WithPassword("pass")} - prov, err := CreateProvisioner(ctx, db, "JWK", "changeme", provOpts...) - if err != nil { - // TODO should we try to clean up? - return nil, WrapErrorISE(err, "error creating first provisioner") - } - - adm := &Admin{ - ProvisionerID: prov.ID, - Subject: "Change Me", - Type: AdminTypeSuper, - } - if err := db.CreateAdmin(ctx, adm); err != nil { - // TODO should we try to clean up? - return nil, WrapErrorISE(err, "error creating first admin") - } - - ac.Provisioners = []*Provisioner{prov} - ac.Admins = []*Admin{adm} - - return ac, nil -} -*/ diff --git a/authority/mgmt/db/nosql/provisioner.go b/authority/mgmt/db/nosql/provisioner.go deleted file mode 100644 index fa5eaf2a..00000000 --- a/authority/mgmt/db/nosql/provisioner.go +++ /dev/null @@ -1,219 +0,0 @@ -package nosql - -import ( - "context" - "encoding/json" - "time" - - "github.com/pkg/errors" - "github.com/smallstep/certificates/authority/mgmt" - "github.com/smallstep/certificates/linkedca" - "github.com/smallstep/nosql" -) - -// 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 is the key - Name string `json:"name"` - Claims *linkedca.Claims `json:"claims"` - Details []byte `json:"details"` - X509Template []byte `json:"x509Template"` - X509TemplateData []byte `json:"x509TemplateData"` - SSHTemplate []byte `json:"sshTemplate"` - SSHTemplateData []byte `json:"sshTemplateData"` - CreatedAt time.Time `json:"createdAt"` - DeletedAt time.Time `json:"deletedAt"` -} - -type provisionerNameID struct { - Name string `json:"name"` - ID string `json:"id"` -} - -func (dbp *dbProvisioner) clone() *dbProvisioner { - u := *dbp - return &u -} - -func (db *DB) getDBProvisionerBytes(ctx context.Context, id string) ([]byte, error) { - data, err := db.db.Get(authorityProvisionersTable, []byte(id)) - if nosql.IsErrNotFound(err) { - return nil, mgmt.NewError(mgmt.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) getDBProvisioner(ctx context.Context, id string) (*dbProvisioner, error) { - data, err := db.getDBProvisionerBytes(ctx, id) - if err != nil { - return nil, err - } - dbp, err := unmarshalDBProvisioner(data, id) - if err != nil { - return nil, err - } - if !dbp.DeletedAt.IsZero() { - return nil, mgmt.NewError(mgmt.ErrorDeletedType, "provisioner %s is deleted", id) - } - if dbp.AuthorityID != db.authorityID { - return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType, - "provisioner %s is not owned by authority %s", dbp.ID, db.authorityID) - } - return dbp, nil -} - -// 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 := unmarshalProvisioner(data, id) - if err != nil { - return nil, err - } - if prov.AuthorityId != db.authorityID { - return nil, mgmt.NewError(mgmt.ErrorAuthorityMismatchType, - "provisioner %s is not owned by authority %s", prov.Id, db.authorityID) - } - return prov, nil -} - -func unmarshalDBProvisioner(data []byte, name 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", name) - } - if !dbp.DeletedAt.IsZero() { - return nil, mgmt.NewError(mgmt.ErrorDeletedType, "provisioner %s is deleted", name) - } - return dbp, nil -} - -func unmarshalProvisioner(data []byte, name string) (*linkedca.Provisioner, error) { - dbp, err := unmarshalDBProvisioner(data, name) - if err != nil { - return nil, err - } - - details, err := linkedca.UnmarshalProvisionerDetails(dbp.Type, dbp.Details) - if err != nil { - return nil, err - } - - prov := &linkedca.Provisioner{ - Id: dbp.ID, - AuthorityId: dbp.AuthorityID, - Type: dbp.Type, - Name: dbp.Name, - Claims: dbp.Claims, - Details: details, - X509Template: dbp.X509Template, - X509TemplateData: dbp.X509TemplateData, - SshTemplate: dbp.SSHTemplate, - SshTemplateData: dbp.SSHTemplateData, - } - 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(authorityProvisionersTable) - if err != nil { - return nil, mgmt.WrapErrorISE(err, "error loading provisioners") - } - var provs []*linkedca.Provisioner - for _, entry := range dbEntries { - prov, err := unmarshalProvisioner(entry.Value, string(entry.Key)) - if err != nil { - 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 errors.Wrap(err, "error generating random id for provisioner") - } - - details, err := json.Marshal(prov.Details.GetData()) - if err != nil { - return mgmt.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, - X509TemplateData: prov.X509TemplateData, - SSHTemplate: prov.SshTemplate, - SSHTemplateData: prov.SshTemplateData, - CreatedAt: clock.Now(), - } - - if err := db.save(ctx, prov.Id, dbp, nil, "provisioner", authorityProvisionersTable); err != nil { - return mgmt.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() - - nu.Type = prov.Type - nu.Name = prov.Name - nu.Claims = prov.Claims - nu.Details, err = json.Marshal(prov.Details) - if err != nil { - return mgmt.WrapErrorISE(err, "error marshaling details when updating provisioner %s", prov.Name) - } - nu.X509Template = prov.X509Template - nu.X509TemplateData = prov.X509TemplateData - nu.SSHTemplate = prov.SshTemplate - nu.SSHTemplateData = prov.SshTemplateData - - if err := db.save(ctx, prov.Id, nu, old, "provisioner", authorityProvisionersTable); err != nil { - return mgmt.WrapErrorISE(err, "error updating provisioner %s", prov.Name) - } - - 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 deleted file mode 100644 index 2177e116..00000000 --- a/authority/mgmt/provisioner.go +++ /dev/null @@ -1,116 +0,0 @@ -package mgmt - -import ( - "context" - - "github.com/smallstep/certificates/authority/config" - "github.com/smallstep/certificates/linkedca" - "go.step.sm/crypto/jose" -) - -/* -type unmarshalProvisioner struct { - ID string `json:"-"` - AuthorityID string `json:"-"` - Type string `json:"type"` - Name string `json:"name"` - Claims *Claims `json:"claims"` - Details json.RawMessage `json:"details"` - X509Template string `json:"x509Template"` - X509TemplateData []byte `json:"x509TemplateData"` - SSHTemplate string `json:"sshTemplate"` - SSHTemplateData []byte `json:"sshTemplateData"` - Status status.Type `json:"status"` -} - -type typ struct { - Type linkedca.Provisioner_Type `json:"type"` -} - -// UnmarshalJSON implements the Unmarshal interface. -func (p *Provisioner) UnmarshalJSON(b []byte) error { - var ( - err error - up = new(unmarshalProvisioner) - ) - if err = json.Unmarshal(b, up); err != nil { - return WrapErrorISE(err, "error unmarshaling provisioner to intermediate type") - } - p.Details, err = UnmarshalProvisionerDetails(up.Details) - if err = json.Unmarshal(b, up); err != nil { - return WrapErrorISE(err, "error unmarshaling provisioner details") - } - - p.ID = up.ID - p.AuthorityID = up.AuthorityID - p.Type = up.Type - p.Name = up.Name - p.Claims = up.Claims - p.X509Template = up.X509Template - p.X509TemplateData = up.X509TemplateData - p.SSHTemplate = up.SSHTemplate - p.SSHTemplateData = up.SSHTemplateData - p.Status = up.Status - - return nil -} -*/ - -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 CreateFirstProvisioner(ctx context.Context, db DB, password string) (*linkedca.Provisioner, error) { - jwk, jwe, err := jose.GenerateDefaultKeyPair([]byte(password)) - if err != nil { - return nil, WrapErrorISE(err, "error generating JWK key pair") - } - - 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") - } - - p := &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), - }, - }, - }, - } - if err := db.CreateProvisioner(ctx, p); err != nil { - return nil, WrapErrorISE(err, "error creating provisioner") - } - return p, nil -} diff --git a/authority/options.go b/authority/options.go index cc2f64af..4e9fbdbc 100644 --- a/authority/options.go +++ b/authority/options.go @@ -7,8 +7,8 @@ import ( "encoding/pem" "github.com/pkg/errors" + "github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/authority/config" - "github.com/smallstep/certificates/authority/mgmt" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/cas" casapi "github.com/smallstep/certificates/cas/apiv1" @@ -189,7 +189,7 @@ func WithX509FederatedBundle(pemCerts []byte) Option { } // WithAdminDB is an option to set the database backing the admin APIs. -func WithAdminDB(db mgmt.DB) Option { +func WithAdminDB(db admin.DB) Option { return func(a *Authority) error { a.adminDB = db return nil diff --git a/authority/provisioner/acme.go b/authority/provisioner/acme.go index 4109d217..d81b0231 100644 --- a/authority/provisioner/acme.go +++ b/authority/provisioner/acme.go @@ -105,7 +105,7 @@ func (p *ACME) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e // certificate was configured to allow renewals. func (p *ACME) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error { if p.claimer.IsDisableRenewal() { - return errs.Unauthorized("acme.AuthorizeRenew; renew is disabled for acme provisioner %s", p.GetID()) + return errs.Unauthorized("acme.AuthorizeRenew; renew is disabled for acme provisioner '%s'", p.GetName()) } return nil } diff --git a/authority/provisioner/acme_test.go b/authority/provisioner/acme_test.go index 7b669d8d..bd173f87 100644 --- a/authority/provisioner/acme_test.go +++ b/authority/provisioner/acme_test.go @@ -61,7 +61,7 @@ func TestACME_Init(t *testing.T) { "fail-bad-claims": func(t *testing.T) ProvisionerValidateTest { return ProvisionerValidateTest{ p: &ACME{Name: "foo", Type: "bar", Claims: &Claims{DefaultTLSDur: &Duration{0}}}, - err: errors.New("claims: DefaultTLSCertDuration must be greater than 0"), + err: errors.New("claims: MinTLSCertDuration must be greater than 0"), } }, "ok": func(t *testing.T) ProvisionerValidateTest { @@ -110,7 +110,7 @@ func TestACME_AuthorizeRenew(t *testing.T) { p: p, cert: &x509.Certificate{}, code: http.StatusUnauthorized, - err: errors.Errorf("acme.AuthorizeRenew; renew is disabled for acme provisioner %s", p.GetID()), + err: errors.Errorf("acme.AuthorizeRenew; renew is disabled for acme provisioner '%s'", p.GetName()), } }, "ok": func(t *testing.T) test { diff --git a/authority/provisioner/aws.go b/authority/provisioner/aws.go index 8a443554..c1c77ce5 100644 --- a/authority/provisioner/aws.go +++ b/authority/provisioner/aws.go @@ -296,7 +296,7 @@ func (p *AWS) GetTokenID(token string) (string, error) { } // Use provisioner + instance-id as the identifier. - unique := fmt.Sprintf("%s.%s", p.GetID(), payload.document.InstanceID) + unique := fmt.Sprintf("%s.%s", p.GetIDForToken(), payload.document.InstanceID) sum := sha256.Sum256([]byte(unique)) return strings.ToLower(hex.EncodeToString(sum[:])), nil } @@ -344,7 +344,7 @@ func (p *AWS) GetIdentityToken(subject, caURL string) (string, error) { return "", err } - audience, err := generateSignAudience(caURL, p.GetID()) + audience, err := generateSignAudience(caURL, p.GetIDForToken()) if err != nil { return "", err } @@ -352,7 +352,7 @@ func (p *AWS) GetIdentityToken(subject, caURL string) (string, error) { // Create unique ID for Trust On First Use (TOFU). Only the first instance // per provisioner is allowed as we don't have a way to trust the given // sans. - unique := fmt.Sprintf("%s.%s", p.GetID(), idoc.InstanceID) + unique := fmt.Sprintf("%s.%s", p.GetIDForToken(), idoc.InstanceID) sum := sha256.Sum256([]byte(unique)) // Create a JWT from the identity document @@ -407,7 +407,7 @@ func (p *AWS) Init(config Config) (err error) { if p.config, err = newAWSConfig(p.IIDRoots); err != nil { return err } - p.audiences = config.Audiences.WithFragment(p.GetID()) + p.audiences = config.Audiences.WithFragment(p.GetIDForToken()) // validate IMDS versions if len(p.IMDSVersions) == 0 { @@ -484,7 +484,7 @@ func (p *AWS) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er // certificate was configured to allow renewals. func (p *AWS) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error { if p.claimer.IsDisableRenewal() { - return errs.Unauthorized("aws.AuthorizeRenew; renew is disabled for aws provisioner %s", p.GetID()) + return errs.Unauthorized("aws.AuthorizeRenew; renew is disabled for aws provisioner '%s'", p.GetName()) } return nil } @@ -697,7 +697,7 @@ func (p *AWS) authorizeToken(token string) (*awsPayload, error) { // AuthorizeSSHSign returns the list of SignOption for a SignSSH request. func (p *AWS) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) { if !p.claimer.IsSSHCAEnabled() { - return nil, errs.Unauthorized("aws.AuthorizeSSHSign; ssh ca is disabled for aws provisioner %s", p.GetID()) + return nil, errs.Unauthorized("aws.AuthorizeSSHSign; ssh ca is disabled for aws provisioner '%s'", p.GetName()) } claims, err := p.authorizeToken(token) if err != nil { diff --git a/authority/provisioner/azure.go b/authority/provisioner/azure.go index b077d735..230f246f 100644 --- a/authority/provisioner/azure.go +++ b/authority/provisioner/azure.go @@ -334,7 +334,7 @@ func (p *Azure) AuthorizeSign(ctx context.Context, token string) ([]SignOption, // certificate was configured to allow renewals. func (p *Azure) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error { if p.claimer.IsDisableRenewal() { - return errs.Unauthorized("azure.AuthorizeRenew; renew is disabled for azure provisioner %s", p.GetID()) + return errs.Unauthorized("azure.AuthorizeRenew; renew is disabled for azure provisioner '%s'", p.GetName()) } return nil } @@ -342,7 +342,7 @@ func (p *Azure) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) erro // AuthorizeSSHSign returns the list of SignOption for a SignSSH request. func (p *Azure) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) { if !p.claimer.IsSSHCAEnabled() { - return nil, errs.Unauthorized("azure.AuthorizeSSHSign; sshCA is disabled for provisioner %s", p.GetID()) + return nil, errs.Unauthorized("azure.AuthorizeSSHSign; sshCA is disabled for provisioner '%s'", p.GetName()) } _, name, _, err := p.authorizeToken(token) diff --git a/authority/provisioner/claims.go b/authority/provisioner/claims.go index 6c87792c..629a313c 100644 --- a/authority/provisioner/claims.go +++ b/authority/provisioner/claims.go @@ -7,27 +7,6 @@ import ( "golang.org/x/crypto/ssh" ) -type _Claims struct { - *X509Claims `json:"x509Claims"` - *SSHClaims `json:"sshClaims"` - DisableRenewal *bool `json:"disableRenewal"` -} - -type X509Claims struct { - Durations *Durations `json:"durations"` -} - -type SSHClaims struct { - UserDuration *Durations `json:"userDurations"` - HostDuration *Durations `json:"hostDuration"` -} - -type Durations struct { - Min string `json:"min"` - Max string `json:"max"` - Default string `json:"default"` -} - // Claims so that individual provisioners can override global claims. type Claims struct { // TLS CA properties @@ -92,6 +71,9 @@ func (c *Claimer) DefaultTLSCertDuration() time.Duration { // minimum from the authority configuration will be used. func (c *Claimer) MinTLSCertDuration() time.Duration { if c.claims == nil || c.claims.MinTLSDur == nil { + if c.claims != nil && c.claims.DefaultTLSDur != nil && c.claims.DefaultTLSDur.Duration < c.global.MinTLSDur.Duration { + return c.claims.DefaultTLSDur.Duration + } return c.global.MinTLSDur.Duration } return c.claims.MinTLSDur.Duration @@ -102,6 +84,9 @@ func (c *Claimer) MinTLSCertDuration() time.Duration { // maximum from the authority configuration will be used. func (c *Claimer) MaxTLSCertDuration() time.Duration { if c.claims == nil || c.claims.MaxTLSDur == nil { + if c.claims != nil && c.claims.DefaultTLSDur != nil && c.claims.DefaultTLSDur.Duration > c.global.MaxTLSDur.Duration { + return c.claims.DefaultTLSDur.Duration + } return c.global.MaxTLSDur.Duration } return c.claims.MaxTLSDur.Duration @@ -147,6 +132,9 @@ func (c *Claimer) DefaultUserSSHCertDuration() time.Duration { // global minimum from the authority configuration will be used. func (c *Claimer) MinUserSSHCertDuration() time.Duration { if c.claims == nil || c.claims.MinUserSSHDur == nil { + if c.claims != nil && c.claims.DefaultUserSSHDur != nil && c.claims.DefaultUserSSHDur.Duration < c.global.MinUserSSHDur.Duration { + return c.claims.DefaultUserSSHDur.Duration + } return c.global.MinUserSSHDur.Duration } return c.claims.MinUserSSHDur.Duration @@ -157,6 +145,9 @@ func (c *Claimer) MinUserSSHCertDuration() time.Duration { // global maximum from the authority configuration will be used. func (c *Claimer) MaxUserSSHCertDuration() time.Duration { if c.claims == nil || c.claims.MaxUserSSHDur == nil { + if c.claims != nil && c.claims.DefaultUserSSHDur != nil && c.claims.DefaultUserSSHDur.Duration > c.global.MaxUserSSHDur.Duration { + return c.claims.DefaultUserSSHDur.Duration + } return c.global.MaxUserSSHDur.Duration } return c.claims.MaxUserSSHDur.Duration @@ -177,6 +168,9 @@ func (c *Claimer) DefaultHostSSHCertDuration() time.Duration { // global minimum from the authority configuration will be used. func (c *Claimer) MinHostSSHCertDuration() time.Duration { if c.claims == nil || c.claims.MinHostSSHDur == nil { + if c.claims != nil && c.claims.DefaultHostSSHDur != nil && c.claims.DefaultHostSSHDur.Duration < c.global.MinHostSSHDur.Duration { + return c.claims.DefaultHostSSHDur.Duration + } return c.global.MinHostSSHDur.Duration } return c.claims.MinHostSSHDur.Duration @@ -187,6 +181,9 @@ func (c *Claimer) MinHostSSHCertDuration() time.Duration { // global maximum from the authority configuration will be used. func (c *Claimer) MaxHostSSHCertDuration() time.Duration { if c.claims == nil || c.claims.MaxHostSSHDur == nil { + if c.claims != nil && c.claims.DefaultHostSSHDur != nil && c.claims.DefaultHostSSHDur.Duration > c.global.MaxHostSSHDur.Duration { + return c.claims.DefaultHostSSHDur.Duration + } return c.global.MaxHostSSHDur.Duration } return c.claims.MaxHostSSHDur.Duration diff --git a/authority/provisioner/collection.go b/authority/provisioner/collection.go index eecacf69..3ba98a23 100644 --- a/authority/provisioner/collection.go +++ b/authority/provisioner/collection.go @@ -12,7 +12,7 @@ import ( "strings" "sync" - "github.com/pkg/errors" + "github.com/smallstep/certificates/authority/admin" "go.step.sm/crypto/jose" ) @@ -148,24 +148,7 @@ func (c *Collection) LoadByCertificate(cert *x509.Certificate) (Interface, bool) if _, err := asn1.Unmarshal(e.Value, &provisioner); err != nil { return nil, false } - switch Type(provisioner.Type) { - case TypeJWK: - return c.Load(string(provisioner.Name) + ":" + string(provisioner.CredentialID)) - case TypeAWS: - return c.Load("aws/" + string(provisioner.Name)) - case TypeGCP: - return c.Load("gcp/" + string(provisioner.Name)) - case TypeACME: - return c.Load("acme/" + string(provisioner.Name)) - case TypeSCEP: - return c.Load("scep/" + string(provisioner.Name)) - case TypeX5C: - return c.Load("x5c/" + string(provisioner.Name)) - case TypeK8sSA: - return c.Load(K8sSAID) - default: - return c.Load(string(provisioner.CredentialID)) - } + return c.LoadByName(string(provisioner.Name)) } } @@ -190,18 +173,21 @@ func (c *Collection) LoadEncryptedKey(keyID string) (string, bool) { func (c *Collection) Store(p Interface) error { // Store provisioner always in byID. ID must be unique. if _, loaded := c.byID.LoadOrStore(p.GetID(), p); loaded { - return errors.New("cannot add multiple provisioners with the same id") + return admin.NewError(admin.ErrorBadRequestType, + "cannot add multiple provisioners with the same id") } // Store provisioner always by name. if _, loaded := c.byName.LoadOrStore(p.GetName(), p); loaded { c.byID.Delete(p.GetID()) - return errors.New("cannot add multiple provisioners with the same name") + return admin.NewError(admin.ErrorBadRequestType, + "cannot add multiple provisioners with the same name") } // Store provisioner always by ID presented in token. if _, loaded := c.byTokenID.LoadOrStore(p.GetIDForToken(), p); loaded { c.byID.Delete(p.GetID()) c.byName.Delete(p.GetName()) - return errors.New("cannot add multiple provisioners with the same token identifier") + return admin.NewError(admin.ErrorBadRequestType, + "cannot add multiple provisioners with the same token identifier") } // Store provisioner in byKey if EncryptedKey is defined. @@ -225,6 +211,65 @@ func (c *Collection) Store(p Interface) error { return nil } +// Remove deletes an provisioner from all associated collections and lists. +func (c *Collection) Remove(id string) error { + prov, ok := c.Load(id) + if !ok { + return admin.NewError(admin.ErrorNotFoundType, "provisioner %s not found", id) + } + + var found bool + for i, elem := range c.sorted { + if elem.provisioner.GetID() == id { + // Remove index in sorted list + copy(c.sorted[i:], c.sorted[i+1:]) // Shift a[i+1:] left one index. + c.sorted[len(c.sorted)-1] = uidProvisioner{} // Erase last element (write zero value). + c.sorted = c.sorted[:len(c.sorted)-1] // Truncate slice. + found = true + break + } + } + if !found { + return admin.NewError(admin.ErrorNotFoundType, "provisioner %s not found in sorted list", prov.GetName()) + } + + c.byID.Delete(id) + c.byName.Delete(prov.GetName()) + c.byTokenID.Delete(prov.GetIDForToken()) + if kid, _, ok := prov.GetEncryptedKey(); ok { + c.byKey.Delete(kid) + } + + return nil +} + +// Update updates the given provisioner in all related lists and collections. +func (c *Collection) Update(nu Interface) error { + old, ok := c.Load(nu.GetID()) + if !ok { + return admin.NewError(admin.ErrorNotFoundType, "provisioner %s not found", nu.GetID()) + } + + if old.GetName() != nu.GetName() { + if _, ok := c.LoadByName(nu.GetName()); ok { + return admin.NewError(admin.ErrorBadRequestType, + "provisioner with name %s already exists", nu.GetName()) + } + } + if old.GetIDForToken() != nu.GetIDForToken() { + if _, ok := c.LoadByTokenID(nu.GetIDForToken()); ok { + return admin.NewError(admin.ErrorBadRequestType, + "provisioner with Token ID %s already exists", nu.GetIDForToken()) + } + } + + if err := c.Remove(old.GetID()); err != nil { + return err + } + + return c.Store(nu) +} + // Find implements pagination on a list of sorted provisioners. func (c *Collection) Find(cursor string, limit int) (List, string) { switch { diff --git a/authority/provisioner/collection_test.go b/authority/provisioner/collection_test.go index a0a79e92..348b797c 100644 --- a/authority/provisioner/collection_test.go +++ b/authority/provisioner/collection_test.go @@ -132,6 +132,7 @@ func TestCollection_LoadByToken(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := &Collection{ byID: tt.fields.byID, + byTokenID: tt.fields.byID, audiences: tt.fields.audiences, } got, got1 := c.LoadByToken(tt.args.token, tt.args.claims) @@ -153,10 +154,10 @@ func TestCollection_LoadByCertificate(t *testing.T) { p3, err := generateACME() assert.FatalError(t, err) - byID := new(sync.Map) - byID.Store(p1.GetID(), p1) - byID.Store(p2.GetID(), p2) - byID.Store(p3.GetID(), p3) + byName := new(sync.Map) + byName.Store(p1.GetName(), p1) + byName.Store(p2.GetName(), p2) + byName.Store(p3.GetName(), p3) ok1Ext, err := createProvisionerExtension(1, p1.Name, p1.Key.KeyID) assert.FatalError(t, err) @@ -186,7 +187,7 @@ func TestCollection_LoadByCertificate(t *testing.T) { } type fields struct { - byID *sync.Map + byName *sync.Map audiences Audiences } type args struct { @@ -199,17 +200,17 @@ func TestCollection_LoadByCertificate(t *testing.T) { want Interface want1 bool }{ - {"ok1", fields{byID, testAudiences}, args{ok1Cert}, p1, true}, - {"ok2", fields{byID, testAudiences}, args{ok2Cert}, p2, true}, - {"ok3", fields{byID, testAudiences}, args{ok3Cert}, p3, true}, - {"noExtension", fields{byID, testAudiences}, args{&x509.Certificate{}}, &noop{}, true}, - {"notFound", fields{byID, testAudiences}, args{notFoundCert}, nil, false}, - {"badCert", fields{byID, testAudiences}, args{badCert}, nil, false}, + {"ok1", fields{byName, testAudiences}, args{ok1Cert}, p1, true}, + {"ok2", fields{byName, testAudiences}, args{ok2Cert}, p2, true}, + {"ok3", fields{byName, testAudiences}, args{ok3Cert}, p3, true}, + {"noExtension", fields{byName, testAudiences}, args{&x509.Certificate{}}, &noop{}, true}, + {"notFound", fields{byName, testAudiences}, args{notFoundCert}, nil, false}, + {"badCert", fields{byName, testAudiences}, args{badCert}, nil, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Collection{ - byID: tt.fields.byID, + byName: tt.fields.byName, audiences: tt.fields.audiences, } got, got1 := c.LoadByCertificate(tt.args.cert) diff --git a/authority/provisioner/gcp.go b/authority/provisioner/gcp.go index 6d19d052..1b599fb3 100644 --- a/authority/provisioner/gcp.go +++ b/authority/provisioner/gcp.go @@ -134,7 +134,7 @@ func (p *GCP) GetTokenID(token string) (string, error) { // Create unique ID for Trust On First Use (TOFU). Only the first instance // per provisioner is allowed as we don't have a way to trust the given // sans. - unique := fmt.Sprintf("%s.%s", p.GetID(), claims.Google.ComputeEngine.InstanceID) + unique := fmt.Sprintf("%s.%s", p.GetIDForToken(), claims.Google.ComputeEngine.InstanceID) sum := sha256.Sum256([]byte(unique)) return strings.ToLower(hex.EncodeToString(sum[:])), nil } @@ -168,7 +168,7 @@ func (p *GCP) GetIdentityURL(audience string) string { // GetIdentityToken does an HTTP request to the identity url. func (p *GCP) GetIdentityToken(subject, caURL string) (string, error) { - audience, err := generateSignAudience(caURL, p.GetID()) + audience, err := generateSignAudience(caURL, p.GetIDForToken()) if err != nil { return "", err } @@ -216,7 +216,7 @@ func (p *GCP) Init(config Config) error { return err } - p.audiences = config.Audiences.WithFragment(p.GetID()) + p.audiences = config.Audiences.WithFragment(p.GetIDForToken()) return nil } @@ -277,7 +277,7 @@ func (p *GCP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er // AuthorizeRenew returns an error if the renewal is disabled. func (p *GCP) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error { if p.claimer.IsDisableRenewal() { - return errs.Unauthorized("gcp.AuthorizeRenew; renew is disabled for gcp provisioner %s", p.GetID()) + return errs.Unauthorized("gcp.AuthorizeRenew; renew is disabled for gcp provisioner '%s'", p.GetName()) } return nil } @@ -382,7 +382,7 @@ func (p *GCP) authorizeToken(token string) (*gcpPayload, error) { // AuthorizeSSHSign returns the list of SignOption for a SignSSH request. func (p *GCP) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) { if !p.claimer.IsSSHCAEnabled() { - return nil, errs.Unauthorized("gcp.AuthorizeSSHSign; sshCA is disabled for gcp provisioner %s", p.GetID()) + return nil, errs.Unauthorized("gcp.AuthorizeSSHSign; sshCA is disabled for gcp provisioner '%s'", p.GetName()) } claims, err := p.authorizeToken(token) if err != nil { diff --git a/authority/provisioner/jwk.go b/authority/provisioner/jwk.go index d57ff4c1..56768fb7 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -194,7 +194,7 @@ func (p *JWK) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er // certificate was configured to allow renewals. func (p *JWK) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error { if p.claimer.IsDisableRenewal() { - return errs.Unauthorized("jwk.AuthorizeRenew; renew is disabled for jwk provisioner %s", p.GetID()) + return errs.Unauthorized("jwk.AuthorizeRenew; renew is disabled for jwk provisioner '%s'", p.GetName()) } return nil } @@ -202,7 +202,7 @@ func (p *JWK) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error // AuthorizeSSHSign returns the list of SignOption for a SignSSH request. func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) { if !p.claimer.IsSSHCAEnabled() { - return nil, errs.Unauthorized("jwk.AuthorizeSSHSign; sshCA is disabled for jwk provisioner %s", p.GetID()) + return nil, errs.Unauthorized("jwk.AuthorizeSSHSign; sshCA is disabled for jwk provisioner '%s'", p.GetName()) } claims, err := p.authorizeToken(token, p.audiences.SSHSign) if err != nil { diff --git a/authority/provisioner/jwk_test.go b/authority/provisioner/jwk_test.go index 9198ff69..deae8f7a 100644 --- a/authority/provisioner/jwk_test.go +++ b/authority/provisioner/jwk_test.go @@ -77,7 +77,7 @@ func TestJWK_Init(t *testing.T) { "fail-bad-claims": func(t *testing.T) ProvisionerValidateTest { return ProvisionerValidateTest{ p: &JWK{Name: "foo", Type: "bar", Key: &jose.JSONWebKey{}, audiences: testAudiences, Claims: &Claims{DefaultTLSDur: &Duration{0}}}, - err: errors.New("claims: DefaultTLSCertDuration must be greater than 0"), + err: errors.New("claims: MinTLSCertDuration must be greater than 0"), } }, "ok": func(t *testing.T) ProvisionerValidateTest { diff --git a/authority/provisioner/k8sSA.go b/authority/provisioner/k8sSA.go index 876131e1..d260f5ec 100644 --- a/authority/provisioner/k8sSA.go +++ b/authority/provisioner/k8sSA.go @@ -111,12 +111,12 @@ func (p *K8sSA) Init(config Config) (err error) { } key, err := pemutil.ParseKey(pem.EncodeToMemory(block)) if err != nil { - return errors.Wrapf(err, "error parsing public key in provisioner %s", p.GetID()) + return errors.Wrapf(err, "error parsing public key in provisioner '%s'", p.GetName()) } switch q := key.(type) { case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey: default: - return errors.Errorf("Unexpected public key type %T in provisioner %s", q, p.GetID()) + return errors.Errorf("Unexpected public key type %T in provisioner '%s'", q, p.GetName()) } p.pubKeys = append(p.pubKeys, key) } @@ -250,7 +250,7 @@ func (p *K8sSA) AuthorizeSign(ctx context.Context, token string) ([]SignOption, // AuthorizeRenew returns an error if the renewal is disabled. func (p *K8sSA) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error { if p.claimer.IsDisableRenewal() { - return errs.Unauthorized("k8ssa.AuthorizeRenew; renew is disabled for k8sSA provisioner %s", p.GetID()) + return errs.Unauthorized("k8ssa.AuthorizeRenew; renew is disabled for k8sSA provisioner '%s'", p.GetName()) } return nil } @@ -258,7 +258,7 @@ func (p *K8sSA) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) erro // AuthorizeSSHSign validates an request for an SSH certificate. func (p *K8sSA) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) { if !p.claimer.IsSSHCAEnabled() { - return nil, errs.Unauthorized("k8ssa.AuthorizeSSHSign; sshCA is disabled for k8sSA provisioner %s", p.GetID()) + return nil, errs.Unauthorized("k8ssa.AuthorizeSSHSign; sshCA is disabled for k8sSA provisioner '%s'", p.GetName()) } claims, err := p.authorizeToken(token, p.audiences.SSHSign) if err != nil { diff --git a/authority/provisioner/k8sSA_test.go b/authority/provisioner/k8sSA_test.go index 03ae7eff..176cdfd3 100644 --- a/authority/provisioner/k8sSA_test.go +++ b/authority/provisioner/k8sSA_test.go @@ -198,7 +198,7 @@ func TestK8sSA_AuthorizeRenew(t *testing.T) { p: p, cert: &x509.Certificate{}, code: http.StatusUnauthorized, - err: errors.Errorf("k8ssa.AuthorizeRenew; renew is disabled for k8sSA provisioner %s", p.GetID()), + err: errors.Errorf("k8ssa.AuthorizeRenew; renew is disabled for k8sSA provisioner '%s'", p.GetName()), } }, "ok": func(t *testing.T) test { @@ -319,7 +319,7 @@ func TestK8sSA_AuthorizeSSHSign(t *testing.T) { p: p, token: "foo", code: http.StatusUnauthorized, - err: errors.Errorf("k8ssa.AuthorizeSSHSign; sshCA is disabled for k8sSA provisioner %s", p.GetID()), + err: errors.Errorf("k8ssa.AuthorizeSSHSign; sshCA is disabled for k8sSA provisioner '%s'", p.GetName()), } }, "fail/invalid-token": func(t *testing.T) test { diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index df3c1f9e..b6bca872 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -377,7 +377,7 @@ func (o *OIDC) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e // certificate was configured to allow renewals. func (o *OIDC) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error { if o.claimer.IsDisableRenewal() { - return errs.Unauthorized("oidc.AuthorizeRenew; renew is disabled for oidc provisioner %s", o.GetID()) + return errs.Unauthorized("oidc.AuthorizeRenew; renew is disabled for oidc provisioner '%s'", o.GetName()) } return nil } @@ -385,7 +385,7 @@ func (o *OIDC) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error // AuthorizeSSHSign returns the list of SignOption for a SignSSH request. func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) { if !o.claimer.IsSSHCAEnabled() { - return nil, errs.Unauthorized("oidc.AuthorizeSSHSign; sshCA is disabled for oidc provisioner %s", o.GetID()) + return nil, errs.Unauthorized("oidc.AuthorizeSSHSign; sshCA is disabled for oidc provisioner '%s'", o.GetName()) } claims, err := o.authorizeToken(token) if err != nil { diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 7673ecc2..d4517627 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -11,6 +11,7 @@ import ( // SCEP provisioning flow type SCEP struct { *base + ID string `json:"-"` Type string `json:"type"` Name string `json:"name"` @@ -26,8 +27,17 @@ type SCEP struct { secretChallengePassword string } -// GetID returns the provisioner unique identifier. -func (s SCEP) GetID() string { +// GetID returns the provisioner unique identifier. The name and credential id +// should uniquely identify any JWK provisioner. +func (s *SCEP) GetID() string { + if s.ID != "" { + return s.ID + } + return s.GetIDForToken() +} + +// GetIDForToken returns the provisioner unique identifier. +func (s SCEP) GetIDForToken() string { return "scep/" + s.Name } diff --git a/authority/provisioner/sshpop.go b/authority/provisioner/sshpop.go index dd9c7d1f..8bc76edf 100644 --- a/authority/provisioner/sshpop.go +++ b/authority/provisioner/sshpop.go @@ -101,7 +101,7 @@ func (p *SSHPOP) Init(config Config) error { return err } - p.audiences = config.Audiences.WithFragment(p.GetID()) + p.audiences = config.Audiences.WithFragment(p.GetIDForToken()) p.db = config.DB p.sshPubKeys = config.SSHKeys return nil diff --git a/authority/provisioner/x5c.go b/authority/provisioner/x5c.go index 9e47aaed..a05f39c7 100644 --- a/authority/provisioner/x5c.go +++ b/authority/provisioner/x5c.go @@ -116,7 +116,7 @@ func (p *X5C) Init(config Config) error { // Verify that at least one root was found. if len(p.rootPool.Subjects()) == 0 { - return errors.Errorf("no x509 certificates found in roots attribute for provisioner %s", p.GetName()) + return errors.Errorf("no x509 certificates found in roots attribute for provisioner '%s'", p.GetName()) } // Update claims with global ones @@ -125,7 +125,7 @@ func (p *X5C) Init(config Config) error { return err } - p.audiences = config.Audiences.WithFragment(p.GetID()) + p.audiences = config.Audiences.WithFragment(p.GetIDForToken()) return nil } @@ -139,7 +139,8 @@ func (p *X5C) authorizeToken(token string, audiences []string) (*x5cPayload, err } verifiedChains, err := jwt.Headers[0].Certificates(x509.VerifyOptions{ - Roots: p.rootPool, + Roots: p.rootPool, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, }) if err != nil { return nil, errs.Wrap(http.StatusUnauthorized, err, @@ -234,7 +235,7 @@ func (p *X5C) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er // AuthorizeRenew returns an error if the renewal is disabled. func (p *X5C) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error { if p.claimer.IsDisableRenewal() { - return errs.Unauthorized("x5c.AuthorizeRenew; renew is disabled for x5c provisioner %s", p.GetID()) + return errs.Unauthorized("x5c.AuthorizeRenew; renew is disabled for x5c provisioner '%s'", p.GetName()) } return nil } @@ -242,7 +243,7 @@ func (p *X5C) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error // AuthorizeSSHSign returns the list of SignOption for a SignSSH request. func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) { if !p.claimer.IsSSHCAEnabled() { - return nil, errs.Unauthorized("x5c.AuthorizeSSHSign; sshCA is disabled for x5c provisioner %s", p.GetID()) + return nil, errs.Unauthorized("x5c.AuthorizeSSHSign; sshCA is disabled for x5c provisioner '%s'", p.GetName()) } claims, err := p.authorizeToken(token, p.audiences.SSHSign) diff --git a/authority/provisioner/x5c_test.go b/authority/provisioner/x5c_test.go index 5d288de5..2959f8c6 100644 --- a/authority/provisioner/x5c_test.go +++ b/authority/provisioner/x5c_test.go @@ -70,7 +70,7 @@ func TestX5C_Init(t *testing.T) { "fail/no-valid-root-certs": func(t *testing.T) ProvisionerValidateTest { return ProvisionerValidateTest{ p: &X5C{Name: "foo", Type: "bar", Roots: []byte("foo"), audiences: testAudiences}, - err: errors.Errorf("no x509 certificates found in roots attribute for provisioner foo"), + err: errors.Errorf("no x509 certificates found in roots attribute for provisioner 'foo'"), } }, "fail/invalid-duration": func(t *testing.T) ProvisionerValidateTest { @@ -79,7 +79,7 @@ func TestX5C_Init(t *testing.T) { p.Claims = &Claims{DefaultTLSDur: &Duration{0}} return ProvisionerValidateTest{ p: p, - err: errors.New("claims: DefaultTLSCertDuration must be greater than 0"), + err: errors.New("claims: MinTLSCertDuration must be greater than 0"), } }, "ok": func(t *testing.T) ProvisionerValidateTest { @@ -568,7 +568,7 @@ func TestX5C_AuthorizeRenew(t *testing.T) { return test{ p: p, code: http.StatusUnauthorized, - err: errors.Errorf("x5c.AuthorizeRenew; renew is disabled for x5c provisioner %s", p.GetID()), + err: errors.Errorf("x5c.AuthorizeRenew; renew is disabled for x5c provisioner '%s'", p.GetName()), } }, "ok": func(t *testing.T) test { @@ -624,7 +624,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { p: p, token: "foo", code: http.StatusUnauthorized, - err: errors.Errorf("x5c.AuthorizeSSHSign; sshCA is disabled for x5c provisioner %s", p.GetID()), + err: errors.Errorf("x5c.AuthorizeSSHSign; sshCA is disabled for x5c provisioner '%s'", p.GetName()), } }, "fail/invalid-token": func(t *testing.T) test { diff --git a/authority/provisioners.go b/authority/provisioners.go index 48f67dc8..d2581e76 100644 --- a/authority/provisioners.go +++ b/authority/provisioners.go @@ -1,19 +1,24 @@ package authority import ( + "context" "crypto/x509" "encoding/json" "fmt" - "github.com/smallstep/certificates/authority/mgmt" + "github.com/smallstep/certificates/authority/admin" + "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/errs" - "github.com/smallstep/certificates/linkedca" "go.step.sm/crypto/jose" + "go.step.sm/linkedca" + "gopkg.in/square/go-jose.v2/jwt" ) // GetEncryptedKey returns the JWE key corresponding to the given kid argument. func (a *Authority) GetEncryptedKey(kid string) (string, error) { + a.adminMutex.RLock() + defer a.adminMutex.RUnlock() key, ok := a.provisioners.LoadEncryptedKey(kid) if !ok { return "", errs.NotFound("encrypted key with kid %s was not found", kid) @@ -24,6 +29,8 @@ func (a *Authority) GetEncryptedKey(kid string) (string, error) { // GetProvisioners returns a map listing each provisioner and the JWK Key Set // with their public keys. func (a *Authority) GetProvisioners(cursor string, limit int) (provisioner.List, string, error) { + a.adminMutex.RLock() + defer a.adminMutex.RUnlock() provisioners, nextCursor := a.provisioners.Find(cursor, limit) return provisioners, nextCursor, nil } @@ -31,39 +38,320 @@ func (a *Authority) GetProvisioners(cursor string, limit int) (provisioner.List, // LoadProvisionerByCertificate returns an interface to the provisioner that // provisioned the certificate. func (a *Authority) LoadProvisionerByCertificate(crt *x509.Certificate) (provisioner.Interface, error) { + a.adminMutex.RLock() + defer a.adminMutex.RUnlock() p, ok := a.provisioners.LoadByCertificate(crt) if !ok { - return nil, errs.NotFound("provisioner not found") + return nil, admin.NewError(admin.ErrorNotFoundType, "unable to load provisioner from certificate") + } + return p, nil +} + +// LoadProvisionerByToken returns an interface to the provisioner that +// provisioned the token. +func (a *Authority) LoadProvisionerByToken(token *jwt.JSONWebToken, claims *jwt.Claims) (provisioner.Interface, error) { + a.adminMutex.RLock() + defer a.adminMutex.RUnlock() + p, ok := a.provisioners.LoadByToken(token, claims) + if !ok { + return nil, admin.NewError(admin.ErrorNotFoundType, "unable to load provisioner from token") } return p, nil } // LoadProvisionerByID returns an interface to the provisioner with the given ID. func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error) { + a.adminMutex.RLock() + defer a.adminMutex.RUnlock() p, ok := a.provisioners.Load(id) if !ok { - return nil, errs.NotFound("provisioner not found") + return nil, admin.NewError(admin.ErrorNotFoundType, "provisioner %s not found", id) } return p, nil } -func provisionerGetOptions(p *linkedca.Provisioner) *provisioner.Options { - return &provisioner.Options{ - X509: &provisioner.X509Options{ - Template: string(p.X509Template), - TemplateData: p.X509TemplateData, +// LoadProvisionerByName returns an interface to the provisioner with the given Name. +func (a *Authority) LoadProvisionerByName(name string) (provisioner.Interface, error) { + a.adminMutex.RLock() + defer a.adminMutex.RUnlock() + p, ok := a.provisioners.LoadByName(name) + if !ok { + return nil, admin.NewError(admin.ErrorNotFoundType, "provisioner %s not found", name) + } + return p, nil +} + +func (a *Authority) generateProvisionerConfig(ctx context.Context) (*provisioner.Config, error) { + // Merge global and configuration claims + claimer, err := provisioner.NewClaimer(a.config.AuthorityConfig.Claims, config.GlobalProvisionerClaims) + if err != nil { + return nil, err + } + // 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(ctx) + if err != nil { + return nil, err + } + return &provisioner.Config{ + Claims: claimer.Claims(), + Audiences: a.config.GetAudiences(), + DB: a.db, + SSHKeys: &provisioner.SSHKeys{ + UserKeys: sshKeys.UserKeys, + HostKeys: sshKeys.HostKeys, }, - SSH: &provisioner.SSHOptions{ - Template: string(p.SshTemplate), - TemplateData: p.SshTemplateData, + GetIdentityFunc: a.getIdentityFunc, + }, nil + +} + +// StoreProvisioner stores an provisioner.Interface to the authority. +func (a *Authority) StoreProvisioner(ctx context.Context, prov *linkedca.Provisioner) error { + a.adminMutex.Lock() + defer a.adminMutex.Unlock() + + certProv, err := ProvisionerToCertificates(prov) + if err != nil { + return admin.WrapErrorISE(err, + "error converting to certificates provisioner from linkedca provisioner") + } + + if _, ok := a.provisioners.LoadByName(prov.GetName()); ok { + return admin.NewError(admin.ErrorBadRequestType, + "provisioner with name %s already exists", prov.GetName()) + } + if _, ok := a.provisioners.LoadByTokenID(certProv.GetIDForToken()); ok { + return admin.NewError(admin.ErrorBadRequestType, + "provisioner with token ID %s already exists", certProv.GetIDForToken()) + } + + // Store to database -- this will set the ID. + if err := a.adminDB.CreateProvisioner(ctx, prov); err != nil { + return admin.WrapErrorISE(err, "error creating admin") + } + + // We need a new conversion that has the newly set ID. + certProv, err = ProvisionerToCertificates(prov) + if err != nil { + return admin.WrapErrorISE(err, + "error converting to certificates provisioner from linkedca provisioner") + } + + provisionerConfig, err := a.generateProvisionerConfig(ctx) + if err != nil { + return admin.WrapErrorISE(err, "error generating provisioner config") + } + + if err := certProv.Init(*provisionerConfig); err != nil { + return admin.WrapErrorISE(err, "error initializing provisioner %s", prov.Name) + } + + if err := a.provisioners.Store(certProv); err != nil { + if err := a.reloadAdminResources(ctx); err != nil { + return admin.WrapErrorISE(err, "error reloading admin resources on failed provisioner store") + } + return admin.WrapErrorISE(err, "error storing provisioner in authority cache") + } + return nil +} + +// UpdateProvisioner stores an provisioner.Interface to the authority. +func (a *Authority) UpdateProvisioner(ctx context.Context, nu *linkedca.Provisioner) error { + a.adminMutex.Lock() + defer a.adminMutex.Unlock() + + certProv, err := ProvisionerToCertificates(nu) + if err != nil { + return admin.WrapErrorISE(err, + "error converting to certificates provisioner from linkedca provisioner") + } + + provisionerConfig, err := a.generateProvisionerConfig(ctx) + if err != nil { + return admin.WrapErrorISE(err, "error generating provisioner config") + } + + if err := certProv.Init(*provisionerConfig); err != nil { + return admin.WrapErrorISE(err, "error initializing provisioner %s", nu.Name) + } + + if err := a.provisioners.Update(certProv); err != nil { + return admin.WrapErrorISE(err, "error updating provisioner '%s' in authority cache", nu.Name) + } + if err := a.adminDB.UpdateProvisioner(ctx, nu); err != nil { + if err := a.reloadAdminResources(ctx); err != nil { + return admin.WrapErrorISE(err, "error reloading admin resources on failed provisioner update") + } + return admin.WrapErrorISE(err, "error updating provisioner '%s'", nu.Name) + } + return nil +} + +// RemoveProvisioner removes an provisioner.Interface from the authority. +func (a *Authority) RemoveProvisioner(ctx context.Context, id string) error { + a.adminMutex.Lock() + defer a.adminMutex.Unlock() + + p, ok := a.provisioners.Load(id) + if !ok { + return admin.NewError(admin.ErrorBadRequestType, + "provisioner %s not found", id) + } + + provName, provID := p.GetName(), p.GetID() + // Validate + // - Check that there will be SUPER_ADMINs that remain after we + // remove this provisioner. + if a.admins.SuperCount() == a.admins.SuperCountByProvisioner(provName) { + return admin.NewError(admin.ErrorBadRequestType, + "cannot remove provisioner %s because no super admins will remain", provName) + } + + // Delete all admins associated with the provisioner. + admins, ok := a.admins.LoadByProvisioner(provName) + if ok { + for _, adm := range admins { + if err := a.removeAdmin(ctx, adm.Id); err != nil { + return admin.WrapErrorISE(err, "error deleting admin %s, as part of provisioner %s deletion", adm.Subject, provName) + } + } + } + + // Remove provisioner from authority caches. + if err := a.provisioners.Remove(provID); err != nil { + return admin.WrapErrorISE(err, "error removing admin from authority cache") + } + // Remove provisioner from database. + if err := a.adminDB.DeleteProvisioner(ctx, provID); err != nil { + if err := a.reloadAdminResources(ctx); err != nil { + return admin.WrapErrorISE(err, "error reloading admin resources on failed provisioner remove") + } + return admin.WrapErrorISE(err, "error deleting provisioner %s", provName) + } + return nil +} + +func CreateFirstProvisioner(ctx context.Context, db admin.DB, password string) (*linkedca.Provisioner, error) { + jwk, jwe, err := jose.GenerateDefaultKeyPair([]byte(password)) + if err != nil { + return nil, admin.WrapErrorISE(err, "error generating JWK key pair") + } + + jwkPubBytes, err := jwk.MarshalJSON() + if err != nil { + return nil, admin.WrapErrorISE(err, "error marshaling JWK") + } + jwePrivStr, err := jwe.CompactSerialize() + if err != nil { + return nil, admin.WrapErrorISE(err, "error serializing JWE") + } + + p := &linkedca.Provisioner{ + Name: "Admin JWK", + Type: linkedca.Provisioner_JWK, + Details: &linkedca.ProvisionerDetails{ + Data: &linkedca.ProvisionerDetails_JWK{ + JWK: &linkedca.JWKProvisioner{ + PublicKey: jwkPubBytes, + EncryptedPrivateKey: []byte(jwePrivStr), + }, + }, + }, + Claims: &linkedca.Claims{ + X509: &linkedca.X509Claims{ + Enabled: true, + Durations: &linkedca.Durations{ + Default: "5m", + }, + }, }, } + if err := db.CreateProvisioner(ctx, p); err != nil { + return nil, admin.WrapErrorISE(err, "error creating provisioner") + } + return p, nil +} + +func ValidateClaims(c *linkedca.Claims) error { + if c == nil { + return nil + } + if c.X509 != nil { + if c.X509.Durations != nil { + if err := ValidateDurations(c.X509.Durations); err != nil { + return err + } + } + } + if c.Ssh != nil { + if c.Ssh.UserDurations != nil { + if err := ValidateDurations(c.Ssh.UserDurations); err != nil { + return err + } + } + if c.Ssh.HostDurations != nil { + if err := ValidateDurations(c.Ssh.HostDurations); err != nil { + return err + } + } + } + return nil +} + +func ValidateDurations(d *linkedca.Durations) error { + var ( + err error + min, max, def *provisioner.Duration + ) + + if d.Min != "" { + min, err = provisioner.NewDuration(d.Min) + if err != nil { + return admin.WrapError(admin.ErrorBadRequestType, err, "min duration '%s' is invalid", d.Min) + } + if min.Value() < 0 { + return admin.WrapError(admin.ErrorBadRequestType, err, "min duration '%s' cannot be less than 0", d.Min) + } + } + if d.Max != "" { + max, err = provisioner.NewDuration(d.Max) + if err != nil { + return admin.WrapError(admin.ErrorBadRequestType, err, "max duration '%s' is invalid", d.Max) + } + if max.Value() < 0 { + return admin.WrapError(admin.ErrorBadRequestType, err, "max duration '%s' cannot be less than 0", d.Max) + } + } + if d.Default != "" { + def, err = provisioner.NewDuration(d.Default) + if err != nil { + return admin.WrapError(admin.ErrorBadRequestType, err, "default duration '%s' is invalid", d.Default) + } + if def.Value() < 0 { + return admin.WrapError(admin.ErrorBadRequestType, err, "default duration '%s' cannot be less than 0", d.Default) + } + } + if d.Min != "" && d.Max != "" && min.Value() > max.Value() { + return admin.NewError(admin.ErrorBadRequestType, + "min duration '%s' cannot be greater than max duration '%s'", d.Min, d.Max) + } + if d.Min != "" && d.Default != "" && min.Value() > def.Value() { + return admin.NewError(admin.ErrorBadRequestType, + "min duration '%s' cannot be greater than default duration '%s'", d.Min, d.Default) + } + if d.Default != "" && d.Max != "" && min.Value() > def.Value() { + return admin.NewError(admin.ErrorBadRequestType, + "default duration '%s' cannot be greater than max duration '%s'", d.Default, d.Max) + } + return nil } func provisionerListToCertificates(l []*linkedca.Provisioner) (provisioner.List, error) { var nu provisioner.List for _, p := range l { - certProv, err := provisionerToCertificates(p) + certProv, err := ProvisionerToCertificates(p) if err != nil { return nil, err } @@ -72,9 +360,87 @@ func provisionerListToCertificates(l []*linkedca.Provisioner) (provisioner.List, return nu, nil } -// provisionerToCertificates converts the landlord provisioner type to the open source -// provisioner type. -func provisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface, error) { +func optionsToCertificates(p *linkedca.Provisioner) *provisioner.Options { + ops := &provisioner.Options{ + X509: &provisioner.X509Options{}, + SSH: &provisioner.SSHOptions{}, + } + if p.X509Template != nil { + ops.X509.Template = string(p.X509Template.Template) + ops.X509.TemplateData = p.X509Template.Data + } + if p.SshTemplate != nil { + ops.SSH.Template = string(p.SshTemplate.Template) + ops.SSH.TemplateData = p.SshTemplate.Data + } + return ops +} + +func durationsToCertificates(d *linkedca.Durations) (min, max, def *provisioner.Duration, err error) { + if len(d.Min) > 0 { + min, err = provisioner.NewDuration(d.Min) + if err != nil { + return nil, nil, nil, admin.WrapErrorISE(err, "error parsing minimum duration '%s'", d.Min) + } + } + if len(d.Max) > 0 { + max, err = provisioner.NewDuration(d.Max) + if err != nil { + return nil, nil, nil, admin.WrapErrorISE(err, "error parsing maximum duration '%s'", d.Max) + } + } + if len(d.Default) > 0 { + def, err = provisioner.NewDuration(d.Default) + if err != nil { + return nil, nil, nil, admin.WrapErrorISE(err, "error parsing default duration '%s'", d.Default) + } + } + return +} + +// claimsToCertificates converts the linkedca provisioner claims type to the +// certifictes claims type. +func claimsToCertificates(c *linkedca.Claims) (*provisioner.Claims, error) { + if c == nil { + return nil, nil + } + + pc := &provisioner.Claims{ + DisableRenewal: &c.DisableRenewal, + } + + var err error + + if xc := c.X509; xc != nil { + if d := xc.Durations; d != nil { + pc.MinTLSDur, pc.MaxTLSDur, pc.DefaultTLSDur, err = durationsToCertificates(d) + if err != nil { + return nil, err + } + } + } + if sc := c.Ssh; sc != nil { + pc.EnableSSHCA = &sc.Enabled + if d := sc.UserDurations; d != nil { + pc.MinUserSSHDur, pc.MaxUserSSHDur, pc.DefaultUserSSHDur, err = durationsToCertificates(d) + if err != nil { + return nil, err + } + } + if d := sc.HostDurations; d != nil { + pc.MinHostSSHDur, pc.MaxHostSSHDur, pc.DefaultHostSSHDur, err = durationsToCertificates(d) + if err != nil { + return nil, err + } + } + } + + return pc, nil +} + +// ProvisionerToCertificates converts the linkedca provisioner type to the certificates provisioner +// interface. +func ProvisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface, error) { claims, err := claimsToCertificates(p.Claims) if err != nil { return nil, err @@ -85,9 +451,10 @@ func provisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface, return nil, fmt.Errorf("provisioner does not have any details") } + options := optionsToCertificates(p) + switch d := details.(type) { case *linkedca.ProvisionerDetails_JWK: - fmt.Printf("d = %+v\n", d) jwk := new(jose.JSONWebKey) if err := json.Unmarshal(d.JWK.PublicKey, &jwk); err != nil { return nil, err @@ -99,153 +466,136 @@ func provisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface, Key: jwk, EncryptedKey: string(d.JWK.EncryptedPrivateKey), Claims: claims, - Options: provisionerGetOptions(p), + Options: options, + }, nil + case *linkedca.ProvisionerDetails_X5C: + var roots []byte + for i, root := range d.X5C.GetRoots() { + if i > 0 { + roots = append(roots, '\n') + } + roots = append(roots, root...) + } + return &provisioner.X5C{ + ID: p.Id, + Type: p.Type.String(), + Name: p.Name, + Roots: roots, + Claims: claims, + Options: options, + }, nil + case *linkedca.ProvisionerDetails_K8SSA: + var publicKeys []byte + for i, k := range d.K8SSA.GetPublicKeys() { + if i > 0 { + publicKeys = append(publicKeys, '\n') + } + publicKeys = append(publicKeys, k...) + } + return &provisioner.K8sSA{ + ID: p.Id, + Type: p.Type.String(), + Name: p.Name, + PubKeys: publicKeys, + Claims: claims, + Options: options, + }, nil + case *linkedca.ProvisionerDetails_SSHPOP: + return &provisioner.SSHPOP{ + ID: p.Id, + Type: p.Type.String(), + Name: p.Name, + Claims: claims, + }, nil + case *linkedca.ProvisionerDetails_ACME: + cfg := d.ACME + return &provisioner.ACME{ + ID: p.Id, + Type: p.Type.String(), + Name: p.Name, + ForceCN: cfg.ForceCn, + Claims: claims, + Options: options, + }, nil + case *linkedca.ProvisionerDetails_OIDC: + cfg := d.OIDC + return &provisioner.OIDC{ + ID: p.Id, + 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 *linkedca.ProvisionerDetails_AWS: + cfg := d.AWS + instanceAge, err := parseInstanceAge(cfg.InstanceAge) + if err != nil { + return nil, err + } + return &provisioner.AWS{ + ID: p.Id, + Type: p.Type.String(), + Name: p.Name, + Accounts: cfg.Accounts, + DisableCustomSANs: cfg.DisableCustomSans, + DisableTrustOnFirstUse: cfg.DisableTrustOnFirstUse, + InstanceAge: instanceAge, + Claims: claims, + Options: options, + }, nil + case *linkedca.ProvisionerDetails_GCP: + cfg := d.GCP + instanceAge, err := parseInstanceAge(cfg.InstanceAge) + if err != nil { + return nil, err + } + return &provisioner.GCP{ + ID: p.Id, + Type: p.Type.String(), + Name: p.Name, + ServiceAccounts: cfg.ServiceAccounts, + ProjectIDs: cfg.ProjectIds, + DisableCustomSANs: cfg.DisableCustomSans, + DisableTrustOnFirstUse: cfg.DisableTrustOnFirstUse, + InstanceAge: instanceAge, + Claims: claims, + Options: options, + }, nil + case *linkedca.ProvisionerDetails_Azure: + cfg := d.Azure + return &provisioner.Azure{ + ID: p.Id, + 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_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) +func parseInstanceAge(age string) (provisioner.Duration, error) { + var instanceAge provisioner.Duration + if age != "" { + iap, err := provisioner.NewDuration(age) if err != nil { - return nil, mgmt.WrapErrorISE(err, "error parsing %s %s from claims", k, v.durStr) + return instanceAge, err } + instanceAge = *iap } - 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 + return instanceAge, nil } diff --git a/authority/provisioners_test.go b/authority/provisioners_test.go index 94b2d715..3975031b 100644 --- a/authority/provisioners_test.go +++ b/authority/provisioners_test.go @@ -56,7 +56,7 @@ func TestGetEncryptedKey(t *testing.T) { } } else { if assert.Nil(t, tc.err) { - val, ok := tc.a.provisioners.Load("max:" + tc.kid) + val, ok := tc.a.provisioners.Load("mike:" + tc.kid) assert.Fatal(t, ok) p, ok := val.(*provisioner.JWK) assert.Fatal(t, ok) diff --git a/authority/ssh_test.go b/authority/ssh_test.go index 1662260c..8ca26af0 100644 --- a/authority/ssh_test.go +++ b/authority/ssh_test.go @@ -590,69 +590,6 @@ func TestSSHConfig_Validate(t *testing.T) { } } -func TestSSHPublicKey_Validate(t *testing.T) { - key, err := jose.GenerateJWK("EC", "P-256", "", "sig", "", 0) - assert.FatalError(t, err) - - type fields struct { - Type string - Federated bool - Key jose.JSONWebKey - } - tests := []struct { - name string - fields fields - wantErr bool - }{ - {"user", fields{"user", true, key.Public()}, false}, - {"host", fields{"host", false, key.Public()}, false}, - {"empty", fields{"", true, key.Public()}, true}, - {"badType", fields{"bad", false, key.Public()}, true}, - {"badKey", fields{"user", false, *key}, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - k := &SSHPublicKey{ - Type: tt.fields.Type, - Federated: tt.fields.Federated, - Key: tt.fields.Key, - } - if err := k.Validate(); (err != nil) != tt.wantErr { - t.Errorf("SSHPublicKey.Validate() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestSSHPublicKey_PublicKey(t *testing.T) { - key, err := jose.GenerateJWK("EC", "P-256", "", "sig", "", 0) - assert.FatalError(t, err) - pub, err := ssh.NewPublicKey(key.Public().Key) - assert.FatalError(t, err) - - type fields struct { - publicKey ssh.PublicKey - } - tests := []struct { - name string - fields fields - want ssh.PublicKey - }{ - {"ok", fields{pub}, pub}, - {"nil", fields{nil}, nil}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - k := &SSHPublicKey{ - publicKey: tt.fields.publicKey, - } - if got := k.PublicKey(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("SSHPublicKey.PublicKey() = %v, want %v", got, tt.want) - } - }) - } -} - func TestAuthority_GetSSHBastion(t *testing.T) { bastion := &Bastion{ Hostname: "bastion.local", diff --git a/authority/tls.go b/authority/tls.go index 1c8b3b8c..4c3420df 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -360,10 +360,9 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error } // This method will also validate the audiences for JWK provisioners. - var ok bool - p, ok = a.provisioners.LoadByToken(token, &claims.Claims) - if !ok { - return errs.InternalServer("authority.Revoke; provisioner not found", opts...) + p, err = a.LoadProvisionerByToken(token, &claims.Claims) + if err != nil { + return err } rci.ProvisionerID = p.GetID() rci.TokenID, err = p.GetTokenID(revokeOpts.OTT) diff --git a/authority/tls_test.go b/authority/tls_test.go index 4c936f0c..cdd4c59a 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -656,7 +656,7 @@ func TestAuthority_Renew(t *testing.T) { "fail/unauthorized": func() (*renewTest, error) { return &renewTest{ cert: certNoRenew, - err: errors.New("authority.Rekey: authority.authorizeRenew: jwk.AuthorizeRenew; renew is disabled for jwk provisioner dev:IMi94WBNI6gP5cNHXlZYNUzvMjGdHyBRmFoo-lCEaqk"), + err: errors.New("authority.Rekey: authority.authorizeRenew: jwk.AuthorizeRenew; renew is disabled for jwk provisioner 'dev'"), code: http.StatusUnauthorized, }, nil }, @@ -856,7 +856,7 @@ func TestAuthority_Rekey(t *testing.T) { "fail/unauthorized": func() (*renewTest, error) { return &renewTest{ cert: certNoRenew, - err: errors.New("authority.Rekey: authority.authorizeRenew: jwk.AuthorizeRenew; renew is disabled for jwk provisioner dev:IMi94WBNI6gP5cNHXlZYNUzvMjGdHyBRmFoo-lCEaqk"), + err: errors.New("authority.Rekey: authority.authorizeRenew: jwk.AuthorizeRenew; renew is disabled for jwk provisioner 'dev'"), code: http.StatusUnauthorized, }, nil }, diff --git a/ca/adminClient.go b/ca/adminClient.go index ad708146..da2ff566 100644 --- a/ca/adminClient.go +++ b/ca/adminClient.go @@ -2,29 +2,42 @@ package ca import ( "bytes" + "crypto/x509" "encoding/json" "io" "net/http" "net/url" "path" "strconv" + "time" "github.com/pkg/errors" - "github.com/smallstep/certificates/authority/mgmt" - mgmtAPI "github.com/smallstep/certificates/authority/mgmt/api" + "github.com/smallstep/certificates/authority/admin" + adminAPI "github.com/smallstep/certificates/authority/admin/api" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/errs" - "github.com/smallstep/certificates/linkedca" + "go.step.sm/cli-utils/token" + "go.step.sm/cli-utils/token/provision" + "go.step.sm/crypto/jose" + "go.step.sm/crypto/randutil" + "go.step.sm/linkedca" + "google.golang.org/protobuf/encoding/protojson" ) var adminURLPrefix = "admin" // AdminClient implements an HTTP client for the CA server. type AdminClient struct { - client *uaClient - endpoint *url.URL - retryFunc RetryFunc - opts []ClientOption + client *uaClient + endpoint *url.URL + retryFunc RetryFunc + opts []ClientOption + x5cJWK *jose.JSONWebKey + x5cCertFile string + x5cCertStrs []string + x5cCert *x509.Certificate + x5cIssuer string + x5cSubject string } // NewAdminClient creates a new AdminClient with the given endpoint and options. @@ -44,13 +57,45 @@ func NewAdminClient(endpoint string, opts ...ClientOption) (*AdminClient, error) } return &AdminClient{ - client: newClient(tr), - endpoint: u, - retryFunc: o.retryFunc, - opts: opts, + client: newClient(tr), + endpoint: u, + retryFunc: o.retryFunc, + opts: opts, + x5cJWK: o.x5cJWK, + x5cCertFile: o.x5cCertFile, + x5cCertStrs: o.x5cCertStrs, + x5cCert: o.x5cCert, + x5cIssuer: o.x5cIssuer, + x5cSubject: o.x5cSubject, }, nil } +func (c *AdminClient) generateAdminToken(path string) (string, error) { + // A random jwt id will be used to identify duplicated tokens + jwtID, err := randutil.Hex(64) // 256 bits + if err != nil { + return "", err + } + + now := time.Now() + tokOptions := []token.Options{ + token.WithJWTID(jwtID), + token.WithKid(c.x5cJWK.KeyID), + token.WithIssuer(c.x5cIssuer), + token.WithAudience(path), + token.WithValidity(now, now.Add(token.DefaultValidity)), + token.WithX5CCerts(c.x5cCertStrs), + } + + tok, err := provision.New(c.x5cSubject, tokOptions...) + if err != nil { + return "", err + } + + return tok.SignedString(c.x5cJWK.Algorithm, c.x5cJWK.Key) + +} + func (c *AdminClient) retryOnError(r *http.Response) bool { if c.retryFunc != nil { if c.retryFunc(r.StatusCode) { @@ -138,7 +183,7 @@ func WithAdminLimit(limit int) AdminOption { } // GetAdminsPaginate returns a page from the the GET /admin/admins request to the CA. -func (c *AdminClient) GetAdminsPaginate(opts ...AdminOption) (*mgmtAPI.GetAdminsResponse, error) { +func (c *AdminClient) GetAdminsPaginate(opts ...AdminOption) (*adminAPI.GetAdminsResponse, error) { var retried bool o := new(adminOptions) if err := o.apply(opts); err != nil { @@ -148,8 +193,17 @@ func (c *AdminClient) GetAdminsPaginate(opts ...AdminOption) (*mgmtAPI.GetAdmins Path: "/admin/admins", RawQuery: o.rawQuery(), }) + tok, err := c.generateAdminToken(u.Path) + if err != nil { + return nil, errors.Wrapf(err, "error generating admin token") + } + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, errors.Wrapf(err, "create GET %s request failed", u) + } + req.Header.Add("Authorization", tok) retry: - resp, err := c.client.Get(u.String()) + resp, err := c.client.Do(req) if err != nil { return nil, errors.Wrapf(err, "client GET %s failed", u) } @@ -160,7 +214,7 @@ retry: } return nil, readAdminError(resp.Body) } - var body = new(mgmtAPI.GetAdminsResponse) + var body = new(adminAPI.GetAdminsResponse) if err := readJSON(resp.Body, body); err != nil { return nil, errors.Wrapf(err, "error reading %s", u) } @@ -184,19 +238,27 @@ func (c *AdminClient) GetAdmins(opts ...AdminOption) ([]*linkedca.Admin, error) } cursor = resp.NextCursor } - return admins, nil } // CreateAdmin performs the POST /admin/admins request to the CA. -func (c *AdminClient) CreateAdmin(req *mgmtAPI.CreateAdminRequest) (*linkedca.Admin, error) { +func (c *AdminClient) CreateAdmin(createAdminRequest *adminAPI.CreateAdminRequest) (*linkedca.Admin, error) { var retried bool - body, err := json.Marshal(req) + body, err := json.Marshal(createAdminRequest) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request") } u := c.endpoint.ResolveReference(&url.URL{Path: "/admin/admins"}) + tok, err := c.generateAdminToken(u.Path) + if err != nil { + return nil, errors.Wrapf(err, "error generating admin token") + } + req, err := http.NewRequest("POST", u.String(), bytes.NewReader(body)) + if err != nil { + return nil, errors.Wrapf(err, "create GET %s request failed", u) + } + req.Header.Add("Authorization", tok) retry: - resp, err := c.client.Post(u.String(), "application/json", bytes.NewReader(body)) + resp, err := c.client.Do(req) if err != nil { return nil, errors.Wrapf(err, "client POST %s failed", u) } @@ -218,10 +280,15 @@ retry: func (c *AdminClient) RemoveAdmin(id string) error { var retried bool u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "admins", id)}) + tok, err := c.generateAdminToken(u.Path) + if err != nil { + return errors.Wrapf(err, "error generating admin token") + } req, err := http.NewRequest("DELETE", u.String(), nil) if err != nil { return errors.Wrapf(err, "create DELETE %s request failed", u) } + req.Header.Add("Authorization", tok) retry: resp, err := c.client.Do(req) if err != nil { @@ -238,17 +305,22 @@ retry: } // UpdateAdmin performs the PUT /admin/admins/{id} request to the CA. -func (c *AdminClient) UpdateAdmin(id string, uar *mgmtAPI.UpdateAdminRequest) (*linkedca.Admin, error) { +func (c *AdminClient) UpdateAdmin(id string, uar *adminAPI.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(adminURLPrefix, "admins", id)}) + tok, err := c.generateAdminToken(u.Path) + if err != nil { + return nil, errors.Wrapf(err, "error generating admin token") + } req, err := http.NewRequest("PATCH", u.String(), bytes.NewReader(body)) if err != nil { return nil, errors.Wrapf(err, "create PUT %s request failed", u) } + req.Header.Add("Authorization", tok) retry: resp, err := c.client.Do(req) if err != nil { @@ -268,12 +340,35 @@ retry: return adm, nil } -// GetProvisionerByName performs the GET /admin/provisioners/{name} request to the CA. -func (c *AdminClient) GetProvisionerByName(name string) (*linkedca.Provisioner, error) { +// GetProvisioner performs the GET /admin/provisioners/{name} request to the CA. +func (c *AdminClient) GetProvisioner(opts ...ProvisionerOption) (*linkedca.Provisioner, error) { var retried bool - u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", name)}) + o := new(provisionerOptions) + if err := o.apply(opts); err != nil { + return nil, err + } + var u *url.URL + if len(o.id) > 0 { + u = c.endpoint.ResolveReference(&url.URL{ + Path: "/admin/provisioners/id", + RawQuery: o.rawQuery(), + }) + } else if len(o.name) > 0 { + u = c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", o.name)}) + } else { + return nil, errors.New("must set either name or id in method options") + } + tok, err := c.generateAdminToken(u.Path) + if err != nil { + return nil, errors.Wrapf(err, "error generating admin token") + } + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, errors.Wrapf(err, "create PUT %s request failed", u) + } + req.Header.Add("Authorization", tok) retry: - resp, err := c.client.Get(u.String()) + resp, err := c.client.Do(req) if err != nil { return nil, errors.Wrapf(err, "client GET %s failed", u) } @@ -285,14 +380,14 @@ retry: return nil, readAdminError(resp.Body) } var prov = new(linkedca.Provisioner) - if err := readJSON(resp.Body, prov); err != nil { + if err := readProtoJSON(resp.Body, prov); err != nil { return nil, errors.Wrapf(err, "error reading %s", u) } return prov, nil } // GetProvisionersPaginate performs the GET /admin/provisioners request to the CA. -func (c *AdminClient) GetProvisionersPaginate(opts ...ProvisionerOption) (*mgmtAPI.GetProvisionersResponse, error) { +func (c *AdminClient) GetProvisionersPaginate(opts ...ProvisionerOption) (*adminAPI.GetProvisionersResponse, error) { var retried bool o := new(provisionerOptions) if err := o.apply(opts); err != nil { @@ -302,8 +397,17 @@ func (c *AdminClient) GetProvisionersPaginate(opts ...ProvisionerOption) (*mgmtA Path: "/admin/provisioners", RawQuery: o.rawQuery(), }) + tok, err := c.generateAdminToken(u.Path) + if err != nil { + return nil, errors.Wrapf(err, "error generating admin token") + } + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, errors.Wrapf(err, "create PUT %s request failed", u) + } + req.Header.Add("Authorization", tok) retry: - resp, err := c.client.Get(u.String()) + resp, err := c.client.Do(req) if err != nil { return nil, errors.Wrapf(err, "client GET %s failed", u) } @@ -314,7 +418,7 @@ retry: } return nil, readAdminError(resp.Body) } - var body = new(mgmtAPI.GetProvisionersResponse) + var body = new(adminAPI.GetProvisionersResponse) if err := readJSON(resp.Body, body); err != nil { return nil, errors.Wrapf(err, "error reading %s", u) } @@ -338,17 +442,39 @@ func (c *AdminClient) GetProvisioners(opts ...AdminOption) (provisioner.List, er } cursor = resp.NextCursor } - return provs, nil } // 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(adminURLPrefix, "provisioners", name)}) +func (c *AdminClient) RemoveProvisioner(opts ...ProvisionerOption) error { + var ( + u *url.URL + retried bool + ) + + o := new(provisionerOptions) + if err := o.apply(opts); err != nil { + return err + } + + if len(o.id) > 0 { + u = c.endpoint.ResolveReference(&url.URL{ + Path: path.Join(adminURLPrefix, "provisioners/id"), + RawQuery: o.rawQuery(), + }) + } else if len(o.name) > 0 { + u = c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", o.name)}) + } else { + return errors.New("must set either name or id in method options") + } + tok, err := c.generateAdminToken(u.Path) + if err != nil { + return errors.Wrapf(err, "error generating admin token") + } req, err := http.NewRequest("DELETE", u.String(), nil) if err != nil { return errors.Wrapf(err, "create DELETE %s request failed", u) } + req.Header.Add("Authorization", tok) retry: resp, err := c.client.Do(req) if err != nil { @@ -367,13 +493,22 @@ retry: // CreateProvisioner performs the POST /admin/provisioners request to the CA. func (c *AdminClient) CreateProvisioner(prov *linkedca.Provisioner) (*linkedca.Provisioner, error) { var retried bool - body, err := json.Marshal(prov) + body, err := protojson.Marshal(prov) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request") } - u := c.endpoint.ResolveReference(&url.URL{Path: "/admin/provisioners"}) + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners")}) + tok, err := c.generateAdminToken(u.Path) + if err != nil { + return nil, errors.Wrapf(err, "error generating admin token") + } + req, err := http.NewRequest("POST", u.String(), bytes.NewReader(body)) + if err != nil { + return nil, errors.Wrapf(err, "create POST %s request failed", u) + } + req.Header.Add("Authorization", tok) retry: - resp, err := c.client.Post(u.String(), "application/json", bytes.NewReader(body)) + resp, err := c.client.Do(req) if err != nil { return nil, errors.Wrapf(err, "client POST %s failed", u) } @@ -385,48 +520,49 @@ retry: return nil, readAdminError(resp.Body) } var nuProv = new(linkedca.Provisioner) - if err := readJSON(resp.Body, nuProv); err != nil { + if err := readProtoJSON(resp.Body, nuProv); err != nil { return nil, errors.Wrapf(err, "error reading %s", u) } return nuProv, nil } -// UpdateProvisioner performs the PUT /admin/provisioners/{id} request to the CA. -func (c *AdminClient) UpdateProvisioner(id string, upr *mgmtAPI.UpdateProvisionerRequest) (*linkedca.Provisioner, error) { +// UpdateProvisioner performs the PUT /admin/provisioners/{name} request to the CA. +func (c *AdminClient) UpdateProvisioner(name string, prov *linkedca.Provisioner) error { var retried bool - body, err := json.Marshal(upr) + body, err := protojson.Marshal(prov) if err != nil { - return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request") + return errs.Wrap(http.StatusInternalServerError, err, "error marshaling request") + } + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", name)}) + tok, err := c.generateAdminToken(u.Path) + if err != nil { + return errors.Wrapf(err, "error generating admin token") } - 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) + return errors.Wrapf(err, "create PUT %s request failed", u) } + req.Header.Add("Authorization", tok) retry: resp, err := c.client.Do(req) if err != nil { - return nil, errors.Wrapf(err, "client PUT %s failed", u) + return errors.Wrapf(err, "client PUT %s failed", u) } if resp.StatusCode >= 400 { if !retried && c.retryOnError(resp) { retried = true goto retry } - return nil, readAdminError(resp.Body) + return readAdminError(resp.Body) } - 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 + return nil } func readAdminError(r io.ReadCloser) error { defer r.Close() - mgmtErr := new(mgmt.Error) - if err := json.NewDecoder(r).Decode(mgmtErr); err != nil { + adminErr := new(admin.Error) + if err := json.NewDecoder(r).Decode(adminErr); err != nil { return err } - return errors.New(mgmtErr.Message) + return errors.New(adminErr.Message) } diff --git a/ca/ca.go b/ca/ca.go index 1882e8a6..1ff265bf 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -17,9 +17,8 @@ import ( acmeNoSQL "github.com/smallstep/certificates/acme/db/nosql" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority" + adminAPI "github.com/smallstep/certificates/authority/admin/api" "github.com/smallstep/certificates/authority/config" - "github.com/smallstep/certificates/authority/mgmt" - mgmtAPI "github.com/smallstep/certificates/authority/mgmt/api" "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/monitoring" @@ -79,11 +78,12 @@ func WithDatabase(db db.AuthDB) Option { // CA is the type used to build the complete certificate authority. It builds // the HTTP server, set ups the middlewares and the HTTP handlers. type CA struct { - auth *authority.Authority - config *config.Config - srv *server.Server - opts *options - renewer *TLSRenewer + auth *authority.Authority + config *config.Config + srv *server.Server + insecureSrv *server.Server + opts *options + renewer *TLSRenewer } // New creates and initializes the CA with the given configuration and options. @@ -130,6 +130,9 @@ func (ca *CA) Init(config *config.Config) (*CA, error) { mux := chi.NewRouter() handler := http.Handler(mux) + insecureMux := chi.NewRouter() + insecureHandler := http.Handler(insecureMux) + // Add regular CA api endpoints in / and /1.0 routerHandler := api.New(auth) routerHandler.Route(mux) @@ -154,7 +157,7 @@ func (ca *CA) Init(config *config.Config) (*CA, error) { if config.DB == nil { acmeDB = nil } else { - acmeDB, err = acmeNoSQL.New(auth.GetDatabase().(nosql.DB), mgmt.DefaultAuthorityID) + acmeDB, err = acmeNoSQL.New(auth.GetDatabase().(nosql.DB)) if err != nil { return nil, errors.Wrap(err, "error configuring ACME DB interface") } @@ -176,12 +179,14 @@ func (ca *CA) Init(config *config.Config) (*CA, error) { }) // Admin API Router - adminDB := auth.GetAdminDatabase() - if adminDB != nil { - mgmtHandler := mgmtAPI.NewHandler(auth) - mux.Route("/admin", func(r chi.Router) { - mgmtHandler.Route(r) - }) + if config.AuthorityConfig.EnableAdmin { + adminDB := auth.GetAdminDatabase() + if adminDB != nil { + adminHandler := adminAPI.NewHandler(auth) + mux.Route("/admin", func(r chi.Router) { + adminHandler.Route(r) + }) + } } if ca.shouldServeSCEPEndpoints() { diff --git a/ca/client.go b/ca/client.go index 3a3350ac..8997fbd5 100644 --- a/ca/client.go +++ b/ca/client.go @@ -10,8 +10,10 @@ import ( "crypto/tls" "crypto/x509" "crypto/x509/pkix" + "encoding/asn1" "encoding/hex" "encoding/json" + "encoding/pem" "io" "io/ioutil" "net/http" @@ -28,10 +30,13 @@ import ( "github.com/smallstep/certificates/ca/identity" "github.com/smallstep/certificates/errs" "go.step.sm/cli-utils/config" + "go.step.sm/crypto/jose" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x509util" "golang.org/x/net/http2" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" "gopkg.in/square/go-jose.v2/jwt" ) @@ -108,6 +113,12 @@ type clientOptions struct { certificate tls.Certificate getClientCertificate func(*tls.CertificateRequestInfo) (*tls.Certificate, error) retryFunc RetryFunc + x5cJWK *jose.JSONWebKey + x5cCertFile string + x5cCertStrs []string + x5cCert *x509.Certificate + x5cIssuer string + x5cSubject string } func (o *clientOptions) apply(opts []ClientOption) (err error) { @@ -266,9 +277,66 @@ func WithCABundle(bundle []byte) ClientOption { // WithCertificate will set the given certificate as the TLS client certificate // in the client. -func WithCertificate(crt tls.Certificate) ClientOption { +func WithCertificate(cert tls.Certificate) ClientOption { return func(o *clientOptions) error { - o.certificate = crt + o.certificate = cert + return nil + } +} + +var ( + stepOIDRoot = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64} + stepOIDProvisioner = append(asn1.ObjectIdentifier(nil), append(stepOIDRoot, 1)...) +) + +type stepProvisionerASN1 struct { + Type int + Name []byte + CredentialID []byte + KeyValuePairs []string `asn1:"optional,omitempty"` +} + +// WithAdminX5C will set the given file as the X5C certificate for use +// by the client. +func WithAdminX5C(certs []*x509.Certificate, key interface{}, passwordFile string) ClientOption { + return func(o *clientOptions) error { + // Get private key from given key file + var ( + err error + opts []jose.Option + ) + if len(passwordFile) != 0 { + opts = append(opts, jose.WithPasswordFile(passwordFile)) + } + blk, err := pemutil.Serialize(key) + if err != nil { + return errors.Wrap(err, "error serializing private key") + } + o.x5cJWK, err = jose.ParseKey(pem.EncodeToMemory(blk), opts...) + if err != nil { + return err + } + o.x5cCertStrs, err = jose.ValidateX5C(certs, o.x5cJWK.Key) + if err != nil { + return errors.Wrap(err, "error validating x5c certificate chain and key for use in x5c header") + } + + o.x5cCert = certs[0] + o.x5cSubject = o.x5cCert.Subject.CommonName + + for _, e := range o.x5cCert.Extensions { + if e.Id.Equal(stepOIDProvisioner) { + var provisioner stepProvisionerASN1 + if _, err := asn1.Unmarshal(e.Value, &provisioner); err != nil { + return errors.Wrap(err, "error unmarshaling provisioner OID from certificate") + } + o.x5cIssuer = string(provisioner.Name) + } + } + if len(o.x5cIssuer) == 0 { + return errors.New("provisioner extension not found in certificate") + } + return nil } } @@ -372,6 +440,8 @@ type ProvisionerOption func(o *provisionerOptions) error type provisionerOptions struct { cursor string limit int + id string + name string } func (o *provisionerOptions) apply(opts []ProvisionerOption) (err error) { @@ -391,6 +461,12 @@ func (o *provisionerOptions) rawQuery() string { if o.limit > 0 { v.Set("limit", strconv.Itoa(o.limit)) } + if len(o.id) > 0 { + v.Set("id", o.id) + } + if len(o.name) > 0 { + v.Set("name", o.name) + } return v.Encode() } @@ -410,6 +486,22 @@ func WithProvisionerLimit(limit int) ProvisionerOption { } } +// WithProvisionerID will request the given provisioner. +func WithProvisionerID(id string) ProvisionerOption { + return func(o *provisionerOptions) error { + o.id = id + return nil + } +} + +// WithProvisionerName will request the given provisioner. +func WithProvisionerName(name string) ProvisionerOption { + return func(o *provisionerOptions) error { + o.name = name + return nil + } +} + // Client implements an HTTP client for the CA server. type Client struct { client *uaClient @@ -1211,6 +1303,15 @@ func readJSON(r io.ReadCloser, v interface{}) error { return json.NewDecoder(r).Decode(v) } +func readProtoJSON(r io.ReadCloser, m proto.Message) error { + defer r.Close() + data, err := ioutil.ReadAll(r) + if err != nil { + return err + } + return protojson.Unmarshal(data, m) +} + func readError(r io.ReadCloser) error { defer r.Close() apiErr := new(errs.Error) diff --git a/ca/testdata/ca.json b/ca/testdata/ca.json index b094c02e..0a5149d9 100644 --- a/ca/testdata/ca.json +++ b/ca/testdata/ca.json @@ -34,7 +34,7 @@ "y": "ZhYcFQBqtErdC_pA7sOXrO7AboCEPIKP9Ik4CHJqANk" } }, { - "name": "max", + "name": "mike", "type": "jwk", "encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6IlZsWnl0dUxrWTR5enlqZXJybnN0aGcifQ.QP15wQYjZ12BLgl-XTq2Vb12G3OHAfic.X35QqAaXwnlmeCUU._2qIUp0TI8yDI7c2e9upIRdrnmB5OvtLfrYN-Su2NLBpaoYtr9O55Wo0Iryc0W2pYqnVDPvgPPes4P4nQAnzw5WhFYc1Xf1ZEetfdNhwi1x2FNwPbACBAgxm5AW40O5AAlbLcWushYASfeMBZocTGXuSGUzwFqoWD-5EDJ80TWQ7cAj3ttHrJ_3QV9hi4O9KJUCiXngN-Yz2zXrhBL4NOH2fmRbaf5c0rF8xUJIIW-TcyYJeX_Fbx1IzzKKPd9USUwkDhxD4tLa51I345xVqjuwG1PEn6nF8JKqLRVUKEKFin-ShXrfE61KceyAvm4YhWKrbJWIm3bH5Hxaphy4.TexIrIhsRxJStpE3EJ925Q", "key": { @@ -76,7 +76,7 @@ "minTLSCertDuration": "1s" } }, { - "name": "mariano", + "name": "maxey", "type": "jwk", "encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6Ik5SLTk5ZkVMSm1CLW1FZGllUlFFc3cifQ.Fr314BEUGTda4ICJl2uxFdjpEUGGqJEV.gBbu_DZE1ONDu14r.X-7MKMyokZIF1HTCVqqL0tTWgaC1ZGZBLLltd11ZUhQTswo_8kvgiTv3cFShj7ATF0tAY8HStyJmzLO8mKPVOPDXSwjdNsPriZclI6JWGi9iOu8pEiN9pZM6-itxan1JMcDUNg2U-P1BmKppHRbDKsOTivymfRyeUk51dBIlS54p5xNK1HFLc1YtWC1Rc_ngYVqOgqlhIrCHArAEBe3jrfUaH2ym-8fkVdwVqtxmte3XXK9g8FchsygRNnOKtRcr0TyzTUV-7bPi8_t02Zi-EHLFaSawVXWV_Qk1GeLYJR22Rp74beo-b5-lCNVp10btO0xdGySUWmCJ4v4_QZw.c8unwWycwtfdJMM_0b0fuA", "key": { diff --git a/cas/stepcas/x5c_issuer.go b/cas/stepcas/x5c_issuer.go index da4aa27e..636d22f9 100644 --- a/cas/stepcas/x5c_issuer.go +++ b/cas/stepcas/x5c_issuer.go @@ -143,7 +143,11 @@ func newX5CSigner(certFile, keyFile, password string) (jose.Signer, error) { if err != nil { return nil, err } - certs, err := jose.ValidateX5C(certFile, signer) + certs, err := pemutil.ReadCertificateBundle(certFile) + if err != nil { + return nil, errors.Wrap(err, "error reading x5c certificate chain") + } + certStrs, err := jose.ValidateX5C(certs, signer) if err != nil { return nil, errors.Wrap(err, "error validating x5c certificate chain and key") } @@ -151,7 +155,7 @@ func newX5CSigner(certFile, keyFile, password string) (jose.Signer, error) { so := new(jose.SignerOptions) so.WithType("JWT") so.WithHeader("kid", kid) - so.WithHeader("x5c", certs) + so.WithHeader("x5c", certStrs) return newJoseSigner(signer, so) } diff --git a/go.mod b/go.mod index 8a2fe4a1..15ea5fa0 100644 --- a/go.mod +++ b/go.mod @@ -23,18 +23,22 @@ require ( github.com/smallstep/nosql v0.3.6 github.com/urfave/cli v1.22.4 go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 - go.step.sm/cli-utils v0.2.0 - go.step.sm/crypto v0.8.3 + go.step.sm/cli-utils v0.4.1 + go.step.sm/crypto v0.9.0 + go.step.sm/linkedca v0.0.0-20210611183751-27424aae8d25 // indirect golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 golang.org/x/net v0.0.0-20210119194325-5f4716e94777 google.golang.org/api v0.33.0 google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154 - google.golang.org/grpc v1.32.0 - google.golang.org/protobuf v1.25.0 + google.golang.org/grpc v1.38.0 + google.golang.org/protobuf v1.26.0 gopkg.in/square/go-jose.v2 v2.5.1 ) // replace github.com/smallstep/nosql => ../nosql -// replace go.step.sm/crypto => ../crypto + +//replace go.step.sm/crypto => ../crypto + +//replace go.step.sm/cli-utils => ../cli-utils replace go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 => github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568 diff --git a/go.sum b/go.sum index 80b154e1..34aea910 100644 --- a/go.sum +++ b/go.sum @@ -47,6 +47,7 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= @@ -56,41 +57,60 @@ github.com/Masterminds/sprig/v3 v3.1.0 h1:j7GpgZ7PdFqNsmncycTHsLmVPf5/3wJtlgW9TN github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Shopify/sarama v1.19.0 h1:9oksLxC6uxVPHPVYUmq6xhr1BOF/hHobWH2UzO67z1s= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/ThalesIgnite/crypto11 v1.2.4 h1:3MebRK/U0mA2SmSthXAIZAdUA9w8+ZuKem2O6HuR1f8= github.com/ThalesIgnite/crypto11 v1.2.4/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE= +github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vajsk51NtYIcwSTkXr+JGrMd36kTDJw= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0 h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a h1:pv34s756C4pEXnjgPfGYgdhg/ZdajGhyOvzx8k+23nw= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3 h1:SuCy7H3NLyp+1Mrfp+m80jcbi9KYWAs9/BXwppwRDzY= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.30.29 h1:NXNqBS9hjOCpDL8SyCyl38gZX3LLLunKOJc5E7vJ8P0= github.com/aws/aws-sdk-go v1.30.29/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go-v2 v0.18.0 h1:qZ+woO4SamnH/eEbjM2IDLhRNwIwND/RQyVlBLp3Jqg= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/casbin/casbin/v2 v2.1.2 h1:bTwon/ECRx9dwBy2ewRVr5OiqjeXSGiTUY74sDPQi/g= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= -github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -98,12 +118,17 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5O github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec h1:EdRZT3IeKQmfCSrgo8SZ8V3MEnskuJP0wCYNpe+aiXo= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -111,13 +136,16 @@ github.com/coreos/go-etcd v2.0.0+incompatible h1:bXhRBIXoTm9BYHS3gE0TtQuyNZyeEMu github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf h1:CAKfRE2YtTUIjjh1bkBtyYFaUT/WmOqsJjgtihT0vMI= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -129,6 +157,7 @@ github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20201003150343-5d1bab4fc658/go.mod h github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd h1:KoJOtZf+6wpQaDTuOWGuo61GxcPBIfhwRxRTaTWGCTc= github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd/go.mod h1:YylP9MpCYGVZQrly/j/diqcdUetCRRePeBB0c2VGXsA= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= @@ -136,22 +165,32 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUn github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4 h1:rEvIZUSZ3fx39WIi3JkQqQBitGwpELBIYWeBVh6wn+E= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d h1:QyzYnTnPE15SQyUeqU6qLbWxMkwyAyu+vGksa0b7j00= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db h1:gb2Z18BhTPJPpLQWj4T+rfKHYCHxRHCtRxhKKjRidVw= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8 h1:a9ENSRDFBUPkJ5lCgVZh26+ZbGyoVJG7yb5SSzF5H54= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs= github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= @@ -180,9 +219,11 @@ github.com/go-stack/stack v1.6.0 h1:MmJCxYVKTJ0SplGKqFVX3SBnmaUhODHZrrFF6jMbpZk= github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/googleapis v1.1.0 h1:kFkMAZBNAn4j7K0GiZr8cRYzejq68VbheufiV3YuyFI= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -214,6 +255,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw= @@ -230,6 +273,9 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -253,43 +299,70 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.4.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c h1:Lh2aW+HnU2Nbe1gqD9SOJLJxW1jBMmQOktN2acDyJk8= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda h1:5ikpG9mYCMFiZX0nkxoV6aU2IpCHPdws3gCNgdZeEV0= github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda/go.mod h1:MyndkAZd5rUMdNogn35MWXBX1UiBigrU8eTj8DoAC2c= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.3.0 h1:HXNYlRkkM/t+Y/Yhxtwcy02dlYwIaoxzvxPnS+cqy78= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0 h1:UOxjlb4xVNF93jak1mzzoBatyFju9nrkxpVwIp/QqxQ= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0 h1:WhIgCr5a7AaVH6jPUwjtRuuE7/RDufnUvzIr48smyxs= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/hudl/fargo v1.3.0 h1:0U6+BtN6LhaYuTnIJq4Wyq5cpn6O2kWrxAtcqBmYY6w= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI= @@ -298,33 +371,35 @@ github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d h1:/WZQPMZNsjZ7IlCpsLGdQBINg5bxKQ1K1sh6awxLtkA= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= +github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0 h1:ZqfnKyx9KGpRcW04j5nnPDgRgoXUeLh2YFBeFzphcA0= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -<<<<<<< HEAD -======= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= ->>>>>>> 7ad90d1 (Refactor initialization of SCEP authority) github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -332,11 +407,14 @@ github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743 h1:143Bb8f8DuGWck/xpNUOckBVYfFbBTnLevfRZ1aVVqo= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1 h1:vi1F1IQ8N7hNWytK9DpJsUfQhGuNSc19z330K6vl4zk= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/lyft/protoc-gen-validate v0.0.13 h1:KNt/RhmQTOLr7Aj8PsJ7mTronaFyx80mRTT9qF261dA= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -350,33 +428,28 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -<<<<<<< HEAD -<<<<<<< HEAD -======= -github.com/micromdm/scep v1.0.1-0.20210219005251-6027e7654b23 h1:QACkVsQ7Qx4PuPDFL2OFD5u4OnYT0TkReWk9IVqaGHA= -github.com/micromdm/scep v1.0.1-0.20210219005251-6027e7654b23/go.mod h1:a82oNZyGV8jj9Do7dh8EkA90+esBls0CZHR6B85Qda8= ->>>>>>> 7ad90d1 (Refactor initialization of SCEP authority) -github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f h1:eVB9ELsoq5ouItQBr5Tj334bhPJG/MX+m7rTchmzVUQ= -github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/micromdm/scep v1.0.0 h1:ai//kcZnxZPq1YE/MatiE2bIRD94KOAwZRpN1fhVQXY= -github.com/micromdm/scep v1.0.0/go.mod h1:CID2SixSr5FvoauZdAFUSpQkn5MAuSy9oyURMGOJbag= -======= +github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/micromdm/scep/v2 v2.0.0 h1:cRzcY0S5QX+0+J+7YC4P2uZSnfMup8S8zJu/bLFgOkA= github.com/micromdm/scep/v2 v2.0.0/go.mod h1:ouaDs5tcjOjdHD/h8BGaQsWE87MUnQ/wMTMgfMMIpPc= +github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f h1:eVB9ELsoq5ouItQBr5Tj334bhPJG/MX+m7rTchmzVUQ= github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= ->>>>>>> c3d9cef (Update to v2.0.0 of github.com/micromdm/scep) github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= @@ -384,70 +457,101 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2 h1:+RB5hMpXUUA2dfxuhBTEkMOrYmM+gKIZYS1KjSostMI= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2 h1:i2Ly0B+1+rzNZHHWtD4ZwKi+OU5l+uQo1iDHZ2PmiIc= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1 h1:ik3HbLhZ0YABLto7iX80pZLPw/6dx3T+++MZJwLnMrQ= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3 h1:6JrEfig+HzTH85yxzhSVbjHRJv9cn0p6n3IngIcM5/k= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/newrelic/go-agent v2.15.0+incompatible h1:IB0Fy+dClpBq9aEoIrLyQXzU34JyI1xVTanPLB/+jvU= github.com/newrelic/go-agent v2.15.0+incompatible/go.mod h1:a8Fv1b/fYhFSReoTU6HDkTYIMZeSVNffmoS726Y0LzQ= +github.com/oklog/oklog v0.3.2 h1:wVfs8F+in6nTBMkA7CbRw+zZMIB7nNM825cM1wuzoTk= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5 h1:58+kh9C6jJVXYjt8IE48G2eWl6BjwU5Gj0gqY84fy78= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568 h1:+MPqEswjYiS0S1FCTg8MIhMBMzxiVQ94rooFwvPPiWk= github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 h1:lM6RxxfUMrYL/f8bWEUqdXrANWtrL7Nndbm9iFN0DlU= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7lZWlQw5UXuoo= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 h1:ZCnq+JUrvXcDVhX/xRolRBZifmabN1HcS1wrPSvxhrU= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2 h1:nY8Hti+WKaP0cRsSeQ026wU03QsM762XBeCXBb9NAWI= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4 h1:OYkFijGHoZAYbOIb1LWXrwKQbMMRUv1oQ89blD2Mh2Q= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/performancecopilot/speed v3.0.0+incompatible h1:2WnRzIquHa5QxaJKShDkLM+sc0JPuwhXzK8OYOyt3Vg= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1 h1:F++O52m40owAmADcojzM+9gyjmMOY/T4oYJkgFDH8RE= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0 h1:miYCvYqFXtl/J9FIy8eNpBfYthAEFg+Ys0XyUVEcDsc= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0 h1:ElTg5tNp4DqfV7UQjDqv2+RJlNzsDtvNAWccbItceIE= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af h1:gu+uRPtBe88sKxUCEXRoeCvVG90TJmwhiqRpvdhQFng= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -457,10 +561,13 @@ github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNue github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f h1:UFr9zpz4xgTnIE5yIMtWAMngCdZ9p/+q6lTbgelo80M= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189 h1:CmSpbxmewNQbzqztaY0bke1qzHhyNyC29wYgh17Gxfo= github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189/go.mod h1:UUwuHEJ9zkkPDxspIHOa59PUeSkGFljESGzbxntLmIg= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da h1:p3Vo3i64TCLY7gIfzeQaUJ+kppEO5WQG3cL8iE8tGHU= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -472,9 +579,13 @@ github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= github.com/smallstep/nosql v0.3.6 h1:cq6a3NwjFJxkVlWU1T4qGskcfEXr0fO1WqQrraDO1Po= github.com/smallstep/nosql v0.3.6/go.mod h1:h1zC/Z54uNHc8euquLED4qJNCrMHd3nytA141ZZh4qQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= @@ -495,7 +606,9 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271 h1:WhxRHzgeVGETMlmVfqhRn8RIeeNoPr2Czh33I4Zdccw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a h1:AhmOdSHeswKHBjhsLs/7+1voOxT+LLrSk/Nxvk35fug= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= @@ -507,6 +620,7 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg= github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -515,6 +629,7 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= @@ -526,14 +641,10 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -<<<<<<< HEAD -go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M= -go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= -======= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 h1:VcrIfasaLFkyjk6KNlXQSzO+B0fZcnECiDrKJsfxka0= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= ->>>>>>> c3d9cef (Update to v2.0.0 of github.com/micromdm/scep) go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -543,23 +654,32 @@ go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.step.sm/cli-utils v0.2.0 h1:hpVu9+6dpv/7/Bd8nGJFc3V+gQ+TciSJRTu9TavDUQ4= go.step.sm/cli-utils v0.2.0/go.mod h1:+t4qCp5NO+080DdGkJxEh3xL5S4TcYC2JTPLMM72b6Y= +go.step.sm/cli-utils v0.4.0 h1:dni6gR/6/LOqfbzm/yUdgz5O12tkxX17SxA9+pRMidI= +go.step.sm/cli-utils v0.4.0/go.mod h1:1zFgatDqEJ1Y4MNStdWa0b1NPc1fvSHbDJC+wZ6iQlE= +go.step.sm/cli-utils v0.4.1 h1:QztRUhGYjOPM1I2Nmi7V6XejQyVtcESmo+sbegxvX7Q= +go.step.sm/cli-utils v0.4.1/go.mod h1:hWYVOSlw8W9Pd+BwIbs/aftVVMRms3EG7Q2qLRwc0WA= go.step.sm/crypto v0.6.1/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0= -<<<<<<< HEAD go.step.sm/crypto v0.8.3 h1:TO/OPlaUrYXhs8srGEFNyL6OWVQvRmEPCUONNnQUuEM= go.step.sm/crypto v0.8.3/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0= -======= -go.step.sm/crypto v0.8.0 h1:S4qBPyy3hR7KWLybSkHB0H14pwFfYkom4RZ96JzmXig= -go.step.sm/crypto v0.8.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0= +go.step.sm/crypto v0.9.0 h1:q2AllTSnVj4NRtyEPkGW2ohArLmbGbe6ZAL/VIOKDzA= +go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0= +go.step.sm/linkedca v0.0.0-20210610014030-59b16916c7e7 h1:hAfzUm80XWGtFnxyVgeT/gc/3XnlVNnHD5HrLbk4Fc0= +go.step.sm/linkedca v0.0.0-20210610014030-59b16916c7e7/go.mod h1:5uTRjozEGSPAZal9xJqlaD38cvJcLe3o1VAFVjqcORo= +go.step.sm/linkedca v0.0.0-20210611183751-27424aae8d25 h1:ncJqviWswJT19IdnfOYQGKG1zL7IDy4lAJz1PuM3fgw= +go.step.sm/linkedca v0.0.0-20210611183751-27424aae8d25/go.mod h1:5uTRjozEGSPAZal9xJqlaD38cvJcLe3o1VAFVjqcORo= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= ->>>>>>> c3d9cef (Update to v2.0.0 of github.com/micromdm/scep) golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -569,8 +689,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -642,9 +762,8 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -854,6 +973,8 @@ google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -865,20 +986,30 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -900,5 +1031,7 @@ rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 h1:ucqkfpjg9WzSUubAO62csmucvxl4/JeW3F4I4909XkM= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/linkedca/admin.pb.go b/linkedca/admin.pb.go deleted file mode 100644 index 02e287c1..00000000 --- a/linkedca/admin.pb.go +++ /dev/null @@ -1,240 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.26.0 -// protoc v3.15.8 -// source: linkedca/admin.proto - -package linkedca - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type Admin_Type int32 - -const ( - Admin_UNKNOWN Admin_Type = 0 - Admin_ADMIN Admin_Type = 1 - Admin_SUPER_ADMIN Admin_Type = 2 -) - -// Enum value maps for Admin_Type. -var ( - Admin_Type_name = map[int32]string{ - 0: "UNKNOWN", - 1: "ADMIN", - 2: "SUPER_ADMIN", - } - Admin_Type_value = map[string]int32{ - "UNKNOWN": 0, - "ADMIN": 1, - "SUPER_ADMIN": 2, - } -) - -func (x Admin_Type) Enum() *Admin_Type { - p := new(Admin_Type) - *p = x - return p -} - -func (x Admin_Type) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (Admin_Type) Descriptor() protoreflect.EnumDescriptor { - return file_linkedca_admin_proto_enumTypes[0].Descriptor() -} - -func (Admin_Type) Type() protoreflect.EnumType { - return &file_linkedca_admin_proto_enumTypes[0] -} - -func (x Admin_Type) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use Admin_Type.Descriptor instead. -func (Admin_Type) EnumDescriptor() ([]byte, []int) { - return file_linkedca_admin_proto_rawDescGZIP(), []int{0, 0} -} - -type Admin struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - AuthorityId string `protobuf:"bytes,2,opt,name=authority_id,json=authorityId,proto3" json:"authority_id,omitempty"` - Subject string `protobuf:"bytes,3,opt,name=subject,proto3" json:"subject,omitempty"` - ProvisionerId string `protobuf:"bytes,4,opt,name=provisioner_id,json=provisionerId,proto3" json:"provisioner_id,omitempty"` - Type Admin_Type `protobuf:"varint,5,opt,name=type,proto3,enum=linkedca.Admin_Type" json:"type,omitempty"` -} - -func (x *Admin) Reset() { - *x = Admin{} - if protoimpl.UnsafeEnabled { - mi := &file_linkedca_admin_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Admin) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Admin) ProtoMessage() {} - -func (x *Admin) ProtoReflect() protoreflect.Message { - mi := &file_linkedca_admin_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Admin.ProtoReflect.Descriptor instead. -func (*Admin) Descriptor() ([]byte, []int) { - return file_linkedca_admin_proto_rawDescGZIP(), []int{0} -} - -func (x *Admin) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *Admin) GetAuthorityId() string { - if x != nil { - return x.AuthorityId - } - return "" -} - -func (x *Admin) GetSubject() string { - if x != nil { - return x.Subject - } - return "" -} - -func (x *Admin) GetProvisionerId() string { - if x != nil { - return x.ProvisionerId - } - return "" -} - -func (x *Admin) GetType() Admin_Type { - if x != nil { - return x.Type - } - return Admin_UNKNOWN -} - -var File_linkedca_admin_proto protoreflect.FileDescriptor - -var file_linkedca_admin_proto_rawDesc = []byte{ - 0x0a, 0x14, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2f, 0x61, 0x64, 0x6d, 0x69, 0x6e, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, - 0x22, 0xd6, 0x01, 0x0a, 0x05, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x18, 0x0a, - 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, - 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x6c, - 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x54, 0x79, - 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2f, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, - 0x05, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x55, 0x50, 0x45, - 0x52, 0x5f, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x02, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x73, 0x74, 0x65, - 0x70, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2f, 0x6c, - 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_linkedca_admin_proto_rawDescOnce sync.Once - file_linkedca_admin_proto_rawDescData = file_linkedca_admin_proto_rawDesc -) - -func file_linkedca_admin_proto_rawDescGZIP() []byte { - file_linkedca_admin_proto_rawDescOnce.Do(func() { - file_linkedca_admin_proto_rawDescData = protoimpl.X.CompressGZIP(file_linkedca_admin_proto_rawDescData) - }) - return file_linkedca_admin_proto_rawDescData -} - -var file_linkedca_admin_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_linkedca_admin_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_linkedca_admin_proto_goTypes = []interface{}{ - (Admin_Type)(0), // 0: linkedca.Admin.Type - (*Admin)(nil), // 1: linkedca.Admin -} -var file_linkedca_admin_proto_depIdxs = []int32{ - 0, // 0: linkedca.Admin.type:type_name -> linkedca.Admin.Type - 1, // [1:1] is the sub-list for method output_type - 1, // [1:1] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name -} - -func init() { file_linkedca_admin_proto_init() } -func file_linkedca_admin_proto_init() { - if File_linkedca_admin_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_linkedca_admin_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Admin); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_linkedca_admin_proto_rawDesc, - NumEnums: 1, - NumMessages: 1, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_linkedca_admin_proto_goTypes, - DependencyIndexes: file_linkedca_admin_proto_depIdxs, - EnumInfos: file_linkedca_admin_proto_enumTypes, - MessageInfos: file_linkedca_admin_proto_msgTypes, - }.Build() - File_linkedca_admin_proto = out.File - file_linkedca_admin_proto_rawDesc = nil - file_linkedca_admin_proto_goTypes = nil - file_linkedca_admin_proto_depIdxs = nil -} diff --git a/linkedca/admin.proto b/linkedca/admin.proto deleted file mode 100644 index 29c76139..00000000 --- a/linkedca/admin.proto +++ /dev/null @@ -1,18 +0,0 @@ -syntax = "proto3"; - -package linkedca; - -option go_package = "github.com/smallstep/certificates/linkedca"; - -message Admin { - enum Type { - UNKNOWN = 0; - ADMIN = 1; - SUPER_ADMIN = 2; - } - string id = 1; - string authority_id = 2; - string subject = 3; - string provisioner_id = 4; - Type type = 5; -} diff --git a/linkedca/doc.go b/linkedca/doc.go deleted file mode 100644 index ceddecb0..00000000 --- a/linkedca/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -package linkedca - -//go:generate protoc --proto_path=.. --go_out=.. --go-grpc_out=.. --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative linkedca/provisioners.proto linkedca/admin.proto diff --git a/linkedca/provisioners.go b/linkedca/provisioners.go deleted file mode 100644 index ea46f342..00000000 --- a/linkedca/provisioners.go +++ /dev/null @@ -1,38 +0,0 @@ -package linkedca - -import ( - "encoding/json" - "fmt" -) - -// UnmarshalProvisionerDetails unmarshals details type to the specific provisioner details. -func UnmarshalProvisionerDetails(typ Provisioner_Type, data []byte) (*ProvisionerDetails, error) { - var v isProvisionerDetails_Data - switch typ { - case Provisioner_JWK: - v = new(ProvisionerDetails_JWK) - case Provisioner_OIDC: - v = new(ProvisionerDetails_OIDC) - case Provisioner_GCP: - v = new(ProvisionerDetails_GCP) - case Provisioner_AWS: - v = new(ProvisionerDetails_AWS) - case Provisioner_AZURE: - v = new(ProvisionerDetails_Azure) - case Provisioner_ACME: - v = new(ProvisionerDetails_ACME) - case Provisioner_X5C: - v = new(ProvisionerDetails_X5C) - case Provisioner_K8SSA: - v = new(ProvisionerDetails_K8SSA) - case Provisioner_SSHPOP: - v = new(ProvisionerDetails_SSHPOP) - default: - return nil, fmt.Errorf("unsupported provisioner type %s", typ) - } - - if err := json.Unmarshal(data, v); err != nil { - return nil, err - } - return &ProvisionerDetails{Data: v}, nil -} diff --git a/linkedca/provisioners.pb.go b/linkedca/provisioners.pb.go deleted file mode 100644 index 4b8ffab0..00000000 --- a/linkedca/provisioners.pb.go +++ /dev/null @@ -1,1833 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.26.0 -// protoc v3.15.8 -// source: linkedca/provisioners.proto - -package linkedca - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type Provisioner_Type int32 - -const ( - Provisioner_NOOP Provisioner_Type = 0 - Provisioner_JWK Provisioner_Type = 1 - Provisioner_OIDC Provisioner_Type = 2 - Provisioner_GCP Provisioner_Type = 3 - Provisioner_AWS Provisioner_Type = 4 - Provisioner_AZURE Provisioner_Type = 5 - Provisioner_ACME Provisioner_Type = 6 - Provisioner_X5C Provisioner_Type = 7 - Provisioner_K8SSA Provisioner_Type = 8 - Provisioner_SSHPOP Provisioner_Type = 9 - Provisioner_SCEP Provisioner_Type = 10 -) - -// Enum value maps for Provisioner_Type. -var ( - Provisioner_Type_name = map[int32]string{ - 0: "NOOP", - 1: "JWK", - 2: "OIDC", - 3: "GCP", - 4: "AWS", - 5: "AZURE", - 6: "ACME", - 7: "X5C", - 8: "K8SSA", - 9: "SSHPOP", - 10: "SCEP", - } - Provisioner_Type_value = map[string]int32{ - "NOOP": 0, - "JWK": 1, - "OIDC": 2, - "GCP": 3, - "AWS": 4, - "AZURE": 5, - "ACME": 6, - "X5C": 7, - "K8SSA": 8, - "SSHPOP": 9, - "SCEP": 10, - } -) - -func (x Provisioner_Type) Enum() *Provisioner_Type { - p := new(Provisioner_Type) - *p = x - return p -} - -func (x Provisioner_Type) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (Provisioner_Type) Descriptor() protoreflect.EnumDescriptor { - return file_linkedca_provisioners_proto_enumTypes[0].Descriptor() -} - -func (Provisioner_Type) Type() protoreflect.EnumType { - return &file_linkedca_provisioners_proto_enumTypes[0] -} - -func (x Provisioner_Type) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use Provisioner_Type.Descriptor instead. -func (Provisioner_Type) EnumDescriptor() ([]byte, []int) { - return file_linkedca_provisioners_proto_rawDescGZIP(), []int{0, 0} -} - -type Provisioner struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - AuthorityId string `protobuf:"bytes,2,opt,name=authority_id,json=authorityId,proto3" json:"authority_id,omitempty"` - Type Provisioner_Type `protobuf:"varint,3,opt,name=type,proto3,enum=linkedca.Provisioner_Type" json:"type,omitempty"` - Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` - Details *ProvisionerDetails `protobuf:"bytes,5,opt,name=details,proto3" json:"details,omitempty"` - Claims *Claims `protobuf:"bytes,6,opt,name=claims,proto3" json:"claims,omitempty"` - X509Template []byte `protobuf:"bytes,7,opt,name=x509_template,json=x509Template,proto3" json:"x509_template,omitempty"` - X509TemplateData []byte `protobuf:"bytes,8,opt,name=x509_template_data,json=x509TemplateData,proto3" json:"x509_template_data,omitempty"` - SshTemplate []byte `protobuf:"bytes,9,opt,name=ssh_template,json=sshTemplate,proto3" json:"ssh_template,omitempty"` - SshTemplateData []byte `protobuf:"bytes,10,opt,name=ssh_template_data,json=sshTemplateData,proto3" json:"ssh_template_data,omitempty"` -} - -func (x *Provisioner) Reset() { - *x = Provisioner{} - if protoimpl.UnsafeEnabled { - mi := &file_linkedca_provisioners_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Provisioner) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Provisioner) ProtoMessage() {} - -func (x *Provisioner) ProtoReflect() protoreflect.Message { - mi := &file_linkedca_provisioners_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Provisioner.ProtoReflect.Descriptor instead. -func (*Provisioner) Descriptor() ([]byte, []int) { - return file_linkedca_provisioners_proto_rawDescGZIP(), []int{0} -} - -func (x *Provisioner) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *Provisioner) GetAuthorityId() string { - if x != nil { - return x.AuthorityId - } - return "" -} - -func (x *Provisioner) GetType() Provisioner_Type { - if x != nil { - return x.Type - } - return Provisioner_NOOP -} - -func (x *Provisioner) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *Provisioner) GetDetails() *ProvisionerDetails { - if x != nil { - return x.Details - } - return nil -} - -func (x *Provisioner) GetClaims() *Claims { - if x != nil { - return x.Claims - } - return nil -} - -func (x *Provisioner) GetX509Template() []byte { - if x != nil { - return x.X509Template - } - return nil -} - -func (x *Provisioner) GetX509TemplateData() []byte { - if x != nil { - return x.X509TemplateData - } - return nil -} - -func (x *Provisioner) GetSshTemplate() []byte { - if x != nil { - return x.SshTemplate - } - return nil -} - -func (x *Provisioner) GetSshTemplateData() []byte { - if x != nil { - return x.SshTemplateData - } - return nil -} - -type ProvisionerDetails struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Types that are assignable to Data: - // *ProvisionerDetails_JWK - // *ProvisionerDetails_OIDC - // *ProvisionerDetails_GCP - // *ProvisionerDetails_AWS - // *ProvisionerDetails_Azure - // *ProvisionerDetails_ACME - // *ProvisionerDetails_X5C - // *ProvisionerDetails_K8SSA - // *ProvisionerDetails_SSHPOP - // *ProvisionerDetails_SCEP - Data isProvisionerDetails_Data `protobuf_oneof:"data"` -} - -func (x *ProvisionerDetails) Reset() { - *x = ProvisionerDetails{} - if protoimpl.UnsafeEnabled { - mi := &file_linkedca_provisioners_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ProvisionerDetails) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ProvisionerDetails) ProtoMessage() {} - -func (x *ProvisionerDetails) ProtoReflect() protoreflect.Message { - mi := &file_linkedca_provisioners_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ProvisionerDetails.ProtoReflect.Descriptor instead. -func (*ProvisionerDetails) Descriptor() ([]byte, []int) { - return file_linkedca_provisioners_proto_rawDescGZIP(), []int{1} -} - -func (m *ProvisionerDetails) GetData() isProvisionerDetails_Data { - if m != nil { - return m.Data - } - return nil -} - -func (x *ProvisionerDetails) GetJWK() *JWKProvisioner { - if x, ok := x.GetData().(*ProvisionerDetails_JWK); ok { - return x.JWK - } - return nil -} - -func (x *ProvisionerDetails) GetOIDC() *OIDCProvisioner { - if x, ok := x.GetData().(*ProvisionerDetails_OIDC); ok { - return x.OIDC - } - return nil -} - -func (x *ProvisionerDetails) GetGCP() *GCPProvisioner { - if x, ok := x.GetData().(*ProvisionerDetails_GCP); ok { - return x.GCP - } - return nil -} - -func (x *ProvisionerDetails) GetAWS() *AWSProvisioner { - if x, ok := x.GetData().(*ProvisionerDetails_AWS); ok { - return x.AWS - } - return nil -} - -func (x *ProvisionerDetails) GetAzure() *AzureProvisioner { - if x, ok := x.GetData().(*ProvisionerDetails_Azure); ok { - return x.Azure - } - return nil -} - -func (x *ProvisionerDetails) GetACME() *ACMEProvisioner { - if x, ok := x.GetData().(*ProvisionerDetails_ACME); ok { - return x.ACME - } - return nil -} - -func (x *ProvisionerDetails) GetX5C() *X5CProvisioner { - if x, ok := x.GetData().(*ProvisionerDetails_X5C); ok { - return x.X5C - } - return nil -} - -func (x *ProvisionerDetails) GetK8SSA() *K8SSAProvisioner { - if x, ok := x.GetData().(*ProvisionerDetails_K8SSA); ok { - return x.K8SSA - } - return nil -} - -func (x *ProvisionerDetails) GetSSHPOP() *SSHPOPProvisioner { - if x, ok := x.GetData().(*ProvisionerDetails_SSHPOP); ok { - return x.SSHPOP - } - return nil -} - -func (x *ProvisionerDetails) GetSCEP() *SCEPProvisioner { - if x, ok := x.GetData().(*ProvisionerDetails_SCEP); ok { - return x.SCEP - } - return nil -} - -type isProvisionerDetails_Data interface { - isProvisionerDetails_Data() -} - -type ProvisionerDetails_JWK struct { - JWK *JWKProvisioner `protobuf:"bytes,20,opt,name=JWK,proto3,oneof"` -} - -type ProvisionerDetails_OIDC struct { - OIDC *OIDCProvisioner `protobuf:"bytes,21,opt,name=OIDC,proto3,oneof"` -} - -type ProvisionerDetails_GCP struct { - GCP *GCPProvisioner `protobuf:"bytes,22,opt,name=GCP,proto3,oneof"` -} - -type ProvisionerDetails_AWS struct { - AWS *AWSProvisioner `protobuf:"bytes,23,opt,name=AWS,proto3,oneof"` -} - -type ProvisionerDetails_Azure struct { - Azure *AzureProvisioner `protobuf:"bytes,24,opt,name=Azure,proto3,oneof"` -} - -type ProvisionerDetails_ACME struct { - ACME *ACMEProvisioner `protobuf:"bytes,25,opt,name=ACME,proto3,oneof"` -} - -type ProvisionerDetails_X5C struct { - X5C *X5CProvisioner `protobuf:"bytes,26,opt,name=X5C,proto3,oneof"` -} - -type ProvisionerDetails_K8SSA struct { - K8SSA *K8SSAProvisioner `protobuf:"bytes,27,opt,name=K8sSA,proto3,oneof"` -} - -type ProvisionerDetails_SSHPOP struct { - SSHPOP *SSHPOPProvisioner `protobuf:"bytes,28,opt,name=SSHPOP,proto3,oneof"` -} - -type ProvisionerDetails_SCEP struct { - SCEP *SCEPProvisioner `protobuf:"bytes,29,opt,name=SCEP,proto3,oneof"` -} - -func (*ProvisionerDetails_JWK) isProvisionerDetails_Data() {} - -func (*ProvisionerDetails_OIDC) isProvisionerDetails_Data() {} - -func (*ProvisionerDetails_GCP) isProvisionerDetails_Data() {} - -func (*ProvisionerDetails_AWS) isProvisionerDetails_Data() {} - -func (*ProvisionerDetails_Azure) isProvisionerDetails_Data() {} - -func (*ProvisionerDetails_ACME) isProvisionerDetails_Data() {} - -func (*ProvisionerDetails_X5C) isProvisionerDetails_Data() {} - -func (*ProvisionerDetails_K8SSA) isProvisionerDetails_Data() {} - -func (*ProvisionerDetails_SSHPOP) isProvisionerDetails_Data() {} - -func (*ProvisionerDetails_SCEP) isProvisionerDetails_Data() {} - -type ProvisionerList struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Provisioners []*Provisioner `protobuf:"bytes,1,rep,name=provisioners,proto3" json:"provisioners,omitempty"` -} - -func (x *ProvisionerList) Reset() { - *x = ProvisionerList{} - if protoimpl.UnsafeEnabled { - mi := &file_linkedca_provisioners_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ProvisionerList) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ProvisionerList) ProtoMessage() {} - -func (x *ProvisionerList) ProtoReflect() protoreflect.Message { - mi := &file_linkedca_provisioners_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ProvisionerList.ProtoReflect.Descriptor instead. -func (*ProvisionerList) Descriptor() ([]byte, []int) { - return file_linkedca_provisioners_proto_rawDescGZIP(), []int{2} -} - -func (x *ProvisionerList) GetProvisioners() []*Provisioner { - if x != nil { - return x.Provisioners - } - return nil -} - -type Claims struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - X509 *X509Claims `protobuf:"bytes,1,opt,name=x509,proto3" json:"x509,omitempty"` - Ssh *SSHClaims `protobuf:"bytes,2,opt,name=ssh,proto3" json:"ssh,omitempty"` - DisableRenewal bool `protobuf:"varint,3,opt,name=disable_renewal,json=disableRenewal,proto3" json:"disable_renewal,omitempty"` -} - -func (x *Claims) Reset() { - *x = Claims{} - if protoimpl.UnsafeEnabled { - mi := &file_linkedca_provisioners_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Claims) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Claims) ProtoMessage() {} - -func (x *Claims) ProtoReflect() protoreflect.Message { - mi := &file_linkedca_provisioners_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Claims.ProtoReflect.Descriptor instead. -func (*Claims) Descriptor() ([]byte, []int) { - return file_linkedca_provisioners_proto_rawDescGZIP(), []int{3} -} - -func (x *Claims) GetX509() *X509Claims { - if x != nil { - return x.X509 - } - return nil -} - -func (x *Claims) GetSsh() *SSHClaims { - if x != nil { - return x.Ssh - } - return nil -} - -func (x *Claims) GetDisableRenewal() bool { - if x != nil { - return x.DisableRenewal - } - return false -} - -type X509Claims struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` - Durations *Durations `protobuf:"bytes,2,opt,name=durations,proto3" json:"durations,omitempty"` -} - -func (x *X509Claims) Reset() { - *x = X509Claims{} - if protoimpl.UnsafeEnabled { - mi := &file_linkedca_provisioners_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *X509Claims) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*X509Claims) ProtoMessage() {} - -func (x *X509Claims) ProtoReflect() protoreflect.Message { - mi := &file_linkedca_provisioners_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use X509Claims.ProtoReflect.Descriptor instead. -func (*X509Claims) Descriptor() ([]byte, []int) { - return file_linkedca_provisioners_proto_rawDescGZIP(), []int{4} -} - -func (x *X509Claims) GetEnabled() bool { - if x != nil { - return x.Enabled - } - return false -} - -func (x *X509Claims) GetDurations() *Durations { - if x != nil { - return x.Durations - } - return nil -} - -type SSHClaims struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` - UserDurations *Durations `protobuf:"bytes,2,opt,name=user_durations,json=userDurations,proto3" json:"user_durations,omitempty"` - HostDurations *Durations `protobuf:"bytes,3,opt,name=host_durations,json=hostDurations,proto3" json:"host_durations,omitempty"` -} - -func (x *SSHClaims) Reset() { - *x = SSHClaims{} - if protoimpl.UnsafeEnabled { - mi := &file_linkedca_provisioners_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SSHClaims) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SSHClaims) ProtoMessage() {} - -func (x *SSHClaims) ProtoReflect() protoreflect.Message { - mi := &file_linkedca_provisioners_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SSHClaims.ProtoReflect.Descriptor instead. -func (*SSHClaims) Descriptor() ([]byte, []int) { - return file_linkedca_provisioners_proto_rawDescGZIP(), []int{5} -} - -func (x *SSHClaims) GetEnabled() bool { - if x != nil { - return x.Enabled - } - return false -} - -func (x *SSHClaims) GetUserDurations() *Durations { - if x != nil { - return x.UserDurations - } - return nil -} - -func (x *SSHClaims) GetHostDurations() *Durations { - if x != nil { - return x.HostDurations - } - return nil -} - -type Durations struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Default string `protobuf:"bytes,1,opt,name=default,proto3" json:"default,omitempty"` - Min string `protobuf:"bytes,2,opt,name=min,proto3" json:"min,omitempty"` - Max string `protobuf:"bytes,3,opt,name=max,proto3" json:"max,omitempty"` -} - -func (x *Durations) Reset() { - *x = Durations{} - if protoimpl.UnsafeEnabled { - mi := &file_linkedca_provisioners_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Durations) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Durations) ProtoMessage() {} - -func (x *Durations) ProtoReflect() protoreflect.Message { - mi := &file_linkedca_provisioners_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Durations.ProtoReflect.Descriptor instead. -func (*Durations) Descriptor() ([]byte, []int) { - return file_linkedca_provisioners_proto_rawDescGZIP(), []int{6} -} - -func (x *Durations) GetDefault() string { - if x != nil { - return x.Default - } - return "" -} - -func (x *Durations) GetMin() string { - if x != nil { - return x.Min - } - return "" -} - -func (x *Durations) GetMax() string { - if x != nil { - return x.Max - } - return "" -} - -type JWKProvisioner struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - PublicKey []byte `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` - EncryptedPrivateKey []byte `protobuf:"bytes,2,opt,name=encrypted_private_key,json=encryptedPrivateKey,proto3" json:"encrypted_private_key,omitempty"` -} - -func (x *JWKProvisioner) Reset() { - *x = JWKProvisioner{} - if protoimpl.UnsafeEnabled { - mi := &file_linkedca_provisioners_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *JWKProvisioner) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*JWKProvisioner) ProtoMessage() {} - -func (x *JWKProvisioner) ProtoReflect() protoreflect.Message { - mi := &file_linkedca_provisioners_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use JWKProvisioner.ProtoReflect.Descriptor instead. -func (*JWKProvisioner) Descriptor() ([]byte, []int) { - return file_linkedca_provisioners_proto_rawDescGZIP(), []int{7} -} - -func (x *JWKProvisioner) GetPublicKey() []byte { - if x != nil { - return x.PublicKey - } - return nil -} - -func (x *JWKProvisioner) GetEncryptedPrivateKey() []byte { - if x != nil { - return x.EncryptedPrivateKey - } - return nil -} - -type OIDCProvisioner struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` - ClientSecret string `protobuf:"bytes,2,opt,name=client_secret,json=clientSecret,proto3" json:"client_secret,omitempty"` - ConfigurationEndpoint string `protobuf:"bytes,3,opt,name=configuration_endpoint,json=configurationEndpoint,proto3" json:"configuration_endpoint,omitempty"` - Admins []string `protobuf:"bytes,4,rep,name=admins,proto3" json:"admins,omitempty"` - Domains []string `protobuf:"bytes,5,rep,name=domains,proto3" json:"domains,omitempty"` - Groups []string `protobuf:"bytes,6,rep,name=groups,proto3" json:"groups,omitempty"` - ListenAddress string `protobuf:"bytes,7,opt,name=listen_address,json=listenAddress,proto3" json:"listen_address,omitempty"` - TenantId string `protobuf:"bytes,8,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` -} - -func (x *OIDCProvisioner) Reset() { - *x = OIDCProvisioner{} - if protoimpl.UnsafeEnabled { - mi := &file_linkedca_provisioners_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *OIDCProvisioner) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*OIDCProvisioner) ProtoMessage() {} - -func (x *OIDCProvisioner) ProtoReflect() protoreflect.Message { - mi := &file_linkedca_provisioners_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use OIDCProvisioner.ProtoReflect.Descriptor instead. -func (*OIDCProvisioner) Descriptor() ([]byte, []int) { - return file_linkedca_provisioners_proto_rawDescGZIP(), []int{8} -} - -func (x *OIDCProvisioner) GetClientId() string { - if x != nil { - return x.ClientId - } - return "" -} - -func (x *OIDCProvisioner) GetClientSecret() string { - if x != nil { - return x.ClientSecret - } - return "" -} - -func (x *OIDCProvisioner) GetConfigurationEndpoint() string { - if x != nil { - return x.ConfigurationEndpoint - } - return "" -} - -func (x *OIDCProvisioner) GetAdmins() []string { - if x != nil { - return x.Admins - } - return nil -} - -func (x *OIDCProvisioner) GetDomains() []string { - if x != nil { - return x.Domains - } - return nil -} - -func (x *OIDCProvisioner) GetGroups() []string { - if x != nil { - return x.Groups - } - return nil -} - -func (x *OIDCProvisioner) GetListenAddress() string { - if x != nil { - return x.ListenAddress - } - return "" -} - -func (x *OIDCProvisioner) GetTenantId() string { - if x != nil { - return x.TenantId - } - return "" -} - -type GCPProvisioner struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - ServiceAccounts []string `protobuf:"bytes,1,rep,name=service_accounts,json=serviceAccounts,proto3" json:"service_accounts,omitempty"` - ProjectIds []string `protobuf:"bytes,2,rep,name=project_ids,json=projectIds,proto3" json:"project_ids,omitempty"` - DisableCustomSans bool `protobuf:"varint,3,opt,name=disable_custom_sans,json=disableCustomSans,proto3" json:"disable_custom_sans,omitempty"` - DisableTrustOnFirstUse bool `protobuf:"varint,4,opt,name=disable_trust_on_first_use,json=disableTrustOnFirstUse,proto3" json:"disable_trust_on_first_use,omitempty"` - InstanceAge string `protobuf:"bytes,5,opt,name=instance_age,json=instanceAge,proto3" json:"instance_age,omitempty"` -} - -func (x *GCPProvisioner) Reset() { - *x = GCPProvisioner{} - if protoimpl.UnsafeEnabled { - mi := &file_linkedca_provisioners_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GCPProvisioner) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GCPProvisioner) ProtoMessage() {} - -func (x *GCPProvisioner) ProtoReflect() protoreflect.Message { - mi := &file_linkedca_provisioners_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GCPProvisioner.ProtoReflect.Descriptor instead. -func (*GCPProvisioner) Descriptor() ([]byte, []int) { - return file_linkedca_provisioners_proto_rawDescGZIP(), []int{9} -} - -func (x *GCPProvisioner) GetServiceAccounts() []string { - if x != nil { - return x.ServiceAccounts - } - return nil -} - -func (x *GCPProvisioner) GetProjectIds() []string { - if x != nil { - return x.ProjectIds - } - return nil -} - -func (x *GCPProvisioner) GetDisableCustomSans() bool { - if x != nil { - return x.DisableCustomSans - } - return false -} - -func (x *GCPProvisioner) GetDisableTrustOnFirstUse() bool { - if x != nil { - return x.DisableTrustOnFirstUse - } - return false -} - -func (x *GCPProvisioner) GetInstanceAge() string { - if x != nil { - return x.InstanceAge - } - return "" -} - -type AWSProvisioner struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Accounts []string `protobuf:"bytes,1,rep,name=accounts,proto3" json:"accounts,omitempty"` - DisableCustomSans bool `protobuf:"varint,2,opt,name=disable_custom_sans,json=disableCustomSans,proto3" json:"disable_custom_sans,omitempty"` - DisableTrustOnFirstUse bool `protobuf:"varint,3,opt,name=disable_trust_on_first_use,json=disableTrustOnFirstUse,proto3" json:"disable_trust_on_first_use,omitempty"` - InstanceAge string `protobuf:"bytes,4,opt,name=instance_age,json=instanceAge,proto3" json:"instance_age,omitempty"` -} - -func (x *AWSProvisioner) Reset() { - *x = AWSProvisioner{} - if protoimpl.UnsafeEnabled { - mi := &file_linkedca_provisioners_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AWSProvisioner) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AWSProvisioner) ProtoMessage() {} - -func (x *AWSProvisioner) ProtoReflect() protoreflect.Message { - mi := &file_linkedca_provisioners_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AWSProvisioner.ProtoReflect.Descriptor instead. -func (*AWSProvisioner) Descriptor() ([]byte, []int) { - return file_linkedca_provisioners_proto_rawDescGZIP(), []int{10} -} - -func (x *AWSProvisioner) GetAccounts() []string { - if x != nil { - return x.Accounts - } - return nil -} - -func (x *AWSProvisioner) GetDisableCustomSans() bool { - if x != nil { - return x.DisableCustomSans - } - return false -} - -func (x *AWSProvisioner) GetDisableTrustOnFirstUse() bool { - if x != nil { - return x.DisableTrustOnFirstUse - } - return false -} - -func (x *AWSProvisioner) GetInstanceAge() string { - if x != nil { - return x.InstanceAge - } - return "" -} - -type AzureProvisioner struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - TenantId string `protobuf:"bytes,1,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` - ResourceGroups []string `protobuf:"bytes,2,rep,name=resource_groups,json=resourceGroups,proto3" json:"resource_groups,omitempty"` - Audience string `protobuf:"bytes,3,opt,name=audience,proto3" json:"audience,omitempty"` - DisableCustomSans bool `protobuf:"varint,4,opt,name=disable_custom_sans,json=disableCustomSans,proto3" json:"disable_custom_sans,omitempty"` - DisableTrustOnFirstUse bool `protobuf:"varint,5,opt,name=disable_trust_on_first_use,json=disableTrustOnFirstUse,proto3" json:"disable_trust_on_first_use,omitempty"` -} - -func (x *AzureProvisioner) Reset() { - *x = AzureProvisioner{} - if protoimpl.UnsafeEnabled { - mi := &file_linkedca_provisioners_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AzureProvisioner) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AzureProvisioner) ProtoMessage() {} - -func (x *AzureProvisioner) ProtoReflect() protoreflect.Message { - mi := &file_linkedca_provisioners_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AzureProvisioner.ProtoReflect.Descriptor instead. -func (*AzureProvisioner) Descriptor() ([]byte, []int) { - return file_linkedca_provisioners_proto_rawDescGZIP(), []int{11} -} - -func (x *AzureProvisioner) GetTenantId() string { - if x != nil { - return x.TenantId - } - return "" -} - -func (x *AzureProvisioner) GetResourceGroups() []string { - if x != nil { - return x.ResourceGroups - } - return nil -} - -func (x *AzureProvisioner) GetAudience() string { - if x != nil { - return x.Audience - } - return "" -} - -func (x *AzureProvisioner) GetDisableCustomSans() bool { - if x != nil { - return x.DisableCustomSans - } - return false -} - -func (x *AzureProvisioner) GetDisableTrustOnFirstUse() bool { - if x != nil { - return x.DisableTrustOnFirstUse - } - return false -} - -type ACMEProvisioner struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - ForceCn bool `protobuf:"varint,1,opt,name=force_cn,json=forceCn,proto3" json:"force_cn,omitempty"` -} - -func (x *ACMEProvisioner) Reset() { - *x = ACMEProvisioner{} - if protoimpl.UnsafeEnabled { - mi := &file_linkedca_provisioners_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ACMEProvisioner) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ACMEProvisioner) ProtoMessage() {} - -func (x *ACMEProvisioner) ProtoReflect() protoreflect.Message { - mi := &file_linkedca_provisioners_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ACMEProvisioner.ProtoReflect.Descriptor instead. -func (*ACMEProvisioner) Descriptor() ([]byte, []int) { - return file_linkedca_provisioners_proto_rawDescGZIP(), []int{12} -} - -func (x *ACMEProvisioner) GetForceCn() bool { - if x != nil { - return x.ForceCn - } - return false -} - -type X5CProvisioner struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Roots [][]byte `protobuf:"bytes,1,rep,name=roots,proto3" json:"roots,omitempty"` -} - -func (x *X5CProvisioner) Reset() { - *x = X5CProvisioner{} - if protoimpl.UnsafeEnabled { - mi := &file_linkedca_provisioners_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *X5CProvisioner) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*X5CProvisioner) ProtoMessage() {} - -func (x *X5CProvisioner) ProtoReflect() protoreflect.Message { - mi := &file_linkedca_provisioners_proto_msgTypes[13] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use X5CProvisioner.ProtoReflect.Descriptor instead. -func (*X5CProvisioner) Descriptor() ([]byte, []int) { - return file_linkedca_provisioners_proto_rawDescGZIP(), []int{13} -} - -func (x *X5CProvisioner) GetRoots() [][]byte { - if x != nil { - return x.Roots - } - return nil -} - -type K8SSAProvisioner struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - PublicKeys [][]byte `protobuf:"bytes,1,rep,name=public_keys,json=publicKeys,proto3" json:"public_keys,omitempty"` -} - -func (x *K8SSAProvisioner) Reset() { - *x = K8SSAProvisioner{} - if protoimpl.UnsafeEnabled { - mi := &file_linkedca_provisioners_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *K8SSAProvisioner) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*K8SSAProvisioner) ProtoMessage() {} - -func (x *K8SSAProvisioner) ProtoReflect() protoreflect.Message { - mi := &file_linkedca_provisioners_proto_msgTypes[14] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use K8SSAProvisioner.ProtoReflect.Descriptor instead. -func (*K8SSAProvisioner) Descriptor() ([]byte, []int) { - return file_linkedca_provisioners_proto_rawDescGZIP(), []int{14} -} - -func (x *K8SSAProvisioner) GetPublicKeys() [][]byte { - if x != nil { - return x.PublicKeys - } - return nil -} - -type SSHPOPProvisioner struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *SSHPOPProvisioner) Reset() { - *x = SSHPOPProvisioner{} - if protoimpl.UnsafeEnabled { - mi := &file_linkedca_provisioners_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SSHPOPProvisioner) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SSHPOPProvisioner) ProtoMessage() {} - -func (x *SSHPOPProvisioner) ProtoReflect() protoreflect.Message { - mi := &file_linkedca_provisioners_proto_msgTypes[15] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SSHPOPProvisioner.ProtoReflect.Descriptor instead. -func (*SSHPOPProvisioner) Descriptor() ([]byte, []int) { - return file_linkedca_provisioners_proto_rawDescGZIP(), []int{15} -} - -type SCEPProvisioner struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - ForceCn bool `protobuf:"varint,1,opt,name=force_cn,json=forceCn,proto3" json:"force_cn,omitempty"` - Challenge string `protobuf:"bytes,2,opt,name=challenge,proto3" json:"challenge,omitempty"` - Capabilities []string `protobuf:"bytes,3,rep,name=capabilities,proto3" json:"capabilities,omitempty"` - MinimumPublicKeyLength int32 `protobuf:"varint,4,opt,name=minimum_public_key_length,json=minimumPublicKeyLength,proto3" json:"minimum_public_key_length,omitempty"` -} - -func (x *SCEPProvisioner) Reset() { - *x = SCEPProvisioner{} - if protoimpl.UnsafeEnabled { - mi := &file_linkedca_provisioners_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SCEPProvisioner) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SCEPProvisioner) ProtoMessage() {} - -func (x *SCEPProvisioner) ProtoReflect() protoreflect.Message { - mi := &file_linkedca_provisioners_proto_msgTypes[16] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SCEPProvisioner.ProtoReflect.Descriptor instead. -func (*SCEPProvisioner) Descriptor() ([]byte, []int) { - return file_linkedca_provisioners_proto_rawDescGZIP(), []int{16} -} - -func (x *SCEPProvisioner) GetForceCn() bool { - if x != nil { - return x.ForceCn - } - return false -} - -func (x *SCEPProvisioner) GetChallenge() string { - if x != nil { - return x.Challenge - } - return "" -} - -func (x *SCEPProvisioner) GetCapabilities() []string { - if x != nil { - return x.Capabilities - } - return nil -} - -func (x *SCEPProvisioner) GetMinimumPublicKeyLength() int32 { - if x != nil { - return x.MinimumPublicKeyLength - } - return 0 -} - -var File_linkedca_provisioners_proto protoreflect.FileDescriptor - -var file_linkedca_provisioners_proto_rawDesc = []byte{ - 0x0a, 0x1b, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x6c, - 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x22, 0xfe, 0x03, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, - 0x64, 0x63, 0x61, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x36, - 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1c, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x07, 0x64, - 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x28, 0x0a, 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, - 0x61, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, - 0x12, 0x23, 0x0a, 0x0d, 0x78, 0x35, 0x30, 0x39, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, - 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x78, 0x35, 0x30, 0x39, 0x54, 0x65, 0x6d, - 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x78, 0x35, 0x30, 0x39, 0x5f, 0x74, 0x65, - 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x10, 0x78, 0x35, 0x30, 0x39, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, - 0x61, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x73, 0x68, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x73, 0x73, 0x68, 0x54, 0x65, - 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x73, 0x68, 0x5f, 0x74, 0x65, - 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0f, 0x73, 0x73, 0x68, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x61, - 0x74, 0x61, 0x22, 0x74, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, - 0x4f, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4a, 0x57, 0x4b, 0x10, 0x01, 0x12, 0x08, 0x0a, - 0x04, 0x4f, 0x49, 0x44, 0x43, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x47, 0x43, 0x50, 0x10, 0x03, - 0x12, 0x07, 0x0a, 0x03, 0x41, 0x57, 0x53, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x5a, 0x55, - 0x52, 0x45, 0x10, 0x05, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x43, 0x4d, 0x45, 0x10, 0x06, 0x12, 0x07, - 0x0a, 0x03, 0x58, 0x35, 0x43, 0x10, 0x07, 0x12, 0x09, 0x0a, 0x05, 0x4b, 0x38, 0x53, 0x53, 0x41, - 0x10, 0x08, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x53, 0x48, 0x50, 0x4f, 0x50, 0x10, 0x09, 0x12, 0x08, - 0x0a, 0x04, 0x53, 0x43, 0x45, 0x50, 0x10, 0x0a, 0x22, 0x86, 0x04, 0x0a, 0x12, 0x50, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, - 0x2c, 0x0a, 0x03, 0x4a, 0x57, 0x4b, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, - 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x4a, 0x57, 0x4b, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, 0x4a, 0x57, 0x4b, 0x12, 0x2f, 0x0a, - 0x04, 0x4f, 0x49, 0x44, 0x43, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x69, - 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x4f, 0x49, 0x44, 0x43, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x04, 0x4f, 0x49, 0x44, 0x43, 0x12, 0x2c, - 0x0a, 0x03, 0x47, 0x43, 0x50, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, - 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x47, 0x43, 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, 0x47, 0x43, 0x50, 0x12, 0x2c, 0x0a, 0x03, - 0x41, 0x57, 0x53, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, - 0x65, 0x64, 0x63, 0x61, 0x2e, 0x41, 0x57, 0x53, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, 0x41, 0x57, 0x53, 0x12, 0x32, 0x0a, 0x05, 0x41, 0x7a, - 0x75, 0x72, 0x65, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, - 0x65, 0x64, 0x63, 0x61, 0x2e, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x05, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x12, 0x2f, - 0x0a, 0x04, 0x41, 0x43, 0x4d, 0x45, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, - 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x41, 0x43, 0x4d, 0x45, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x04, 0x41, 0x43, 0x4d, 0x45, 0x12, - 0x2c, 0x0a, 0x03, 0x58, 0x35, 0x43, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, - 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x58, 0x35, 0x43, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, 0x58, 0x35, 0x43, 0x12, 0x32, 0x0a, - 0x05, 0x4b, 0x38, 0x73, 0x53, 0x41, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, - 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x4b, 0x38, 0x73, 0x53, 0x41, 0x50, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x05, 0x4b, 0x38, 0x73, 0x53, - 0x41, 0x12, 0x35, 0x0a, 0x06, 0x53, 0x53, 0x48, 0x50, 0x4f, 0x50, 0x18, 0x1c, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x53, 0x53, 0x48, - 0x50, 0x4f, 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x48, 0x00, - 0x52, 0x06, 0x53, 0x53, 0x48, 0x50, 0x4f, 0x50, 0x12, 0x2f, 0x0a, 0x04, 0x53, 0x43, 0x45, 0x50, - 0x18, 0x1d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, - 0x61, 0x2e, 0x53, 0x43, 0x45, 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x48, 0x00, 0x52, 0x04, 0x53, 0x43, 0x45, 0x50, 0x42, 0x06, 0x0a, 0x04, 0x64, 0x61, 0x74, - 0x61, 0x22, 0x4c, 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x4c, 0x69, 0x73, 0x74, 0x12, 0x39, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x69, 0x6e, - 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x22, - 0x82, 0x01, 0x0a, 0x06, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x28, 0x0a, 0x04, 0x78, 0x35, - 0x30, 0x39, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, - 0x64, 0x63, 0x61, 0x2e, 0x58, 0x35, 0x30, 0x39, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x04, - 0x78, 0x35, 0x30, 0x39, 0x12, 0x25, 0x0a, 0x03, 0x73, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x53, 0x53, 0x48, - 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x52, 0x03, 0x73, 0x73, 0x68, 0x12, 0x27, 0x0a, 0x0f, 0x64, - 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x6e, - 0x65, 0x77, 0x61, 0x6c, 0x22, 0x59, 0x0a, 0x0a, 0x58, 0x35, 0x30, 0x39, 0x43, 0x6c, 0x61, 0x69, - 0x6d, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x31, 0x0a, 0x09, - 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x13, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x09, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, - 0x9d, 0x01, 0x0a, 0x09, 0x53, 0x53, 0x48, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x18, 0x0a, - 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, - 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x3a, 0x0a, 0x0e, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x13, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x3a, 0x0a, 0x0e, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x64, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x69, - 0x6e, 0x6b, 0x65, 0x64, 0x63, 0x61, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x52, 0x0d, 0x68, 0x6f, 0x73, 0x74, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, - 0x49, 0x0a, 0x09, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, - 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, - 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x6e, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x78, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x61, 0x78, 0x22, 0x63, 0x0a, 0x0e, 0x4a, 0x57, - 0x4b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, - 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x32, 0x0a, 0x15, 0x65, - 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, - 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x13, 0x65, 0x6e, 0x63, 0x72, - 0x79, 0x70, 0x74, 0x65, 0x64, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x22, - 0x98, 0x02, 0x0a, 0x0f, 0x4f, 0x49, 0x44, 0x43, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, - 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, - 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x35, 0x0a, 0x16, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, - 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x61, 0x64, - 0x6d, 0x69, 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, - 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x16, - 0x0a, 0x06, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, - 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, - 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, - 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, - 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x22, 0xeb, 0x01, 0x0a, 0x0e, 0x47, - 0x43, 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x29, 0x0a, - 0x10, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x6a, - 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x70, - 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x69, 0x73, - 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x73, 0x61, 0x6e, 0x73, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, - 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x61, 0x6e, 0x73, 0x12, 0x3a, 0x0a, 0x1a, 0x64, 0x69, 0x73, - 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x5f, 0x6f, 0x6e, 0x5f, 0x66, 0x69, - 0x72, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x64, - 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x4f, 0x6e, 0x46, 0x69, 0x72, - 0x73, 0x74, 0x55, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, - 0x65, 0x5f, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x73, - 0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x67, 0x65, 0x22, 0xbb, 0x01, 0x0a, 0x0e, 0x41, 0x57, 0x53, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x61, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x61, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, - 0x6c, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x73, 0x61, 0x6e, 0x73, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x75, 0x73, - 0x74, 0x6f, 0x6d, 0x53, 0x61, 0x6e, 0x73, 0x12, 0x3a, 0x0a, 0x1a, 0x64, 0x69, 0x73, 0x61, 0x62, - 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x5f, 0x6f, 0x6e, 0x5f, 0x66, 0x69, 0x72, 0x73, - 0x74, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x64, 0x69, 0x73, - 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x4f, 0x6e, 0x46, 0x69, 0x72, 0x73, 0x74, - 0x55, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, - 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, - 0x6e, 0x63, 0x65, 0x41, 0x67, 0x65, 0x22, 0xe0, 0x01, 0x0a, 0x10, 0x41, 0x7a, 0x75, 0x72, 0x65, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x74, - 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, - 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, - 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, - 0x73, 0x61, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, - 0x62, 0x6c, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x61, 0x6e, 0x73, 0x12, 0x3a, 0x0a, - 0x1a, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x5f, 0x6f, - 0x6e, 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x4f, - 0x6e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x55, 0x73, 0x65, 0x22, 0x2c, 0x0a, 0x0f, 0x41, 0x43, 0x4d, - 0x45, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, - 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, - 0x66, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6e, 0x22, 0x26, 0x0a, 0x0e, 0x58, 0x35, 0x43, 0x50, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x6f, - 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x22, - 0x33, 0x0a, 0x10, 0x4b, 0x38, 0x73, 0x53, 0x41, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, - 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x4b, 0x65, 0x79, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x53, 0x53, 0x48, 0x50, 0x4f, 0x50, 0x50, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x22, 0xa9, 0x01, 0x0a, 0x0f, 0x53, 0x43, - 0x45, 0x50, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x19, 0x0a, - 0x08, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x07, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x68, 0x61, 0x6c, - 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, 0x61, - 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, - 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x61, - 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x19, 0x6d, 0x69, - 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, - 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x16, 0x6d, - 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x4c, - 0x65, 0x6e, 0x67, 0x74, 0x68, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x73, 0x74, 0x65, 0x70, 0x2f, 0x63, 0x65, - 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2f, 0x6c, 0x69, 0x6e, 0x6b, 0x65, - 0x64, 0x63, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_linkedca_provisioners_proto_rawDescOnce sync.Once - file_linkedca_provisioners_proto_rawDescData = file_linkedca_provisioners_proto_rawDesc -) - -func file_linkedca_provisioners_proto_rawDescGZIP() []byte { - file_linkedca_provisioners_proto_rawDescOnce.Do(func() { - file_linkedca_provisioners_proto_rawDescData = protoimpl.X.CompressGZIP(file_linkedca_provisioners_proto_rawDescData) - }) - return file_linkedca_provisioners_proto_rawDescData -} - -var file_linkedca_provisioners_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_linkedca_provisioners_proto_msgTypes = make([]protoimpl.MessageInfo, 17) -var file_linkedca_provisioners_proto_goTypes = []interface{}{ - (Provisioner_Type)(0), // 0: linkedca.Provisioner.Type - (*Provisioner)(nil), // 1: linkedca.Provisioner - (*ProvisionerDetails)(nil), // 2: linkedca.ProvisionerDetails - (*ProvisionerList)(nil), // 3: linkedca.ProvisionerList - (*Claims)(nil), // 4: linkedca.Claims - (*X509Claims)(nil), // 5: linkedca.X509Claims - (*SSHClaims)(nil), // 6: linkedca.SSHClaims - (*Durations)(nil), // 7: linkedca.Durations - (*JWKProvisioner)(nil), // 8: linkedca.JWKProvisioner - (*OIDCProvisioner)(nil), // 9: linkedca.OIDCProvisioner - (*GCPProvisioner)(nil), // 10: linkedca.GCPProvisioner - (*AWSProvisioner)(nil), // 11: linkedca.AWSProvisioner - (*AzureProvisioner)(nil), // 12: linkedca.AzureProvisioner - (*ACMEProvisioner)(nil), // 13: linkedca.ACMEProvisioner - (*X5CProvisioner)(nil), // 14: linkedca.X5CProvisioner - (*K8SSAProvisioner)(nil), // 15: linkedca.K8sSAProvisioner - (*SSHPOPProvisioner)(nil), // 16: linkedca.SSHPOPProvisioner - (*SCEPProvisioner)(nil), // 17: linkedca.SCEPProvisioner -} -var file_linkedca_provisioners_proto_depIdxs = []int32{ - 0, // 0: linkedca.Provisioner.type:type_name -> linkedca.Provisioner.Type - 2, // 1: linkedca.Provisioner.details:type_name -> linkedca.ProvisionerDetails - 4, // 2: linkedca.Provisioner.claims:type_name -> linkedca.Claims - 8, // 3: linkedca.ProvisionerDetails.JWK:type_name -> linkedca.JWKProvisioner - 9, // 4: linkedca.ProvisionerDetails.OIDC:type_name -> linkedca.OIDCProvisioner - 10, // 5: linkedca.ProvisionerDetails.GCP:type_name -> linkedca.GCPProvisioner - 11, // 6: linkedca.ProvisionerDetails.AWS:type_name -> linkedca.AWSProvisioner - 12, // 7: linkedca.ProvisionerDetails.Azure:type_name -> linkedca.AzureProvisioner - 13, // 8: linkedca.ProvisionerDetails.ACME:type_name -> linkedca.ACMEProvisioner - 14, // 9: linkedca.ProvisionerDetails.X5C:type_name -> linkedca.X5CProvisioner - 15, // 10: linkedca.ProvisionerDetails.K8sSA:type_name -> linkedca.K8sSAProvisioner - 16, // 11: linkedca.ProvisionerDetails.SSHPOP:type_name -> linkedca.SSHPOPProvisioner - 17, // 12: linkedca.ProvisionerDetails.SCEP:type_name -> linkedca.SCEPProvisioner - 1, // 13: linkedca.ProvisionerList.provisioners:type_name -> linkedca.Provisioner - 5, // 14: linkedca.Claims.x509:type_name -> linkedca.X509Claims - 6, // 15: linkedca.Claims.ssh:type_name -> linkedca.SSHClaims - 7, // 16: linkedca.X509Claims.durations:type_name -> linkedca.Durations - 7, // 17: linkedca.SSHClaims.user_durations:type_name -> linkedca.Durations - 7, // 18: linkedca.SSHClaims.host_durations:type_name -> linkedca.Durations - 19, // [19:19] is the sub-list for method output_type - 19, // [19:19] is the sub-list for method input_type - 19, // [19:19] is the sub-list for extension type_name - 19, // [19:19] is the sub-list for extension extendee - 0, // [0:19] is the sub-list for field type_name -} - -func init() { file_linkedca_provisioners_proto_init() } -func file_linkedca_provisioners_proto_init() { - if File_linkedca_provisioners_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_linkedca_provisioners_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Provisioner); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_linkedca_provisioners_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ProvisionerDetails); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_linkedca_provisioners_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ProvisionerList); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_linkedca_provisioners_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Claims); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_linkedca_provisioners_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*X509Claims); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_linkedca_provisioners_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SSHClaims); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_linkedca_provisioners_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Durations); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_linkedca_provisioners_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*JWKProvisioner); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_linkedca_provisioners_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OIDCProvisioner); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_linkedca_provisioners_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GCPProvisioner); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_linkedca_provisioners_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AWSProvisioner); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_linkedca_provisioners_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AzureProvisioner); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_linkedca_provisioners_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ACMEProvisioner); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_linkedca_provisioners_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*X5CProvisioner); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_linkedca_provisioners_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*K8SSAProvisioner); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_linkedca_provisioners_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SSHPOPProvisioner); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_linkedca_provisioners_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SCEPProvisioner); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_linkedca_provisioners_proto_msgTypes[1].OneofWrappers = []interface{}{ - (*ProvisionerDetails_JWK)(nil), - (*ProvisionerDetails_OIDC)(nil), - (*ProvisionerDetails_GCP)(nil), - (*ProvisionerDetails_AWS)(nil), - (*ProvisionerDetails_Azure)(nil), - (*ProvisionerDetails_ACME)(nil), - (*ProvisionerDetails_X5C)(nil), - (*ProvisionerDetails_K8SSA)(nil), - (*ProvisionerDetails_SSHPOP)(nil), - (*ProvisionerDetails_SCEP)(nil), - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_linkedca_provisioners_proto_rawDesc, - NumEnums: 1, - NumMessages: 17, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_linkedca_provisioners_proto_goTypes, - DependencyIndexes: file_linkedca_provisioners_proto_depIdxs, - EnumInfos: file_linkedca_provisioners_proto_enumTypes, - MessageInfos: file_linkedca_provisioners_proto_msgTypes, - }.Build() - File_linkedca_provisioners_proto = out.File - file_linkedca_provisioners_proto_rawDesc = nil - file_linkedca_provisioners_proto_goTypes = nil - file_linkedca_provisioners_proto_depIdxs = nil -} diff --git a/linkedca/provisioners.proto b/linkedca/provisioners.proto deleted file mode 100644 index ae047391..00000000 --- a/linkedca/provisioners.proto +++ /dev/null @@ -1,133 +0,0 @@ -syntax = "proto3"; - -package linkedca; - -option go_package = "github.com/smallstep/certificates/linkedca"; - -message Provisioner { - enum Type { - NOOP = 0; - JWK = 1; - OIDC = 2; - GCP = 3; - AWS = 4; - AZURE = 5; - ACME = 6; - X5C = 7; - K8SSA = 8; - SSHPOP = 9; - SCEP = 10; - } - string id = 1; - string authority_id = 2; - Type type = 3; - string name = 4; - ProvisionerDetails details = 5; - Claims claims = 6; - bytes x509_template = 7; - bytes x509_template_data = 8; - bytes ssh_template = 9; - bytes ssh_template_data = 10; -} - -message ProvisionerDetails { - oneof data { - JWKProvisioner JWK = 20; - OIDCProvisioner OIDC = 21; - GCPProvisioner GCP = 22; - AWSProvisioner AWS = 23; - AzureProvisioner Azure = 24; - ACMEProvisioner ACME = 25; - X5CProvisioner X5C = 26; - K8sSAProvisioner K8sSA = 27; - SSHPOPProvisioner SSHPOP = 28; - SCEPProvisioner SCEP = 29; - } -} - -message ProvisionerList { - repeated Provisioner provisioners = 1; -} - -message Claims { - X509Claims x509 = 1; - SSHClaims ssh = 2; - bool disable_renewal = 3; -} - -message X509Claims { - bool enabled = 1; - Durations durations = 2; -} - -message SSHClaims { - bool enabled = 1; - Durations user_durations = 2; - Durations host_durations = 3; -} - -message Durations { - string default = 1; - string min = 2; - string max = 3; -} - -message JWKProvisioner { - bytes public_key = 1; - bytes encrypted_private_key = 2; -} - -message OIDCProvisioner { - string client_id = 1; - string client_secret = 2; - string configuration_endpoint = 3; - repeated string admins = 4; - repeated string domains = 5; - repeated string groups = 6; - string listen_address = 7; - string tenant_id = 8; -} - -message GCPProvisioner { - repeated string service_accounts = 1; - repeated string project_ids = 2; - bool disable_custom_sans = 3; - bool disable_trust_on_first_use = 4; - string instance_age = 5; -} - -message AWSProvisioner { - repeated string accounts = 1; - bool disable_custom_sans = 2; - bool disable_trust_on_first_use = 3; - string instance_age = 4; -} - -message AzureProvisioner { - string tenant_id = 1; - repeated string resource_groups = 2; - string audience = 3; - bool disable_custom_sans = 4; - bool disable_trust_on_first_use = 5; -} - -message ACMEProvisioner { - bool force_cn = 1; -} - -message X5CProvisioner { - repeated bytes roots = 1; -} - -message K8sSAProvisioner { - repeated bytes public_keys = 1; -} - -message SSHPOPProvisioner {} - -message SCEPProvisioner { - bool force_cn = 1; - string challenge = 2; - repeated string capabilities = 3; - int32 minimum_public_key_length = 4; -} \ No newline at end of file diff --git a/scep/api/api.go b/scep/api/api.go index e64eef83..7036ffcb 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -67,8 +67,8 @@ func New(scepAuth scep.Interface) api.RouterHandler { // Route traffic and implement the Router interface. func (h *Handler) Route(r api.Router) { getLink := h.Auth.GetLinkExplicit - r.MethodFunc(http.MethodGet, getLink("{provisionerID}", false, nil), h.lookupProvisioner(h.Get)) - r.MethodFunc(http.MethodPost, getLink("{provisionerID}", false, nil), h.lookupProvisioner(h.Post)) + r.MethodFunc(http.MethodGet, getLink("{name}", false, nil), h.lookupProvisioner(h.Get)) + r.MethodFunc(http.MethodPost, getLink("{name}", false, nil), h.lookupProvisioner(h.Post)) } // Get handles all SCEP GET requests @@ -185,14 +185,14 @@ func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) { func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP { return func(w http.ResponseWriter, r *http.Request) { - name := chi.URLParam(r, "provisionerID") - provisionerID, err := url.PathUnescape(name) + nameEscaped := chi.URLParam(r, "name") + name, err := url.PathUnescape(nameEscaped) if err != nil { api.WriteError(w, errors.Errorf("error url unescaping provisioner id '%s'", name)) return } - p, err := h.Auth.LoadProvisionerByID("scep/" + provisionerID) + p, err := h.Auth.LoadProvisionerByName(name) if err != nil { api.WriteError(w, err) return diff --git a/scep/authority.go b/scep/authority.go index 47d1d41c..5c863b63 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -20,7 +20,7 @@ import ( // Interface is the SCEP authority interface. type Interface interface { - LoadProvisionerByID(string) (provisioner.Interface, error) + LoadProvisionerByName(string) (provisioner.Interface, error) GetLinkExplicit(provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string GetCACertificates() ([]*x509.Certificate, error) @@ -56,7 +56,7 @@ type AuthorityOptions struct { // SignAuthority is the interface for a signing authority type SignAuthority interface { Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) - LoadProvisionerByID(string) (provisioner.Interface, error) + LoadProvisionerByName(string) (provisioner.Interface, error) } // New returns a new Authority that implements the SCEP interface. @@ -92,10 +92,10 @@ var ( } ) -// LoadProvisionerByID calls out to the SignAuthority interface to load a -// provisioner by ID. -func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error) { - return a.signAuth.LoadProvisionerByID(id) +// LoadProvisionerByName calls out to the SignAuthority interface to load a +// provisioner by name. +func (a *Authority) LoadProvisionerByName(name string) (provisioner.Interface, error) { + return a.signAuth.LoadProvisionerByName(name) } // GetLinkExplicit returns the requested link from the directory. From 5679c9933df5077736fa1d242cef7490f0157a28 Mon Sep 17 00:00:00 2001 From: max furman Date: Sat, 3 Jul 2021 12:08:30 -0700 Subject: [PATCH 87/89] Fixes from PR review --- authority/admin/api/middleware.go | 12 ------------ authority/administrator/collection.go | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/authority/admin/api/middleware.go b/authority/admin/api/middleware.go index 137bd6f7..0261047a 100644 --- a/authority/admin/api/middleware.go +++ b/authority/admin/api/middleware.go @@ -52,15 +52,3 @@ const ( // adminContextKey account key adminContextKey = ContextKey("admin") ) - -/* -// adminFromContext searches the context for the token. Returns the -// token or an error. -func adminFromContext(ctx context.Context) (*linkedca.Admin, error) { - val, ok := ctx.Value(adminContextKey).(*linkedca.Admin) - if !ok || val == nil { - return nil, admin.NewErrorISE("admin not in context") - } - return val, nil -} -*/ diff --git a/authority/administrator/collection.go b/authority/administrator/collection.go index 47d3a35d..ff04a41f 100644 --- a/authority/administrator/collection.go +++ b/authority/administrator/collection.go @@ -184,7 +184,7 @@ func (c *Collection) Update(id string, nu *linkedca.Admin) (*linkedca.Admin, err return nil, admin.NewError(admin.ErrorNotFoundType, "admin %s not found", adm.Id) } if adm.Type == nu.Type { - return nil, admin.NewError(admin.ErrorBadRequestType, "admin %s already has type %s", id, adm.Type) + return adm, nil } if adm.Type == linkedca.Admin_SUPER_ADMIN && c.SuperCount() == 1 { return nil, admin.NewError(admin.ErrorBadRequestType, "cannot change role of last super admin") From bc1434138732839236411096912752e133e7f27d Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 6 Jul 2021 16:35:00 +0200 Subject: [PATCH 88/89] Fix bootstrap command. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d3c0573f..235517b2 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ ci: testcgo build bootstra%: # Using a released version of golangci-lint to take into account custom replacements in their go.mod - $Q curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.39.0 + $Q curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.39.0 .PHONY: bootstra% From 1df21b9b6a5f86df54cba905110212b583b5a1b7 Mon Sep 17 00:00:00 2001 From: max furman Date: Tue, 6 Jul 2021 17:14:13 -0700 Subject: [PATCH 89/89] Addressing comments in PR review - added a bit of validation to admin create and update - using protojson where possible in admin api - fixing a few instances of admin -> acme in errors --- authority/admin/api/admin.go | 27 ++++++++++++++++++++++++--- authority/admin/api/middleware.go | 2 +- authority/admin/api/provisioner.go | 2 +- authority/admin/db/nosql/nosql.go | 4 ++-- authority/admin/errors.go | 12 ++++++------ authority/authority.go | 6 ++++++ ca/adminClient.go | 6 +++--- 7 files changed, 43 insertions(+), 16 deletions(-) diff --git a/authority/admin/api/admin.go b/authority/admin/api/admin.go index d92af5b2..bf79ebcf 100644 --- a/authority/admin/api/admin.go +++ b/authority/admin/api/admin.go @@ -18,6 +18,17 @@ type CreateAdminRequest struct { // Validate validates a new-admin request body. func (car *CreateAdminRequest) Validate() error { + if car.Subject == "" { + return admin.NewError(admin.ErrorBadRequestType, "subject cannot be empty") + } + if car.Provisioner == "" { + return admin.NewError(admin.ErrorBadRequestType, "provisioner cannot be empty") + } + switch car.Type { + case linkedca.Admin_SUPER_ADMIN, linkedca.Admin_ADMIN: + default: + return admin.NewError(admin.ErrorBadRequestType, "invalid value for admin type") + } return nil } @@ -34,6 +45,11 @@ type UpdateAdminRequest struct { // Validate validates a new-admin request body. func (uar *UpdateAdminRequest) Validate() error { + switch uar.Type { + case linkedca.Admin_SUPER_ADMIN, linkedca.Admin_ADMIN: + default: + return admin.NewError(admin.ErrorBadRequestType, "invalid value for admin type") + } return nil } @@ -52,7 +68,7 @@ func (h *Handler) GetAdmin(w http.ResponseWriter, r *http.Request) { "admin %s not found", id)) return } - api.JSON(w, adm) + api.ProtoJSON(w, adm) } // GetAdmins returns a segment of admins associated with the authority. @@ -104,7 +120,7 @@ func (h *Handler) CreateAdmin(w http.ResponseWriter, r *http.Request) { return } - api.JSON(w, adm) + api.ProtoJSONStatus(w, adm, http.StatusCreated) } // DeleteAdmin deletes admin. @@ -127,6 +143,11 @@ func (h *Handler) UpdateAdmin(w http.ResponseWriter, r *http.Request) { return } + if err := body.Validate(); err != nil { + api.WriteError(w, err) + return + } + id := chi.URLParam(r, "id") adm, err := h.auth.UpdateAdmin(r.Context(), id, &linkedca.Admin{Type: body.Type}) @@ -135,5 +156,5 @@ func (h *Handler) UpdateAdmin(w http.ResponseWriter, r *http.Request) { return } - api.JSON(w, adm) + api.ProtoJSON(w, adm) } diff --git a/authority/admin/api/middleware.go b/authority/admin/api/middleware.go index 0261047a..90289f85 100644 --- a/authority/admin/api/middleware.go +++ b/authority/admin/api/middleware.go @@ -14,7 +14,7 @@ type nextHTTP = func(http.ResponseWriter, *http.Request) // is enabled before servicing requests. func (h *Handler) requireAPIEnabled(next nextHTTP) nextHTTP { return func(w http.ResponseWriter, r *http.Request) { - if h.db == nil { + if !h.auth.IsAdminAPIEnabled() { api.WriteError(w, admin.NewError(admin.ErrorNotImplementedType, "administration API not enabled")) return diff --git a/authority/admin/api/provisioner.go b/authority/admin/api/provisioner.go index e63a4417..fd1a02d5 100644 --- a/authority/admin/api/provisioner.go +++ b/authority/admin/api/provisioner.go @@ -171,5 +171,5 @@ func (h *Handler) UpdateProvisioner(w http.ResponseWriter, r *http.Request) { api.WriteError(w, err) return } - api.ProtoJSONStatus(w, nu, http.StatusOK) + api.ProtoJSON(w, nu) } diff --git a/authority/admin/db/nosql/nosql.go b/authority/admin/db/nosql/nosql.go index 2ce4297c..18599b02 100644 --- a/authority/admin/db/nosql/nosql.go +++ b/authority/admin/db/nosql/nosql.go @@ -15,7 +15,7 @@ var ( provisionersTable = []byte("provisioners") ) -// DB is a struct that implements the AcmeDB interface. +// DB is a struct that implements the AdminDB interface. type DB struct { db nosqlDB.DB authorityID string @@ -54,7 +54,7 @@ func (db *DB) save(ctx context.Context, id string, nu interface{}, old interface } else { oldB, err = json.Marshal(old) if err != nil { - return errors.Wrapf(err, "error marshaling acme type: %s, value: %v", typ, old) + return errors.Wrapf(err, "error marshaling admin type: %s, value: %v", typ, old) } } diff --git a/authority/admin/errors.go b/authority/admin/errors.go index 81ae6d6f..607093b0 100644 --- a/authority/admin/errors.go +++ b/authority/admin/errors.go @@ -12,7 +12,7 @@ import ( "github.com/smallstep/certificates/logging" ) -// ProblemType is the type of the ACME problem. +// ProblemType is the type of the Admin problem. type ProblemType int const ( @@ -33,7 +33,7 @@ const ( ErrorServerInternalType ) -// String returns the string representation of the acme problem type, +// String returns the string representation of the admin problem type, // fulfilling the Stringer interface. func (ap ProblemType) String() string { switch ap { @@ -73,17 +73,17 @@ var ( ErrorNotFoundType: { typ: ErrorNotFoundType.String(), details: "resource not found", - status: 400, + status: http.StatusNotFound, }, ErrorAuthorityMismatchType: { typ: ErrorAuthorityMismatchType.String(), details: "resource not owned by authority", - status: 401, + status: http.StatusUnauthorized, }, ErrorDeletedType: { typ: ErrorDeletedType.String(), details: "resource is deleted", - status: http.StatusUnauthorized, + status: http.StatusNotFound, }, ErrorNotImplementedType: { typ: ErrorNotImplementedType.String(), @@ -104,7 +104,7 @@ var ( } ) -// Error represents an ACME +// Error represents an Admin type Error struct { Type string `json:"type"` Detail string `json:"detail"` diff --git a/authority/authority.go b/authority/authority.go index dbe803ca..0f171fa7 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -508,6 +508,12 @@ func (a *Authority) GetAdminDatabase() admin.DB { return a.adminDB } +// IsAdminAPIEnabled returns a boolean indicating whether the Admin API has +// been enabled. +func (a *Authority) IsAdminAPIEnabled() bool { + return a.config.AuthorityConfig.EnableAdmin +} + // Shutdown safely shuts down any clients, databases, etc. held by the Authority. func (a *Authority) Shutdown() error { if err := a.keyManager.Close(); err != nil { diff --git a/ca/adminClient.go b/ca/adminClient.go index da2ff566..2f3d4b5d 100644 --- a/ca/adminClient.go +++ b/ca/adminClient.go @@ -132,7 +132,7 @@ retry: return nil, readAdminError(resp.Body) } var adm = new(linkedca.Admin) - if err := readJSON(resp.Body, adm); err != nil { + if err := readProtoJSON(resp.Body, adm); err != nil { return nil, errors.Wrapf(err, "error reading %s", u) } return adm, nil @@ -270,7 +270,7 @@ retry: return nil, readAdminError(resp.Body) } var adm = new(linkedca.Admin) - if err := readJSON(resp.Body, adm); err != nil { + if err := readProtoJSON(resp.Body, adm); err != nil { return nil, errors.Wrapf(err, "error reading %s", u) } return adm, nil @@ -334,7 +334,7 @@ retry: return nil, readAdminError(resp.Body) } var adm = new(linkedca.Admin) - if err := readJSON(resp.Body, adm); err != nil { + if err := readProtoJSON(resp.Body, adm); err != nil { return nil, errors.Wrapf(err, "error reading %s", u) } return adm, nil