2017-05-28 11:33:04 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
|
2017-05-31 21:03:51 +02:00
|
|
|
"github.com/gibheer/pkiadm"
|
2017-05-28 11:33:04 +02:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
|
|
|
// Storage is used to add and lookup resources and manages the dependency
|
|
|
|
// chain on an update.
|
|
|
|
Storage struct {
|
|
|
|
// path to the place where the storage should be stored.
|
|
|
|
path string
|
|
|
|
PrivateKeys map[string]*PrivateKey
|
|
|
|
PublicKeys map[string]*PublicKey
|
|
|
|
Locations map[string]*Location
|
|
|
|
Certificates map[string]*Certificate
|
|
|
|
CSRs map[string]*CSR
|
|
|
|
Serials map[string]*Serial
|
|
|
|
Subjects map[string]*Subject
|
|
|
|
// dependencies maps from a resource name to all resources which depend
|
|
|
|
// on it.
|
|
|
|
dependencies map[string]map[string]Resource
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// NewStorage builds a new storage instance and loads available data from the
|
|
|
|
// provided file path.
|
|
|
|
func NewStorage(path string) (*Storage, error) {
|
|
|
|
s := &Storage{
|
|
|
|
path: path,
|
|
|
|
PrivateKeys: map[string]*PrivateKey{},
|
|
|
|
PublicKeys: map[string]*PublicKey{},
|
|
|
|
Locations: map[string]*Location{},
|
|
|
|
Certificates: map[string]*Certificate{},
|
|
|
|
CSRs: map[string]*CSR{},
|
|
|
|
Serials: map[string]*Serial{},
|
|
|
|
Subjects: map[string]*Subject{},
|
|
|
|
dependencies: map[string]map[string]Resource{},
|
|
|
|
}
|
|
|
|
if err := s.load(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// load will restore the state from the file to the storage and overwrite
|
|
|
|
// already existing resources.
|
|
|
|
func (s *Storage) load() error {
|
|
|
|
raw, err := ioutil.ReadFile(s.path)
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := json.Unmarshal(raw, s); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := s.refreshDependencies(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// refreshDependencies updates the inter resource dependencies.
|
|
|
|
func (s *Storage) refreshDependencies() error {
|
|
|
|
// TODO do something about broken dependencies
|
|
|
|
for _, se := range s.Serials {
|
|
|
|
_ = s.addDependency(se) // ignore errors here, as dependencies might be broken
|
|
|
|
}
|
|
|
|
for _, subj := range s.Subjects {
|
|
|
|
_ = s.addDependency(subj)
|
|
|
|
}
|
|
|
|
for _, pk := range s.PrivateKeys {
|
|
|
|
_ = s.addDependency(pk)
|
|
|
|
}
|
|
|
|
for _, pub := range s.PublicKeys {
|
|
|
|
_ = s.addDependency(pub)
|
|
|
|
}
|
|
|
|
for _, csr := range s.CSRs {
|
|
|
|
_ = s.addDependency(csr)
|
|
|
|
}
|
|
|
|
for _, cert := range s.Certificates {
|
|
|
|
_ = s.addDependency(cert)
|
|
|
|
}
|
|
|
|
for _, l := range s.Locations {
|
|
|
|
_ = s.addDependency(l)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// addDependency adds a resource to the dependency graph.
|
|
|
|
func (s *Storage) addDependency(r Resource) error {
|
|
|
|
for _, rn := range r.DependsOn() {
|
|
|
|
_, err := s.Get(rn)
|
|
|
|
if err != nil {
|
|
|
|
return Error(fmt.Sprintf("problem with dependency '%s': %s", rn, err))
|
|
|
|
}
|
|
|
|
deps, found := s.dependencies[rn.String()]
|
|
|
|
if !found {
|
|
|
|
s.dependencies[rn.String()] = map[string]Resource{r.Name().String(): r}
|
|
|
|
} else {
|
|
|
|
deps[r.Name().String()] = r
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// store writes the content of the storage to the disk in json format.
|
|
|
|
func (s *Storage) store() error {
|
|
|
|
raw, err := json.MarshalIndent(s, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("could not marshal data: %s", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := ioutil.WriteFile(s.path, raw, 0600); err != nil {
|
|
|
|
log.Printf("could not write to file '%s': %s", s.path, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddSerial adds a serial to the storage and refreshes the dependencies.
|
|
|
|
func (s *Storage) AddSerial(se *Serial) error {
|
|
|
|
if err := se.Refresh(s); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
s.Serials[se.Name().ID] = se
|
|
|
|
return s.addDependency(se)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddSubject adds a new subject to the storage and refreshes the dependencies.
|
|
|
|
func (s *Storage) AddSubject(se *Subject) error {
|
|
|
|
if _, found := s.Subjects[se.Name().ID]; found {
|
|
|
|
return EAlreadyExist
|
|
|
|
}
|
|
|
|
if err := se.Refresh(s); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
s.Subjects[se.Name().ID] = se
|
|
|
|
return s.addDependency(se)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddPrivateKey adds a private key to the storage and refreshes the dependencies.
|
|
|
|
func (s *Storage) AddPrivateKey(pk *PrivateKey) error {
|
|
|
|
if err := pk.Refresh(s); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
s.PrivateKeys[pk.Name().ID] = pk
|
|
|
|
return s.addDependency(pk)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddPublicKey adds a public key to the storage and refreshes the dependencies.
|
|
|
|
func (s *Storage) AddPublicKey(pub *PublicKey) error {
|
|
|
|
if err := pub.Refresh(s); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
s.PublicKeys[pub.Name().ID] = pub
|
|
|
|
return s.addDependency(pub)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddCertificate adds a certificate to the storage and refreshes the dependencies.
|
|
|
|
func (s *Storage) AddCertificate(cert *Certificate) error {
|
|
|
|
if err := cert.Refresh(s); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
s.Certificates[cert.Name().ID] = cert
|
|
|
|
return s.addDependency(cert)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddCSR adds a CSR to the storage and refreshes the dependencies.
|
|
|
|
func (s *Storage) AddCSR(csr *CSR) error {
|
|
|
|
if err := csr.Refresh(s); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
s.CSRs[csr.Name().ID] = csr
|
|
|
|
return s.addDependency(csr)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddLocation adds a location to the storage and refreshes the dependencies.
|
|
|
|
func (s *Storage) AddLocation(l *Location) error {
|
|
|
|
if err := l.Refresh(s); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
s.Locations[l.Name().ID] = l
|
|
|
|
return s.addDependency(l)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get figures out the resource to the ResourceName if available.
|
2017-05-31 21:03:51 +02:00
|
|
|
func (s *Storage) Get(r pkiadm.ResourceName) (Resource, error) {
|
2017-05-28 11:33:04 +02:00
|
|
|
if r.ID == "" {
|
|
|
|
return nil, ENoIDGiven
|
|
|
|
}
|
|
|
|
switch r.Type {
|
2017-05-31 21:03:51 +02:00
|
|
|
case pkiadm.RTSerial:
|
2017-05-28 11:33:04 +02:00
|
|
|
return s.GetSerial(r)
|
2017-05-31 21:03:51 +02:00
|
|
|
case pkiadm.RTSubject:
|
2017-05-28 11:33:04 +02:00
|
|
|
return s.GetSubject(r)
|
2017-05-31 21:03:51 +02:00
|
|
|
case pkiadm.RTPrivateKey:
|
2017-05-28 11:33:04 +02:00
|
|
|
return s.GetPrivateKey(r)
|
2017-05-31 21:03:51 +02:00
|
|
|
case pkiadm.RTPublicKey:
|
2017-05-28 11:33:04 +02:00
|
|
|
return s.GetPublicKey(r)
|
2017-05-31 21:03:51 +02:00
|
|
|
case pkiadm.RTCSR:
|
2017-05-28 11:33:04 +02:00
|
|
|
return s.GetCSR(r)
|
2017-05-31 21:03:51 +02:00
|
|
|
case pkiadm.RTCertificate:
|
2017-05-28 11:33:04 +02:00
|
|
|
return s.GetCertificate(r)
|
2017-05-31 21:03:51 +02:00
|
|
|
case pkiadm.RTLocation:
|
2017-05-28 11:33:04 +02:00
|
|
|
return s.GetLocation(r)
|
|
|
|
default:
|
|
|
|
return nil, EUnknownType
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetSerial returns the Serial matching the ResourceName.
|
2017-05-31 21:03:51 +02:00
|
|
|
func (s *Storage) GetSerial(r pkiadm.ResourceName) (*Serial, error) {
|
2017-05-28 11:33:04 +02:00
|
|
|
if se, found := s.Serials[r.ID]; found {
|
|
|
|
return se, nil
|
|
|
|
}
|
|
|
|
return nil, errors.Wrapf(ENotFound, "no serial with id '%s' found", r)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetSubject returns the Subject matching the ResourceName.
|
2017-05-31 21:03:51 +02:00
|
|
|
func (s *Storage) GetSubject(r pkiadm.ResourceName) (*Subject, error) {
|
2017-05-28 11:33:04 +02:00
|
|
|
if se, found := s.Subjects[r.ID]; found {
|
|
|
|
return se, nil
|
|
|
|
}
|
|
|
|
return nil, errors.Wrapf(ENotFound, "no subject with id '%s' found", r)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetPrivateKey returns the PrivateKey to the ResourceName.
|
2017-05-31 21:03:51 +02:00
|
|
|
func (s *Storage) GetPrivateKey(r pkiadm.ResourceName) (*PrivateKey, error) {
|
2017-05-28 11:33:04 +02:00
|
|
|
if pk, found := s.PrivateKeys[r.ID]; found {
|
|
|
|
return pk, nil
|
|
|
|
}
|
|
|
|
return nil, errors.Wrapf(ENotFound, "no private key with id '%s' found", r)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetPublicKey returns the PublicKey to the ResourceName.
|
2017-05-31 21:03:51 +02:00
|
|
|
func (s *Storage) GetPublicKey(r pkiadm.ResourceName) (*PublicKey, error) {
|
2017-05-28 11:33:04 +02:00
|
|
|
if res, found := s.PublicKeys[r.ID]; found {
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
return nil, errors.Wrapf(ENotFound, "no public key with id '%s' found", r)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetCSR returns the CSR to the CSR.
|
2017-05-31 21:03:51 +02:00
|
|
|
func (s *Storage) GetCSR(r pkiadm.ResourceName) (*CSR, error) {
|
2017-05-28 11:33:04 +02:00
|
|
|
if res, found := s.CSRs[r.ID]; found {
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
return nil, errors.Wrapf(ENotFound, "no CSR with id '%s' found", r)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetCertificate returns the Certificate matching the ResourceName.
|
2017-05-31 21:03:51 +02:00
|
|
|
func (s *Storage) GetCertificate(r pkiadm.ResourceName) (*Certificate, error) {
|
2017-05-28 11:33:04 +02:00
|
|
|
if res, found := s.Certificates[r.ID]; found {
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
return nil, errors.Wrapf(ENotFound, "no certificate with id '%s' found", r)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetLocation returns the Location matching the ResourceName.
|
2017-05-31 21:03:51 +02:00
|
|
|
func (s *Storage) GetLocation(r pkiadm.ResourceName) (*Location, error) {
|
2017-05-28 11:33:04 +02:00
|
|
|
if res, found := s.Locations[r.ID]; found {
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
return nil, errors.Wrapf(ENotFound, "no location with id '%s' found", r)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove takes a resource and removes it from the system.
|
|
|
|
func (s *Storage) Remove(r Resource) error {
|
|
|
|
// TODO implement unable to remove when having dependencies
|
|
|
|
switch r.Name().Type {
|
2017-05-31 21:03:51 +02:00
|
|
|
case pkiadm.RTSerial:
|
2017-05-28 11:33:04 +02:00
|
|
|
delete(s.Serials, r.Name().ID)
|
2017-05-31 21:03:51 +02:00
|
|
|
case pkiadm.RTSubject:
|
2017-05-28 11:33:04 +02:00
|
|
|
delete(s.Subjects, r.Name().ID)
|
2017-05-31 21:03:51 +02:00
|
|
|
case pkiadm.RTPrivateKey:
|
2017-05-28 11:33:04 +02:00
|
|
|
delete(s.PrivateKeys, r.Name().ID)
|
2017-05-31 21:03:51 +02:00
|
|
|
case pkiadm.RTPublicKey:
|
2017-05-28 11:33:04 +02:00
|
|
|
delete(s.PublicKeys, r.Name().ID)
|
2017-05-31 21:03:51 +02:00
|
|
|
case pkiadm.RTCSR:
|
2017-05-28 11:33:04 +02:00
|
|
|
delete(s.CSRs, r.Name().ID)
|
2017-05-31 21:03:51 +02:00
|
|
|
case pkiadm.RTCertificate:
|
2017-05-28 11:33:04 +02:00
|
|
|
delete(s.Certificates, r.Name().ID)
|
2017-05-31 21:03:51 +02:00
|
|
|
case pkiadm.RTLocation:
|
2017-05-28 11:33:04 +02:00
|
|
|
delete(s.Locations, r.Name().ID)
|
|
|
|
default:
|
|
|
|
return EUnknownType
|
|
|
|
}
|
|
|
|
for _, rn := range r.DependsOn() {
|
|
|
|
if deps, found := s.dependencies[rn.String()]; found {
|
|
|
|
delete(deps, r.Name().String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update sends a refresh through all resources depending on the one given.
|
2017-05-31 21:03:51 +02:00
|
|
|
func (s *Storage) Update(rn pkiadm.ResourceName) error {
|
2017-05-28 11:33:04 +02:00
|
|
|
r, err := s.Get(rn)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
updateOrder := []Resource{r}
|
|
|
|
checkList := map[string]bool{rn.String(): true}
|
|
|
|
depsToCheck := []Resource{}
|
|
|
|
for _, nextDep := range s.dependencies[rn.String()] {
|
|
|
|
depsToCheck = append(depsToCheck, nextDep)
|
|
|
|
}
|
|
|
|
|
|
|
|
var dep Resource
|
|
|
|
for {
|
|
|
|
if len(depsToCheck) == 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
dep, depsToCheck = depsToCheck[0], depsToCheck[1:]
|
|
|
|
if _, found := checkList[dep.Name().String()]; found {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
updateOrder = append(updateOrder, dep)
|
|
|
|
checkList[dep.Name().String()] = true
|
|
|
|
for _, nextDep := range s.dependencies[dep.Name().String()] {
|
|
|
|
depsToCheck = append(depsToCheck, nextDep)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, dep := range updateOrder {
|
|
|
|
if err := dep.Refresh(s); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|