mirror of
https://github.com/vxcontrol/rancher-letsencrypt.git
synced 2026-07-19 17:13:37 -04:00
Support for claiming existing certificate + Store certificate in Volume
* Adds a new required "certificate name" parameter (backward compatible): When there is an existing certificate by that name it will be overwritten with a renewed certificate * fullchain.pem and privatekey.pem are now stored in a Volume under /etc/letsencrypt/production/certificates/. This is until we find a better way to share the private key with other services.
This commit is contained in:
+2
-1
@@ -3,9 +3,10 @@ MAINTAINER Jan Broer <jan@festplatte.eu.org>
|
||||
|
||||
RUN apk add --no-cache ca-certificates
|
||||
|
||||
ENV LETSENCRYPT_RELEASE v0.2.5
|
||||
ENV LETSENCRYPT_RELEASE v0.2.6
|
||||
|
||||
ADD https://github.com/janeczku/rancher-letsencrypt/releases/download/${LETSENCRYPT_RELEASE}/rancher-letsencrypt-linux-amd64.tar.gz /tmp/rancher-letsencrypt.tar.gz
|
||||
|
||||
RUN tar -zxvf /tmp/rancher-letsencrypt.tar.gz -C /usr/bin \
|
||||
&& chmod +x /usr/bin/rancher-letsencrypt
|
||||
|
||||
|
||||
@@ -4,8 +4,11 @@ MAINTAINER Jan Broer <jan@festplatte.eu.org>
|
||||
RUN apk add --no-cache ca-certificates
|
||||
|
||||
ADD build/rancher-letsencrypt-linux-amd64 /usr/bin/rancher-letsencrypt
|
||||
|
||||
RUN chmod +x /usr/bin/rancher-letsencrypt
|
||||
|
||||
VOLUME /etc/letsencrypt
|
||||
|
||||
ENV DEBUG=true
|
||||
|
||||
ENTRYPOINT ["/usr/bin/rancher-letsencrypt"]
|
||||
@@ -52,7 +52,7 @@ dockerhub: image
|
||||
docker push $(DOCKER_IMAGE):$(VERSION)
|
||||
|
||||
image:
|
||||
docker build -t $(DOCKER_IMAGE):$(VERSION) -f Dockerfile.dev .
|
||||
docker build -t $(DOCKER_IMAGE):dev-$(SHA) -f Dockerfile.dev .
|
||||
|
||||
version:
|
||||
@echo $(VERSION) $(SHA)
|
||||
|
||||
+19
-11
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
DESCRIPTION = "Created by Let's Encrypt Certificate Manager"
|
||||
CERT_DESCRIPTION = "Created by Let's Encrypt Certificate Manager"
|
||||
ISSUER_PRODUCTION = "Let's Encrypt"
|
||||
ISSUER_STAGING = "fake CA"
|
||||
PREFIX_PROD = "[LE] "
|
||||
@@ -38,12 +38,13 @@ func (c *Context) InitContext() {
|
||||
cattleUrl := getEnvOption("CATTLE_URL", true)
|
||||
cattleApiKey := getEnvOption("CATTLE_ACCESS_KEY", true)
|
||||
cattleSecretKey := getEnvOption("CATTLE_SECRET_KEY", true)
|
||||
debug := getEnvOption("DEBUG", false)
|
||||
debugParam := getEnvOption("DEBUG", false)
|
||||
eulaParam := getEnvOption("EULA", false)
|
||||
apiVerParam := getEnvOption("API_VERSION", true)
|
||||
emailParam := getEnvOption("EMAIL", true)
|
||||
domainParam := getEnvOption("DOMAINS", true)
|
||||
keyTypeParam := getEnvOption("PUBLIC_KEY_TYPE", true)
|
||||
certNameParam := getEnvOption("CERT_NAME", false)
|
||||
timeParam := getEnvOption("RENEWAL_TIME", true)
|
||||
providerParam := getEnvOption("PROVIDER", true)
|
||||
|
||||
@@ -63,18 +64,26 @@ func (c *Context) InitContext() {
|
||||
logrus.Fatalf("Invalid value for RENEWAL_TIME: %s", timeParam)
|
||||
}
|
||||
|
||||
var serverURI string
|
||||
var certAutoName string
|
||||
|
||||
switch apiVerParam {
|
||||
case "Production":
|
||||
serverURI = letsencrypt.PRODUCTION_URI
|
||||
c.RancherCertName = PREFIX_PROD + c.Domains[0]
|
||||
certAutoName = PREFIX_PROD + c.Domains[0]
|
||||
case "Sandbox":
|
||||
serverURI = letsencrypt.STAGING_URI
|
||||
c.RancherCertName = PREFIX_STAGING + c.Domains[0]
|
||||
certAutoName = PREFIX_STAGING + c.Domains[0]
|
||||
default:
|
||||
logrus.Fatalf("Invalid value for API_VERSION: %s", apiVerParam)
|
||||
}
|
||||
|
||||
apiVersion := letsencrypt.ApiVersion(apiVerParam)
|
||||
keyType := letsencrypt.KeyType(keyTypeParam)
|
||||
|
||||
if len(certNameParam) != 0 {
|
||||
c.RancherCertName = certNameParam
|
||||
} else {
|
||||
c.RancherCertName = certAutoName
|
||||
}
|
||||
|
||||
c.Rancher, err = rancher.NewClient(cattleUrl, cattleApiKey, cattleSecretKey)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Rancher client: %v", err)
|
||||
@@ -92,19 +101,18 @@ func (c *Context) InitContext() {
|
||||
DynCustomerName: os.Getenv("DYN_CUSTOMER_NAME"),
|
||||
DynUserName: os.Getenv("DYN_USER_NAME"),
|
||||
DynPassword: os.Getenv("DYN_PASSWORD"),
|
||||
AwsRegionName: "us-east-1",
|
||||
}
|
||||
|
||||
c.Acme, err = letsencrypt.NewClient(emailParam, keyTypeParam, serverURI, providerOpts)
|
||||
c.Acme, err = letsencrypt.NewClient(emailParam, keyType, apiVersion, providerOpts)
|
||||
if err != nil {
|
||||
logrus.Fatalf("LetsEncrypt client: %v", err)
|
||||
}
|
||||
|
||||
// Enable debug/test mode
|
||||
if strings.EqualFold(debug, "true") {
|
||||
if strings.EqualFold(debugParam, "true") {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
c.Debug = true
|
||||
c.Acme.EnableDebugLogging()
|
||||
c.Acme.EnableDebug()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+18
-7
@@ -7,21 +7,25 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
lego "github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
type Account struct {
|
||||
Email string `json:"email"`
|
||||
key crypto.PrivateKey
|
||||
Email string `json:"email"`
|
||||
Registration *lego.RegistrationResource `json:"registrations"`
|
||||
|
||||
key crypto.PrivateKey
|
||||
path string
|
||||
}
|
||||
|
||||
// NewAccount creates a new or gets a stored LE account for the given email
|
||||
func NewAccount(email string, keyType lego.KeyType) (*Account, error) {
|
||||
keyFile := path.Join(configPath(), email+".key")
|
||||
accountFile := path.Join(configPath(), email+".json")
|
||||
func NewAccount(email string, apiVer ApiVersion, keyType lego.KeyType) (*Account, error) {
|
||||
accPath := accountPath(email, apiVer)
|
||||
keyFile := path.Join(accPath, "account.key")
|
||||
accountFile := path.Join(accPath, "account.json")
|
||||
|
||||
var privKey crypto.PrivateKey
|
||||
if _, err := os.Stat(keyFile); os.IsNotExist(err) {
|
||||
@@ -39,7 +43,7 @@ func NewAccount(email string, keyType lego.KeyType) (*Account, error) {
|
||||
}
|
||||
|
||||
if _, err := os.Stat(accountFile); os.IsNotExist(err) {
|
||||
return &Account{Email: email, key: privKey}, nil
|
||||
return &Account{Email: email, key: privKey, path: accPath}, nil
|
||||
}
|
||||
|
||||
fileBytes, err := ioutil.ReadFile(accountFile)
|
||||
@@ -54,6 +58,7 @@ func NewAccount(email string, keyType lego.KeyType) (*Account, error) {
|
||||
}
|
||||
|
||||
acc.key = privKey
|
||||
acc.path = accPath
|
||||
return &acc, nil
|
||||
}
|
||||
|
||||
@@ -63,7 +68,7 @@ func (a *Account) Save() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
accountFile := path.Join(configPath(), a.Email+".json")
|
||||
accountFile := path.Join(a.path, "account.json")
|
||||
return ioutil.WriteFile(accountFile, jsonBytes, 0700)
|
||||
}
|
||||
|
||||
@@ -83,3 +88,9 @@ func (a *Account) GetPrivateKey() crypto.PrivateKey {
|
||||
func (a *Account) GetRegistration() *lego.RegistrationResource {
|
||||
return a.Registration
|
||||
}
|
||||
|
||||
func accountPath(email string, apiVer ApiVersion) string {
|
||||
path := path.Join(ConfigDir, strings.ToLower(string(apiVer)), "accounts", email)
|
||||
maybeCreatePath(path)
|
||||
return path
|
||||
}
|
||||
|
||||
+138
-82
@@ -15,54 +15,81 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
PRODUCTION_URI = "https://acme-v01.api.letsencrypt.org/directory"
|
||||
STAGING_URI = "https://acme-staging.api.letsencrypt.org/directory"
|
||||
DATA_DIR = "/etc/letsencrypt"
|
||||
ConfigDir = "/etc/letsencrypt"
|
||||
ProductionApiUri = "https://acme-v01.api.letsencrypt.org/directory"
|
||||
StagingApiUri = "https://acme-staging.api.letsencrypt.org/directory"
|
||||
)
|
||||
|
||||
type KeyType string
|
||||
|
||||
const (
|
||||
RSA2048 KeyType = "RSA-2048"
|
||||
RSA4096 KeyType = "RSA-4096"
|
||||
RSA8192 KeyType = "RSA-8192"
|
||||
EC256 KeyType = "ECDSA-256"
|
||||
EC384 KeyType = "ECDSA-384"
|
||||
)
|
||||
|
||||
type ApiVersion string
|
||||
|
||||
const (
|
||||
Production ApiVersion = "Production"
|
||||
Sandbox ApiVersion = "Sandbox"
|
||||
)
|
||||
|
||||
// AcmeCertificate represents a CA issued certificate,
|
||||
// PrivateKey and Certificate are both PEM encoded.
|
||||
//
|
||||
// Anonymous fields:
|
||||
// PrivateKey []byte
|
||||
// Certificate []byte
|
||||
// Domain string
|
||||
type AcmeCertificate struct {
|
||||
PrivateKey []byte `json:"privateKey"`
|
||||
Certificate []byte `json:"certificate"`
|
||||
lego.CertificateResource
|
||||
ExpiryDate time.Time `json:"expiryDate"`
|
||||
SerialNumber string `json:"serialnumber"`
|
||||
}
|
||||
|
||||
type acmeCertificateStore struct {
|
||||
CertRes lego.CertificateResource `json:"certRes"`
|
||||
AcmeCert AcmeCertificate `json:"acmeCert"`
|
||||
}
|
||||
|
||||
// Client represents a Lets Encrypt client
|
||||
type Client struct {
|
||||
client *lego.Client
|
||||
client *lego.Client
|
||||
apiVersion ApiVersion
|
||||
}
|
||||
|
||||
// NewClient returns a new Lets Encrypt client
|
||||
func NewClient(email, keyTypeStr, uri string, provider ProviderOpts) (*Client, error) {
|
||||
func NewClient(email string, kt KeyType, apiVer ApiVersion, provider ProviderOpts) (*Client, error) {
|
||||
var keyType lego.KeyType
|
||||
switch strings.ToUpper(keyTypeStr) {
|
||||
case "RSA-2048":
|
||||
switch kt {
|
||||
case RSA2048:
|
||||
keyType = lego.RSA2048
|
||||
case "RSA-4096":
|
||||
case RSA4096:
|
||||
keyType = lego.RSA4096
|
||||
case "RSA-8192":
|
||||
case RSA8192:
|
||||
keyType = lego.RSA8192
|
||||
case "ECDSA-256":
|
||||
case EC256:
|
||||
keyType = lego.EC256
|
||||
case "ECDSA-384":
|
||||
case EC384:
|
||||
keyType = lego.EC384
|
||||
default:
|
||||
return nil, fmt.Errorf("Invalid private key type: %s", keyTypeStr)
|
||||
return nil, fmt.Errorf("Invalid private key type: %s", string(kt))
|
||||
}
|
||||
|
||||
acc, err := NewAccount(email, keyType)
|
||||
var serverUri string
|
||||
switch apiVer {
|
||||
case Production:
|
||||
serverUri = ProductionApiUri
|
||||
case Sandbox:
|
||||
serverUri = StagingApiUri
|
||||
default:
|
||||
return nil, fmt.Errorf("Invalid LE API version: %s", string(apiVer))
|
||||
}
|
||||
|
||||
acc, err := NewAccount(email, apiVer, keyType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not initialize account store for %s: %v", email, err)
|
||||
}
|
||||
|
||||
client, err := lego.NewClient(uri, acc, keyType)
|
||||
client, err := lego.NewClient(serverUri, acc, keyType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not create client: %v", err)
|
||||
}
|
||||
@@ -105,10 +132,16 @@ func NewClient(email, keyTypeStr, uri string, provider ProviderOpts) (*Client, e
|
||||
client.ExcludeChallenges([]lego.Challenge{lego.HTTP01, lego.TLSSNI01})
|
||||
|
||||
return &Client{
|
||||
client: client,
|
||||
client: client,
|
||||
apiVersion: apiVer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EnableDebugLogging enables logging in the upstream lego library
|
||||
func (c *Client) EnableDebug() {
|
||||
lego.Logger = log.New(os.Stdout, "", 0)
|
||||
}
|
||||
|
||||
// Issue obtains a new SAN certificate from the Lets Encrypt CA
|
||||
func (c *Client) Issue(domains []string) (*AcmeCertificate, map[string]error) {
|
||||
cert, failures := c.client.ObtainCertificate(domains, true, nil)
|
||||
@@ -117,30 +150,26 @@ func (c *Client) Issue(domains []string) (*AcmeCertificate, map[string]error) {
|
||||
}
|
||||
|
||||
expirydate, _ := lego.GetPEMCertExpiration(cert.Certificate)
|
||||
serial, _ := getPEMCertSerialNo(cert.Certificate)
|
||||
serialnum, _ := getPEMCertSerialNo(cert.Certificate)
|
||||
|
||||
acmeCert := AcmeCertificate{
|
||||
cert.PrivateKey,
|
||||
cert.Certificate,
|
||||
expirydate,
|
||||
serial,
|
||||
CertificateResource: cert,
|
||||
ExpiryDate: expirydate,
|
||||
SerialNumber: serialnum,
|
||||
}
|
||||
|
||||
saveCertStore(cert, acmeCert)
|
||||
c.saveCertificate(acmeCert)
|
||||
return &acmeCert, nil
|
||||
}
|
||||
|
||||
// Renew obtains a renewed SAN certificate from the Lets Encrypt CA
|
||||
func (c *Client) Renew(domains []string) (*AcmeCertificate, error) {
|
||||
domain := domains[0]
|
||||
certStore, err := loadCertStore(domain)
|
||||
currentCert, err := c.loadCertificate(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
logrus.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
certRes := certStore.CertRes
|
||||
certRes.PrivateKey = certStore.AcmeCert.PrivateKey
|
||||
certRes.Certificate = certStore.AcmeCert.Certificate
|
||||
certRes := currentCert.CertificateResource
|
||||
|
||||
newCert, err := c.client.RenewCertificate(certRes, true)
|
||||
if err != nil {
|
||||
@@ -148,98 +177,125 @@ func (c *Client) Renew(domains []string) (*AcmeCertificate, error) {
|
||||
}
|
||||
|
||||
expirydate, _ := lego.GetPEMCertExpiration(newCert.Certificate)
|
||||
serial, _ := getPEMCertSerialNo(newCert.Certificate)
|
||||
serialnum, _ := getPEMCertSerialNo(newCert.Certificate)
|
||||
|
||||
acmeCert := AcmeCertificate{
|
||||
newCert.PrivateKey,
|
||||
newCert.Certificate,
|
||||
expirydate,
|
||||
serial,
|
||||
CertificateResource: newCert,
|
||||
ExpiryDate: expirydate,
|
||||
SerialNumber: serialnum,
|
||||
}
|
||||
|
||||
saveCertStore(newCert, acmeCert)
|
||||
c.saveCertificate(acmeCert)
|
||||
return &acmeCert, nil
|
||||
}
|
||||
|
||||
// GetStoredCert returns a locally stored certificate for the given domains
|
||||
func (c *Client) GetStoredCert(domains []string) (bool, *AcmeCertificate) {
|
||||
// GetStoredCertificate returns a locally stored certificate for the given domains
|
||||
func (c *Client) GetStoredCertificate(domains []string) (bool, *AcmeCertificate) {
|
||||
domain := domains[0]
|
||||
if !haveCertStore(domain) {
|
||||
if !c.haveCertificate(domain) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
certStore, err := loadCertStore(domain)
|
||||
acmeCert, err := c.loadCertificate(domain)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
// Don't fail here. Try to create a new certificate instead.
|
||||
logrus.Error(err.Error())
|
||||
return false, nil
|
||||
}
|
||||
return true, &certStore.AcmeCert
|
||||
return true, &acmeCert
|
||||
}
|
||||
|
||||
// EnableDebugLogging enables logging in the upstream lego library
|
||||
func (c *Client) EnableDebugLogging() {
|
||||
lego.Logger = log.New(os.Stdout, "", 0)
|
||||
}
|
||||
|
||||
func haveCertStore(domain string) bool {
|
||||
path := certStorePath(domain)
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
func (c *Client) haveCertificate(domain string) bool {
|
||||
certPath := c.CertPath(domain)
|
||||
if _, err := os.Stat(path.Join(certPath, "metadata.json")); err != nil {
|
||||
logrus.Debugf("No existing acme certificate resource found for '%s'", domain)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func loadCertStore(domain string) (*acmeCertificateStore, error) {
|
||||
file := certStorePath(domain)
|
||||
certStoreBytes, err := ioutil.ReadFile(file)
|
||||
func (c *Client) loadCertificate(domain string) (AcmeCertificate, error) {
|
||||
var acmeCert AcmeCertificate
|
||||
certPath := c.CertPath(domain)
|
||||
|
||||
logrus.Debugf("Loading acme certificate resource for '%s' from '%s'", domain, certPath)
|
||||
|
||||
certIn := path.Join(certPath, "fullchain.pem")
|
||||
privIn := path.Join(certPath, "privkey.pem")
|
||||
metaIn := path.Join(certPath, "metadata.json")
|
||||
|
||||
certBytes, err := ioutil.ReadFile(certIn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error loading certificate store for %s: %v", domain, err)
|
||||
return acmeCert, fmt.Errorf("Failed to load certificate for domain '%s': %s", domain, err.Error())
|
||||
}
|
||||
|
||||
var certStore acmeCertificateStore
|
||||
err = json.Unmarshal(certStoreBytes, &certStore)
|
||||
metaBytes, err := ioutil.ReadFile(metaIn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing certificate store for %s: %v", domain, err)
|
||||
return acmeCert, fmt.Errorf("Failed to load meta data for domain '%s': %s", domain, err.Error())
|
||||
}
|
||||
|
||||
return &certStore, nil
|
||||
keyBytes, err := ioutil.ReadFile(privIn)
|
||||
if err != nil {
|
||||
return acmeCert, fmt.Errorf("Failed to load private key for domain '%s': %s", domain, err.Error())
|
||||
}
|
||||
|
||||
err = json.Unmarshal(metaBytes, &acmeCert)
|
||||
if err != nil {
|
||||
return acmeCert, fmt.Errorf("Failed to unmarshal meta data for domain '%s': %s", domain, err.Error())
|
||||
}
|
||||
|
||||
acmeCert.PrivateKey = keyBytes
|
||||
acmeCert.Certificate = certBytes
|
||||
|
||||
return acmeCert, nil
|
||||
}
|
||||
|
||||
func saveCertStore(certRes lego.CertificateResource, acmeCert AcmeCertificate) {
|
||||
out := certStorePath(certRes.Domain)
|
||||
func (c *Client) saveCertificate(acmeCert AcmeCertificate) {
|
||||
certPath := c.CertPath(acmeCert.Domain)
|
||||
maybeCreatePath(certPath)
|
||||
|
||||
certStore := acmeCertificateStore{
|
||||
CertRes: certRes,
|
||||
AcmeCert: acmeCert,
|
||||
logrus.Debugf("Saving acme certificate resource for '%s' to '%s'", acmeCert.Domain, certPath)
|
||||
|
||||
certOut := path.Join(certPath, "fullchain.pem")
|
||||
privOut := path.Join(certPath, "privkey.pem")
|
||||
metaOut := path.Join(certPath, "metadata.json")
|
||||
|
||||
err := ioutil.WriteFile(certOut, acmeCert.Certificate, 0600)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed to save certificate for domain %s\n\t%s", acmeCert.Domain, err.Error())
|
||||
}
|
||||
|
||||
jsonBytes, err := json.MarshalIndent(certStore, "", "\t")
|
||||
err = ioutil.WriteFile(privOut, acmeCert.PrivateKey, 0600)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Unable to marshal certificate store for domain %s: %s", certRes.Domain, err.Error())
|
||||
logrus.Fatalf("Failed to save private key for domain %s\n\t%s", acmeCert.Domain, err.Error())
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(out, jsonBytes, 0600)
|
||||
jsonBytes, err := json.MarshalIndent(acmeCert, "", "\t")
|
||||
if err != nil {
|
||||
logrus.Fatalf("Unable to save certificate store for domain %s: %s", certRes.Domain, err.Error())
|
||||
logrus.Fatalf("Failed to marshal meta data for domain %s\n\t%s", acmeCert.Domain, err.Error())
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(metaOut, jsonBytes, 0600)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed to save meta data for domain %s\n\t%s", acmeCert.Domain, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func configPath() string {
|
||||
path := path.Join(DATA_DIR, ".acme")
|
||||
if err := checkFolder(path); err != nil {
|
||||
logrus.Fatalf("Could not check/create config directory: %v", err)
|
||||
}
|
||||
func (c *Client) ConfigPath() string {
|
||||
path := path.Join(ConfigDir, strings.ToLower(string(c.apiVersion)))
|
||||
maybeCreatePath(path)
|
||||
return path
|
||||
}
|
||||
|
||||
func certStorePath(domain string) string {
|
||||
path := path.Join(configPath(), domain+".json")
|
||||
return path
|
||||
func (c *Client) CertPath(domain string) string {
|
||||
return path.Join(c.ConfigPath(), "certificates", domain)
|
||||
}
|
||||
|
||||
func checkFolder(path string) error {
|
||||
func maybeCreatePath(path string) {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return os.MkdirAll(path, 0700)
|
||||
err = os.MkdirAll(path, 0700)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Error creating path: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -25,9 +25,8 @@ type ProviderOpts struct {
|
||||
DoAccessToken string
|
||||
|
||||
// AWS Route 53 credentials
|
||||
AwsAccessKey string
|
||||
AwsSecretKey string
|
||||
AwsRegionName string
|
||||
AwsAccessKey string
|
||||
AwsSecretKey string
|
||||
|
||||
// DNSimple credentials
|
||||
DNSimpleEmail string
|
||||
@@ -105,11 +104,8 @@ func makeRoute53Provider(opts ProviderOpts) (lego.ChallengeProvider, error) {
|
||||
if len(opts.AwsSecretKey) == 0 {
|
||||
return nil, fmt.Errorf("AWS secret key is not set")
|
||||
}
|
||||
if len(opts.AwsRegionName) == 0 {
|
||||
return nil, fmt.Errorf("AWS region name is not set")
|
||||
}
|
||||
|
||||
os.Setenv("AWS_REGION", opts.AwsRegionName)
|
||||
os.Setenv("AWS_REGION", "us-east-1")
|
||||
os.Setenv("AWS_ACCESS_KEY_ID", opts.AwsAccessKey)
|
||||
os.Setenv("AWS_SECRET_ACCESS_KEY", opts.AwsSecretKey)
|
||||
|
||||
|
||||
+44
-36
@@ -21,40 +21,38 @@ func (c *Context) Run() {
|
||||
}
|
||||
|
||||
func (c *Context) startup() {
|
||||
var haveLocal bool
|
||||
var haveRemote bool
|
||||
|
||||
ok, acmeCert := c.Acme.GetStoredCert(c.Domains)
|
||||
var haveLocal, haveRemote bool
|
||||
ok, acmeCert := c.Acme.GetStoredCertificate(c.Domains)
|
||||
if ok {
|
||||
haveLocal = true
|
||||
c.ExpiryDate = acmeCert.ExpiryDate
|
||||
logrus.Info("Found local store for certificate")
|
||||
logrus.Infof("Found locally stored certificate for '%s'", strings.Join(c.Domains, " | "))
|
||||
}
|
||||
|
||||
rancherCert, err := c.Rancher.FindCertByName(c.RancherCertName)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed to lookup Rancher certificates: %v", err)
|
||||
logrus.Fatalf("Error looking up Rancher certificates: %v", err)
|
||||
}
|
||||
|
||||
if rancherCert != nil {
|
||||
haveRemote = true
|
||||
c.RancherCertId = rancherCert.Id
|
||||
logrus.Infof("Found existing Rancher certificate '%s'", rancherCert.Name)
|
||||
}
|
||||
|
||||
if haveLocal && haveRemote {
|
||||
if rancherCert.SerialNumber != acmeCert.SerialNumber {
|
||||
logrus.Fatalf("Cannot manage existing Rancher certificate %s: Serial number not matching local store", rancherCert.Name)
|
||||
if rancherCert.SerialNumber == acmeCert.SerialNumber {
|
||||
logrus.Infof("Managing renewal of Rancher certificate '%s'", rancherCert.Name)
|
||||
return
|
||||
}
|
||||
logrus.Infof("Managing existing Rancher certificate: %s", rancherCert.Name)
|
||||
logrus.Infof("Certificate serial number mismatch. Overwriting Rancher certificate '%s'", rancherCert.Name)
|
||||
c.updateRancherCert(acmeCert.PrivateKey, acmeCert.Certificate)
|
||||
return
|
||||
}
|
||||
|
||||
if !haveLocal && haveRemote {
|
||||
logrus.Fatalf("Cannot manage existing Rancher certificate %s: Not in local store", rancherCert.Name)
|
||||
}
|
||||
|
||||
if haveLocal && !haveRemote {
|
||||
c.addCertToRancher(acmeCert.PrivateKey, acmeCert.Certificate)
|
||||
logrus.Infof("Adding Rancher certificate '%s'", rancherCert.Name)
|
||||
c.addRancherCert(acmeCert.PrivateKey, acmeCert.Certificate)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -63,29 +61,48 @@ func (c *Context) startup() {
|
||||
acmeCert, failures := c.Acme.Issue(c.Domains)
|
||||
if len(failures) > 0 {
|
||||
for k, v := range failures {
|
||||
logrus.Errorf("[%s] Failed to obtain certificate: %s", k, v.Error())
|
||||
logrus.Errorf("[%s] Error obtaining certificate: %s", k, v.Error())
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logrus.Infof("Successfully obtained certificate")
|
||||
logrus.Infof("Successfully obtained SSL certificate")
|
||||
|
||||
c.ExpiryDate = acmeCert.ExpiryDate
|
||||
|
||||
c.addCertToRancher(acmeCert.PrivateKey, acmeCert.Certificate)
|
||||
if haveRemote {
|
||||
logrus.Infof("Overwriting Rancher certificate '%s'", rancherCert.Name)
|
||||
c.updateRancherCert(acmeCert.PrivateKey, acmeCert.Certificate)
|
||||
return
|
||||
}
|
||||
|
||||
c.addRancherCert(acmeCert.PrivateKey, acmeCert.Certificate)
|
||||
}
|
||||
|
||||
func (c *Context) addCertToRancher(privateKey, cert []byte) {
|
||||
rancherCert, err := c.Rancher.AddCertificate(c.RancherCertName, DESCRIPTION, privateKey, cert)
|
||||
func (c *Context) addRancherCert(privateKey, cert []byte) {
|
||||
rancherCert, err := c.Rancher.AddCertificate(c.RancherCertName, CERT_DESCRIPTION, privateKey, cert)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed to add Rancher certificate resource: %v", err)
|
||||
logrus.Fatalf("Failed to add Rancher certificate '%s': %v", c.RancherCertName, err)
|
||||
}
|
||||
c.RancherCertId = rancherCert.Id
|
||||
logrus.Infof("Added Rancher certificate resource: %s", c.RancherCertName)
|
||||
logrus.Infof("Added Rancher certificate '%s'", c.RancherCertName)
|
||||
}
|
||||
|
||||
func (c *Context) updateRancherCert(privateKey, cert []byte) {
|
||||
err := c.Rancher.UpdateCertificate(c.RancherCertId, CERT_DESCRIPTION, privateKey, cert)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed to update Rancher certificate '%s': %v", c.RancherCertName, err)
|
||||
}
|
||||
logrus.Infof("Updated Rancher certificate '%s'", c.RancherCertName)
|
||||
|
||||
err = c.Rancher.UpgradeLoadBalancers(c.RancherCertId)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Error upgrading load balancers: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) renew() {
|
||||
logrus.Infof("Trying to renew certificate for %s", strings.Join(c.Domains, " | "))
|
||||
logrus.Infof("Trying to renew certificate for '%s'", strings.Join(c.Domains, " | "))
|
||||
|
||||
acmeCert, err := c.Acme.Renew(c.Domains)
|
||||
if err != nil {
|
||||
@@ -95,17 +112,7 @@ func (c *Context) renew() {
|
||||
logrus.Infof("Successfully renewed certificate")
|
||||
|
||||
c.ExpiryDate = acmeCert.ExpiryDate
|
||||
err = c.Rancher.UpdateCertificate(c.RancherCertId, acmeCert.Certificate)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed to update Rancher certificate resource: %v", err)
|
||||
}
|
||||
|
||||
logrus.Infof("Updated Rancher certificate resource %s", c.RancherCertName)
|
||||
|
||||
err = c.Rancher.UpgradeLoadBalancers(c.RancherCertId)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Error upgrading load balancers: %v", err)
|
||||
}
|
||||
c.updateRancherCert(acmeCert.PrivateKey, acmeCert.Certificate)
|
||||
}
|
||||
|
||||
func (c *Context) timer() <-chan time.Time {
|
||||
@@ -116,13 +123,14 @@ func (c *Context) timer() <-chan time.Time {
|
||||
left = 10 * time.Second
|
||||
}
|
||||
|
||||
// Test mode
|
||||
logrus.Infof("Next certificate renewal scheduled for %s", next.Format("2006/01/02 15:04 MST"))
|
||||
|
||||
// Debug option set to true enables test mode
|
||||
if c.Debug {
|
||||
logrus.Debug("Test mode enabled: Certificate renewal in 120 seconds")
|
||||
logrus.Debug("Test mode enabled: certificate will be renewed in 120 seconds")
|
||||
left = 120 * time.Second
|
||||
}
|
||||
|
||||
logrus.Infof("Next certificate renewal scheduled for %s", next.Format("2006/01/02 15:04 MST"))
|
||||
return time.After(left)
|
||||
}
|
||||
|
||||
|
||||
+11
-8
@@ -24,7 +24,7 @@ func (r *Client) AddCertificate(name, descr string, privateKey, cert []byte) (*r
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logrus.Debugf("Added certificate: %s", rancherCert.Name)
|
||||
logrus.Debugf("Waiting for added certificate '%s' to become active", rancherCert.Name)
|
||||
|
||||
if err := r.WaitCertificate(rancherCert); err != nil {
|
||||
return nil, err
|
||||
@@ -34,28 +34,31 @@ func (r *Client) AddCertificate(name, descr string, privateKey, cert []byte) (*r
|
||||
}
|
||||
|
||||
// UpdateCertificate updates an existing certificate resource using the given PEM encoded certificate
|
||||
func (r *Client) UpdateCertificate(certId string, cert []byte) error {
|
||||
func (r *Client) UpdateCertificate(certId, descr string, privateKey, cert []byte) error {
|
||||
certString := string(cert[:])
|
||||
keyString := string(privateKey[:])
|
||||
rancherCert, err := r.client.Certificate.ById(certId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rancherCert, err = r.client.Certificate.Update(rancherCert, &rancherClient.Certificate{
|
||||
Cert: certString,
|
||||
Description: descr,
|
||||
Cert: certString,
|
||||
Key: keyString,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Debugf("Updated certificate %s", rancherCert.Name)
|
||||
logrus.Debugf("Waiting for updated certificate '%s' to become active", rancherCert.Name)
|
||||
|
||||
return r.WaitCertificate(rancherCert)
|
||||
}
|
||||
|
||||
// FindCertByName retrieves an existing certificate
|
||||
func (r *Client) FindCertByName(name string) (*rancherClient.Certificate, error) {
|
||||
logrus.Debugf("Looking up certificate by name %s", name)
|
||||
logrus.Debugf("Looking up Rancher certificate by name: '%s'", name)
|
||||
|
||||
certificates, err := r.client.Certificate.List(&rancherClient.ListOpts{
|
||||
Filters: map[string]interface{}{
|
||||
@@ -72,7 +75,7 @@ func (r *Client) FindCertByName(name string) (*rancherClient.Certificate, error)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
logrus.Debugf("Found existing certificate %s", name)
|
||||
logrus.Debugf("Found existing Rancher certificate by name: '%s'", name)
|
||||
return &certificates.Data[0], nil
|
||||
}
|
||||
|
||||
@@ -84,9 +87,9 @@ func (r *Client) GetCertById(certId string) (*rancherClient.Certificate, error)
|
||||
}
|
||||
|
||||
if rancherCert == nil {
|
||||
return nil, fmt.Errorf("Certificate with Id %s does not exist.", certId)
|
||||
return nil, fmt.Errorf("Rancher certificate with Id '%s' does not exist", certId)
|
||||
}
|
||||
|
||||
logrus.Debugf("Found certificate %s by Id %s", rancherCert.Name, certId)
|
||||
logrus.Debugf("Got Rancher certificate '%s' by Id '%s'", rancherCert.Name, certId)
|
||||
return rancherCert, nil
|
||||
}
|
||||
|
||||
+11
-9
@@ -13,22 +13,22 @@ func (r *Client) UpgradeLoadBalancers(certId string) error {
|
||||
}
|
||||
|
||||
if len(balancers) == 0 {
|
||||
logrus.Info("Certificate is not being used by any load balancers")
|
||||
logrus.Info("Certificate is not being used by any load balancer")
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, id := range balancers {
|
||||
lb, err := r.client.LoadBalancerService.ById(id)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to acquire load balancer by id %s: %v", id, err)
|
||||
logrus.Errorf("Failed to get load balancer by id '%s': %v", id, err)
|
||||
continue
|
||||
}
|
||||
logrus.Infof("Upgrading load balancer %s...", lb.Name)
|
||||
|
||||
err = r.upgrade(lb)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to upgrade load balancer %s: %v", lb.Name, err)
|
||||
logrus.Errorf("Failed to upgrade load balancer '%s': %v", lb.Name, err)
|
||||
} else {
|
||||
logrus.Infof("Successfully upgraded load balancer %s with renewed certificate", lb.Name)
|
||||
logrus.Infof("Upgraded load balancer '%s' with renewed certificate", lb.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ func (r *Client) upgrade(lb *rancherClient.LoadBalancerService) error {
|
||||
}
|
||||
upgrade.ToServiceStrategy = &rancherClient.ToServiceUpgradeStrategy{}
|
||||
|
||||
logrus.Debugf("Upgrading load balancer '%s'", lb.Name)
|
||||
|
||||
service, err := r.client.LoadBalancerService.ActionUpgrade(lb, upgrade)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -50,11 +52,11 @@ func (r *Client) upgrade(lb *rancherClient.LoadBalancerService) error {
|
||||
|
||||
err = r.WaitService(service)
|
||||
if err != nil {
|
||||
logrus.Warnf("Upgrade check: %v", err)
|
||||
logrus.Warnf(err.Error())
|
||||
}
|
||||
|
||||
if service.State == "upgraded" {
|
||||
logrus.Debugf("Finishing upgrade of load balancer: %s", lb.Name)
|
||||
logrus.Debugf("Finishing upgrade for load balancer '%s'", lb.Name)
|
||||
|
||||
service, err = r.client.Service.ActionFinishupgrade(service)
|
||||
if err != nil {
|
||||
@@ -62,7 +64,7 @@ func (r *Client) upgrade(lb *rancherClient.LoadBalancerService) error {
|
||||
}
|
||||
err = r.WaitService(service)
|
||||
if err != nil {
|
||||
logrus.Warnf("Upgrade check: %v", err)
|
||||
logrus.Warnf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +74,7 @@ func (r *Client) upgrade(lb *rancherClient.LoadBalancerService) error {
|
||||
func (r *Client) findLoadBalancerServicesByCert(certId string) ([]string, error) {
|
||||
var results []string
|
||||
|
||||
logrus.Debugf("Looking up load balancers matching certificate id %s", certId)
|
||||
logrus.Debugf("Looking up load balancers matching certificate id '%s'", certId)
|
||||
|
||||
balancers, err := r.client.LoadBalancerService.List(&rancherClient.ListOpts{
|
||||
Filters: map[string]interface{}{
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ func backoff(maxDuration time.Duration, timeoutMessage string, f func() (bool, e
|
||||
|
||||
// WaitFor waits for a resource to reach a certain state.
|
||||
func (r *Client) WaitFor(resource *rancherClient.Resource, output interface{}, transitioning func() string) error {
|
||||
return backoff(2*time.Minute, fmt.Sprintf("Time out waiting for %s:%s to transition", resource.Type, resource.Id), func() (bool, error) {
|
||||
return backoff(2*time.Minute, fmt.Sprintf("Time out waiting for %s:%s to become active", resource.Type, resource.Id), func() (bool, error) {
|
||||
err := r.client.Reload(resource, output)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
||||
Reference in New Issue
Block a user