forked from TrueCloudLab/certificates
Add first working version of External Account Binding
This commit is contained in:
parent
b9743b36e1
commit
f81d49d963
13 changed files with 353 additions and 22 deletions
|
@ -4,6 +4,7 @@ import (
|
|||
"crypto"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
@ -40,3 +41,12 @@ func KeyToID(jwk *jose.JSONWebKey) (string, error) {
|
|||
}
|
||||
return base64.RawURLEncoding.EncodeToString(kid), nil
|
||||
}
|
||||
|
||||
type ExternalAccountKey struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
AccountID string `json:"-"`
|
||||
KeyBytes []byte `json:"-"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
BoundAt time.Time `json:"boundAt,omitempty"`
|
||||
}
|
||||
|
|
|
@ -1,20 +1,26 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/smallstep/certificates/acme"
|
||||
"github.com/smallstep/certificates/api"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/logging"
|
||||
|
||||
squarejose "gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
// NewAccountRequest represents the payload for a new account request.
|
||||
type NewAccountRequest struct {
|
||||
Contact []string `json:"contact"`
|
||||
OnlyReturnExisting bool `json:"onlyReturnExisting"`
|
||||
TermsOfServiceAgreed bool `json:"termsOfServiceAgreed"`
|
||||
Contact []string `json:"contact"`
|
||||
OnlyReturnExisting bool `json:"onlyReturnExisting"`
|
||||
TermsOfServiceAgreed bool `json:"termsOfServiceAgreed"`
|
||||
ExternalAccountBinding interface{} `json:"externalAccountBinding,omitempty"`
|
||||
}
|
||||
|
||||
func validateContacts(cs []string) error {
|
||||
|
@ -83,6 +89,14 @@ func (h *Handler) NewAccount(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := h.validateExternalAccountBinding(ctx, &nar); err != nil {
|
||||
api.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: link account to the key when created; mark boundat timestamp
|
||||
// TODO: return the externalAccountBinding field (should contain same info) if new account created
|
||||
|
||||
httpStatus := http.StatusCreated
|
||||
acc, err := accountFromContext(r.Context())
|
||||
if err != nil {
|
||||
|
@ -205,3 +219,66 @@ func (h *Handler) GetOrdersByAccountID(w http.ResponseWriter, r *http.Request) {
|
|||
api.JSON(w, orders)
|
||||
logOrdersByAccount(w, orders)
|
||||
}
|
||||
|
||||
// validateExternalAccountBinding validates the externalAccountBinding property in a call to new-account
|
||||
func (h *Handler) validateExternalAccountBinding(ctx context.Context, nar *NewAccountRequest) error {
|
||||
|
||||
prov, err := provisionerFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
acmeProv, ok := prov.(*provisioner.ACME) // TODO: rewrite into providing configuration via function on acme.Provisioner
|
||||
if !ok || acmeProv == nil {
|
||||
return acme.NewErrorISE("provisioner in context is not an ACME provisioner")
|
||||
}
|
||||
|
||||
shouldSkipAccountBindingValidation := !acmeProv.RequireEAB
|
||||
if shouldSkipAccountBindingValidation {
|
||||
return nil
|
||||
}
|
||||
|
||||
if nar.ExternalAccountBinding == nil {
|
||||
return acme.NewError(acme.ErrorExternalAccountRequiredType, "no external account binding provided")
|
||||
}
|
||||
|
||||
eabJSONBytes, err := json.Marshal(nar.ExternalAccountBinding)
|
||||
if err != nil {
|
||||
return acme.WrapErrorISE(err, "error marshalling externalAccountBinding")
|
||||
}
|
||||
|
||||
eabJWS, err := squarejose.ParseSigned(string(eabJSONBytes))
|
||||
if err != nil {
|
||||
return acme.WrapErrorISE(err, "error parsing externalAccountBinding jws")
|
||||
}
|
||||
|
||||
// TODO: verify supported algorithms against the incoming alg (and corresponding settings)?
|
||||
// TODO: implement strategy pattern to allow for different ways of verification (i.e. webhook call) based on configuration
|
||||
|
||||
keyID := eabJWS.Signatures[0].Protected.KeyID
|
||||
externalAccountKey, err := h.db.GetExternalAccountKey(ctx, keyID)
|
||||
if err != nil {
|
||||
return acme.WrapErrorISE(err, "error retrieving external account key")
|
||||
}
|
||||
|
||||
payload, err := eabJWS.Verify(externalAccountKey.KeyBytes)
|
||||
if err != nil {
|
||||
return acme.WrapErrorISE(err, "error verifying externalAccountBinding signature")
|
||||
}
|
||||
|
||||
jwk, err := jwkFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jwkJSONBytes, err := jwk.MarshalJSON()
|
||||
if err != nil {
|
||||
return acme.WrapErrorISE(err, "error marshaling jwk")
|
||||
}
|
||||
|
||||
if bytes.Equal(payload, jwkJSONBytes) {
|
||||
acme.NewError(acme.ErrorMalformedType, "keys in jws and eab payload do not match") // TODO: decide ACME error type to use
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -123,6 +123,13 @@ func (h *Handler) GetNonce(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
type Meta struct {
|
||||
TermsOfService string `json:"termsOfService,omitempty"`
|
||||
Website string `json:"website,omitempty"`
|
||||
CaaIdentities []string `json:"caaIdentities,omitempty"`
|
||||
ExternalAccountRequired bool `json:"externalAccountRequired,omitempty"`
|
||||
}
|
||||
|
||||
// Directory represents an ACME directory for configuring clients.
|
||||
type Directory struct {
|
||||
NewNonce string `json:"newNonce"`
|
||||
|
@ -130,6 +137,7 @@ type Directory struct {
|
|||
NewOrder string `json:"newOrder"`
|
||||
RevokeCert string `json:"revokeCert"`
|
||||
KeyChange string `json:"keyChange"`
|
||||
Meta Meta `json:"meta"`
|
||||
}
|
||||
|
||||
// ToLog enables response logging for the Directory type.
|
||||
|
@ -145,12 +153,27 @@ func (d *Directory) ToLog() (interface{}, error) {
|
|||
// for client configuration.
|
||||
func (h *Handler) GetDirectory(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
prov, err := provisionerFromContext(ctx)
|
||||
if err != nil {
|
||||
api.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
acmeProv, ok := prov.(*provisioner.ACME) // TODO: rewrite into providing configuration via function on acme.Provisioner
|
||||
if !ok || acmeProv == nil {
|
||||
api.WriteError(w, acme.NewErrorISE("provisioner in context is not an ACME provisioner"))
|
||||
return
|
||||
}
|
||||
|
||||
api.JSON(w, &Directory{
|
||||
NewNonce: h.linker.GetLink(ctx, NewNonceLinkType),
|
||||
NewAccount: h.linker.GetLink(ctx, NewAccountLinkType),
|
||||
NewOrder: h.linker.GetLink(ctx, NewOrderLinkType),
|
||||
RevokeCert: h.linker.GetLink(ctx, RevokeCertLinkType),
|
||||
KeyChange: h.linker.GetLink(ctx, KeyChangeLinkType),
|
||||
Meta: Meta{
|
||||
ExternalAccountRequired: acmeProv.RequireEAB,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
|
26
acme/db.go
26
acme/db.go
|
@ -19,6 +19,9 @@ type DB interface {
|
|||
GetAccountByKeyID(ctx context.Context, kid string) (*Account, error)
|
||||
UpdateAccount(ctx context.Context, acc *Account) error
|
||||
|
||||
CreateExternalAccountKey(ctx context.Context, name string) (*ExternalAccountKey, error)
|
||||
GetExternalAccountKey(ctx context.Context, keyID string) (*ExternalAccountKey, error)
|
||||
|
||||
CreateNonce(ctx context.Context) (Nonce, error)
|
||||
DeleteNonce(ctx context.Context, nonce Nonce) error
|
||||
|
||||
|
@ -47,6 +50,9 @@ type MockDB struct {
|
|||
MockGetAccountByKeyID func(ctx context.Context, kid string) (*Account, error)
|
||||
MockUpdateAccount func(ctx context.Context, acc *Account) error
|
||||
|
||||
MockCreateExternalAccountKey func(ctx context.Context, name string) (*ExternalAccountKey, error)
|
||||
MockGetExternalAccountKey func(ctx context.Context, keyID string) (*ExternalAccountKey, error)
|
||||
|
||||
MockCreateNonce func(ctx context.Context) (Nonce, error)
|
||||
MockDeleteNonce func(ctx context.Context, nonce Nonce) error
|
||||
|
||||
|
@ -110,6 +116,26 @@ func (m *MockDB) UpdateAccount(ctx context.Context, acc *Account) error {
|
|||
return m.MockError
|
||||
}
|
||||
|
||||
// CreateExternalAccountKey mock
|
||||
func (m *MockDB) CreateExternalAccountKey(ctx context.Context, name string) (*ExternalAccountKey, error) {
|
||||
if m.MockCreateExternalAccountKey != nil {
|
||||
return m.MockCreateExternalAccountKey(ctx, name)
|
||||
} else if m.MockError != nil {
|
||||
return nil, m.MockError
|
||||
}
|
||||
return m.MockRet1.(*ExternalAccountKey), m.MockError
|
||||
}
|
||||
|
||||
// GetExternalAccountKey mock
|
||||
func (m *MockDB) GetExternalAccountKey(ctx context.Context, keyID string) (*ExternalAccountKey, error) {
|
||||
if m.MockGetExternalAccountKey != nil {
|
||||
return m.MockGetExternalAccountKey(ctx, keyID)
|
||||
} else if m.MockError != nil {
|
||||
return nil, m.MockError
|
||||
}
|
||||
return m.MockRet1.(*ExternalAccountKey), m.MockError
|
||||
}
|
||||
|
||||
// CreateNonce mock
|
||||
func (m *MockDB) CreateNonce(ctx context.Context) (Nonce, error) {
|
||||
if m.MockCreateNonce != nil {
|
||||
|
|
|
@ -2,6 +2,7 @@ package nosql
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
|
@ -26,6 +27,15 @@ func (dba *dbAccount) clone() *dbAccount {
|
|||
return &nu
|
||||
}
|
||||
|
||||
type dbExternalAccountKey struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
AccountID string `json:"accountID,omitempty"`
|
||||
KeyBytes []byte `json:"key,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
BoundAt time.Time `json:"boundAt"`
|
||||
}
|
||||
|
||||
func (db *DB) getAccountIDByKeyID(ctx context.Context, kid string) (string, error) {
|
||||
id, err := db.db.Get(accountByKeyIDTable, []byte(kid))
|
||||
if err != nil {
|
||||
|
@ -134,3 +144,61 @@ func (db *DB) UpdateAccount(ctx context.Context, acc *acme.Account) error {
|
|||
|
||||
return db.save(ctx, old.ID, nu, old, "account", accountTable)
|
||||
}
|
||||
|
||||
// CreateExternalAccountKey creates a new External Account Binding key with a name
|
||||
func (db *DB) CreateExternalAccountKey(ctx context.Context, name string) (*acme.ExternalAccountKey, error) {
|
||||
keyID, err := randID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
random := make([]byte, 32)
|
||||
_, err = rand.Read(random)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dbeak := &dbExternalAccountKey{
|
||||
ID: keyID,
|
||||
Name: name,
|
||||
KeyBytes: random,
|
||||
CreatedAt: clock.Now(),
|
||||
}
|
||||
|
||||
if err = db.save(ctx, keyID, dbeak, nil, "external_account_key", externalAccountKeyTable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &acme.ExternalAccountKey{
|
||||
ID: dbeak.ID,
|
||||
Name: dbeak.Name,
|
||||
AccountID: dbeak.AccountID,
|
||||
KeyBytes: dbeak.KeyBytes,
|
||||
CreatedAt: dbeak.CreatedAt,
|
||||
BoundAt: dbeak.BoundAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetExternalAccountKey retrieves an External Account Binding key by KeyID
|
||||
func (db *DB) GetExternalAccountKey(ctx context.Context, keyID string) (*acme.ExternalAccountKey, error) {
|
||||
data, err := db.db.Get(externalAccountKeyTable, []byte(keyID))
|
||||
if err != nil {
|
||||
if nosqlDB.IsErrNotFound(err) {
|
||||
return nil, acme.ErrNotFound
|
||||
}
|
||||
return nil, errors.Wrapf(err, "error loading external account key %s", keyID)
|
||||
}
|
||||
|
||||
dbeak := new(dbExternalAccountKey)
|
||||
if err = json.Unmarshal(data, dbeak); err != nil {
|
||||
return nil, errors.Wrapf(err, "error unmarshaling external account key %s into dbExternalAccountKey", keyID)
|
||||
}
|
||||
|
||||
return &acme.ExternalAccountKey{
|
||||
ID: dbeak.ID,
|
||||
Name: dbeak.Name,
|
||||
AccountID: dbeak.AccountID,
|
||||
KeyBytes: dbeak.KeyBytes,
|
||||
CreatedAt: dbeak.CreatedAt,
|
||||
BoundAt: dbeak.BoundAt,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -11,14 +11,15 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
accountTable = []byte("acme_accounts")
|
||||
accountByKeyIDTable = []byte("acme_keyID_accountID_index")
|
||||
authzTable = []byte("acme_authzs")
|
||||
challengeTable = []byte("acme_challenges")
|
||||
nonceTable = []byte("nonces")
|
||||
orderTable = []byte("acme_orders")
|
||||
ordersByAccountIDTable = []byte("acme_account_orders_index")
|
||||
certTable = []byte("acme_certs")
|
||||
accountTable = []byte("acme_accounts")
|
||||
accountByKeyIDTable = []byte("acme_keyID_accountID_index")
|
||||
authzTable = []byte("acme_authzs")
|
||||
challengeTable = []byte("acme_challenges")
|
||||
nonceTable = []byte("nonces")
|
||||
orderTable = []byte("acme_orders")
|
||||
ordersByAccountIDTable = []byte("acme_account_orders_index")
|
||||
certTable = []byte("acme_certs")
|
||||
externalAccountKeyTable = []byte("acme_external_account_keys")
|
||||
)
|
||||
|
||||
// DB is a struct that implements the AcmeDB interface.
|
||||
|
@ -29,7 +30,7 @@ type DB struct {
|
|||
// New configures and returns a new ACME DB backend implemented using a nosql DB.
|
||||
func New(db nosqlDB.DB) (*DB, error) {
|
||||
tables := [][]byte{accountTable, accountByKeyIDTable, authzTable,
|
||||
challengeTable, nonceTable, orderTable, ordersByAccountIDTable, certTable}
|
||||
challengeTable, nonceTable, orderTable, ordersByAccountIDTable, certTable, externalAccountKeyTable}
|
||||
for _, b := range tables {
|
||||
if err := db.CreateTable(b); err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating table %s",
|
||||
|
|
45
authority/admin/api/eak.go
Normal file
45
authority/admin/api/eak.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/smallstep/certificates/api"
|
||||
"github.com/smallstep/certificates/authority/admin"
|
||||
)
|
||||
|
||||
// CreateExternalAccountKeyRequest is the type for GET /admin/eak requests
|
||||
type CreateExternalAccountKeyRequest struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// CreateExternalAccountKeyResponse is the type for GET /admin/eak responses
|
||||
type CreateExternalAccountKeyResponse struct {
|
||||
KeyID string `json:"keyID"`
|
||||
Name string `json:"name"`
|
||||
Key []byte `json:"key"`
|
||||
}
|
||||
|
||||
// CreateExternalAccountKey creates a new External Account Binding key
|
||||
func (h *Handler) CreateExternalAccountKey(w http.ResponseWriter, r *http.Request) {
|
||||
var eakRequest = new(CreateExternalAccountKeyRequest)
|
||||
if err := api.ReadJSON(r.Body, eakRequest); err != nil { // TODO: rewrite into protobuf json (likely)
|
||||
api.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Validate input
|
||||
|
||||
eak, err := h.db.CreateExternalAccountKey(r.Context(), eakRequest.Name)
|
||||
if err != nil {
|
||||
api.WriteError(w, admin.WrapErrorISE(err, "error creating external account key %s", eakRequest.Name))
|
||||
return
|
||||
}
|
||||
|
||||
eakResponse := CreateExternalAccountKeyResponse{
|
||||
KeyID: eak.ID,
|
||||
Name: eak.Name,
|
||||
Key: eak.KeyBytes,
|
||||
}
|
||||
|
||||
api.JSONStatus(w, eakResponse, http.StatusCreated) // TODO: rewrite into protobuf json (likely)
|
||||
}
|
|
@ -38,4 +38,7 @@ func (h *Handler) Route(r api.Router) {
|
|||
r.MethodFunc("POST", "/admins", authnz(h.CreateAdmin))
|
||||
r.MethodFunc("PATCH", "/admins/{id}", authnz(h.UpdateAdmin))
|
||||
r.MethodFunc("DELETE", "/admins/{id}", authnz(h.DeleteAdmin))
|
||||
|
||||
// External Account Binding Keys
|
||||
r.MethodFunc("POST", "/eak", h.CreateExternalAccountKey) // TODO: authnz
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"go.step.sm/linkedca"
|
||||
|
||||
"github.com/smallstep/certificates/authority/admin/eak"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -67,6 +69,8 @@ type DB interface {
|
|||
GetAdmins(ctx context.Context) ([]*linkedca.Admin, error)
|
||||
UpdateAdmin(ctx context.Context, admin *linkedca.Admin) error
|
||||
DeleteAdmin(ctx context.Context, id string) error
|
||||
|
||||
CreateExternalAccountKey(ctx context.Context, name string) (*eak.ExternalAccountKey, error)
|
||||
}
|
||||
|
||||
// MockDB is an implementation of the DB interface that should only be used as
|
||||
|
@ -84,6 +88,8 @@ type MockDB struct {
|
|||
MockUpdateAdmin func(ctx context.Context, adm *linkedca.Admin) error
|
||||
MockDeleteAdmin func(ctx context.Context, id string) error
|
||||
|
||||
MockCreateExternalAccountKey func(ctx context.Context, name string) (*eak.ExternalAccountKey, error)
|
||||
|
||||
MockError error
|
||||
MockRet1 interface{}
|
||||
}
|
||||
|
@ -177,3 +183,10 @@ func (m *MockDB) DeleteAdmin(ctx context.Context, id string) error {
|
|||
}
|
||||
return m.MockError
|
||||
}
|
||||
|
||||
func (m *MockDB) CreateExternalAccountKey(ctx context.Context, name string) (*eak.ExternalAccountKey, error) {
|
||||
if m.MockCreateExternalAccountKey != nil {
|
||||
return m.MockCreateExternalAccountKey(ctx, name)
|
||||
}
|
||||
return m.MockRet1.(*eak.ExternalAccountKey), m.MockError
|
||||
}
|
||||
|
|
51
authority/admin/db/nosql/eak.go
Normal file
51
authority/admin/db/nosql/eak.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package nosql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"time"
|
||||
|
||||
"github.com/smallstep/certificates/authority/admin/eak"
|
||||
)
|
||||
|
||||
type dbExternalAccountKey struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
AccountID string `json:"accountID,omitempty"`
|
||||
KeyBytes []byte `json:"key,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
BoundAt time.Time `json:"boundAt"`
|
||||
}
|
||||
|
||||
// CreateExternalAccountKey creates a new External Account Binding key
|
||||
func (db *DB) CreateExternalAccountKey(ctx context.Context, name string) (*eak.ExternalAccountKey, error) {
|
||||
keyID, err := randID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
random := make([]byte, 32)
|
||||
_, err = rand.Read(random)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dbeak := &dbExternalAccountKey{
|
||||
ID: keyID,
|
||||
Name: name,
|
||||
KeyBytes: random,
|
||||
CreatedAt: clock.Now(),
|
||||
}
|
||||
|
||||
if err = db.save(ctx, keyID, dbeak, nil, "external_account_key", externalAccountKeyTable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &eak.ExternalAccountKey{
|
||||
ID: dbeak.ID,
|
||||
Name: dbeak.Name,
|
||||
AccountID: dbeak.AccountID,
|
||||
KeyBytes: dbeak.KeyBytes,
|
||||
CreatedAt: dbeak.CreatedAt,
|
||||
BoundAt: dbeak.BoundAt,
|
||||
}, nil
|
||||
}
|
|
@ -11,8 +11,9 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
adminsTable = []byte("admins")
|
||||
provisionersTable = []byte("provisioners")
|
||||
adminsTable = []byte("admins")
|
||||
provisionersTable = []byte("provisioners")
|
||||
externalAccountKeyTable = []byte("acme_external_account_keys")
|
||||
)
|
||||
|
||||
// DB is a struct that implements the AdminDB interface.
|
||||
|
@ -23,7 +24,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{adminsTable, provisionersTable}
|
||||
tables := [][]byte{adminsTable, provisionersTable, externalAccountKeyTable}
|
||||
for _, b := range tables {
|
||||
if err := db.CreateTable(b); err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating table %s",
|
||||
|
|
12
authority/admin/eak/eak.go
Normal file
12
authority/admin/eak/eak.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package eak
|
||||
|
||||
import "time"
|
||||
|
||||
type ExternalAccountKey struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
AccountID string `json:"-"`
|
||||
KeyBytes []byte `json:"-"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
BoundAt time.Time `json:"boundAt,omitempty"`
|
||||
}
|
|
@ -13,13 +13,14 @@ import (
|
|||
// provisioning flow.
|
||||
type ACME struct {
|
||||
*base
|
||||
ID string `json:"-"`
|
||||
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
|
||||
ID string `json:"-"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
ForceCN bool `json:"forceCN,omitempty"`
|
||||
RequireEAB bool `json:"requireEAB,omitempty"`
|
||||
Claims *Claims `json:"claims,omitempty"`
|
||||
Options *Options `json:"options,omitempty"`
|
||||
claimer *Claimer
|
||||
}
|
||||
|
||||
// GetID returns the provisioner unique identifier.
|
||||
|
|
Loading…
Reference in a new issue