add basic CA support
This is the intial CA support, but not yet connected with the certificate signing.
This commit is contained in:
parent
99e4d60516
commit
70515f6fa6
|
@ -0,0 +1,88 @@
|
|||
package pkiadm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
CALocal CAType = iota
|
||||
CALetsEncrypt
|
||||
CAUnknown
|
||||
)
|
||||
|
||||
type (
|
||||
CAType uint
|
||||
|
||||
CA struct {
|
||||
ID string
|
||||
Type CAType
|
||||
Certificate ResourceName
|
||||
}
|
||||
ResultCA struct {
|
||||
Result Result
|
||||
CAs []CA
|
||||
}
|
||||
CAChange struct {
|
||||
CA CA
|
||||
FieldList []string
|
||||
}
|
||||
)
|
||||
|
||||
// CreateCA sends a RPC request to create a new private key.
|
||||
func (c *Client) CreateCA(pk CA) error {
|
||||
return c.exec("CreateCA", pk)
|
||||
}
|
||||
func (c *Client) SetCA(pk CA, fieldList []string) error {
|
||||
changeset := CAChange{pk, fieldList}
|
||||
return c.exec("SetCA", changeset)
|
||||
}
|
||||
func (c *Client) DeleteCA(id string) error {
|
||||
pk := ResourceName{ID: id, Type: RTCA}
|
||||
return c.exec("DeleteCA", pk)
|
||||
}
|
||||
func (c *Client) ListCA() ([]CA, error) {
|
||||
result := &ResultCA{}
|
||||
if err := c.query("ListCA", Filter{}, result); err != nil {
|
||||
return []CA{}, err
|
||||
}
|
||||
if result.Result.HasError {
|
||||
return []CA{}, result.Result.Error
|
||||
}
|
||||
return result.CAs, nil
|
||||
}
|
||||
func (c *Client) ShowCA(id string) (CA, error) {
|
||||
ca := ResourceName{ID: id, Type: RTCA}
|
||||
result := &ResultCA{}
|
||||
if err := c.query("ShowCA", ca, result); err != nil {
|
||||
return CA{}, err
|
||||
}
|
||||
if result.Result.HasError {
|
||||
return CA{}, result.Result.Error
|
||||
}
|
||||
for _, privateKey := range result.CAs {
|
||||
return privateKey, nil
|
||||
}
|
||||
return CA{}, nil
|
||||
}
|
||||
|
||||
func (ct CAType) String() string {
|
||||
switch ct {
|
||||
case CALocal:
|
||||
return "local"
|
||||
case CALetsEncrypt:
|
||||
return "LetsEncrypt"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func StringToCAType(in string) CAType {
|
||||
switch strings.ToLower(in) {
|
||||
case "local":
|
||||
return CALocal
|
||||
case "letsencrypt":
|
||||
return CALetsEncrypt
|
||||
default:
|
||||
return CAUnknown
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/gibheer/pkiadm"
|
||||
"github.com/pkg/errors"
|
||||
flag "github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func createCA(args []string, client *pkiadm.Client) error {
|
||||
fs := flag.NewFlagSet("pkiadm create-public", flag.ExitOnError)
|
||||
id := fs.String("id", "", "the id to set for the CA")
|
||||
ct := fs.String("type", "local", "the type of CA to create (local, LetsEncrypt)")
|
||||
cert := fs.String("certificate", "", "the id of the certificate to use for CA creation")
|
||||
fs.Parse(args)
|
||||
|
||||
caType := pkiadm.StringToCAType(*ct)
|
||||
if caType == pkiadm.CAUnknown {
|
||||
return errors.New("unknown ca type")
|
||||
}
|
||||
caName := pkiadm.ResourceName{ID: *cert, Type: pkiadm.RTCertificate}
|
||||
if err := client.CreateCA(
|
||||
pkiadm.CA{ID: *id, Type: caType, Certificate: caName},
|
||||
); err != nil {
|
||||
return errors.Wrap(err, "Could not create CA")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func setCA(args []string, client *pkiadm.Client) error {
|
||||
fs := flag.NewFlagSet("pkiadm set-public", flag.ExitOnError)
|
||||
id := fs.String("id", "", "the id of the CA to change")
|
||||
pk := fs.String("private-key", "", "the id of the new private key to use for CA generation")
|
||||
fs.Parse(args)
|
||||
|
||||
if !fs.Lookup("private-key").Changed {
|
||||
return nil
|
||||
}
|
||||
caName := pkiadm.ResourceName{ID: *pk, Type: pkiadm.RTPrivateKey}
|
||||
if err := client.SetCA(
|
||||
pkiadm.CA{ID: *id, Certificate: caName},
|
||||
[]string{"private-key"},
|
||||
); err != nil {
|
||||
return errors.Wrap(err, "Could not change CA")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func deleteCA(args []string, client *pkiadm.Client) error {
|
||||
fs := flag.NewFlagSet("pkiadm delete-public", flag.ExitOnError)
|
||||
id := fs.String("id", "", "the id of the CA to delete")
|
||||
fs.Parse(args)
|
||||
|
||||
if err := client.DeleteCA(*id); err != nil {
|
||||
return errors.Wrap(err, "Could not delete CA")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func listCA(args []string, client *pkiadm.Client) error {
|
||||
fs := flag.NewFlagSet("list-private", flag.ExitOnError)
|
||||
fs.Parse(args)
|
||||
|
||||
cas, err := client.ListCA()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(cas) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := tabwriter.NewWriter(os.Stdout, 2, 2, 1, ' ', tabwriter.AlignRight)
|
||||
fmt.Fprintf(out, "%s\t%s\t%s\t\n", "id", "type", "certificate")
|
||||
for _, ca := range cas {
|
||||
fmt.Fprintf(out, "%s\t%s\t%s\t\n", ca.ID, ca.Type.String(), ca.Certificate.ID)
|
||||
}
|
||||
out.Flush()
|
||||
|
||||
return nil
|
||||
}
|
||||
func showCA(args []string, client *pkiadm.Client) error {
|
||||
fs := flag.NewFlagSet("show-private", flag.ExitOnError)
|
||||
var id = fs.String("id", "", "set the id of the private key to show")
|
||||
fs.Parse(args)
|
||||
|
||||
ca, err := client.ShowCA(*id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out := tabwriter.NewWriter(os.Stdout, 2, 2, 1, ' ', tabwriter.AlignRight)
|
||||
fmt.Fprintf(out, "ID:\t%s\t\n", ca.ID)
|
||||
fmt.Fprintf(out, "type:\t%s\t\n", ca.Type.String())
|
||||
fmt.Fprintf(out, "certificate:\t%s\t\n", ca.Certificate.ID)
|
||||
out.Flush()
|
||||
return nil
|
||||
}
|
|
@ -43,6 +43,16 @@ func main() {
|
|||
err = setSerial(args, client)
|
||||
case `show-serial`:
|
||||
err = showSerial(args, client)
|
||||
case `create-ca`:
|
||||
err = createCA(args, client)
|
||||
case `delete-ca`:
|
||||
err = deleteCA(args, client)
|
||||
case `list-ca`:
|
||||
err = listCA(args, client)
|
||||
case `set-ca`:
|
||||
err = setCA(args, client)
|
||||
case `show-ca`:
|
||||
err = showCA(args, client)
|
||||
case `create-subj`:
|
||||
err = createSubject(args, client)
|
||||
case `delete-subj`:
|
||||
|
@ -118,6 +128,7 @@ func printCommands() {
|
|||
fmt.Println(`Usage: pkiadm <subcommand> [options]
|
||||
where subcommand is one of:`)
|
||||
out := tabwriter.NewWriter(os.Stdout, 0, 4, 1, ' ', 0)
|
||||
fmt.Fprintf(out, " %s\t%s\n", "create-ca", "create a new CA")
|
||||
fmt.Fprintf(out, " %s\t%s\n", "create-cert", "create a new certificate")
|
||||
fmt.Fprintf(out, " %s\t%s\n", "create-csr", "create a new certificate sign request")
|
||||
fmt.Fprintf(out, " %s\t%s\n", "create-location", "create a new file export")
|
||||
|
@ -126,6 +137,7 @@ where subcommand is one of:`)
|
|||
fmt.Fprintf(out, " %s\t%s\n", "create-serial", "")
|
||||
fmt.Fprintf(out, " %s\t%s\n", "create-subj", "")
|
||||
|
||||
fmt.Fprintf(out, " %s\t%s\n", "delete-ca", "delete a CA")
|
||||
fmt.Fprintf(out, " %s\t%s\n", "delete-cert", "")
|
||||
fmt.Fprintf(out, " %s\t%s\n", "delete-csr", "")
|
||||
fmt.Fprintf(out, " %s\t%s\n", "delete-location", "")
|
||||
|
@ -135,6 +147,7 @@ where subcommand is one of:`)
|
|||
fmt.Fprintf(out, " %s\t%s\n", "delete-subj", "")
|
||||
|
||||
fmt.Fprintf(out, " %s\t%s\n", "list", "")
|
||||
fmt.Fprintf(out, " %s\t%s\n", "list-ca", "list all available CAs")
|
||||
fmt.Fprintf(out, " %s\t%s\n", "list-cert", "list all available certificates")
|
||||
fmt.Fprintf(out, " %s\t%s\n", "list-csr", "list all available certificate sign requests")
|
||||
fmt.Fprintf(out, " %s\t%s\n", "list-location", "list all file exports")
|
||||
|
@ -143,6 +156,7 @@ where subcommand is one of:`)
|
|||
fmt.Fprintf(out, " %s\t%s\n", "list-serial", "")
|
||||
fmt.Fprintf(out, " %s\t%s\n", "list-subj", "")
|
||||
|
||||
fmt.Fprintf(out, " %s\t%s\n", "set-ca", "change attributes of a CA")
|
||||
fmt.Fprintf(out, " %s\t%s\n", "set-cert", "change attributes of a certificate")
|
||||
fmt.Fprintf(out, " %s\t%s\n", "set-csr", "change attributes of a certificate sign request")
|
||||
fmt.Fprintf(out, " %s\t%s\n", "set-location", "change attributes of a location")
|
||||
|
@ -151,6 +165,7 @@ where subcommand is one of:`)
|
|||
fmt.Fprintf(out, " %s\t%s\n", "set-serial", "")
|
||||
fmt.Fprintf(out, " %s\t%s\n", "set-subj", "")
|
||||
|
||||
fmt.Fprintf(out, " %s\t%s\n", "show-ca", "")
|
||||
fmt.Fprintf(out, " %s\t%s\n", "show-cert", "")
|
||||
fmt.Fprintf(out, " %s\t%s\n", "show-csr", "")
|
||||
fmt.Fprintf(out, " %s\t%s\n", "show-location", "")
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/gibheer/pkiadm"
|
||||
)
|
||||
|
||||
type (
|
||||
// CA is an instance that can sign certificates. When a certificate needs an
|
||||
// update, the given CSR is signed by the CA.
|
||||
// A CA can be responsible for multiple certificates to sign.
|
||||
CA struct {
|
||||
ID string
|
||||
Type pkiadm.CAType
|
||||
Certificate pkiadm.ResourceName
|
||||
}
|
||||
)
|
||||
|
||||
func NewCA(id string, caType pkiadm.CAType, cert pkiadm.ResourceName) (*CA, error) {
|
||||
ca := &CA{
|
||||
ID: id,
|
||||
Type: caType,
|
||||
Certificate: cert,
|
||||
}
|
||||
return ca, nil
|
||||
}
|
||||
|
||||
// Return the unique ResourceName
|
||||
func (ca *CA) Name() pkiadm.ResourceName {
|
||||
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.
|
||||
func (ca *CA) Refresh(*Storage) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return the PEM output of the contained resource.
|
||||
func (ca *CA) Pem() ([]byte, error) { return []byte{}, nil }
|
||||
func (ca *CA) Checksum() []byte { return []byte{} }
|
||||
|
||||
// DependsOn must return the resource names it is depending on.
|
||||
func (ca *CA) DependsOn() []pkiadm.ResourceName {
|
||||
return []pkiadm.ResourceName{
|
||||
ca.Certificate,
|
||||
}
|
||||
}
|
||||
|
||||
func (ca *CA) Sign(csr *CSR) (*Certificate, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *Server) CreateCA(inCA pkiadm.CA, res *pkiadm.Result) error {
|
||||
s.lock()
|
||||
defer s.unlock()
|
||||
|
||||
ca, err := NewCA(inCA.ID, inCA.Type, inCA.Certificate)
|
||||
if err != nil {
|
||||
res.SetError(err, "could not create CA '%s'", inCA.ID)
|
||||
return nil
|
||||
}
|
||||
if err := s.storage.AddCA(ca); err != nil {
|
||||
res.SetError(err, "could not add CA '%s'", inCA.ID)
|
||||
return nil
|
||||
}
|
||||
return s.store(res)
|
||||
}
|
||||
|
||||
func (s *Server) SetCA(change pkiadm.CAChange, res *pkiadm.Result) error {
|
||||
s.lock()
|
||||
defer s.unlock()
|
||||
|
||||
ca, err := s.storage.GetCA(pkiadm.ResourceName{ID: change.CA.ID, Type: pkiadm.RTCA})
|
||||
if err != nil {
|
||||
res.SetError(err, "could not find CA '%s'", change.CA.ID)
|
||||
return nil
|
||||
}
|
||||
for _, field := range change.FieldList {
|
||||
switch field {
|
||||
case "type":
|
||||
ca.Type = change.CA.Type
|
||||
case "certificate":
|
||||
ca.Certificate = change.CA.Certificate
|
||||
}
|
||||
}
|
||||
return s.store(res)
|
||||
}
|
||||
|
||||
func (s *Server) DeleteCA(inCA pkiadm.CA, res *pkiadm.Result) error {
|
||||
s.lock()
|
||||
defer s.unlock()
|
||||
|
||||
ca, err := s.storage.GetCA(pkiadm.ResourceName{inCA.ID, pkiadm.RTCA})
|
||||
if err != nil {
|
||||
res.SetError(err, "Could not find ca '%s'", ca.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := s.storage.Remove(ca); err != nil {
|
||||
res.SetError(err, "Could not remove ca '%s'", ca.ID)
|
||||
return nil
|
||||
}
|
||||
return s.store(res)
|
||||
}
|
||||
|
||||
func (s *Server) ShowCA(inCA pkiadm.CA, res *pkiadm.ResultCA) error {
|
||||
s.lock()
|
||||
defer s.unlock()
|
||||
|
||||
ca, err := s.storage.GetCA(pkiadm.ResourceName{ID: inCA.ID, Type: pkiadm.RTCA})
|
||||
if err != nil {
|
||||
res.Result.SetError(err, "Could not find private key '%s'", inCA.ID)
|
||||
return nil
|
||||
}
|
||||
res.CAs = []pkiadm.CA{pkiadm.CA{
|
||||
ID: ca.ID,
|
||||
Type: ca.Type,
|
||||
Certificate: ca.Certificate,
|
||||
}}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) ListCA(filter pkiadm.Filter, res *pkiadm.ResultCA) error {
|
||||
s.lock()
|
||||
defer s.unlock()
|
||||
|
||||
for _, ca := range s.storage.CAs {
|
||||
res.CAs = append(res.CAs, pkiadm.CA{
|
||||
ID: ca.ID,
|
||||
Type: ca.Type,
|
||||
Certificate: ca.Certificate,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -24,6 +24,7 @@ type (
|
|||
CSRs map[string]*CSR
|
||||
Serials map[string]*Serial
|
||||
Subjects map[string]*Subject
|
||||
CAs map[string]*CA
|
||||
// dependencies maps from a resource name to all resources which depend
|
||||
// on it.
|
||||
dependencies map[string]map[string]Resource
|
||||
|
@ -42,6 +43,7 @@ func NewStorage(path string) (*Storage, error) {
|
|||
CSRs: map[string]*CSR{},
|
||||
Serials: map[string]*Serial{},
|
||||
Subjects: map[string]*Subject{},
|
||||
CAs: map[string]*CA{},
|
||||
dependencies: map[string]map[string]Resource{},
|
||||
}
|
||||
if err := s.load(); err != nil {
|
||||
|
@ -93,6 +95,9 @@ func (s *Storage) refreshDependencies() error {
|
|||
for _, l := range s.Locations {
|
||||
_ = s.addDependency(l)
|
||||
}
|
||||
for _, ca := range s.CAs {
|
||||
_ = s.addDependency(ca)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -193,6 +198,14 @@ func (s *Storage) AddLocation(l *Location) error {
|
|||
return s.addDependency(l)
|
||||
}
|
||||
|
||||
func (s *Storage) AddCA(ca *CA) error {
|
||||
if err := ca.Refresh(s); err != nil {
|
||||
return err
|
||||
}
|
||||
s.CAs[ca.Name().ID] = ca
|
||||
return s.addDependency(ca)
|
||||
}
|
||||
|
||||
// Get figures out the resource to the ResourceName if available.
|
||||
func (s *Storage) Get(r pkiadm.ResourceName) (Resource, error) {
|
||||
if r.ID == "" {
|
||||
|
@ -213,6 +226,8 @@ func (s *Storage) Get(r pkiadm.ResourceName) (Resource, error) {
|
|||
return s.GetCertificate(r)
|
||||
case pkiadm.RTLocation:
|
||||
return s.GetLocation(r)
|
||||
case pkiadm.RTCA:
|
||||
return s.GetCA(r)
|
||||
default:
|
||||
return nil, EUnknownType
|
||||
}
|
||||
|
@ -274,6 +289,14 @@ func (s *Storage) GetLocation(r pkiadm.ResourceName) (*Location, error) {
|
|||
return nil, errors.Wrapf(ENotFound, "no location with id '%s' found", r)
|
||||
}
|
||||
|
||||
// GetCA returns the CA matching the resource name.
|
||||
func (s *Storage) GetCA(r pkiadm.ResourceName) (*CA, error) {
|
||||
if res, found := s.CAs[r.ID]; found {
|
||||
return res, nil
|
||||
}
|
||||
return nil, errors.Wrapf(ENotFound, "no CA 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
|
||||
|
@ -292,6 +315,8 @@ func (s *Storage) Remove(r Resource) error {
|
|||
delete(s.Certificates, r.Name().ID)
|
||||
case pkiadm.RTLocation:
|
||||
delete(s.Locations, r.Name().ID)
|
||||
case pkiadm.RTCA:
|
||||
delete(s.CAs, r.Name().ID)
|
||||
default:
|
||||
return EUnknownType
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ const (
|
|||
RTSerial
|
||||
RTSubject
|
||||
RTUnknown
|
||||
RTCA
|
||||
)
|
||||
|
||||
type ResourceName struct {
|
||||
|
|
Loading…
Reference in New Issue