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/agent/checks"
|
||||
"github.com/hashicorp/consul/agent/config"
|
||||
"github.com/hashicorp/consul/agent/connect"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/ipaddr"
|
||||
|
@ -21,6 +22,9 @@ import (
|
|||
"github.com/hashicorp/serf/serf"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"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 {
|
||||
|
@ -844,3 +848,40 @@ func (s *HTTPServer) AgentConnectCARoots(resp http.ResponseWriter, req *http.Req
|
|||
// behavior will differ.
|
||||
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 (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -2074,3 +2075,58 @@ func TestAgentConnectCARoots_list(t *testing.T) {
|
|||
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()
|
||||
}
|
||||
|
||||
// TestCSR returns a CSR to sign the given service.
|
||||
func TestCSR(t testing.T, id SpiffeID) string {
|
||||
// TestCSR returns a CSR to sign the given service along with the PEM-encoded
|
||||
// private key for this certificate.
|
||||
func TestCSR(t testing.T, id SpiffeID) (string, string) {
|
||||
template := &x509.CertificateRequest{
|
||||
URIs: []*url.URL{id.URI()},
|
||||
SignatureAlgorithm: x509.ECDSAWithSHA256,
|
||||
}
|
||||
|
||||
// Result buffers
|
||||
var csrBuf, pkBuf bytes.Buffer
|
||||
|
||||
// Create the private key we'll use
|
||||
signer := testPrivateKey(t, nil)
|
||||
|
||||
// Create the CSR itself
|
||||
bs, err := x509.CreateCertificateRequest(rand.Reader, template, signer)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating CSR: %s", err)
|
||||
{
|
||||
// Create the private key PEM
|
||||
bs, err := x509.MarshalECPrivateKey(signer.(*ecdsa.PrivateKey))
|
||||
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})
|
||||
if err != nil {
|
||||
t.Fatalf("error encoding CSR: %s", err)
|
||||
{
|
||||
// Create the CSR itself
|
||||
bs, err := x509.CreateCertificateRequest(rand.Reader, template, signer)
|
||||
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
|
||||
|
|
|
@ -171,7 +171,7 @@ func (s *ConnectCA) Sign(
|
|||
// Set the response
|
||||
*reply = structs.IssuedCert{
|
||||
SerialNumber: template.SerialNumber,
|
||||
Cert: buf.String(),
|
||||
CertPEM: buf.String(),
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -30,6 +30,7 @@ func init() {
|
|||
registerEndpoint("/v1/agent/check/fail/", []string{"PUT"}, (*HTTPServer).AgentCheckFail)
|
||||
registerEndpoint("/v1/agent/check/update/", []string{"PUT"}, (*HTTPServer).AgentCheckUpdate)
|
||||
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/deregister/", []string{"PUT"}, (*HTTPServer).AgentDeregisterService)
|
||||
registerEndpoint("/v1/agent/service/maintenance/", []string{"PUT"}, (*HTTPServer).AgentServiceMaintenance)
|
||||
|
|
|
@ -2,6 +2,7 @@ package structs
|
|||
|
||||
import (
|
||||
"math/big"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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 *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.
|
||||
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.
|
||||
|
|
Loading…
Reference in New Issue