You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
smallstep-certificates/authority/admin/collection.go

181 lines
5.0 KiB
Go

3 years ago
package admin
import (
"crypto/sha1"
3 years ago
"encoding/binary"
"encoding/hex"
"fmt"
"sort"
"strings"
3 years ago
"sync"
"github.com/pkg/errors"
3 years ago
"github.com/smallstep/certificates/authority/provisioner"
3 years ago
"github.com/smallstep/certificates/linkedca"
3 years ago
)
3 years ago
// DefaultAdminLimit is the default limit for listing provisioners.
const DefaultAdminLimit = 20
3 years ago
3 years ago
// DefaultAdminMax is the maximum limit for listing provisioners.
const DefaultAdminMax = 100
3 years ago
3 years ago
type uidAdmin struct {
3 years ago
admin *linkedca.Admin
3 years ago
uid string
3 years ago
}
3 years ago
type adminSlice []uidAdmin
3 years ago
3 years ago
func (p adminSlice) Len() int { return len(p) }
func (p adminSlice) Less(i, j int) bool { return p[i].uid < p[j].uid }
func (p adminSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
3 years ago
// Collection is a memory map of admins.
type Collection struct {
3 years ago
byID *sync.Map
bySubProv *sync.Map
byProv *sync.Map
sorted adminSlice
provisioners *provisioner.Collection
superCount int
superCountByProvisioner map[string]int
3 years ago
}
// NewCollection initializes a collection of provisioners. The given list of
// audiences are the audiences used by the JWT provisioner.
3 years ago
func NewCollection(provisioners *provisioner.Collection) *Collection {
3 years ago
return &Collection{
3 years ago
byID: new(sync.Map),
byProv: new(sync.Map),
bySubProv: new(sync.Map),
superCountByProvisioner: map[string]int{},
provisioners: provisioners,
3 years ago
}
}
// LoadByID a admin by the ID.
3 years ago
func (c *Collection) LoadByID(id string) (*linkedca.Admin, bool) {
3 years ago
return loadAdmin(c.byID, id)
}
func subProvNameHash(sub, provName string) string {
subHash := sha1.Sum([]byte(sub))
provNameHash := sha1.Sum([]byte(provName))
_res := sha1.Sum(append(subHash[:], provNameHash[:]...))
return string(_res[:])
}
// LoadBySubProv a admin by the subject and provisioner name.
3 years ago
func (c *Collection) LoadBySubProv(sub, provName string) (*linkedca.Admin, bool) {
3 years ago
return loadAdmin(c.bySubProv, subProvNameHash(sub, provName))
}
// LoadByProvisioner a admin by the subject and provisioner name.
3 years ago
func (c *Collection) LoadByProvisioner(provName string) ([]*linkedca.Admin, bool) {
3 years ago
a, ok := c.byProv.Load(provName)
if !ok {
return nil, false
}
3 years ago
admins, ok := a.([]*linkedca.Admin)
3 years ago
if !ok {
return nil, false
}
return admins, true
}
// Store adds an admin to the collection and enforces the uniqueness of
// admin IDs and amdin subject <-> provisioner name combos.
3 years ago
func (c *Collection) Store(adm *linkedca.Admin) error {
p, ok := c.provisioners.Load(adm.ProvisionerId)
3 years ago
if !ok {
3 years ago
return fmt.Errorf("provisioner %s not found", adm.ProvisionerId)
3 years ago
}
3 years ago
// Store admin always in byID. ID must be unique.
3 years ago
if _, loaded := c.byID.LoadOrStore(adm.Id, adm); loaded {
3 years ago
return errors.New("cannot add multiple admins with the same id")
}
3 years ago
provName := p.GetName()
// Store admin always in bySubProv. Subject <-> ProvisionerName must be unique.
3 years ago
if _, loaded := c.bySubProv.LoadOrStore(subProvNameHash(adm.Subject, provName), adm); loaded {
3 years ago
c.byID.Delete(adm.Id)
3 years ago
return errors.New("cannot add multiple admins with the same subject and provisioner")
}
if admins, ok := c.LoadByProvisioner(provName); ok {
c.byProv.Store(provName, append(admins, adm))
3 years ago
c.superCountByProvisioner[provName]++
3 years ago
} else {
3 years ago
c.byProv.Store(provName, []*linkedca.Admin{adm})
3 years ago
c.superCountByProvisioner[provName] = 1
3 years ago
}
3 years ago
c.superCount++
3 years ago
3 years ago
// Store sorted admins.
// Use the first 4 bytes (32bit) of the sum to insert the order
// Using big endian format to get the strings sorted:
// 0x00000000, 0x00000001, 0x00000002, ...
bi := make([]byte, 4)
3 years ago
_sum := sha1.Sum([]byte(adm.Id))
3 years ago
sum := _sum[:]
binary.BigEndian.PutUint32(bi, uint32(c.sorted.Len()))
sum[0], sum[1], sum[2], sum[3] = bi[0], bi[1], bi[2], bi[3]
c.sorted = append(c.sorted, uidAdmin{
admin: adm,
uid: hex.EncodeToString(sum),
})
sort.Sort(c.sorted)
3 years ago
return nil
}
3 years ago
// SuperCount returns the total number of admins.
func (c *Collection) SuperCount() int {
return c.superCount
3 years ago
}
3 years ago
// SuperCountByProvisioner returns the total number of admins.
func (c *Collection) SuperCountByProvisioner(provName string) int {
if cnt, ok := c.superCountByProvisioner[provName]; ok {
3 years ago
return cnt
}
return 0
}
// Find implements pagination on a list of sorted provisioners.
3 years ago
func (c *Collection) Find(cursor string, limit int) ([]*linkedca.Admin, string) {
3 years ago
switch {
case limit <= 0:
3 years ago
limit = DefaultAdminLimit
case limit > DefaultAdminMax:
limit = DefaultAdminMax
3 years ago
}
n := c.sorted.Len()
cursor = fmt.Sprintf("%040s", cursor)
i := sort.Search(n, func(i int) bool { return c.sorted[i].uid >= cursor })
3 years ago
slice := []*linkedca.Admin{}
3 years ago
for ; i < n && len(slice) < limit; i++ {
3 years ago
slice = append(slice, c.sorted[i].admin)
3 years ago
}
if i < n {
return slice, strings.TrimLeft(c.sorted[i].uid, "0")
}
return slice, ""
}
3 years ago
func loadAdmin(m *sync.Map, key string) (*linkedca.Admin, bool) {
3 years ago
a, ok := m.Load(key)
if !ok {
return nil, false
}
3 years ago
adm, ok := a.(*linkedca.Admin)
3 years ago
if !ok {
return nil, false
}
return adm, true
}