Skip to content

Commit

Permalink
Parse decorated and chained files
Browse files Browse the repository at this point in the history
Signed-off-by: Derek Collison <derek@nats.io>
  • Loading branch information
derekcollison committed Nov 24, 2018
1 parent 9527d54 commit d1705ee
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 11 deletions.
59 changes: 49 additions & 10 deletions nats.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"math/rand"
"net"
"net/url"
"regexp"
"runtime"
"strconv"
"strings"
Expand Down Expand Up @@ -534,7 +535,6 @@ func Secure(tls ...*tls.Config) Option {
return func(o *Options) error {
o.Secure = true
// Use of variadic just simplifies testing scenarios. We only take the first one.
// fixme(DLC) - Could panic if more than one. Could also do TLS option.
if len(tls) > 1 {
return ErrMultipleTLSConfigs
}
Expand Down Expand Up @@ -741,12 +741,18 @@ func TokenHandler(cb AuthTokenHandler) Option {

// UserCredentials is a convenience function that takes a filename
// for a user's JWT and a filename for the user's private Nkey seed.
func UserCredentials(userJWTFile, seedFile string) Option {
func UserCredentials(userOrChainedFile string, seedFiles ...string) Option {
userCB := func() (string, error) {
return userFromFile(userJWTFile)
return userFromFile(userOrChainedFile)
}
var keyFile string
if len(seedFiles) > 0 {
keyFile = seedFiles[0]
} else {
keyFile = userOrChainedFile
}
sigCB := func(nonce []byte) ([]byte, error) {
return sigHandler(nonce, seedFile)
return sigHandler(nonce, keyFile)
}
return UserJWT(userCB, sigCB)
}
Expand Down Expand Up @@ -3698,12 +3704,39 @@ func NkeyOptionFromSeed(seedFile string) (Option, error) {
return Nkey(string(pub), sigCB), nil
}

// This is a regex to match decorated jwts in keys/seeds.
// .e.g.
// -----BEGIN NATS USER JWT-----
// eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5...
// ------END NATS USER JWT------
//
// ************************* IMPORTANT *************************
// NKEY Seed printed below can be used sign and prove identity.
// NKEYs are sensitive and should be treated as secrets.
//
// -----BEGIN USER NKEY SEED-----
// SUAIO3FHUX5PNV2LQIIP7TZ3N4L7TX3W53MQGEIVYFIGA635OZCKEYHFLM
// ------END USER NKEY SEED------

var nscDecoratedRe = regexp.MustCompile(`\s*(?:(?:[-]{3,}[^\n]*[-]{3,}\n)(.+)(?:\n\s*[-]{3,}[^\n]*[-]{3,}\n))`)

func userFromFile(userFile string) (string, error) {
contents, err := ioutil.ReadFile(userFile)
if err != nil {
return _EMPTY_, fmt.Errorf("nats: %v", err)
}
return string(contents), nil
defer wipeSlice(contents)

items := nscDecoratedRe.FindAllSubmatch(contents, -1)
if len(items) == 0 {
return string(contents), nil
}
// First result should be the user JWT.
// We copy here so that if the file contained a seed file too we wipe appropriately.
raw := items[0][1]
tmp := make([]byte, len(raw))
copy(tmp, raw)
return string(tmp), nil
}

func nkeyPairFromSeedFile(seedFile string) (nkeys.KeyPair, error) {
Expand All @@ -3714,13 +3747,19 @@ func nkeyPairFromSeedFile(seedFile string) (nkeys.KeyPair, error) {
}
defer wipeSlice(contents)

lines := bytes.Split(contents, []byte("\n"))
for _, line := range lines {
if bytes.HasPrefix(bytes.TrimSpace(line), []byte("SU")) {
seed = line
break
items := nscDecoratedRe.FindAllSubmatch(contents, -1)
if len(items) > 1 {
seed = items[1][1]
} else {
lines := bytes.Split(contents, []byte("\n"))
for _, line := range lines {
if bytes.HasPrefix(bytes.TrimSpace(line), []byte("SU")) {
seed = line
break
}
}
}

if seed == nil {
return nil, fmt.Errorf("nats: No nkey user seed found in %q", seedFile)
}
Expand Down
34 changes: 33 additions & 1 deletion nats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1288,6 +1288,20 @@ var (
aJWT = "eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJqdGkiOiJJSTdKSU5JUENVWTZEU1JDSUpZT1daR0k0UlRGNUdCNjVZUUtSNE9RVlBCQlpBNFhCQlhRIiwiaWF0IjoxNTQyMzMxNzgwLCJpc3MiOiJPRDJXMkk0TVZSQTVUR1pMWjJBRzZaSEdWTDNPVEtGV1FKRklYNFROQkVSMjNFNlA0NlMzNDVZWSIsIm5hbWUiOiJmb28iLCJzdWIiOiJBQ1E1VkpLS1dEM0s1QzdSVkFFMjJNT1hESkFNTEdFTUZJM1NDR1JWUlpKSlFUTU9QTjMzQlhVSyIsInR5cGUiOiJhY2NvdW50IiwibmF0cyI6e319.Dg2A1NCJWvXhBQZN9QNHAq1KqsFIKxzLhYvD5yH0DYZPC0gXtdhLkwJ5uiooki6YvzR8UNQZ9XuWgDpNpwryDg"

uJWT = "eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJqdGkiOiJRVzRWWktISEJCUkFaSkFWREg3UjVDSk1RQ1pHWDZJM1FJWEJSMkdWSjRHSVRMRlJRMlpBIiwiaWF0IjoxNTQyMzg1NjMxLCJpc3MiOiJBQ1E1VkpLS1dEM0s1QzdSVkFFMjJNT1hESkFNTEdFTUZJM1NDR1JWUlpKSlFUTU9QTjMzQlhVSyIsIm5hbWUiOiJkZXJlayIsInN1YiI6IlVEMkZMTEdGRVJRVlFRM1NCS09OTkcyUU1JTVRaUUtLTFRVM0FWRzVJM0VRRUZIQlBHUEUyWFFTIiwidHlwZSI6InVzZXIiLCJuYXRzIjp7InB1YiI6e30sInN1YiI6e319fQ.6PmFNn3x0AH3V05oemO28riP63+QTvk9g/Qtt6wBcXJqgW6YSVxk6An1MjvTn1tH7S9tJ0zOIGp7/OLjP1tbBQ"

chained = `
-----BEGIN NATS USER JWT-----
eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJqdGkiOiJRVzRWWktISEJCUkFaSkFWREg3UjVDSk1RQ1pHWDZJM1FJWEJSMkdWSjRHSVRMRlJRMlpBIiwiaWF0IjoxNTQyMzg1NjMxLCJpc3MiOiJBQ1E1VkpLS1dEM0s1QzdSVkFFMjJNT1hESkFNTEdFTUZJM1NDR1JWUlpKSlFUTU9QTjMzQlhVSyIsIm5hbWUiOiJkZXJlayIsInN1YiI6IlVEMkZMTEdGRVJRVlFRM1NCS09OTkcyUU1JTVRaUUtLTFRVM0FWRzVJM0VRRUZIQlBHUEUyWFFTIiwidHlwZSI6InVzZXIiLCJuYXRzIjp7InB1YiI6e30sInN1YiI6e319fQ.6PmFNn3x0AH3V05oemO28riP63+QTvk9g/Qtt6wBcXJqgW6YSVxk6An1MjvTn1tH7S9tJ0zOIGp7/OLjP1tbBQ
------END NATS USER JWT------
************************* IMPORTANT *************************
NKEY Seed printed below can be used sign and prove identity.
NKEYs are sensitive and should be treated as secrets.
-----BEGIN USER NKEY SEED-----
SUAIO3FHUX5PNV2LQIIP7TZ3N4L7TX3W53MQGEIVYFIGA635OZCKEYHFLM
------END USER NKEY SEED------
`
)

func runTrustServer() *server.Server {
Expand Down Expand Up @@ -1346,7 +1360,7 @@ func TestBasicUserJWTAuth(t *testing.T) {
nc.Close()
}

func TestUserCredentials(t *testing.T) {
func TestUserCredentialsTwoFiles(t *testing.T) {
if server.VERSION[0] == '1' {
t.Skip()
}
Expand All @@ -1366,6 +1380,24 @@ func TestUserCredentials(t *testing.T) {
nc.Close()
}

func TestUserCredentialsChainedFile(t *testing.T) {
if server.VERSION[0] == '1' {
t.Skip()
}
ts := runTrustServer()
defer ts.Shutdown()

chainedFile := createTmpFile(t, []byte(chained))
defer os.Remove(chainedFile)

url := fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT)
nc, err := Connect(url, UserCredentials(chainedFile))
if err != nil {
t.Fatalf("Expected to connect, got %v", err)
}
nc.Close()
}

func TestNkeyAuth(t *testing.T) {
if server.VERSION[0] == '1' {
t.Skip()
Expand Down

0 comments on commit d1705ee

Please sign in to comment.