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
94532246
Commit
94532246
authored
Jan 08, 2016
by
Matthew Holt
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'letsencryptfix'
parents
a3f3bc67
fd176597
Changes
30
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
30 changed files
with
1044 additions
and
567 deletions
+1044
-567
caddy/caddy.go
caddy/caddy.go
+4
-2
caddy/caddyfile/json.go
caddy/caddyfile/json.go
+15
-10
caddy/caddyfile/json_test.go
caddy/caddyfile/json_test.go
+36
-1
caddy/config.go
caddy/config.go
+34
-54
caddy/config_test.go
caddy/config_test.go
+16
-4
caddy/letsencrypt/crypto_test.go
caddy/letsencrypt/crypto_test.go
+5
-5
caddy/letsencrypt/handler.go
caddy/letsencrypt/handler.go
+12
-19
caddy/letsencrypt/handler_test.go
caddy/letsencrypt/handler_test.go
+63
-0
caddy/letsencrypt/letsencrypt.go
caddy/letsencrypt/letsencrypt.go
+256
-278
caddy/letsencrypt/letsencrypt_test.go
caddy/letsencrypt/letsencrypt_test.go
+265
-5
caddy/letsencrypt/maintain.go
caddy/letsencrypt/maintain.go
+33
-21
caddy/letsencrypt/storage_test.go
caddy/letsencrypt/storage_test.go
+13
-13
caddy/letsencrypt/user.go
caddy/letsencrypt/user.go
+1
-1
caddy/letsencrypt/user_test.go
caddy/letsencrypt/user_test.go
+5
-0
caddy/parse/parse.go
caddy/parse/parse.go
+1
-1
caddy/parse/parsing.go
caddy/parse/parsing.go
+51
-32
caddy/parse/parsing_test.go
caddy/parse/parsing_test.go
+86
-75
caddy/restart.go
caddy/restart.go
+54
-1
caddy/setup/startupshutdown_test.go
caddy/setup/startupshutdown_test.go
+7
-6
caddy/setup/tls.go
caddy/setup/tls.go
+15
-15
caddy/setup/tls_test.go
caddy/setup/tls_test.go
+34
-4
main.go
main.go
+2
-0
middleware/gzip/response_filter.go
middleware/gzip/response_filter.go
+2
-2
middleware/gzip/response_filter_test.go
middleware/gzip/response_filter_test.go
+1
-1
middleware/rewrite/condition.go
middleware/rewrite/condition.go
+1
-1
middleware/rewrite/rewrite.go
middleware/rewrite/rewrite.go
+7
-7
middleware/rewrite/to.go
middleware/rewrite/to.go
+1
-1
server/config.go
server/config.go
+9
-4
server/config_test.go
server/config_test.go
+2
-2
server/server.go
server/server.go
+13
-2
No files found.
caddy/caddy.go
View file @
94532246
// Package caddy implements the Caddy web server as a service.
// Package caddy implements the Caddy web server as a service
// in your own Go programs.
//
// To use this package, follow a few simple steps:
//
...
...
@@ -190,7 +191,8 @@ func startServers(groupings bindingGroup) error {
if
err
!=
nil
{
return
err
}
s
.
HTTP2
=
HTTP2
// TODO: This setting is temporary
s
.
HTTP2
=
HTTP2
// TODO: This setting is temporary
s
.
ReqCallback
=
letsencrypt
.
RequestCallback
// ensures we can solve ACME challenges while running
var
ln
server
.
ListenerFile
if
IsRestart
()
{
...
...
caddy/caddyfile/json.go
View file @
94532246
...
...
@@ -28,7 +28,7 @@ func ToJSON(caddyfile []byte) ([]byte, error) {
// Fill up host list
for
_
,
host
:=
range
sb
.
HostList
()
{
block
.
Hosts
=
append
(
block
.
Hosts
,
st
rings
.
TrimSuffix
(
host
,
":"
))
block
.
Hosts
=
append
(
block
.
Hosts
,
st
andardizeScheme
(
host
))
}
// Extract directives deterministically by sorting them
...
...
@@ -62,7 +62,6 @@ func ToJSON(caddyfile []byte) ([]byte, error) {
// but only one line at a time, to be used at the top-level of
// a server block only (where the first token on each line is a
// directive) - not to be used at any other nesting level.
// goes to end of line
func
constructLine
(
d
*
parse
.
Dispenser
)
[]
interface
{}
{
var
args
[]
interface
{}
...
...
@@ -80,8 +79,8 @@ func constructLine(d *parse.Dispenser) []interface{} {
}
// constructBlock recursively processes tokens into a
// JSON-encodable structure.
//
goes to end of block
// JSON-encodable structure.
To be used in a directive's
//
block. Goes to end of block.
func
constructBlock
(
d
*
parse
.
Dispenser
)
[][]
interface
{}
{
block
:=
[][]
interface
{}{}
...
...
@@ -110,15 +109,10 @@ func FromJSON(jsonBytes []byte) ([]byte, error) {
result
+=
"
\n\n
"
}
for
i
,
host
:=
range
sb
.
Hosts
{
if
hostname
,
port
,
err
:=
net
.
SplitHostPort
(
host
);
err
==
nil
{
if
port
==
"http"
||
port
==
"https"
{
host
=
port
+
"://"
+
hostname
}
}
if
i
>
0
{
result
+=
", "
}
result
+=
st
rings
.
TrimSuffix
(
host
,
":"
)
result
+=
st
andardizeScheme
(
host
)
}
result
+=
jsonToText
(
sb
.
Body
,
1
)
}
...
...
@@ -170,6 +164,17 @@ func jsonToText(scope interface{}, depth int) string {
return
result
}
// standardizeScheme turns an address like host:https into https://host,
// or "host:" into "host".
func
standardizeScheme
(
addr
string
)
string
{
if
hostname
,
port
,
err
:=
net
.
SplitHostPort
(
addr
);
err
==
nil
{
if
port
==
"http"
||
port
==
"https"
{
addr
=
port
+
"://"
+
hostname
}
}
return
strings
.
TrimSuffix
(
addr
,
":"
)
}
// Caddyfile encapsulates a slice of ServerBlocks.
type
Caddyfile
[]
ServerBlock
...
...
caddy/caddyfile/json_test.go
View file @
94532246
...
...
@@ -63,7 +63,7 @@ baz"
{
// 8
caddyfile
:
`http://host, https://host {
}`
,
json
:
`[{"hosts":["h
ost:http","host:https
"],"body":[]}]`
,
// hosts in JSON are always host:port format (if port is specified), for consistency
json
:
`[{"hosts":["h
ttp://host","https://host
"],"body":[]}]`
,
// hosts in JSON are always host:port format (if port is specified), for consistency
},
{
// 9
caddyfile
:
`host {
...
...
@@ -124,3 +124,38 @@ func TestFromJSON(t *testing.T) {
}
}
}
func
TestStandardizeAddress
(
t
*
testing
.
T
)
{
// host:https should be converted to https://host
output
,
err
:=
ToJSON
([]
byte
(
`host:https`
))
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
expected
,
actual
:=
`[{"hosts":["https://host"],"body":[]}]`
,
string
(
output
);
expected
!=
actual
{
t
.
Errorf
(
"Expected:
\n
'%s'
\n
Actual:
\n
'%s'"
,
expected
,
actual
)
}
output
,
err
=
FromJSON
([]
byte
(
`[{"hosts":["https://host"],"body":[]}]`
))
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
expected
,
actual
:=
"https://host {
\n
}"
,
string
(
output
);
expected
!=
actual
{
t
.
Errorf
(
"Expected:
\n
'%s'
\n
Actual:
\n
'%s'"
,
expected
,
actual
)
}
// host: should be converted to just host
output
,
err
=
ToJSON
([]
byte
(
`host:`
))
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
expected
,
actual
:=
`[{"hosts":["host"],"body":[]}]`
,
string
(
output
);
expected
!=
actual
{
t
.
Errorf
(
"Expected:
\n
'%s'
\n
Actual:
\n
'%s'"
,
expected
,
actual
)
}
output
,
err
=
FromJSON
([]
byte
(
`[{"hosts":["host:"],"body":[]}]`
))
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
expected
,
actual
:=
"host {
\n
}"
,
string
(
output
);
expected
!=
actual
{
t
.
Errorf
(
"Expected:
\n
'%s'
\n
Actual:
\n
'%s'"
,
expected
,
actual
)
}
}
caddy/config.go
View file @
94532246
...
...
@@ -21,25 +21,22 @@ const (
DefaultConfigFile
=
"Caddyfile"
)
// loadConfigs reads input (named filename) and parses it, returning the
// server configurations in the order they appeared in the input. As part
// of this, it activates Let's Encrypt for the configs that are produced.
// Thus, the returned configs are already optimally configured optimally
// for HTTPS.
func
loadConfigs
(
filename
string
,
input
io
.
Reader
)
([]
server
.
Config
,
error
)
{
// loadConfigsUpToIncludingTLS loads the configs from input with name filename and returns them,
// the parsed server blocks, the index of the last directive it processed, and an error (if any).
func
loadConfigsUpToIncludingTLS
(
filename
string
,
input
io
.
Reader
)
([]
server
.
Config
,
[]
parse
.
ServerBlock
,
int
,
error
)
{
var
configs
[]
server
.
Config
// Each server block represents similar hosts/addresses, since they
// were grouped together in the Caddyfile.
serverBlocks
,
err
:=
parse
.
ServerBlocks
(
filename
,
input
,
true
)
if
err
!=
nil
{
return
nil
,
err
return
nil
,
nil
,
0
,
err
}
if
len
(
serverBlocks
)
==
0
{
newInput
:=
DefaultInput
()
serverBlocks
,
err
=
parse
.
ServerBlocks
(
newInput
.
Path
(),
bytes
.
NewReader
(
newInput
.
Body
()),
true
)
if
err
!=
nil
{
return
nil
,
err
return
nil
,
nil
,
0
,
err
}
}
...
...
@@ -56,6 +53,7 @@ func loadConfigs(filename string, input io.Reader) ([]server.Config, error) {
config
:=
server
.
Config
{
Host
:
addr
.
Host
,
Port
:
addr
.
Port
,
Scheme
:
addr
.
Scheme
,
Root
:
Root
,
Middleware
:
make
(
map
[
string
][]
middleware
.
Middleware
),
ConfigFile
:
filename
,
...
...
@@ -88,7 +86,7 @@ func loadConfigs(filename string, input io.Reader) ([]server.Config, error) {
// execute setup function and append middleware handler, if any
midware
,
err
:=
dir
.
setup
(
controller
)
if
err
!=
nil
{
return
nil
,
err
return
nil
,
nil
,
lastDirectiveIndex
,
err
}
if
midware
!=
nil
{
// TODO: For now, we only support the default path scope /
...
...
@@ -109,22 +107,31 @@ func loadConfigs(filename string, input io.Reader) ([]server.Config, error) {
}
}
return
configs
,
serverBlocks
,
lastDirectiveIndex
,
nil
}
// loadConfigs reads input (named filename) and parses it, returning the
// server configurations in the order they appeared in the input. As part
// of this, it activates Let's Encrypt for the configs that are produced.
// Thus, the returned configs are already optimally configured for HTTPS.
func
loadConfigs
(
filename
string
,
input
io
.
Reader
)
([]
server
.
Config
,
error
)
{
configs
,
serverBlocks
,
lastDirectiveIndex
,
err
:=
loadConfigsUpToIncludingTLS
(
filename
,
input
)
if
err
!=
nil
{
return
nil
,
err
}
// Now we have all the configs, but they have only been set up to the
// point of tls. We need to activate Let's Encrypt before setting up
// the rest of the middlewares so they have correct information regarding
// TLS configuration, if necessary. (this
call is append-only, so our
//
iteration
s below shouldn't be affected)
// TLS configuration, if necessary. (this
only appends, so our iterations
//
over server block
s below shouldn't be affected)
if
!
IsRestart
()
&&
!
Quiet
{
fmt
.
Print
(
"Activating privacy features..."
)
}
configs
,
err
=
letsencrypt
.
Activate
(
configs
)
if
err
!=
nil
{
if
!
Quiet
{
fmt
.
Println
()
}
return
nil
,
err
}
if
!
IsRestart
()
&&
!
Quiet
{
}
else
if
!
IsRestart
()
&&
!
Quiet
{
fmt
.
Println
(
" done."
)
}
...
...
@@ -277,44 +284,17 @@ func arrangeBindings(allConfigs []server.Config) (bindingGroup, error) {
// but execution may continue. The second error, if not nil, is a real
// problem and the server should not be started.
//
// This function handles edge cases gracefully. If a port name like
// "http" or "https" is unknown to the system, this function will
// change them to 80 or 443 respectively. If a hostname fails to
// resolve, that host can still be served but will be listening on
// the wildcard host instead. This function takes care of this for you.
// This function does not handle edge cases like port "http" or "https" if
// they are not known to the system. It does, however, serve on the wildcard
// host if resolving the address of the specific hostname fails.
func
resolveAddr
(
conf
server
.
Config
)
(
resolvAddr
*
net
.
TCPAddr
,
warnErr
,
fatalErr
error
)
{
bindHost
:=
conf
.
BindHost
// TODO: Do we even need the port? Maybe we just need to look up the host.
resolvAddr
,
warnErr
=
net
.
ResolveTCPAddr
(
"tcp"
,
net
.
JoinHostPort
(
bindHost
,
conf
.
Port
))
resolvAddr
,
warnErr
=
net
.
ResolveTCPAddr
(
"tcp"
,
net
.
JoinHostPort
(
conf
.
BindHost
,
conf
.
Port
))
if
warnErr
!=
nil
{
// Most likely the host lookup failed or the port is unknown
tryPort
:=
conf
.
Port
switch
errVal
:=
warnErr
.
(
type
)
{
case
*
net
.
AddrError
:
if
errVal
.
Err
==
"unknown port"
{
// some odd Linux machines don't support these port names; see issue #136
switch
conf
.
Port
{
case
"http"
:
tryPort
=
"80"
case
"https"
:
tryPort
=
"443"
}
}
resolvAddr
,
fatalErr
=
net
.
ResolveTCPAddr
(
"tcp"
,
net
.
JoinHostPort
(
bindHost
,
tryPort
))
if
fatalErr
!=
nil
{
return
}
default
:
// the hostname probably couldn't be resolved, just bind to wildcard then
resolvAddr
,
fatalErr
=
net
.
ResolveTCPAddr
(
"tcp"
,
net
.
JoinHostPort
(
"0.0.0.0"
,
tryPort
))
if
fatalErr
!=
nil
{
return
}
// the hostname probably couldn't be resolved, just bind to wildcard then
resolvAddr
,
fatalErr
=
net
.
ResolveTCPAddr
(
"tcp"
,
net
.
JoinHostPort
(
""
,
conf
.
Port
))
if
fatalErr
!=
nil
{
return
}
return
}
return
...
...
@@ -334,12 +314,12 @@ func validDirective(d string) bool {
// DefaultInput returns the default Caddyfile input
// to use when it is otherwise empty or missing.
// It uses the default host and port (depends on
// host, e.g. localhost is 2015, otherwise
https
) and
// host, e.g. localhost is 2015, otherwise
443
) and
// root.
func
DefaultInput
()
CaddyfileInput
{
port
:=
Port
if
letsencrypt
.
HostQualifies
(
Host
)
{
port
=
"
https
"
if
letsencrypt
.
HostQualifies
(
Host
)
&&
port
==
DefaultPort
{
port
=
"
443
"
}
return
CaddyfileInput
{
Contents
:
[]
byte
(
fmt
.
Sprintf
(
"%s:%s
\n
root %s"
,
Host
,
port
,
Root
)),
...
...
caddy/config_test.go
View file @
94532246
...
...
@@ -13,10 +13,10 @@ func TestDefaultInput(t *testing.T) {
t
.
Errorf
(
"Host=%s; Port=%s; Root=%s;
\n
EXPECTED: '%s'
\n
ACTUAL: '%s'"
,
Host
,
Port
,
Root
,
expected
,
actual
)
}
// next few tests simulate user providing -host
flag
// next few tests simulate user providing -host
and/or -port flags
Host
=
"not-localhost.com"
if
actual
,
expected
:=
string
(
DefaultInput
()
.
Body
()),
"not-localhost.com:
https
\n
root ."
;
actual
!=
expected
{
if
actual
,
expected
:=
string
(
DefaultInput
()
.
Body
()),
"not-localhost.com:
443
\n
root ."
;
actual
!=
expected
{
t
.
Errorf
(
"Host=%s; Port=%s; Root=%s;
\n
EXPECTED: '%s'
\n
ACTUAL: '%s'"
,
Host
,
Port
,
Root
,
expected
,
actual
)
}
...
...
@@ -29,6 +29,18 @@ func TestDefaultInput(t *testing.T) {
if
actual
,
expected
:=
string
(
DefaultInput
()
.
Body
()),
"127.0.1.1:2015
\n
root ."
;
actual
!=
expected
{
t
.
Errorf
(
"Host=%s; Port=%s; Root=%s;
\n
EXPECTED: '%s'
\n
ACTUAL: '%s'"
,
Host
,
Port
,
Root
,
expected
,
actual
)
}
Host
=
"not-localhost.com"
Port
=
"1234"
if
actual
,
expected
:=
string
(
DefaultInput
()
.
Body
()),
"not-localhost.com:1234
\n
root ."
;
actual
!=
expected
{
t
.
Errorf
(
"Host=%s; Port=%s; Root=%s;
\n
EXPECTED: '%s'
\n
ACTUAL: '%s'"
,
Host
,
Port
,
Root
,
expected
,
actual
)
}
Host
=
DefaultHost
Port
=
"1234"
if
actual
,
expected
:=
string
(
DefaultInput
()
.
Body
()),
":1234
\n
root ."
;
actual
!=
expected
{
t
.
Errorf
(
"Host=%s; Port=%s; Root=%s;
\n
EXPECTED: '%s'
\n
ACTUAL: '%s'"
,
Host
,
Port
,
Root
,
expected
,
actual
)
}
}
func
TestResolveAddr
(
t
*
testing
.
T
)
{
...
...
@@ -51,14 +63,14 @@ func TestResolveAddr(t *testing.T) {
{
server
.
Config
{
Host
:
"localhost"
,
Port
:
"80"
},
false
,
false
,
"<nil>"
,
80
},
{
server
.
Config
{
BindHost
:
"localhost"
,
Port
:
"1234"
},
false
,
false
,
"127.0.0.1"
,
1234
},
{
server
.
Config
{
BindHost
:
"127.0.0.1"
,
Port
:
"1234"
},
false
,
false
,
"127.0.0.1"
,
1234
},
{
server
.
Config
{
BindHost
:
"should-not-resolve"
,
Port
:
"1234"
},
true
,
false
,
"
0.0.0.0
"
,
1234
},
{
server
.
Config
{
BindHost
:
"should-not-resolve"
,
Port
:
"1234"
},
true
,
false
,
"
<nil>
"
,
1234
},
{
server
.
Config
{
BindHost
:
"localhost"
,
Port
:
"http"
},
false
,
false
,
"127.0.0.1"
,
80
},
{
server
.
Config
{
BindHost
:
"localhost"
,
Port
:
"https"
},
false
,
false
,
"127.0.0.1"
,
443
},
{
server
.
Config
{
BindHost
:
""
,
Port
:
"1234"
},
false
,
false
,
"<nil>"
,
1234
},
{
server
.
Config
{
BindHost
:
"localhost"
,
Port
:
"abcd"
},
false
,
true
,
""
,
0
},
{
server
.
Config
{
BindHost
:
"127.0.0.1"
,
Host
:
"should-not-be-used"
,
Port
:
"1234"
},
false
,
false
,
"127.0.0.1"
,
1234
},
{
server
.
Config
{
BindHost
:
"localhost"
,
Host
:
"should-not-be-used"
,
Port
:
"1234"
},
false
,
false
,
"127.0.0.1"
,
1234
},
{
server
.
Config
{
BindHost
:
"should-not-resolve"
,
Host
:
"localhost"
,
Port
:
"1234"
},
true
,
false
,
"
0.0.0.0
"
,
1234
},
{
server
.
Config
{
BindHost
:
"should-not-resolve"
,
Host
:
"localhost"
,
Port
:
"1234"
},
true
,
false
,
"
<nil>
"
,
1234
},
}
{
actualAddr
,
warnErr
,
fatalErr
:=
resolveAddr
(
test
.
config
)
...
...
caddy/letsencrypt/crypto_test.go
View file @
94532246
...
...
@@ -40,12 +40,12 @@ func TestSaveAndLoadRSAPrivateKey(t *testing.T) {
}
}
// rsaPrivateKeyBytes returns the bytes of DER-encoded key.
func
rsaPrivateKeyBytes
(
key
*
rsa
.
PrivateKey
)
[]
byte
{
return
x509
.
MarshalPKCS1PrivateKey
(
key
)
}
// rsaPrivateKeysSame compares the bytes of a and b and returns true if they are the same.
func
rsaPrivateKeysSame
(
a
,
b
*
rsa
.
PrivateKey
)
bool
{
return
bytes
.
Equal
(
rsaPrivateKeyBytes
(
a
),
rsaPrivateKeyBytes
(
b
))
}
// rsaPrivateKeyBytes returns the bytes of DER-encoded key.
func
rsaPrivateKeyBytes
(
key
*
rsa
.
PrivateKey
)
[]
byte
{
return
x509
.
MarshalPKCS1PrivateKey
(
key
)
}
caddy/letsencrypt/handler.go
View file @
94532246
...
...
@@ -2,30 +2,21 @@ package letsencrypt
import
(
"crypto/tls"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"github.com/mholt/caddy/middleware"
)
const
challengeBasePath
=
"/.well-known/acme-challenge"
// Handler is a Caddy middleware that can proxy ACME challenge
// requests to the real ACME client endpoint. This is necessary
// to renew certificates while the server is running.
type
Handler
struct
{
Next
middleware
.
Handler
//ChallengeActive int32 // (TODO) use sync/atomic to set/get this flag safely and efficiently
}
// ServeHTTP is basically a no-op unless an ACME challenge is active on this host
// and the request path matches the expected path exactly.
func
(
h
*
Handler
)
ServeHTTP
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
(
int
,
error
)
{
// Proxy challenge requests to ACME client
// TODO: Only do this if a challenge is active?
// RequestCallback proxies challenge requests to ACME client if the
// request path starts with challengeBasePath. It returns true if it
// handled the request and no more needs to be done; it returns false
// if this call was a no-op and the request still needs handling.
func
RequestCallback
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
bool
{
if
strings
.
HasPrefix
(
r
.
URL
.
Path
,
challengeBasePath
)
{
scheme
:=
"http"
if
r
.
TLS
!=
nil
{
...
...
@@ -37,9 +28,11 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error)
hostname
=
r
.
URL
.
Host
}
upstream
,
err
:=
url
.
Parse
(
scheme
+
"://"
+
hostname
+
":"
+
a
lternatePort
)
upstream
,
err
:=
url
.
Parse
(
scheme
+
"://"
+
hostname
+
":"
+
A
lternatePort
)
if
err
!=
nil
{
return
http
.
StatusInternalServerError
,
err
w
.
WriteHeader
(
http
.
StatusInternalServerError
)
log
.
Printf
(
"[ERROR] letsencrypt handler: %v"
,
err
)
return
true
}
proxy
:=
httputil
.
NewSingleHostReverseProxy
(
upstream
)
...
...
@@ -48,8 +41,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error)
}
proxy
.
ServeHTTP
(
w
,
r
)
return
0
,
nil
return
true
}
return
h
.
Next
.
ServeHTTP
(
w
,
r
)
return
false
}
caddy/letsencrypt/handler_test.go
0 → 100644
View file @
94532246
package
letsencrypt
import
(
"net"
"net/http"
"net/http/httptest"
"testing"
)
func
TestRequestCallbackNoOp
(
t
*
testing
.
T
)
{
// try base paths that aren't handled by this handler
for
_
,
url
:=
range
[]
string
{
"http://localhost/"
,
"http://localhost/foo.html"
,
"http://localhost/.git"
,
"http://localhost/.well-known/"
,
"http://localhost/.well-known/acme-challenging"
,
}
{
req
,
err
:=
http
.
NewRequest
(
"GET"
,
url
,
nil
)
if
err
!=
nil
{
t
.
Fatalf
(
"Could not craft request, got error: %v"
,
err
)
}
rw
:=
httptest
.
NewRecorder
()
if
RequestCallback
(
rw
,
req
)
{
t
.
Errorf
(
"Got true with this URL, but shouldn't have: %s"
,
url
)
}
}
}
func
TestRequestCallbackSuccess
(
t
*
testing
.
T
)
{
expectedPath
:=
challengeBasePath
+
"/asdf"
// Set up fake acme handler backend to make sure proxying succeeds
var
proxySuccess
bool
ts
:=
httptest
.
NewUnstartedServer
(
http
.
HandlerFunc
(
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
proxySuccess
=
true
if
r
.
URL
.
Path
!=
expectedPath
{
t
.
Errorf
(
"Expected path '%s' but got '%s' instead"
,
expectedPath
,
r
.
URL
.
Path
)
}
}))
// Custom listener that uses the port we expect
ln
,
err
:=
net
.
Listen
(
"tcp"
,
"127.0.0.1:"
+
AlternatePort
)
if
err
!=
nil
{
t
.
Fatalf
(
"Unable to start test server listener: %v"
,
err
)
}
ts
.
Listener
=
ln
// Start our engines and run the test
ts
.
Start
()
defer
ts
.
Close
()
req
,
err
:=
http
.
NewRequest
(
"GET"
,
"http://127.0.0.1:"
+
AlternatePort
+
expectedPath
,
nil
)
if
err
!=
nil
{
t
.
Fatalf
(
"Could not craft request, got error: %v"
,
err
)
}
rw
:=
httptest
.
NewRecorder
()
RequestCallback
(
rw
,
req
)
if
!
proxySuccess
{
t
.
Fatal
(
"Expected request to be proxied, but it wasn't"
)
}
}
caddy/letsencrypt/letsencrypt.go
View file @
94532246
This diff is collapsed.
Click to expand it.
caddy/letsencrypt/letsencrypt_test.go
View file @
94532246
This diff is collapsed.
Click to expand it.
caddy/letsencrypt/maintain.go
View file @
94532246
...
...
@@ -27,8 +27,8 @@ var OnChange func() error
// which you'll close when maintenance should stop, to allow this
// goroutine to clean up after itself and unblock.
func
maintainAssets
(
configs
[]
server
.
Config
,
stopChan
chan
struct
{})
{
renewalTicker
:=
time
.
NewTicker
(
r
enewInterval
)
ocspTicker
:=
time
.
NewTicker
(
ocsp
Interval
)
renewalTicker
:=
time
.
NewTicker
(
R
enewInterval
)
ocspTicker
:=
time
.
NewTicker
(
OCSP
Interval
)
for
{
select
{
...
...
@@ -47,15 +47,29 @@ func maintainAssets(configs []server.Config, stopChan chan struct{}) {
}
}
case
<-
ocspTicker
.
C
:
for
bundle
,
oldStatus
:=
range
ocspStatus
{
_
,
newStatus
,
err
:=
acme
.
GetOCSPForCert
(
*
bundle
)
if
err
==
nil
&&
newStatus
!=
oldStatus
&&
OnChange
!=
nil
{
log
.
Printf
(
"[INFO] OCSP status changed from %v to %v"
,
oldStatus
,
newStatus
)
err
:=
OnChange
()
for
bundle
,
oldResp
:=
range
ocspCache
{
// start checking OCSP staple about halfway through validity period for good measure
refreshTime
:=
oldResp
.
ThisUpdate
.
Add
(
oldResp
.
NextUpdate
.
Sub
(
oldResp
.
ThisUpdate
)
/
2
)
// only check for updated OCSP validity window if refreshTime is in the past
if
time
.
Now
()
.
After
(
refreshTime
)
{
_
,
newResp
,
err
:=
acme
.
GetOCSPForCert
(
*
bundle
)
if
err
!=
nil
{
log
.
Printf
(
"[ERROR] OnChange after OCSP update: %v"
,
err
)
log
.
Printf
(
"[ERROR] Checking OCSP for bundle: %v"
,
err
)
continue
}
// we're not looking for different status, just a more future expiration
if
newResp
.
NextUpdate
!=
oldResp
.
NextUpdate
{
if
OnChange
!=
nil
{
log
.
Printf
(
"[INFO] Updating OCSP stapling to extend validity period to %v"
,
newResp
.
NextUpdate
)
err
:=
OnChange
()
if
err
!=
nil
{
log
.
Printf
(
"[ERROR] OnChange after OCSP trigger: %v"
,
err
)
}
break
}
}
break
}
}
case
<-
stopChan
:
...
...
@@ -102,12 +116,12 @@ func renewCertificates(configs []server.Config, useCustomPort bool) (int, []erro
// Directly convert it to days for the following checks.
daysLeft
:=
int
(
expTime
.
Sub
(
time
.
Now
()
.
UTC
())
.
Hours
()
/
24
)
// Renew
with two weeks or less remaining
.
if
daysLeft
<=
14
{
// Renew
if getting close to expiration
.
if
daysLeft
<=
renewDaysBefore
{
log
.
Printf
(
"[INFO] Certificate for %s has %d days remaining; attempting renewal"
,
cfg
.
Host
,
daysLeft
)
var
client
*
acme
.
Client
if
useCustomPort
{
client
,
err
=
newClientPort
(
""
,
a
lternatePort
)
// email not used for renewal
client
,
err
=
newClientPort
(
""
,
A
lternatePort
)
// email not used for renewal
}
else
{
client
,
err
=
newClient
(
""
)
}
...
...
@@ -134,7 +148,7 @@ func renewCertificates(configs []server.Config, useCustomPort bool) (int, []erro
// Renew certificate
Renew
:
newCertMeta
,
err
:=
client
.
RenewCertificate
(
certMeta
,
true
,
true
)
newCertMeta
,
err
:=
client
.
RenewCertificate
(
certMeta
,
true
)
if
err
!=
nil
{
if
_
,
ok
:=
err
.
(
acme
.
TOSError
);
ok
{
err
:=
client
.
AgreeToTOS
()
...
...
@@ -145,24 +159,22 @@ func renewCertificates(configs []server.Config, useCustomPort bool) (int, []erro
}
time
.
Sleep
(
10
*
time
.
Second
)
newCertMeta
,
err
=
client
.
RenewCertificate
(
certMeta
,
true
,
true
)
newCertMeta
,
err
=
client
.
RenewCertificate
(
certMeta
,
true
)
if
err
!=
nil
{
errs
=
append
(
errs
,
err
)
continue
}
}
saveCert
sAndKeys
([]
acme
.
CertificateResource
{
newCertMeta
}
)
saveCert
Resource
(
newCertMeta
)
n
++
}
else
if
daysLeft
<=
30
{
// Warn on 30 days remaining. TODO: Just do this once...
log
.
Printf
(
"[WARNING] Certificate for %s has %d days remaining; will automatically renew when 14 days remain
\n
"
,
cfg
.
Host
,
daysLeft
)
}
else
if
daysLeft
<=
renewDaysBefore
+
7
&&
daysLeft
>=
renewDaysBefore
+
6
{
log
.
Printf
(
"[WARNING] Certificate for %s has %d days remaining; will automatically renew when %d days remain
\n
"
,
cfg
.
Host
,
daysLeft
,
renewDaysBefore
)
}
}
return
n
,
errs
}
// acmeHandlers is a map of host to ACME handler. These
// are used to proxy ACME requests to the ACME client.
var
acmeHandlers
=
make
(
map
[
string
]
*
Handler
)
// renewDaysBefore is how many days before expiration to renew certificates.
const
renewDaysBefore
=
14
caddy/letsencrypt/storage_test.go
View file @
94532246
...
...
@@ -6,44 +6,44 @@ import (
)
func
TestStorage
(
t
*
testing
.
T
)
{
storage
=
Storage
(
"./le
tsencryp
t"
)
storage
=
Storage
(
"./le
_tes
t"
)
if
expected
,
actual
:=
filepath
.
Join
(
"le
tsencryp
t"
,
"sites"
),
storage
.
Sites
();
actual
!=
expected
{
if
expected
,
actual
:=
filepath
.
Join
(
"le
_tes
t"
,
"sites"
),
storage
.
Sites
();
actual
!=
expected
{
t
.
Errorf
(
"Expected Sites() to return '%s' but got '%s'"
,
expected
,
actual
)
}
if
expected
,
actual
:=
filepath
.
Join
(
"le
tsencryp
t"
,
"sites"
,
"test.com"
),
storage
.
Site
(
"test.com"
);
actual
!=
expected
{
if
expected
,
actual
:=
filepath
.
Join
(
"le
_tes
t"
,
"sites"
,
"test.com"
),
storage
.
Site
(
"test.com"
);
actual
!=
expected
{
t
.
Errorf
(
"Expected Site() to return '%s' but got '%s'"
,
expected
,
actual
)
}
if
expected
,
actual
:=
filepath
.
Join
(
"le
tsencryp
t"
,
"sites"
,
"test.com"
,
"test.com.crt"
),
storage
.
SiteCertFile
(
"test.com"
);
actual
!=
expected
{
if
expected
,
actual
:=
filepath
.
Join
(
"le
_tes
t"
,
"sites"
,
"test.com"
,
"test.com.crt"
),
storage
.
SiteCertFile
(
"test.com"
);
actual
!=
expected
{
t
.
Errorf
(
"Expected SiteCertFile() to return '%s' but got '%s'"
,
expected
,
actual
)
}
if
expected
,
actual
:=
filepath
.
Join
(
"le
tsencryp
t"
,
"sites"
,
"test.com"
,
"test.com.key"
),
storage
.
SiteKeyFile
(
"test.com"
);
actual
!=
expected
{
if
expected
,
actual
:=
filepath
.
Join
(
"le
_tes
t"
,
"sites"
,
"test.com"
,
"test.com.key"
),
storage
.
SiteKeyFile
(
"test.com"
);
actual
!=
expected
{
t
.
Errorf
(
"Expected SiteKeyFile() to return '%s' but got '%s'"
,
expected
,
actual
)
}
if
expected
,
actual
:=
filepath
.
Join
(
"le
tsencryp
t"
,
"sites"
,
"test.com"
,
"test.com.json"
),
storage
.
SiteMetaFile
(
"test.com"
);
actual
!=
expected
{
if
expected
,
actual
:=
filepath
.
Join
(
"le
_tes
t"
,
"sites"
,
"test.com"
,
"test.com.json"
),
storage
.
SiteMetaFile
(
"test.com"
);
actual
!=
expected
{
t
.
Errorf
(
"Expected SiteMetaFile() to return '%s' but got '%s'"
,
expected
,
actual
)
}
if
expected
,
actual
:=
filepath
.
Join
(
"le
tsencryp
t"
,
"users"
),
storage
.
Users
();
actual
!=
expected
{
if
expected
,
actual
:=
filepath
.
Join
(
"le
_tes
t"
,
"users"
),
storage
.
Users
();
actual
!=
expected
{
t
.
Errorf
(
"Expected Users() to return '%s' but got '%s'"
,
expected
,
actual
)
}
if
expected
,
actual
:=
filepath
.
Join
(
"le
tsencryp
t"
,
"users"
,
"me@example.com"
),
storage
.
User
(
"me@example.com"
);
actual
!=
expected
{
if
expected
,
actual
:=
filepath
.
Join
(
"le
_tes
t"
,
"users"
,
"me@example.com"
),
storage
.
User
(
"me@example.com"
);
actual
!=
expected
{
t
.
Errorf
(
"Expected User() to return '%s' but got '%s'"
,
expected
,
actual
)
}
if
expected
,
actual
:=
filepath
.
Join
(
"le
tsencryp
t"
,
"users"
,
"me@example.com"
,
"me.json"
),
storage
.
UserRegFile
(
"me@example.com"
);
actual
!=
expected
{
if
expected
,
actual
:=
filepath
.
Join
(
"le
_tes
t"
,
"users"
,
"me@example.com"
,
"me.json"
),
storage
.
UserRegFile
(
"me@example.com"
);
actual
!=
expected
{
t
.
Errorf
(
"Expected UserRegFile() to return '%s' but got '%s'"
,
expected
,
actual
)
}
if
expected
,
actual
:=
filepath
.
Join
(
"le
tsencryp
t"
,
"users"
,
"me@example.com"
,
"me.key"
),
storage
.
UserKeyFile
(
"me@example.com"
);
actual
!=
expected
{
if
expected
,
actual
:=
filepath
.
Join
(
"le
_tes
t"
,
"users"
,
"me@example.com"
,
"me.key"
),
storage
.
UserKeyFile
(
"me@example.com"
);
actual
!=
expected
{
t
.
Errorf
(
"Expected UserKeyFile() to return '%s' but got '%s'"
,
expected
,
actual
)
}
// Test with empty emails
if
expected
,
actual
:=
filepath
.
Join
(
"le
tsencryp
t"
,
"users"
,
emptyEmail
),
storage
.
User
(
emptyEmail
);
actual
!=
expected
{
if
expected
,
actual
:=
filepath
.
Join
(
"le
_tes
t"
,
"users"
,
emptyEmail
),
storage
.
User
(
emptyEmail
);
actual
!=
expected
{
t
.
Errorf
(
"Expected User(
\"\"
) to return '%s' but got '%s'"
,
expected
,
actual
)
}
if
expected
,
actual
:=
filepath
.
Join
(
"le
tsencryp
t"
,
"users"
,
emptyEmail
,
emptyEmail
+
".json"
),
storage
.
UserRegFile
(
""
);
actual
!=
expected
{
if
expected
,
actual
:=
filepath
.
Join
(
"le
_tes
t"
,
"users"
,
emptyEmail
,
emptyEmail
+
".json"
),
storage
.
UserRegFile
(
""
);
actual
!=
expected
{
t
.
Errorf
(
"Expected UserRegFile(
\"\"
) to return '%s' but got '%s'"
,
expected
,
actual
)
}
if
expected
,
actual
:=
filepath
.
Join
(
"le
tsencryp
t"
,
"users"
,
emptyEmail
,
emptyEmail
+
".key"
),
storage
.
UserKeyFile
(
""
);
actual
!=
expected
{
if
expected
,
actual
:=
filepath
.
Join
(
"le
_tes
t"
,
"users"
,
emptyEmail
,
emptyEmail
+
".key"
),
storage
.
UserKeyFile
(
""
);
actual
!=
expected
{
t
.
Errorf
(
"Expected UserKeyFile(
\"\"
) to return '%s' but got '%s'"
,
expected
,
actual
)
}
}
...
...
caddy/letsencrypt/user.go
View file @
94532246
...
...
@@ -144,7 +144,7 @@ func getEmail(cfg server.Config) string {
// Alas, we must bother the user and ask for an email address;
// if they proceed they also agree to the SA.
reader
:=
bufio
.
NewReader
(
stdin
)
fmt
.
Println
(
"Your sites will be served over HTTPS automatically using Let's Encrypt."
)
fmt
.
Println
(
"
\n
Your sites will be served over HTTPS automatically using Let's Encrypt."
)
fmt
.
Println
(
"By continuing, you agree to the Let's Encrypt Subscriber Agreement at:"
)
fmt
.
Println
(
" "
+
saURL
)
// TODO: Show current SA link
fmt
.
Println
(
"Please enter your email address so you can recover your account if needed."
)
...
...
caddy/letsencrypt/user_test.go
View file @
94532246
...
...
@@ -125,6 +125,11 @@ func TestGetUserAlreadyExists(t *testing.T) {
}
func
TestGetEmail
(
t
*
testing
.
T
)
{
// let's not clutter up the output
origStdout
:=
os
.
Stdout
os
.
Stdout
=
nil
defer
func
()
{
os
.
Stdout
=
origStdout
}()
storage
=
Storage
(
"./testdata"
)
defer
os
.
RemoveAll
(
string
(
storage
))
DefaultEmail
=
"test2@foo.com"
...
...
caddy/parse/parse.go
View file @
94532246
...
...
@@ -8,7 +8,7 @@ import "io"
// If checkDirectives is true, only valid directives will be allowed
// otherwise we consider it a parse error. Server blocks are returned
// in the order in which they appear.
func
ServerBlocks
(
filename
string
,
input
io
.
Reader
,
checkDirectives
bool
)
([]
s
erverBlock
,
error
)
{
func
ServerBlocks
(
filename
string
,
input
io
.
Reader
,
checkDirectives
bool
)
([]
S
erverBlock
,
error
)
{
p
:=
parser
{
Dispenser
:
NewDispenser
(
filename
,
input
)}
p
.
checkDirectives
=
checkDirectives
blocks
,
err
:=
p
.
parseAll
()
...
...
caddy/parse/parsing.go
View file @
94532246
package
parse
import
(
"fmt"
"net"
"os"
"path/filepath"
...
...
@@ -9,13 +10,13 @@ import (
type
parser
struct
{
Dispenser
block
s
erverBlock
// current server block being parsed
block
S
erverBlock
// current server block being parsed
eof
bool
// if we encounter a valid EOF in a hard place
checkDirectives
bool
// if true, directives must be known
}
func
(
p
*
parser
)
parseAll
()
([]
s
erverBlock
,
error
)
{
var
blocks
[]
s
erverBlock
func
(
p
*
parser
)
parseAll
()
([]
S
erverBlock
,
error
)
{
var
blocks
[]
S
erverBlock
for
p
.
Next
()
{
err
:=
p
.
parseOne
()
...
...
@@ -31,7 +32,7 @@ func (p *parser) parseAll() ([]serverBlock, error) {
}
func
(
p
*
parser
)
parseOne
()
error
{
p
.
block
=
s
erverBlock
{
Tokens
:
make
(
map
[
string
][]
token
)}
p
.
block
=
S
erverBlock
{
Tokens
:
make
(
map
[
string
][]
token
)}
err
:=
p
.
begin
()
if
err
!=
nil
{
...
...
@@ -99,11 +100,11 @@ func (p *parser) addresses() error {
}
// Parse and save this address
host
,
port
,
err
:=
standardAddress
(
tkn
)
addr
,
err
:=
standardAddress
(
tkn
)
if
err
!=
nil
{
return
err
}
p
.
block
.
Addresses
=
append
(
p
.
block
.
Addresses
,
addr
ess
{
host
,
port
}
)
p
.
block
.
Addresses
=
append
(
p
.
block
.
Addresses
,
addr
)
}
// Advance token and possibly break out of loop or return error
...
...
@@ -303,39 +304,57 @@ func (p *parser) closeCurlyBrace() error {
return
nil
}
// standardAddress turns the accepted host and port patterns
// into a format accepted by net.Dial.
func
standardAddress
(
str
string
)
(
host
,
port
string
,
err
error
)
{
var
schemePort
,
splitPort
string
// standardAddress parses an address string into a structured format with separate
// scheme, host, and port portions, as well as the original input string.
func
standardAddress
(
str
string
)
(
address
,
error
)
{
var
scheme
string
var
err
error
// first check for scheme and strip it off
input
:=
str
if
strings
.
HasPrefix
(
str
,
"https://"
)
{
scheme
Port
=
"https"
scheme
=
"https"
str
=
str
[
8
:
]
}
else
if
strings
.
HasPrefix
(
str
,
"http://"
)
{
scheme
Port
=
"http"
scheme
=
"http"
str
=
str
[
7
:
]
}
host
,
splitPort
,
err
=
net
.
SplitHostPort
(
str
)
// separate host and port
host
,
port
,
err
:=
net
.
SplitHostPort
(
str
)
if
err
!=
nil
{
host
,
splitPort
,
err
=
net
.
SplitHostPort
(
str
+
":"
)
// tack on empty port
host
,
port
,
err
=
net
.
SplitHostPort
(
str
+
":"
)
// no error check here; return err at end of function
}
if
err
!=
nil
{
// ¯\_(ツ)_/¯
host
=
str
// see if we can set port based off scheme
if
port
==
""
{
if
scheme
==
"http"
{
port
=
"80"
}
else
if
scheme
==
"https"
{
port
=
"443"
}
}
// repeated or conflicting scheme is confusing, so error
if
scheme
!=
""
&&
(
port
==
"http"
||
port
==
"https"
)
{
return
address
{},
fmt
.
Errorf
(
"[%s] scheme specified twice in address"
,
str
)
}
if
splitPort
!=
""
{
port
=
splitPort
}
else
{
port
=
schemePort
// standardize http and https ports to their respective port numbers
if
port
==
"http"
{
scheme
=
"http"
port
=
"80"
}
else
if
port
==
"https"
{
scheme
=
"https"
port
=
"443"
}
return
return
address
{
Original
:
input
,
Scheme
:
scheme
,
Host
:
host
,
Port
:
port
},
err
}
// replaceEnvVars replaces environment variables that appear in the token
// and understands both the
Unix $SYNTAX and Windows %SYNTAX%
.
// and understands both the
$UNIX and %WINDOWS% syntaxes
.
func
replaceEnvVars
(
s
string
)
string
{
s
=
replaceEnvReferences
(
s
,
"{%"
,
"%}"
)
s
=
replaceEnvReferences
(
s
,
"{$"
,
"}"
)
...
...
@@ -360,26 +379,26 @@ func replaceEnvReferences(s, refStart, refEnd string) string {
}
type
(
//
s
erverBlock associates tokens with a list of addresses
//
S
erverBlock associates tokens with a list of addresses
// and groups tokens by directive name.
s
erverBlock
struct
{
S
erverBlock
struct
{
Addresses
[]
address
Tokens
map
[
string
][]
token
}
address
struct
{
Host
,
Port
string
Original
,
Scheme
,
Host
,
Port
string
}
)
// HostList converts the list of addresses
(hosts)
//
that are associated with this server block into
//
a slice of strings. Each string is a host:port
//
combination
.
func
(
sb
s
erverBlock
)
HostList
()
[]
string
{
// HostList converts the list of addresses
that are
//
associated with this server block into a slice of
//
strings, where each address is as it was originally
//
read from the input
.
func
(
sb
S
erverBlock
)
HostList
()
[]
string
{
sbHosts
:=
make
([]
string
,
len
(
sb
.
Addresses
))
for
j
,
addr
:=
range
sb
.
Addresses
{
sbHosts
[
j
]
=
net
.
JoinHostPort
(
addr
.
Host
,
addr
.
Port
)
sbHosts
[
j
]
=
addr
.
Original
}
return
sbHosts
}
caddy/parse/parsing_test.go
View file @
94532246
This diff is collapsed.
Click to expand it.
caddy/restart.go
View file @
94532246
...
...
@@ -3,11 +3,17 @@
package
caddy
import
(
"bytes"
"encoding/gob"
"errors"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"github.com/mholt/caddy/caddy/letsencrypt"
"github.com/mholt/caddy/server"
)
func
init
()
{
...
...
@@ -33,6 +39,12 @@ func Restart(newCaddyfile Input) error {
caddyfileMu
.
Unlock
()
}
// Get certificates for any new hosts in the new Caddyfile without causing downtime
err
:=
getCertsForNewCaddyfile
(
newCaddyfile
)
if
err
!=
nil
{
return
errors
.
New
(
"TLS preload: "
+
err
.
Error
())
}
if
len
(
os
.
Args
)
==
0
{
// this should never happen, but...
os
.
Args
=
[]
string
{
""
}
}
...
...
@@ -61,7 +73,7 @@ func Restart(newCaddyfile Input) error {
// Pass along relevant file descriptors to child process; ordering
// is very important since we rely on these being in certain positions.
extraFiles
:=
[]
*
os
.
File
{
sigwpipe
}
extraFiles
:=
[]
*
os
.
File
{
sigwpipe
}
// fd 3
// Add file descriptors of all the sockets
serversMu
.
Lock
()
...
...
@@ -110,3 +122,44 @@ func Restart(newCaddyfile Input) error {
// Looks like child is successful; we can exit gracefully.
return
Stop
()
}
func
getCertsForNewCaddyfile
(
newCaddyfile
Input
)
error
{
// parse the new caddyfile only up to (and including) TLS
// so we can know what we need to get certs for.
configs
,
_
,
_
,
err
:=
loadConfigsUpToIncludingTLS
(
path
.
Base
(
newCaddyfile
.
Path
()),
bytes
.
NewReader
(
newCaddyfile
.
Body
()))
if
err
!=
nil
{
return
errors
.
New
(
"loading Caddyfile: "
+
err
.
Error
())
}
// first mark the configs that are qualified for managed TLS
letsencrypt
.
MarkQualified
(
configs
)
// we must make sure port is set before we group by bind address
letsencrypt
.
EnableTLS
(
configs
)
// we only need to issue certs for hosts where we already have an active listener
groupings
,
err
:=
arrangeBindings
(
configs
)
if
err
!=
nil
{
return
errors
.
New
(
"arranging bindings: "
+
err
.
Error
())
}
var
configsToSetup
[]
server
.
Config
serversMu
.
Lock
()
GroupLoop
:
for
_
,
group
:=
range
groupings
{
for
_
,
server
:=
range
servers
{
if
server
.
Addr
==
group
.
BindAddr
.
String
()
{
configsToSetup
=
append
(
configsToSetup
,
group
.
Configs
...
)
continue
GroupLoop
}
}
}
serversMu
.
Unlock
()
// place certs on the disk
err
=
letsencrypt
.
ObtainCerts
(
configsToSetup
,
letsencrypt
.
AlternatePort
)
if
err
!=
nil
{
return
errors
.
New
(
"obtaining certs: "
+
err
.
Error
())
}
return
nil
}
caddy/setup/startupshutdown_test.go
View file @
94532246
...
...
@@ -2,7 +2,6 @@ package setup
import
(
"os"
"os/exec"
"path/filepath"
"strconv"
"testing"
...
...
@@ -13,16 +12,19 @@ import (
// because the Startup and Shutdown functions share virtually the
// same functionality
func
TestStartup
(
t
*
testing
.
T
)
{
tempDirPath
,
err
:=
getTempDirPath
()
if
err
!=
nil
{
t
.
Fatalf
(
"BeforeTest: Failed to find an existing directory for testing! Error was: %v"
,
err
)
}
testDir
:=
filepath
.
Join
(
tempDirPath
,
"temp_dir_for_testing_startupshutdown.go"
)
testDir
:=
filepath
.
Join
(
tempDirPath
,
"temp_dir_for_testing_startupshutdown"
)
defer
func
()
{
// clean up after non-blocking startup function quits
time
.
Sleep
(
500
*
time
.
Millisecond
)
os
.
RemoveAll
(
testDir
)
}()
osSenitiveTestDir
:=
filepath
.
FromSlash
(
testDir
)
exec
.
Command
(
"rm"
,
"-r"
,
osSenitiveTestDir
)
.
Run
()
// removes osSenitiveTestDir from the OS's temp directory, if the osSenitiveTestDir already exists
os
.
RemoveAll
(
osSenitiveTestDir
)
// start with a clean slate
tests
:=
[]
struct
{
input
string
...
...
@@ -53,6 +55,5 @@ func TestStartup(t *testing.T) {
if
err
!=
nil
&&
!
test
.
shouldRemoveErr
{
t
.
Errorf
(
"Test %d recieved an error of:
\n
%v"
,
i
,
err
)
}
}
}
caddy/setup/tls.go
View file @
94532246
...
...
@@ -11,12 +11,12 @@ import (
// TLS sets up the TLS configuration (but does not activate Let's Encrypt; that is handled elsewhere).
func
TLS
(
c
*
Controller
)
(
middleware
.
Middleware
,
error
)
{
if
c
.
Port
==
"http
"
{
if
c
.
Scheme
==
"http"
&&
c
.
Port
!=
"80
"
{
c
.
TLS
.
Enabled
=
false
log
.
Printf
(
"[WARNING] TLS disabled for %s://%s. To force TLS over the plaintext HTTP port, "
+
"specify port 80 explicitly (https://%s:80)."
,
c
.
Port
,
c
.
Host
,
c
.
Host
)
"specify port 80 explicitly (https://%s:80)."
,
c
.
Scheme
,
c
.
Address
()
,
c
.
Host
)
}
else
{
c
.
TLS
.
Enabled
=
true
// they had a tls directive, so assume it's on unless we confirm otherwise later
c
.
TLS
.
Enabled
=
true
}
for
c
.
Next
()
{
...
...
@@ -32,18 +32,9 @@ func TLS(c *Controller) (middleware.Middleware, error) {
case
2
:
c
.
TLS
.
Certificate
=
args
[
0
]
c
.
TLS
.
Key
=
args
[
1
]
// manual HTTPS configuration without port specified should be
// served on the HTTPS port; that is what user would expect, and
// makes it consistent with how the letsencrypt package works.
if
c
.
Port
==
""
{
c
.
Port
=
"https"
}
default
:
return
nil
,
c
.
ArgErr
()
}
// Optional block
// Optional block
with extra parameters
for
c
.
NextBlock
()
{
switch
c
.
Val
()
{
case
"protocols"
:
...
...
@@ -74,6 +65,9 @@ func TLS(c *Controller) (middleware.Middleware, error) {
if
len
(
c
.
TLS
.
ClientCerts
)
==
0
{
return
nil
,
c
.
ArgErr
()
}
// TODO: Allow this? It's a bad idea to allow HTTP. If we do this, make sure invoking tls at all (even manually) also sets up a redirect if possible?
// case "allow_http":
// c.TLS.DisableHTTPRedir = true
default
:
return
nil
,
c
.
Errf
(
"Unknown keyword '%s'"
,
c
.
Val
())
}
...
...
@@ -85,8 +79,9 @@ func TLS(c *Controller) (middleware.Middleware, error) {
return
nil
,
nil
}
// SetDefaultTLSParams sets the default TLS cipher suites, protocol versions and server preferences
// of a server.Config if they were not previously set.
// SetDefaultTLSParams sets the default TLS cipher suites, protocol versions,
// and server preferences of a server.Config if they were not previously set
// (it does not overwrite; only fills in missing values).
func
SetDefaultTLSParams
(
c
*
server
.
Config
)
{
// If no ciphers provided, use all that Caddy supports for the protocol
if
len
(
c
.
TLS
.
Ciphers
)
==
0
{
...
...
@@ -106,6 +101,11 @@ func SetDefaultTLSParams(c *server.Config) {
// Prefer server cipher suites
c
.
TLS
.
PreferServerCipherSuites
=
true
// Default TLS port is 443; only use if port is not manually specified
if
c
.
Port
==
""
{
c
.
Port
=
"443"
}
}
// Map of supported protocols
...
...
caddy/setup/tls_test.go
View file @
94532246
...
...
@@ -64,11 +64,12 @@ func TestTLSParseBasic(t *testing.T) {
}
func
TestTLSParseIncompleteParams
(
t
*
testing
.
T
)
{
// This doesn't do anything useful but is allowed in case the user wants to be explicit
// about TLS being enabled...
c
:=
NewTestController
(
`tls`
)
_
,
err
:=
TLS
(
c
)
if
err
=
=
nil
{
t
.
Errorf
(
"Expected
errors (first check), but no error returned"
)
if
err
!
=
nil
{
t
.
Errorf
(
"Expected
no error, but got %v"
,
err
)
}
}
...
...
@@ -93,10 +94,39 @@ func TestTLSParseWithOptionalParams(t *testing.T) {
}
if
len
(
c
.
TLS
.
Ciphers
)
-
1
!=
3
{
t
.
Errorf
(
"Expected 3 Ciphers (not including TLS_FALLBACK_SCSV), got %v"
,
len
(
c
.
TLS
.
Ciphers
))
t
.
Errorf
(
"Expected 3 Ciphers (not including TLS_FALLBACK_SCSV), got %v"
,
len
(
c
.
TLS
.
Ciphers
)
-
1
)
}
}
func
TestTLSDefaultWithOptionalParams
(
t
*
testing
.
T
)
{
params
:=
`tls {
ciphers RSA-3DES-EDE-CBC-SHA
}`
c
:=
NewTestController
(
params
)
_
,
err
:=
TLS
(
c
)
if
err
!=
nil
{
t
.
Errorf
(
"Expected no errors, got: %v"
,
err
)
}
if
len
(
c
.
TLS
.
Ciphers
)
-
1
!=
1
{
t
.
Errorf
(
"Expected 1 ciphers (not including TLS_FALLBACK_SCSV), got %v"
,
len
(
c
.
TLS
.
Ciphers
)
-
1
)
}
}
// TODO: If we allow this... but probably not a good idea.
// func TestTLSDisableHTTPRedirect(t *testing.T) {
// c := NewTestController(`tls {
// allow_http
// }`)
// _, err := TLS(c)
// if err != nil {
// t.Errorf("Expected no error, but got %v", err)
// }
// if !c.TLS.DisableHTTPRedir {
// t.Error("Expected HTTP redirect to be disabled, but it wasn't")
// }
// }
func
TestTLSParseWithWrongOptionalParams
(
t
*
testing
.
T
)
{
// Test protocols wrong params
params
:=
`tls cert.crt cert.key {
...
...
main.go
View file @
94532246
...
...
@@ -14,6 +14,7 @@ import (
"github.com/mholt/caddy/caddy"
"github.com/mholt/caddy/caddy/letsencrypt"
"github.com/xenolf/lego/acme"
)
var
(
...
...
@@ -53,6 +54,7 @@ func main() {
caddy
.
AppName
=
appName
caddy
.
AppVersion
=
appVersion
acme
.
UserAgent
=
appName
+
"/"
+
appVersion
// set up process log before anything bad happens
switch
logfile
{
...
...
middleware/gzip/response_filter.go
View file @
94532246
...
...
@@ -40,8 +40,8 @@ func NewResponseFilterWriter(filters []ResponseFilter, gz *gzipResponseWriter) *
return
&
ResponseFilterWriter
{
filters
:
filters
,
gzipResponseWriter
:
gz
}
}
// Write
wraps underlying WriteHeader method and compresses if filters
// are satisfied.
// Write
Header wraps underlying WriteHeader method and
//
compresses if filters
are satisfied.
func
(
r
*
ResponseFilterWriter
)
WriteHeader
(
code
int
)
{
// Determine if compression should be used or not.
r
.
shouldCompress
=
true
...
...
middleware/gzip/response_filter_test.go
View file @
94532246
...
...
@@ -11,7 +11,7 @@ import (
)
func
TestLengthFilter
(
t
*
testing
.
T
)
{
var
filters
[]
ResponseFilter
=
[]
ResponseFilter
{
var
filters
=
[]
ResponseFilter
{
LengthFilter
(
100
),
LengthFilter
(
1000
),
LengthFilter
(
0
),
...
...
middleware/rewrite/condition.go
View file @
94532246
...
...
@@ -9,8 +9,8 @@ import (
"github.com/mholt/caddy/middleware"
)
// Operators
const
(
// Operators
Is
=
"is"
Not
=
"not"
Has
=
"has"
...
...
middleware/rewrite/rewrite.go
View file @
94532246
...
...
@@ -13,12 +13,12 @@ import (
"github.com/mholt/caddy/middleware"
)
// Re
writeRe
sult is the result of a rewrite
type
Re
writeRe
sult
int
// Result is the result of a rewrite
type
Result
int
const
(
// RewriteIgnored is returned when rewrite is not done on request.
RewriteIgnored
Re
writeRe
sult
=
iota
RewriteIgnored
Result
=
iota
// RewriteDone is returned when rewrite is done on request.
RewriteDone
// RewriteStatus is returned when rewrite is not needed and status code should be set
...
...
@@ -55,7 +55,7 @@ outer:
// Rule describes an internal location rewrite rule.
type
Rule
interface
{
// Rewrite rewrites the internal location of the current request.
Rewrite
(
http
.
FileSystem
,
*
http
.
Request
)
Re
writeRe
sult
Rewrite
(
http
.
FileSystem
,
*
http
.
Request
)
Result
}
// SimpleRule is a simple rewrite rule.
...
...
@@ -69,7 +69,7 @@ func NewSimpleRule(from, to string) SimpleRule {
}
// Rewrite rewrites the internal location of the current request.
func
(
s
SimpleRule
)
Rewrite
(
fs
http
.
FileSystem
,
r
*
http
.
Request
)
Re
writeRe
sult
{
func
(
s
SimpleRule
)
Rewrite
(
fs
http
.
FileSystem
,
r
*
http
.
Request
)
Result
{
if
s
.
From
==
r
.
URL
.
Path
{
// take note of this rewrite for internal use by fastcgi
// all we need is the URI, not full URL
...
...
@@ -102,7 +102,7 @@ type ComplexRule struct {
*
regexp
.
Regexp
}
// New
Regexp
Rule creates a new RegexpRule. It returns an error if regexp
// New
Complex
Rule creates a new RegexpRule. It returns an error if regexp
// pattern (pattern) or extensions (ext) are invalid.
func
NewComplexRule
(
base
,
pattern
,
to
string
,
status
int
,
ext
[]
string
,
ifs
[]
If
)
(
*
ComplexRule
,
error
)
{
// validate regexp if present
...
...
@@ -136,7 +136,7 @@ func NewComplexRule(base, pattern, to string, status int, ext []string, ifs []If
}
// Rewrite rewrites the internal location of the current request.
func
(
r
*
ComplexRule
)
Rewrite
(
fs
http
.
FileSystem
,
req
*
http
.
Request
)
(
re
Re
writeRe
sult
)
{
func
(
r
*
ComplexRule
)
Rewrite
(
fs
http
.
FileSystem
,
req
*
http
.
Request
)
(
re
Result
)
{
rPath
:=
req
.
URL
.
Path
replacer
:=
newReplacer
(
req
)
...
...
middleware/rewrite/to.go
View file @
94532246
...
...
@@ -13,7 +13,7 @@ import (
// To attempts rewrite. It attempts to rewrite to first valid path
// or the last path if none of the paths are valid.
// Returns true if rewrite is successful and false otherwise.
func
To
(
fs
http
.
FileSystem
,
r
*
http
.
Request
,
to
string
,
replacer
middleware
.
Replacer
)
Re
writeRe
sult
{
func
To
(
fs
http
.
FileSystem
,
r
*
http
.
Request
,
to
string
,
replacer
middleware
.
Replacer
)
Result
{
tos
:=
strings
.
Fields
(
to
)
// try each rewrite paths
...
...
server/config.go
View file @
94532246
...
...
@@ -17,6 +17,9 @@ type Config struct {
// The port to listen on
Port
string
// The protocol (http/https) to serve with this config; only set if user explicitly specifies it
Scheme
string
// The directory from which to serve files
Root
string
...
...
@@ -62,10 +65,12 @@ func (c Config) Address() string {
// TLSConfig describes how TLS should be configured and used.
type
TLSConfig
struct
{
Enabled
bool
Certificate
string
Key
string
LetsEncryptEmail
string
Enabled
bool
Certificate
string
Key
string
LetsEncryptEmail
string
Managed
bool
// will be set to true if config qualifies for automatic, managed TLS
//DisableHTTPRedir bool // TODO: not a good idea - should we really allow it?
OCSPStaple
[]
byte
Ciphers
[]
uint16
ProtocolMinVersion
uint16
...
...
server/config_test.go
View file @
94532246
...
...
@@ -18,8 +18,8 @@ func TestConfigAddress(t *testing.T) {
t
.
Errorf
(
"Expected '%s' but got '%s'"
,
expected
,
actual
)
}
cfg
=
Config
{
Host
:
"::1"
,
Port
:
"
https
"
}
if
actual
,
expected
:=
cfg
.
Address
(),
"[::1]:
https
"
;
expected
!=
actual
{
cfg
=
Config
{
Host
:
"::1"
,
Port
:
"
443
"
}
if
actual
,
expected
:=
cfg
.
Address
(),
"[::1]:
443
"
;
expected
!=
actual
{
t
.
Errorf
(
"Expected '%s' but got '%s'"
,
expected
,
actual
)
}
}
server/server.go
View file @
94532246
...
...
@@ -33,6 +33,7 @@ type Server struct {
httpWg
sync
.
WaitGroup
// used to wait on outstanding connections
startChan
chan
struct
{}
// used to block until server is finished starting
connTimeout
time
.
Duration
// the maximum duration of a graceful shutdown
ReqCallback
OptionalCallback
// if non-nil, is executed at the beginning of every request
}
// ListenerFile represents a listener.
...
...
@@ -41,6 +42,11 @@ type ListenerFile interface {
File
()
(
*
os
.
File
,
error
)
}
// OptionalCallback is a function that may or may not handle a request.
// It returns whether or not it handled the request. If it handled the
// request, it is presumed that no further request handling should occur.
type
OptionalCallback
func
(
http
.
ResponseWriter
,
*
http
.
Request
)
bool
// New creates a new Server which will bind to addr and serve
// the sites/hosts configured in configs. Its listener will
// gracefully close when the server is stopped which will take
...
...
@@ -309,6 +315,13 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}()
w
.
Header
()
.
Set
(
"Server"
,
"Caddy"
)
// Execute the optional request callback if it exists
if
s
.
ReqCallback
!=
nil
&&
s
.
ReqCallback
(
w
,
r
)
{
return
}
host
,
_
,
err
:=
net
.
SplitHostPort
(
r
.
Host
)
if
err
!=
nil
{
host
=
r
.
Host
// oh well
...
...
@@ -324,8 +337,6 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
if
vh
,
ok
:=
s
.
vhosts
[
host
];
ok
{
w
.
Header
()
.
Set
(
"Server"
,
"Caddy"
)
status
,
_
:=
vh
.
stack
.
ServeHTTP
(
w
,
r
)
// Fallback error response in case error handling wasn't chained in
...
...
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