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

Support injection of Coordinator root and intermediate certificates into Marble environment #784

Merged
merged 1 commit into from
Jan 9, 2025
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
41 changes: 14 additions & 27 deletions coordinator/core/marbleapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,6 @@ import (
"google.golang.org/grpc/status"
)

type reservedSecrets struct {
RootCA manifest.Secret
MarbleCert manifest.Secret
CoordinatorRoot manifest.Secret
CoordinatorIntermediate manifest.Secret
}

// Defines the "MarbleRun" prefix when mentioned in a manifest.
type secretsWrapper struct {
MarbleRun reservedSecrets
Secrets map[string]manifest.Secret
}

// Activate implements the MarbleAPI function to authenticate a marble (implements the MarbleServer interface).
//
// Verifies the marble's integrity and subsequently provides the marble with a certificate for authentication and application-specific parameters as defined in the Coordinator's manifest.
Expand Down Expand Up @@ -309,15 +296,15 @@ func (c *Core) generateCertFromCSR(txdata storeGetter, csrReq []byte, pubk ecdsa
}

// customizeParameters replaces the placeholders in the manifest's parameters with the actual values.
func customizeParameters(params manifest.Parameters, specialSecrets reservedSecrets, userSecrets map[string]manifest.Secret) (*rpc.Parameters, error) {
func customizeParameters(params manifest.Parameters, specialSecrets manifest.ReservedSecrets, userSecrets map[string]manifest.Secret) (*rpc.Parameters, error) {
customParams := rpc.Parameters{
Argv: params.Argv,
Files: make(map[string][]byte),
Env: make(map[string][]byte),
}

// Wrap the authentication secrets to have the "MarbleRun" prefix in front of them when mentioned in a manifest
secretsWrapped := secretsWrapper{
secretsWrapped := manifest.SecretsWrapper{
MarbleRun: specialSecrets,
Secrets: userSecrets,
}
Expand Down Expand Up @@ -382,7 +369,7 @@ func customizeParameters(params manifest.Parameters, specialSecrets reservedSecr
return &customParams, nil
}

func parseSecrets(data string, tplFunc template.FuncMap, secretsWrapped secretsWrapper) (string, error) {
func parseSecrets(data string, tplFunc template.FuncMap, secretsWrapped manifest.SecretsWrapper) (string, error) {
var templateResult bytes.Buffer

tpl, err := template.New("data").Funcs(tplFunc).Parse(data)
Expand All @@ -397,47 +384,47 @@ func parseSecrets(data string, tplFunc template.FuncMap, secretsWrapped secretsW
return templateResult.String(), nil
}

func (c *Core) generateMarbleAuthSecrets(txdata storeGetter, req *rpc.ActivationReq, marbleUUID uuid.UUID) (reservedSecrets, error) {
func (c *Core) generateMarbleAuthSecrets(txdata storeGetter, req *rpc.ActivationReq, marbleUUID uuid.UUID) (manifest.ReservedSecrets, error) {
// generate key-pair for marble
privk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return reservedSecrets{}, err
return manifest.ReservedSecrets{}, err
}
encodedPrivKey, err := x509.MarshalPKCS8PrivateKey(privk)
if err != nil {
return reservedSecrets{}, err
return manifest.ReservedSecrets{}, err
}
encodedPubKey, err := x509.MarshalPKIXPublicKey(&privk.PublicKey)
if err != nil {
return reservedSecrets{}, err
return manifest.ReservedSecrets{}, err
}

// Generate Marble certificate
certRaw, err := c.generateCertFromCSR(txdata, req.GetCSR(), privk.PublicKey, req.GetMarbleType(), marbleUUID.String())
if err != nil {
return reservedSecrets{}, err
return manifest.ReservedSecrets{}, err
}

marbleCert, err := x509.ParseCertificate(certRaw)
if err != nil {
return reservedSecrets{}, err
return manifest.ReservedSecrets{}, err
}

marbleRootCert, err := txdata.GetCertificate(constants.SKMarbleRootCert)
if err != nil {
return reservedSecrets{}, err
return manifest.ReservedSecrets{}, err
}
coordinatorRootCert, err := txdata.GetCertificate(constants.SKCoordinatorRootCert)
if err != nil {
return reservedSecrets{}, err
return manifest.ReservedSecrets{}, err
}
coordinatorIntermediateCert, err := txdata.GetCertificate(constants.SKCoordinatorIntermediateCert)
if err != nil {
return reservedSecrets{}, err
return manifest.ReservedSecrets{}, err
}

// customize marble's parameters
authSecrets := reservedSecrets{
authSecrets := manifest.ReservedSecrets{
RootCA: manifest.Secret{Cert: manifest.Certificate(*marbleRootCert)},
MarbleCert: manifest.Secret{Cert: manifest.Certificate(*marbleCert), Public: encodedPubKey, Private: encodedPrivKey},
CoordinatorRoot: manifest.Secret{Cert: manifest.Certificate(*coordinatorRootCert)},
Expand All @@ -447,7 +434,7 @@ func (c *Core) generateMarbleAuthSecrets(txdata storeGetter, req *rpc.Activation
return authSecrets, nil
}

func (c *Core) setTTLSConfig(txdata storeGetter, marble *manifest.Marble, specialSecrets reservedSecrets, userSecrets map[string]manifest.Secret) error {
func (c *Core) setTTLSConfig(txdata storeGetter, marble *manifest.Marble, specialSecrets manifest.ReservedSecrets, userSecrets map[string]manifest.Secret) error {
if len(marble.TLS) == 0 {
return nil
}
Expand Down
4 changes: 2 additions & 2 deletions coordinator/core/marbleapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -474,12 +474,12 @@ func TestParseSecrets(t *testing.T) {
"emptysecret": {},
}

testReservedSecrets := reservedSecrets{
testReservedSecrets := manifest.ReservedSecrets{
RootCA: manifest.Secret{Public: []byte{0, 0, 42}, Private: []byte{0, 0, 7}},
MarbleCert: manifest.Secret{Public: []byte{42, 0, 0}, Private: []byte{7, 0, 0}},
}

testWrappedSecrets := secretsWrapper{
testWrappedSecrets := manifest.SecretsWrapper{
MarbleRun: testReservedSecrets,
Secrets: testSecrets,
}
Expand Down
12 changes: 10 additions & 2 deletions coordinator/manifest/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,12 @@ func (m Manifest) TemplateDryRun(secrets map[string]Secret) error {
Public: []byte{0x41},
Private: []byte{0x41},
},
CoordinatorRoot: Secret{
Cert: Certificate{Raw: []byte{0x41}},
},
CoordinatorIntermediate: Secret{
Cert: Certificate{Raw: []byte{0x41}},
},
},
}
// make sure templates in file/env declarations can actually be executed
Expand Down Expand Up @@ -620,8 +626,10 @@ func (m Manifest) CheckUpdate(originalPackages map[string]quote.PackagePropertie

// ReservedSecrets is a tuple of secrets reserved for a single Marble.
type ReservedSecrets struct {
RootCA Secret
MarbleCert Secret
RootCA Secret
MarbleCert Secret
CoordinatorRoot Secret
CoordinatorIntermediate Secret
}

// SecretsWrapper is used to define the "MarbleRun" prefix when mentioned in a manifest.
Expand Down
118 changes: 115 additions & 3 deletions coordinator/manifest/manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"testing"

"github.com/edgelesssys/marblerun/coordinator/quote"
Expand Down Expand Up @@ -71,7 +70,6 @@ func TestFile(t *testing.T) {
func TestTemplateDryRun(t *testing.T) {
testCases := map[string]struct {
manifest []byte
secrets map[string]Secret
wantErr bool
}{
"valid": {
Expand Down Expand Up @@ -225,6 +223,121 @@ func TestTemplateDryRun(t *testing.T) {
}`),
wantErr: true,
},
"reserved secrets": {
manifest: []byte(`{
"Packages": { "backend": { "UniqueID": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" }},
"Marbles": {
"backend_first": {
"Package": "backend",
"Parameters": {
"Files": {
"root_ca": "{{ pem .MarbleRun.RootCA.Cert }}",
"marble_cert": "{{ pem .MarbleRun.MarbleCert.Cert }}",
"marble_key": "{{ pem .MarbleRun.MarbleCert.Private }}",
"coordinator_root": "{{ pem .MarbleRun.CoordinatorRoot.Cert }}",
"coordinator_intermediate": "{{ pem .MarbleRun.CoordinatorIntermediate.Cert }}"
}
}
}
}
}`),
},
"reserved root ca does not support private": {
manifest: []byte(`{
"Packages": { "backend": { "UniqueID": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" }},
"Marbles": {
"backend_first": {
"Package": "backend",
"Parameters": {
"Files": {
"root_ca": "{{ pem .MarbleRun.RootCA.Private }}"
}
}
}
}
}`),
wantErr: true,
},
"reserved root ca does not support public": {
manifest: []byte(`{
"Packages": { "backend": { "UniqueID": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" }},
"Marbles": {
"backend_first": {
"Package": "backend",
"Parameters": {
"Files": {
"root_ca": "{{ pem .MarbleRun.RootCA.Public }}"
}
}
}
}
}`),
wantErr: true,
},
"reserved coordinator root does not support private": {
manifest: []byte(`{
"Packages": { "backend": { "UniqueID": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" }},
"Marbles": {
"backend_first": {
"Package": "backend",
"Parameters": {
"Files": {
"root_ca": "{{ pem .MarbleRun.CoordinatorRoot.Private }}"
}
}
}
}
}`),
wantErr: true,
},
"reserved coordinator root does not support public": {
manifest: []byte(`{
"Packages": { "backend": { "UniqueID": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" }},
"Marbles": {
"backend_first": {
"Package": "backend",
"Parameters": {
"Files": {
"root_ca": "{{ pem .MarbleRun.CoordinatorRoot.Public }}"
}
}
}
}
}`),
wantErr: true,
},
"reserved coordinator intermediate does not support private": {
manifest: []byte(`{
"Packages": { "backend": { "UniqueID": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" }},
"Marbles": {
"backend_first": {
"Package": "backend",
"Parameters": {
"Files": {
"root_ca": "{{ pem .MarbleRun.CoordinatorIntermediate.Private }}"
}
}
}
}
}`),
wantErr: true,
},
"reserved coordinator intermediate does not support public": {
manifest: []byte(`{
"Packages": { "backend": { "UniqueID": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" }},
"Marbles": {
"backend_first": {
"Package": "backend",
"Parameters": {
"Files": {
"root_ca": "{{ pem .MarbleRun.CoordinatorIntermediate.Public }}"
}
}
}
}
}`),
wantErr: true,
},
}

for name, tc := range testCases {
Expand All @@ -248,7 +361,6 @@ func TestTemplateDryRun(t *testing.T) {
err := manifest.TemplateDryRun(manifest.Secrets)
if tc.wantErr {
assert.Error(err)
fmt.Println(err)
} else {
assert.NoError(err)
}
Expand Down
2 changes: 2 additions & 0 deletions docs/docs/workflows/define-manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@
* `.MarbleRun.RootCA.Cert`: the root certificate of the cluster issued by the Coordinator; this can be used to verify the certificates of all Marbles in the cluster.
* `.MarbleRun.MarbleCert.Cert`: the Marble's certificate; this is issued by the `.MarbleRun.RootCA.Cert` and is for Marble-to-Marble and Marble-to-client authentication.
* `.MarbleRun.MarbleCert.Private`: the Marble's private key corresponding to `.MarbleRun.MarbleCert.Cert`
* `.MarbleRun.CoordinatorRoot.Cert`: the root certificate of the Coordinator; this can be used to verify Marbles in the cluster across multiple manifest updates.

Check warning on line 180 in docs/docs/workflows/define-manifest.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Microsoft.Passive] 'be used' looks like passive voice. Raw Output: {"message": "[Microsoft.Passive] 'be used' looks like passive voice.", "location": {"path": "docs/docs/workflows/define-manifest.md", "range": {"start": {"line": 180, "column": 88}}}, "severity": "INFO"}

Check warning on line 180 in docs/docs/workflows/define-manifest.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Microsoft.ComplexWords] Consider using 'many' instead of 'multiple'. Raw Output: {"message": "[Microsoft.ComplexWords] Consider using 'many' instead of 'multiple'.", "location": {"path": "docs/docs/workflows/define-manifest.md", "range": {"start": {"line": 180, "column": 136}}}, "severity": "INFO"}
* `.MarbleRun.CoordinatorIntermediate.Cert`: the intermediate certificate of the Coordinator; see [the public Key infrastructure and certificate authority section](../architecture/security.md#public-key-infrastructure-and-certificate-authority) for more information on how this certificate relates to `.MarbleRun.RootCA`.

Finally, the optional field `MaxActivations` can be used to restrict the number of distinct instances that can be created of a Marble.

Expand Down