Generate CSR using real trust-domain
This commit is contained in:
parent
5a1408f186
commit
30d90b3be4
|
@ -2106,13 +2106,14 @@ func TestAgentConnectCARoots_empty(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
a := NewTestAgent(t.Name(), "connect { enabled = false }")
|
||||
defer a.Shutdown()
|
||||
|
||||
req, _ := http.NewRequest("GET", "/v1/agent/connect/ca/roots", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
obj, err := a.srv.AgentConnectCARoots(resp, req)
|
||||
assert.Nil(err)
|
||||
require.NoError(err)
|
||||
|
||||
value := obj.(structs.IndexedCARoots)
|
||||
assert.Equal(value.ActiveRootID, "")
|
||||
|
@ -2122,6 +2123,7 @@ func TestAgentConnectCARoots_empty(t *testing.T) {
|
|||
func TestAgentConnectCARoots_list(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
a := NewTestAgent(t.Name(), "")
|
||||
defer a.Shutdown()
|
||||
|
@ -2137,30 +2139,34 @@ func TestAgentConnectCARoots_list(t *testing.T) {
|
|||
req, _ := http.NewRequest("GET", "/v1/agent/connect/ca/roots", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
obj, err := a.srv.AgentConnectCARoots(resp, req)
|
||||
require.Nil(err)
|
||||
require.NoError(err)
|
||||
|
||||
value := obj.(structs.IndexedCARoots)
|
||||
require.Equal(value.ActiveRootID, ca2.ID)
|
||||
require.Len(value.Roots, 2)
|
||||
assert.Equal(value.ActiveRootID, ca2.ID)
|
||||
// Would like to assert that it's the same as the TestAgent domain but the
|
||||
// only way to access that state via this package is by RPC to the server
|
||||
// implementation running in TestAgent which is more or less a tautology.
|
||||
assert.NotEmpty(value.TrustDomain)
|
||||
assert.Len(value.Roots, 2)
|
||||
|
||||
// We should never have the secret information
|
||||
for _, r := range value.Roots {
|
||||
require.Equal("", r.SigningCert)
|
||||
require.Equal("", r.SigningKey)
|
||||
assert.Equal("", r.SigningCert)
|
||||
assert.Equal("", r.SigningKey)
|
||||
}
|
||||
|
||||
// That should've been a cache miss, so no hit change
|
||||
require.Equal(cacheHits, a.cache.Hits())
|
||||
assert.Equal(cacheHits, a.cache.Hits())
|
||||
|
||||
// Test caching
|
||||
{
|
||||
// List it again
|
||||
obj2, err := a.srv.AgentConnectCARoots(httptest.NewRecorder(), req)
|
||||
require.Nil(err)
|
||||
require.Equal(obj, obj2)
|
||||
require.NoError(err)
|
||||
assert.Equal(obj, obj2)
|
||||
|
||||
// Should cache hit this time and not make request
|
||||
require.Equal(cacheHits+1, a.cache.Hits())
|
||||
assert.Equal(cacheHits+1, a.cache.Hits())
|
||||
cacheHits++
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package cachetype
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
@ -9,9 +10,7 @@ import (
|
|||
"github.com/hashicorp/consul/agent/cache"
|
||||
"github.com/hashicorp/consul/agent/connect"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
|
||||
// NOTE(mitcehllh): This is temporary while certs are stubbed out.
|
||||
"github.com/mitchellh/go-testing-interface"
|
||||
)
|
||||
|
||||
// Recommended name for registration.
|
||||
|
@ -97,16 +96,41 @@ func (c *ConnectCALeaf) Fetch(opts cache.FetchOptions, req cache.Request) (cache
|
|||
// by the above channel).
|
||||
}
|
||||
|
||||
// Create a CSR.
|
||||
// TODO(mitchellh): This is obviously not production ready! The host
|
||||
// needs a correct host ID, and we probably don't want to use TestCSR
|
||||
// and want a non-test-specific way to create a CSR.
|
||||
csr, pk := connect.TestCSR(&testing.RuntimeT{}, &connect.SpiffeIDService{
|
||||
Host: "11111111-2222-3333-4444-555555555555.consul",
|
||||
Namespace: "default",
|
||||
// Need to lookup RootCAs response to discover trust domain. First just lookup
|
||||
// with no blocking info - this should be a cache hit most of the time.
|
||||
rawRoots, err := c.Cache.Get(ConnectCARootName, &structs.DCSpecificRequest{
|
||||
Datacenter: reqReal.Datacenter,
|
||||
Service: reqReal.Service,
|
||||
})
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
roots, ok := rawRoots.(*structs.IndexedCARoots)
|
||||
if !ok {
|
||||
return result, errors.New("invalid RootCA response type")
|
||||
}
|
||||
if roots.TrustDomain == "" {
|
||||
return result, errors.New("cluster has no CA bootstrapped")
|
||||
}
|
||||
|
||||
// Build the service ID
|
||||
serviceID := &connect.SpiffeIDService{
|
||||
Host: roots.TrustDomain,
|
||||
Datacenter: reqReal.Datacenter,
|
||||
Namespace: "default",
|
||||
Service: reqReal.Service,
|
||||
}
|
||||
|
||||
// Create a new private key
|
||||
pk, pkPEM, err := connect.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Create a CSR.
|
||||
csr, err := connect.CreateCSR(serviceID, pk)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Request signing
|
||||
var reply structs.IssuedCert
|
||||
|
@ -117,7 +141,7 @@ func (c *ConnectCALeaf) Fetch(opts cache.FetchOptions, req cache.Request) (cache
|
|||
if err := c.RPC.RPC("ConnectCA.Sign", &args, &reply); err != nil {
|
||||
return result, err
|
||||
}
|
||||
reply.PrivateKeyPEM = pk
|
||||
reply.PrivateKeyPEM = pkPEM
|
||||
|
||||
// Lock the issued certs map so we can insert it. We only insert if
|
||||
// we didn't happen to get a newer one. This should never happen since
|
||||
|
|
|
@ -25,10 +25,11 @@ func TestConnectCALeaf_changingRoots(t *testing.T) {
|
|||
defer close(rootsCh)
|
||||
rootsCh <- structs.IndexedCARoots{
|
||||
ActiveRootID: "1",
|
||||
TrustDomain: "fake-trust-domain.consul",
|
||||
QueryMeta: structs.QueryMeta{Index: 1},
|
||||
}
|
||||
|
||||
// Instrument ConnectCA.Sign to
|
||||
// Instrument ConnectCA.Sign to return signed cert
|
||||
var resp *structs.IssuedCert
|
||||
var idx uint64
|
||||
rpc.On("RPC", "ConnectCA.Sign", mock.Anything, mock.Anything).Return(nil).
|
||||
|
@ -67,6 +68,7 @@ func TestConnectCALeaf_changingRoots(t *testing.T) {
|
|||
// Let's send in new roots, which should trigger the sign req
|
||||
rootsCh <- structs.IndexedCARoots{
|
||||
ActiveRootID: "2",
|
||||
TrustDomain: "fake-trust-domain.consul",
|
||||
QueryMeta: structs.QueryMeta{Index: 2},
|
||||
}
|
||||
select {
|
||||
|
@ -101,6 +103,7 @@ func TestConnectCALeaf_expiringLeaf(t *testing.T) {
|
|||
defer close(rootsCh)
|
||||
rootsCh <- structs.IndexedCARoots{
|
||||
ActiveRootID: "1",
|
||||
TrustDomain: "fake-trust-domain.consul",
|
||||
QueryMeta: structs.QueryMeta{Index: 1},
|
||||
}
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ func NewConsulProvider(rawConfig map[string]interface{}, delegate ConsulProvider
|
|||
|
||||
// Generate a private key if needed
|
||||
if conf.PrivateKey == "" {
|
||||
pk, err := connect.GeneratePrivateKey()
|
||||
_, pk, err := connect.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -247,7 +247,7 @@ func (c *ConsulProvider) Sign(csr *x509.CertificateRequest) (string, error) {
|
|||
}
|
||||
err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error encoding private key: %s", err)
|
||||
return "", fmt.Errorf("error encoding certificate: %s", err)
|
||||
}
|
||||
|
||||
err = c.incrementProviderIndex(providerState)
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
package connect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// CreateCSR returns a CSR to sign the given service along with the PEM-encoded
|
||||
// private key for this certificate.
|
||||
func CreateCSR(uri CertURI, privateKey crypto.Signer) (string, error) {
|
||||
template := &x509.CertificateRequest{
|
||||
URIs: []*url.URL{uri.URI()},
|
||||
SignatureAlgorithm: x509.ECDSAWithSHA256,
|
||||
}
|
||||
|
||||
// Create the CSR itself
|
||||
var csrBuf bytes.Buffer
|
||||
bs, err := x509.CreateCertificateRequest(rand.Reader, template, privateKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = pem.Encode(&csrBuf, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: bs})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return csrBuf.String(), nil
|
||||
}
|
||||
|
||||
// GeneratePrivateKey generates a new Private key
|
||||
func GeneratePrivateKey() (crypto.Signer, string, error) {
|
||||
var pk *ecdsa.PrivateKey
|
||||
|
||||
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("error generating private key: %s", err)
|
||||
}
|
||||
|
||||
bs, err := x509.MarshalECPrivateKey(pk)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("error generating private key: %s", err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = pem.Encode(&buf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: bs})
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("error encoding private key: %s", err)
|
||||
}
|
||||
|
||||
return pk, buf.String(), nil
|
||||
}
|
|
@ -161,7 +161,10 @@ func TestLeaf(t testing.T, service string, root *structs.CARoot) (string, string
|
|||
}
|
||||
|
||||
// Generate fresh private key
|
||||
pkSigner, pkPEM := testPrivateKey(t)
|
||||
pkSigner, pkPEM, err := GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate private key: %s", err)
|
||||
}
|
||||
|
||||
// Cert template for generation
|
||||
template := x509.Certificate{
|
||||
|
|
|
@ -62,7 +62,8 @@ func (id *SpiffeIDSigning) CanSign(cu CertURI) bool {
|
|||
}
|
||||
|
||||
// SpiffeIDSigningForCluster returns the SPIFFE signing identifier (trust
|
||||
// domain) representation of the given CA config.
|
||||
// domain) representation of the given CA config. If config is nil this function
|
||||
// will panic.
|
||||
//
|
||||
// NOTE(banks): we intentionally fix the tld `.consul` for now rather than tie
|
||||
// this to the `domain` config used for DNS because changing DNS domain can't
|
||||
|
|
|
@ -224,9 +224,17 @@ func (s *ConnectCA) Roots(
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Build TrustDomain based on the ClusterID stored.
|
||||
signingID := connect.SpiffeIDSigningForCluster(config)
|
||||
reply.TrustDomain = signingID.Host()
|
||||
// Check CA is actually bootstrapped...
|
||||
if config != nil {
|
||||
// Build TrustDomain based on the ClusterID stored.
|
||||
signingID := connect.SpiffeIDSigningForCluster(config)
|
||||
if signingID == nil {
|
||||
// If CA is bootstrapped at all then this should never happen but be
|
||||
// defensive.
|
||||
return errors.New("no cluster trust domain setup")
|
||||
}
|
||||
reply.TrustDomain = signingID.Host()
|
||||
}
|
||||
}
|
||||
|
||||
return s.srv.blockingQuery(
|
||||
|
|
|
@ -157,7 +157,7 @@ func TestConnectCAConfig_TriggerRotation(t *testing.T) {
|
|||
|
||||
// Update the provider config to use a new private key, which should
|
||||
// cause a rotation.
|
||||
newKey, err := connect.GeneratePrivateKey()
|
||||
_, newKey, err := connect.GeneratePrivateKey()
|
||||
assert.NoError(err)
|
||||
newConfig := &structs.CAConfiguration{
|
||||
Provider: "consul",
|
||||
|
|
|
@ -16,6 +16,8 @@ import (
|
|||
"time"
|
||||
|
||||
metrics "github.com/armon/go-metrics"
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
|
||||
"github.com/hashicorp/consul/agent/config"
|
||||
"github.com/hashicorp/consul/agent/consul"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
|
@ -23,7 +25,6 @@ import (
|
|||
"github.com/hashicorp/consul/lib/freeport"
|
||||
"github.com/hashicorp/consul/logger"
|
||||
"github.com/hashicorp/consul/testutil/retry"
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
Loading…
Reference in New Issue