get.pme.sh/pnats@v0.0.0-20240304004023-26bb5a137ed0/server/certstore_windows_test.go (about) 1 // Copyright 2022-2023 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, 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 63 # Above should be equivalent to: 64 # cert_file: "../test/configs/certs/tlsauth/client.pem" 65 # key_file: "../test/configs/certs/tlsauth/client-key.pem" 66 67 ca_file: "../test/configs/certs/tlsauth/ca.pem" 68 timeout: 5 69 } 70 } 71 ] 72 } 73 `, u.String(), certStore, matchBy, match) 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 = Synadia Communications Inc., 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 // Fire up the hub 109 hubConfig := createConfFile(t, []byte(` 110 port: -1 111 leaf { 112 listen: "127.0.0.1:-1" 113 tls { 114 ca_file: "../test/configs/certs/tlsauth/ca.pem" 115 cert_file: "../test/configs/certs/tlsauth/server.pem" 116 key_file: "../test/configs/certs/tlsauth/server-key.pem" 117 timeout: 5 118 verify_and_map: true 119 } 120 } 121 122 accounts: { 123 AcctA: { 124 users: [ {user: "OU = NATS.io, CN = example.com"} ] 125 }, 126 AcctB: { 127 users: [ {user: UserB1} ] 128 }, 129 SYS: { 130 users: [ {user: System} ] 131 } 132 } 133 system_account: "SYS" 134 `)) 135 defer removeFile(t, hubConfig) 136 hubServer, hubOptions := RunServerWithConfig(hubConfig) 137 defer hubServer.Shutdown() 138 139 testCases := []struct { 140 certStore string 141 certMatchBy string 142 certMatch string 143 expectedLeafCount int 144 }{ 145 {"WindowsCurrentUser", "Subject", "example.com", 1}, 146 {"WindowsCurrentUser", "Issuer", "Synadia Communications Inc.", 1}, 147 {"WindowsCurrentUser", "Issuer", "Frodo Baggins, Inc.", 0}, 148 } 149 for _, tc := range testCases { 150 t.Run(fmt.Sprintf("%s by %s match %s", tc.certStore, tc.certMatchBy, tc.certMatch), func(t *testing.T) { 151 defer func() { 152 if r := recover(); r != nil { 153 if tc.expectedLeafCount != 0 { 154 t.Fatalf("did not expect panic") 155 } else { 156 if !strings.Contains(fmt.Sprintf("%v", r), "Error processing configuration file") { 157 t.Fatalf("did not expect unknown panic cause") 158 } 159 } 160 } 161 }() 162 runConfiguredLeaf(t, hubOptions.LeafNode.Port, tc.certStore, tc.certMatchBy, tc.certMatch, tc.expectedLeafCount) 163 }) 164 } 165 } 166 167 // TestServerTLSWindowsCertStore tests the topology of a NATS server requiring TLS and gettings it own server 168 // cert identiy (as used when accepting NATS client connections and negotiating TLS) from Windows certificate store. 169 func TestServerTLSWindowsCertStore(t *testing.T) { 170 171 // Server Identity (server.pem) 172 // Issuer: O = Synadia Communications Inc., OU = NATS.io, CN = localhost 173 // Subject: OU = NATS.io Operators, CN = localhost 174 175 // Make sure windows cert store is reset to avoid conflict with other tests 176 err := runPowershellScript("../test/configs/certs/tlsauth/certstore/delete-cert-from-store.ps1", nil) 177 if err != nil { 178 t.Fatalf("expected powershell cert delete to succeed: %s", err.Error()) 179 } 180 181 // Provision Windows cert store with server cert and secret 182 err = runPowershellScript("../test/configs/certs/tlsauth/certstore/import-p12-server.ps1", nil) 183 if err != nil { 184 t.Fatalf("expected powershell provision to succeed: %s", err.Error()) 185 } 186 187 // Fire up the server 188 srvConfig := createConfFile(t, []byte(` 189 listen: "localhost:-1" 190 tls { 191 cert_store: "WindowsCurrentUser" 192 cert_match_by: "Subject" 193 cert_match: "NATS.io Operators" 194 timeout: 5 195 } 196 `)) 197 defer removeFile(t, srvConfig) 198 srvServer, _ := RunServerWithConfig(srvConfig) 199 if srvServer == nil { 200 t.Fatalf("expected to be able start server with cert store configuration") 201 } 202 defer srvServer.Shutdown() 203 204 testCases := []struct { 205 clientCA string 206 expect bool 207 }{ 208 {"../test/configs/certs/tlsauth/ca.pem", true}, 209 {"../test/configs/certs/tlsauth/client.pem", false}, 210 } 211 for _, tc := range testCases { 212 t.Run(fmt.Sprintf("Client CA: %s", tc.clientCA), func(t *testing.T) { 213 nc, _ := nats.Connect(srvServer.clientConnectURLs[0], nats.RootCAs(tc.clientCA)) 214 err := nc.Publish("foo", []byte("hello TLS server-authenticated server")) 215 if (err != nil) == tc.expect { 216 t.Fatalf("expected publish result %v to TLS authenticated server", tc.expect) 217 } 218 nc.Close() 219 220 for i := 0; i < 5; i++ { 221 nc, _ = nats.Connect(srvServer.clientConnectURLs[0], nats.RootCAs(tc.clientCA)) 222 err = nc.Publish("foo", []byte("hello TLS server-authenticated server")) 223 if (err != nil) == tc.expect { 224 t.Fatalf("expected repeated connection result %v to TLS authenticated server", tc.expect) 225 } 226 nc.Close() 227 } 228 }) 229 } 230 }