Files
nats-server/server/jetstream_tpm_test.go
Colin Sullivan d1408c3fb7 cleanup tests
Signed-off-by: Colin Sullivan <[email protected]>
2024-04-09 10:39:04 -06:00

249 lines
5.7 KiB
Go

// Copyright 2024 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build windows
package server
import (
"fmt"
"os"
"strings"
"testing"
"time"
"github.com/nats-io/nats.go"
)
// The tests in this file are not complete, but are a start to test the TPM
// with default settings. For a complete test we would need to use a TPM
// configured with an owner password and a SRK password. This is not
// typically the default or setup in CI/CD environments.
var jsTPMConfigPassword = `
listen: 127.0.0.1:-1
jetstream: {
store_dir: %q
tpm {
keys_file: %q
encryption_password: "s3cr3t!"
}
}`
var jsTPMConfigBadPassword = `
listen: 127.0.0.1:-1
jetstream: {
store_dir: %q
tpm {
keys_file: %q
encryption_password: "garbage"
}
}`
var jsTPMConfigPasswordPcr = `
listen: 127.0.0.1:-1
jetstream: {
store_dir: %q
tpm {
keys_file: %q
encryption_password: "s3cr3t!"
pcr: %d
}
}`
var jsTPMConfigAllFields = `
listen: 127.0.0.1:-1
jetstream: {
store_dir: %q
tpm {
keys_file: %q
encryption_password: "s3cr3t!"
pcr: %d
srk_password: %q
cipher: "aes"
}
}`
var jsTPMConfigInvalidBothOptionsSet = `
listen: 127.0.0.1:-1
jetstream: {
store_dir: %q
encryption_key: "foo"
tpm {
keys_file: "bar.json"
encryption_password: "s3cr3t!"
}
}`
func getTPMFileName(t *testing.T) string {
return t.TempDir() + "/" + t.Name() + "/jskeys.json"
}
func checkSendMessage(t *testing.T, s *Server) {
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "tpm_test",
Subjects: []string{"tpm_test"},
})
require_NoError(t, err)
_, err = js.Publish("tpm_test", []byte("hello"))
require_NoError(t, err)
}
func checkReceiveMessage(t *testing.T, s *Server) {
// reconnect and get the message
nc, js := jsClientConnect(t, s)
defer nc.Close()
// get the message
sub, err := js.PullSubscribe("tpm_test", "cls")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
m := fetchMsgs(t, sub, 1, 5*time.Second)
if m == nil {
t.Fatalf("Did not receive the message")
}
if len(m) != 1 {
t.Fatalf("Expected 1 message, got %d", len(m))
}
}
func TestJetStreamTPMBasic(t *testing.T) {
fileName := getTPMFileName(t)
cf := createConfFile(t, []byte(fmt.Sprintf(jsTPMConfigPassword, t.TempDir(), fileName)))
s, _ := RunServerWithConfig(cf)
defer s.Shutdown()
// Note, actual encryption is tested elsewhere. This just tests that the key is generated.
key := s.opts.JetStreamKey
if !strings.HasPrefix(key, "SU") {
t.Fatalf("expected a TPM key to be generated. Key = %q", key)
}
if _, err := os.Stat(fileName); err != nil {
t.Fatalf("keys file was not created")
}
checkSendMessage(t, s)
// restart the server.
s.Shutdown()
s, _ = RunServerWithConfig(cf)
if s.Running() == false {
t.Fatalf("Server should be running")
}
defer s.Shutdown()
// double check we're encrypted with the same key.
if s.opts.JetStreamKey != key {
t.Fatalf("expected same key")
}
checkReceiveMessage(t, s)
}
// Warning: Running this test too frequently will considerably slow down
// these tests and eventually lock out the TPM.
//
// Depending on the environment this varies from 10 failures per day with
// a 24 hour lockout (Dell) to 32 failures per day with a 10
// minute healing time (decrementing one failure every 10 minutes).
func TestJetStreamTPMKeyBadPassword(t *testing.T) {
fileName := getTPMFileName(t)
cf := createConfFile(t, []byte(fmt.Sprintf(jsTPMConfigPassword,
t.TempDir(), fileName)))
s, _ := RunServerWithConfig(cf)
defer s.Shutdown()
checkSendMessage(t, s)
s.Shutdown()
// restart the server.
defer func() {
r := recover()
if r == nil {
t.Fatalf("Expected server panic, unexpected start.")
}
}()
cf2 := createConfFile(t, []byte(fmt.Sprintf(jsTPMConfigBadPassword,
t.TempDir(), fileName)))
s, _ = RunServerWithConfig(cf2)
time.Sleep(3 * time.Second)
if s.Running() {
s.Shutdown()
t.Fatalf("Server should NOT be running")
}
}
func TestJetStreamTPMKeyWithPCR(t *testing.T) {
cf := createConfFile(t, []byte(fmt.Sprintf(jsTPMConfigPasswordPcr,
t.TempDir(), getTPMFileName(t), 18)))
s, _ := RunServerWithConfig(cf)
defer s.Shutdown()
checkSendMessage(t, s)
s.Shutdown()
// restart the server
s, _ = RunServerWithConfig(cf)
if !s.Running() {
t.Fatalf("Server should be running")
}
}
func TestJetStreamTPMAll(t *testing.T) {
fileName := getTPMFileName(t)
// TODO: When the CI/CD environment supports updating the TPM,
// expand this test with the SRK password.
cf := createConfFile(t, []byte(fmt.Sprintf(jsTPMConfigAllFields,
getTPMFileName(t), fileName, 19, "")))
s, _ := RunServerWithConfig(cf)
defer s.Shutdown()
checkSendMessage(t, s)
s.Shutdown()
// restart the server.
s, _ = RunServerWithConfig(cf)
if s.Running() == false {
t.Fatalf("Server should be running")
}
defer s.Shutdown()
checkReceiveMessage(t, s)
}
func TestJetStreamInvalidConfig(t *testing.T) {
cf := createConfFile(t, []byte(fmt.Sprintf(jsTPMConfigInvalidBothOptionsSet,
getTPMFileName(t))))
defer func() {
r := recover()
if r == nil {
t.Fatalf("Expected server panic, unexpected start.")
}
}()
s, _ := RunServerWithConfig(cf)
if s.Running() == true {
s.Shutdown()
t.Fatalf("Server should NOT be running")
}
}