certificates/authority/admin/collection.go

191 lines
5.1 KiB
Go
Raw Normal View History

2021-05-18 04:07:25 +00:00
package admin
import (
"crypto/sha1"
2021-05-18 23:50:54 +00:00
"encoding/binary"
"encoding/hex"
"fmt"
"sort"
"strings"
2021-05-18 04:07:25 +00:00
"sync"
"github.com/pkg/errors"
2021-05-18 23:50:54 +00:00
"github.com/smallstep/certificates/authority/provisioner"
2021-05-18 04:07:25 +00:00
)
2021-05-18 23:50:54 +00:00
// DefaultAdminLimit is the default limit for listing provisioners.
const DefaultAdminLimit = 20
2021-05-18 04:07:25 +00:00
2021-05-18 23:50:54 +00:00
// DefaultAdminMax is the maximum limit for listing provisioners.
const DefaultAdminMax = 100
2021-05-18 04:07:25 +00:00
2021-05-18 23:50:54 +00:00
type uidAdmin struct {
admin *Admin
uid string
2021-05-18 04:07:25 +00:00
}
2021-05-18 23:50:54 +00:00
type adminSlice []uidAdmin
2021-05-18 04:07:25 +00:00
2021-05-18 23:50:54 +00:00
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] }
2021-05-18 04:07:25 +00:00
// Collection is a memory map of admins.
type Collection struct {
2021-05-21 20:31:41 +00:00
byID *sync.Map
bySubProv *sync.Map
byProv *sync.Map
sorted adminSlice
provisioners *provisioner.Collection
superCount int
superCountByProvisioner map[string]int
2021-05-18 04:07:25 +00:00
}
// NewCollection initializes a collection of provisioners. The given list of
// audiences are the audiences used by the JWT provisioner.
2021-05-18 23:50:54 +00:00
func NewCollection(provisioners *provisioner.Collection) *Collection {
2021-05-18 04:07:25 +00:00
return &Collection{
2021-05-21 20:31:41 +00:00
byID: new(sync.Map),
byProv: new(sync.Map),
bySubProv: new(sync.Map),
superCountByProvisioner: map[string]int{},
provisioners: provisioners,
2021-05-18 04:07:25 +00:00
}
}
// LoadByID a admin by the ID.
func (c *Collection) LoadByID(id string) (*Admin, bool) {
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.
func (c *Collection) LoadBySubProv(sub, provName string) (*Admin, bool) {
return loadAdmin(c.bySubProv, subProvNameHash(sub, provName))
}
// LoadByProvisioner a admin by the subject and provisioner name.
func (c *Collection) LoadByProvisioner(provName string) ([]*Admin, bool) {
a, ok := c.byProv.Load(provName)
if !ok {
return nil, false
}
admins, ok := a.([]*Admin)
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.
func (c *Collection) Store(adm *Admin) error {
2021-05-18 23:50:54 +00:00
p, ok := c.provisioners.Load(adm.ProvisionerID)
if !ok {
return fmt.Errorf("provisioner %s not found", adm.ProvisionerID)
}
adm.ProvisionerName = p.GetName()
adm.ProvisionerType = p.GetType().String()
2021-05-18 04:07:25 +00:00
// Store admin always in byID. ID must be unique.
if _, loaded := c.byID.LoadOrStore(adm.ID, adm); loaded {
return errors.New("cannot add multiple admins with the same id")
}
2021-05-18 23:50:54 +00:00
provName := adm.ProvisionerName
2021-05-18 04:07:25 +00:00
// Store admin alwasy in bySubProv. Subject <-> ProvisionerName must be unique.
if _, loaded := c.bySubProv.LoadOrStore(subProvNameHash(adm.Subject, provName), adm); loaded {
c.byID.Delete(adm.ID)
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))
2021-05-21 20:31:41 +00:00
c.superCountByProvisioner[provName]++
2021-05-18 04:07:25 +00:00
} else {
c.byProv.Store(provName, []*Admin{adm})
2021-05-21 20:31:41 +00:00
c.superCountByProvisioner[provName] = 1
2021-05-18 04:07:25 +00:00
}
2021-05-21 20:31:41 +00:00
c.superCount++
2021-05-18 04:07:25 +00:00
2021-05-18 23:50:54 +00:00
// 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)
_sum := sha1.Sum([]byte(adm.ID))
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)
2021-05-18 04:07:25 +00:00
return nil
}
2021-05-21 20:31:41 +00:00
// SuperCount returns the total number of admins.
func (c *Collection) SuperCount() int {
return c.superCount
2021-05-18 04:07:25 +00:00
}
2021-05-21 20:31:41 +00:00
// SuperCountByProvisioner returns the total number of admins.
func (c *Collection) SuperCountByProvisioner(provName string) int {
if cnt, ok := c.superCountByProvisioner[provName]; ok {
2021-05-18 04:07:25 +00:00
return cnt
}
return 0
}
// Find implements pagination on a list of sorted provisioners.
2021-05-18 23:50:54 +00:00
func (c *Collection) Find(cursor string, limit int) ([]*Admin, string) {
2021-05-18 04:07:25 +00:00
switch {
case limit <= 0:
2021-05-18 23:50:54 +00:00
limit = DefaultAdminLimit
case limit > DefaultAdminMax:
limit = DefaultAdminMax
2021-05-18 04:07:25 +00:00
}
n := c.sorted.Len()
cursor = fmt.Sprintf("%040s", cursor)
i := sort.Search(n, func(i int) bool { return c.sorted[i].uid >= cursor })
2021-05-18 23:50:54 +00:00
slice := []*Admin{}
2021-05-18 04:07:25 +00:00
for ; i < n && len(slice) < limit; i++ {
2021-05-18 23:50:54 +00:00
slice = append(slice, c.sorted[i].admin)
2021-05-18 04:07:25 +00:00
}
if i < n {
return slice, strings.TrimLeft(c.sorted[i].uid, "0")
}
return slice, ""
}
func loadAdmin(m *sync.Map, key string) (*Admin, bool) {
a, ok := m.Load(key)
if !ok {
return nil, false
}
adm, ok := a.(*Admin)
if !ok {
return nil, false
}
return adm, true
}
/*
// provisionerSum returns the SHA1 of the provisioners ID. From this we will
// create the unique and sorted id.
func provisionerSum(p Interface) []byte {
sum := sha1.Sum([]byte(p.GetID()))
return sum[:]
}
*/