vault/sdk/helper/nonce/sync_map_nonce.go
Alexander Scheel b1f0d4e495
Add nonce service to sdk/helpers, use in PKI (#20688)
* Build a better nonce service

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add internal nonce service for testing

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add benchmarks for nonce service

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add statistics around how long tidy took

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Replace ACME nonces with shared nonce service

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add an initialize method to nonce services

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Use the new initialize helper on nonce service in PKI

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add additional tests for nonces

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Format sdk/helper/nonce

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Use default 90s nonce expiry in PKI

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Remove parallel test case as covered by benchmark

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add additional commentary to encrypted nonce implementation

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add nonce to test_packages

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

---------

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
2023-05-23 19:44:05 +00:00

108 lines
2.1 KiB
Go

package nonce
import (
"crypto/rand"
"encoding/base64"
"io"
"sync"
"sync/atomic"
"time"
)
type syncMapNonceService struct {
validity time.Duration
issued *atomic.Uint64
nextExpiry *atomic.Int64
nonces *sync.Map // map[string]time.Time
}
var _ NonceService = &syncMapNonceService{}
func newSyncMapNonceService(validity time.Duration) *syncMapNonceService {
return &syncMapNonceService{
validity: validity,
issued: new(atomic.Uint64),
nextExpiry: new(atomic.Int64),
nonces: new(sync.Map),
}
}
func (a *syncMapNonceService) Initialize() error { return nil }
func (a *syncMapNonceService) IsStrict() bool { return true }
func (a *syncMapNonceService) IsCrossNode() bool { return false }
func generateNonce() (string, error) {
return generateRandomBase64(21)
}
func generateRandomBase64(srcBytes int) (string, error) {
data := make([]byte, 21)
if _, err := io.ReadFull(rand.Reader, data); err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(data), nil
}
func (a *syncMapNonceService) Get() (string, time.Time, error) {
now := time.Now()
nonce, err := generateNonce()
if err != nil {
return "", now, err
}
then := now.Add(a.validity)
a.nonces.Store(nonce, then)
nextExpiry := a.nextExpiry.Load()
next := time.Unix(nextExpiry, 0)
if then.Before(next) {
a.nextExpiry.Store(then.Unix())
}
a.issued.Add(1)
return nonce, then, nil
}
func (a *syncMapNonceService) Redeem(nonce string) bool {
rawTimeout, present := a.nonces.LoadAndDelete(nonce)
if !present {
return false
}
timeout := rawTimeout.(time.Time)
if time.Now().After(timeout) {
return false
}
return true
}
func (a *syncMapNonceService) Tidy() *NonceStatus {
now := time.Now()
nextRun := now.Add(a.validity)
var outstanding uint64
a.nonces.Range(func(key, value any) bool {
timeout := value.(time.Time)
if now.After(timeout) {
a.nonces.Delete(key)
} else {
outstanding += 1
}
if timeout.Before(nextRun) {
nextRun = timeout
}
return false /* don't quit looping */
})
a.nextExpiry.Store(nextRun.Unix())
return &NonceStatus{
Issued: a.issued.Load(),
Outstanding: outstanding,
}
}