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
a3a82657
Commit
a3a82657
authored
Oct 17, 2015
by
Matthew Holt
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor letsencrypt code into its own package
parent
307c2ffe
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
318 additions
and
168 deletions
+318
-168
config/config.go
config/config.go
+2
-7
config/letsencrypt/crypto.go
config/letsencrypt/crypto.go
+43
-0
config/letsencrypt/letsencrypt.go
config/letsencrypt/letsencrypt.go
+45
-159
config/letsencrypt/storage.go
config/letsencrypt/storage.go
+128
-0
config/letsencrypt/user.go
config/letsencrypt/user.go
+97
-0
main.go
main.go
+3
-2
No files found.
config/config.go
View file @
a3a82657
...
...
@@ -8,6 +8,7 @@ import (
"sync"
"github.com/mholt/caddy/app"
"github.com/mholt/caddy/config/letsencrypt"
"github.com/mholt/caddy/config/parse"
"github.com/mholt/caddy/config/setup"
"github.com/mholt/caddy/middleware"
...
...
@@ -102,7 +103,7 @@ func Load(filename string, input io.Reader) (Group, error) {
log
.
SetFlags
(
flags
)
// secure all the things
configs
,
err
=
initiateLetsEncrypt
(
configs
)
configs
,
err
=
letsencrypt
.
Activate
(
configs
)
if
err
!=
nil
{
return
nil
,
err
}
...
...
@@ -272,12 +273,6 @@ var (
// Site port
Port
=
DefaultPort
// Let's Encrypt account email
LetsEncryptEmail
string
// Agreement to Let's Encrypt terms
LetsEncryptAgree
bool
)
// Group maps network addresses to their configurations.
...
...
config/letsencrypt/crypto.go
0 → 100644
View file @
a3a82657
package
letsencrypt
import
(
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"os"
)
// saveCertificate saves a DER-encoded (binary format) certificate
// to file.
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
}
// loadRSAPrivateKey loads a PEM-encoded RSA private key from file.
func
loadRSAPrivateKey
(
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
)
}
// saveRSAPrivateKey saves a PEM-encoded RSA private key to file.
func
saveRSAPrivateKey
(
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
}
defer
keyOut
.
Close
()
return
pem
.
Encode
(
keyOut
,
&
pemKey
)
}
config/letsencrypt.go
→
config/letsencrypt
/letsencrypt
.go
View file @
a3a82657
package
config
// TODO: This code is a mess but I'm cleaning it up locally and
// refactoring a bunch. It will have tests, too. Don't worry. :)
package
letsencrypt
import
(
"bufio"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/mholt/caddy/app"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/redirect"
"github.com/mholt/caddy/server"
"github.com/xenolf/lego/acme"
)
// Some essential values related to the Let's Encrypt process
const
(
// Size of RSA keys in bits
rsaKeySize
=
2048
// The base URL to the Let's Encrypt CA
caURL
=
"http://192.168.99.100:4000"
// The port to expose to the CA server for Simple HTTP Challenge
exposePort
=
"5001"
)
// initiateLetsEncrypt sets up TLS for each server config
// in configs as needed. It only skips the config if the
// cert and key are already specified or if plaintext http
// is explicitly specified as the port.
func
initiateLetsEncrypt
(
configs
[]
server
.
Config
)
([]
server
.
Config
,
error
)
{
// Activate sets up TLS for each server config in configs
// as needed. It only skips the config if the cert and key
// are already provided or if plaintext http is explicitly
// specified as the port.
func
Activate
(
configs
[]
server
.
Config
)
([]
server
.
Config
,
error
)
{
// populate 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
)
...
...
@@ -59,7 +38,7 @@ func initiateLetsEncrypt(configs []server.Config) ([]server.Config, error) {
// than one certificate per email address, and still save them individually.
for
leEmail
,
serverConfigs
:=
range
initMap
{
// Look up or create the LE user account
leUser
,
err
:=
get
LetsEncrypt
User
(
leEmail
)
leUser
,
err
:=
getUser
(
leEmail
)
if
err
!=
nil
{
return
configs
,
err
}
...
...
@@ -79,11 +58,11 @@ func initiateLetsEncrypt(configs []server.Config) ([]server.Config, error) {
// TODO: we can just do the agreement once, when registering, right?
err
=
client
.
AgreeToTos
()
if
err
!=
nil
{
save
LetsEncrypt
User
(
leUser
)
// TODO: Might as well try, right? Error check?
saveUser
(
leUser
)
// TODO: Might as well try, right? Error check?
return
configs
,
errors
.
New
(
"error agreeing to terms: "
+
err
.
Error
())
}
err
=
save
LetsEncrypt
User
(
leUser
)
err
=
saveUser
(
leUser
)
if
err
!=
nil
{
return
configs
,
errors
.
New
(
"could not save user: "
+
err
.
Error
())
}
...
...
@@ -103,17 +82,16 @@ func initiateLetsEncrypt(configs []server.Config) ([]server.Config, error) {
// ... that's it. 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
)
os
.
MkdirAll
(
storage
.
Site
(
cert
.
Domain
),
0700
)
// Save cert
err
=
saveCertificate
(
cert
.
Certificate
,
filepath
.
Join
(
certFolder
,
cert
.
Domain
+
".crt"
))
err
=
saveCertificate
(
cert
.
Certificate
,
storage
.
SiteCertFile
(
cert
.
Domain
))
if
err
!=
nil
{
return
configs
,
err
}
// Save private key
err
=
ioutil
.
WriteFile
(
filepath
.
Join
(
certFolder
,
cert
.
Domain
+
".key"
),
cert
.
PrivateKey
,
0600
)
err
=
ioutil
.
WriteFile
(
storage
.
SiteKeyFile
(
cert
.
Domain
),
cert
.
PrivateKey
,
0600
)
if
err
!=
nil
{
return
configs
,
err
}
...
...
@@ -123,7 +101,7 @@ func initiateLetsEncrypt(configs []server.Config) ([]server.Config, error) {
if
err
!=
nil
{
return
configs
,
err
}
err
=
ioutil
.
WriteFile
(
filepath
.
Join
(
certFolder
,
cert
.
Domain
+
".json"
),
jsonBytes
,
0600
)
err
=
ioutil
.
WriteFile
(
storage
.
SiteMetaFile
(
cert
.
Domain
),
jsonBytes
,
0600
)
if
err
!=
nil
{
return
configs
,
err
}
...
...
@@ -131,8 +109,8 @@ func initiateLetsEncrypt(configs []server.Config) ([]server.Config, error) {
// 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"
)
cfg
.
TLS
.
Certificate
=
storage
.
SiteCertFile
(
cfg
.
Host
)
cfg
.
TLS
.
Key
=
storage
.
SiteKeyFile
(
cfg
.
Host
)
cfg
.
TLS
.
Enabled
=
true
cfg
.
Port
=
"https"
...
...
@@ -188,12 +166,12 @@ func getEmail(cfg server.Config) string {
leEmail
:=
cfg
.
TLS
.
LetsEncryptEmail
if
leEmail
==
""
{
// Then try memory (command line flag or typed by user previously)
leEmail
=
LetsEncryp
tEmail
leEmail
=
Defaul
tEmail
}
if
leEmail
==
""
{
// Then try to get most recent user email ~/.caddy/users file
// TODO: Probably better to open the user's json file and read the email out of there...
userDirs
,
err
:=
ioutil
.
ReadDir
(
filepath
.
Join
(
app
.
DataFolder
(),
"letsencrypt"
,
"users"
))
userDirs
,
err
:=
ioutil
.
ReadDir
(
storage
.
Users
(
))
if
err
==
nil
{
var
mostRecent
os
.
FileInfo
for
_
,
dir
:=
range
userDirs
{
...
...
@@ -204,7 +182,9 @@ func getEmail(cfg server.Config) string {
mostRecent
=
dir
}
}
leEmail
=
mostRecent
.
Name
()
if
mostRecent
!=
nil
{
leEmail
=
mostRecent
.
Name
()
}
}
}
if
leEmail
==
""
{
...
...
@@ -216,135 +196,41 @@ func getEmail(cfg server.Config) string {
if
err
!=
nil
{
return
""
}
LetsEncryp
tEmail
=
leEmail
Defaul
tEmail
=
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
,
rsaKeySize
)
if
err
!=
nil
{
return
user
,
errors
.
New
(
"error generating private key: "
+
err
.
Error
())
}
user
.
key
=
privateKey
return
user
,
nil
}
var
(
// Let's Encrypt account email to use if none provided
DefaultEmail
string
func
emailUsername
(
email
string
)
string
{
at
:=
strings
.
Index
(
email
,
"@"
)
if
at
==
-
1
{
return
email
}
return
email
[
:
at
]
}
// Whether user has agreed to the Let's Encrypt SA
Agreed
bool
)
type
LetsEncryptUser
struct
{
Email
string
Registration
*
acme
.
RegistrationResource
KeyFile
string
key
*
rsa
.
PrivateKey
}
// Some essential values related to the Let's Encrypt process
const
(
// Size of RSA keys in bits
rsaKeySize
=
2048
func
(
u
LetsEncryptUser
)
GetEmail
()
string
{
return
u
.
Email
}
func
(
u
LetsEncryptUser
)
GetRegistration
()
*
acme
.
RegistrationResource
{
return
u
.
Registration
}
func
(
u
LetsEncryptUser
)
GetPrivateKey
()
*
rsa
.
PrivateKey
{
return
u
.
key
}
// The base URL to the Let's Encrypt CA
caURL
=
"http://192.168.99.100:4000"
// 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
}
// The port to expose to the CA server for Simple HTTP Challenge
exposePort
=
"5001"
)
// 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
}
// KeySize represents the length of a key in bits
type
KeySize
int
// 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
)
}
// Key sizes
const
(
ECC_224
KeySize
=
224
ECC_256
=
256
RSA_2048
=
2048
RSA_4096
=
4096
)
type
CertificateMeta
struct
{
Domain
,
URL
string
...
...
config/letsencrypt/storage.go
0 → 100644
View file @
a3a82657
package
letsencrypt
import
(
"path/filepath"
"strings"
"github.com/mholt/caddy/app"
)
// storage is used to get file paths in a consistent,
// cross-platform way for persisting Let's Encrypt assets
// on the file system.
var
storage
=
Storage
(
filepath
.
Join
(
app
.
DataFolder
(),
"letsencrypt"
))
// Storage is a root directory and facilitates
// forming file paths derived from it.
type
Storage
string
func
(
s
Storage
)
Path
(
parts
...
string
)
string
{
return
filepath
.
Join
(
append
([]
string
{
string
(
s
)},
parts
...
)
...
)
}
// Sites gets the directory that stores site certificate and keys.
func
(
s
Storage
)
Sites
()
string
{
return
filepath
.
Join
(
string
(
s
),
"sites"
)
}
// Site returns the path to the folder containing assets for domain.
func
(
s
Storage
)
Site
(
domain
string
)
string
{
return
filepath
.
Join
(
s
.
Sites
(),
domain
)
}
// CertFile returns the path to the certificate file for domain.
func
(
s
Storage
)
SiteCertFile
(
domain
string
)
string
{
return
filepath
.
Join
(
s
.
Site
(
domain
),
domain
+
".crt"
)
}
// SiteKeyFile returns the path to domain's private key file.
func
(
s
Storage
)
SiteKeyFile
(
domain
string
)
string
{
return
filepath
.
Join
(
s
.
Site
(
domain
),
domain
+
".key"
)
}
// SiteMetaFile returns the path to the domain's asset metadata file.
func
(
s
Storage
)
SiteMetaFile
(
domain
string
)
string
{
return
filepath
.
Join
(
s
.
Site
(
domain
),
domain
+
".json"
)
}
// Users gets the directory that stores account folders.
func
(
s
Storage
)
Users
()
string
{
return
filepath
.
Join
(
string
(
s
),
"users"
)
}
// User gets the account folder for the user with email.
func
(
s
Storage
)
User
(
email
string
)
string
{
return
filepath
.
Join
(
s
.
Users
(),
email
)
}
// UserRegFile gets the path to the registration file for
// the user with the given email address.
func
(
s
Storage
)
UserRegFile
(
email
string
)
string
{
fileName
:=
emailUsername
(
email
)
if
fileName
==
""
{
fileName
=
"registration"
}
return
filepath
.
Join
(
s
.
User
(
email
),
fileName
+
".json"
)
}
// UserKeyFile gets the path to the private key file for
// the user with the given email address.
func
(
s
Storage
)
UserKeyFile
(
email
string
)
string
{
// TODO: Read the KeyFile property in the registration file instead?
fileName
:=
emailUsername
(
email
)
if
fileName
==
""
{
fileName
=
"private"
}
return
filepath
.
Join
(
s
.
User
(
email
),
fileName
+
".key"
)
}
// emailUsername returns the username portion of an
// email address (part before '@') or the original
// input if it can't find the "@" symbol.
func
emailUsername
(
email
string
)
string
{
at
:=
strings
.
Index
(
email
,
"@"
)
if
at
==
-
1
{
return
email
}
return
email
[
:
at
]
}
/*
// StorageDir is the full path to the folder where this Let's
// Encrypt client will set up camp. In other words, where it
// stores user account information, keys, and certificates.
// All files will be contained in a 'letsencrypt' folder
// within StorageDir.
//
// Changing this after the program has accessed this folder
// will result in undefined behavior.
var StorageDir = "."
// Values related to persisting things on the file system
const (
// ContainerDir is the name of the folder within StorageDir
// in which files or folders are placed.
ContainerDir = "letsencrypt"
// File that contains information about the user's LE account
UserRegistrationFile = "registration.json"
)
// BaseDir returns the full path to the base directory in which
// files or folders may be placed, e.g. "<StorageDir>/letsencrypt".
func BaseDir() string {
return filepath.Join(StorageDir, ContainerDir)
}
// AccountsDir returns the full path to the directory where account
// information is stored for LE users.
func AccountsDir() string {
return filepath.Join(BaseDir(), "users")
}
// AccountsDir gets the full path to the directory for a certain
// user with the email address email.
func AccountDir(email string) string {
return filepath.Join(AccountsDir(), email)
}
*/
config/letsencrypt/user.go
0 → 100644
View file @
a3a82657
package
letsencrypt
import
(
"crypto/rand"
"crypto/rsa"
"encoding/json"
"errors"
"io/ioutil"
"os"
"github.com/xenolf/lego/acme"
)
type
User
struct
{
Email
string
Registration
*
acme
.
RegistrationResource
KeyFile
string
key
*
rsa
.
PrivateKey
}
func
(
u
User
)
GetEmail
()
string
{
return
u
.
Email
}
func
(
u
User
)
GetRegistration
()
*
acme
.
RegistrationResource
{
return
u
.
Registration
}
func
(
u
User
)
GetPrivateKey
()
*
rsa
.
PrivateKey
{
return
u
.
key
}
// getUser loads the user with the given email from disk.
func
getUser
(
email
string
)
(
User
,
error
)
{
var
user
User
// open user file
regFile
,
err
:=
os
.
Open
(
storage
.
UserRegFile
(
email
))
if
err
!=
nil
{
if
os
.
IsNotExist
(
err
)
{
// create a new user
return
newUser
(
email
)
}
return
user
,
err
}
defer
regFile
.
Close
()
// load user information
err
=
json
.
NewDecoder
(
regFile
)
.
Decode
(
&
user
)
if
err
!=
nil
{
return
user
,
err
}
// load their private key
user
.
key
,
err
=
loadRSAPrivateKey
(
user
.
KeyFile
)
if
err
!=
nil
{
return
user
,
err
}
return
user
,
nil
}
// saveUser persists a user's key and account registration
// to the file system.
func
saveUser
(
user
User
)
error
{
// make user account folder
err
:=
os
.
MkdirAll
(
storage
.
User
(
user
.
Email
),
0700
)
if
err
!=
nil
{
return
err
}
// save private key file
user
.
KeyFile
=
storage
.
UserKeyFile
(
user
.
Email
)
err
=
saveRSAPrivateKey
(
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
(
storage
.
UserRegFile
(
user
.
Email
),
jsonBytes
,
0600
)
}
// newUser creates a new User for the given email address
// with a new private key. This function does not register
// the user via ACME.
func
newUser
(
email
string
)
(
User
,
error
)
{
user
:=
User
{
Email
:
email
}
privateKey
,
err
:=
rsa
.
GenerateKey
(
rand
.
Reader
,
rsaKeySize
)
if
err
!=
nil
{
return
user
,
errors
.
New
(
"error generating private key: "
+
err
.
Error
())
}
user
.
key
=
privateKey
return
user
,
nil
}
main.go
View file @
a3a82657
...
...
@@ -15,6 +15,7 @@ import (
"github.com/mholt/caddy/app"
"github.com/mholt/caddy/config"
"github.com/mholt/caddy/config/letsencrypt"
"github.com/mholt/caddy/server"
)
...
...
@@ -33,8 +34,8 @@ func init() {
flag
.
StringVar
(
&
config
.
Host
,
"host"
,
config
.
DefaultHost
,
"Default host"
)
flag
.
StringVar
(
&
config
.
Port
,
"port"
,
config
.
DefaultPort
,
"Default port"
)
flag
.
BoolVar
(
&
version
,
"version"
,
false
,
"Show version"
)
flag
.
BoolVar
(
&
config
.
LetsEncryptAgree
,
"agree"
,
false
,
"Agree to Let's Encrypt Subscriber Agreement"
)
flag
.
StringVar
(
&
config
.
LetsEncryptEmail
,
"email"
,
""
,
"Email address to use for Let's Encrypt account
"
)
flag
.
BoolVar
(
&
letsencrypt
.
Agreed
,
"agree"
,
false
,
"Agree to Let's Encrypt Subscriber Agreement"
)
flag
.
StringVar
(
&
letsencrypt
.
DefaultEmail
,
"email"
,
""
,
"Default email address to use for Let's Encrypt transactions
"
)
}
func
main
()
{
...
...
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