github.com/nats-io/nats-server/v2@v2.11.0-preview.2/server/jetstream_tpm_test.go (about)

     1  // Copyright 2024 The NATS Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  //go:build windows
    15  
    16  package server
    17  
    18  import (
    19  	"fmt"
    20  	"os"
    21  	"strings"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/nats-io/nats.go"
    26  )
    27  
    28  // The tests in this file are not complete, but are a start to test the TPM
    29  // with default settings. For a complete test we would need to use a TPM
    30  // configured with an owner password and a SRK password. This is not
    31  // typically the default or setup in CI/CD environments.
    32  
    33  var jsTPMConfigPassword = `
    34  	listen: 127.0.0.1:-1
    35  	jetstream: {
    36  		store_dir: %q
    37  		tpm {
    38  			keys_file: %q
    39  			encryption_password: "s3cr3t!"
    40  		}
    41  	}`
    42  
    43  var jsTPMConfigBadPassword = `
    44      listen: 127.0.0.1:-1
    45  	jetstream: {
    46  		store_dir: %q
    47  		tpm {
    48  			keys_file: %q
    49  			encryption_password: "garbage"
    50  		}
    51  	}`
    52  
    53  var jsTPMConfigPasswordPcr = `
    54  	listen: 127.0.0.1:-1
    55  	jetstream: {
    56  		store_dir: %q
    57  		tpm {
    58  			keys_file: %q
    59  			encryption_password: "s3cr3t!"
    60  			pcr: %d
    61  		}
    62  	}`
    63  
    64  var jsTPMConfigAllFields = `
    65  	listen: 127.0.0.1:-1
    66  	jetstream: {
    67  		store_dir: %q
    68  		tpm {
    69  			keys_file: %q
    70  			encryption_password: "s3cr3t!"
    71  			pcr: %d
    72  			srk_password: %q
    73  			cipher: "aes"
    74  		}
    75  	}`
    76  
    77  var jsTPMConfigInvalidBothOptionsSet = `
    78  	listen: 127.0.0.1:-1
    79  	jetstream: {
    80  		store_dir: %q
    81  		encryption_key: "foo"
    82  		tpm {
    83  			keys_file: "bar.json"
    84  			encryption_password: "s3cr3t!"
    85  		}
    86  	}`
    87  
    88  func getTPMFileName(t *testing.T) string {
    89  	return t.TempDir() + "/" + t.Name() + "/jskeys.json"
    90  }
    91  
    92  func checkSendMessage(t *testing.T, s *Server) {
    93  	nc, js := jsClientConnect(t, s)
    94  	defer nc.Close()
    95  
    96  	_, err := js.AddStream(&nats.StreamConfig{
    97  		Name:     "tpm_test",
    98  		Subjects: []string{"tpm_test"},
    99  	})
   100  	require_NoError(t, err)
   101  
   102  	_, err = js.Publish("tpm_test", []byte("hello"))
   103  	require_NoError(t, err)
   104  }
   105  
   106  func checkReceiveMessage(t *testing.T, s *Server) {
   107  	// reconnect and get the message
   108  	nc, js := jsClientConnect(t, s)
   109  	defer nc.Close()
   110  
   111  	// get the message
   112  	sub, err := js.PullSubscribe("tpm_test", "cls")
   113  	if err != nil {
   114  		t.Fatalf("Unexpected error: %v", err)
   115  	}
   116  	m := fetchMsgs(t, sub, 1, 5*time.Second)
   117  	if m == nil {
   118  		t.Fatalf("Did not receive the message")
   119  	}
   120  	if len(m) != 1 {
   121  		t.Fatalf("Expected 1 message, got %d", len(m))
   122  	}
   123  }
   124  
   125  func TestJetStreamTPMBasic(t *testing.T) {
   126  	fileName := getTPMFileName(t)
   127  	cf := createConfFile(t, []byte(fmt.Sprintf(jsTPMConfigPassword, t.TempDir(), fileName)))
   128  
   129  	s, _ := RunServerWithConfig(cf)
   130  	defer s.Shutdown()
   131  
   132  	// Note, actual encryption is tested elsewhere. This just tests that the key is generated.
   133  	key := s.opts.JetStreamKey
   134  	if !strings.HasPrefix(key, "SU") {
   135  		t.Fatalf("expected a TPM key to be generated. Key = %q", key)
   136  	}
   137  
   138  	if _, err := os.Stat(fileName); err != nil {
   139  		t.Fatalf("keys file was not created")
   140  	}
   141  
   142  	checkSendMessage(t, s)
   143  
   144  	// restart the server.
   145  	s.Shutdown()
   146  	s, _ = RunServerWithConfig(cf)
   147  	if s.Running() == false {
   148  		t.Fatalf("Server should be running")
   149  	}
   150  	defer s.Shutdown()
   151  
   152  	// double check we're encrypted with the same key.
   153  	if s.opts.JetStreamKey != key {
   154  		t.Fatalf("expected same key")
   155  	}
   156  	checkReceiveMessage(t, s)
   157  }
   158  
   159  // Warning: Running this test too frequently will considerably slow down
   160  // these tests and eventually lock out the TPM.
   161  //
   162  // Depending on the environment this varies from 10 failures per day with
   163  // a 24 hour lockout (Dell) to 32 failures per day with a 10
   164  // minute healing time (decrementing one failure every 10 minutes).
   165  func TestJetStreamTPMKeyBadPassword(t *testing.T) {
   166  	fileName := getTPMFileName(t)
   167  	cf := createConfFile(t, []byte(fmt.Sprintf(jsTPMConfigPassword,
   168  		t.TempDir(), fileName)))
   169  
   170  	s, _ := RunServerWithConfig(cf)
   171  	defer s.Shutdown()
   172  
   173  	checkSendMessage(t, s)
   174  	s.Shutdown()
   175  
   176  	// restart the server.
   177  	defer func() {
   178  		r := recover()
   179  		if r == nil {
   180  			t.Fatalf("Expected server panic, unexpected start.")
   181  		}
   182  	}()
   183  	cf2 := createConfFile(t, []byte(fmt.Sprintf(jsTPMConfigBadPassword,
   184  		t.TempDir(), fileName)))
   185  	s, _ = RunServerWithConfig(cf2)
   186  	time.Sleep(3 * time.Second)
   187  	if s.Running() {
   188  		s.Shutdown()
   189  		t.Fatalf("Server should NOT be running")
   190  	}
   191  }
   192  
   193  func TestJetStreamTPMKeyWithPCR(t *testing.T) {
   194  	cf := createConfFile(t, []byte(fmt.Sprintf(jsTPMConfigPasswordPcr,
   195  		t.TempDir(), getTPMFileName(t), 18)))
   196  
   197  	s, _ := RunServerWithConfig(cf)
   198  	defer s.Shutdown()
   199  
   200  	checkSendMessage(t, s)
   201  	s.Shutdown()
   202  
   203  	// restart the server
   204  	s, _ = RunServerWithConfig(cf)
   205  	if !s.Running() {
   206  		t.Fatalf("Server should be running")
   207  	}
   208  }
   209  
   210  func TestJetStreamTPMAll(t *testing.T) {
   211  	fileName := getTPMFileName(t)
   212  
   213  	// TODO: When the CI/CD environment supports updating the TPM,
   214  	// expand this test with the SRK password.
   215  	cf := createConfFile(t, []byte(fmt.Sprintf(jsTPMConfigAllFields,
   216  		getTPMFileName(t), fileName, 19, "")))
   217  
   218  	s, _ := RunServerWithConfig(cf)
   219  	defer s.Shutdown()
   220  
   221  	checkSendMessage(t, s)
   222  	s.Shutdown()
   223  
   224  	// restart the server.
   225  	s, _ = RunServerWithConfig(cf)
   226  	if s.Running() == false {
   227  		t.Fatalf("Server should be running")
   228  	}
   229  	defer s.Shutdown()
   230  	checkReceiveMessage(t, s)
   231  }
   232  
   233  func TestJetStreamInvalidConfig(t *testing.T) {
   234  	cf := createConfFile(t, []byte(fmt.Sprintf(jsTPMConfigInvalidBothOptionsSet,
   235  		getTPMFileName(t))))
   236  
   237  	defer func() {
   238  		r := recover()
   239  		if r == nil {
   240  			t.Fatalf("Expected server panic, unexpected start.")
   241  		}
   242  	}()
   243  	s, _ := RunServerWithConfig(cf)
   244  	if s.Running() == true {
   245  		s.Shutdown()
   246  		t.Fatalf("Server should NOT be running")
   247  	}
   248  }