254 lines
7.7 KiB
Go
254 lines
7.7 KiB
Go
//go:build integration
|
|
|
|
package identity
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ed25519"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/pem"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
// The authority that certifies names inside the mesh.
|
|
//
|
|
// Asserted by verifying, not by inspecting: a certificate that parses and does not chain is a
|
|
// certificate that fails at the moment something connects, which is the worst place to find out.
|
|
|
|
func aServingKey(t *testing.T) (public string, private ed25519.PrivateKey) {
|
|
t.Helper()
|
|
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return base64.StdEncoding.EncodeToString(pub), priv
|
|
}
|
|
|
|
func parsed(t *testing.T, certificate string) *x509.Certificate {
|
|
t.Helper()
|
|
block, _ := pem.Decode([]byte(certificate))
|
|
if block == nil {
|
|
t.Fatal("not a certificate")
|
|
}
|
|
got, err := x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return got
|
|
}
|
|
|
|
func TestACertificateChainsToTheMeshsOwnAuthority(t *testing.T) {
|
|
ident := fresh(t)
|
|
ctx := context.Background()
|
|
public, _ := aServingKey(t)
|
|
|
|
certificate, err := ident.Certify(ctx, "workstation", "workstation.internal", public)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
authority, err := ident.EstablishAuthority(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
roots := x509.NewCertPool()
|
|
if !roots.AppendCertsFromPEM([]byte(authority.Certificate)) {
|
|
t.Fatal("the mesh's authority is not usable as a root")
|
|
}
|
|
if _, err := parsed(t, certificate).Verify(x509.VerifyOptions{
|
|
Roots: roots, DNSName: "workstation.internal",
|
|
}); err != nil {
|
|
t.Fatalf("what the mesh issued does not verify against the mesh: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTheNameIsWhereEverythingLooksForIt(t *testing.T) {
|
|
// A certificate carrying the name only in its common name is one every modern client refuses.
|
|
ident := fresh(t)
|
|
public, _ := aServingKey(t)
|
|
certificate, err := ident.Certify(context.Background(), "a", "a.internal", public)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := parsed(t, certificate)
|
|
if len(got.DNSNames) != 1 || got.DNSNames[0] != "a.internal" {
|
|
t.Fatalf("the name is not in the subject alternative names: %v", got.DNSNames)
|
|
}
|
|
}
|
|
|
|
func TestItCertifiesTheKeyTheNodeGeneratedAndNoOther(t *testing.T) {
|
|
// A certificate authority's whole job is to say "this name belongs to the holder of this
|
|
// key". One that made the key would be saying something about a key it also holds.
|
|
ident := fresh(t)
|
|
public, private := aServingKey(t)
|
|
certificate, err := ident.Certify(context.Background(), "a", "a.internal", public)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
inside, ok := parsed(t, certificate).PublicKey.(ed25519.PublicKey)
|
|
if !ok {
|
|
t.Fatalf("the certificate carries a %T", parsed(t, certificate).PublicKey)
|
|
}
|
|
if !inside.Equal(private.Public()) {
|
|
t.Fatal("the certificate is for a key the node does not hold")
|
|
}
|
|
}
|
|
|
|
func TestAnAuthorityIsEstablishedOnceAndKept(t *testing.T) {
|
|
// Two authorities and nothing says which certificate to believe.
|
|
ident := fresh(t)
|
|
ctx := context.Background()
|
|
first, err := ident.EstablishAuthority(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
second, err := ident.EstablishAuthority(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if first.Certificate != second.Certificate {
|
|
t.Fatal("asking twice made a second authority")
|
|
}
|
|
}
|
|
|
|
func TestSomethingThatIsNotAServingKeyIsRefused(t *testing.T) {
|
|
ident := fresh(t)
|
|
for _, bad := range []string{"", "not-base64!", base64.StdEncoding.EncodeToString([]byte("short"))} {
|
|
if _, err := ident.Certify(context.Background(), "a", "a.internal", bad); err == nil {
|
|
t.Fatalf("%q was certified", bad)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTheAuthorityCannotBeUsedToMakeAnotherAuthority(t *testing.T) {
|
|
// An authority that could sign another is one that can be delegated without anybody deciding
|
|
// to. The path length says it cannot.
|
|
ident := fresh(t)
|
|
authority, err := ident.EstablishAuthority(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := parsed(t, authority.Certificate)
|
|
if !got.IsCA {
|
|
t.Fatal("the authority is not an authority")
|
|
}
|
|
if got.MaxPathLen != 0 || !got.MaxPathLenZero {
|
|
t.Fatalf("the authority may sign another authority: path length %d", got.MaxPathLen)
|
|
}
|
|
}
|
|
|
|
func TestACertificateFromAnotherMeshDoesNotVerify(t *testing.T) {
|
|
// The whole point of two authorities being separate: one mesh's certificate means nothing to
|
|
// another, and the check that says so is the one that must not be skipped.
|
|
one, two := fresh(t), fresh(t)
|
|
public, _ := aServingKey(t)
|
|
certificate, err := one.Certify(context.Background(), "a", "a.internal", public)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
other, err := two.EstablishAuthority(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
roots := x509.NewCertPool()
|
|
roots.AppendCertsFromPEM([]byte(other.Certificate))
|
|
if _, err := parsed(t, certificate).Verify(x509.VerifyOptions{
|
|
Roots: roots, DNSName: "a.internal",
|
|
}); err == nil {
|
|
t.Fatal("another mesh's certificate verified")
|
|
} else if !strings.Contains(err.Error(), "authority") && !strings.Contains(err.Error(), "signed") {
|
|
t.Fatalf("refused for the wrong reason: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTwoProcessesStartingTogetherAgreeOnOneAuthority(t *testing.T) {
|
|
// A restart while another copy is coming up. Both find nothing and both generate; only one
|
|
// insert may survive, and the loser must read back the winner rather than return the
|
|
// authority it generated and did not store — a mesh with two authorities has certificates
|
|
// half its machines refuse.
|
|
ident := fresh(t)
|
|
|
|
var wg sync.WaitGroup
|
|
authorities := make([]Authority, 6)
|
|
errs := make([]error, 6)
|
|
for i := range authorities {
|
|
wg.Add(1)
|
|
go func(i int) {
|
|
defer wg.Done()
|
|
authorities[i], errs[i] = ident.EstablishAuthority(context.Background())
|
|
}(i)
|
|
}
|
|
wg.Wait()
|
|
|
|
for i, err := range errs {
|
|
if err != nil {
|
|
t.Fatalf("establish %d failed: %v", i, err)
|
|
}
|
|
}
|
|
for i, a := range authorities {
|
|
if a.Certificate != authorities[0].Certificate {
|
|
t.Errorf("establish %d has a different authority from establish 0", i)
|
|
}
|
|
}
|
|
|
|
var count int
|
|
if err := ident.store.Pool().QueryRow(t.Context(),
|
|
`select count(*) from authority`).Scan(&count); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if count != 1 {
|
|
t.Errorf("%d authorities exist; exactly one may", count)
|
|
}
|
|
}
|
|
|
|
// Asking twice gives the same certificate, to the byte.
|
|
//
|
|
// Every signing carries a fresh random serial, so a mesh that signed per composition composed a
|
|
// different declaration each time it was asked what a machine should be — and every machine
|
|
// carrying a certificate stood eternally "waiting", pushed seconds ago and already behind. Found
|
|
// live on a kept mesh: two plans seconds apart, identical but for one serial.
|
|
func TestACertificateIsIssuedOnceAndKept(t *testing.T) {
|
|
ident := fresh(t)
|
|
ctx := context.Background()
|
|
public, _ := aServingKey(t)
|
|
|
|
if _, err := ident.RecordNodeKey(ctx, "1b7e0000-0000-4000-8000-000000000001", make(ed25519.PublicKey, ed25519.PublicKeySize)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := ident.RecordServingKey(ctx, "1b7e0000-0000-4000-8000-000000000001", public); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
first, err := ident.Certify(ctx, "a", "a.internal", public)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
second, err := ident.Certify(ctx, "a", "a.internal", public)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if first != second {
|
|
t.Fatal("two askings gave two certificates; every composition then differs by a serial " +
|
|
"and a machine carrying one is eternally behind")
|
|
}
|
|
|
|
// And a new key is a new world: the kept answer must not outlive what it certifies.
|
|
fresh2, _ := aServingKey(t)
|
|
if err := ident.RecordServingKey(ctx, "1b7e0000-0000-4000-8000-000000000001", fresh2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
third, err := ident.Certify(ctx, "a", "a.internal", fresh2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if third == first {
|
|
t.Fatal("the node rejoined with a new key and was handed the certificate of its old one")
|
|
}
|
|
}
|