diff --git a/acme/api/handler_test.go b/acme/api/handler_test.go index f4ec41b5..70c366d7 100644 --- a/acme/api/handler_test.go +++ b/acme/api/handler_test.go @@ -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)) diff --git a/acme/authority.go b/acme/authority.go index 63ad07eb..471b1bca 100644 --- a/acme/authority.go +++ b/acme/authority.go @@ -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 diff --git a/acme/authority_test.go b/acme/authority_test.go index ff6cec0a..9171af08 100644 --- a/acme/authority_test.go +++ b/acme/authority_test.go @@ -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, diff --git a/ca/ca.go b/ca/ca.go index 96bebba4..18b43d92 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -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") } diff --git a/commands/app.go b/commands/app.go index 9b22ac2d..19b09a47 100644 --- a/commands/app.go +++ b/commands/app.go @@ -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 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) }