Merge branch 'master' into fix-wfe-test-better

This commit is contained in:
Roland Bracewell Shoemaker 2015-09-11 11:58:28 -07:00
commit d55e0e0d8d
7 changed files with 146 additions and 88 deletions

View File

@ -33,7 +33,7 @@ EXPOSE 4000
ENV BOULDER_CONFIG /go/src/github.com/letsencrypt/boulder/test/boulder-config.json
# Get the Let's Encrypt client
RUN git clone https://www.github.com/letsencrypt/lets-encrypt-preview.git /letsencrypt
RUN git clone https://www.github.com/letsencrypt/letsencrypt.git /letsencrypt
WORKDIR /letsencrypt
RUN ./bootstrap/debian.sh && \
apt-get clean && \
@ -43,20 +43,13 @@ RUN ./bootstrap/debian.sh && \
RUN virtualenv --no-site-packages -p python2 venv && \
./venv/bin/pip install -r requirements.txt -e acme -e .[dev,docs,testing] -e letsencrypt-apache -e letsencrypt-nginx
RUN go get bitbucket.org/liamstask/goose/cmd/goose
ENV LETSENCRYPT_PATH /letsencrypt
# Copy in the Boulder sources
COPY . /go/src/github.com/letsencrypt/boulder
# Build Boulder
RUN go install \
github.com/letsencrypt/boulder/cmd/activity-monitor \
github.com/letsencrypt/boulder/cmd/boulder-ca \
github.com/letsencrypt/boulder/cmd/boulder-ra \
github.com/letsencrypt/boulder/cmd/boulder-sa \
github.com/letsencrypt/boulder/cmd/boulder-va \
github.com/letsencrypt/boulder/cmd/boulder-wfe
WORKDIR /go/src/github.com/letsencrypt/boulder
CMD ["bash", "-c", "service mysql start && \
service rsyslog start && \

View File

@ -7,49 +7,30 @@ This is an initial implementation of an ACME-based CA. The [ACME protocol](https
[![Build Status](https://travis-ci.org/letsencrypt/boulder.svg)](https://travis-ci.org/letsencrypt/boulder)
[![Coverage Status](https://coveralls.io/repos/letsencrypt/boulder/badge.svg)](https://coveralls.io/r/letsencrypt/boulder)
Docker
Quickstart
------
Boulder is available as a [Docker image from Quay.io](https://quay.io/repository/letsencrypt/boulder). The Docker image expects the `config.json` file to be located at `/boulder/config.json` within the container.
Boulder has a Dockerfile to make it easy to install and set up all its
dependencies. This approach is most suitable if you just need to set up Boulder
for the purpose of testing client software against it. To start Boulder
in a Docker container, run:
(Note: You can override the `config.json` location by specifying a different BOULDER_CONFIG environment variable, such as with `-e BOULDER_CONFIG=mypath/myfile.config`.)
./test/run-docker.sh
There are no default commands; you must choose one of the executables from the `cmd` path.
There are several tags available:
- `stable` is maintained by the Let's Encrypt team as a fairly stable copy of Boulder.
- `latest` is a more recent build of Boulder. It may lag behind the `master` ref, as automated builds are being reworked.
- Tags for individual short-format git refs, representing those builds.
A quick-start method for running a Boulder instance is to use one of the example configurations:
docker run -i --name=boulder --read-only=true --rm=true -p 4000:4000 quay.io/letsencrypt/boulder:latest
Alternatively, to run all services locally, using AMQP to pass messages between them, you can use:
```
> python start.py
# start.py will use the configuration specified by BOULDER_CONFIG or test/boulder-config.json
```
To run a single module, specifying the AMQP server, you might use something more like:
```
> docker run --name=boulder --read-only=true --rm=true -v $(pwd)/.boulder-config:/boulder:ro quay.io/letsencrypt/boulder:stable boulder-ra
```
Quickstart
Slow start
----------
Boulder requires an installation of RabbitMQ, libtool-ltdl, and
This approach is better if you intend to develop on Boulder frequently, because
it's challenging to develop inside the Docker container.
Boulder requires an installation of RabbitMQ, libtool-ltdl, goose, and
MariaDB 10 to work correctly. On Ubuntu and CentOS, you may have to
install RabbitMQ from https://rabbitmq.com/download.html to get a
recent version.
Also, Boulder requires Go 1.5. As of September 2015 this version is not yet
available in OS repostiories, so you will have to install from https://golang.org/dl/.
Ubuntu:
sudo apt-get install libltdl3-dev mariadb-server rabbitmq-server
@ -69,19 +50,22 @@ or
(On OS X, using port, you will have to add `CGO_CFLAGS="-I/opt/local/include" CGO_LDFLAGS="-L/opt/local/lib"` to your environment or `go` invocations.)
```
> go get bitbucket.org/liamstask/goose/cmd/goose
> go get github.com/letsencrypt/boulder/ # Ignore errors about no buildable files
> cd $GOPATH/src/github.com/letsencrypt/boulder
> ./test/create_db.sh
# This starts each Boulder component with test configs. Ctrl-C kills all.
> ./start.py
# Run tests
> ./test.sh
```
The databases that boulder requires to operate in development and
testing can be created using test/create\_db.sh. It uses the root
MariaDB user, so if you have disabled that account you may have to
adjust the file or recreate the commands.
Note: create\_db.sh it uses the root MariaDB user, so if you
have disabled that account you may have to adjust the file or
recreate the commands.
You can also check out the official client from
https://github.com/letsencrypt/lets-encrypt-preview/ and follow the setup
https://github.com/letsencrypt/letsencrypt/ and follow the setup
instructions there.
Component Model
@ -130,7 +114,7 @@ and to [avoid insecure fallback in go
get](https://github.com/golang/go/issues/9637).
Local development also requires a RabbitMQ installation and MariaDB
10 installation. MariaDB should be run on port 3306 for the
10 installation (see above). MariaDB should be run on port 3306 for the
default integration tests.
To update the Go dependencies:

View File

@ -45,9 +45,9 @@ var (
// DNSResolverImpl represents a client that talks to an external resolver
type DNSResolverImpl struct {
DNSClient *dns.Client
Servers []string
allowLoopbackAddresses bool
DNSClient *dns.Client
Servers []string
allowRestrictedAddresses bool
}
// NewDNSResolverImpl constructs a new DNS resolver object that utilizes the
@ -59,9 +59,9 @@ func NewDNSResolverImpl(dialTimeout time.Duration, servers []string) *DNSResolve
dnsClient.DialTimeout = dialTimeout
return &DNSResolverImpl{
DNSClient: dnsClient,
Servers: servers,
allowLoopbackAddresses: false,
DNSClient: dnsClient,
Servers: servers,
allowRestrictedAddresses: false,
}
}
@ -70,7 +70,7 @@ func NewDNSResolverImpl(dialTimeout time.Duration, servers []string) *DNSResolve
// This constructor should *only* be called from tests (unit or integration).
func NewTestDNSResolverImpl(dialTimeout time.Duration, servers []string) *DNSResolverImpl {
resolver := NewDNSResolverImpl(dialTimeout, servers)
resolver.allowLoopbackAddresses = true
resolver.allowRestrictedAddresses = true
return resolver
}
@ -120,8 +120,8 @@ func (dnsResolver *DNSResolverImpl) LookupTXT(hostname string) ([]string, time.D
return txt, rtt, err
}
func isPrivateV4(ip net.IP, allowLoopback bool) bool {
return rfc1918_10.Contains(ip) || rfc1918_172_16.Contains(ip) || rfc1918_192_168.Contains(ip) || (!allowLoopback && rfc5735_127.Contains(ip))
func isPrivateV4(ip net.IP) bool {
return rfc1918_10.Contains(ip) || rfc1918_172_16.Contains(ip) || rfc1918_192_168.Contains(ip) || rfc5735_127.Contains(ip)
}
// LookupHost sends a DNS query to find all A records associated with the provided
@ -141,7 +141,7 @@ func (dnsResolver *DNSResolverImpl) LookupHost(hostname string) ([]net.IP, time.
for _, answer := range r.Answer {
if answer.Header().Rrtype == dns.TypeA {
if a, ok := answer.(*dns.A); ok && a.A.To4() != nil && !isPrivateV4(a.A, dnsResolver.allowLoopbackAddresses) {
if a, ok := answer.(*dns.A); ok && a.A.To4() != nil && (!isPrivateV4(a.A) || dnsResolver.allowRestrictedAddresses) {
addrs = append(addrs, a.A)
}
}

View File

@ -8,6 +8,7 @@ package main
import (
"fmt"
"net"
"os"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/miekg/dns"
@ -19,6 +20,14 @@ func dnsHandler(w dns.ResponseWriter, r *dns.Msg) {
m.SetReply(r)
m.Compress = false
// Normally this test DNS server will return 127.0.0.1 for everything.
// However, in some situations (for instance Docker), it's useful to return a
// different hardcoded host. You can do so by setting the FAKE_DNS environment
// variable.
fakeDNS := os.Getenv("FAKE_DNS")
if fakeDNS == "" {
fakeDNS = "127.0.0.1"
}
for _, q := range r.Question {
fmt.Printf("dns-srv: Query -- [%s] %s\n", q.Name, dns.TypeToString[q.Qtype])
switch q.Qtype {
@ -30,7 +39,7 @@ func dnsHandler(w dns.ResponseWriter, r *dns.Msg) {
Class: dns.ClassINET,
Ttl: 0,
}
record.A = net.ParseIP("127.0.0.1")
record.A = net.ParseIP(fakeDNS)
m.Answer = append(m.Answer, record)
case dns.TypeMX:

33
test/run-docker.sh Executable file
View File

@ -0,0 +1,33 @@
#!/bin/bash
#
# Build and run a docker image for Boulder. This is suitable for running
# repeatedly during development because Docker will cache the image it builds,
# and will only re-do the minimum necessary.
#
# NOTE: Currently we're not able to effectively cache the DB setup steps,
# because setting up the DB depends on source files in the Boulder repo. So any
# time source files change, Docker treats that as potentially invalidating the
# steps that came after the COPY. In theory we could add a step that copies only
# the files necessary to do the migrations, run them, and then copy the rest of
# the source.
set -o errexit
cd $(dirname $0)/..
# In order to talk to a letsencrypt client running on the host, the fake DNS
# client used in Boulder's start.py needs to know what the host's IP is from the
# perspective of the container. We try to figure it out automatically. If you'd
# like your Boulder instance to always talk to some other host, you can set
# FAKE_DNS to that host's IP address.
if [ -z "${FAKE_DNS}" ] ; then
FAKE_DNS=$(ifconfig docker0 | sed -n 's/ *inet addr:\([0-9.]\+\).*/\1/p')
fi
docker build --tag boulder .
# The -i command makes the instance interactive, so you can kill start.py with Ctrl-C.
docker run \
--interactive \
--tty \
--rm=true \
--publish 4000-4001:4000-4001 \
--publish 8000-8100:8000-8100 \
--env FAKE_DNS="${FAKE_DNS}" \
boulder

View File

@ -658,7 +658,7 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
// TODO: Implement method of revocation by authorizations on account.
if !(core.KeyDigestEquals(requestKey, parsedCertificate.PublicKey) ||
registration.ID == cert.RegistrationID) {
logEvent.Error = "Revocation request must be signed by private key of cert to be revoked"
logEvent.Error = "Revocation request must be signed by private key of cert to be revoked, or by the account key of the account that issued it."
wfe.log.Debug("Key mismatch for revoke")
wfe.sendError(response,
logEvent.Error,

View File

@ -876,8 +876,32 @@ func TestNewRegistration(t *testing.T) {
test.AssertEquals(t, responseWriter.Code, 409)
}
// Valid revocation request for existing, non-revoked cert
func TestRevokeCertificate(t *testing.T) {
func makeRevokeRequestJSON() ([]byte, error) {
certPemBytes, err := ioutil.ReadFile("test/238.crt")
if err != nil {
return nil, err
}
certBlock, _ := pem.Decode(certPemBytes)
if err != nil {
return nil, err
}
revokeRequest := struct {
Resource string `json:"resource"`
CertificateDER core.JSONBuffer `json:"certificate"`
}{
Resource: "revoke-cert",
CertificateDER: certBlock.Bytes,
}
revokeRequestJSON, err := json.Marshal(revokeRequest)
if err != nil {
return nil, err
}
return revokeRequestJSON, nil
}
// Valid revocation request for existing, non-revoked cert, signed with cert
// key.
func TestRevokeCertificateCertKey(t *testing.T) {
keyPemBytes, err := ioutil.ReadFile("test/238.key")
test.AssertNotError(t, err, "Failed to load key")
key, err := jose.LoadPrivateKey(keyPemBytes)
@ -887,29 +911,12 @@ func TestRevokeCertificate(t *testing.T) {
signer, err := jose.NewSigner("RS256", rsaKey)
test.AssertNotError(t, err, "Failed to make signer")
certPemBytes, err := ioutil.ReadFile("test/238.crt")
test.AssertNotError(t, err, "Failed to load cert")
certBlock, _ := pem.Decode(certPemBytes)
test.Assert(t, certBlock != nil, "Failed to decode PEM")
revokeRequest := struct {
Resource string `json:"resource"`
CertificateDER core.JSONBuffer `json:"certificate"`
}{
Resource: "revoke-cert",
CertificateDER: certBlock.Bytes,
}
revokeRequestJSON, err := json.Marshal(revokeRequest)
test.AssertNotError(t, err, "Failed to marshal request")
revokeRequestJSON, err := makeRevokeRequestJSON()
test.AssertNotError(t, err, "Failed to make revokeRequestJSON")
// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
wfe := setupWFE(t)
wfe.RA = &MockRegistrationAuthority{}
wfe.SA = &MockSA{}
wfe.Stats, _ = statsd.NewNoopClient()
wfe.SubscriberAgreementURL = agreementURL
responseWriter := httptest.NewRecorder()
responseWriter.Body.Reset()
nonce, err := wfe.nonceService.Nonce()
test.AssertNotError(t, err, "Unable to create nonce")
result, _ := signer.Sign(revokeRequestJSON, nonce)
@ -917,25 +924,57 @@ func TestRevokeCertificate(t *testing.T) {
makePostRequest(result.FullSerialize()))
test.AssertEquals(t, responseWriter.Code, 200)
test.AssertEquals(t, responseWriter.Body.String(), "")
}
// Valid revocation request for existing, non-revoked cert, signed with account
// key.
func TestRevokeCertificateAccountKey(t *testing.T) {
revokeRequestJSON, err := makeRevokeRequestJSON()
test.AssertNotError(t, err, "Failed to make revokeRequestJSON")
wfe := setupWFE(t)
responseWriter := httptest.NewRecorder()
// Try the revoke request again, signed by account key associated with cert.
// Should also succeed.
responseWriter.Body.Reset()
test1JWK, err := jose.LoadPrivateKey([]byte(test1KeyPrivatePEM))
test.AssertNotError(t, err, "Failed to load key")
test1Key, ok := test1JWK.(*rsa.PrivateKey)
test.Assert(t, ok, "Couldn't load RSA key")
accountKeySigner, err := jose.NewSigner("RS256", test1Key)
test.AssertNotError(t, err, "Failed to make signer")
nonce, err = wfe.nonceService.Nonce()
nonce, err := wfe.nonceService.Nonce()
test.AssertNotError(t, err, "Unable to create nonce")
result, _ = accountKeySigner.Sign(revokeRequestJSON, nonce)
result, _ := accountKeySigner.Sign(revokeRequestJSON, nonce)
wfe.RevokeCertificate(responseWriter,
makePostRequest(result.FullSerialize()))
test.AssertEquals(t, responseWriter.Code, 200)
test.AssertEquals(t, responseWriter.Body.String(), "")
}
// A revocation request signed by an unauthorized key.
func TestRevokeCertificateWrongKey(t *testing.T) {
wfe := setupWFE(t)
nonce, err := wfe.nonceService.Nonce()
test.AssertNotError(t, err, "Unable to create nonce")
responseWriter := httptest.NewRecorder()
test2JWK, err := jose.LoadPrivateKey([]byte(test2KeyPrivatePEM))
test.AssertNotError(t, err, "Failed to load key")
test2Key, ok := test2JWK.(*rsa.PrivateKey)
test.Assert(t, ok, "Couldn't load RSA key")
accountKeySigner2, err := jose.NewSigner("RS256", test2Key)
test.AssertNotError(t, err, "Failed to make signer")
nonce, err = wfe.nonceService.Nonce()
test.AssertNotError(t, err, "Unable to create nonce")
revokeRequestJSON, err := makeRevokeRequestJSON()
test.AssertNotError(t, err, "Unable to create revoke request")
result, _ := accountKeySigner2.Sign(revokeRequestJSON, nonce)
wfe.RevokeCertificate(responseWriter,
makePostRequest(result.FullSerialize()))
test.AssertEquals(t, responseWriter.Code, 403)
test.AssertEquals(t, responseWriter.Body.String(),
`{"type":"urn:acme:error:unauthorized","detail":"Revocation request must be signed by private key of cert to be revoked, or by the account key of the account that issued it."}`)
}
// Valid revocation request for already-revoked cert
func TestRevokeCertificateAlreadyRevoked(t *testing.T) {
keyPemBytes, err := ioutil.ReadFile("test/178.key")