api: endpoints for working with CA roots, agent authorize, etc.

This commit is contained in:
Mitchell Hashimoto 2018-03-27 16:50:17 -07:00
parent 94e7a0a3c1
commit b5b301aa2a
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
4 changed files with 188 additions and 0 deletions

View File

@ -172,6 +172,19 @@ type SampledValue struct {
Labels map[string]string
}
// AgentAuthorizeParams are the request parameters for authorizing a request.
type AgentAuthorizeParams struct {
Target string
ClientID string
ClientCertSerial string
}
// AgentAuthorize is the response structure for Connect authorization.
type AgentAuthorize struct {
Authorized bool
Reason string
}
// Agent can be used to query the Agent endpoints
type Agent struct {
c *Client
@ -505,6 +518,75 @@ func (a *Agent) ForceLeave(node string) error {
return nil
}
// ConnectAuthorize is used to authorize an incoming connection
// to a natively integrated Connect service.
//
// TODO(mitchellh): we need to test this better once we have a way to
// configure CAs from the API package (when the CA work is done).
func (a *Agent) ConnectAuthorize(auth *AgentAuthorizeParams) (*AgentAuthorize, error) {
r := a.c.newRequest("POST", "/v1/agent/connect/authorize")
r.obj = auth
_, resp, err := requireOK(a.c.doRequest(r))
if err != nil {
return nil, err
}
resp.Body.Close()
var out AgentAuthorize
if err := decodeBody(resp, &out); err != nil {
return nil, err
}
return &out, nil
}
// ConnectCARoots returns the list of roots.
//
// TODO(mitchellh): we need to test this better once we have a way to
// configure CAs from the API package (when the CA work is done).
func (a *Agent) ConnectCARoots(q *QueryOptions) (*CARootList, *QueryMeta, error) {
r := a.c.newRequest("GET", "/v1/agent/connect/ca/roots")
r.setQueryOptions(q)
rtt, resp, err := requireOK(a.c.doRequest(r))
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
qm := &QueryMeta{}
parseQueryMeta(resp, qm)
qm.RequestTime = rtt
var out CARootList
if err := decodeBody(resp, &out); err != nil {
return nil, nil, err
}
return &out, qm, nil
}
// ConnectCALeaf gets the leaf certificate for the given service ID.
//
// TODO(mitchellh): we need to test this better once we have a way to
// configure CAs from the API package (when the CA work is done).
func (a *Agent) ConnectCALeaf(serviceID string, q *QueryOptions) (*IssuedCert, *QueryMeta, error) {
r := a.c.newRequest("GET", "/v1/agent/connect/ca/leaf/"+serviceID)
r.setQueryOptions(q)
rtt, resp, err := requireOK(a.c.doRequest(r))
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
qm := &QueryMeta{}
parseQueryMeta(resp, qm)
qm.RequestTime = rtt
var out IssuedCert
if err := decodeBody(resp, &out); err != nil {
return nil, nil, err
}
return &out, qm, nil
}
// EnableServiceMaintenance toggles service maintenance mode on
// for the given service ID.
func (a *Agent) EnableServiceMaintenance(serviceID, reason string) error {

View File

@ -11,6 +11,7 @@ import (
"github.com/hashicorp/consul/testutil"
"github.com/hashicorp/consul/testutil/retry"
"github.com/hashicorp/serf/serf"
"github.com/stretchr/testify/require"
)
func TestAPI_AgentSelf(t *testing.T) {
@ -981,3 +982,17 @@ func TestAPI_AgentUpdateToken(t *testing.T) {
t.Fatalf("err: %v", err)
}
}
func TestAPI_AgentConnectCARoots_empty(t *testing.T) {
t.Parallel()
require := require.New(t)
c, s := makeClient(t)
defer s.Stop()
agent := c.Agent()
list, meta, err := agent.ConnectCARoots(nil)
require.Nil(err)
require.Equal(uint64(0), meta.LastIndex)
require.Len(list.Roots, 0)
}

65
api/connect.go Normal file
View File

@ -0,0 +1,65 @@
package api
import (
"time"
)
// CARootList is the structure for the results of listing roots.
type CARootList struct {
ActiveRootID string
Roots []*CARoot
}
// CARoot is a single CA within Connect.
type CARoot struct {
ID string
Name string
RootCert string
Active bool
CreateIndex uint64
ModifyIndex uint64
}
type IssuedCert struct {
SerialNumber string
CertPEM string
PrivateKeyPEM string
Service string
ServiceURI string
ValidAfter time.Time
ValidBefore time.Time
CreateIndex uint64
ModifyIndex uint64
}
// Connect can be used to work with endpoints related to Connect, the
// feature for securely connecting services within Consul.
type Connect struct {
c *Client
}
// Health returns a handle to the health endpoints
func (c *Client) Connect() *Connect {
return &Connect{c}
}
// CARoots queries the list of available roots.
func (h *Connect) CARoots(q *QueryOptions) (*CARootList, *QueryMeta, error) {
r := h.c.newRequest("GET", "/v1/connect/ca/roots")
r.setQueryOptions(q)
rtt, resp, err := requireOK(h.c.doRequest(r))
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
qm := &QueryMeta{}
parseQueryMeta(resp, qm)
qm.RequestTime = rtt
var out CARootList
if err := decodeBody(resp, &out); err != nil {
return nil, nil, err
}
return &out, qm, nil
}

26
api/connect_test.go Normal file
View File

@ -0,0 +1,26 @@
package api
import (
"testing"
"github.com/stretchr/testify/require"
)
// NOTE(mitchellh): we don't have a way to test CA roots yet since there
// is no API public way to configure the root certs. This wll be resolved
// in the future and we can write tests then. This is tested in agent and
// agent/consul which do have internal access to manually create roots.
func TestAPI_ConnectCARoots_empty(t *testing.T) {
t.Parallel()
require := require.New(t)
c, s := makeClient(t)
defer s.Stop()
connect := c.Connect()
list, meta, err := connect.CARoots(nil)
require.Nil(err)
require.Equal(uint64(0), meta.LastIndex)
require.Len(list.Roots, 0)
}