mirror of
https://github.com/hanzozt/zt.git
synced 2026-11-02 11:38:22 +00:00
181 lines
5.4 KiB
Go
181 lines
5.4 KiB
Go
/*
|
|
Copyright NetFoundry Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package lets_encrypt
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/x509"
|
|
"github.com/go-acme/lego/v4/certcrypto"
|
|
"github.com/go-acme/lego/v4/certificate"
|
|
"github.com/go-acme/lego/v4/lego"
|
|
cmdhelper "github.com/openziti/ziti/ziti/cmd/helpers"
|
|
"github.com/openziti/ziti/ziti/internal/log"
|
|
"github.com/spf13/cobra"
|
|
"io"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
renewEnvAccountEmail = "LEGO_ACCOUNT_EMAIL"
|
|
renewEnvCertDomain = "LEGO_CERT_DOMAIN"
|
|
renewEnvCertPath = "LEGO_CERT_PATH"
|
|
renewEnvCertKeyPath = "LEGO_CERT_KEY_PATH"
|
|
)
|
|
|
|
// newListCmd creates a command object for the "controller list" command
|
|
func newRenewCmd(out io.Writer, errOut io.Writer) *cobra.Command {
|
|
options := &leOptions{}
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "renew",
|
|
Short: "Renew a Let's Encrypt certificate",
|
|
Long: "Renew a Let's Encrypt certificate",
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
options.Cmd = cmd
|
|
options.Args = args
|
|
err := runRenew(options)
|
|
cmdhelper.CheckErr(err)
|
|
},
|
|
}
|
|
|
|
// allow interspersing positional args and flags
|
|
cmd.Flags().SetInterspersed(true)
|
|
|
|
cmd.Flags().StringVarP(&options.domain, "domain", "d", "", "Domain for which Cert is being generated (e.g. me.example.com)")
|
|
if err := cmd.MarkFlagRequired("domain"); err != nil {
|
|
panic(err)
|
|
}
|
|
cmd.Flags().StringVarP(&options.path, "path", "p", "", "Directory where data is stored")
|
|
if err := cmd.MarkFlagRequired("path"); err != nil {
|
|
panic(err)
|
|
}
|
|
cmd.Flags().IntVarP(&options.days, "days", "", 14, "The number of days left on a certificate to renew it")
|
|
cmd.Flags().BoolVarP(&options.reuseKey, "reuse-key", "r", true, "Used to indicate you want to reuse your current private key for the renewed certificate")
|
|
cmd.Flags().StringVarP(&options.email, "email", "e", "[email protected]", "Email used for registration and recovery contact")
|
|
if err := options.keyType.Set("RSA4096"); err != nil { // set default
|
|
panic(err)
|
|
}
|
|
cmd.Flags().VarP(&options.keyType, "keytype", "k", "Key type to use for private keys")
|
|
cmd.Flags().StringVarP(&options.acmeserver, "acmeserver", "a", acmeProd, "ACME CA hostname")
|
|
cmd.Flags().BoolVarP(&options.staging, "staging", "s", false, "Enable creation of 'staging' Certs (instead of production Certs)")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func runRenew(options *leOptions) (err error) {
|
|
|
|
if options.staging {
|
|
options.acmeserver = acmeStaging
|
|
}
|
|
|
|
accountsStorage := NewAccountsStorage(options)
|
|
|
|
account, client := setup(options, accountsStorage)
|
|
|
|
if account.Registration == nil {
|
|
log.Fatalf("Account %s is not registered. Use 'run' to register a new account.\n", account.Email)
|
|
}
|
|
|
|
certsStorage := NewCertificatesStorage(options.path)
|
|
|
|
meta := map[string]string{renewEnvAccountEmail: account.Email}
|
|
|
|
return renewForDomains(options, client, certsStorage, meta)
|
|
}
|
|
|
|
func renewForDomains(options *leOptions, client *lego.Client, certsStorage *CertificatesStorage, meta map[string]string) error {
|
|
domain := options.domain
|
|
|
|
// load the cert resource from files.
|
|
// We store the certificate, private key and metadata in different files
|
|
// as web servers would not be able to work with a combined file.
|
|
certificates, err := certsStorage.ReadCertificate(domain, ".crt")
|
|
if err != nil {
|
|
log.Fatalf("Error while loading the certificate for domain %s\n\t%v", domain, err)
|
|
}
|
|
|
|
cert := certificates[0]
|
|
|
|
if !needRenewal(cert, domain, options.days) {
|
|
return nil
|
|
}
|
|
|
|
// This is just meant to be informal for the user.
|
|
timeLeft := cert.NotAfter.Sub(time.Now().UTC())
|
|
log.Infof("[%s] acme: Trying renewal with %d hours remaining", domain, int(timeLeft.Hours()))
|
|
|
|
certDomains := certcrypto.ExtractDomains(cert)
|
|
|
|
var privateKey crypto.PrivateKey
|
|
if options.reuseKey {
|
|
keyBytes, errR := certsStorage.ReadFile(domain, ".key")
|
|
if errR != nil {
|
|
log.Fatalf("Error while loading the private key for domain %s\n\t%v", domain, errR)
|
|
}
|
|
|
|
privateKey, errR = certcrypto.ParsePEMPrivateKey(keyBytes)
|
|
if errR != nil {
|
|
return errR
|
|
}
|
|
}
|
|
|
|
request := certificate.ObtainRequest{
|
|
Domains: merge(certDomains, domain),
|
|
PrivateKey: privateKey,
|
|
}
|
|
certRes, err := client.Certificate.Obtain(request)
|
|
if err != nil {
|
|
log.Fatalf("%v", err)
|
|
}
|
|
|
|
certsStorage.SaveResource(certRes)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
func needRenewal(x509Cert *x509.Certificate, domain string, days int) bool {
|
|
if x509Cert.IsCA {
|
|
log.Fatalf("[%s] Certificate bundle starts with a CA certificate", domain)
|
|
}
|
|
|
|
if days >= 0 {
|
|
notAfter := int(time.Until(x509Cert.NotAfter).Hours() / 24.0)
|
|
if notAfter > days {
|
|
log.Infof("[%s] The certificate expires in %d days, the number of days defined to perform the renewal is %d: no renewal.",
|
|
domain, notAfter, days)
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func merge(prevDomains []string, nextDomain string) []string {
|
|
var found bool
|
|
for _, prev := range prevDomains {
|
|
if prev == nextDomain {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
prevDomains = append(prevDomains, nextDomain)
|
|
}
|
|
return prevDomains
|
|
}
|