agent/consul: test for ConnectCA.Sign

This commit is contained in:
Mitchell Hashimoto 2018-03-19 20:29:14 -07:00
parent a360c5cca4
commit 9a8653f45e
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
7 changed files with 101 additions and 3 deletions

View File

@ -17,6 +17,7 @@ import (
"time" "time"
"github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/go-uuid"
"github.com/mitchellh/go-testing-interface" "github.com/mitchellh/go-testing-interface"
) )
@ -32,14 +33,15 @@ const testClusterID = "11111111-2222-3333-4444-555555555555"
var testCACounter uint64 = 0 var testCACounter uint64 = 0
// TestCA creates a test CA certificate and signing key and returns it // TestCA creates a test CA certificate and signing key and returns it
// in the CARoot structure format. The CARoot returned will NOT have an ID // in the CARoot structure format. The returned CA will be set as Active = true.
// set.
// //
// If xc is non-nil, then the returned certificate will have a signing cert // If xc is non-nil, then the returned certificate will have a signing cert
// that is cross-signed with the previous cert, and this will be set as // that is cross-signed with the previous cert, and this will be set as
// SigningCert. // SigningCert.
func TestCA(t testing.T, xc *structs.CARoot) *structs.CARoot { func TestCA(t testing.T, xc *structs.CARoot) *structs.CARoot {
var result structs.CARoot var result structs.CARoot
result.ID = testUUID(t)
result.Active = true
result.Name = fmt.Sprintf("Test CA %d", atomic.AddUint64(&testCACounter, 1)) result.Name = fmt.Sprintf("Test CA %d", atomic.AddUint64(&testCACounter, 1))
// Create the private key we'll use for this CA cert. // Create the private key we'll use for this CA cert.
@ -276,3 +278,13 @@ func testPrivateKey(t testing.T, ca *structs.CARoot) crypto.Signer {
func testSerialNumber() (*big.Int, error) { func testSerialNumber() (*big.Int, error) {
return rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(159), nil)) return rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(159), nil))
} }
// testUUID generates a UUID for testing.
func testUUID(t testing.T) string {
ret, err := uuid.GenerateUUID()
if err != nil {
t.Fatalf("Unable to generate a UUID, %s", err)
}
return ret
}

View File

@ -0,0 +1,15 @@
package connect
import (
"github.com/mitchellh/go-testing-interface"
)
// TestSpiffeIDService returns a SPIFFE ID representing a service.
func TestSpiffeIDService(t testing.T, service string) *SpiffeIDService {
return &SpiffeIDService{
Host: testClusterID + ".consul",
Namespace: "default",
Datacenter: "dc01",
Service: service,
}
}

View File

@ -88,7 +88,12 @@ func (s *ConnectCA) Sign(
return fmt.Errorf("SPIFFE ID in CSR must be a service ID") return fmt.Errorf("SPIFFE ID in CSR must be a service ID")
} }
var root *structs.CARoot // Get the currently active root
state := s.srv.fsm.State()
_, root, err := state.CARootActive(nil)
if err != nil {
return err
}
// Determine the signing certificate. It is the set signing cert // Determine the signing certificate. It is the set signing cert
// unless that is empty, in which case it is identically to the public // unless that is empty, in which case it is identically to the public

View File

@ -0,0 +1,42 @@
package consul
import (
"os"
"testing"
"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/testrpc"
"github.com/hashicorp/net-rpc-msgpackrpc"
"github.com/stretchr/testify/assert"
)
// Test CA signing
//
// NOTE(mitchellh): Just testing the happy path and not all the other validation
// issues because the internals of this method will probably be gutted for the
// CA plugins then we can just test mocks.
func TestConnectCASign(t *testing.T) {
t.Parallel()
assert := assert.New(t)
dir1, s1 := testServer(t)
defer os.RemoveAll(dir1)
defer s1.Shutdown()
codec := rpcClient(t, s1)
defer codec.Close()
testrpc.WaitForLeader(t, s1.RPC, "dc1")
// Insert a CA
state := s1.fsm.State()
assert.Nil(state.CARootSet(1, connect.TestCA(t, nil)))
// Generate a CSR and request signing
args := &structs.CASignRequest{
Datacenter: "dc01",
CSR: connect.TestCSR(t, connect.TestSpiffeIDService(t, "web")),
}
var reply interface{}
assert.Nil(msgpackrpc.CallWithCodec(codec, "ConnectCA.Sign", args, &reply))
}

View File

@ -55,6 +55,24 @@ func (s *Store) CARoots(ws memdb.WatchSet) (uint64, structs.CARoots, error) {
return idx, results, nil return idx, results, nil
} }
// CARootActive returns the currently active CARoot.
func (s *Store) CARootActive(ws memdb.WatchSet) (uint64, *structs.CARoot, error) {
// Get all the roots since there should never be that many and just
// do the filtering in this method.
var result *structs.CARoot
idx, roots, err := s.CARoots(ws)
if err == nil {
for _, r := range roots {
if r.Active {
result = r
break
}
}
}
return idx, result, err
}
// CARootSet creates or updates a CA root. // CARootSet creates or updates a CA root.
// //
// NOTE(mitchellh): I have a feeling we'll want a CARootMultiSetCAS to // NOTE(mitchellh): I have a feeling we'll want a CARootMultiSetCAS to

View File

@ -33,6 +33,12 @@ type CARoot struct {
SigningCert string SigningCert string
SigningKey string SigningKey string
// Active is true if this is the current active CA. This must only
// be true for exactly one CA. For any method that modifies roots in the
// state store, tests should be written to verify that multiple roots
// cannot be active.
Active bool
RaftIndex RaftIndex
} }