From 48c858504e419a29f808e38dd70b02d8e9022a20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Wei=C3=9Fe?= Date: Fri, 17 Nov 2023 15:03:20 +0100 Subject: [PATCH 1/3] Allow adding additional IPs for Coordinator root cert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Weiße --- coordinator/crypto/crypto.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/coordinator/crypto/crypto.go b/coordinator/crypto/crypto.go index 3d57e85e..639b96b5 100644 --- a/coordinator/crypto/crypto.go +++ b/coordinator/crypto/crypto.go @@ -15,6 +15,7 @@ import ( "crypto/x509/pkix" "fmt" "math" + "net" "time" "github.com/edgelesssys/marblerun/util" @@ -44,13 +45,23 @@ func GenerateCert( return nil, nil, fmt.Errorf("generating serial number: %w", err) } + var additionalDNSNames []string + var additionalIPs []net.IP + for _, name := range dnsNames { + if ip := net.ParseIP(name); ip != nil { + additionalIPs = append(additionalIPs, ip) + } else { + additionalDNSNames = append(additionalDNSNames, name) + } + } + template := x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ CommonName: commonName, }, - DNSNames: dnsNames, - IPAddresses: util.DefaultCertificateIPAddresses, + DNSNames: additionalDNSNames, + IPAddresses: append(util.DefaultCertificateIPAddresses, additionalIPs...), NotBefore: notBefore, NotAfter: notAfter, From cf844b4463f369ae65bc97eff7109daaaa512dd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Wei=C3=9Fe?= Date: Mon, 20 Nov 2023 09:04:51 +0100 Subject: [PATCH 2/3] Allow adding additional IPs for Marbles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Weiße --- coordinator/crypto/crypto.go | 15 +++-------- util/tls.go | 28 ++++++++++++++----- util/tls_test.go | 52 ++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 18 deletions(-) create mode 100644 util/tls_test.go diff --git a/coordinator/crypto/crypto.go b/coordinator/crypto/crypto.go index 639b96b5..b91d5a48 100644 --- a/coordinator/crypto/crypto.go +++ b/coordinator/crypto/crypto.go @@ -15,7 +15,6 @@ import ( "crypto/x509/pkix" "fmt" "math" - "net" "time" "github.com/edgelesssys/marblerun/util" @@ -24,7 +23,7 @@ import ( // GenerateCert creates a new certificate with the given parameters. // If privk is nil, a new private key is generated. func GenerateCert( - dnsNames []string, commonName string, privk *ecdsa.PrivateKey, + subjAltNames []string, commonName string, privk *ecdsa.PrivateKey, parentCertificate *x509.Certificate, parentPrivateKey *ecdsa.PrivateKey, ) (*x509.Certificate, *ecdsa.PrivateKey, error) { // Generate private key @@ -45,22 +44,14 @@ func GenerateCert( return nil, nil, fmt.Errorf("generating serial number: %w", err) } - var additionalDNSNames []string - var additionalIPs []net.IP - for _, name := range dnsNames { - if ip := net.ParseIP(name); ip != nil { - additionalIPs = append(additionalIPs, ip) - } else { - additionalDNSNames = append(additionalDNSNames, name) - } - } + additionalIPs, dnsNames := util.ExtractIPsFromAltNames(subjAltNames) template := x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ CommonName: commonName, }, - DNSNames: additionalDNSNames, + DNSNames: dnsNames, IPAddresses: append(util.DefaultCertificateIPAddresses, additionalIPs...), NotBefore: notBefore, NotAfter: notAfter, diff --git a/util/tls.go b/util/tls.go index 354bd303..23115d9f 100644 --- a/util/tls.go +++ b/util/tls.go @@ -42,7 +42,7 @@ func MustGenerateTestMarbleCredentials() (cert *x509.Certificate, csrRaw []byte, } // GenerateCert generates a new self-signed certificate associated key-pair. -func GenerateCert(dnsNames []string, ipAddrs []net.IP, isCA bool) (*x509.Certificate, *ecdsa.PrivateKey, error) { +func GenerateCert(subjAltNames []string, ipAddrs []net.IP, isCA bool) (*x509.Certificate, *ecdsa.PrivateKey, error) { privk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, nil, err @@ -56,8 +56,8 @@ func GenerateCert(dnsNames []string, ipAddrs []net.IP, isCA bool) (*x509.Certifi return nil, nil, err } - // TODO: what else do we need to set here? - // Do we need x509.KeyUsageKeyEncipherment? + additionalIPs, dnsNames := ExtractIPsFromAltNames(subjAltNames) + template := x509.Certificate{ Subject: pkix.Name{ CommonName: marbleName, @@ -66,7 +66,7 @@ func GenerateCert(dnsNames []string, ipAddrs []net.IP, isCA bool) (*x509.Certifi NotBefore: notBefore, NotAfter: notAfter, DNSNames: dnsNames, - IPAddresses: ipAddrs, + IPAddresses: append(additionalIPs, ipAddrs...), KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, @@ -86,10 +86,12 @@ func GenerateCert(dnsNames []string, ipAddrs []net.IP, isCA bool) (*x509.Certifi } // GenerateCSR generates a new CSR for the given DNSNames and private key. -func GenerateCSR(dnsNames []string, privk *ecdsa.PrivateKey) (*x509.CertificateRequest, error) { +func GenerateCSR(subjAltNames []string, privk *ecdsa.PrivateKey) (*x509.CertificateRequest, error) { + additionalIPs, dnsNames := ExtractIPsFromAltNames(subjAltNames) + template := x509.CertificateRequest{ DNSNames: dnsNames, - IPAddresses: DefaultCertificateIPAddresses, + IPAddresses: append(DefaultCertificateIPAddresses, additionalIPs...), } csrRaw, err := x509.CreateCertificateRequest(rand.Reader, &template, privk) if err != nil { @@ -122,3 +124,17 @@ func LoadGRPCTLSCredentials(cert *x509.Certificate, privk *ecdsa.PrivateKey, ins func TLSCertFromDER(certDER []byte, privk interface{}) *tls.Certificate { return &tls.Certificate{Certificate: [][]byte{certDER}, PrivateKey: privk} } + +// ExtractIPsFromAltNames extracts IP addresses and DNS names from a list of subject alternative names. +func ExtractIPsFromAltNames(subjAltNames []string) ([]net.IP, []string) { + var dnsNames []string + var additionalIPs []net.IP + for _, name := range subjAltNames { + if ip := net.ParseIP(name); ip != nil { + additionalIPs = append(additionalIPs, ip) + } else { + dnsNames = append(dnsNames, name) + } + } + return additionalIPs, dnsNames +} diff --git a/util/tls_test.go b/util/tls_test.go new file mode 100644 index 00000000..caf42f4e --- /dev/null +++ b/util/tls_test.go @@ -0,0 +1,52 @@ +// Copyright (c) Edgeless Systems GmbH. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package util + +import ( + "net" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestExtractIPsFromAltNames(t *testing.T) { + testCases := map[string]struct { + altNames []string + wantIPs []net.IP + wantDNSNames []string + }{ + "empty": { + altNames: []string{}, + wantIPs: []net.IP{}, + wantDNSNames: []string{}, + }, + "only IPs": { + altNames: []string{"192.0.2.1", "192.0.2.15"}, + wantIPs: []net.IP{net.ParseIP("192.0.2.1"), net.ParseIP("192.0.2.15")}, + wantDNSNames: []string{}, + }, + "only DNS names": { + altNames: []string{"foo.bar", "example.com"}, + wantIPs: []net.IP{}, + wantDNSNames: []string{"foo.bar", "example.com"}, + }, + "mixed": { + altNames: []string{"192.0.2.1", "foo.bar"}, + wantIPs: []net.IP{net.ParseIP("192.0.2.1")}, + wantDNSNames: []string{"foo.bar"}, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + gotIPs, gotDNSNames := ExtractIPsFromAltNames(tc.altNames) + assert.ElementsMatch(tc.wantIPs, gotIPs) + assert.ElementsMatch(tc.wantDNSNames, gotDNSNames) + }) + } +} From 109c13aee77912236d25d7211691080731e22ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Wei=C3=9Fe?= Date: Mon, 20 Nov 2023 09:05:56 +0100 Subject: [PATCH 3/3] Update docs to mention additional IPs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Weiße --- docs/docs/architecture/coordinator.md | 2 +- docs/docs/deployment/standalone.md | 4 ++-- docs/docs/workflows/add-service.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/docs/architecture/coordinator.md b/docs/docs/architecture/coordinator.md index 16a54f0d..bd08260a 100644 --- a/docs/docs/architecture/coordinator.md +++ b/docs/docs/architecture/coordinator.md @@ -14,7 +14,7 @@ The Coordinator can be configured with several environment variables: * `EDG_COORDINATOR_MESH_ADDR`: The listener address for the gRPC server * `EDG_COORDINATOR_CLIENT_ADDR`: The listener address for the HTTP REST server -* `EDG_COORDINATOR_DNS_NAMES`: The DNS names for the cluster's root certificate +* `EDG_COORDINATOR_DNS_NAMES`: The DNS names and IPs for the cluster's root certificate * `EDG_COORDINATOR_SEAL_DIR`: The file path for storing sealed data When you use MarbleRun [with Kubernetes](../deployment/kubernetes.md), you can [scale the Coordinator to multiple instances](../features/recovery.md#distributed-coordinator) to increase availability and reduce the occurrence of events that require [manual recovery](../workflows/recover-coordinator.md). diff --git a/docs/docs/deployment/standalone.md b/docs/docs/deployment/standalone.md index 25725b81..f3677ac4 100644 --- a/docs/docs/deployment/standalone.md +++ b/docs/docs/deployment/standalone.md @@ -23,7 +23,7 @@ Per default, the Coordinator starts with the following default values. You can s | --- | --- | --- | | the listener address for the gRPC server | localhost:2001 | EDG_COORDINATOR_MESH_ADDR | | the listener address for the HTTP server | localhost: 4433 | EDG_COORDINATOR_CLIENT_ADDR | -| the DNS names for the cluster’s root certificate | localhost | EDG_COORDINATOR_DNS_NAMES | +| the DNS names and IPs for the cluster’s root certificate | localhost | EDG_COORDINATOR_DNS_NAMES | | the file path for storing sealed data | $PWD/marblerun-coordinator-data | EDG_COORDINATOR_SEAL_DIR | :::tip @@ -53,4 +53,4 @@ Per default, a Marble starts with the following default values. You can set your | network address of the Coordinator’s API for Marbles | `localhost:2001` | EDG_MARBLE_COORDINATOR_ADDR | | reference on one entry from your manifest’s `Marbles` section | - (this needs to be set every time) | EDG_MARBLE_TYPE | | local file path where the Marble stores its UUID | `$PWD/uuid` | EDG_MARBLE_UUID_FILE | -| DNS names the Coordinator will issue the Marble’s certificate for | `$EDG_MARBLE_TYPE` | EDG_MARBLE_DNS_NAMES | +| DNS names and IPs the Coordinator will issue the Marble’s certificate for | `$EDG_MARBLE_TYPE` | EDG_MARBLE_DNS_NAMES | diff --git a/docs/docs/workflows/add-service.md b/docs/docs/workflows/add-service.md index 79c0e2d0..568362d1 100644 --- a/docs/docs/workflows/add-service.md +++ b/docs/docs/workflows/add-service.md @@ -123,7 +123,7 @@ The environment variables have the following purposes. * `EDG_MARBLE_UUID_FILE` is the local file path where the Marble stores its UUID. Every instance of a Marble has its unique and public UUID. The file is needed to allow a Marble to restart under its UUID. -* `EDG_MARBLE_DNS_NAMES` is the list of DNS names the Coordinator will issue the Marble's certificate for. +* `EDG_MARBLE_DNS_NAMES` is the list of DNS names and IPs the Coordinator will issue the Marble's certificate for. ## **Step 4:** Deploy your service with Kubernetes