Skip to content

Commit

Permalink
Allow adding additional IPs to MarbleRun generated certificates (#528)
Browse files Browse the repository at this point in the history
* Allow adding additional IPs for Coordinator root cert
* Allow adding additional IPs for Marble TLS cert

---------

Signed-off-by: Daniel Weiße <[email protected]>
  • Loading branch information
daniel-weisse authored Nov 20, 2023
1 parent 1311c6a commit 92dcebf
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 12 deletions.
6 changes: 4 additions & 2 deletions coordinator/crypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,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
Expand All @@ -44,13 +44,15 @@ func GenerateCert(
return nil, nil, fmt.Errorf("generating serial number: %w", err)
}

additionalIPs, dnsNames := util.ExtractIPsFromAltNames(subjAltNames)

template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: commonName,
},
DNSNames: dnsNames,
IPAddresses: util.DefaultCertificateIPAddresses,
IPAddresses: append(util.DefaultCertificateIPAddresses, additionalIPs...),
NotBefore: notBefore,
NotAfter: notAfter,

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/architecture/coordinator.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/deployment/standalone.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 |
2 changes: 1 addition & 1 deletion docs/docs/workflows/add-service.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
28 changes: 22 additions & 6 deletions util/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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},
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}
52 changes: 52 additions & 0 deletions util/tls_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
}

0 comments on commit 92dcebf

Please sign in to comment.