04e2d74406
This commit adds a new DNS provider for [acme-dns](https://github.com/joohoi/acme-dns) to allow Lego to set DNS-01 challenge response TXT with an ACME-DNS server automatically. ACME-DNS allows ceding minimal zone editing permissions to the ACME client and can be useful when the primary DNS provider for the zone does not allow scripting/API access but can set a CNAME to an ACME-DNS server. Lower level ACME-DNS API calls & account loading/storing is handled by the `github.com/cpu/goacmedns` library. The provider loads existing ACME-DNS accounts from the specified JSON file on disk. Any accounts the provider registers on behalf of the user will also be saved to this JSON file. When required, the provider handles registering accounts with the ACME-DNS server domains that do not already have an ACME-DNS account. This will halt issuance with an error prompting the user to set the one-time manual CNAME required to delegate the DNS-01 challenge record to the ACME-DNS server. Subsequent runs will use the account from disk and assume the CNAME is in-place.
241 lines
8.4 KiB
Go
241 lines
8.4 KiB
Go
// Let's Encrypt client to go!
|
|
// CLI application for generating Let's Encrypt certificates using the ACME package.
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"text/tabwriter"
|
|
|
|
"github.com/urfave/cli"
|
|
"github.com/xenolf/lego/acme"
|
|
"github.com/xenolf/lego/log"
|
|
)
|
|
|
|
var (
|
|
version = "dev"
|
|
)
|
|
|
|
func main() {
|
|
app := cli.NewApp()
|
|
app.Name = "lego"
|
|
app.Usage = "Let's Encrypt client written in Go"
|
|
|
|
app.Version = version
|
|
|
|
acme.UserAgent = "lego/" + app.Version
|
|
|
|
defaultPath := ""
|
|
cwd, err := os.Getwd()
|
|
if err == nil {
|
|
defaultPath = path.Join(cwd, ".lego")
|
|
}
|
|
|
|
app.Before = func(c *cli.Context) error {
|
|
if c.GlobalString("path") == "" {
|
|
log.Fatal("Could not determine current working directory. Please pass --path.")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
app.Commands = []cli.Command{
|
|
{
|
|
Name: "run",
|
|
Usage: "Register an account, then create and install a certificate",
|
|
Action: run,
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "no-bundle",
|
|
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "must-staple",
|
|
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "revoke",
|
|
Usage: "Revoke a certificate",
|
|
Action: revoke,
|
|
},
|
|
{
|
|
Name: "renew",
|
|
Usage: "Renew a certificate",
|
|
Action: renew,
|
|
Flags: []cli.Flag{
|
|
cli.IntFlag{
|
|
Name: "days",
|
|
Value: 0,
|
|
Usage: "The number of days left on a certificate to renew it.",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "reuse-key",
|
|
Usage: "Used to indicate you want to reuse your current private key for the new certificate.",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "no-bundle",
|
|
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "must-staple",
|
|
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "dnshelp",
|
|
Usage: "Shows additional help for the --dns global option",
|
|
Action: dnshelp,
|
|
},
|
|
}
|
|
|
|
app.Flags = []cli.Flag{
|
|
cli.StringSliceFlag{
|
|
Name: "domains, d",
|
|
Usage: "Add a domain to the process. Can be specified multiple times.",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "csr, c",
|
|
Usage: "Certificate signing request filename, if an external CSR is to be used",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "server, s",
|
|
Value: "https://acme-v02.api.letsencrypt.org/directory",
|
|
Usage: "CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client.",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "email, m",
|
|
Usage: "Email used for registration and recovery contact.",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "accept-tos, a",
|
|
Usage: "By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service.",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "eab",
|
|
Usage: "Use External Account Binding for account registration. Requires --kid and --hmac.",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "kid",
|
|
Usage: "Key identifier from External CA. Used for External Account Binding.",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "hmac",
|
|
Usage: "MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding.",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "key-type, k",
|
|
Value: "rsa2048",
|
|
Usage: "Key type to use for private keys. Supported: rsa2048, rsa4096, rsa8192, ec256, ec384",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "path",
|
|
Usage: "Directory to use for storing the data",
|
|
Value: defaultPath,
|
|
},
|
|
cli.StringSliceFlag{
|
|
Name: "exclude, x",
|
|
Usage: "Explicitly disallow solvers by name from being used. Solvers: \"http-01\", \"dns-01\", \"tls-alpn-01\".",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "webroot",
|
|
Usage: "Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge",
|
|
},
|
|
cli.StringSliceFlag{
|
|
Name: "memcached-host",
|
|
Usage: "Set the memcached host(s) to use for HTTP based challenges. Challenges will be written to all specified hosts.",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "http",
|
|
Usage: "Set the port and interface to use for HTTP based challenges to listen on. Supported: interface:port or :port",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "tls",
|
|
Usage: "Set the port and interface to use for TLS based challenges to listen on. Supported: interface:port or :port",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "dns",
|
|
Usage: "Solve a DNS challenge using the specified provider. Disables all other challenges. Run 'lego dnshelp' for help on usage.",
|
|
},
|
|
cli.IntFlag{
|
|
Name: "http-timeout",
|
|
Usage: "Set the HTTP timeout value to a specific value in seconds. The default is 10 seconds.",
|
|
},
|
|
cli.IntFlag{
|
|
Name: "dns-timeout",
|
|
Usage: "Set the DNS timeout value to a specific value in seconds. The default is 10 seconds.",
|
|
},
|
|
cli.StringSliceFlag{
|
|
Name: "dns-resolvers",
|
|
Usage: "Set the resolvers to use for performing recursive DNS queries. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "pem",
|
|
Usage: "Generate a .pem file by concatanating the .key and .crt files together.",
|
|
},
|
|
}
|
|
|
|
err = app.Run(os.Args)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func dnshelp(c *cli.Context) error {
|
|
fmt.Printf(
|
|
`Credentials for DNS providers must be passed through environment variables.
|
|
|
|
Here is an example bash command using the CloudFlare DNS provider:
|
|
|
|
$ CLOUDFLARE_EMAIL=foo@bar.com \
|
|
CLOUDFLARE_API_KEY=b9841238feb177a84330febba8a83208921177bffe733 \
|
|
lego --dns cloudflare --domains www.example.com --email me@bar.com run
|
|
|
|
`)
|
|
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
|
|
fmt.Fprintln(w, "Valid providers and their associated credential environment variables:")
|
|
fmt.Fprintln(w)
|
|
fmt.Fprintln(w, "\tacme-dns:\tACME_DNS_API_BASE, ACME_DNS_STORAGE_PATH")
|
|
fmt.Fprintln(w, "\tazure:\tAZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID, AZURE_RESOURCE_GROUP")
|
|
fmt.Fprintln(w, "\tauroradns:\tAURORA_USER_ID, AURORA_KEY, AURORA_ENDPOINT")
|
|
fmt.Fprintln(w, "\tbluecat:\tBLUECAT_SERVER_URL, BLUECAT_USER_NAME, BLUECAT_PASSWORD, BLUECAT_CONFIG_NAME, BLUECAT_DNS_VIEW")
|
|
fmt.Fprintln(w, "\tcloudxns:\tCLOUDXNS_API_KEY, CLOUDXNS_SECRET_KEY")
|
|
fmt.Fprintln(w, "\tcloudflare:\tCLOUDFLARE_EMAIL, CLOUDFLARE_API_KEY")
|
|
fmt.Fprintln(w, "\tdigitalocean:\tDO_AUTH_TOKEN")
|
|
fmt.Fprintln(w, "\tdnsimple:\tDNSIMPLE_EMAIL, DNSIMPLE_OAUTH_TOKEN")
|
|
fmt.Fprintln(w, "\tdnsmadeeasy:\tDNSMADEEASY_API_KEY, DNSMADEEASY_API_SECRET")
|
|
fmt.Fprintln(w, "\tduckdns:\tDUCKDNS_TOKEN")
|
|
fmt.Fprintln(w, "\texoscale:\tEXOSCALE_API_KEY, EXOSCALE_API_SECRET, EXOSCALE_ENDPOINT")
|
|
fmt.Fprintln(w, "\tgandi:\tGANDI_API_KEY")
|
|
fmt.Fprintln(w, "\tgandiv5:\tGANDIV5_API_KEY")
|
|
fmt.Fprintln(w, "\tgcloud:\tGCE_PROJECT, GCE_SERVICE_ACCOUNT_FILE")
|
|
fmt.Fprintln(w, "\tglesys:\tGLESYS_API_USER, GLESYS_API_KEY")
|
|
fmt.Fprintln(w, "\tlinode:\tLINODE_API_KEY")
|
|
fmt.Fprintln(w, "\tlightsail:\tAWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, DNS_ZONE")
|
|
fmt.Fprintln(w, "\tmanual:\tnone")
|
|
fmt.Fprintln(w, "\tnamecheap:\tNAMECHEAP_API_USER, NAMECHEAP_API_KEY")
|
|
fmt.Fprintln(w, "\tnamedotcom:\tNAMECOM_USERNAME, NAMECOM_API_TOKEN")
|
|
fmt.Fprintln(w, "\tnifcloud:\tNIFCLOUD_ACCESS_KEY_ID, NIFCLOUD_SECRET_ACCESS_KEY")
|
|
fmt.Fprintln(w, "\trackspace:\tRACKSPACE_USER, RACKSPACE_API_KEY")
|
|
fmt.Fprintln(w, "\trfc2136:\tRFC2136_TSIG_KEY, RFC2136_TSIG_SECRET,\n\t\tRFC2136_TSIG_ALGORITHM, RFC2136_NAMESERVER")
|
|
fmt.Fprintln(w, "\troute53:\tAWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, AWS_HOSTED_ZONE_ID")
|
|
fmt.Fprintln(w, "\tdyn:\tDYN_CUSTOMER_NAME, DYN_USER_NAME, DYN_PASSWORD")
|
|
fmt.Fprintln(w, "\tvegadns:\tSECRET_VEGADNS_KEY, SECRET_VEGADNS_SECRET, VEGADNS_URL")
|
|
fmt.Fprintln(w, "\tvultr:\tVULTR_API_KEY")
|
|
fmt.Fprintln(w, "\tovh:\tOVH_ENDPOINT, OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY")
|
|
fmt.Fprintln(w, "\tpdns:\tPDNS_API_KEY, PDNS_API_URL")
|
|
fmt.Fprintln(w, "\tdnspod:\tDNSPOD_API_KEY")
|
|
fmt.Fprintln(w, "\totc:\tOTC_USER_NAME, OTC_PASSWORD, OTC_PROJECT_NAME, OTC_DOMAIN_NAME, OTC_IDENTITY_ENDPOINT")
|
|
fmt.Fprintln(w, "\tsakuracloud:\tSAKURACLOUD_ACCESS_TOKEN, SAKURACLOUD_ACCESS_TOKEN_SECRET")
|
|
fmt.Fprintln(w, "\texec:\tEXEC_PATH")
|
|
w.Flush()
|
|
|
|
fmt.Println(`
|
|
For a more detailed explanation of a DNS provider's credential variables,
|
|
please consult their online documentation.`)
|
|
|
|
return nil
|
|
}
|