Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue & configure Let's Encrypt certificates #29

Closed
flashmob opened this issue Dec 21, 2016 · 22 comments
Closed

Issue & configure Let's Encrypt certificates #29

flashmob opened this issue Dec 21, 2016 · 22 comments

Comments

@flashmob
Copy link
Owner

Let’s Encrypt and the golang.org/x/crypto/acme/autocert package’s GetCertificate function.

Requirements:

  • Implemented as a set of new commands, eg.

./guerrillad certificate <server-interface> new to issue a new cert & add to config. (using the primary_mail_host as as the FQDN

./guerrillad certificate <server-interface> renew to renew

./guerrillad certificate renew to renew all certificates

@flashmob flashmob changed the title Automatically issue & configure Let's Encrypt certificate Issue & configure Let's Encrypt certificate Dec 21, 2016
@flashmob flashmob changed the title Issue & configure Let's Encrypt certificate Issue & configure Let's Encrypt certificates Dec 21, 2016
@flashmob
Copy link
Owner Author

Note that currently working on configuration file reloading feature, which has the ability to reload TLS config without restarting the server. This means that after each command run, the server can send a SIG_HUP to itself if it running, and TLS certificates will be reloaded.

@ginkoob
Copy link

ginkoob commented Jan 24, 2017

I think the configuration file should include the whitelisted hosts/domains and the certificates should be served/fetched/verified automatically much like the net/http implementation

        m := autocert.Manager{
                Prompt:     autocert.AcceptTOS,
                HostPolicy: autocert.HostWhitelist("example.com", "example2.com"),
                Cache: autocert.DirCache("/root/letsencrypt"),
        }
        s := &http.Server{
                Addr:      ":443",
                TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
        }
        panic(s.ListenAndServeTLS("", ""))

I think /guerrillad certificate <server-interface> new and ./guerrillad certificate renew approach sucks because it's manual and the whole point of letsencrypt is to automate the certificate management.

@flashmob
Copy link
Owner Author

Thanks for feedback.

Well then, perhaps it can be automatically setup if all conditions are met:

  • The hostname is not a local host (and ip is not 127.0.0.1)
  • STARTTLS is enabled or listening on port 465
  • No external certificate specified in config
  • Server is not used as a package (If used as a package, they would need to call a function to have the feature enabled, including their own cert storage handler)

For storage of Let's encrypt certs: would a dot folder be ok?
For renewals, check if the cert is about to expire when we get a SIGHUP, then renew if it matches above conditions.

@schmorrison
Copy link

There are likely a couple caveats. I think LE only permits requests for certs from servers listening on 'web ports', eg. :443, so the A Name record for the domain should be pointed at the IP address that the mail server is running on. This cert could then be used by the mail server, I imagine, without an difficulties.

I imagine the safest, and least breaking, way to do this would be adding options in the config file for ACME configuration(eg. Email address, agreeToTOS, ACME url etc), the user would then leave ServerConfig.PrivateKeyFile and ServerConfig.PublicKeyFile line empty or filled with a placeholder, perhaps "acme" for each host requiring ACME certs. When starting the server, calls to the autocert package will run after parsing the config; obtaining a cert pair for each requiring certs(unless they exist in file already), saving the cert pairs to file, and adding that absolute path to the ServerConfig.PrivateKeyFile and ServerConfig.PublicKeyFile for that host.

I tried to implement a simple example within your package (a couple functions in config.go); not sure why but anything I try to print or log is simply ignored or suppressed. I imagine this has to do with the cobra cli app, but I am not sure how.

Lemme know, if you know why my functions seem to be ignored.

Schmorrison

@flashmob
Copy link
Owner Author

flashmob commented Feb 21, 2017

Hi, thanks for doing some research.

For logging, have you tried setting the log_file to "stderr" or "stdout" in the config? Also, try starting with the -v flag.

FYI, as a convention, config values should be treated as read only, and only can be modified via the config file. That is because the config could be re-loaded at any time, there could be a race condition. To modify a config value, one must modify the file first and then either send a SIGHUP or call readConfig func, and then emit the config change events. See serve.go to see how it's done.

As for placeholders, would prefer not to use placeholders. The server should try to get the certs automatically as much as possible, as long as the above conditions are met. #29 (comment)

Email and 'agree to tos' are needed for account creation? Yes, these would need to be added to the main config, if an account is required.

So it looks like the problem is with how to do "Identifier Validation Challenges". https://github.com/letsencrypt/acme-spec/blob/master/draft-barnes-acme.md#identifier-validation-challenges

Yes, For HTTP based, it looks like it must bind to :443 or :80, which would not be possible, since almost certainly, there would be other servers bound to that port.

So, the alternative is to use DNS based?

We can model on how Caddy does it, for DNS https://caddyserver.com/docs/automatic-https
After having a first look, it seems it has a few plugins which can talk to some of the most common DNS providers. It also loads in the API credentials from the environment.

Maybe sometime in the future, it may be good to propose an SMTP based "Identifier Validation Challenge" to the Lets Encrypt people? For MTAs, the DNS validation is already half-there in the form of an MX records, all they need to do is send an email with the challenge. They don't need to register an account, an email address can be synonymous with an account. Say if they send it to postmaster@example.com then our MTA would parse it. That would make it just as easy to get certificates for MTAs as it is for HTTP servers

@flashmob
Copy link
Owner Author

flashmob commented Feb 22, 2017

Another alternative would be to use a tool called certbot from https://certbot.eff.org/
and writing a plugin: https://certbot.eff.org/docs/contributing.html#dev-plugin
The plugin would update the go-guerrilla config & send a sighup to the server so that the certificate can reload. https://certbot.eff.org/docs/contributing.html#dev-plugin - they have some plugin examples. It's written in Python.

@schmorrison
Copy link

Thanks flashmob.

I was using -v flag but it wasn't revealing any additional info. I will try modifying the log_file line in the config tonight.

Also, duly noted on the config conventions.

So it sounds like the preferred way would be writing a wrapper package for certbot. Using it to request certs manually, then update the config and send a SIGHUP. Then on server startup, with the certificates correctly mapped in the config file, check if the certs are about to expire, and if they are then renew them and send a SIGHUP.

I am sure you are aware, but this will still require having port :443 available for the certbot --standalone, or mapping to the correct webroot directory of the host web server that is occupying that port. I am sure having both options would be advantageous, and I imagine having a separate json config for the certbot cli flags would also make sense.

@flashmob
Copy link
Owner Author

flashmob commented Feb 23, 2017

Thanks for the info. It is now becoming clearer that bunding an Acme client in go-guerrilla may be too much (at least for now). It may bring in some technical debt, especially to support all the various DNS providers, which probably change all the time.

So it seems like certbot does a lot of the heavy lifting. Also, it conforms to the Unix philosophy of "Make each program do one thing well".

With certbot, there is no need to check if the certs are about to expire. Just put certbot renew on cron to run once per day, and then send a sighup to go-guerrilla. Certbot does the renewals automatically.

Cerbot supports the following challenges: tls-sni-01 (port 443), http (port 80), and dns. So yes, unless DNS based challenge is used, certbot must be able to have access to the webroot for that web server.

Btw, certbot is used for go-guerrilla deployment on Guerrillamail.com but we don't have a plugin for it, so some things are manual.

Here is how we deploy it with Nginx. In /etc/nginx/sites-available/default we add the following to the default server block:

location ~ /.well-known {
                allow all;
        }
}

Then run this command:

certbot certonly --webroot -w /home/www/public -d example.com -d www.example.com

where /home/www/public is the webroot. Certificate gets dropped to /etc/letsencrypt/live/grr.la/fullchain.pem and then we enter that to the config for go-guerrilla manually to install it.

In summary, a solution using certbot would be acceptable to claim the bounty, just needs to be a bit less manual : -)

So deliverables to claim the bounty would be:

  • provide a certbot plugin. Assuming that this plugin reads the go-guerrilla config to figure out what cert it needs, does the challenge, updates the go-guerrilla configuration & does sighup. (no need to update config manually). [Note: please scope the plugin before developing, see if the assumption is correct or see if there is any hidden scope]
  • Perhaps go-guerrilla could check if certbot is available, check if the host is able to do challenges and what challenges it can support. This check could be made when starting go-guerrilla with a certain flag, eg --certbot
  • a wiki page documenting all, the way it works, any new options, how to install the plugin, briefly describe certbot and how to install it. etc.

What do you think? (edit: grammar)

@schmorrison
Copy link

Well I will keep it in mind, but unfortunately I have not used Python nearly enough to feel confident writing a plugin.

I'm all up for claiming a bounty, but after this discussion I do not believe this is the one.
At least the issue has been clarified for someone with more experience in Python.

@flashmob
Copy link
Owner Author

flashmob commented Feb 23, 2017

Having a look at Python, it has a lot of similarity to Go, so if you know Go it may be easy to learn Python.

That said, the above are not absolute requirements. In the end, the contributor for this feature can decide on what to implement and how to do it. It should have been clarified that the above specifies the minim that would be accepted.

In summary,

  • We now know what the conditions are for when to enable Let's encrypt automatically.
  • We are clear that we can choose from multiple challenges types, and the details of each one. (If not using DNS challenge, then we need access to Port 80 or 433, or access to the webroot of a an already running web server).
  • We will require new config options, eg email address for account creation and 'agree to terms'
  • We are clear on the minimal solution that would be accepted. (Certbot plugin)

Although we have not fully discussed the scope of a DNS based challenge. Perhaps the minimal solution that could work here is you could start go-guerrilla with --agree option which would then prompt the user to set an A/AAA record for their domain, press any key to continue. Then a goroutine could try the challenge and update the config file once the challenge passed. In that case, there would be a manual step involved. Others, such as Caddy server are able to remove the manual step by using external APsi, however, for us, this requirement may be too much to do on first version. Therefore, if a DNS change requires a manual step then that would be acceptable.

Also, what do you think if we drafted a proposal to the Let's Encrypt people to support an new MX record based challenge? It seems like LE is HTTPS centric, an MX record based challenge would be great for SMTP and possibly even POP. Maybe @ginkoob has some thoughts on this?

@schmorrison
Copy link

Been thinking about this, and while I'm not able to spend enough time to learn Python to write a plugin. I am able to continue thinking about the best way to implement it in Go.
I'll take a close look at Caddy and how they manage the DNS challenges. In lieu of automated DNS challenges, knowing you are amenable to a manual step for the time being, I will think about how to display the txt record that needs to be changed for letsencrypt.

For example, if they know they are doing a DNS challenge; then first, they need to see the challenge value; then update the DNS record manually at their name server; then continue the challenge. It is not immediately clear to me how to execute this outside the main routine as you suggested.

It occurs to me that it could be significantly easier to write, if the user knows which type of challenge they are doing for certificates, and can configure that. If they want to do the "golang.org/x/crypto/acme/autocert" then:

  • we should test for ports open(:80, :443)

If they have configured a webroot of a running server:

  • test that the folder(webroot) exists and we have permissions

In all challenge types, probably:

  • that the domain name resolves locally
  • the folder to store certificates exists

In the case of a "acme/autocert" or webroot, after informing go-guerilla about the desired challenge, it certainly would be possible to execute in a separate goroutine, but if the challenge fails; what is the best way to inform the user?

I'm sure there are a number of incoherent thoughts in there. I want to spend some time this weekend maybe working on this.

schmorrison

@flashmob
Copy link
Owner Author

flashmob commented Mar 3, 2017

Yes, it all boils down to how the user will do the challenge.
Perhaps we can have this set in the config.
In that case, please see cmd/guerrillad/serve.go - there is a CmdConfig struct defined. This struct adds additional options to the server config when starting from the command line.

You can add extra config options to CmdConfig, eg, what challenge method to use, where is the web-root, folder to store certificates, LE account email, accept to the LE TOS, etc.
Somewhere in the serve function, after the config has loaded, you would need to add a call to your processing function, where these config settings and evaluated determine what steps to take. eg. perhaps configureAcme(c *CmdConfig)

Your configureAcme should update the config with new settings. If the configureAcme function changed the config file, i.e it installed a certificate, then you would need to reload the config. ie. err := readConfig(configPath, pidFile, &cmdConfig) (doing it before config is used by the guerrilla package). Maybe it would be acceptable to prompt the user (y/n) to acknowledge if they want to do a step manually if starting from a command line. Timeout after x seconds if no response given.

The tricky part may be to get it to work with auto-config reloading (see sigHandler func. In that case, we can do only if challenges are fully automated. The server can already detect if a certificate changed by comparing the timestamp, and re-configure automatically when a SIGHUP signal is caught.

That said, the code in serve.go is about change after huge refactor - but if you keep the changes around in serve.go and acme processing stuff in a separate file, that should minimise merge conflicts. See the new branch that's about to get merged (pending review) here #71

@schmorrison
Copy link

Duly noted flashmob. I'll let you know if I have any questions.

@flashmob
Copy link
Owner Author

flashmob commented Mar 4, 2017

Ok, sorry a bit in a hurry now and made a few mistakes and omitted a few details - made some edits above. Cheers.

@rahulgi
Copy link

rahulgi commented May 21, 2018

@flashmob is the bounty on this issue still active?

@flashmob
Copy link
Owner Author

@rahulgi Yes! There's been no activity on this for a while - so you're welcome to take over.

@lord-alfred
Copy link
Contributor

lord-alfred commented Aug 14, 2019

Why do need a plugin for certbot? I used deploy hook and I hope that everything will be fine)

@flashmob
Copy link
Owner Author

Going to shelve this feature from the roadmap.

Rationale:

It seems certbot works fairly well for getting LE certificates, and having them enabled in go-guerrilla is just a question of pointing the config to them.

If this feature is re-visited in the future, perhaps it can use the "DNS-01 challenge" using this library https://github.com/go-acme/lego

@alexandrevicenzi
Copy link

@flashmob what is the status on Lets Encrypt? Free certificate is always good, better if built in, no need to use any other scripts to do some hacks.

I think Lego is being used by Traefik, and it seems like a solid choice, if there are any vendor changes, just bump lego version and keep on going.

@flashmob
Copy link
Owner Author

flashmob commented Oct 16, 2020 via email

@flashmob
Copy link
Owner Author

flashmob commented Oct 16, 2020 via email

@schmorrison
Copy link

I have been using Caddy for my routing, and as a bonus it handles SSL certs via LE automatically if required.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants