Gibheer
039f72c3d5
The basic server and client are working and it is possible to add, list, show, set and remove subjects. Locations are not yet written to the filesystem yet and need to be fixed.
341 lines
8.9 KiB
Go
341 lines
8.9 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
|
|
"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.
|
|
func (s *Storage) Get(r ResourceName) (Resource, error) {
|
|
if r.ID == "" {
|
|
return nil, ENoIDGiven
|
|
}
|
|
switch r.Type {
|
|
case RTSerial:
|
|
return s.GetSerial(r)
|
|
case RTSubject:
|
|
return s.GetSubject(r)
|
|
case RTPrivateKey:
|
|
return s.GetPrivateKey(r)
|
|
case RTPublicKey:
|
|
return s.GetPublicKey(r)
|
|
case RTCSR:
|
|
return s.GetCSR(r)
|
|
case RTCertificate:
|
|
return s.GetCertificate(r)
|
|
case RTLocation:
|
|
return s.GetLocation(r)
|
|
default:
|
|
return nil, EUnknownType
|
|
}
|
|
}
|
|
|
|
// GetSerial returns the Serial matching the ResourceName.
|
|
func (s *Storage) GetSerial(r ResourceName) (*Serial, error) {
|
|
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.
|
|
func (s *Storage) GetSubject(r ResourceName) (*Subject, error) {
|
|
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.
|
|
func (s *Storage) GetPrivateKey(r ResourceName) (*PrivateKey, error) {
|
|
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.
|
|
func (s *Storage) GetPublicKey(r ResourceName) (*PublicKey, error) {
|
|
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.
|
|
func (s *Storage) GetCSR(r ResourceName) (*CSR, error) {
|
|
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.
|
|
func (s *Storage) GetCertificate(r ResourceName) (*Certificate, error) {
|
|
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.
|
|
func (s *Storage) GetLocation(r ResourceName) (*Location, error) {
|
|
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 {
|
|
case RTSerial:
|
|
delete(s.Serials, r.Name().ID)
|
|
case RTSubject:
|
|
delete(s.Subjects, r.Name().ID)
|
|
case RTPrivateKey:
|
|
delete(s.PrivateKeys, r.Name().ID)
|
|
case RTPublicKey:
|
|
delete(s.PublicKeys, r.Name().ID)
|
|
case RTCSR:
|
|
delete(s.CSRs, r.Name().ID)
|
|
case RTCertificate:
|
|
delete(s.Certificates, r.Name().ID)
|
|
case RTLocation:
|
|
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.
|
|
func (s *Storage) Update(rn ResourceName) error {
|
|
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
|
|
}
|