mirror of https://github.com/hashicorp/consul
agent: /v1/connect/ca/leaf/:service_id
parent
571d9aa785
commit
c2588262b7
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/hashicorp/consul/acl"
|
"github.com/hashicorp/consul/acl"
|
||||||
"github.com/hashicorp/consul/agent/checks"
|
"github.com/hashicorp/consul/agent/checks"
|
||||||
"github.com/hashicorp/consul/agent/config"
|
"github.com/hashicorp/consul/agent/config"
|
||||||
|
"github.com/hashicorp/consul/agent/connect"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
"github.com/hashicorp/consul/ipaddr"
|
"github.com/hashicorp/consul/ipaddr"
|
||||||
|
@ -21,6 +22,9 @@ import (
|
||||||
"github.com/hashicorp/serf/serf"
|
"github.com/hashicorp/serf/serf"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
|
||||||
|
// NOTE(mitcehllh): This is temporary while certs are stubbed out.
|
||||||
|
"github.com/mitchellh/go-testing-interface"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Self struct {
|
type Self struct {
|
||||||
|
@ -844,3 +848,40 @@ func (s *HTTPServer) AgentConnectCARoots(resp http.ResponseWriter, req *http.Req
|
||||||
// behavior will differ.
|
// behavior will differ.
|
||||||
return s.ConnectCARoots(resp, req)
|
return s.ConnectCARoots(resp, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AgentConnectCALeafCert returns the certificate bundle for a service
|
||||||
|
// instance. This supports blocking queries to update the returned bundle.
|
||||||
|
func (s *HTTPServer) AgentConnectCALeafCert(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
|
// Test the method
|
||||||
|
if req.Method != "GET" {
|
||||||
|
return nil, MethodNotAllowedError{req.Method, []string{"GET"}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the service ID. Note that this is the ID of a service instance.
|
||||||
|
id := strings.TrimPrefix(req.URL.Path, "/v1/agent/connect/ca/leaf/")
|
||||||
|
|
||||||
|
// Retrieve the service specified
|
||||||
|
service := s.agent.State.Service(id)
|
||||||
|
if service == nil {
|
||||||
|
return nil, fmt.Errorf("unknown service ID: %s", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a CSR.
|
||||||
|
// TODO(mitchellh): This is obviously not production ready!
|
||||||
|
csr, pk := connect.TestCSR(&testing.RuntimeT{}, &connect.SpiffeIDService{
|
||||||
|
Host: "1234.consul",
|
||||||
|
Namespace: "default",
|
||||||
|
Datacenter: s.agent.config.Datacenter,
|
||||||
|
Service: service.Service,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Request signing
|
||||||
|
var reply structs.IssuedCert
|
||||||
|
args := structs.CASignRequest{CSR: csr}
|
||||||
|
if err := s.agent.RPC("ConnectCA.Sign", &args, &reply); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
reply.PrivateKeyPEM = pk
|
||||||
|
|
||||||
|
return &reply, nil
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -2074,3 +2075,58 @@ func TestAgentConnectCARoots_list(t *testing.T) {
|
||||||
assert.Equal("", r.SigningKey)
|
assert.Equal("", r.SigningKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAgentConnectCALeafCert_good(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
assert := assert.New(t)
|
||||||
|
a := NewTestAgent(t.Name(), "")
|
||||||
|
defer a.Shutdown()
|
||||||
|
|
||||||
|
// Set CAs
|
||||||
|
var reply interface{}
|
||||||
|
ca1 := connect.TestCA(t, nil)
|
||||||
|
assert.Nil(a.RPC("Test.ConnectCASetRoots", []*structs.CARoot{ca1}, &reply))
|
||||||
|
|
||||||
|
{
|
||||||
|
// Register a local service
|
||||||
|
args := &structs.ServiceDefinition{
|
||||||
|
ID: "foo",
|
||||||
|
Name: "test",
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
Port: 8000,
|
||||||
|
Check: structs.CheckType{
|
||||||
|
TTL: 15 * time.Second,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
req, _ := http.NewRequest("PUT", "/v1/agent/service/register", jsonReader(args))
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
_, err := a.srv.AgentRegisterService(resp, req)
|
||||||
|
assert.Nil(err)
|
||||||
|
if !assert.Equal(200, resp.Code) {
|
||||||
|
t.Log("Body: ", resp.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List
|
||||||
|
req, _ := http.NewRequest("GET", "/v1/agent/connect/ca/leaf/foo", nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
obj, err := a.srv.AgentConnectCALeafCert(resp, req)
|
||||||
|
assert.Nil(err)
|
||||||
|
|
||||||
|
// Get the issued cert
|
||||||
|
issued, ok := obj.(*structs.IssuedCert)
|
||||||
|
assert.True(ok)
|
||||||
|
|
||||||
|
// Verify that the cert is signed by the CA
|
||||||
|
roots := x509.NewCertPool()
|
||||||
|
assert.True(roots.AppendCertsFromPEM([]byte(ca1.RootCert)))
|
||||||
|
leaf, err := connect.ParseCert(issued.CertPEM)
|
||||||
|
assert.Nil(err)
|
||||||
|
_, err = leaf.Verify(x509.VerifyOptions{
|
||||||
|
Roots: roots,
|
||||||
|
})
|
||||||
|
assert.Nil(err)
|
||||||
|
|
||||||
|
// TODO(mitchellh): verify the private key matches the cert
|
||||||
|
}
|
||||||
|
|
|
@ -187,29 +187,47 @@ func TestLeaf(t testing.T, service string, root *structs.CARoot) string {
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCSR returns a CSR to sign the given service.
|
// TestCSR returns a CSR to sign the given service along with the PEM-encoded
|
||||||
func TestCSR(t testing.T, id SpiffeID) string {
|
// private key for this certificate.
|
||||||
|
func TestCSR(t testing.T, id SpiffeID) (string, string) {
|
||||||
template := &x509.CertificateRequest{
|
template := &x509.CertificateRequest{
|
||||||
URIs: []*url.URL{id.URI()},
|
URIs: []*url.URL{id.URI()},
|
||||||
SignatureAlgorithm: x509.ECDSAWithSHA256,
|
SignatureAlgorithm: x509.ECDSAWithSHA256,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Result buffers
|
||||||
|
var csrBuf, pkBuf bytes.Buffer
|
||||||
|
|
||||||
// Create the private key we'll use
|
// Create the private key we'll use
|
||||||
signer := testPrivateKey(t, nil)
|
signer := testPrivateKey(t, nil)
|
||||||
|
|
||||||
// Create the CSR itself
|
{
|
||||||
bs, err := x509.CreateCertificateRequest(rand.Reader, template, signer)
|
// Create the private key PEM
|
||||||
if err != nil {
|
bs, err := x509.MarshalECPrivateKey(signer.(*ecdsa.PrivateKey))
|
||||||
t.Fatalf("error creating CSR: %s", err)
|
if err != nil {
|
||||||
|
t.Fatalf("error marshalling PK: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pem.Encode(&pkBuf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: bs})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error encoding PK: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
{
|
||||||
err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: bs})
|
// Create the CSR itself
|
||||||
if err != nil {
|
bs, err := x509.CreateCertificateRequest(rand.Reader, template, signer)
|
||||||
t.Fatalf("error encoding CSR: %s", err)
|
if err != nil {
|
||||||
|
t.Fatalf("error creating CSR: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pem.Encode(&csrBuf, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: bs})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error encoding CSR: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf.String()
|
return csrBuf.String(), pkBuf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// testKeyID returns a KeyID from the given public key. The "raw" must be
|
// testKeyID returns a KeyID from the given public key. The "raw" must be
|
||||||
|
|
|
@ -171,7 +171,7 @@ func (s *ConnectCA) Sign(
|
||||||
// Set the response
|
// Set the response
|
||||||
*reply = structs.IssuedCert{
|
*reply = structs.IssuedCert{
|
||||||
SerialNumber: template.SerialNumber,
|
SerialNumber: template.SerialNumber,
|
||||||
Cert: buf.String(),
|
CertPEM: buf.String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -30,6 +30,7 @@ func init() {
|
||||||
registerEndpoint("/v1/agent/check/fail/", []string{"PUT"}, (*HTTPServer).AgentCheckFail)
|
registerEndpoint("/v1/agent/check/fail/", []string{"PUT"}, (*HTTPServer).AgentCheckFail)
|
||||||
registerEndpoint("/v1/agent/check/update/", []string{"PUT"}, (*HTTPServer).AgentCheckUpdate)
|
registerEndpoint("/v1/agent/check/update/", []string{"PUT"}, (*HTTPServer).AgentCheckUpdate)
|
||||||
registerEndpoint("/v1/agent/connect/ca/roots", []string{"GET"}, (*HTTPServer).AgentConnectCARoots)
|
registerEndpoint("/v1/agent/connect/ca/roots", []string{"GET"}, (*HTTPServer).AgentConnectCARoots)
|
||||||
|
registerEndpoint("/v1/agent/connect/ca/leaf/", []string{"GET"}, (*HTTPServer).AgentConnectCALeafCert)
|
||||||
registerEndpoint("/v1/agent/service/register", []string{"PUT"}, (*HTTPServer).AgentRegisterService)
|
registerEndpoint("/v1/agent/service/register", []string{"PUT"}, (*HTTPServer).AgentRegisterService)
|
||||||
registerEndpoint("/v1/agent/service/deregister/", []string{"PUT"}, (*HTTPServer).AgentDeregisterService)
|
registerEndpoint("/v1/agent/service/deregister/", []string{"PUT"}, (*HTTPServer).AgentDeregisterService)
|
||||||
registerEndpoint("/v1/agent/service/maintenance/", []string{"PUT"}, (*HTTPServer).AgentServiceMaintenance)
|
registerEndpoint("/v1/agent/service/maintenance/", []string{"PUT"}, (*HTTPServer).AgentServiceMaintenance)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package structs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IndexedCARoots is the list of currently trusted CA Roots.
|
// IndexedCARoots is the list of currently trusted CA Roots.
|
||||||
|
@ -72,9 +73,23 @@ type IssuedCert struct {
|
||||||
// SerialNumber is the unique serial number for this certificate.
|
// SerialNumber is the unique serial number for this certificate.
|
||||||
SerialNumber *big.Int
|
SerialNumber *big.Int
|
||||||
|
|
||||||
// Cert is the PEM-encoded certificate. This should not be stored in the
|
// CertPEM and PrivateKeyPEM are the PEM-encoded certificate and private
|
||||||
|
// key for that cert, respectively. This should not be stored in the
|
||||||
// state store, but is present in the sign API response.
|
// state store, but is present in the sign API response.
|
||||||
Cert string `json:",omitempty"`
|
CertPEM string `json:",omitempty"`
|
||||||
|
PrivateKeyPEM string
|
||||||
|
|
||||||
|
// Service is the name of the service for which the cert was issued.
|
||||||
|
// ServiceURI is the cert URI value.
|
||||||
|
Service string
|
||||||
|
ServiceURI string
|
||||||
|
|
||||||
|
// ValidAfter and ValidBefore are the validity periods for the
|
||||||
|
// certificate.
|
||||||
|
ValidAfter time.Time
|
||||||
|
ValidBefore time.Time
|
||||||
|
|
||||||
|
RaftIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
// CAOp is the operation for a request related to intentions.
|
// CAOp is the operation for a request related to intentions.
|
||||||
|
|
Loading…
Reference in New Issue