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

Add capability for the v1/connect/ca/roots endpoint to return a PEM encoded certificate chain #8774

Merged
merged 2 commits into from
Oct 9, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 *HTTPHandlers) ConnectCARoots(resp http.ResponseWriter, req *http.Reques
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 paramter must be a boolean value"}
mkeeler marked this conversation as resolved.
Show resolved Hide resolved
}
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")
mkeeler marked this conversation as resolved.
Show resolved Hide resolved
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