forked from TrueCloudLab/lego
feat: support custom duration for certificate (#1925)
This commit is contained in:
parent
8bf0cee70e
commit
c341e6a381
8 changed files with 268 additions and 40 deletions
|
@ -4,14 +4,26 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/acme"
|
"github.com/go-acme/lego/v4/acme"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// OrderOptions used to create an order (optional).
|
||||||
|
type OrderOptions struct {
|
||||||
|
NotBefore time.Time
|
||||||
|
NotAfter time.Time
|
||||||
|
}
|
||||||
|
|
||||||
type OrderService service
|
type OrderService service
|
||||||
|
|
||||||
// New Creates a new order.
|
// New Creates a new order.
|
||||||
func (o *OrderService) New(domains []string) (acme.ExtendedOrder, error) {
|
func (o *OrderService) New(domains []string) (acme.ExtendedOrder, error) {
|
||||||
|
return o.NewWithOptions(domains, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWithOptions Creates a new order.
|
||||||
|
func (o *OrderService) NewWithOptions(domains []string, opts *OrderOptions) (acme.ExtendedOrder, error) {
|
||||||
var identifiers []acme.Identifier
|
var identifiers []acme.Identifier
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
ident := acme.Identifier{Value: domain, Type: "dns"}
|
ident := acme.Identifier{Value: domain, Type: "dns"}
|
||||||
|
@ -25,6 +37,16 @@ func (o *OrderService) New(domains []string) (acme.ExtendedOrder, error) {
|
||||||
|
|
||||||
orderReq := acme.Order{Identifiers: identifiers}
|
orderReq := acme.Order{Identifiers: identifiers}
|
||||||
|
|
||||||
|
if opts != nil {
|
||||||
|
if !opts.NotAfter.IsZero() {
|
||||||
|
orderReq.NotAfter = opts.NotAfter.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.NotBefore.IsZero() {
|
||||||
|
orderReq.NotBefore = opts.NotBefore.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var order acme.Order
|
var order acme.Order
|
||||||
resp, err := o.core.post(o.core.GetDirectory().NewOrderURL, orderReq, &order)
|
resp, err := o.core.post(o.core.GetDirectory().NewOrderURL, orderReq, &order)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/acme"
|
"github.com/go-acme/lego/v4/acme"
|
||||||
"github.com/go-acme/lego/v4/platform/tester"
|
"github.com/go-acme/lego/v4/platform/tester"
|
||||||
|
@ -15,7 +16,7 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOrderService_New(t *testing.T) {
|
func TestOrderService_NewWithOptions(t *testing.T) {
|
||||||
mux, apiURL := tester.SetupFakeAPI(t)
|
mux, apiURL := tester.SetupFakeAPI(t)
|
||||||
|
|
||||||
// small value keeps test fast
|
// small value keeps test fast
|
||||||
|
@ -42,8 +43,15 @@ func TestOrderService_New(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tester.WriteJSONResponse(w, acme.Order{
|
err = tester.WriteJSONResponse(w, acme.Order{
|
||||||
Status: acme.StatusValid,
|
Status: acme.StatusValid,
|
||||||
Identifiers: order.Identifiers,
|
Expires: order.Expires,
|
||||||
|
Identifiers: order.Identifiers,
|
||||||
|
NotBefore: order.NotBefore,
|
||||||
|
NotAfter: order.NotAfter,
|
||||||
|
Error: order.Error,
|
||||||
|
Authorizations: order.Authorizations,
|
||||||
|
Finalize: order.Finalize,
|
||||||
|
Certificate: order.Certificate,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
@ -54,16 +62,48 @@ func TestOrderService_New(t *testing.T) {
|
||||||
core, err := New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey)
|
core, err := New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
order, err := core.Orders.New([]string{"example.com"})
|
testCases := []struct {
|
||||||
require.NoError(t, err)
|
desc string
|
||||||
|
opts *OrderOptions
|
||||||
expected := acme.ExtendedOrder{
|
expected acme.ExtendedOrder
|
||||||
Order: acme.Order{
|
}{
|
||||||
Status: "valid",
|
{
|
||||||
Identifiers: []acme.Identifier{{Type: "dns", Value: "example.com"}},
|
desc: "simple",
|
||||||
|
expected: acme.ExtendedOrder{
|
||||||
|
Order: acme.Order{
|
||||||
|
Status: "valid",
|
||||||
|
Identifiers: []acme.Identifier{{Type: "dns", Value: "example.com"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "with options",
|
||||||
|
opts: &OrderOptions{
|
||||||
|
NotBefore: time.Date(2023, 1, 1, 1, 0, 0, 0, time.UTC),
|
||||||
|
NotAfter: time.Date(2023, 1, 2, 1, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
expected: acme.ExtendedOrder{
|
||||||
|
Order: acme.Order{
|
||||||
|
Status: "valid",
|
||||||
|
Identifiers: []acme.Identifier{{Type: "dns", Value: "example.com"}},
|
||||||
|
NotBefore: "2023-01-01T01:00:00Z",
|
||||||
|
NotAfter: "2023-01-02T01:00:00Z",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert.Equal(t, expected, order)
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
order, err := core.Orders.NewWithOptions([]string{"example.com"}, test.opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, order)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func readSignedBody(r *http.Request, privateKey *rsa.PrivateKey) ([]byte, error) {
|
func readSignedBody(r *http.Request, privateKey *rsa.PrivateKey) ([]byte, error) {
|
||||||
|
|
|
@ -54,10 +54,13 @@ type Resource struct {
|
||||||
// If `AlwaysDeactivateAuthorizations` is true, the authorizations are also relinquished if the obtain request was successful.
|
// If `AlwaysDeactivateAuthorizations` is true, the authorizations are also relinquished if the obtain request was successful.
|
||||||
// See https://datatracker.ietf.org/doc/html/rfc8555#section-7.5.2.
|
// See https://datatracker.ietf.org/doc/html/rfc8555#section-7.5.2.
|
||||||
type ObtainRequest struct {
|
type ObtainRequest struct {
|
||||||
Domains []string
|
Domains []string
|
||||||
|
PrivateKey crypto.PrivateKey
|
||||||
|
MustStaple bool
|
||||||
|
|
||||||
|
NotBefore time.Time
|
||||||
|
NotAfter time.Time
|
||||||
Bundle bool
|
Bundle bool
|
||||||
PrivateKey crypto.PrivateKey
|
|
||||||
MustStaple bool
|
|
||||||
PreferredChain string
|
PreferredChain string
|
||||||
AlwaysDeactivateAuthorizations bool
|
AlwaysDeactivateAuthorizations bool
|
||||||
}
|
}
|
||||||
|
@ -69,7 +72,10 @@ type ObtainRequest struct {
|
||||||
// If `AlwaysDeactivateAuthorizations` is true, the authorizations are also relinquished if the obtain request was successful.
|
// If `AlwaysDeactivateAuthorizations` is true, the authorizations are also relinquished if the obtain request was successful.
|
||||||
// See https://datatracker.ietf.org/doc/html/rfc8555#section-7.5.2.
|
// See https://datatracker.ietf.org/doc/html/rfc8555#section-7.5.2.
|
||||||
type ObtainForCSRRequest struct {
|
type ObtainForCSRRequest struct {
|
||||||
CSR *x509.CertificateRequest
|
CSR *x509.CertificateRequest
|
||||||
|
|
||||||
|
NotBefore time.Time
|
||||||
|
NotAfter time.Time
|
||||||
Bundle bool
|
Bundle bool
|
||||||
PreferredChain string
|
PreferredChain string
|
||||||
AlwaysDeactivateAuthorizations bool
|
AlwaysDeactivateAuthorizations bool
|
||||||
|
@ -117,7 +123,12 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
|
||||||
log.Infof("[%s] acme: Obtaining SAN certificate", strings.Join(domains, ", "))
|
log.Infof("[%s] acme: Obtaining SAN certificate", strings.Join(domains, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
order, err := c.core.Orders.New(domains)
|
orderOpts := &api.OrderOptions{
|
||||||
|
NotBefore: request.NotBefore,
|
||||||
|
NotAfter: request.NotAfter,
|
||||||
|
}
|
||||||
|
|
||||||
|
order, err := c.core.Orders.NewWithOptions(domains, orderOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -182,7 +193,12 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error)
|
||||||
log.Infof("[%s] acme: Obtaining SAN certificate given a CSR", strings.Join(domains, ", "))
|
log.Infof("[%s] acme: Obtaining SAN certificate given a CSR", strings.Join(domains, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
order, err := c.core.Orders.New(domains)
|
orderOpts := &api.OrderOptions{
|
||||||
|
NotBefore: request.NotBefore,
|
||||||
|
NotAfter: request.NotAfter,
|
||||||
|
}
|
||||||
|
|
||||||
|
order, err := c.core.Orders.NewWithOptions(domains, orderOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -388,6 +404,18 @@ func (c *Certifier) RevokeWithReason(cert []byte, reason *uint) error {
|
||||||
return c.core.Certificates.Revoke(revokeMsg)
|
return c.core.Certificates.Revoke(revokeMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RenewOptions options used by Certifier.RenewWithOptions.
|
||||||
|
type RenewOptions struct {
|
||||||
|
NotBefore time.Time
|
||||||
|
NotAfter time.Time
|
||||||
|
// If true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
||||||
|
Bundle bool
|
||||||
|
PreferredChain string
|
||||||
|
AlwaysDeactivateAuthorizations bool
|
||||||
|
// Not supported for CSR request.
|
||||||
|
MustStaple bool
|
||||||
|
}
|
||||||
|
|
||||||
// Renew takes a Resource and tries to renew the certificate.
|
// Renew takes a Resource and tries to renew the certificate.
|
||||||
//
|
//
|
||||||
// If the renewal process succeeds, the new certificate will be returned in a new CertResource.
|
// If the renewal process succeeds, the new certificate will be returned in a new CertResource.
|
||||||
|
@ -398,7 +426,26 @@ func (c *Certifier) RevokeWithReason(cert []byte, reason *uint) error {
|
||||||
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
||||||
//
|
//
|
||||||
// For private key reuse the PrivateKey property of the passed in Resource should be non-nil.
|
// For private key reuse the PrivateKey property of the passed in Resource should be non-nil.
|
||||||
|
// Deprecated: use RenewWithOptions instead.
|
||||||
func (c *Certifier) Renew(certRes Resource, bundle, mustStaple bool, preferredChain string) (*Resource, error) {
|
func (c *Certifier) Renew(certRes Resource, bundle, mustStaple bool, preferredChain string) (*Resource, error) {
|
||||||
|
return c.RenewWithOptions(certRes, &RenewOptions{
|
||||||
|
Bundle: bundle,
|
||||||
|
PreferredChain: preferredChain,
|
||||||
|
MustStaple: mustStaple,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewWithOptions takes a Resource and tries to renew the certificate.
|
||||||
|
//
|
||||||
|
// If the renewal process succeeds, the new certificate will be returned in a new CertResource.
|
||||||
|
// Please be aware that this function will return a new certificate in ANY case that is not an error.
|
||||||
|
// If the server does not provide us with a new cert on a GET request to the CertURL
|
||||||
|
// this function will start a new-cert flow where a new certificate gets generated.
|
||||||
|
//
|
||||||
|
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
||||||
|
//
|
||||||
|
// For private key reuse the PrivateKey property of the passed in Resource should be non-nil.
|
||||||
|
func (c *Certifier) RenewWithOptions(certRes Resource, options *RenewOptions) (*Resource, error) {
|
||||||
// Input certificate is PEM encoded.
|
// Input certificate is PEM encoded.
|
||||||
// Decode it here as we may need the decoded cert later on in the renewal process.
|
// Decode it here as we may need the decoded cert later on in the renewal process.
|
||||||
// The input may be a bundle or a single certificate.
|
// The input may be a bundle or a single certificate.
|
||||||
|
@ -425,11 +472,17 @@ func (c *Certifier) Renew(certRes Resource, bundle, mustStaple bool, preferredCh
|
||||||
return nil, errP
|
return nil, errP
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.ObtainForCSR(ObtainForCSRRequest{
|
request := ObtainForCSRRequest{CSR: csr}
|
||||||
CSR: csr,
|
|
||||||
Bundle: bundle,
|
if options != nil {
|
||||||
PreferredChain: preferredChain,
|
request.NotBefore = options.NotBefore
|
||||||
})
|
request.NotAfter = options.NotAfter
|
||||||
|
request.Bundle = options.Bundle
|
||||||
|
request.PreferredChain = options.PreferredChain
|
||||||
|
request.AlwaysDeactivateAuthorizations = options.AlwaysDeactivateAuthorizations
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.ObtainForCSR(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
var privateKey crypto.PrivateKey
|
var privateKey crypto.PrivateKey
|
||||||
|
@ -440,14 +493,21 @@ func (c *Certifier) Renew(certRes Resource, bundle, mustStaple bool, preferredCh
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query := ObtainRequest{
|
request := ObtainRequest{
|
||||||
Domains: certcrypto.ExtractDomains(x509Cert),
|
Domains: certcrypto.ExtractDomains(x509Cert),
|
||||||
Bundle: bundle,
|
PrivateKey: privateKey,
|
||||||
PrivateKey: privateKey,
|
|
||||||
MustStaple: mustStaple,
|
|
||||||
PreferredChain: preferredChain,
|
|
||||||
}
|
}
|
||||||
return c.Obtain(query)
|
|
||||||
|
if options != nil {
|
||||||
|
request.MustStaple = options.MustStaple
|
||||||
|
request.NotBefore = options.NotBefore
|
||||||
|
request.NotAfter = options.NotAfter
|
||||||
|
request.Bundle = options.Bundle
|
||||||
|
request.PreferredChain = options.PreferredChain
|
||||||
|
request.AlwaysDeactivateAuthorizations = options.AlwaysDeactivateAuthorizations
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Obtain(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOCSP takes a PEM encoded cert or cert bundle returning the raw OCSP response,
|
// GetOCSP takes a PEM encoded cert or cert bundle returning the raw OCSP response,
|
||||||
|
|
|
@ -75,9 +75,15 @@ func createRenew() *cli.Command {
|
||||||
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate." +
|
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate." +
|
||||||
" Only works if the CSR is generated by lego.",
|
" Only works if the CSR is generated by lego.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.TimestampFlag{
|
||||||
Name: "renew-hook",
|
Name: "not-before",
|
||||||
Usage: "Define a hook. The hook is executed only when the certificates are effectively renewed.",
|
Usage: "Set the notBefore field in the certificate (RFC3339 format)",
|
||||||
|
Layout: time.RFC3339,
|
||||||
|
},
|
||||||
|
&cli.TimestampFlag{
|
||||||
|
Name: "not-after",
|
||||||
|
Usage: "Set the notAfter field in the certificate (RFC3339 format)",
|
||||||
|
Layout: time.RFC3339,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "preferred-chain",
|
Name: "preferred-chain",
|
||||||
|
@ -88,6 +94,10 @@ func createRenew() *cli.Command {
|
||||||
Name: "always-deactivate-authorizations",
|
Name: "always-deactivate-authorizations",
|
||||||
Usage: "Force the authorizations to be relinquished even if the certificate request was successful.",
|
Usage: "Force the authorizations to be relinquished even if the certificate request was successful.",
|
||||||
},
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "renew-hook",
|
||||||
|
Usage: "Define a hook. The hook is executed only when the certificates are effectively renewed.",
|
||||||
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "no-random-sleep",
|
Name: "no-random-sleep",
|
||||||
Usage: "Do not add a random sleep before the renewal." +
|
Usage: "Do not add a random sleep before the renewal." +
|
||||||
|
@ -188,12 +198,15 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif
|
||||||
|
|
||||||
request := certificate.ObtainRequest{
|
request := certificate.ObtainRequest{
|
||||||
Domains: merge(certDomains, domains),
|
Domains: merge(certDomains, domains),
|
||||||
Bundle: bundle,
|
|
||||||
PrivateKey: privateKey,
|
PrivateKey: privateKey,
|
||||||
MustStaple: ctx.Bool("must-staple"),
|
MustStaple: ctx.Bool("must-staple"),
|
||||||
|
NotBefore: getTime(ctx, "not-before"),
|
||||||
|
NotAfter: getTime(ctx, "not-after"),
|
||||||
|
Bundle: bundle,
|
||||||
PreferredChain: ctx.String("preferred-chain"),
|
PreferredChain: ctx.String("preferred-chain"),
|
||||||
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
|
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
|
||||||
}
|
}
|
||||||
|
|
||||||
certRes, err := client.Certificate.Obtain(request)
|
certRes, err := client.Certificate.Obtain(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -265,12 +278,16 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat
|
||||||
timeLeft := cert.NotAfter.Sub(time.Now().UTC())
|
timeLeft := cert.NotAfter.Sub(time.Now().UTC())
|
||||||
log.Infof("[%s] acme: Trying renewal with %d hours remaining", domain, int(timeLeft.Hours()))
|
log.Infof("[%s] acme: Trying renewal with %d hours remaining", domain, int(timeLeft.Hours()))
|
||||||
|
|
||||||
certRes, err := client.Certificate.ObtainForCSR(certificate.ObtainForCSRRequest{
|
request := certificate.ObtainForCSRRequest{
|
||||||
CSR: csr,
|
CSR: csr,
|
||||||
|
NotBefore: getTime(ctx, "not-before"),
|
||||||
|
NotAfter: getTime(ctx, "not-after"),
|
||||||
Bundle: bundle,
|
Bundle: bundle,
|
||||||
PreferredChain: ctx.String("preferred-chain"),
|
PreferredChain: ctx.String("preferred-chain"),
|
||||||
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
|
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
|
||||||
})
|
}
|
||||||
|
|
||||||
|
certRes, err := client.Certificate.ObtainForCSR(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
|
@ -40,9 +41,15 @@ func createRun() *cli.Command {
|
||||||
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate." +
|
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate." +
|
||||||
" Only works if the CSR is generated by lego.",
|
" Only works if the CSR is generated by lego.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.TimestampFlag{
|
||||||
Name: "run-hook",
|
Name: "not-before",
|
||||||
Usage: "Define a hook. The hook is executed when the certificates are effectively created.",
|
Usage: "Set the notBefore field in the certificate (RFC3339 format)",
|
||||||
|
Layout: time.RFC3339,
|
||||||
|
},
|
||||||
|
&cli.TimestampFlag{
|
||||||
|
Name: "not-after",
|
||||||
|
Usage: "Set the notAfter field in the certificate (RFC3339 format)",
|
||||||
|
Layout: time.RFC3339,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "preferred-chain",
|
Name: "preferred-chain",
|
||||||
|
@ -53,6 +60,10 @@ func createRun() *cli.Command {
|
||||||
Name: "always-deactivate-authorizations",
|
Name: "always-deactivate-authorizations",
|
||||||
Usage: "Force the authorizations to be relinquished even if the certificate request was successful.",
|
Usage: "Force the authorizations to be relinquished even if the certificate request was successful.",
|
||||||
},
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "run-hook",
|
||||||
|
Usage: "Define a hook. The hook is executed when the certificates are effectively created.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,6 +188,17 @@ func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Reso
|
||||||
PreferredChain: ctx.String("preferred-chain"),
|
PreferredChain: ctx.String("preferred-chain"),
|
||||||
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
|
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notBefore := ctx.Timestamp("not-before")
|
||||||
|
if notBefore != nil {
|
||||||
|
request.NotBefore = *notBefore
|
||||||
|
}
|
||||||
|
|
||||||
|
notAfter := ctx.Timestamp("not-after")
|
||||||
|
if notAfter != nil {
|
||||||
|
request.NotAfter = *notAfter
|
||||||
|
}
|
||||||
|
|
||||||
return client.Certificate.Obtain(request)
|
return client.Certificate.Obtain(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,10 +209,14 @@ func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Reso
|
||||||
}
|
}
|
||||||
|
|
||||||
// obtain a certificate for this CSR
|
// obtain a certificate for this CSR
|
||||||
return client.Certificate.ObtainForCSR(certificate.ObtainForCSRRequest{
|
request := certificate.ObtainForCSRRequest{
|
||||||
CSR: csr,
|
CSR: csr,
|
||||||
|
NotBefore: getTime(ctx, "not-before"),
|
||||||
|
NotAfter: getTime(ctx, "not-after"),
|
||||||
Bundle: bundle,
|
Bundle: bundle,
|
||||||
PreferredChain: ctx.String("preferred-chain"),
|
PreferredChain: ctx.String("preferred-chain"),
|
||||||
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
|
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
|
||||||
})
|
}
|
||||||
|
|
||||||
|
return client.Certificate.ObtainForCSR(request)
|
||||||
}
|
}
|
||||||
|
|
10
cmd/flags.go
10
cmd/flags.go
|
@ -1,6 +1,8 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"software.sslmate.com/src/go-pkcs12"
|
"software.sslmate.com/src/go-pkcs12"
|
||||||
|
@ -142,3 +144,11 @@ func CreateFlags(defaultPath string) []cli.Flag {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getTime(ctx *cli.Context, name string) time.Time {
|
||||||
|
value := ctx.Timestamp(name)
|
||||||
|
if value == nil {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
return *value
|
||||||
|
}
|
||||||
|
|
|
@ -63,6 +63,8 @@ OPTIONS:
|
||||||
--always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful.
|
--always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful.
|
||||||
--must-staple Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego. (default: false)
|
--must-staple Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego. (default: false)
|
||||||
--no-bundle Do not create a certificate bundle by adding the issuers certificate to the new certificate. (default: false)
|
--no-bundle Do not create a certificate bundle by adding the issuers certificate to the new certificate. (default: false)
|
||||||
|
--not-after value Set the notAfter field in the certificate
|
||||||
|
--not-before value Set the notBefore field in the certificate
|
||||||
--preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.
|
--preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.
|
||||||
--run-hook value Define a hook. The hook is executed when the certificates are effectively created.
|
--run-hook value Define a hook. The hook is executed when the certificates are effectively created.
|
||||||
"""
|
"""
|
||||||
|
@ -85,6 +87,8 @@ OPTIONS:
|
||||||
--must-staple Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego. (default: false)
|
--must-staple Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego. (default: false)
|
||||||
--no-bundle Do not create a certificate bundle by adding the issuers certificate to the new certificate. (default: false)
|
--no-bundle Do not create a certificate bundle by adding the issuers certificate to the new certificate. (default: false)
|
||||||
--no-random-sleep Do not add a random sleep before the renewal. We do not recommend using this flag if you are doing your renewals in an automated way. (default: false)
|
--no-random-sleep Do not add a random sleep before the renewal. We do not recommend using this flag if you are doing your renewals in an automated way. (default: false)
|
||||||
|
--not-after value Set the notAfter field in the certificate
|
||||||
|
--not-before value Set the notBefore field in the certificate
|
||||||
--preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.
|
--preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.
|
||||||
--renew-hook value Define a hook. The hook is executed only when the certificates are effectively renewed.
|
--renew-hook value Define a hook. The hook is executed only when the certificates are effectively renewed.
|
||||||
--reuse-key Used to indicate you want to reuse your current private key for the new certificate. (default: false)
|
--reuse-key Used to indicate you want to reuse your current private key for the new certificate. (default: false)
|
||||||
|
|
|
@ -8,7 +8,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
"github.com/go-acme/lego/v4/challenge/http01"
|
"github.com/go-acme/lego/v4/challenge/http01"
|
||||||
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
||||||
|
@ -255,6 +257,53 @@ func TestChallengeHTTP_Client_Obtain(t *testing.T) {
|
||||||
assert.Empty(t, resource.CSR)
|
assert.Empty(t, resource.CSR)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestChallengeHTTP_Client_Obtain_notBefore_notAfter(t *testing.T) {
|
||||||
|
err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }()
|
||||||
|
|
||||||
|
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
require.NoError(t, err, "Could not generate test key")
|
||||||
|
|
||||||
|
user := &fakeUser{privateKey: privateKey}
|
||||||
|
config := lego.NewConfig(user)
|
||||||
|
config.CADirURL = load.PebbleOptions.HealthCheckURL
|
||||||
|
|
||||||
|
client, err := lego.NewClient(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "5002"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
|
require.NoError(t, err)
|
||||||
|
user.registration = reg
|
||||||
|
|
||||||
|
now := time.Now().UTC()
|
||||||
|
|
||||||
|
request := certificate.ObtainRequest{
|
||||||
|
Domains: []string{"acme.wtf"},
|
||||||
|
NotBefore: now.Add(1 * time.Hour),
|
||||||
|
NotAfter: now.Add(2 * time.Hour),
|
||||||
|
Bundle: true,
|
||||||
|
}
|
||||||
|
resource, err := client.Certificate.Obtain(request)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.NotNil(t, resource)
|
||||||
|
assert.Equal(t, "acme.wtf", resource.Domain)
|
||||||
|
assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertURL)
|
||||||
|
assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertStableURL)
|
||||||
|
assert.NotEmpty(t, resource.Certificate)
|
||||||
|
assert.NotEmpty(t, resource.IssuerCertificate)
|
||||||
|
assert.Empty(t, resource.CSR)
|
||||||
|
|
||||||
|
cert, err := certcrypto.ParsePEMCertificate(resource.Certificate)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.WithinDuration(t, now.Add(1*time.Hour), cert.NotBefore, 1*time.Second)
|
||||||
|
assert.WithinDuration(t, now.Add(2*time.Hour), cert.NotAfter, 1*time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
func TestChallengeHTTP_Client_Registration_QueryRegistration(t *testing.T) {
|
func TestChallengeHTTP_Client_Registration_QueryRegistration(t *testing.T) {
|
||||||
err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem")
|
err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
Loading…
Reference in a new issue