You've already forked step-ca-cli
mirror of
https://github.com/smallstep/cli.git
synced 2026-10-29 00:17:23 +03:00
@@ -3,10 +3,12 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
|
||||
@@ -36,6 +38,7 @@ var BuildTime = "N/A"
|
||||
|
||||
func init() {
|
||||
config.Set(Version, BuildTime)
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
package ca
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/api"
|
||||
@@ -112,79 +107,6 @@ $ step ca sign --token $TOKEN --not-after=1h internal.csr internal.crt
|
||||
}
|
||||
}
|
||||
|
||||
func renewCertificateCommand() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "renew",
|
||||
Action: command.ActionFunc(renewCertificateAction),
|
||||
Usage: "renew a valid certificate",
|
||||
UsageText: `**step ca renew** <crt-file> <key-file>
|
||||
[**--ca-url**=<uri>] [**--root**=<file>]
|
||||
[**--out**=<file>] [**--expires-in**=<duration>] [**--force**]`,
|
||||
Description: `
|
||||
**step ca renew** command renews the given certificates on the certificate
|
||||
authority and writes the new certificate to disk either overwriting <crt-file>
|
||||
or using a new file if the **--out**=<file> flag is used.
|
||||
|
||||
## POSITIONAL ARGUMENTS
|
||||
|
||||
<crt-file>
|
||||
: The certificate in PEM format that we want to renew.
|
||||
|
||||
<key-file>
|
||||
: They key file of the certificate.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
Renew a certificate with the configured CA:
|
||||
'''
|
||||
$ step ca renew internal.crt internal.key
|
||||
Would you like to overwrite internal.crt [Y/n]: y
|
||||
'''
|
||||
|
||||
Renew a certificate without overwriting the previous certificate:
|
||||
'''
|
||||
$ step ca renew --out renewed.crt internal.crt internal.key
|
||||
'''
|
||||
|
||||
Renew a certificate forcing the overwrite of the previous certificate:
|
||||
'''
|
||||
$ step ca renew --force internal.crt internal.key
|
||||
'''
|
||||
|
||||
Renew a certificate providing the <--ca-url> and <--root> flags:
|
||||
'''
|
||||
$ step ca renew --ca-url https://ca.smallstep.com:9000 \
|
||||
--root /path/to/root_ca.crt internal.crt internal.key
|
||||
Would you like to overwrite internal.crt [Y/n]: y
|
||||
'''
|
||||
|
||||
Renew skipped because it was too early:
|
||||
'''
|
||||
$ step ca renew --expires-in 8h internal.crt internal.key
|
||||
certificate not renewed: expires in 10h52m5s
|
||||
'''`,
|
||||
Flags: []cli.Flag{
|
||||
caURLFlag,
|
||||
rootFlag,
|
||||
cli.StringFlag{
|
||||
Name: "out,output-file",
|
||||
Usage: "The new certificate <file> path. Defaults to overwriting the <crt-file> positional argument",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "expires-in",
|
||||
Usage: `The <duration> check that will be performed before renewing the certificate. The
|
||||
certificate renew will be skipped if the time to expiration is greater than the
|
||||
passed one. A random jitter (duration/20) will be added to avoid multiple
|
||||
services hitting the renew endpoint at the same time. The <duration> is a
|
||||
sequence of decimal numbers, each with optional fraction and a unit suffix, such
|
||||
as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms",
|
||||
"s", "m", "h".`,
|
||||
},
|
||||
flags.Force,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newCertificateAction(ctx *cli.Context) error {
|
||||
if err := errs.NumberOfArguments(ctx, 3); err != nil {
|
||||
return err
|
||||
@@ -378,104 +300,3 @@ func signCertificateRequest(ctx *cli.Context, token string, csr api.CertificateR
|
||||
data := append(pem.EncodeToMemory(serverBlock), pem.EncodeToMemory(caBlock)...)
|
||||
return utils.WriteFile(crtFile, data, 0600)
|
||||
}
|
||||
|
||||
func renewCertificateAction(ctx *cli.Context) error {
|
||||
if err := errs.NumberOfArguments(ctx, 2); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := ctx.Args()
|
||||
crtFile := args.Get(0)
|
||||
keyFile := args.Get(1)
|
||||
|
||||
outFile := ctx.String("out")
|
||||
if len(outFile) == 0 {
|
||||
outFile = crtFile
|
||||
}
|
||||
|
||||
root := ctx.String("root")
|
||||
if len(root) == 0 {
|
||||
root = pki.GetRootCAPath()
|
||||
}
|
||||
|
||||
caURL := ctx.String("ca-url")
|
||||
if len(caURL) == 0 {
|
||||
return errs.RequiredFlag(ctx, "ca-url")
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(crtFile, keyFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error loading certificates")
|
||||
}
|
||||
if len(cert.Certificate) == 0 {
|
||||
return errors.New("error loading certificate: certificate chain is empty")
|
||||
}
|
||||
|
||||
// Do not renew if (now - cert.notBefore) > (expiresIn + jitter)
|
||||
if s := ctx.String("expires-in"); len(s) > 0 {
|
||||
duration, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return errs.InvalidFlagValue(ctx, "expires-in", s, "")
|
||||
}
|
||||
|
||||
leaf, err := x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error parsing certificate")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
jitter := rand.Int63n(int64(duration / 20))
|
||||
if d := leaf.NotAfter.Sub(now); d > duration+time.Duration(jitter) {
|
||||
fmt.Printf("certificate not renewed: expires in %s\n", d.Round(time.Second))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
rootInt, err := pemutil.Read(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rootCert, ok := rootInt.(*x509.Certificate)
|
||||
if !ok {
|
||||
return errors.Errorf("error parsing %s: file is not a root certificate", root)
|
||||
}
|
||||
|
||||
pool := x509.NewCertPool()
|
||||
pool.AddCert(rootCert)
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
RootCAs: pool,
|
||||
PreferServerCipherSuites: true,
|
||||
},
|
||||
}
|
||||
|
||||
client, err := ca.NewClient(caURL, ca.WithTransport(tr))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := client.Renew(tr)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error renewing certificate")
|
||||
}
|
||||
|
||||
serverBlock, err := pemutil.Serialize(resp.ServerPEM.Certificate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
caBlock, err := pemutil.Serialize(resp.CaPEM.Certificate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := append(pem.EncodeToMemory(serverBlock), pem.EncodeToMemory(caBlock)...)
|
||||
|
||||
if err := utils.WriteFile(outFile, data, 0600); err != nil {
|
||||
return errs.FileError(err, outFile)
|
||||
}
|
||||
|
||||
ui.Printf("Your certificate has been saved in %s.\n", outFile)
|
||||
return nil
|
||||
}
|
||||
|
||||
460
command/ca/renew.go
Normal file
460
command/ca/renew.go
Normal file
@@ -0,0 +1,460 @@
|
||||
package ca
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/api"
|
||||
"github.com/smallstep/certificates/ca"
|
||||
"github.com/smallstep/cli/command"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/crypto/pki"
|
||||
"github.com/smallstep/cli/crypto/x509util"
|
||||
"github.com/smallstep/cli/errs"
|
||||
"github.com/smallstep/cli/flags"
|
||||
"github.com/smallstep/cli/ui"
|
||||
"github.com/smallstep/cli/utils"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func renewCertificateCommand() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "renew",
|
||||
Action: command.ActionFunc(renewCertificateAction),
|
||||
Usage: "renew a valid certificate",
|
||||
UsageText: `**step ca renew** <crt-file> <key-file>
|
||||
[**--ca-url**=<uri>] [**--root**=<file>]
|
||||
[**--out**=<file>] [**--expires-in**=<duration>] [**--force**]`,
|
||||
Description: `
|
||||
**step ca renew** command renews the given certificate (with a request to the
|
||||
certificate authority) and writes the new certificate to disk - either overwriting
|
||||
<crt-file> or using a new file when the **--out**=<file> flag is used.
|
||||
|
||||
With the **--daemon** flag the command will periodically update the given
|
||||
certificate. By default, it will renew the certificate before 2/3 of the validity
|
||||
period of the certificate has elapsed. A random jitter is used to avoid multiple
|
||||
instances running at the same time. The amount of time between renewal and
|
||||
certificate expiration can be configured using the **--expires-in** flag, or a
|
||||
fixed period can be set with the **--renew-period** flag.
|
||||
|
||||
The **--daemon** flag can be combined with **--pid**, **--signal**, or **--exec**
|
||||
to provide certificate reloads on your services.
|
||||
|
||||
## POSITIONAL ARGUMENTS
|
||||
|
||||
<crt-file>
|
||||
: The certificate in PEM format that we want to renew.
|
||||
|
||||
<key-file>
|
||||
: They key file of the certificate.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
Renew a certificate with the configured CA:
|
||||
'''
|
||||
$ step ca renew internal.crt internal.key
|
||||
Would you like to overwrite internal.crt [Y/n]: y
|
||||
'''
|
||||
|
||||
Renew a certificate without overwriting the previous certificate:
|
||||
'''
|
||||
$ step ca renew --out renewed.crt internal.crt internal.key
|
||||
'''
|
||||
|
||||
Renew a certificate forcing the overwrite of the previous certificate:
|
||||
'''
|
||||
$ step ca renew --force internal.crt internal.key
|
||||
'''
|
||||
|
||||
Renew a certificate providing the <--ca-url> and <--root> flags:
|
||||
'''
|
||||
$ step ca renew --ca-url https://ca.smallstep.com:9000 \
|
||||
--root /path/to/root_ca.crt internal.crt internal.key
|
||||
Would you like to overwrite internal.crt [Y/n]: y
|
||||
'''
|
||||
|
||||
Renew skipped because it was too early:
|
||||
'''
|
||||
$ step ca renew --expires-in 8h internal.crt internal.key
|
||||
certificate not renewed: expires in 10h52m5s
|
||||
'''
|
||||
|
||||
Renew the certificate before 2/3 of the validity has passed:
|
||||
'''
|
||||
$ step ca renew --daemon internal.crt internal.key
|
||||
'''
|
||||
|
||||
Renew the certificate before 8 hours and 30m of the expiration time:
|
||||
'''
|
||||
$ step ca renew --daemon --expires-in 8h30m internal.crt internal.key
|
||||
'''
|
||||
|
||||
Renew the certificate every 16h:
|
||||
'''
|
||||
$ step ca renew --daemon --renew-period 16h internal.crt internal.key
|
||||
'''
|
||||
|
||||
Renew the certificate and reload nginx:
|
||||
'''
|
||||
$ step ca renew --daemon --exec "nginx -s reload" internal.crt internal.key
|
||||
'''
|
||||
|
||||
Renew the certificate and convert it to DER:
|
||||
'''
|
||||
$ step ca renew --daemon --renew-period 16h \
|
||||
--exec "step certificate format --force --out internal.der internal.crt" \
|
||||
internal.crt internal.key
|
||||
'''`,
|
||||
Flags: []cli.Flag{
|
||||
caURLFlag,
|
||||
rootFlag,
|
||||
cli.StringFlag{
|
||||
Name: "out,output-file",
|
||||
Usage: "The new certificate <file> path. Defaults to overwriting the <crt-file> positional argument",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "expires-in",
|
||||
Usage: `The amount of time remaining before certificate expiration,
|
||||
at which point a renewal should be attempted. The certificate renewal will not
|
||||
be performed if the time to expiration is greater than the **--expires-in** value.
|
||||
A random jitter (duration/20) will be added to avoid multiple services hitting the
|
||||
renew endpoint at the same time. The <duration> is a sequence of decimal numbers,
|
||||
each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m".
|
||||
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`,
|
||||
},
|
||||
flags.Force,
|
||||
cli.IntFlag{
|
||||
Name: "pid",
|
||||
Usage: `The process id to signal after the certificate has been renewed. By default the
|
||||
the SIGHUP (1) signal will be used, but this can be configured with the **--signal**
|
||||
flag.`,
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "signal",
|
||||
Usage: `The signal <number> to send to the selected PID, so it can reload the
|
||||
configuration and load the new certificate. Default value is SIGHUP (1)`,
|
||||
Value: int(syscall.SIGHUP),
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "exec",
|
||||
Usage: "The <command> to run after the certificate has been renewed.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "daemon",
|
||||
Usage: `Run the renew command as a daemon, renewing and overwriting the certificate
|
||||
periodically. By default the daemon will renew a certificate before 2/3 of the
|
||||
time to expiration has elapsed. The period can be configured using the
|
||||
**--renew-period** or **--expires-in** flags.`,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "renew-period",
|
||||
Usage: `The period with which to schedule renewals of the certificate in daemon mode.
|
||||
Requires the **--daemon** flag. The <duration> is a sequence of decimal numbers,
|
||||
each with optional fraction and a unit suffix, such as "300ms", "1.5h", or "2h45m".
|
||||
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func renewCertificateAction(ctx *cli.Context) error {
|
||||
err := errs.NumberOfArguments(ctx, 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := ctx.Args()
|
||||
crtFile := args.Get(0)
|
||||
keyFile := args.Get(1)
|
||||
isDaemon := ctx.Bool("daemon")
|
||||
execCmd := ctx.String("exec")
|
||||
|
||||
outFile := ctx.String("out")
|
||||
if len(outFile) == 0 {
|
||||
outFile = crtFile
|
||||
}
|
||||
|
||||
rootFile := ctx.String("root")
|
||||
if len(rootFile) == 0 {
|
||||
rootFile = pki.GetRootCAPath()
|
||||
}
|
||||
|
||||
caURL := ctx.String("ca-url")
|
||||
if len(caURL) == 0 {
|
||||
return errs.RequiredFlag(ctx, "ca-url")
|
||||
}
|
||||
|
||||
var expiresIn, renewPeriod time.Duration
|
||||
if s := ctx.String("expires-in"); len(s) > 0 {
|
||||
if expiresIn, err = time.ParseDuration(s); err != nil {
|
||||
return errs.InvalidFlagValue(ctx, "expires-in", s, "")
|
||||
}
|
||||
}
|
||||
if s := ctx.String("renew-period"); len(s) > 0 {
|
||||
if renewPeriod, err = time.ParseDuration(s); err != nil {
|
||||
return errs.InvalidFlagValue(ctx, "renew-period", s, "")
|
||||
}
|
||||
}
|
||||
if expiresIn > 0 && renewPeriod > 0 {
|
||||
return errs.IncompatibleFlagWithFlag(ctx, "expires-in", "renew-period")
|
||||
}
|
||||
if renewPeriod > 0 && !isDaemon {
|
||||
return errs.RequiredWithFlag(ctx, "renew-period", "daemon")
|
||||
}
|
||||
|
||||
pid := ctx.Int("pid")
|
||||
if ctx.IsSet("pid") && pid <= 0 {
|
||||
return errs.InvalidFlagValue(ctx, "pid", strconv.Itoa(pid), "")
|
||||
}
|
||||
|
||||
signum := ctx.Int("signal")
|
||||
if ctx.IsSet("signal") && signum <= 0 {
|
||||
return errs.InvalidFlagValue(ctx, "signal", strconv.Itoa(signum), "")
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(crtFile, keyFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error loading certificates")
|
||||
}
|
||||
if len(cert.Certificate) == 0 {
|
||||
return errors.New("error loading certificate: certificate chain is empty")
|
||||
}
|
||||
|
||||
leaf, err := x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error parsing certificate")
|
||||
}
|
||||
if leaf.NotAfter.Before(time.Now()) {
|
||||
return errors.New("cannot renew an expired certificate")
|
||||
}
|
||||
cvp := leaf.NotAfter.Sub(leaf.NotBefore)
|
||||
if renewPeriod > 0 && renewPeriod >= cvp {
|
||||
return errors.Errorf("flag '--renew-period' must be within (lower than) the certificate "+
|
||||
"validity period; renew-period=%v, cert-validity-period=%v", renewPeriod, cvp)
|
||||
}
|
||||
|
||||
renewer, err := newRenewer(caURL, crtFile, keyFile, rootFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
afterRenew := getAfterRenewFunc(pid, signum, execCmd)
|
||||
if isDaemon {
|
||||
// Force is always enabled when daemon mode is used
|
||||
ctx.Set("force", "true")
|
||||
next := nextRenewDuration(leaf, expiresIn, renewPeriod)
|
||||
return renewer.Daemon(outFile, next, expiresIn, renewPeriod, afterRenew)
|
||||
}
|
||||
|
||||
// Do not renew if (cert.notAfter - now) > (expiresIn + jitter)
|
||||
if expiresIn > 0 {
|
||||
jitter := rand.Int63n(int64(expiresIn / 20))
|
||||
if d := leaf.NotAfter.Sub(time.Now()); d > expiresIn+time.Duration(jitter) {
|
||||
ui.Printf("certificate not renewed: expires in %s\n", d.Round(time.Second))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := renewer.Renew(outFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ui.Printf("Your certificate has been saved in %s.\n", outFile)
|
||||
return afterRenew()
|
||||
}
|
||||
|
||||
func nextRenewDuration(leaf *x509.Certificate, expiresIn, renewPeriod time.Duration) time.Duration {
|
||||
if renewPeriod > 0 {
|
||||
// Renew now if it will be expired in renewPeriod
|
||||
if (leaf.NotAfter.Sub(time.Now()) - renewPeriod) <= 0 {
|
||||
return 0
|
||||
}
|
||||
return renewPeriod
|
||||
}
|
||||
|
||||
period := leaf.NotAfter.Sub(leaf.NotBefore)
|
||||
if expiresIn == 0 {
|
||||
expiresIn = period / 3
|
||||
}
|
||||
|
||||
d := leaf.NotAfter.Sub(time.Now()) - expiresIn
|
||||
n := rand.Int63n(int64(period / 20))
|
||||
d -= time.Duration(n)
|
||||
if d < 0 {
|
||||
d = 0
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func getAfterRenewFunc(pid, signum int, execCmd string) func() error {
|
||||
return func() error {
|
||||
if err := runKillPid(pid, signum); err != nil {
|
||||
return err
|
||||
}
|
||||
return runExecCmd(execCmd)
|
||||
}
|
||||
}
|
||||
|
||||
func runKillPid(pid, signum int) error {
|
||||
if pid == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := syscall.Kill(pid, syscall.Signal(signum)); err != nil {
|
||||
return errors.Wrapf(err, "kill %d with signal %d failed", pid, signum)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runExecCmd(execCmd string) error {
|
||||
execCmd = strings.TrimSpace(execCmd)
|
||||
if execCmd == "" {
|
||||
return nil
|
||||
}
|
||||
parts := strings.Split(execCmd, " ")
|
||||
cmd := exec.Command(parts[0], parts[1:]...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
type renewer struct {
|
||||
client *ca.Client
|
||||
transport *http.Transport
|
||||
keyFile string
|
||||
}
|
||||
|
||||
func newRenewer(caURL, crtFile, keyFile, rootFile string) (*renewer, error) {
|
||||
cert, err := tls.LoadX509KeyPair(crtFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error loading certificates")
|
||||
}
|
||||
if len(cert.Certificate) == 0 {
|
||||
return nil, errors.New("error loading certificate: certificate chain is empty")
|
||||
}
|
||||
|
||||
rootCAs, err := x509util.ReadCertPool(rootFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
RootCAs: rootCAs,
|
||||
PreferServerCipherSuites: true,
|
||||
},
|
||||
}
|
||||
|
||||
client, err := ca.NewClient(caURL, ca.WithTransport(tr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &renewer{
|
||||
client: client,
|
||||
transport: tr,
|
||||
keyFile: keyFile,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *renewer) Renew(outFile string) (*api.SignResponse, error) {
|
||||
resp, err := r.client.Renew(r.transport)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error renewing certificate")
|
||||
}
|
||||
|
||||
serverBlock, err := pemutil.Serialize(resp.ServerPEM.Certificate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caBlock, err := pemutil.Serialize(resp.CaPEM.Certificate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := append(pem.EncodeToMemory(serverBlock), pem.EncodeToMemory(caBlock)...)
|
||||
|
||||
if err := utils.WriteFile(outFile, data, 0600); err != nil {
|
||||
return nil, errs.FileError(err, outFile)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (r *renewer) RenewAndPrepareNext(outFile string, expiresIn, renewPeriod time.Duration) (time.Duration, error) {
|
||||
const durationOnErrors = 1 * time.Minute
|
||||
|
||||
resp, err := r.Renew(outFile)
|
||||
if err != nil {
|
||||
return durationOnErrors, err
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(outFile, r.keyFile)
|
||||
if err != nil {
|
||||
return durationOnErrors, errors.Wrap(err, "error loading certificates")
|
||||
}
|
||||
if len(cert.Certificate) == 0 {
|
||||
return durationOnErrors, errors.New("error loading certificate: certificate chain is empty")
|
||||
}
|
||||
|
||||
// Prepare next transport
|
||||
r.transport.TLSClientConfig.Certificates = []tls.Certificate{cert}
|
||||
|
||||
// Get next renew duration
|
||||
return nextRenewDuration(resp.ServerPEM.Certificate, expiresIn, renewPeriod), nil
|
||||
}
|
||||
|
||||
func (r *renewer) Daemon(outFile string, next, expiresIn, renewPeriod time.Duration, afterRenew func() error) error {
|
||||
// Loggers
|
||||
Info := log.New(os.Stdout, "INFO: ", log.LstdFlags)
|
||||
Error := log.New(os.Stderr, "ERROR: ", log.LstdFlags)
|
||||
|
||||
// Daemon loop
|
||||
signals := make(chan os.Signal, 1)
|
||||
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||
defer signal.Stop(signals)
|
||||
|
||||
Info.Printf("first renewal in %s", next.Round(time.Second))
|
||||
for {
|
||||
select {
|
||||
case sig := <-signals:
|
||||
switch sig {
|
||||
case syscall.SIGHUP:
|
||||
if n, err := r.RenewAndPrepareNext(outFile, expiresIn, renewPeriod); err != nil {
|
||||
Error.Println(err)
|
||||
} else {
|
||||
next = n
|
||||
Info.Printf("certificate renewed, next in %s", next.Round(time.Second))
|
||||
if err := afterRenew(); err != nil {
|
||||
Error.Println(err)
|
||||
}
|
||||
}
|
||||
case syscall.SIGINT, syscall.SIGTERM:
|
||||
return nil
|
||||
}
|
||||
case <-time.After(next):
|
||||
if n, err := r.RenewAndPrepareNext(outFile, expiresIn, renewPeriod); err != nil {
|
||||
next = n
|
||||
Error.Println(err)
|
||||
} else {
|
||||
next = n
|
||||
Info.Printf("certificate renewed, next in %s", next.Round(time.Second))
|
||||
if err := afterRenew(); err != nil {
|
||||
Error.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user