Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow adding additional IPs for Coordinator root cert #528

Merged
merged 3 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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...),
daniel-weisse marked this conversation as resolved.
Show resolved Hide resolved
daniel-weisse marked this conversation as resolved.
Show resolved Hide resolved
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)
})
}
}
Loading