From c0f47a9a65f461bccd9e1a9843f3a9f3e8001422 Mon Sep 17 00:00:00 2001 From: Markus Rudy Date: Wed, 20 Mar 2024 14:50:57 +0100 Subject: [PATCH] ca: support IP address as SAN --- internal/ca/ca.go | 16 +++++++++++++++- internal/ca/ca_test.go | 32 +++++++++++++++++++------------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/internal/ca/ca.go b/internal/ca/ca.go index f3b1ffde8..000ad5700 100644 --- a/internal/ca/ca.go +++ b/internal/ca/ca.go @@ -10,6 +10,7 @@ import ( "encoding/pem" "errors" "fmt" + "net" "sync" "time" @@ -72,7 +73,19 @@ func New() (*CA, error) { } // NewAttestedMeshCert creates a new attested mesh certificate. -func (c *CA) NewAttestedMeshCert(dnsNames []string, extensions []pkix.Extension, subjectPublicKey any) ([]byte, error) { +func (c *CA) NewAttestedMeshCert(names []string, extensions []pkix.Extension, subjectPublicKey any) ([]byte, error) { + var dnsNames []string + var ips []net.IP + for _, name := range names { + // If a string parses correctly as an IP address, it is not a valid DNS name anyway, so we + // can split the SANs into DNS and IP by that predicate. + if ip := net.ParseIP(name); ip != nil { + ips = append(ips, ip) + } else { + dnsNames = append(dnsNames, name) + } + } + c.intermMux.RLock() defer c.intermMux.RUnlock() now := time.Now() @@ -86,6 +99,7 @@ func (c *CA) NewAttestedMeshCert(dnsNames []string, extensions []pkix.Extension, BasicConstraintsValid: true, ExtraExtensions: extensions, DNSNames: dnsNames, + IPAddresses: ips, } certPEM, err := createCert(certTemplate, c.meshCACert, subjectPublicKey, c.intermPrivKey) diff --git a/internal/ca/ca_test.go b/internal/ca/ca_test.go index 080a0e23a..3b18577c7 100644 --- a/internal/ca/ca_test.go +++ b/internal/ca/ca_test.go @@ -33,10 +33,7 @@ func TestNewCA(t *testing.T) { ok := root.AppendCertsFromPEM(ca.rootPEM) assert.True(ok) - block, _ := pem.Decode(ca.intermPEM) - require.NotNil(block) - cert, err := x509.ParseCertificate(block.Bytes) - require.NoError(err) + cert := parsePEMCertificate(t, ca.intermPEM) opts := x509.VerifyOptions{Roots: root} @@ -52,12 +49,19 @@ func TestAttestedMeshCert(t *testing.T) { extensions []pkix.Extension subjectPub any wantErr bool + wantIPs int }{ "valid": { dnsNames: []string{"foo", "bar"}, extensions: []pkix.Extension{}, subjectPub: newKey(req).Public(), }, + "ips": { + dnsNames: []string{"foo", "192.0.2.1"}, + extensions: []pkix.Extension{}, + subjectPub: newKey(req).Public(), + wantIPs: 1, + }, } for name, tc := range testCases { @@ -68,20 +72,21 @@ func TestAttestedMeshCert(t *testing.T) { ca, err := New() require.NoError(err) - cert, err := ca.NewAttestedMeshCert(tc.dnsNames, tc.extensions, tc.subjectPub) + pem, err := ca.NewAttestedMeshCert(tc.dnsNames, tc.extensions, tc.subjectPub) if tc.wantErr { assert.Error(err) return } assert.NoError(err) - assert.NotNil(cert) + assert.NotNil(pem) - assertValidPEM(assert, cert) + cert := parsePEMCertificate(t, pem) + assert.Len(cert.IPAddresses, tc.wantIPs) }) } } -func TestCerateCert(t *testing.T) { +func TestCreateCert(t *testing.T) { req := require.New(t) testCases := map[string]struct { @@ -141,7 +146,7 @@ func TestCerateCert(t *testing.T) { } assert.NoError(err) - assertValidPEM(assert, pem) + parsePEMCertificate(t, pem) }) } } @@ -236,9 +241,10 @@ func newKey(require *require.Assertions) *ecdsa.PrivateKey { return key } -func assertValidPEM(assert *assert.Assertions, data []byte) { +func parsePEMCertificate(t *testing.T, data []byte) *x509.Certificate { block, _ := pem.Decode(data) - assert.NotNil(block) - _, err := x509.ParseCertificate(block.Bytes) - assert.NoError(err) + require.NotNil(t, block) + cert, err := x509.ParseCertificate(block.Bytes) + require.NoError(t, err) + return cert }