Skip to content

Commit

Permalink
Merge branch 'master' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
hlandau committed Dec 6, 2015
2 parents 19c7895 + be55958 commit 83f529c
Show file tree
Hide file tree
Showing 9 changed files with 434 additions and 18 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ sudo: false
services:
- rabbitmq

install:
- go get -v -t ./...
script:
- source ./.travis/script
after_success:
Expand Down
7 changes: 4 additions & 3 deletions .travis/script
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,13 @@ echo Cross-compiling releases...
mkdir -p "$GOPATH/releasing/idist" "$GOPATH/releasing/dist"

# cgo crosscompile
REPOS="github.com/hlandau/acme/cmd/acmetool"
gox -cgo -osarch 'linux/386 linux/amd64' -output "$GOPATH/releasing/idist/{{.Dir}}-$TRAVIS_TAG-{{.OS}}_{{.Arch}}/bin/acmetool" $REPOS
REPO=github.com/$TRAVIS_REPO_SLUG
BINARIES=$REPO/cmd/acmetool
gox -cgo -osarch 'linux/386 linux/amd64' -output "$GOPATH/releasing/idist/acmetool-$TRAVIS_TAG-{{.OS}}_{{.Arch}}/bin/{{.Dir}}" $BINARIES
RESULT1=$?

# non-cgo crosscompile
gox -osarch 'darwin/386 darwin/amd64 linux/arm freebsd/386 freebsd/amd64 freebsd/arm openbsd/386 openbsd/amd64 netbsd/386 netbsd/amd64 netbsd/arm dragonfly/amd64 solaris/amd64' -output "$GOPATH/releasing/idist/{{.Dir}}-$TRAVIS_TAG-{{.OS}}_{{.Arch}}/bin/acmetool" $REPOS
gox -osarch 'darwin/386 darwin/amd64 linux/arm freebsd/386 freebsd/amd64 freebsd/arm openbsd/386 openbsd/amd64 netbsd/386 netbsd/amd64 netbsd/arm dragonfly/amd64 solaris/amd64' -output "$GOPATH/releasing/idist/acmetool-$TRAVIS_TAG-{{.OS}}_{{.Arch}}/bin/{{.Dir}}" $BINARIES
RESULT2=$?

# Defer exiting to get as much error output as possible upfront.
Expand Down
16 changes: 10 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ BINARIES=$(PROJNAME)/cmd/acmetool
# v1.8 NNSC:github.com/hlandau/degoutils/_stdenv/Makefile.ref
# This is a standard Makefile for building Go code designed to be copied into
# other projects. Code below this line is not intended to be modified.
#
# NOTE: Use of this Makefile is not mandatory. People familiar with the use
# of the "go" command who have a GOPATH setup can use go get/go install.

# XXX: prebuild-checks needs bash, fix this at some point
SHELL := /bin/bash

-include Makefile.extra
-include Makefile.assets
Expand Down Expand Up @@ -47,20 +53,18 @@ all: prebuild-checks $(DIRS)
prebuild-checks:
$(call QI,RELOCATE)if [ `find . -iname '*.go' | grep -v ./src/ | wc -l` != 0 ]; then \
if [ -e "$(GOPATH)/src/$(PROJNAME)/" ]; then \
echo "GOPATH/src/$(PROJNAME)/ already exists, can't auto-relocate."; \
echo "$$GOPATH/src/$(PROJNAME)/ already exists, can't auto-relocate. Since you appear to have a GOPATH configured, just use go get -u '$(PROJNAME)/...; go install $(BINARIES)'. Alternatively, move this Makefile to either GOPATH or an empty directory outside GOPATH (preferred) and run it. Or delete '$$GOPATH/src/$(PROJNAME)/'."; \
exit 1; \
fi; \
echo Relocating makefile.; \
mkdir -p "$(GOPATH)/src/$(PROJNAME)/"; \
for x in ./* ./.*; do \
[ "$$x" == "./src" ] && continue; \
mv -n "$$x" "$(GOPATH)/src/$(PROJNAME)/"; \
done; \
ln -s "$(GOPATH)/src/$(PROJNAME)/Makefile"; \
[ -e "$(GOPATH)/src/$(PROJNAME)/_doc" ] && ln -s "$(GOPATH)/src/$(PROJNAME)/_doc" doc; \
echo Relocated, please run make again.; \
exit 1; \
fi
fi; \
exit 0

$(DIRS): | .gotten
$(call QI,DIRS)mkdir -p $(GOPATH)/src $(GOBIN); \
Expand All @@ -82,6 +86,6 @@ test:
$(call QI,GO-TEST,$(PROJNAME))for x in $(PROJNAME); do go test -cover -v $$x/...; done

install: all
$(call QI,INSTALL,foo)for x in $(BINARIES); do \
$(call QI,INSTALL,$(BINARIES))for x in $(BINARIES); do \
install -Dp $(GOBIN)/`basename "$$x"` $(DESTDIR)$(PREFIX)/bin; \
done
228 changes: 225 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,229 @@
# ACME Client Utilities [![Build Status](https://travis-ci.org/hlandau/acme.svg?branch=master)](https://travis-ci.org/hlandau/acme)

Command line tools for obtaining certificates from an ACME server.
acmetool is an easy-to-use command line tool for automatically acquiring
certificates from ACME servers (such as Let's Encrypt). Designed to flexibly
integrate into your webserver setup to enable automatic verification. Unlike
the official Let's Encrypt client, this doesn't modify your web server
configuration.

## [Binary releases](https://github.com/hlandau/acme/releases)
You can perform verifications using port 80 or 443 (if you don't yet have a
server running on one of them); via webroot; by configuring your webserver to
proxy requests for `/.well-known/acme-challenge/` to a special port (402) which
acmetool can listen on; or by configuring your webserver not to listen on port
80, and instead running acmetool's built in HTTPS redirector (and challenge
responder) on port 80. This is useful if all you want to do with port 80 is
redirect people to port 443.

You can run acmetool on a cron job to renew certificates automatically (`acmetool --batch`). The
preferred certificate for a given hostname is always at
`/var/lib/acme/live/HOSTNAME/{cert,chain,fullchain,privkey}`. You can configure
acmetool to reload your webserver automatically when it renews a certificate.

acmetool is intended to be "magic-free". All of acmetool's state is stored in a
simple, comprehensible directory of flat files. [The schema for this directory
is documented.](https://github.com/hlandau/acme/blob/master/_doc/SCHEMA.md)

acmetool is intended to work like "make". The state directory expresses target
domain names, and whenever acmetool is invoked, it ensures that valid
certificates are available to meet those names. Certificates which will expire
soon are renewed. acmetool is thus idempotent and minimises the use of state.

acmetool can optionally be used [without running it as
root.](https://github.com/hlandau/acme/blob/master/_doc/NOROOT.md) If you have
existing certificates issued using the official client, acmetool can import
those certificates, keys and account keys (`acmetool import-le`).

## Getting Started

[**Binary releases are also available.**](https://github.com/hlandau/acme/releases)

You will need Go installed to build from source.

If you are on Linux, you will need to make sure the development files for
`libcap` are installed. This is probably a package for your distro called
`libcap-dev` or `libcap-devel` or similar.

```bash
$ git clone https://github.com/hlandau/acme
$ cd acme
$ make && sudo make install

# (People familiar with Go with a GOPATH setup can alternatively use go get/go install:)
$ go get github.com/hlandau/acme/cmd/acmetool

# Run the quickstart wizard. Sets up account, cronjob, etc.
$ sudo acmetool quickstart

# Configure your webserver to serve challenges if necessary.
# See https://github.com/hlandau/acme/blob/master/_doc/WSCONFIG.md
$ ...

# Request the hostnames you want:
$ sudo acmetool want example.com www.example.com

# Now you have certificates:
$ ls -l /var/lib/acme/live/example.com/
```

<!-- # Renew certificates automatically:
# Change '42' to a random integer in [0,59] to distribute the load on the server.
$ sudo /bin/sh -c "echo '42 0 * * * root /usr/local/bin/acmetool -batch' > /etc/cron.d/acmetool" -->

The `quickstart` subcommand is a recommended wizard which guides you through the
setup of ACME on your system.

The `want` subcommand states that you want a certificate for the given hostnames.
(If you want separate certificates for each of the hostnames, run the want
subcommand separately for each hostname.)

The default subcommand, `reconcile`, is like "make" and makes sure all desired
hostnames are satisfied by valid certificates which aren't soon to expire.
`want` calls `reconcile` automatically.

If you run `acmetool reconcile` on a cronjob to facilitate automatic renewal,
pass `--batch` to ensure it doesn't attempt to interact with a terminal.

<!--
## Introduction
- A command line tool for acquiring certificates using a certificate storage
repository to automatically determine what certificates need to be requested.
- Acquiring a certificate is as simple as this:
`# acmetool want example.com`
If successfully acquired, the certificate will be placed in
`/var/lib/acme/live/example.com/{cert,chain,fullchain,privkey}`.
Running `acmetool -``-batch` as root on a cronjob will allow it to
automatically reacquire certificates before they expire. The certificate data
in `/var/lib/acme/live/example.com` will be updated automatically with the
new certificate. acmetool can optionally invoke a shell script after having
changed certificates if you need to reload a webserver.
- Works with Let's Encrypt.
- acmetool is designed to work like `make`. A filesystem-based certificate
repository expresses target domain names, and whenever acmetool is invoked,
it ensures that valid certificates are available to meet those names.
Certificates which will expire soon are renewed. The certificate matching
each target is symlinked into `/var/lib/acme/live/DOMAIN`, so the right
certificate for a given domain is always at `/var/lib/acme/live/DOMAIN`.
acmetool is thus idempotent and it minimises the use of state. All state is
explicitly kept in the certificate repository. There are essentially no
proprietary file formats or configuration or state files; only a repository
of certificates, a repository of ACME account keys and a set of targets. On
each invocation, ACME figures out which certificates satisfy which targets
and obtains certificates as necessary.
[Details on the state directory format.](https://github.com/hlandau/acme/blob/master/_doc/SCHEMA.md)
-->

## Validation Options

<img src="https://i.imgur.com/w8TbgLL.png" align="right" alt="[screenshot]" />

**Webroot:** acmetool can place challenge files in a given directory, allowing your normal
web server to serve them. The files must be served from the path you specify at
`/.well-known/acme-challenge/`.

[Information on configuring your web server.](https://github.com/hlandau/acme/blob/master/_doc/WSCONFIG.md)

**Proxy:** acmetool can respond to validation challenges by serving them on port 402. In
order for this to be useful, you must configure your webserver to proxy
requests under `/.well-known/acme-challenge/` to
`http://127.0.0.1:402/.well-known/acme-challenge`.

[Information on configuring your web server.](https://github.com/hlandau/acme/blob/master/_doc/WSCONFIG.md)

**Redirector:** `acmetool redirector` starts an HTTP server on port 80 which redirects all
requests to HTTPS, as well as serving any necessary validation responses. The
`acmetool quickstart` wizard can set it up for you if you use systemd.
Otherwise, you'll need to configure your system to run `acmetool redirector
--service.uid=USERNAME --service.daemon=1` as a service, where `USERNAME` is
the username you want the daemon to drop to.

Make sure your web server is not listening on port 80.

**Listen:** If you are for some reason not running anything on port 80 or 443, acmetool
will use those ports. Either port being available is sufficient. This is only
really useful for development purposes.

## Library

The client library which these utilities use
(`github.com/hlandau/acme/acmeapi`) can be used independently by any Go code.
[![GoDoc](https://godoc.org/github.com/hlandau/acme/acmeapi?status.svg)](https://godoc.org/github.com/hlandau/acme/acmeapi)

[Source code.](https://github.com/hlandau/acme)

## Comparison with...

**Let's Encrypt Official Client:** A heavyweight Python implementation which is
a bit too “magic” for my tastes. Tries to mutate your webserver configuration
automatically.

acmetool is a single-file binary which only depends on basic system libraries
(on Linux, these are libc, libpthread, libcap, libattr). It doesn't do anything
to your webserver; it just places certificates at a standard location and can
also reload your webserver (whichever webserver it is) by executing hook shell
scripts.

acmetool isn't based around individual transactions for obtaining certificates;
it's about satisfying expressed requirements by any means necessary. Its
comprehensible, magic-free state directory makes it as stateless and idempotent
as possible.

**lego:** Like acmetool, [xenolf/lego](https://github.com/xenolf/lego) provides
a library and client utility. The utility provides commands for creating
certificates, but doesn't provide a compelling system for managing the lifetime
of the short-lived certificates offered by Let's Encrypt. The user is expected
to generate and install all certificates manually.

**gethttpsforfree:**
[diafygi/gethttpsforfree](https://github.com/diafygi/gethttpsforfree) provides
an HTML file which uses JavaScript to make requests to an ACME server and
obtain certificates. It's a functional user interface, but like lego it
provides no answer for the automation issue, and is thus impractical given the
short lifetime of certificates issued by Let's Encrypt.

### Comparison, list of client implementations

<table>
<tr><td></td><th>acmetool</th><th><a href="https://github.com/letsencrypt/letsencrypt">letsencrypt</a></th><th><a href="https://github.com/xenolf/lego">lego</a></th><th><a href="https://github.com/diafygi/gethttpsforfree">gethttpsforfree</a></th></tr>
<tr><td>Automatic renewal</td><td>Yes</td><td>Not yet</td><td>No</td><td>No</td></tr>
<tr><td>State management</td><td>Yes†</td><td>Yes</td><td>—</td><td>—</td></tr>
<tr><td>Single-file binary</td><td>Yes</td><td>No</td><td>Yes</td><td>Yes</td></tr>
<tr><td>Quickstart wizard</td><td>Yes</td><td>Yes</td><td>No</td><td>No</td></tr>
<tr><td>Modifies webserver config</td><td>No</td><td>By default</td><td>No</td><td>No</td></tr>
<tr><td>Non-root support</td><td><a href="https://github.com/hlandau/acme/blob/master/_doc/NOROOT.md">Optional</a></td><td>No</td><td>Optional</td><td>—</td></tr>
<tr><td>Supports Apache</td><td>Yes</td><td>Yes</td><td>—</td><td>—</td></tr>
<tr><td>Supports nginx</td><td>Yes</td><td>Experimental</td><td>—</td><td>—</td></tr>
<tr><td>Supports HAProxy</td><td>Yes</td><td>No</td><td>—</td><td>—</td></tr>
<tr><td>Supports any web server</td><td>Yes</td><td>Webroot‡</td><td>—</td><td>—</td></tr>
<tr><td>Authorization via webroot</td><td>Yes</td><td>Yes</td><td>—</td><td>Manual</td></tr>
<tr><td>Authorization via port 80 redirector</td><td>Yes</td><td>No</td><td>No</td><td>No</td></tr>
<tr><td>Authorization via proxy</td><td>Yes</td><td>No</td><td>No</td><td>No</td></tr>
<tr><td>Authorization via listener§</td><td>Yes</td><td>Yes</td><td>Yes</td><td>No</td></tr>
<tr><td>Import state from official client</td><td>Yes</td><td>—</td><td>—</td><td>—</td></tr>
</table>

† acmetool has a different philosophy to state management and configuration to
the Let's Encrypt client; see the beginning of this README.

‡ The webroot method does not appear to provide any means of reloading the
webserver once the certificate has been changed, which means auto-renewal
requires manual intervention.

§ Requires downtime.

This table is maintained in good faith; I believe the above comparison to be
accurate. If notified of any inaccuracies, I will rectify the table and publish
a notice of correction here.

## Licence

© 2015 Hugo Landau <[email protected]> MIT License

## [Click here for introduction, README and build instructions](https://github.com/hlandau/acme.t)
93 changes: 93 additions & 0 deletions _doc/NOROOT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
Rootless support
================

acmetool has experimental support for root-free operation.

In order to run root-free, after installing acmetool in `/usr/local/bin` (or
wherever you want it), before running acmetool, do the following:

- Create a new user `acme` <small>(or whatever you want)</small>.

- Create the directory `/var/lib/acme` and change the owning user and group to
`acme`. <small>(You can use a different directory, but you must then make sure you
pass `--state PATH` to all invocations of acmetool.)</small>

- Create the directory `/usr/lib/acme/hooks` <small>(`/usr/libexec/acme/hooks` on
distros which use libexec)</small>. Make it writable by `acme` for the time being by
changing the group to `acme` and making the directory group-writable. (You
can make this read-only after running the quickstart process, which places
some shell scripts in here to reload servers. You can audit these scripts
yourself or use your own if you wish.)

- Change to the user `acme` and run `acmetool quickstart`.

$ sudo -u acme acmetool quickstart

A crontab will be installed automatically as the `acme` user; you may wish to
examine it.

- As root, make the `hooks` directory root-owned/not group writable once more.
Ensure that the scripts are root-owned:

# chown -R root:root /usr/lib*/acme/hooks
# chmod 755 /usr/lib*/acme/hooks

Inspect the hook scripts if you wish. Mark the hook scripts setuid:

# chmod u+s /usr/lib*/acme/hooks/*

UNIX systems don't support setuid shell scripts, so this bit is ignored.
Rather, acmetool takes it as a flag to tell it to run these scripts via
`sudo`. This is necessary so that web servers, etc. can be reloaded.

The conditions for running using `sudo` are that the files have the setuid
bit set, that they be owned be root, that they be scripts and not binaries,
and that acmetool is not being run as root.

- Setup sudo. You will need to edit the sudoers file so that the hook scripts
(which you have inspected and trust) can be executed by acmetool. It is
essential that these have the `NOPASSWD` flag as the scripts must be executable
noninteractively.

`# visudo`

Add the line:

`acme ALL=(root) NOPASSWD: /usr/.../acme/hooks/`

Replace `...` above with `lib` or `libexec` as appropriate.

- Setup your challenge method:

**Webroot:** Make sure the `acme` user can write to the webroot directory you
configured.

**Redirector:** Make sure the directory `/var/run/acme/acme-challenge` is
writable by the `acme` user. `acmetool` puts challenges here because the
redirector looks here (internally it's a glorified webroot mode).

Note that `/var/run` will be a tmpfs on many modern OSes, so the directory
ceases to exist on reboots. The redirector will try to create the directory
(as user root, mode 0755) if it doesn't exist. This happens before the
redirector drops privileges from root. (It has to run as root initially to
bind to port 80.)

A configuration option has been added to make the redirector ensure that
the directory is writable by a certain group when starting up. When this
option is used, mode 0775 is used instead and the group owner is changed
to a given GID.

Pass `--challenge-gid=GID` to `acmetool redirector` (edit your service
manager's configuration, e.g. the systemd unit file), where GID is the
numeric group ID of the group owner for the challenge directory (i.e. the GID
of the `acme` group). (Group names rather than IDs may be supported on some
platforms, but this is not guaranteed and will vary. Use of a GID is
recommended.)

**Proxy:** If you are using the proxy method, you won't be able to listen on
port 402 as a non-root user. Use port 4402 instead, which acmetool will try
to use instead.

**Listener:** Not usable under non-root operation, as it would not be able
to bind to ports 80/443. But this is not really relevant as this mode is
not useful for anything other than development anyway.
Loading

0 comments on commit 83f529c

Please sign in to comment.