Skip to content

Commit

Permalink
Add a simple/stupid 'command' service manager.
Browse files Browse the repository at this point in the history
If a cert specifies 'service_manager' setting as 'command', then action
is passed to /bin/bash -c (or /bin/sh if bash can't be found).  This allows
custom actions to be taken- things beyond just init scripts.

Note that there are multiple CERTMGR_* variables exposed to the shell code
invoked; this is to allow for the target to decide what to do when these
things change.
  • Loading branch information
Brian Harring authored and kisom committed Apr 25, 2018
1 parent f6366fb commit f5ae024
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 8 deletions.
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,9 @@ This contains all of the currently available parameters:
* `dir`: this specifies the directory containing the certificate specs
* `svcmgr`: this specifies the service manager to use for restarting
or reloading services. This can be `systemd` (using `systemctl`),
`sysv` (using `service`), `circus` (using `circusctl`), `openrc` (using `rc-service`)
or `dummy` (no restart/reload behavior).
`sysv` (using `service`), `circus` (using `circusctl`), `openrc` (using `rc-service`),
`dummy` (no restart/reload behavior), or `command` (see the command svcmgr section
for details of how to use this).
* `before`: this is the interval before a certificate expires to start
attempting to renew it.
* `interval`: this controls how often to check certificate expirations
Expand Down Expand Up @@ -159,7 +160,9 @@ A certificate spec has the following fields:
or "nop".
* `svcmgr`: this is optional, and defaults to whatever the global
config defines. This allows fine grained control for specifying the
svcmgr per cert.
svcmgr per cert. If you're using this in a raw certificate definition,
you likely want the 'command' svcmgr- see that section for details of
how to use it.
* `request`: a CFSSL certificate request (see below).
* `private_key` and `certificate`: file specifications (see below) for
the private key and certificate.
Expand Down Expand Up @@ -200,6 +203,25 @@ The CA specification contains the following fields:
* `file`: if this is included, the CA certificate will be saved here. It
follows the same file specification format above.

## `command svcmgr` and how to use it

If the svcmgr is set to `command`, then `action` is interpretted as a
shell snippet to invoke via `bash -c`. Bash is preferred since
it allows parse checks to be ran- if bash isn't available, parse checks
are skipped and `sh -c` is used. If `sh` can't be found, then this svcmgr
is disabled.

Environment variables are set as follows:

* CERTMGR_CHANGE_TYPE: either 'CA' or 'key'. This indicates if the CA
changes, or if it's just a cert renewal.
* CERTMGR_CA_PATH: if CA was configured for the spec, this is the path
to the CA ondisk that was changed.
* CERTMGR_CERT_PATH: This is the path to the cert that was written.
* CERTMGR_KEY_PATH: This is the path to the key that was written.
* CERTMGR_SPEC_PATH: This is the path to the cert spec definition that
was just refreshed.

## Subcommands

In addition to the certificate manager, there are a few utility
Expand Down
14 changes: 12 additions & 2 deletions mgr/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ type CertServiceManager struct {
serviceManager svcmgr.Manager
}

func (csm *CertServiceManager) TakeAction(change_type string) error {
ca_path := ""
if csm.CA.File != nil {
ca_path = csm.CA.File.Path
}
cert_path := csm.Cert.Path
key_path := csm.Key.Path
return csm.serviceManager.TakeAction(change_type, csm.Path, ca_path, cert_path, key_path)
}

// The Manager structure contains the certificates to be managed. A
// manager needs to be constructed with one of the New functions, and
// should not be constructed by hand.
Expand Down Expand Up @@ -214,7 +224,7 @@ func (m *Manager) CheckCA(spec *CertServiceManager) error {
if changed, err := spec.CA.Refresh(); err != nil {
return err
} else if changed {
err := spec.serviceManager.TakeAction()
err := spec.TakeAction("CA")

if err != nil {
log.Errorf("manager: %s", err)
Expand Down Expand Up @@ -450,7 +460,7 @@ func (m *Manager) refreshKeys(cert *CertServiceManager) {
}

metrics.QueueCount.Dec()
err = cert.serviceManager.TakeAction()
err = cert.TakeAction("key")

// Even though there was an error managing the service
// associated with the certificate, the certificate has been
Expand Down
62 changes: 62 additions & 0 deletions svcmgr/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package svcmgr

import (
"fmt"
"os/exec"

"github.com/cloudflare/cfssl/log"
)

var (
shellBinary string
canCheckSyntax bool
)

type commandManager struct {
command string
}

func (cm commandManager) TakeAction(change_type string, spec_path string, ca_path string, cert_path string, key_path string) error {
env := []string{
"CERTMGR_CA_PATH=" + ca_path,
"CERTMGR_CERT_PATH=" + cert_path,
"CERTMGR_KEY_PATH=" + key_path,
"CERTMGR_SPEC_PATH=" + spec_path,
"CERTMGR_CHANGE_TYPE=" + change_type,
}
return runEnv(env, shellBinary, "-c", cm.command)
}

func newCommandManager(action string, service string) (Manager, error) {
if service != "" {
log.Warningf("svcmgr 'command': service '%s' for action '%s' doesn't do anything, ignoring", service, action)
}
if canCheckSyntax {
err := run(shellBinary, "-n", "-c", action)
if err != nil {
return nil, fmt.Errorf("svcmgr 'command': action '%s' failed bash -n -c parse checks: %s", action, err)
}
} else {
log.Warningf("svcmgr 'command': skipping parse check for '%s' since bash couldn't be found", action)
}
return &commandManager{
command: action,
}, nil
}

func init() {
// prefer bash if we can find it since it allows us to validate
var err error
shellBinary, err = exec.LookPath("bash")
canCheckSyntax = true
if err != nil {
log.Infof("svcmgr 'command' couldn't find a bash binary; action statements will not be able to be validated for syntax: err %s", err)
shellBinary, err = exec.LookPath("sh")
if err != nil {
log.Error("svcmgr 'command' is unavailable due to both bash and sh not being found in PATH")
return
}
canCheckSyntax = false
}
SupportedBackends["command"] = newCommandManager
}
12 changes: 9 additions & 3 deletions svcmgr/svcmgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package svcmgr

import (
"fmt"
"os"
"os/exec"

"github.com/cloudflare/cfssl/log"
Expand All @@ -26,11 +27,16 @@ var defaultValidActions = map[string]bool{
// The Manager interface provides a common API for interacting with
// service managers.
type Manager interface {
TakeAction() error
TakeAction(change_type string, spec_path string, ca_path string, cert_path string, key_path string) error
}

func run(prog string, args ...string) error {
return runEnv([]string{}, prog, args...)
}

func runEnv(env []string, prog string, args ...string) error {
cmd := exec.Command(prog, args...)
cmd.Env = append(os.Environ(), env...)
log.Debugf("running '%s %v'", prog, args)
return cmd.Run()
}
Expand All @@ -57,7 +63,7 @@ type simpleManager struct {
service string
}

func (sm simpleManager) TakeAction() error {
func (sm simpleManager) TakeAction(string, string, string, string, string) error {
log.Infof("%ving service %v", sm.action, sm.service)
if sm.action_is_last {
return run(sm.binary, sm.service, sm.action)
Expand Down Expand Up @@ -87,7 +93,7 @@ func registerSimpleManager(binary string, action_is_last bool) managerCreator {

type dummyManager struct{}

func (dummyManager) TakeAction() error {
func (dummyManager) TakeAction(string, string, string, string, string) error {
return nil
}
func newDummyManager(action string, service string) (Manager, error) {
Expand Down

0 comments on commit f5ae024

Please sign in to comment.