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  }