add refresh

This adds the facilities to refresh resources automatically.
This commit is contained in:
Gibheer 2017-10-06 14:25:02 +02:00
parent bc410d2d10
commit 89e7ac94f5
14 changed files with 300 additions and 44 deletions

View File

@ -88,9 +88,9 @@ func listCertificate(args []string, client *pkiadm.Client) error {
return nil return nil
} }
out := tabwriter.NewWriter(os.Stdout, 2, 2, 1, ' ', tabwriter.AlignRight) out := tabwriter.NewWriter(os.Stdout, 2, 2, 1, ' ', tabwriter.AlignRight)
fmt.Fprintf(out, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t\n", "id", "private", "csr", "ca", "serial", "created", "duration", "self-signed") fmt.Fprintf(out, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t\n", "id", "private", "csr", "ca", "serial", "created", "duration", "self-signed")
for _, cert := range certs { for _, cert := range certs {
fmt.Fprintf(out, "%s\t%s\t%s\t%s\t%s\t%s\t%t\t\n", cert.ID, cert.PrivateKey.ID, cert.CSR.ID, cert.CA.ID, cert.Serial.ID, cert.Created, cert.Duration, cert.IsCA) fmt.Fprintf(out, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%t\t\n", cert.ID, cert.PrivateKey.ID, cert.CSR.ID, cert.CA.ID, cert.Serial.ID, cert.Created, cert.Duration, cert.IsCA)
} }
out.Flush() out.Flush()

View File

@ -3,6 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"sort"
"text/tabwriter" "text/tabwriter"
"github.com/gibheer/pkiadm" "github.com/gibheer/pkiadm"
@ -185,6 +186,7 @@ func list(args []string, c *pkiadm.Client) error {
if err != nil { if err != nil {
return err return err
} }
sort.Sort(resources)
out := tabwriter.NewWriter(os.Stdout, 0, 4, 1, ' ', 0) out := tabwriter.NewWriter(os.Stdout, 0, 4, 1, ' ', 0)
fmt.Fprintf(out, "%s\t%s\t\n", "type", "id") fmt.Fprintf(out, "%s\t%s\t\n", "type", "id")
for _, res := range resources { for _, res := range resources {

View File

@ -22,6 +22,7 @@ type (
ID string ID string
Type pkiadm.CAType Type pkiadm.CAType
Certificate pkiadm.ResourceName Certificate pkiadm.ResourceName
Interval Interval
} }
) )
@ -87,12 +88,18 @@ func (ca *CA) Name() pkiadm.ResourceName {
return pkiadm.ResourceName{ca.ID, pkiadm.RTCA} return pkiadm.ResourceName{ca.ID, pkiadm.RTCA}
} }
// AddDependency registers a depending resource to be retuened by Dependencies() // Refresh must trigger a rebuild of the resource. In this case, this is a NOOP.
// Refresh must trigger a rebuild of the resource.
func (ca *CA) Refresh(*Storage) error { func (ca *CA) Refresh(*Storage) error {
return nil return nil
} }
// RefreshInterval returns the dates and interval settings which are used to
// decide when to trigger a refresh for the resource.
// For CAs, this is a NOOP, as the underlying cert needs the refresh.
func (ca *CA) RefreshInterval() Interval {
return NoInterval
}
// Return the PEM output of the contained resource. // Return the PEM output of the contained resource.
func (ca *CA) Pem() ([]byte, error) { return []byte{}, nil } func (ca *CA) Pem() ([]byte, error) { return []byte{}, nil }
func (ca *CA) Checksum() []byte { return []byte{} } func (ca *CA) Checksum() []byte { return []byte{} }

View File

@ -18,6 +18,8 @@ type (
ID string ID string
IsCA bool IsCA bool
Interval Interval
// TODO remove obsolete field - got replaced with interval
Duration time.Duration Duration time.Duration
Created time.Time Created time.Time
@ -31,6 +33,11 @@ type (
) )
func NewCertificate(id string, privateKey, serial, csr, ca pkiadm.ResourceName, selfSign bool, duration time.Duration) (*Certificate, error) { func NewCertificate(id string, privateKey, serial, csr, ca pkiadm.ResourceName, selfSign bool, duration time.Duration) (*Certificate, error) {
if id == "" {
return nil, ENoIDGiven
}
return &Certificate{ return &Certificate{
ID: id, ID: id,
PrivateKey: privateKey, PrivateKey: privateKey,
@ -39,6 +46,10 @@ func NewCertificate(id string, privateKey, serial, csr, ca pkiadm.ResourceName,
CA: ca, CA: ca,
IsCA: selfSign, IsCA: selfSign,
Duration: duration, Duration: duration,
Interval: Interval{
Created: time.Now(),
RefreshAfter: duration,
},
}, nil }, nil
} }
@ -86,10 +97,16 @@ func (c *Certificate) Refresh(lookup *Storage) error {
return err return err
} }
c.Data = pem.EncodeToMemory(&block) c.Data = pem.EncodeToMemory(&block)
// TODO remove obsolete field
c.Created = time.Now() c.Created = time.Now()
c.Interval.LastRefresh = time.Now()
return nil return nil
} }
func (c *Certificate) RefreshInterval() Interval {
return c.Interval
}
func (c *Certificate) GetCertificate() (*pki.Certificate, error) { func (c *Certificate) GetCertificate() (*pki.Certificate, error) {
// TODO fix this, we must check if there is anything else // TODO fix this, we must check if there is anything else
block, _ := pem.Decode(c.Data) block, _ := pem.Decode(c.Data)
@ -156,6 +173,7 @@ func (s *Server) SetCertificate(changeset pkiadm.CertificateChange, res *pkiadm.
switch field { switch field {
case "duration": case "duration":
cert.Duration = change.Duration cert.Duration = change.Duration
cert.Interval.RefreshAfter = change.Duration
case "private": case "private":
cert.PrivateKey = change.PrivateKey cert.PrivateKey = change.PrivateKey
case "csr": case "csr":

View File

@ -4,6 +4,7 @@ import (
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"net" "net"
"time"
"github.com/gibheer/pki" "github.com/gibheer/pki"
"github.com/gibheer/pkiadm" "github.com/gibheer/pkiadm"
@ -14,6 +15,9 @@ type (
// ID is the unique identifier of the CSR. // ID is the unique identifier of the CSR.
ID string ID string
// Interval represents the refresh timing information.
Interval Interval
// The following options are used to generate the content of the CSR. // The following options are used to generate the content of the CSR.
DNSNames []string DNSNames []string
EmailAddresses []string EmailAddresses []string
@ -30,7 +34,8 @@ type (
// NewCSR creates a new CSR. // NewCSR creates a new CSR.
func NewCSR(id string, pk, subject pkiadm.ResourceName, dnsNames []string, func NewCSR(id string, pk, subject pkiadm.ResourceName, dnsNames []string,
emailAddresses []string, iPAddresses []net.IP) (*CSR, error) { emailAddresses []string, iPAddresses []net.IP, refreshAfter time.Duration,
invalidAfter time.Duration) (*CSR, error) {
return &CSR{ return &CSR{
ID: id, ID: id,
Subject: subject, Subject: subject,
@ -38,6 +43,11 @@ func NewCSR(id string, pk, subject pkiadm.ResourceName, dnsNames []string,
EmailAddresses: emailAddresses, EmailAddresses: emailAddresses,
IPAddresses: iPAddresses, IPAddresses: iPAddresses,
PrivateKey: pk, PrivateKey: pk,
Interval: Interval{
Created: time.Now(),
RefreshAfter: refreshAfter,
InvalidAfter: invalidAfter,
},
}, nil }, nil
} }
@ -78,9 +88,14 @@ func (c *CSR) Refresh(lookup *Storage) error {
return err return err
} }
c.Data = pem.EncodeToMemory(&block) c.Data = pem.EncodeToMemory(&block)
c.Interval.LastRefresh = time.Now()
return nil return nil
} }
func (c *CSR) RefreshInterval() Interval {
return c.Interval
}
// Return the PEM output of the contained resource. // Return the PEM output of the contained resource.
func (c *CSR) Pem() ([]byte, error) { return c.Data, nil } func (c *CSR) Pem() ([]byte, error) { return c.Data, nil }
func (c *CSR) Checksum() []byte { return Hash(c.Data) } func (c *CSR) Checksum() []byte { return Hash(c.Data) }
@ -111,6 +126,7 @@ func (s *Server) CreateCSR(inCSR pkiadm.CSR, res *pkiadm.Result) error {
inCSR.DNSNames, inCSR.DNSNames,
inCSR.EmailAddresses, inCSR.EmailAddresses,
inCSR.IPAddresses, inCSR.IPAddresses,
0, 0,
) )
if err != nil { if err != nil {
res.SetError(err, "Could not create new private key '%s'", inCSR.ID) res.SetError(err, "Could not create new private key '%s'", inCSR.ID)

View File

@ -6,6 +6,7 @@ import (
"log" "log"
"os" "os"
"os/exec" "os/exec"
"time"
"github.com/gibheer/pkiadm" "github.com/gibheer/pkiadm"
) )
@ -23,10 +24,13 @@ type (
Path string Path string
Dependencies []pkiadm.ResourceName Dependencies []pkiadm.ResourceName
Interval Interval
} }
) )
func NewLocation(id, path, preCom, postCom string, res []pkiadm.ResourceName) (*Location, error) { func NewLocation(id, path, preCom, postCom string, res []pkiadm.ResourceName,
interval Interval) (*Location, error) {
if id == "" { if id == "" {
return nil, ENoIDGiven return nil, ENoIDGiven
} }
@ -37,6 +41,7 @@ func NewLocation(id, path, preCom, postCom string, res []pkiadm.ResourceName) (*
ID: id, ID: id,
Path: path, Path: path,
Dependencies: res, Dependencies: res,
Interval: interval,
} }
return l, nil return l, nil
} }
@ -78,9 +83,14 @@ func (l *Location) Refresh(lookup *Storage) error {
return err return err
} }
} }
l.Interval.LastRefresh = time.Now()
return nil return nil
} }
func (l *Location) RefreshInterval() Interval {
return l.Interval
}
func (l *Location) DependsOn() []pkiadm.ResourceName { return l.Dependencies } func (l *Location) DependsOn() []pkiadm.ResourceName { return l.Dependencies }
// Pem is not used by location, as it does not contain any data. // Pem is not used by location, as it does not contain any data.
@ -97,7 +107,7 @@ func (s *Server) CreateLocation(inLoc pkiadm.Location, res *pkiadm.Result) error
for _, dep := range inLoc.Dependencies { for _, dep := range inLoc.Dependencies {
deps = append(deps, pkiadm.ResourceName{ID: dep.ID, Type: dep.Type}) deps = append(deps, pkiadm.ResourceName{ID: dep.ID, Type: dep.Type})
} }
loc, err := NewLocation(inLoc.ID, inLoc.Path, inLoc.PreCommand, inLoc.PostCommand, deps) loc, err := NewLocation(inLoc.ID, inLoc.Path, inLoc.PreCommand, inLoc.PostCommand, deps, NoInterval)
if err != nil { if err != nil {
res.SetError(err, "Could not create location '%s'", inLoc.ID) res.SetError(err, "Could not create location '%s'", inLoc.ID)
return nil return nil

View File

@ -6,6 +6,7 @@ import (
"net/rpc" "net/rpc"
"os" "os"
"os/signal" "os/signal"
"time"
"github.com/gibheer/pkiadm" "github.com/gibheer/pkiadm"
) )
@ -19,20 +20,42 @@ const (
EAlreadyExist = Error("resource already exists") EAlreadyExist = Error("resource already exists")
) )
var (
NoInterval = Interval{}
)
type ( type (
Resource interface { Resource interface {
// Return the unique ResourceName // Return the unique ResourceName
Name() pkiadm.ResourceName Name() pkiadm.ResourceName
// AddDependency registers a depending resource to be retuened by Dependencies()
// Refresh must trigger a rebuild of the resource. // Refresh must trigger a rebuild of the resource.
Refresh(*Storage) error Refresh(*Storage) error
// RefreshInterval returns the dates and interval settings which are used to
// decide when to trigger a refresh for the resource.
RefreshInterval() Interval
// Return the PEM output of the contained resource. // Return the PEM output of the contained resource.
Pem() ([]byte, error) Pem() ([]byte, error)
// Return the checksum of the PEM content.
Checksum() []byte Checksum() []byte
// DependsOn must return the resource names it is depending on. // DependsOn must return the resource names it is depending on.
DependsOn() []pkiadm.ResourceName DependsOn() []pkiadm.ResourceName
} }
Interval struct {
// Created states the time, the resource was created.
Created time.Time
// LastRefresh is the time, when the resource was last refreshed.
LastRefresh time.Time
// RefreshAfter is the duration after which the refresh of the resource
// is triggered.
RefreshAfter time.Duration
// InvalidAfter is the duration after which this resource becomes invalid.
// The decision when a resource becomes invalid is based on the created time
// and the duration. When the refresh duration is less than the invalid
// duration, then the resource will never be invalid.
InvalidAfter time.Duration
}
Error string Error string
) )

View File

@ -4,6 +4,7 @@ import (
"crypto/elliptic" "crypto/elliptic"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"time"
"github.com/gibheer/pki" "github.com/gibheer/pki"
"github.com/gibheer/pkiadm" "github.com/gibheer/pkiadm"
@ -17,14 +18,15 @@ const (
type ( type (
PrivateKey struct { PrivateKey struct {
ID string ID string
PKType pkiadm.PrivateKeyType PKType pkiadm.PrivateKeyType
Bits uint Bits uint
Key []byte Key []byte
Interval Interval
} }
) )
func NewPrivateKey(id string, pkType pkiadm.PrivateKeyType, bits uint) (*PrivateKey, error) { func NewPrivateKey(id string, pkType pkiadm.PrivateKeyType, bits uint, interval Interval) (*PrivateKey, error) {
if id == "" { if id == "" {
return nil, ENoIDGiven return nil, ENoIDGiven
} }
@ -32,9 +34,10 @@ func NewPrivateKey(id string, pkType pkiadm.PrivateKeyType, bits uint) (*Private
return nil, err return nil, err
} }
pk := PrivateKey{ pk := PrivateKey{
ID: id, ID: id,
PKType: pkType, PKType: pkType,
Bits: bits, Bits: bits,
Interval: interval,
} }
return &pk, nil return &pk, nil
} }
@ -88,9 +91,16 @@ func (p *PrivateKey) Refresh(_ *Storage) error {
return err return err
} }
p.Key = pem.EncodeToMemory(&block) p.Key = pem.EncodeToMemory(&block)
p.Interval.LastRefresh = time.Now()
return nil return nil
} }
// RefreshInterval returns the dates and interval settings which are used to
// decide when to trigger a refresh for the resource.
func (p *PrivateKey) RefreshInterval() Interval {
return p.Interval
}
func (p *PrivateKey) GetKey() (pki.PrivateKey, error) { func (p *PrivateKey) GetKey() (pki.PrivateKey, error) {
var ( var (
err error err error
@ -139,7 +149,7 @@ func (s *Server) CreatePrivateKey(inPk pkiadm.PrivateKey, res *pkiadm.Result) er
s.lock() s.lock()
defer s.unlock() defer s.unlock()
pk, err := NewPrivateKey(inPk.ID, inPk.Type, inPk.Bits) pk, err := NewPrivateKey(inPk.ID, inPk.Type, inPk.Bits, NoInterval)
if err != nil { if err != nil {
res.SetError(err, "Could not create new private key '%s'", inPk.ID) res.SetError(err, "Could not create new private key '%s'", inPk.ID)
return nil return nil

View File

@ -3,6 +3,7 @@ package main
import ( import (
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"time"
"github.com/gibheer/pkiadm" "github.com/gibheer/pkiadm"
) )
@ -14,13 +15,21 @@ type (
PrivateKey pkiadm.ResourceName PrivateKey pkiadm.ResourceName
Type pkiadm.PrivateKeyType // mark the type of the public key Type pkiadm.PrivateKeyType // mark the type of the public key
Key []byte Key []byte
Interval Interval
} }
) )
func NewPublicKey(id string, pk pkiadm.ResourceName) (*PublicKey, error) { func NewPublicKey(id string, pk pkiadm.ResourceName, refreshAfter time.Duration,
invalidAfter time.Duration) (*PublicKey, error) {
pub := PublicKey{ pub := PublicKey{
ID: id, ID: id,
PrivateKey: pk, PrivateKey: pk,
Interval: Interval{
Created: time.Now(),
RefreshAfter: refreshAfter,
InvalidAfter: invalidAfter,
},
} }
return &pub, nil return &pub, nil
} }
@ -45,9 +54,14 @@ func (p *PublicKey) Refresh(lookup *Storage) error {
return err return err
} }
p.Key = pem.EncodeToMemory(&block) p.Key = pem.EncodeToMemory(&block)
p.Interval.LastRefresh = time.Now()
return nil return nil
} }
func (p *PublicKey) RefreshInterval() Interval {
return p.Interval
}
func (p *PublicKey) DependsOn() []pkiadm.ResourceName { func (p *PublicKey) DependsOn() []pkiadm.ResourceName {
return []pkiadm.ResourceName{p.PrivateKey} return []pkiadm.ResourceName{p.PrivateKey}
} }
@ -64,7 +78,7 @@ func (s *Server) CreatePublicKey(inPub pkiadm.PublicKey, res *pkiadm.Result) err
s.lock() s.lock()
defer s.unlock() defer s.unlock()
pub, err := NewPublicKey(inPub.ID, inPub.PrivateKey) pub, err := NewPublicKey(inPub.ID, inPub.PrivateKey, 0, 0)
if err != nil { if err != nil {
res.SetError(err, "Could not create public key '%s'", inPub.ID) res.SetError(err, "Could not create public key '%s'", inPub.ID)
return nil return nil

View File

@ -33,7 +33,6 @@ func NewSerial(id string, min, max int64) (*Serial, error) {
// Return the unique ResourceName // Return the unique ResourceName
func (s *Serial) Name() pkiadm.ResourceName { return pkiadm.ResourceName{s.ID, pkiadm.RTSerial} } func (s *Serial) Name() pkiadm.ResourceName { return pkiadm.ResourceName{s.ID, pkiadm.RTSerial} }
// AddDependency registers a depending resource to be retuened by Dependencies()
// Refresh must trigger a rebuild of the resource. // Refresh must trigger a rebuild of the resource.
func (s *Serial) Refresh(*Storage) error { func (s *Serial) Refresh(*Storage) error {
// This is a NOOP, because there is nothing to refresh. Depending resources // This is a NOOP, because there is nothing to refresh. Depending resources
@ -41,6 +40,11 @@ func (s *Serial) Refresh(*Storage) error {
return nil return nil
} }
// RefreshInterval is a NOOP here, as serials can't be refreshed.
func (s *Serial) RefreshInterval() Interval {
return NoInterval
}
// Return the PEM output of the contained resource. // Return the PEM output of the contained resource.
func (s *Serial) Pem() ([]byte, error) { return []byte{}, nil } func (s *Serial) Pem() ([]byte, error) { return []byte{}, nil }
func (s *Serial) Checksum() []byte { return []byte{} } func (s *Serial) Checksum() []byte { return []byte{} }

View File

@ -33,26 +33,10 @@ func (s *Server) store(res *pkiadm.Result) error {
} }
func (s *Server) List(filter pkiadm.Filter, result *pkiadm.ResultResource) error { func (s *Server) List(filter pkiadm.Filter, result *pkiadm.ResultResource) error {
for _, res := range s.storage.PrivateKeys { resources := s.storage.List()
result.Resources = append(result.Resources, res.Name()) result.Resources = make([]pkiadm.ResourceName, len(resources))
} for i, res := range resources {
for _, res := range s.storage.PublicKeys { result.Resources[i] = res.Name()
result.Resources = append(result.Resources, res.Name())
}
for _, res := range s.storage.Locations {
result.Resources = append(result.Resources, res.Name())
}
for _, res := range s.storage.Certificates {
result.Resources = append(result.Resources, res.Name())
}
for _, res := range s.storage.CSRs {
result.Resources = append(result.Resources, res.Name())
}
for _, res := range s.storage.Serials {
result.Resources = append(result.Resources, res.Name())
}
for _, res := range s.storage.Subjects {
result.Resources = append(result.Resources, res.Name())
} }
return nil return nil
} }

View File

@ -6,6 +6,8 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"sort"
"time"
"github.com/gibheer/pkiadm" "github.com/gibheer/pkiadm"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -28,6 +30,19 @@ type (
// dependencies maps from a resource name to all resources which depend // dependencies maps from a resource name to all resources which depend
// on it. // on it.
dependencies map[string]map[string]Resource dependencies map[string]map[string]Resource
// refresh order contains all resources in the order they need to be
// refreshed next.
refreshOrder RefreshList
refreshTimer *time.Timer
}
// RefreshList is a list of resources
RefreshList []RefreshSet
// RefreshSet contains the vital information to decide, when to refresh
// the specified resource.
RefreshSet struct {
Name pkiadm.ResourceName
Interval Interval
} }
) )
@ -68,6 +83,7 @@ func (s *Storage) load() error {
if err := s.refreshDependencies(); err != nil { if err := s.refreshDependencies(); err != nil {
return err return err
} }
s.scanForRefresh()
return nil return nil
} }
@ -101,6 +117,69 @@ func (s *Storage) refreshDependencies() error {
return nil return nil
} }
// scanForRefresh updates the list of resources that need an update.
func (s *Storage) scanForRefresh() {
if s.refreshTimer != nil {
s.refreshTimer.Stop()
}
refList := RefreshList{}
for _, res := range s.PrivateKeys {
refList.Add(res)
}
for _, res := range s.PublicKeys {
refList.Add(res)
}
for _, res := range s.CSRs {
refList.Add(res)
}
for _, res := range s.Certificates {
refList.Add(res)
}
for _, res := range s.Locations {
refList.Add(res)
}
sort.Sort(refList)
if len(refList) == 0 {
log.Println("nothing found to refresh, looking again in 24h")
s.refreshTimer = time.AfterFunc(24*time.Hour, s.scanForRefresh)
return
}
s.refreshOrder = refList
duration := refList[0].Interval.LastRefresh.
Add(refList[0].Interval.RefreshAfter).
Sub(time.Now())
if duration <= 5*time.Second {
duration = 5 * time.Second
}
log.Printf("next refresh planned for '%s' in %s", refList[0].Name, duration)
s.refreshTimer = time.AfterFunc(
duration,
s.refresh,
)
}
func (s *Storage) refresh() {
if len(s.refreshOrder) == 0 {
return
}
resName := s.refreshOrder[0].Name
res, err := s.Get(resName)
if err != nil {
// the resource doesn't exist anymore, so just rescan
log.Printf("resource to refresh has gone away: %s", resName)
goto rescan
}
if err := res.Refresh(s); err != nil {
log.Printf("error refreshing resource '%s': %s", res.Name(), err)
}
if err := s.store(); err != nil {
log.Printf("could not update resources: %s", err)
}
rescan:
log.Printf("rescanning for new entries")
s.scanForRefresh()
}
// addDependency adds a resource to the dependency graph. // addDependency adds a resource to the dependency graph.
func (s *Storage) addDependency(r Resource) error { func (s *Storage) addDependency(r Resource) error {
for _, rn := range r.DependsOn() { for _, rn := range r.DependsOn() {
@ -138,6 +217,7 @@ func (s *Storage) AddSerial(se *Serial) error {
return err return err
} }
s.Serials[se.Name().ID] = se s.Serials[se.Name().ID] = se
s.scanForRefresh()
return s.addDependency(se) return s.addDependency(se)
} }
@ -150,6 +230,7 @@ func (s *Storage) AddSubject(se *Subject) error {
return err return err
} }
s.Subjects[se.Name().ID] = se s.Subjects[se.Name().ID] = se
s.scanForRefresh()
return s.addDependency(se) return s.addDependency(se)
} }
@ -159,6 +240,7 @@ func (s *Storage) AddPrivateKey(pk *PrivateKey) error {
return err return err
} }
s.PrivateKeys[pk.Name().ID] = pk s.PrivateKeys[pk.Name().ID] = pk
s.scanForRefresh()
return s.addDependency(pk) return s.addDependency(pk)
} }
@ -168,6 +250,7 @@ func (s *Storage) AddPublicKey(pub *PublicKey) error {
return err return err
} }
s.PublicKeys[pub.Name().ID] = pub s.PublicKeys[pub.Name().ID] = pub
s.scanForRefresh()
return s.addDependency(pub) return s.addDependency(pub)
} }
@ -177,6 +260,7 @@ func (s *Storage) AddCertificate(cert *Certificate) error {
return err return err
} }
s.Certificates[cert.Name().ID] = cert s.Certificates[cert.Name().ID] = cert
s.scanForRefresh()
return s.addDependency(cert) return s.addDependency(cert)
} }
@ -186,6 +270,7 @@ func (s *Storage) AddCSR(csr *CSR) error {
return err return err
} }
s.CSRs[csr.Name().ID] = csr s.CSRs[csr.Name().ID] = csr
s.scanForRefresh()
return s.addDependency(csr) return s.addDependency(csr)
} }
@ -195,6 +280,7 @@ func (s *Storage) AddLocation(l *Location) error {
return err return err
} }
s.Locations[l.Name().ID] = l s.Locations[l.Name().ID] = l
s.scanForRefresh()
return s.addDependency(l) return s.addDependency(l)
} }
@ -203,6 +289,7 @@ func (s *Storage) AddCA(ca *CA) error {
return err return err
} }
s.CAs[ca.Name().ID] = ca s.CAs[ca.Name().ID] = ca
s.scanForRefresh()
return s.addDependency(ca) return s.addDependency(ca)
} }
@ -326,6 +413,7 @@ func (s *Storage) Remove(r Resource) error {
delete(deps, r.Name().String()) delete(deps, r.Name().String())
} }
} }
s.scanForRefresh()
return nil return nil
} }
@ -364,5 +452,65 @@ func (s *Storage) Update(rn pkiadm.ResourceName) error {
return err return err
} }
} }
s.scanForRefresh()
return nil return nil
} }
// List returns all currently registered resources.
func (s *Storage) List() []Resource {
resources := []Resource{}
for _, res := range s.PrivateKeys {
resources = append(resources, res)
}
for _, res := range s.PublicKeys {
resources = append(resources, res)
}
for _, res := range s.Locations {
resources = append(resources, res)
}
for _, res := range s.Certificates {
resources = append(resources, res)
}
for _, res := range s.CSRs {
resources = append(resources, res)
}
for _, res := range s.Serials {
resources = append(resources, res)
}
for _, res := range s.Subjects {
resources = append(resources, res)
}
return resources
}
// Add adds a resource to the refreshList when it should be refreshed.
func (refList *RefreshList) Add(res Resource) {
refSet := RefreshSet{
Name: res.Name(),
Interval: res.RefreshInterval(),
}
if refSet.Interval.RefreshAfter <= 0 {
return
}
newRefList := append(*refList, refSet)
*refList = newRefList
}
// Len is the number of elements in the collection.
func (refList RefreshList) Len() int { return len(refList) }
// Less reports whether the element with
// index i should sort before the element with index j.
func (refList RefreshList) Less(i, j int) bool {
return 0 > refList[i].Interval.LastRefresh.Add(
refList[i].Interval.RefreshAfter,
).Sub(
refList[j].Interval.LastRefresh.Add(
refList[j].Interval.RefreshAfter),
)
}
// Swap swaps the elements with indexes i and j.
func (refList RefreshList) Swap(i, j int) {
refList[i], refList[j] = refList[j], refList[i]
}

View File

@ -3,14 +3,16 @@ package main
import ( import (
"crypto/x509/pkix" "crypto/x509/pkix"
"fmt" "fmt"
"time"
"github.com/gibheer/pkiadm" "github.com/gibheer/pkiadm"
) )
type ( type (
Subject struct { Subject struct {
ID string ID string
Data pkix.Name Data pkix.Name
Created time.Time
} }
) )
@ -26,11 +28,14 @@ func NewSubject(id string, name pkix.Name) (*Subject, error) {
// Return the unique ResourceName // Return the unique ResourceName
func (sub *Subject) Name() pkiadm.ResourceName { return pkiadm.ResourceName{sub.ID, pkiadm.RTSubject} } func (sub *Subject) Name() pkiadm.ResourceName { return pkiadm.ResourceName{sub.ID, pkiadm.RTSubject} }
// AddDependency registers a depending resource to be retuened by Dependencies()
// Refresh must trigger a rebuild of the resource. // Refresh must trigger a rebuild of the resource.
// This is a NOOP as it does not have any dependencies. // This is a NOOP as it does not have any dependencies.
func (sub *Subject) Refresh(_ *Storage) error { return nil } func (sub *Subject) Refresh(_ *Storage) error { return nil }
// RefreshInterval returns the dates and interval settings which are used to
// decide when to trigger a refresh for the resource.
func (sub *Subject) RefreshInterval() Interval { return Interval{Created: sub.Created} }
// Return the PEM output of the contained resource. // Return the PEM output of the contained resource.
func (sub *Subject) Pem() ([]byte, error) { return []byte{}, nil } func (sub *Subject) Pem() ([]byte, error) { return []byte{}, nil }
func (sub *Subject) Checksum() []byte { return []byte{} } func (sub *Subject) Checksum() []byte { return []byte{} }

View File

@ -49,6 +49,21 @@ type ResourceType uint
func (r ResourceName) String() string { return r.Type.String() + "/" + r.ID } func (r ResourceName) String() string { return r.Type.String() + "/" + r.ID }
type ResourceNameList []ResourceName
func (r ResourceNameList) Len() int {
return len(r)
}
func (r ResourceNameList) Less(i, j int) bool {
if r[i].Type != r[j].Type {
return r[i].Type < r[j].Type
}
return r[i].ID < r[j].ID
}
func (r ResourceNameList) Swap(i, j int) {
r[i], r[j] = r[j], r[i]
}
type Filter struct{} type Filter struct{}
type ResultResource struct { type ResultResource struct {
@ -56,7 +71,7 @@ type ResultResource struct {
Resources []ResourceName Resources []ResourceName
} }
func (c *Client) List() ([]ResourceName, error) { func (c *Client) List() (ResourceNameList, error) {
result := ResultResource{} result := ResultResource{}
if err := c.query("List", Filter{}, &result); err != nil { if err := c.query("List", Filter{}, &result); err != nil {
return []ResourceName{}, err return []ResourceName{}, err