diff --git a/coordinator/internal/authority/authority.go b/coordinator/internal/authority/authority.go index 70c4c3cdce..32a09e7b1b 100644 --- a/coordinator/internal/authority/authority.go +++ b/coordinator/internal/authority/authority.go @@ -212,7 +212,7 @@ func (m *Authority) syncState() error { return fmt.Errorf("parsing manifest: %w", err) } - meshKey, err := se.DeriveMeshCAKey(latest.TransitionHash) + meshKey, err := se.GenerateMeshCAKey() if err != nil { return fmt.Errorf("deriving mesh CA key: %w", err) } diff --git a/coordinator/internal/authority/userapi.go b/coordinator/internal/authority/userapi.go index 751a7b5324..d8e2be8d07 100644 --- a/coordinator/internal/authority/userapi.go +++ b/coordinator/internal/authority/userapi.go @@ -122,7 +122,7 @@ func (a *Authority) SetManifest(ctx context.Context, req *userapi.SetManifestReq } se := a.se.Load() - meshKey, err := se.DeriveMeshCAKey(nextTransitionHash) + meshKey, err := se.GenerateMeshCAKey() if err != nil { return nil, status.Errorf(codes.Internal, "deriving mesh CA key: %v", err) } diff --git a/coordinator/internal/seedengine/seedengine.go b/coordinator/internal/seedengine/seedengine.go index a70d687009..b3cc422473 100644 --- a/coordinator/internal/seedengine/seedengine.go +++ b/coordinator/internal/seedengine/seedengine.go @@ -9,6 +9,7 @@ import ( "bytes" "crypto/ecdsa" "crypto/elliptic" + "crypto/rand" "crypto/sha256" "errors" "fmt" @@ -92,23 +93,9 @@ func (s *SeedEngine) DerivePodSecret(policyHash [hashSize]byte) ([]byte, error) return s.hkdfDerive(s.podStateSeed, fmt.Sprintf("POD SECRET %x", policyHash)) } -// DeriveMeshCAKey derives a secret for a mesh CA from the transaction hash and the secret seed. -func (s *SeedEngine) DeriveMeshCAKey(transactionHash [hashSize]byte) (*ecdsa.PrivateKey, error) { - if transactionHash == [hashSize]byte{} { - return nil, errors.New("transaction hash must not be empty") - } - if bytes.Equal(transactionHash[:], s.hashFun().Sum(nil)) { - return nil, errors.New("transaction hash is the hash of an empty byte slice") - } - transactionSecret, err := s.hkdfDerive(s.historySeed, fmt.Sprintf("TRANSACTION SECRET %x", transactionHash)) - if err != nil { - return nil, err - } - meshCASeed, err := s.hkdfDerive(transactionSecret, "MESH CA SECRET") - if err != nil { - return nil, err - } - return s.generateECDSAPrivateKey(meshCASeed) +// GenerateMeshCAKey generates a new random key for the mesh authority. +func (s *SeedEngine) GenerateMeshCAKey() (*ecdsa.PrivateKey, error) { + return ecdsa.GenerateKey(elliptic.P384(), rand.Reader) } // RootCAKey returns the root CA key which is derived from the secret seed. diff --git a/coordinator/internal/seedengine/seedengine_test.go b/coordinator/internal/seedengine/seedengine_test.go index cbbdf4df64..33a9571a81 100644 --- a/coordinator/internal/seedengine/seedengine_test.go +++ b/coordinator/internal/seedengine/seedengine_test.go @@ -133,59 +133,3 @@ func TestSeedEngine_DerivePodSecret(t *testing.T) { }) } } - -func TestSeedEngine_DeriveMeshCAKey(t *testing.T) { - req := require.New(t) - - // transactionHash -> want - testCases := map[string]struct { - meshCAKey string // hex(x509.MarshalECPrivateKey(meshCAKey)) - err bool - }{ - /* - Crypto-determinism regression test cases. - - DO NOT CHANGE! - */ - "8d62644ef9944dbbb1a2b1a574840cbd6b09e5e7f96ac0f82a8a37271edd983b": {meshCAKey: "3081a40201010430f1247e2b66958faf9aa524a94816da813b18a035f15daf4d35cbbb60643e97b72df91a9135ea2fab7724979e913a3422a00706052b81040022a16403620004593ff9c7ecdefe21b428416e0ae81784b954b331faf3de9612b504db1e13305b834a1126fbd6fa7073081cb3b92f6464628fc51de3baf7b78037e02b1371259fa7633bc3b99588a16f24111c56f8f6db41369619204857b7d1416534c77f3c47"}, - "b838a7adb60d110d6c3c7a1dfa51b439b78386f439a092eda0d67d53cc01c02e": {meshCAKey: "3081a40201010430390804190ef6e01e34fff963a50850cd5d8345d092c413deb86c32dcdd14a1e2c7dac4317139e94842b97b335104150aa00706052b81040022a16403620004eb8541e09d18cb96c49dd66d2585597ae3f185d125ac6ff78371213d96636d74ced679370d63723eebbf1f98bb23e3cead1535cac7d595f5ff3b480dd53c7146e409956474fa4c20dc5956082d1c843343e65b827e7c91172ca5143250575591"}, - "11103d1efce19d05f5aaac2c8af405136ad91dae9f64ba25c2402100ff0e03eb": {meshCAKey: "3081a40201010430d89ede876f64c8c683fbb953ec57a841bb2c2f59a3863f0c325855da40f6da0a3babe7e64d925553d03d11c4d8caa8a4a00706052b81040022a164036200045ab49a846c2b742b85415fdfebd08272bf6e32bd1a0c605cf4b1cd25dbcce4c17f847c6dd27235a4da548134c505694048a6ecbf3864815dafca842edef5451440c103f3143e538bd5aa2a9bd8897a82923ac06a24dee3f7cb25187befbab8b8"}, - "d229c5714ca84d4e73b973636723e6cd5fe49f3c3e486732facfba61f94a10fc": {meshCAKey: "3081a402010104304b9320746578895fe917b55040fbfb65a806c9ca7757ff06d467a5eb9e9eec3f5a6ea5dda90734f5effa7e1ec14f4d12a00706052b81040022a164036200042e11ae0fc85bfde3a762f597d7bcafad8122749fc4c8ac614925b5c0060a2520541ff7c5c96d7c5bcb8784254f299e99fb263daf9204905f59cff7d94265b36f3b613e3625e5443e90d54b8d401e15b1a06bea5f6595891a23f2631633cd8bc3"}, - "91b7513a7709d2ab92d2c1fe1e431e37f0bea18165dd908b0e6386817b0c6faf": {meshCAKey: "3081a40201010430d7dc79626cbdb350d9bd7ea95f1461dc2ebf6f06d2cff9b501e4e802dd7a2ab30bb6298b509063b3237737da45564a95a00706052b81040022a164036200047831f43068f9b4acf553a19983a15efff4cc928a4be56be2e239eecbd9446eb6aacd8ac6e13a4fc1957636a9586c26b2f8b5cc92653bc148cc30d0e5f0c46351ccdb2972802af849b5d7d24785dfde1e881a907358c6e285f3593334362730de"}, - "99704c8b2a08ae9b8165a300ea05dbeae3b4c9a2096a6221aa4175bad43d53ec": {meshCAKey: "3081a40201010430f08b066f918684cb97e33449143327b651c206a8473c3d789c3091bb4c677afca0dc24d78df527523d345a75a9be4fc4a00706052b81040022a164036200041b84754f8963621c77ad7dba9a452869e96407e6646eb028ab11f46d07cac4341fb6762d500c96b6a480250e11f3ba45d664c88382d242be39632270f540dcf2f98cc66f46d0495aa713cfebb8123483e3e7667c3461f4cd65c2f8c84abbe384"}, - "f2e57529d3b92832eef960b75b2d299aadf1e373473bebf28cc105dae55c5f4e": {meshCAKey: "3081a4020101043095e15f05598731a2f1e9b54b3c3e6c914fbf532be808eaa65010fb9a6d5dc18de9f1678216c82d7113e4b9f93e74cc97a00706052b81040022a16403620004259d18d11b0fa7cb30ca190f5ba637f5c400346c95084d515d313ebe40cd699b08d0a3bf0f4b1494229e5ce5d2347be90c9af51c131f627b1789da925ff2221da3e0cee4e34460fcfc3748e976fc9f608b404e7bb52de456e8d79e4fec4f2042"}, - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855": {err: true}, - "": {err: true}, - } - - secretSeed, err := hex.DecodeString("ccebed634ddee7535cd593e1e200b19b780f3906d8782207fa09c59e87a07cb3") - req.NoError(err) - salt, err := hex.DecodeString("8c1b1225c5f6cb7eef6dbd8f77a1e1e149de031d6e3718e660a8b04c8e2b0037") - req.NoError(err) - - se, err := New(secretSeed, salt) - req.NoError(err) - - for transactionHashStr, want := range testCases { - t.Run(transactionHashStr, func(t *testing.T) { - require := require.New(t) - - var transactionHash [32]byte - transactionHashSlice, err := hex.DecodeString(transactionHashStr) - require.NoError(err) - copy(transactionHash[:], transactionHashSlice) - - meshCAKey, err := se.DeriveMeshCAKey(transactionHash) - - if want.err { - require.Error(err) - return - } - require.NoError(err) - - meshCAKeyDER, err := x509.MarshalECPrivateKey(meshCAKey) - require.NoError(err) - require.Equal(want.meshCAKey, hex.EncodeToString(meshCAKeyDER)) - }) - } -} diff --git a/docs/docs/deployment.md b/docs/docs/deployment.md index 4cd8187fd5..81eda5f01c 100644 --- a/docs/docs/deployment.md +++ b/docs/docs/deployment.md @@ -324,3 +324,11 @@ contrast recover -c "${coordinator}:1313" Now that the Coordinator is recovered, all workloads should pass initialization and enter the running state. You can now verify the Coordinator again, which should return the same manifest you set before. + +:::warning + +The recovery process invalidates the mesh CA certificate: +existing workloads won't be able to communicate with workloads newly spawned. +All workloads should be restarted after the recovery succeeded. + +::: diff --git a/e2e/openssl/openssl_test.go b/e2e/openssl/openssl_test.go index 0a05c88167..2060e1563a 100644 --- a/e2e/openssl/openssl_test.go +++ b/e2e/openssl/openssl_test.go @@ -201,15 +201,29 @@ func TestOpenSSL(t *testing.T) { require.NoError(c.Restart(ctx, kubeclient.Deployment{}, ct.Namespace, opensslFrontend)) require.NoError(c.WaitFor(ctx, kubeclient.Deployment{}, ct.Namespace, opensslFrontend)) - for _, cert := range []string{rootCAFile, meshCAFile} { - t.Run(cert, func(t *testing.T) { - stdout, stderr, err := c.ExecDeployment(ctx, ct.Namespace, opensslBackend, []string{"/bin/bash", "-c", opensslConnectCmd("openssl-frontend:443", cert)}) - if err != nil { - t.Logf("openssl with %q after recovery:\n%s", cert, stdout) - } - assert.NoError(t, err, "stderr: %q", stderr) - }) - } + t.Run("root CA is still accepted after coordinator recovery", func(t *testing.T) { + stdout, stderr, err := c.ExecDeployment(ctx, ct.Namespace, opensslBackend, []string{"/bin/bash", "-c", opensslConnectCmd("openssl-frontend:443", rootCAFile)}) + if err != nil { + t.Logf("openssl with %q after recovery:\n%s", rootCAFile, stdout) + } + assert.NoError(t, err, "stderr: %q", stderr) + }) + + t.Run("coordinator can't recover mesh CA key", func(t *testing.T) { + _, _, err := c.ExecDeployment(ctx, ct.Namespace, opensslBackend, []string{"/bin/bash", "-c", opensslConnectCmd("openssl-frontend:443", meshCAFile)}) + assert.Error(t, err) + }) + + require.NoError(c.Restart(ctx, kubeclient.Deployment{}, ct.Namespace, opensslBackend)) + require.NoError(c.WaitFor(ctx, kubeclient.Deployment{}, ct.Namespace, opensslBackend)) + + t.Run("mesh CA after coordinator recovery is accepted when workloads are restarted", func(t *testing.T) { + stdout, stderr, err := c.ExecDeployment(ctx, ct.Namespace, opensslBackend, []string{"/bin/bash", "-c", opensslConnectCmd("openssl-frontend:443", meshCAFile)}) + if err != nil { + t.Logf("openssl with %q after recovery:\n%s", meshCAFile, stdout) + } + assert.NoError(t, err, "stderr: %q", stderr) + }) }) }