Skip to content

Commit

Permalink
Add capability for the v1/connect/ca/roots endpoint to return a PEM e…
Browse files Browse the repository at this point in the history
…ncoded certificate chain (#8774)

Co-authored-by: R.B. Boyer <rb@hashicorp.com>
  • Loading branch information
2 people authored and hashicorp-ci committed Oct 9, 2020
1 parent c0fa572 commit 6cae442
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 1 deletion.
4 changes: 4 additions & 0 deletions .changelog/8774.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
```release-note:improvement
api: The `v1/connect/ca/roots` endpoint now accepts a `pem=true` query parameter and will return a PEM encoded certificate chain of
all the certificates that would normally be in the JSON version of the response.
```
29 changes: 28 additions & 1 deletion agent/connect_ca_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package agent
import (
"fmt"
"net/http"
"strconv"

"github.com/hashicorp/consul/agent/consul"
"github.com/hashicorp/consul/agent/structs"
Expand All @@ -15,13 +16,39 @@ func (s *HTTPServer) ConnectCARoots(resp http.ResponseWriter, req *http.Request)
return nil, nil
}

pemResponse := false
if pemParam := req.URL.Query().Get("pem"); pemParam != "" {
val, err := strconv.ParseBool(pemParam)
if err != nil {
return nil, BadRequestError{Reason: "The 'pem' query parameter must be a boolean value"}
}
pemResponse = val
}

var reply structs.IndexedCARoots
defer setMeta(resp, &reply.QueryMeta)
if err := s.agent.RPC("ConnectCA.Roots", &args, &reply); err != nil {
return nil, err
}

return reply, nil
if !pemResponse {
return reply, nil
}

// defined in RFC 8555 and registered with the IANA
resp.Header().Set("Content-Type", "application/pem-certificate-chain")
for _, root := range reply.Roots {
if _, err := resp.Write([]byte(root.RootCert)); err != nil {
return nil, err
}
for _, intermediate := range root.IntermediateCerts {
if _, err := resp.Write([]byte(intermediate)); err != nil {
return nil, err
}
}
}

return nil, nil
}

// /v1/connect/ca/configuration
Expand Down
33 changes: 33 additions & 0 deletions agent/connect_ca_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package agent

import (
"bytes"
"crypto/x509"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
Expand Down Expand Up @@ -258,3 +260,34 @@ func TestConnectCAConfig(t *testing.T) {
})
}
}

func TestConnectCARoots_PEMEncoding(t *testing.T) {
primary := NewTestAgent(t, "")
defer primary.Shutdown()
testrpc.WaitForActiveCARoot(t, primary.RPC, "dc1", nil)

secondary := NewTestAgent(t, `
primary_datacenter = "dc1"
datacenter = "dc2"
retry_join_wan = ["`+primary.Config.SerfBindAddrWAN.String()+`"]
`)
defer secondary.Shutdown()
testrpc.WaitForActiveCARoot(t, secondary.RPC, "dc2", nil)

req, _ := http.NewRequest("GET", "/v1/connect/ca/roots?pem=true", nil)
recorder := httptest.NewRecorder()
obj, err := secondary.srv.ConnectCARoots(recorder, req)
require.NoError(t, err)
require.Nil(t, obj, "Endpoint returned an object for serialization when it should have returned nil and written to the responses")
resp := recorder.Result()
require.Equal(t, resp.Header.Get("Content-Type"), "application/pem-certificate-chain")

data, err := ioutil.ReadAll(resp.Body)
require.NoError(t, err)

pool := x509.NewCertPool()

require.True(t, pool.AppendCertsFromPEM(data))
// expecting the root cert from dc1 and an intermediate in dc2
require.Len(t, pool.Subjects(), 2)
}
40 changes: 40 additions & 0 deletions website/pages/api-docs/connect/ca.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ The table below shows this endpoint's support for
| ---------------- | ----------------- | ------------- | ------------ |
| `YES` | `all` | `none` | `none` |

### Parameters

- `pem` `(boolean: false)` - Specifies that the return body should be a PEM encoded
certificate chain suitable for use by applications needing to trust Connect CA
signed certificates. The Content-Type will be set to `application/pem-certificate-chain`
to indicate the format of the response.

### Sample Request

```shell-session
Expand Down Expand Up @@ -65,6 +72,39 @@ $ curl \
}
```

### Sample PEM Encoded Response

```
-----BEGIN CERTIFICATE-----
MIICDzCCAbWgAwIBAgIBCDAKBggqhkjOPQQDAjAxMS8wLQYDVQQDEyZwcmktMWNq
OHphbW0uY29uc3VsLmNhLjA3OTMzYTEzLmNvbnN1bDAeFw0yMDEwMDgxOTQ4MzZa
Fw0zMDEwMDgxOTQ4MzZaMDExLzAtBgNVBAMTJnByaS0xY2o4emFtbS5jb25zdWwu
Y2EuMDc5MzNhMTMuY29uc3VsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDCkT
IIxSDhA3XCKIuDcj4s9IVjf0NQT6QHPAzFBb964/4fTtX/J8x2n6A1lOXowFIWtx
GvAD/IJF74zn5ZA/wqOBvTCBujAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUw
AwEB/zApBgNVHQ4EIgQguPlAkrIkOnLr9+8DZ4afZWrYZUd2LB6nMJP72jDVxmcw
KwYDVR0jBCQwIoAguPlAkrIkOnLr9+8DZ4afZWrYZUd2LB6nMJP72jDVxmcwPwYD
VR0RBDgwNoY0c3BpZmZlOi8vMDc5MzNhMTMtYTYyYi1iZTkwLTQ0ZjEtZGVkOWE2
NjczNzZlLmNvbnN1bDAKBggqhkjOPQQDAgNIADBFAiEA0ExkvLESG1I1TMFVronr
2fjoORukgzBgRMbWAEC2DJ0CIACsxeFS6tprHiRv4cEa2Md75h1iIisb2V2U7dvY
Z7Rr
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICEzCCAbigAwIBAgIBCTAKBggqhkjOPQQDAjAxMS8wLQYDVQQDEyZwcmktMWNq
OHphbW0uY29uc3VsLmNhLjA3OTMzYTEzLmNvbnN1bDAeFw0yMDEwMDgxOTQ3Mzda
Fw0yMTEwMDgxOTQ3MzdaMDExLzAtBgNVBAMTJnNlYy0xbmIxMHZ0by5jb25zdWwu
Y2EuMDc5MzNhMTMuY29uc3VsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9zWs
UxEYvLZUySoflz6e+HqLcaXM8heNRRkAiLiGkmn6nan6olnnrVBLyHAfHaHWJQ9W
wI8HwSZf0g4Ms16LWKOBwDCBvTAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgw
BgEB/wIBADApBgNVHQ4EIgQg+csK9Sg6odIfLLk3aiRY2OB4O0DiOa1XRTVdOVDE
t6QwKwYDVR0jBCQwIoAguPlAkrIkOnLr9+8DZ4afZWrYZUd2LB6nMJP72jDVxmcw
PwYDVR0RBDgwNoY0c3BpZmZlOi8vMDc5MzNhMTMtYTYyYi1iZTkwLTQ0ZjEtZGVk
OWE2NjczNzZlLmNvbnN1bDAKBggqhkjOPQQDAgNJADBGAiEAqJ60KJepAP4Xe4Ak
5UYB1huu/B8Lyz5yEYUpUplgdD4CIQCrrkoXoD4SGJ4HaIjy6a5eNf3YkhLpmbXO
6DL6FXVa1Q==
-----END CERTIFICATE-----
```

## Get CA Configuration

This endpoint returns the current CA configuration.
Expand Down

0 comments on commit 6cae442

Please sign in to comment.