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 }