Files
nats-server/server/certstore_windows_test.go
2023-01-25 21:22:20 -08:00

231 lines
6.9 KiB
Go

// Copyright 2022-2023 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build windows
package server
import (
"fmt"
"net/url"
"os"
"os/exec"
"strings"
"testing"
"time"
"github.com/nats-io/nats.go"
)
func runPowershellScript(scriptFile string, args []string) error {
_ = args
psExec, _ := exec.LookPath("powershell.exe")
execArgs := []string{psExec, "-command", fmt.Sprintf("& '%s'", scriptFile)}
cmdImport := &exec.Cmd{
Path: psExec,
Args: execArgs,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
return cmdImport.Run()
}
func runConfiguredLeaf(t *testing.T, hubPort int, certStore string, matchBy string, match string, expectedLeafCount int) {
// Fire up the leaf
u, err := url.Parse(fmt.Sprintf("nats://localhost:%d", hubPort))
if err != nil {
t.Fatalf("Error parsing url: %v", err)
}
configStr := fmt.Sprintf(`
port: -1
leaf {
remotes [
{
url: "%s"
tls {
cert_store: "%s"
cert_match_by: "%s"
cert_match: "%s"
# Above should be equivalent to:
# cert_file: "../test/configs/certs/tlsauth/client.pem"
# key_file: "../test/configs/certs/tlsauth/client-key.pem"
ca_file: "../test/configs/certs/tlsauth/ca.pem"
timeout: 5
}
}
]
}
`, u.String(), certStore, matchBy, match)
leafConfig := createConfFile(t, []byte(configStr))
defer removeFile(t, leafConfig)
leafServer, _ := RunServerWithConfig(leafConfig)
defer leafServer.Shutdown()
// After client verify, hub will match by SAN email, SAN dns, and Subject (in that order)
// Our test client specifies Subject only so we should match on that...
// A little settle time
time.Sleep(1 * time.Second)
checkLeafNodeConnectedCount(t, leafServer, expectedLeafCount)
}
// TestLeafTLSWindowsCertStore tests the topology of two NATS Servers connected as leaf and hub with authentication of
// leaf to hub via mTLS with leaf's certificate and signing key provisioned in the Windows certificate store.
func TestLeafTLSWindowsCertStore(t *testing.T) {
// Client Identity (client.pem)
// Issuer: O = Synadia Communications Inc., OU = NATS.io, CN = localhost
// Subject: OU = NATS.io, CN = example.com
// Make sure windows cert store is reset to avoid conflict with other tests
err := runPowershellScript("../test/configs/certs/tlsauth/certstore/delete-cert-from-store.ps1", nil)
if err != nil {
t.Fatalf("expected powershell cert delete to succeed: %s", err.Error())
}
// Provision Windows cert store with client cert and secret
err = runPowershellScript("../test/configs/certs/tlsauth/certstore/import-p12-client.ps1", nil)
if err != nil {
t.Fatalf("expected powershell provision to succeed: %s", err.Error())
}
// Fire up the hub
hubConfig := createConfFile(t, []byte(`
port: -1
leaf {
listen: "127.0.0.1:-1"
tls {
ca_file: "../test/configs/certs/tlsauth/ca.pem"
cert_file: "../test/configs/certs/tlsauth/server.pem"
key_file: "../test/configs/certs/tlsauth/server-key.pem"
timeout: 5
verify_and_map: true
}
}
accounts: {
AcctA: {
users: [ {user: "OU = NATS.io, CN = example.com"} ]
},
AcctB: {
users: [ {user: UserB1} ]
},
SYS: {
users: [ {user: System} ]
}
}
system_account: "SYS"
`))
defer removeFile(t, hubConfig)
hubServer, hubOptions := RunServerWithConfig(hubConfig)
defer hubServer.Shutdown()
testCases := []struct {
certStore string
certMatchBy string
certMatch string
expectedLeafCount int
}{
{"WindowsCurrentUser", "Subject", "example.com", 1},
{"WindowsCurrentUser", "Issuer", "Synadia Communications Inc.", 1},
{"WindowsCurrentUser", "Issuer", "Frodo Baggins, Inc.", 0},
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("%s by %s match %s", tc.certStore, tc.certMatchBy, tc.certMatch), func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
if tc.expectedLeafCount != 0 {
t.Fatalf("did not expect panic")
} else {
if !strings.Contains(fmt.Sprintf("%v", r), "Error processing configuration file") {
t.Fatalf("did not expect unknown panic cause")
}
}
}
}()
runConfiguredLeaf(t, hubOptions.LeafNode.Port, tc.certStore, tc.certMatchBy, tc.certMatch, tc.expectedLeafCount)
})
}
}
// TestServerTLSWindowsCertStore tests the topology of a NATS server requiring TLS and gettings it own server
// cert identiy (as used when accepting NATS client connections and negotiating TLS) from Windows certificate store.
func TestServerTLSWindowsCertStore(t *testing.T) {
// Server Identity (server.pem)
// Issuer: O = Synadia Communications Inc., OU = NATS.io, CN = localhost
// Subject: OU = NATS.io Operators, CN = localhost
// Make sure windows cert store is reset to avoid conflict with other tests
err := runPowershellScript("../test/configs/certs/tlsauth/certstore/delete-cert-from-store.ps1", nil)
if err != nil {
t.Fatalf("expected powershell cert delete to succeed: %s", err.Error())
}
// Provision Windows cert store with server cert and secret
err = runPowershellScript("../test/configs/certs/tlsauth/certstore/import-p12-server.ps1", nil)
if err != nil {
t.Fatalf("expected powershell provision to succeed: %s", err.Error())
}
// Fire up the server
srvConfig := createConfFile(t, []byte(`
listen: "localhost:-1"
tls {
cert_store: "WindowsCurrentUser"
cert_match_by: "Subject"
cert_match: "NATS.io Operators"
timeout: 5
}
`))
defer removeFile(t, srvConfig)
srvServer, _ := RunServerWithConfig(srvConfig)
if srvServer == nil {
t.Fatalf("expected to be able start server with cert store configuration")
}
defer srvServer.Shutdown()
testCases := []struct {
clientCA string
expect bool
}{
{"../test/configs/certs/tlsauth/ca.pem", true},
{"../test/configs/certs/tlsauth/client.pem", false},
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("Client CA: %s", tc.clientCA), func(t *testing.T) {
nc, _ := nats.Connect(srvServer.clientConnectURLs[0], nats.RootCAs(tc.clientCA))
err := nc.Publish("foo", []byte("hello TLS server-authenticated server"))
if (err != nil) == tc.expect {
t.Fatalf("expected publish result %v to TLS authenticated server", tc.expect)
}
nc.Close()
for i := 0; i < 5; i++ {
nc, _ = nats.Connect(srvServer.clientConnectURLs[0], nats.RootCAs(tc.clientCA))
err = nc.Publish("foo", []byte("hello TLS server-authenticated server"))
if (err != nil) == tc.expect {
t.Fatalf("expected repeated connection result %v to TLS authenticated server", tc.expect)
}
nc.Close()
}
})
}
}