forked from TrueCloudLab/certificates
Add used OTT to DB during authToken step
This commit is contained in:
parent
25c710c056
commit
b73fe8c157
8 changed files with 222 additions and 13 deletions
4
Gopkg.lock
generated
4
Gopkg.lock
generated
|
@ -363,7 +363,7 @@
|
|||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:f1f1df1e19d55a1ef1f0a63633191e7d2a99993c0a17f945c0b9ebd16b17871b"
|
||||
digest = "1:5e778214d472b6d2ad4d544d293d1478d9b222db8ffc6079623fbe3e58e1841e"
|
||||
name = "github.com/smallstep/nosql"
|
||||
packages = [
|
||||
".",
|
||||
|
@ -373,7 +373,7 @@
|
|||
"mysql",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "5a355c598075a346d9ca9b50ec10e3f86ac66148"
|
||||
revision = "b66b34823456721912ba037126e92414690c07d6"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/cli/jose"
|
||||
)
|
||||
|
||||
|
@ -72,11 +73,23 @@ func (a *Authority) authorizeToken(ott string) (provisioner.Interface, error) {
|
|||
reuseKey = claims.Nonce
|
||||
}
|
||||
if reuseKey != "" {
|
||||
if _, ok := a.ottMap.LoadOrStore(reuseKey, &idUsed{
|
||||
UsedAt: time.Now().Unix(),
|
||||
Subject: claims.Subject,
|
||||
}); ok {
|
||||
return nil, &apiError{errors.Errorf("authorizeToken: token already used"), http.StatusUnauthorized, errContext}
|
||||
switch a.db.(type) {
|
||||
case *db.NoopDB:
|
||||
if _, ok := a.ottMap.LoadOrStore(reuseKey, &idUsed{
|
||||
UsedAt: time.Now().Unix(),
|
||||
Subject: claims.Subject,
|
||||
}); ok {
|
||||
return nil, &apiError{errors.Errorf("authorizeToken: token already used"), http.StatusUnauthorized, errContext}
|
||||
}
|
||||
default:
|
||||
ok, err := a.db.UseToken(reuseKey, ott)
|
||||
if err != nil {
|
||||
return nil, &apiError{errors.Wrap(err, "authorizeToken: failed when checking if token already used"),
|
||||
http.StatusInternalServerError, errContext}
|
||||
}
|
||||
if !ok {
|
||||
return nil, &apiError{errors.Errorf("authorizeToken: token already used"), http.StatusUnauthorized, errContext}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -117,7 +117,7 @@ func TestAuthority_authorizeToken(t *testing.T) {
|
|||
http.StatusUnauthorized, context{"ott": raw}},
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) *authorizeTest {
|
||||
"ok/noopdb": func(t *testing.T) *authorizeTest {
|
||||
cl := jwt.Claims{
|
||||
Subject: "test.smallstep.com",
|
||||
Issuer: validIssuer,
|
||||
|
@ -133,7 +133,7 @@ func TestAuthority_authorizeToken(t *testing.T) {
|
|||
ott: raw,
|
||||
}
|
||||
},
|
||||
"fail/token-already-used": func(t *testing.T) *authorizeTest {
|
||||
"fail/noopdb/token-already-used": func(t *testing.T) *authorizeTest {
|
||||
_a := testAuthority(t)
|
||||
|
||||
cl := jwt.Claims{
|
||||
|
@ -155,6 +155,79 @@ func TestAuthority_authorizeToken(t *testing.T) {
|
|||
http.StatusUnauthorized, context{"ott": raw}},
|
||||
}
|
||||
},
|
||||
"ok/mockNoSQLDB": func(t *testing.T) *authorizeTest {
|
||||
_a := testAuthority(t)
|
||||
_a.db = &MockAuthDB{
|
||||
useToken: func(id, tok string) (bool, error) {
|
||||
return true, nil
|
||||
},
|
||||
}
|
||||
|
||||
cl := jwt.Claims{
|
||||
Subject: "test.smallstep.com",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: validAudience,
|
||||
ID: "43",
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
return &authorizeTest{
|
||||
auth: _a,
|
||||
ott: raw,
|
||||
}
|
||||
},
|
||||
"fail/mockNoSQLDB/error": func(t *testing.T) *authorizeTest {
|
||||
_a := testAuthority(t)
|
||||
_a.db = &MockAuthDB{
|
||||
useToken: func(id, tok string) (bool, error) {
|
||||
return false, errors.New("force")
|
||||
},
|
||||
}
|
||||
|
||||
cl := jwt.Claims{
|
||||
Subject: "test.smallstep.com",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: validAudience,
|
||||
ID: "43",
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
return &authorizeTest{
|
||||
auth: _a,
|
||||
ott: raw,
|
||||
err: &apiError{errors.New("authorizeToken: failed when checking if token already used: force"),
|
||||
http.StatusInternalServerError, context{"ott": raw}},
|
||||
}
|
||||
},
|
||||
"fail/mockNoSQLDB/token-already-used": func(t *testing.T) *authorizeTest {
|
||||
_a := testAuthority(t)
|
||||
_a.db = &MockAuthDB{
|
||||
useToken: func(id, tok string) (bool, error) {
|
||||
return false, nil
|
||||
},
|
||||
}
|
||||
|
||||
cl := jwt.Claims{
|
||||
Subject: "test.smallstep.com",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: validAudience,
|
||||
ID: "43",
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
return &authorizeTest{
|
||||
auth: _a,
|
||||
ott: raw,
|
||||
err: &apiError{errors.New("authorizeToken: token already used"),
|
||||
http.StatusUnauthorized, context{"ott": raw}},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
for name, genTestCase := range tests {
|
||||
|
|
|
@ -13,6 +13,7 @@ type MockAuthDB struct {
|
|||
isRevoked func(string) (bool, error)
|
||||
revoke func(rci *db.RevokedCertificateInfo) error
|
||||
storeCertificate func(crt *x509.Certificate) error
|
||||
useToken func(id, tok string) (bool, error)
|
||||
shutdown func() error
|
||||
}
|
||||
|
||||
|
@ -33,6 +34,16 @@ func (m *MockAuthDB) IsRevoked(sn string) (bool, error) {
|
|||
return m.ret1.(bool), m.err
|
||||
}
|
||||
|
||||
func (m *MockAuthDB) UseToken(id, tok string) (bool, error) {
|
||||
if m.useToken != nil {
|
||||
return m.useToken(id, tok)
|
||||
}
|
||||
if m.ret1 == nil {
|
||||
return false, m.err
|
||||
}
|
||||
return m.ret1.(bool), m.err
|
||||
}
|
||||
|
||||
func (m *MockAuthDB) Revoke(rci *db.RevokedCertificateInfo) error {
|
||||
if m.revoke != nil {
|
||||
return m.revoke(rci)
|
||||
|
|
|
@ -640,7 +640,12 @@ func TestRevoke(t *testing.T) {
|
|||
},
|
||||
"error/db-revoke": func() test {
|
||||
a := testAuthority(t)
|
||||
a.db = &MockAuthDB{err: errors.New("force")}
|
||||
a.db = &MockAuthDB{
|
||||
useToken: func(id, tok string) (bool, error) {
|
||||
return true, nil
|
||||
},
|
||||
err: errors.New("force"),
|
||||
}
|
||||
|
||||
cl := jwt.Claims{
|
||||
Subject: "sn",
|
||||
|
@ -671,7 +676,12 @@ func TestRevoke(t *testing.T) {
|
|||
},
|
||||
"error/already-revoked": func() test {
|
||||
a := testAuthority(t)
|
||||
a.db = &MockAuthDB{err: db.ErrAlreadyExists}
|
||||
a.db = &MockAuthDB{
|
||||
useToken: func(id, tok string) (bool, error) {
|
||||
return true, nil
|
||||
},
|
||||
err: db.ErrAlreadyExists,
|
||||
}
|
||||
|
||||
cl := jwt.Claims{
|
||||
Subject: "sn",
|
||||
|
@ -702,7 +712,11 @@ func TestRevoke(t *testing.T) {
|
|||
},
|
||||
"ok/token": func() test {
|
||||
a := testAuthority(t)
|
||||
a.db = &MockAuthDB{}
|
||||
a.db = &MockAuthDB{
|
||||
useToken: func(id, tok string) (bool, error) {
|
||||
return true, nil
|
||||
},
|
||||
}
|
||||
|
||||
cl := jwt.Claims{
|
||||
Subject: "sn",
|
||||
|
|
21
db/db.go
21
db/db.go
|
@ -10,8 +10,9 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
revokedCertsTable = []byte("revoked_x509_certs")
|
||||
certsTable = []byte("x509_certs")
|
||||
revokedCertsTable = []byte("revoked_x509_certs")
|
||||
usedOTTTable = []byte("used_ott")
|
||||
)
|
||||
|
||||
// ErrAlreadyExists can be returned if the DB attempts to set a key that has
|
||||
|
@ -31,6 +32,7 @@ type AuthDB interface {
|
|||
IsRevoked(sn string) (bool, error)
|
||||
Revoke(rci *RevokedCertificateInfo) error
|
||||
StoreCertificate(crt *x509.Certificate) error
|
||||
UseToken(id, tok string) (bool, error)
|
||||
Shutdown() error
|
||||
}
|
||||
|
||||
|
@ -126,6 +128,23 @@ func (db *DB) StoreCertificate(crt *x509.Certificate) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// UseToken returns true if we were able to successfully store the token for
|
||||
// for the first time, false otherwise.
|
||||
func (db *DB) UseToken(id, tok string) (bool, error) {
|
||||
// If the error is `Not Found` then the certificate has not been revoked.
|
||||
// Any other error should be propagated to the caller.
|
||||
_, found, err := db.LoadOrStore(usedOTTTable, []byte(id), []byte(tok))
|
||||
switch {
|
||||
case err != nil:
|
||||
return false, errors.Wrapf(err, "error LoadOrStore-ing token %s/%s",
|
||||
string(usedOTTTable), id)
|
||||
case found:
|
||||
return false, nil
|
||||
default:
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown sends a shutdown message to the database.
|
||||
func (db *DB) Shutdown() error {
|
||||
if db.isUp {
|
||||
|
|
|
@ -20,6 +20,17 @@ type MockNoSQLDB struct {
|
|||
del func(bucket, key []byte) error
|
||||
list func(bucket []byte) ([]*database.Entry, error)
|
||||
update func(tx *database.Tx) error
|
||||
loadOrStore func(bucket, key, value []byte) ([]byte, bool, error)
|
||||
}
|
||||
|
||||
func (m *MockNoSQLDB) LoadOrStore(bucket, key, value []byte) ([]byte, bool, error) {
|
||||
if m.get != nil {
|
||||
return m.loadOrStore(bucket, key, value)
|
||||
}
|
||||
if m.ret1 == nil {
|
||||
return nil, false, m.err
|
||||
}
|
||||
return m.ret1.([]byte), m.ret2.(bool), m.err
|
||||
}
|
||||
|
||||
func (m *MockNoSQLDB) Get(bucket, key []byte) ([]byte, error) {
|
||||
|
@ -188,3 +199,66 @@ func TestRevoke(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUseToken(t *testing.T) {
|
||||
type result struct {
|
||||
err error
|
||||
ok bool
|
||||
}
|
||||
tests := map[string]struct {
|
||||
id, tok string
|
||||
db *DB
|
||||
want result
|
||||
}{
|
||||
"fail/force-LoadOrStore-error": {
|
||||
id: "id",
|
||||
tok: "token",
|
||||
db: &DB{&MockNoSQLDB{
|
||||
loadOrStore: func(bucket, key, value []byte) ([]byte, bool, error) {
|
||||
return nil, false, errors.New("force")
|
||||
},
|
||||
}, true},
|
||||
want: result{
|
||||
ok: false,
|
||||
err: errors.New("error LoadOrStore-ing token id/token"),
|
||||
},
|
||||
},
|
||||
"fail/LoadOrStore-found": {
|
||||
id: "id",
|
||||
tok: "token",
|
||||
db: &DB{&MockNoSQLDB{
|
||||
loadOrStore: func(bucket, key, value []byte) ([]byte, bool, error) {
|
||||
return []byte("foo"), true, nil
|
||||
},
|
||||
}, true},
|
||||
want: result{
|
||||
ok: false,
|
||||
},
|
||||
},
|
||||
"ok/LoadOrStore-not-found": {
|
||||
id: "id",
|
||||
tok: "token",
|
||||
db: &DB{&MockNoSQLDB{
|
||||
loadOrStore: func(bucket, key, value []byte) ([]byte, bool, error) {
|
||||
return nil, false, nil
|
||||
},
|
||||
}, true},
|
||||
want: result{
|
||||
ok: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ok, err := tc.db.UseToken(tc.id, tc.tok)
|
||||
if err != nil {
|
||||
if assert.NotNil(t, tc.want.err) {
|
||||
assert.HasPrefix(t, tc.want.err.Error(), err.Error())
|
||||
}
|
||||
assert.False(t, ok)
|
||||
} else {
|
||||
assert.True(t, ok)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,11 @@ func (n *NoopDB) StoreCertificate(crt *x509.Certificate) error {
|
|||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
// UseToken returns a "NotImplemented" error.
|
||||
func (n *NoopDB) UseToken(id, tok string) (bool, error) {
|
||||
return false, ErrNotImplemented
|
||||
}
|
||||
|
||||
// Shutdown returns nil
|
||||
func (n *NoopDB) Shutdown() error {
|
||||
return nil
|
||||
|
|
Loading…
Reference in a new issue