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
nexedi
caddy
Commits
a0c8428f
Commit
a0c8428f
authored
Oct 16, 2015
by
Matthew Holt
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Can issue and use SSL certs and serve sites
Code is a huge mess; much cleanup to follow.
parent
dd91812b
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
241 additions
and
36 deletions
+241
-36
config/config.go
config/config.go
+4
-27
config/letsencrypt.go
config/letsencrypt.go
+236
-5
config/setup/tls.go
config/setup/tls.go
+1
-4
No files found.
config/config.go
View file @
a0c8428f
package
config
import
(
"errors"
"fmt"
"io"
"log"
...
...
@@ -13,7 +12,6 @@ import (
"github.com/mholt/caddy/config/setup"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/server"
"github.com/xenolf/lego/acme"
)
const
(
...
...
@@ -103,34 +101,13 @@ func Load(filename string, input io.Reader) (Group, error) {
// restore logging settings
log
.
SetFlags
(
flags
)
//
Initiate Let's Encrypt
leUser
,
err
:=
NewLetsEncryptUser
(
"example1@mail.com"
)
//
secure all the things
err
=
initiateLetsEncrypt
(
configs
)
if
err
!=
nil
{
return
Group
{},
err
}
for
_
,
cfg
:=
range
configs
{
// TODO: && !IsLoopback()
if
!
cfg
.
TLS
.
Enabled
&&
cfg
.
Port
!=
"http"
{
client
:=
acme
.
NewClient
(
"http://192.168.99.100:4000"
,
&
leUser
,
2048
,
"5001"
)
reg
,
err
:=
client
.
Register
()
if
err
!=
nil
{
return
Group
{},
errors
.
New
(
"Error Registering: "
+
err
.
Error
())
}
leUser
.
Registration
=
reg
err
=
client
.
AgreeToTos
()
if
err
!=
nil
{
return
Group
{},
errors
.
New
(
"Error Agreeing to ToS: "
+
err
.
Error
())
}
certs
,
err
:=
client
.
ObtainCertificates
([]
string
{
"caddy.dev"
})
if
err
!=
nil
{
return
Group
{},
errors
.
New
(
"Error Obtaining Certs: "
+
err
.
Error
())
}
}
return
nil
,
err
}
//
G
roup by address/virtualhosts
//
g
roup by address/virtualhosts
return
arrangeBindings
(
configs
)
}
...
...
config/letsencrypt.go
View file @
a0c8428f
package
config
import
(
"bufio"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/mholt/caddy/app"
"github.com/mholt/caddy/server"
"github.com/xenolf/lego/acme"
)
func
NewLetsEncryptUser
(
email
string
)
(
LetsEncryptUser
,
error
)
{
const
rsaKeySize
=
2048
// initiateLetsEncrypt sets up TLS ... <TODO>
func
initiateLetsEncrypt
(
configs
[]
server
.
Config
)
error
{
// fill map of email address to server configs that use that email address for TLS.
// this will help us reduce roundtrips when getting the certs.
initMap
:=
make
(
map
[
string
][]
*
server
.
Config
)
for
i
:=
0
;
i
<
len
(
configs
);
i
++
{
if
configs
[
i
]
.
TLS
.
Certificate
==
""
&&
configs
[
i
]
.
TLS
.
Key
==
""
&&
configs
[
i
]
.
Port
!=
"http"
{
// TODO: && !cfg.Host.IsLoopback()
leEmail
:=
getEmail
(
configs
[
i
])
if
leEmail
==
""
{
return
errors
.
New
(
"cannot serve HTTPS without email address OR certificate and key"
)
}
initMap
[
leEmail
]
=
append
(
initMap
[
leEmail
],
&
configs
[
i
])
}
}
for
leEmail
,
serverConfigs
:=
range
initMap
{
leUser
,
err
:=
getLetsEncryptUser
(
leEmail
)
if
err
!=
nil
{
return
err
}
client
:=
acme
.
NewClient
(
"http://192.168.99.100:4000"
,
&
leUser
,
rsaKeySize
,
"5001"
)
if
leUser
.
Registration
==
nil
{
reg
,
err
:=
client
.
Register
()
if
err
!=
nil
{
return
errors
.
New
(
"registration error: "
+
err
.
Error
())
}
leUser
.
Registration
=
reg
// TODO: we can just do the agreement once, when registering, right?
err
=
client
.
AgreeToTos
()
if
err
!=
nil
{
saveLetsEncryptUser
(
leUser
)
// TODO: Might as well try, right? Error check?
return
errors
.
New
(
"error agreeing to terms: "
+
err
.
Error
())
}
err
=
saveLetsEncryptUser
(
leUser
)
if
err
!=
nil
{
return
errors
.
New
(
"could not save user: "
+
err
.
Error
())
}
}
// collect all the hostnames
var
hosts
[]
string
for
_
,
cfg
:=
range
serverConfigs
{
hosts
=
append
(
hosts
,
cfg
.
Host
)
}
// showtime: let's get free, trusted SSL certificates! yee-haw!
certificates
,
err
:=
client
.
ObtainCertificates
(
hosts
)
if
err
!=
nil
{
return
errors
.
New
(
"error obtaining certs: "
+
err
.
Error
())
}
// ... that's it. pain gone. save the certs, keys, and update server configs.
for
_
,
cert
:=
range
certificates
{
certFolder
:=
filepath
.
Join
(
app
.
DataFolder
(),
"letsencrypt"
,
"sites"
,
cert
.
Domain
)
os
.
MkdirAll
(
certFolder
,
0700
)
// Save cert
err
=
saveCertificate
(
cert
.
Certificate
,
filepath
.
Join
(
certFolder
,
cert
.
Domain
+
".crt"
))
//err = ioutil.WriteFile(filepath.Join(certFolder, cert.Domain+".crt"), cert.Certificate, 0600)
if
err
!=
nil
{
return
err
}
// Save private key
//savePrivateKey(cert.PrivateKey, filepath.Join(certFolder, cert.Domain+".key"))
err
=
ioutil
.
WriteFile
(
filepath
.
Join
(
certFolder
,
cert
.
Domain
+
".key"
),
cert
.
PrivateKey
,
0600
)
if
err
!=
nil
{
return
err
}
// Save cert metadata
jsonBytes
,
err
:=
json
.
MarshalIndent
(
&
CertificateMeta
{
URL
:
cert
.
CertURL
,
Domain
:
cert
.
Domain
},
""
,
"
\t
"
)
if
err
!=
nil
{
return
err
}
err
=
ioutil
.
WriteFile
(
filepath
.
Join
(
certFolder
,
cert
.
Domain
+
".json"
),
jsonBytes
,
0600
)
if
err
!=
nil
{
return
err
}
}
// it all comes down to this: filling in the file path of a valid certificate automatically
for
_
,
cfg
:=
range
serverConfigs
{
cfg
.
TLS
.
Certificate
=
filepath
.
Join
(
app
.
DataFolder
(),
"letsencrypt"
,
"sites"
,
cfg
.
Host
,
cfg
.
Host
+
".crt"
)
cfg
.
TLS
.
Key
=
filepath
.
Join
(
app
.
DataFolder
(),
"letsencrypt"
,
"sites"
,
cfg
.
Host
,
cfg
.
Host
+
".key"
)
}
}
return
nil
}
func
getEmail
(
cfg
server
.
Config
)
string
{
leEmail
:=
cfg
.
TLS
.
LetsEncryptEmail
if
leEmail
==
""
{
leEmail
=
LetsEncryptEmail
}
if
leEmail
==
""
{
// TODO: get most recent email from ~/.caddy/users file
}
if
leEmail
==
""
{
reader
:=
bufio
.
NewReader
(
os
.
Stdin
)
fmt
.
Print
(
"Email address: "
)
var
err
error
leEmail
,
err
=
reader
.
ReadString
(
'\n'
)
if
err
!=
nil
{
return
""
}
LetsEncryptEmail
=
leEmail
}
return
strings
.
TrimSpace
(
leEmail
)
}
func
saveLetsEncryptUser
(
user
LetsEncryptUser
)
error
{
// make user account folder
userFolder
:=
filepath
.
Join
(
app
.
DataFolder
(),
"letsencrypt"
,
"users"
,
user
.
Email
)
err
:=
os
.
MkdirAll
(
userFolder
,
0700
)
if
err
!=
nil
{
return
err
}
// save private key file
user
.
KeyFile
=
filepath
.
Join
(
userFolder
,
emailUsername
(
user
.
Email
)
+
".key"
)
err
=
savePrivateKey
(
user
.
key
,
user
.
KeyFile
)
if
err
!=
nil
{
return
err
}
// save registration file
jsonBytes
,
err
:=
json
.
MarshalIndent
(
&
user
,
""
,
"
\t
"
)
if
err
!=
nil
{
return
err
}
return
ioutil
.
WriteFile
(
filepath
.
Join
(
userFolder
,
"registration.json"
),
jsonBytes
,
0600
)
}
func
getLetsEncryptUser
(
email
string
)
(
LetsEncryptUser
,
error
)
{
var
user
LetsEncryptUser
userFolder
:=
filepath
.
Join
(
app
.
DataFolder
(),
"letsencrypt"
,
"users"
,
email
)
regFile
,
err
:=
os
.
Open
(
filepath
.
Join
(
userFolder
,
"registration.json"
))
if
err
!=
nil
{
if
os
.
IsNotExist
(
err
)
{
// create a new user
return
newLetsEncryptUser
(
email
)
}
return
user
,
err
}
err
=
json
.
NewDecoder
(
regFile
)
.
Decode
(
&
user
)
if
err
!=
nil
{
return
user
,
err
}
user
.
key
,
err
=
loadPrivateKey
(
user
.
KeyFile
)
if
err
!=
nil
{
return
user
,
err
}
return
user
,
nil
}
func
newLetsEncryptUser
(
email
string
)
(
LetsEncryptUser
,
error
)
{
user
:=
LetsEncryptUser
{
Email
:
email
}
privateKey
,
err
:=
rsa
.
GenerateKey
(
rand
.
Reader
,
2048
)
privateKey
,
err
:=
rsa
.
GenerateKey
(
rand
.
Reader
,
rsaKeySize
)
if
err
!=
nil
{
return
user
,
errors
.
New
(
"error generating private key: "
+
err
.
Error
())
}
user
.
K
ey
=
privateKey
user
.
k
ey
=
privateKey
return
user
,
nil
}
func
emailUsername
(
email
string
)
string
{
at
:=
strings
.
Index
(
email
,
"@"
)
if
at
==
-
1
{
return
email
}
return
email
[
:
at
]
}
type
LetsEncryptUser
struct
{
Email
string
Registration
*
acme
.
RegistrationResource
Key
*
rsa
.
PrivateKey
KeyFile
string
key
*
rsa
.
PrivateKey
}
func
(
u
LetsEncryptUser
)
GetEmail
()
string
{
...
...
@@ -31,5 +218,49 @@ func (u LetsEncryptUser) GetRegistration() *acme.RegistrationResource {
return
u
.
Registration
}
func
(
u
LetsEncryptUser
)
GetPrivateKey
()
*
rsa
.
PrivateKey
{
return
u
.
Key
return
u
.
key
}
// savePrivateKey saves an RSA private key to file.
//
// Borrowed from Sebastian Erhart
// https://github.com/xenolf/lego/blob/34910bd541315993224af1f04f9b2877513e5477/crypto.go
func
savePrivateKey
(
key
*
rsa
.
PrivateKey
,
file
string
)
error
{
pemKey
:=
pem
.
Block
{
Type
:
"RSA PRIVATE KEY"
,
Bytes
:
x509
.
MarshalPKCS1PrivateKey
(
key
)}
keyOut
,
err
:=
os
.
Create
(
file
)
if
err
!=
nil
{
return
err
}
pem
.
Encode
(
keyOut
,
&
pemKey
)
keyOut
.
Close
()
return
nil
}
// TODO: Check file permission
func
saveCertificate
(
certBytes
[]
byte
,
file
string
)
error
{
pemCert
:=
pem
.
Block
{
Type
:
"CERTIFICATE"
,
Bytes
:
certBytes
}
certOut
,
err
:=
os
.
Create
(
file
)
if
err
!=
nil
{
return
err
}
pem
.
Encode
(
certOut
,
&
pemCert
)
certOut
.
Close
()
return
nil
}
// loadPrivateKey loads an RSA private key from filename.
//
// Borrowed from Sebastian Erhart
// https://github.com/xenolf/lego/blob/34910bd541315993224af1f04f9b2877513e5477/crypto.go
func
loadPrivateKey
(
file
string
)
(
*
rsa
.
PrivateKey
,
error
)
{
keyBytes
,
err
:=
ioutil
.
ReadFile
(
file
)
if
err
!=
nil
{
return
nil
,
err
}
keyBlock
,
_
:=
pem
.
Decode
(
keyBytes
)
return
x509
.
ParsePKCS1PrivateKey
(
keyBlock
.
Bytes
)
}
type
CertificateMeta
struct
{
Domain
,
URL
string
}
config/setup/tls.go
View file @
a0c8428f
...
...
@@ -11,10 +11,7 @@ import (
func
TLS
(
c
*
Controller
)
(
middleware
.
Middleware
,
error
)
{
if
c
.
Port
!=
"http"
{
c
.
TLS
.
Enabled
=
true
}
if
c
.
Port
==
"http"
{
c
.
TLS
.
Enabled
=
false
}
else
{
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
)
}
...
...
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