acme: Move ordinal to application

The authority now receives the ordinal in its constructor rather than a
global variable set at package initialization time. The ordinal is
passed via the command line option `--ordinal`.
This commit is contained in:
David Cowden 2020-05-13 19:22:07 -07:00
parent b8b3ca2ac1
commit c378e0043a
5 changed files with 68 additions and 69 deletions

View file

@ -231,7 +231,7 @@ func TestHandlerGetNonce(t *testing.T) {
}
func TestHandlerGetDirectory(t *testing.T) {
auth, err := acme.NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil)
auth, err := acme.NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
prov := newProv()
url := fmt.Sprintf("http://ca.smallstep.com/acme/%s/directory", acme.URLSafeProvisionerName(prov))

View file

@ -5,12 +5,9 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"log"
"net"
"net/http"
"net/url"
"os"
"strconv"
"time"
"github.com/pkg/errors"
@ -46,6 +43,7 @@ type Authority struct {
db nosql.DB
dir *directory
signAuth SignAuthority
ordinal int
}
var (
@ -57,26 +55,10 @@ var (
orderTable = []byte("acme_orders")
ordersByAccountIDTable = []byte("acme_account_orders_index")
certTable = []byte("acme_certs")
ordinal int
)
// Ordinal is used during challenge retries to indicate ownership.
func init() {
ordstr := os.Getenv("STEP_CA_ORDINAL")
if ordstr == "" {
ordinal = 0
} else {
ord, err := strconv.Atoi(ordstr)
if err != nil {
log.Fatal("Unrecognized ordinal ingeter value.")
panic(nil)
}
ordinal = ord
}
}
// NewAuthority returns a new Authority that implements the ACME interface.
func NewAuthority(db nosql.DB, dns, prefix string, signAuth SignAuthority) (*Authority, error) {
func NewAuthority(db nosql.DB, dns, prefix string, signAuth SignAuthority, ordinal int) (*Authority, error) {
if _, ok := db.(*database.SimpleDB); !ok {
// If it's not a SimpleDB then go ahead and bootstrap the DB with the
// necessary ACME tables. SimpleDB should ONLY be used for testing.
@ -91,7 +73,7 @@ func NewAuthority(db nosql.DB, dns, prefix string, signAuth SignAuthority) (*Aut
}
}
return &Authority{
db: db, dir: newDirectory(dns, prefix), signAuth: signAuth,
db: db, dir: newDirectory(dns, prefix), signAuth: signAuth, ordinal: ordinal,
}, nil
}
@ -336,7 +318,7 @@ func (a *Authority) ValidateChallenge(p provisioner.Interface, accID, chID strin
up := ch.clone()
up.Status = StatusProcessing
up.Retry = &Retry{
Owner: ordinal,
Owner: a.ordinal,
ProvisionerID: p.GetID(),
NumAttempts: 0,
MaxAttempts: 10,
@ -420,7 +402,7 @@ func (a *Authority) RetryChallenge(chID string) {
// Then check to make sure Retry.NextAttempt is in the past.
retry := ch.getRetry()
switch {
case retry.Owner != ordinal:
case retry.Owner != a.ordinal:
return
case !retry.Active():
return

View file

@ -14,7 +14,7 @@ import (
)
func TestAuthorityGetLink(t *testing.T) {
auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil)
auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
provID := "acme-test-provisioner"
type test struct {
@ -70,7 +70,7 @@ func TestAuthorityGetLink(t *testing.T) {
}
func TestAuthorityGetDirectory(t *testing.T) {
auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil)
auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
prov := newProv()
acmeDir := auth.GetDirectory(prov)
@ -94,7 +94,7 @@ func TestAuthorityNewNonce(t *testing.T) {
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
return nil, false, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -110,7 +110,7 @@ func TestAuthorityNewNonce(t *testing.T) {
*res = string(key)
return nil, true, nil
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -149,7 +149,7 @@ func TestAuthorityUseNonce(t *testing.T) {
MUpdate: func(tx *database.Tx) error {
return errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -161,7 +161,7 @@ func TestAuthorityUseNonce(t *testing.T) {
MUpdate: func(tx *database.Tx) error {
return nil
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -205,7 +205,7 @@ func TestAuthorityNewAccount(t *testing.T) {
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
return nil, false, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -231,7 +231,7 @@ func TestAuthorityNewAccount(t *testing.T) {
count++
return nil, true, nil
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -281,7 +281,7 @@ func TestAuthorityGetAccount(t *testing.T) {
assert.Equals(t, key, []byte(id))
return nil, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -298,7 +298,7 @@ func TestAuthorityGetAccount(t *testing.T) {
MGet: func(bucket, key []byte) ([]byte, error) {
return b, nil
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -348,7 +348,7 @@ func TestAuthorityGetAccountByKey(t *testing.T) {
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
jwk.Key = "foo"
auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil)
auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -367,7 +367,7 @@ func TestAuthorityGetAccountByKey(t *testing.T) {
assert.Equals(t, key, []byte(kid))
return nil, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -399,7 +399,7 @@ func TestAuthorityGetAccountByKey(t *testing.T) {
count++
return ret, nil
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -453,7 +453,7 @@ func TestAuthorityGetOrder(t *testing.T) {
assert.Equals(t, key, []byte(id))
return nil, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -472,7 +472,7 @@ func TestAuthorityGetOrder(t *testing.T) {
assert.Equals(t, key, []byte(o.ID))
return b, nil
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -501,7 +501,7 @@ func TestAuthorityGetOrder(t *testing.T) {
return nil, ServerInternalErr(errors.New("force"))
}
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -522,7 +522,7 @@ func TestAuthorityGetOrder(t *testing.T) {
assert.Equals(t, key, []byte(o.ID))
return b, nil
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -576,7 +576,7 @@ func TestAuthorityGetCertificate(t *testing.T) {
assert.Equals(t, key, []byte(id))
return nil, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -595,7 +595,7 @@ func TestAuthorityGetCertificate(t *testing.T) {
assert.Equals(t, key, []byte(cert.ID))
return b, nil
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -615,7 +615,7 @@ func TestAuthorityGetCertificate(t *testing.T) {
assert.Equals(t, key, []byte(cert.ID))
return b, nil
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -670,7 +670,7 @@ func TestAuthorityGetAuthz(t *testing.T) {
assert.Equals(t, key, []byte(id))
return nil, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -689,7 +689,7 @@ func TestAuthorityGetAuthz(t *testing.T) {
assert.Equals(t, key, []byte(az.getID()))
return b, nil
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -720,7 +720,7 @@ func TestAuthorityGetAuthz(t *testing.T) {
count++
return ret, nil
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -812,7 +812,7 @@ func TestAuthorityGetAuthz(t *testing.T) {
count++
return ret, nil
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -862,7 +862,7 @@ func TestAuthorityNewOrder(t *testing.T) {
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
return nil, false, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -916,7 +916,7 @@ func TestAuthorityNewOrder(t *testing.T) {
MGet: func(bucket, key []byte) ([]byte, error) {
return nil, database.ErrNotFound
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -966,7 +966,7 @@ func TestAuthorityGetOrdersByAccount(t *testing.T) {
assert.Equals(t, key, []byte(id))
return nil, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -998,7 +998,7 @@ func TestAuthorityGetOrdersByAccount(t *testing.T) {
count++
return ret, nil
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -1045,7 +1045,7 @@ func TestAuthorityGetOrdersByAccount(t *testing.T) {
count++
return ret, nil
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -1094,7 +1094,7 @@ func TestAuthorityFinalizeOrder(t *testing.T) {
assert.Equals(t, key, []byte(id))
return nil, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -1113,7 +1113,7 @@ func TestAuthorityFinalizeOrder(t *testing.T) {
assert.Equals(t, key, []byte(o.ID))
return b, nil
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -1139,7 +1139,7 @@ func TestAuthorityFinalizeOrder(t *testing.T) {
assert.Equals(t, key, []byte(o.ID))
return nil, false, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -1161,7 +1161,7 @@ func TestAuthorityFinalizeOrder(t *testing.T) {
assert.Equals(t, key, []byte(o.ID))
return b, nil
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -1216,7 +1216,7 @@ func TestAuthorityValidateChallenge(t *testing.T) {
assert.Equals(t, key, []byte(id))
return nil, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -1236,7 +1236,7 @@ func TestAuthorityValidateChallenge(t *testing.T) {
assert.Equals(t, key, []byte(ch.getID()))
return b, nil
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -1262,7 +1262,7 @@ func TestAuthorityValidateChallenge(t *testing.T) {
assert.Equals(t, key, []byte(ch.getID()))
return nil, false, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -1288,7 +1288,7 @@ func TestAuthorityValidateChallenge(t *testing.T) {
assert.Equals(t, key, []byte(ch.getID()))
return b, nil
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -1344,7 +1344,7 @@ func TestAuthorityUpdateAccount(t *testing.T) {
assert.Equals(t, key, []byte(id))
return nil, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -1366,7 +1366,7 @@ func TestAuthorityUpdateAccount(t *testing.T) {
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
return nil, false, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -1394,7 +1394,7 @@ func TestAuthorityUpdateAccount(t *testing.T) {
assert.Equals(t, key, []byte(acc.ID))
return nil, true, nil
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -1449,7 +1449,7 @@ func TestAuthorityDeactivateAccount(t *testing.T) {
assert.Equals(t, key, []byte(id))
return nil, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -1470,7 +1470,7 @@ func TestAuthorityDeactivateAccount(t *testing.T) {
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
return nil, false, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,
@ -1498,7 +1498,7 @@ func TestAuthorityDeactivateAccount(t *testing.T) {
assert.Equals(t, key, []byte(acc.ID))
return nil, true, nil
},
}, "ca.smallstep.com", "acme", nil)
}, "ca.smallstep.com", "acme", nil, 0)
assert.FatalError(t, err)
return test{
auth: auth,

View file

@ -26,6 +26,7 @@ type options struct {
configFile string
password []byte
database db.AuthDB
ordinal int
}
func (o *options) apply(opts []Option) {
@ -60,6 +61,14 @@ func WithDatabase(db db.AuthDB) Option {
}
}
// WithOrdinal sets the server's ordinal identifier (an int).
func WithOrdinal(ordinal int) Option {
return func(o *options) {
o.ordinal = ordinal
}
}
// 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 {
@ -124,7 +133,7 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) {
}
prefix := "acme"
acmeAuth, err := acme.NewAuthority(auth.GetDatabase().(nosql.DB), dns, prefix, auth)
acmeAuth, err := acme.NewAuthority(auth.GetDatabase().(nosql.DB), dns, prefix, auth, ca.opts.ordinal)
if err != nil {
return nil, errors.Wrap(err, "error creating ACME authority")
}

View file

@ -34,6 +34,11 @@ intermediate private key.`,
Name: "resolver",
Usage: "address of a DNS resolver to be used instead of the default.",
},
cli.IntFlag{
Name: "ordinal",
Usage: `Unique <index> identifying this instance of step-ca in a highly-
available (replicated) deployment.`,
},
},
}
@ -42,6 +47,9 @@ func appAction(ctx *cli.Context) error {
passFile := ctx.String("password-file")
resolver := ctx.String("resolver")
// grab the ordinal or default to 0
ordinal := ctx.Int("ordinal")
// If zero cmd line args show help, if >1 cmd line args show error.
if ctx.NArg() == 0 {
return cli.ShowAppHelp(ctx)
@ -72,7 +80,7 @@ func appAction(ctx *cli.Context) error {
}
}
srv, err := ca.New(config, ca.WithConfigFile(configFile), ca.WithPassword(password))
srv, err := ca.New(config, ca.WithConfigFile(configFile), ca.WithPassword(password), ca.WithOrdinal(ordinal))
if err != nil {
fatal(err)
}