Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
caddy
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Łukasz Nowak
caddy
Commits
c626774d
Commit
c626774d
authored
Oct 20, 2015
by
xenolf
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
First, raw renewal implementation. Pretty basic :D
parent
cd0b47d0
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
150 additions
and
1 deletion
+150
-1
config/letsencrypt/letsencrypt.go
config/letsencrypt/letsencrypt.go
+145
-1
config/letsencrypt/storage.go
config/letsencrypt/storage.go
+5
-0
No files found.
config/letsencrypt/letsencrypt.go
View file @
c626774d
...
@@ -7,9 +7,11 @@ import (
...
@@ -7,9 +7,11 @@ import (
"encoding/json"
"encoding/json"
"errors"
"errors"
"io/ioutil"
"io/ioutil"
"log"
"net/http"
"net/http"
"os"
"os"
"strings"
"strings"
"time"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/redirect"
"github.com/mholt/caddy/middleware/redirect"
...
@@ -38,6 +40,8 @@ func Activate(configs []server.Config) ([]server.Config, error) {
...
@@ -38,6 +40,8 @@ func Activate(configs []server.Config) ([]server.Config, error) {
configs
=
autoConfigure
(
&
configs
[
i
],
configs
)
configs
=
autoConfigure
(
&
configs
[
i
],
configs
)
}
}
}
}
// Handle cert renewal on Startup
processCertificateRenewal
(
configs
)
// Group configs by LE email address; this will help us
// Group configs by LE email address; this will help us
// reduce round-trips when getting the certs.
// reduce round-trips when getting the certs.
...
@@ -73,6 +77,8 @@ func Activate(configs []server.Config) ([]server.Config, error) {
...
@@ -73,6 +77,8 @@ func Activate(configs []server.Config) ([]server.Config, error) {
}
}
}
}
go
renewalFunc
(
configs
)
return
configs
,
nil
return
configs
,
nil
}
}
...
@@ -211,7 +217,7 @@ func saveCertsAndKeys(certificates []acme.CertificateResource) error {
...
@@ -211,7 +217,7 @@ func saveCertsAndKeys(certificates []acme.CertificateResource) error {
}
}
// Save cert metadata
// Save cert metadata
jsonBytes
,
err
:=
json
.
MarshalIndent
(
&
CertificateMeta
{
URL
:
cert
.
CertURL
,
Domain
:
cert
.
Domain
}
,
""
,
"
\t
"
)
jsonBytes
,
err
:=
json
.
MarshalIndent
(
&
cert
,
""
,
"
\t
"
)
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
...
@@ -278,6 +284,141 @@ func redirPlaintextHost(cfg server.Config) server.Config {
...
@@ -278,6 +284,141 @@ func redirPlaintextHost(cfg server.Config) server.Config {
}
}
}
}
func
renewalFunc
(
configs
[]
server
.
Config
)
{
nextRun
,
err
:=
processCertificateRenewal
(
configs
)
if
err
!=
nil
{
log
.
Printf
(
"[ERROR] Could not start renewal routine. %v"
,
err
)
return
}
for
{
timer
:=
time
.
NewTimer
(
time
.
Duration
(
nextRun
)
*
time
.
Hour
)
<-
timer
.
C
nextRun
,
err
=
processCertificateRenewal
(
configs
)
if
err
!=
nil
{
log
.
Printf
(
"[ERROR] Renewal routing stopped. %v"
,
err
)
return
}
}
}
// checkCertificateRenewal loops through all configured
// sites and looks for certificates to renew. Nothing is mutated
// through this function. The changes happen directly on disk.
func
processCertificateRenewal
(
configs
[]
server
.
Config
)
(
int
,
error
)
{
log
.
Print
(
"[INFO] Processing certificate renewals..."
)
// Check if we should run. If not, get out of here.
next
,
err
:=
getNextRenewalShedule
()
if
err
!=
nil
{
return
0
,
err
}
if
next
>
0
{
return
next
,
nil
}
// We are executing. Write the current timestamp into the file.
err
=
ioutil
.
WriteFile
(
storage
.
RenewTimerFile
(),
[]
byte
(
time
.
Now
()
.
UTC
()
.
Format
(
time
.
RFC3339
)),
0600
)
if
err
!=
nil
{
return
0
,
err
}
next
=
renewTimer
for
_
,
cfg
:=
range
configs
{
// Check if this entry is TLS enabled and managed by LE
if
!
cfg
.
TLS
.
Enabled
||
!
existingCertAndKey
(
cfg
.
Host
)
{
continue
}
// Read the certificate and get the NotAfter time.
certBytes
,
err
:=
ioutil
.
ReadFile
(
storage
.
SiteCertFile
(
cfg
.
Host
))
if
err
!=
nil
{
return
0
,
err
}
expTime
,
err
:=
acme
.
GetPEMCertExpiration
(
certBytes
)
if
err
!=
nil
{
return
0
,
err
}
// The time returned from the certificate is always in UTC.
// So calculate the time left with local time as UTC.
// Directly convert it to days for the following checks.
daysLeft
:=
int
(
expTime
.
Sub
(
time
.
Now
()
.
UTC
())
.
Hours
()
/
24
)
// Renew on two or less days remaining.
if
daysLeft
<=
2
{
log
.
Printf
(
"[WARN] There are %d days left on the certificate of %s. Trying to renew now."
,
daysLeft
,
cfg
.
Host
)
client
,
err
:=
newClient
(
getEmail
(
cfg
))
if
err
!=
nil
{
return
0
,
err
}
// Read metadata
metaBytes
,
err
:=
ioutil
.
ReadFile
(
storage
.
SiteMetaFile
(
cfg
.
Host
))
if
err
!=
nil
{
return
0
,
err
}
privBytes
,
err
:=
ioutil
.
ReadFile
(
storage
.
SiteKeyFile
(
cfg
.
Host
))
if
err
!=
nil
{
return
0
,
err
}
var
certMeta
acme
.
CertificateResource
err
=
json
.
Unmarshal
(
metaBytes
,
&
certMeta
)
certMeta
.
Certificate
=
certBytes
certMeta
.
PrivateKey
=
privBytes
// Renew certificate.
// TODO: revokeOld should be an option in the caddyfile
newCertMeta
,
err
:=
client
.
RenewCertificate
(
certMeta
,
true
)
if
err
!=
nil
{
return
0
,
err
}
saveCertsAndKeys
([]
acme
.
CertificateResource
{
newCertMeta
})
}
// Warn on 14 days remaining
if
daysLeft
<=
14
{
log
.
Printf
(
"[WARN] There are %d days left on the certificate of %s. Will renew on two days left.
\n
"
,
daysLeft
,
cfg
.
Host
)
}
}
return
next
,
nil
}
// getNextRenewalShedule calculates the offset in hours the renew process should
// run from the current time. If the file the time is in does not exists, the
// function returns zero to trigger a renew asap.
func
getNextRenewalShedule
()
(
int
,
error
)
{
// Check if the file exists. If it does not, return 0 to indicate immediate processing.
if
_
,
err
:=
os
.
Stat
(
storage
.
RenewTimerFile
());
os
.
IsNotExist
(
err
)
{
return
0
,
nil
}
renewTimeBytes
,
err
:=
ioutil
.
ReadFile
(
storage
.
RenewTimerFile
())
if
err
!=
nil
{
return
0
,
err
}
renewalTime
,
err
:=
time
.
Parse
(
time
.
RFC3339
,
string
(
renewTimeBytes
))
if
err
!=
nil
{
return
0
,
err
}
// The time read from the file was equal or more then 24 hours in the past,
// write the current time to the file and return true.
hoursSinceRenew
:=
int
(
time
.
Now
()
.
UTC
()
.
Sub
(
renewalTime
)
.
Hours
())
if
hoursSinceRenew
>=
renewTimer
{
return
0
,
nil
}
return
hoursSinceRenew
,
nil
}
var
(
var
(
// Let's Encrypt account email to use if none provided
// Let's Encrypt account email to use if none provided
DefaultEmail
string
DefaultEmail
string
...
@@ -294,6 +435,9 @@ const (
...
@@ -294,6 +435,9 @@ const (
// The port to expose to the CA server for Simple HTTP Challenge
// The port to expose to the CA server for Simple HTTP Challenge
exposePort
=
"5001"
exposePort
=
"5001"
// Renewal Timer - Check renewals every x hours.
renewTimer
=
24
)
)
// KeySize represents the length of a key in bits.
// KeySize represents the length of a key in bits.
...
...
config/letsencrypt/storage.go
View file @
c626774d
...
@@ -16,6 +16,11 @@ var storage = Storage(filepath.Join(app.DataFolder(), "letsencrypt"))
...
@@ -16,6 +16,11 @@ var storage = Storage(filepath.Join(app.DataFolder(), "letsencrypt"))
// forming file paths derived from it.
// forming file paths derived from it.
type
Storage
string
type
Storage
string
// RenewTimerFile returns the path to the file used for renewal timing.
func
(
s
Storage
)
RenewTimerFile
()
string
{
return
filepath
.
Join
(
string
(
s
),
"lastrenew"
)
}
// Sites gets the directory that stores site certificate and keys.
// Sites gets the directory that stores site certificate and keys.
func
(
s
Storage
)
Sites
()
string
{
func
(
s
Storage
)
Sites
()
string
{
return
filepath
.
Join
(
string
(
s
),
"sites"
)
return
filepath
.
Join
(
string
(
s
),
"sites"
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment