Repository: nats-io/nats-streaming-server (go)

Issue:
STAN authentication problem when connecting with NKeys
Hello!

We're setting up a NATS and STAN cluster. The STAN cluster needs to connect to our NATS cluster, obviously. But now I'm having trouble to authenticate, when connecting the STAN cluster to the NATS cluster.

We are using NKeys for authentication, but I can't seem to figure out how to configure STAN correctly.

The NATS auth configuration looks like this:
```
authorization: {

  ...

  STAN: {
    publish: ">"
    subscribe: ">"
  }

  users: [
    ...
    //STAN-SERVER
    {nkey: UDNBXF3RNSPSZZIACU42AQDV7XF6ZLKUSADUGEH3F2HQW3E2PEWN6JIK, permissions: $STAN}
   ...
  ]
}
```

And the STAN config:
```
streaming {
      ...
      credentials: /etc/stan-creds/stan.creds
      ...
    }
```

The file stan.creds contains the NKey seed, corresponding to the user public key for above. I tried various formats, for example
```
SUAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```

OR
```
-----BEGIN USER NKEY SEED-----
SUAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
------END USER NKEY SEED------
```

In STAN I get the following (obvious) error:
```
[1] 2020/10/05 15:50:51.725194 [FTL] STREAM: Failed to start: nats: Authorization Violation
```
And in NATS (just the empty quotes after Nkey):
```
[6] 2020/10/05 15:52:24.788799 [ERR] 10.244.0.99:50458 - cid:145 - authentication error - Nkey "" 
```

Is STAN not supporting NKey authentication or am I doing something wrong?

Thanks in advance

Pull request description:
[ADDED] NKey support
You can now configure the streaming server by providing a file
to the NKey seed. This will be used when creating the NATS connections
and when NKey authentication is required.
The file is read to get the seed in order to sign the nonce from
the NATS server. The seed is wiped out from memory after this
is done.

Resolves #1095

Signed-off-by: Ivan Kozlovic <ivan@synadia.com>

Patch:
diff --git a/server/conf.go b/server/conf.go
index 6f9c78d..8a4c29d 100644
--- a/server/conf.go
+++ b/server/conf.go
@@ -194,6 +194,11 @@ func ProcessConfigFile(configFile string, opts *Options) error {
 				return err
 			}
 			opts.Token = v.(string)
+		case "nkey_seed_file":
+			if err := checkType(k, reflect.String, v); err != nil {
+				return err
+			}
+			opts.NKeySeedFile = v.(string)
 		}
 	}
 	return nil
diff --git a/server/server.go b/server/server.go
index b92fca0..d578acb 100644
--- a/server/server.go
+++ b/server/server.go
@@ -1322,9 +1322,10 @@ type Options struct {
 	IOSleepTime        int64         // Duration (in micro-seconds) the server waits for more message to fill up a batch.
 	NATSServerURL      string        // URL for external NATS Server to connect to. If empty, NATS Server is embedded.
 	NATSCredentials    string        // Credentials file for connecting to external NATS Server.
-	Username           string        // Username to use if not provided from command line
-	Password           string        // Password to use if not provided from command line
-	Token              string        // Authentication token to use if not provided from command line
+	Username           string        // Username to use if not provided from command line.
+	Password           string        // Password to use if not provided from command line.
+	Token              string        // Authentication token to use if not provided from command line.
+	NKeySeedFile       string        // File name containing NKey private key.
 	ClientHBInterval   time.Duration // Interval at which server sends heartbeat to a client.
 	ClientHBTimeout    time.Duration // How long server waits for a heartbeat response.
 	ClientHBFailCount  int           // Number of failed heartbeats before server closes client connection.
@@ -1513,6 +1514,14 @@ func (s *StanServer) createNatsClientConn(name string) (*nats.Conn, error) {
 		ncOpts.Token = s.opts.Token
 	}
 
+	if s.opts.NKeySeedFile != "" {
+		nkey, err := nats.NkeyOptionFromSeed(s.opts.NKeySeedFile)
+		if err != nil {
+			return nil, err
+		}
+		nkey(&ncOpts)
+	}
+
 	ncOpts.Name = fmt.Sprintf("_NSS-%s-%s", s.opts.ID, name)
 
 	if err = nats.ErrorHandler(s.stanErrorHandler)(&ncOpts); err != nil {


Test patch:
diff --git a/server/conf_test.go b/server/conf_test.go
index 399cc5f..8636191 100644
--- a/server/conf_test.go
+++ b/server/conf_test.go
@@ -90,6 +90,9 @@ func TestParseConfig(t *testing.T) {
 	if opts.Password != "password" {
 		t.Fatalf("Expected Password to be %q, got %q", "password", opts.Password)
 	}
+	if opts.NKeySeedFile != "seedfile" {
+		t.Fatalf("Expected NKeySeedFile to be %q, got %q", "seedfile", opts.NKeySeedFile)
+	}
 	if opts.Token != "token" {
 		t.Fatalf("Expected Token to be %q, got %q", "token", opts.Token)
 	}
@@ -509,6 +512,7 @@ func TestParseWrongTypes(t *testing.T) {
 	expectFailureFor(t, "username: 123", wrongTypeErr)
 	expectFailureFor(t, "password: 123", wrongTypeErr)
 	expectFailureFor(t, "token: 123", wrongTypeErr)
+	expectFailureFor(t, "nkey_seed_file: 123", wrongTypeErr)
 }
 
 func expectFailureFor(t *testing.T, content, errorMatch string) {
diff --git a/server/server_test.go b/server/server_test.go
index 6950dc0..a37c946 100644
--- a/server/server_test.go
+++ b/server/server_test.go
@@ -1555,3 +1555,56 @@ func TestServerAuthInConfig(t *testing.T) {
 	}
 	nc.Close()
 }
+
+func TestServerAuthNKey(t *testing.T) {
+	nkeyfile := createConfFile(t, []byte(`
+-----BEGIN USER NKEY SEED-----
+SUACSSL3UAHUDXKFSNVUZRF5UHPMWZ6BFDTJ7M6USDXIEDNPPQYYYCU3VY
+------END USER NKEY SEED------
+	`))
+	defer os.Remove(nkeyfile)
+
+	conf := createConfFile(t, []byte(fmt.Sprintf(`
+		authorization {
+			users [
+				# Streaming Server
+				{nkey: UDXU4RCSJNZOIQHZNWXHXORDPRTGNJAHAHFRGZNEEJCPQTT2M7NLCNF4}
+				# Some other user
+				{nkey: UCNGL4W5QX66CFX6A6DCBVDH5VOHMI7B2UZZU7TXAUQQSI2JPHULCKBR}
+			]
+		}
+		streaming {
+			nkey_seed_file: '%s'
+		}
+	`, nkeyfile)))
+	defer os.Remove(conf)
+
+	fs := flag.NewFlagSet("test", flag.ContinueOnError)
+	noPrint := func() {}
+	sopts, nopts, err := ConfigureOptions(fs, []string{"-c", conf}, noPrint, noPrint, noPrint)
+	if err != nil {
+		t.Fatalf("Error on configure: %v", err)
+	}
+	s := runServerWithOpts(t, sopts, nopts)
+	s.Shutdown()
+	s = nil
+
+	// Check that it fails with wrong seed
+	changeCurrentConfigContentWithNewContent(t, nkeyfile, []byte(`
+-----BEGIN USER NKEY SEED-----
+SUAKYRHVIOREXV7EUZTBHUHL7NUMHPMAS7QMDU3GTIUWEI5LDNOXD43IZY
+------END USER NKEY SEED------
+	`))
+	fs = flag.NewFlagSet("test", flag.ContinueOnError)
+	sopts, nopts, err = ConfigureOptions(fs, []string{"-c", conf}, noPrint, noPrint, noPrint)
+	if err != nil {
+		t.Fatalf("Error on configure: %v", err)
+	}
+	s, err = RunServerWithOpts(sopts, nopts)
+	if err == nil {
+		if s != nil {
+			s.Shutdown()
+		}
+		t.Fatal("Expected failure to start")
+	}
+}
diff --git a/test/configs/test_parse.conf b/test/configs/test_parse.conf
index 7f3152e..5efce6b 100644
--- a/test/configs/test_parse.conf
+++ b/test/configs/test_parse.conf
@@ -20,6 +20,7 @@ streaming: {
   username: "user"
   password: "password"
   token: "token"
+  nkey_seed_file: "seedfile"
 
   store_limits: {
       max_channels: 11