diff --git a/coordinator/core/marbleapi.go b/coordinator/core/marbleapi.go index 41cb6946..43417548 100644 --- a/coordinator/core/marbleapi.go +++ b/coordinator/core/marbleapi.go @@ -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. @@ -309,7 +296,7 @@ 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), @@ -317,7 +304,7 @@ func customizeParameters(params manifest.Parameters, specialSecrets reservedSecr } // 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, } @@ -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) @@ -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)}, @@ -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 } diff --git a/coordinator/core/marbleapi_test.go b/coordinator/core/marbleapi_test.go index 8d2f4a1f..3da1c253 100644 --- a/coordinator/core/marbleapi_test.go +++ b/coordinator/core/marbleapi_test.go @@ -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, } diff --git a/coordinator/manifest/manifest.go b/coordinator/manifest/manifest.go index 58795359..ee49e712 100644 --- a/coordinator/manifest/manifest.go +++ b/coordinator/manifest/manifest.go @@ -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 @@ -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. diff --git a/coordinator/manifest/manifest_test.go b/coordinator/manifest/manifest_test.go index 311e54db..abec189f 100644 --- a/coordinator/manifest/manifest_test.go +++ b/coordinator/manifest/manifest_test.go @@ -11,7 +11,6 @@ import ( "crypto/x509" "encoding/json" "encoding/pem" - "fmt" "testing" "github.com/edgelesssys/marblerun/coordinator/quote" @@ -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": { @@ -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 { @@ -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) } diff --git a/docs/docs/workflows/define-manifest.md b/docs/docs/workflows/define-manifest.md index ca5fbad0..0d76970d 100644 --- a/docs/docs/workflows/define-manifest.md +++ b/docs/docs/workflows/define-manifest.md @@ -177,6 +177,8 @@ The following named keys and certificates are always available. * `.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. +* `.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.