github.com/nats-io/nats-server/v2@v2.11.0-preview.2/server/certstore_windows_test.go (about) 1 // Copyright 2022-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 "net/url" 21 "os" 22 "os/exec" 23 "strings" 24 "testing" 25 "time" 26 27 "github.com/nats-io/nats.go" 28 ) 29 30 func runPowershellScript(scriptFile string, args []string) error { 31 _ = args 32 psExec, _ := exec.LookPath("powershell.exe") 33 execArgs := []string{psExec, "-command", fmt.Sprintf("& '%s'", scriptFile)} 34 35 cmdImport := &exec.Cmd{ 36 Path: psExec, 37 Args: execArgs, 38 Stdout: os.Stdout, 39 Stderr: os.Stderr, 40 } 41 return cmdImport.Run() 42 } 43 44 func runConfiguredLeaf(t *testing.T, hubPort int, certStore string, matchBy string, match string, caMatch string, expectedLeafCount int) { 45 46 // Fire up the leaf 47 u, err := url.Parse(fmt.Sprintf("nats://localhost:%d", hubPort)) 48 if err != nil { 49 t.Fatalf("Error parsing url: %v", err) 50 } 51 52 configStr := fmt.Sprintf(` 53 port: -1 54 leaf { 55 remotes [ 56 { 57 url: "%s" 58 tls { 59 cert_store: "%s" 60 cert_match_by: "%s" 61 cert_match: "%s" 62 ca_certs_match: %s 63 64 # Test settings that succeed should be equivalent to: 65 # cert_file: "../test/configs/certs/tlsauth/client.pem" 66 # key_file: "../test/configs/certs/tlsauth/client-key.pem" 67 # ca_file: "../test/configs/certs/tlsauth/ca.pem" 68 timeout: 5 69 } 70 } 71 ] 72 } 73 `, u.String(), certStore, matchBy, match, caMatch) 74 75 leafConfig := createConfFile(t, []byte(configStr)) 76 defer removeFile(t, leafConfig) 77 leafServer, _ := RunServerWithConfig(leafConfig) 78 defer leafServer.Shutdown() 79 80 // After client verify, hub will match by SAN email, SAN dns, and Subject (in that order) 81 // Our test client specifies Subject only so we should match on that... 82 83 // A little settle time 84 time.Sleep(1 * time.Second) 85 checkLeafNodeConnectedCount(t, leafServer, expectedLeafCount) 86 } 87 88 // TestLeafTLSWindowsCertStore tests the topology of two NATS Servers connected as leaf and hub with authentication of 89 // leaf to hub via mTLS with leaf's certificate and signing key provisioned in the Windows certificate store. 90 func TestLeafTLSWindowsCertStore(t *testing.T) { 91 92 // Client Identity (client.pem) 93 // Issuer: O = NATS CA, OU = NATS.io, CN = localhost 94 // Subject: OU = NATS.io, CN = example.com 95 96 // Make sure windows cert store is reset to avoid conflict with other tests 97 err := runPowershellScript("../test/configs/certs/tlsauth/certstore/delete-cert-from-store.ps1", nil) 98 if err != nil { 99 t.Fatalf("expected powershell cert delete to succeed: %s", err.Error()) 100 } 101 102 // Provision Windows cert store with client cert and secret 103 err = runPowershellScript("../test/configs/certs/tlsauth/certstore/import-p12-client.ps1", nil) 104 if err != nil { 105 t.Fatalf("expected powershell provision to succeed: %s", err.Error()) 106 } 107 108 err = runPowershellScript("../test/configs/certs/tlsauth/certstore/import-p12-ca.ps1", nil) 109 if err != nil { 110 t.Fatalf("expected powershell provision CA to succeed: %s", err.Error()) 111 } 112 113 // Fire up the hub 114 hubConfig := createConfFile(t, []byte(` 115 port: -1 116 leaf { 117 listen: "127.0.0.1:-1" 118 tls { 119 ca_file: "../test/configs/certs/tlsauth/ca.pem" 120 cert_file: "../test/configs/certs/tlsauth/server.pem" 121 key_file: "../test/configs/certs/tlsauth/server-key.pem" 122 timeout: 5 123 verify_and_map: true 124 } 125 } 126 127 accounts: { 128 AcctA: { 129 users: [ {user: "OU = NATS.io, CN = example.com"} ] 130 }, 131 AcctB: { 132 users: [ {user: UserB1} ] 133 }, 134 SYS: { 135 users: [ {user: System} ] 136 } 137 } 138 system_account: "SYS" 139 `)) 140 defer removeFile(t, hubConfig) 141 hubServer, hubOptions := RunServerWithConfig(hubConfig) 142 defer hubServer.Shutdown() 143 144 testCases := []struct { 145 certStore string 146 certMatchBy string 147 certMatch string 148 caCertsMatch string 149 expectedLeafCount int 150 }{ 151 // Test subject and issuer 152 {"WindowsCurrentUser", "Subject", "example.com", "\"NATS CA\"", 1}, 153 {"WindowsCurrentUser", "Issuer", "NATS CA", "\"NATS CA\"", 1}, 154 {"WindowsCurrentUser", "Issuer", "Frodo Baggins, Inc.", "\"NATS CA\"", 0}, 155 // Test CAs, NATS CA is valid, others are missing 156 {"WindowsCurrentUser", "Subject", "example.com", "[\"NATS CA\"]", 1}, 157 {"WindowsCurrentUser", "Subject", "example.com", "[\"GlobalSign\"]", 0}, 158 {"WindowsCurrentUser", "Subject", "example.com", "[\"Missing NATS Cert\"]", 0}, 159 {"WindowsCurrentUser", "Subject", "example.com", "[\"NATS CA\", \"Missing NATS Cert1\"]", 1}, 160 {"WindowsCurrentUser", "Subject", "example.com", "[\"Missing Cert2\",\"NATS CA\"]", 1}, 161 {"WindowsCurrentUser", "Subject", "example.com", "[\"Missing, Cert3\",\"Missing NATS Cert4\"]", 0}, 162 } 163 for _, tc := range testCases { 164 testName := fmt.Sprintf("%s by %s match %s", tc.certStore, tc.certMatchBy, tc.certMatch) 165 t.Run(fmt.Sprintf(testName, tc.certStore, tc.certMatchBy, tc.certMatch, tc.caCertsMatch), func(t *testing.T) { 166 defer func() { 167 if r := recover(); r != nil { 168 if tc.expectedLeafCount != 0 { 169 t.Fatalf("did not expect panic: %s", testName) 170 } else { 171 if !strings.Contains(fmt.Sprintf("%v", r), "Error processing configuration file") { 172 t.Fatalf("did not expect unknown panic: %s", testName) 173 } 174 } 175 } 176 }() 177 runConfiguredLeaf(t, hubOptions.LeafNode.Port, 178 tc.certStore, tc.certMatchBy, tc.certMatch, 179 tc.caCertsMatch, tc.expectedLeafCount) 180 }) 181 } 182 } 183 184 // TestServerTLSWindowsCertStore tests the topology of a NATS server requiring TLS and gettings it own server 185 // cert identiy (as used when accepting NATS client connections and negotiating TLS) from Windows certificate store. 186 func TestServerTLSWindowsCertStore(t *testing.T) { 187 188 // Server Identity (server.pem) 189 // Issuer: O = NATS CA, OU = NATS.io, CN = localhost 190 // Subject: OU = NATS.io Operators, CN = localhost 191 192 // Make sure windows cert store is reset to avoid conflict with other tests 193 err := runPowershellScript("../test/configs/certs/tlsauth/certstore/delete-cert-from-store.ps1", nil) 194 if err != nil { 195 t.Fatalf("expected powershell cert delete to succeed: %s", err.Error()) 196 } 197 198 // Provision Windows cert store with server cert and secret 199 err = runPowershellScript("../test/configs/certs/tlsauth/certstore/import-p12-server.ps1", nil) 200 if err != nil { 201 t.Fatalf("expected powershell provision to succeed: %s", err.Error()) 202 } 203 204 err = runPowershellScript("../test/configs/certs/tlsauth/certstore/import-p12-ca.ps1", nil) 205 if err != nil { 206 t.Fatalf("expected powershell provision CA to succeed: %s", err.Error()) 207 } 208 209 // Fire up the server 210 srvConfig := createConfFile(t, []byte(` 211 listen: "localhost:-1" 212 tls { 213 cert_store: "WindowsCurrentUser" 214 cert_match_by: "Subject" 215 cert_match: "NATS.io Operators" 216 ca_certs_match: ["NATS CA"] 217 timeout: 5 218 } 219 `)) 220 defer removeFile(t, srvConfig) 221 srvServer, _ := RunServerWithConfig(srvConfig) 222 if srvServer == nil { 223 t.Fatalf("expected to be able start server with cert store configuration") 224 } 225 defer srvServer.Shutdown() 226 227 testCases := []struct { 228 clientCA string 229 expect bool 230 }{ 231 {"../test/configs/certs/tlsauth/ca.pem", true}, 232 {"../test/configs/certs/tlsauth/client.pem", false}, 233 } 234 for _, tc := range testCases { 235 t.Run(fmt.Sprintf("Client CA: %s", tc.clientCA), func(t *testing.T) { 236 nc, _ := nats.Connect(srvServer.clientConnectURLs[0], nats.RootCAs(tc.clientCA)) 237 err := nc.Publish("foo", []byte("hello TLS server-authenticated server")) 238 if (err != nil) == tc.expect { 239 t.Fatalf("expected publish result %v to TLS authenticated server", tc.expect) 240 } 241 nc.Close() 242 243 for i := 0; i < 5; i++ { 244 nc, _ = nats.Connect(srvServer.clientConnectURLs[0], nats.RootCAs(tc.clientCA)) 245 err = nc.Publish("foo", []byte("hello TLS server-authenticated server")) 246 if (err != nil) == tc.expect { 247 t.Fatalf("expected repeated connection result %v to TLS authenticated server", tc.expect) 248 } 249 nc.Close() 250 } 251 }) 252 } 253 }