Release 2.1.2

Signed-off-by: Ivan Kozlovic <ivan@synadia.com>
This commit is contained in:
Ivan Kozlovic
2019-11-18 15:17:13 -07:00
parent da5ed6eaa6
commit 4a03b6382e
25 changed files with 408 additions and 239 deletions

View File

@@ -1,12 +0,0 @@
FROM golang:1.12.9
MAINTAINER Ivan Kozlovic <ivan@synadia.com>
COPY . /go/src/github.com/nats-io/nats-server
WORKDIR /go/src/github.com/nats-io/nats-server
RUN CGO_ENABLED=0 GO111MODULE=off go install -v -a -tags netgo -installsuffix netgo -ldflags "-s -w -X github.com/nats-io/nats-server/server.gitCommit=`git rev-parse --short HEAD`"
EXPOSE 4222 8222
ENTRYPOINT ["nats-server"]
CMD ["--help"]

View File

@@ -1,15 +0,0 @@
FROM golang:1.12.9
MAINTAINER Ivan Kozlovic <ivan@synadia.com>
COPY . /go/src/github.com/nats-io/nats-server
WORKDIR /go/src/github.com/nats-io/nats-server
RUN CGO_ENABLED=0 GO111MODULE=off GOOS=linux GOARCH=amd64 go build -v -a -tags netgo -installsuffix netgo -ldflags "-s -w -X github.com/nats-io/nats-server/server.gitCommit=`git rev-parse --short HEAD`" -o pkg/linux-amd64/nats-server
RUN CGO_ENABLED=0 GO111MODULE=off GOOS=linux GOARCH=arm GOARM=6 go build -v -a -tags netgo -installsuffix netgo -ldflags "-s -w -X github.com/nats-io/nats-server/server.gitCommit=`git rev-parse --short HEAD`" -o pkg/linux-arm6/nats-server
RUN CGO_ENABLED=0 GO111MODULE=off GOOS=linux GOARCH=arm GOARM=7 go build -v -a -tags netgo -installsuffix netgo -ldflags "-s -w -X github.com/nats-io/nats-server/server.gitCommit=`git rev-parse --short HEAD`" -o pkg/linux-arm7/nats-server
RUN CGO_ENABLED=0 GO111MODULE=off GOOS=linux GOARCH=arm64 go build -v -a -tags netgo -installsuffix netgo -ldflags "-s -w -X github.com/nats-io/nats-server/server.gitCommit=`git rev-parse --short HEAD`" -o pkg/linux-arm64/nats-server
RUN CGO_ENABLED=0 GO111MODULE=off GOOS=windows GOARCH=amd64 go build -v -a -tags netgo -installsuffix netgo -ldflags "-s -w -X github.com/nats-io/nats-server/server.gitCommit=`git rev-parse --short HEAD`" -o pkg/win-amd64/nats-server.exe
ENTRYPOINT ["go"]
CMD ["version"]

View File

@@ -29,8 +29,8 @@ If you are interested in contributing to NATS, read about our...
[Fossa-Image]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fnats-io%2Fgnatsd.svg?type=shield
[Build-Status-Url]: https://travis-ci.org/nats-io/nats-server
[Build-Status-Image]: https://travis-ci.org/nats-io/nats-server.svg?branch=master
[Release-Url]: https://github.com/nats-io/nats-server/releases/tag/v2.1.0
[Release-image]: https://img.shields.io/badge/release-v2.1.0-1eb0fc.svg
[Release-Url]: https://github.com/nats-io/nats-server/releases/tag/v2.1.2
[Release-image]: https://img.shields.io/badge/release-v2.1.2-1eb0fc.svg
[Coverage-Url]: https://coveralls.io/r/nats-io/nats-server?branch=master
[Coverage-image]: https://coveralls.io/repos/github/nats-io/nats-server/badge.svg?branch=master
[ReportCard-Url]: https://goreportcard.com/report/nats-io/nats-server

6
go.mod
View File

@@ -2,9 +2,9 @@ module github.com/nats-io/nats-server/v2
require (
github.com/golang/protobuf v1.3.2 // indirect
github.com/nats-io/jwt v0.3.0
github.com/nats-io/nats.go v1.8.1
github.com/nats-io/nkeys v0.1.0
github.com/nats-io/jwt v0.3.2
github.com/nats-io/nats.go v1.9.1
github.com/nats-io/nkeys v0.1.3
github.com/nats-io/nuid v1.0.1
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e

12
go.sum
View File

@@ -1,15 +1,15 @@
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/nats-io/jwt v0.3.0 h1:xdnzwFETV++jNc4W1mw//qFyJGb2ABOombmZJQS4+Qo=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/nats.go v1.8.1 h1:6lF/f1/NN6kzUDBz6pyvQDEXO39jqXcWRLu/tKjtOUQ=
github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
github.com/nats-io/nkeys v0.1.0 h1:qMd4+pRHgdr1nAClu+2h/2a5F2TmKcCzjCDazVgRoX4=
github.com/nats-io/jwt v0.3.2 h1:+RB5hMpXUUA2dfxuhBTEkMOrYmM+gKIZYS1KjSostMI=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats.go v1.9.1 h1:ik3HbLhZ0YABLto7iX80pZLPw/6dx3T+++MZJwLnMrQ=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3 h1:6JrEfig+HzTH85yxzhSVbjHRJv9cn0p6n3IngIcM5/k=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=

View File

@@ -40,7 +40,7 @@ var (
const (
// VERSION is the current version for the server.
VERSION = "2.1.1-RC6"
VERSION = "2.1.2"
// PROTO is the currently supported protocol.
// 0 was the original

View File

@@ -1,8 +1,8 @@
language: go
sudo: false
go:
- 1.13.x
- 1.12.x
- 1.11.x
install:
- go get -t ./...
@@ -18,4 +18,5 @@ before_script:
- staticcheck ./...
script:
- if [[ "$TRAVIS_GO_VERSION" == 1.10.* ]] ; then ./scripts/cov.sh TRAVIS; else go test -v -race ./...; fi
- go test -v -race ./...
- if [[ "$TRAVIS_GO_VERSION" =~ 1.12 ]]; then ./scripts/cov.sh TRAVIS; fi

View File

@@ -17,6 +17,7 @@ package jwt
import (
"errors"
"sort"
"time"
"github.com/nats-io/nkeys"
@@ -127,7 +128,8 @@ func (a *AccountClaims) Encode(pair nkeys.KeyPair) (string, error) {
if !nkeys.IsValidPublicAccountKey(a.Subject) {
return "", errors.New("expected subject to be account public key")
}
sort.Sort(a.Exports)
sort.Sort(a.Imports)
a.ClaimsData.Type = AccountClaim
return a.ClaimsData.Encode(pair, a)
}

View File

@@ -23,6 +23,7 @@ func formatJwt(kind string, jwtString string) ([]byte, error) {
templ := `-----BEGIN NATS %s JWT-----
%s
------END NATS %s JWT------
`
w := bytes.NewBuffer(nil)
kind = strings.ToUpper(kind)
@@ -60,6 +61,7 @@ func DecorateSeed(seed []byte) ([]byte, error) {
header := `************************* IMPORTANT *************************
NKEY Seed printed below can be used to sign and prove identity.
NKEYs are sensitive and should be treated as secrets.
-----BEGIN %s NKEY SEED-----
`
_, err := fmt.Fprintf(w, header, kind)
@@ -70,6 +72,7 @@ NKEYs are sensitive and should be treated as secrets.
footer := `
------END %s NKEY SEED------
*************************************************************
`
_, err = fmt.Fprintf(w, footer, kind)

View File

@@ -158,7 +158,7 @@ func (e *Export) IsRevoked(pubKey string) bool {
return e.Revocations.IsRevoked(pubKey, time.Now())
}
// Exports is an array of exports
// Exports is a slice of exports
type Exports []*Export
// Add appends exports to the list
@@ -222,3 +222,15 @@ func (e *Exports) HasExportContainingSubject(subject Subject) bool {
}
return false
}
func (e Exports) Len() int {
return len(e)
}
func (e Exports) Swap(i, j int) {
e[i], e[j] = e[j], e[i]
}
func (e Exports) Less(i, j int) bool {
return e[i].Subject < e[j].Subject
}

View File

@@ -1,3 +1,3 @@
module github.com/nats-io/jwt
require github.com/nats-io/nkeys v0.1.0
require github.com/nats-io/nkeys v0.1.3

View File

@@ -1,5 +1,5 @@
github.com/nats-io/nkeys v0.1.0 h1:qMd4+pRHgdr1nAClu+2h/2a5F2TmKcCzjCDazVgRoX4=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3 h1:6JrEfig+HzTH85yxzhSVbjHRJv9cn0p6n3IngIcM5/k=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=

View File

@@ -23,7 +23,7 @@ import (
const (
// Version is semantic version.
Version = "0.3.0"
Version = "0.3.2"
// TokenTypeJwt is the JWT token type supported JWT tokens
// encoded and decoded by this library

View File

@@ -63,10 +63,11 @@ func (i *Import) Validate(actPubKey string, vr *ValidationResults) {
i.Subject.Validate(vr)
if i.IsService() {
if i.Subject.HasWildCards() {
vr.AddWarning("services cannot have wildcard subject: %q", i.Subject)
}
if i.IsService() && i.Subject.HasWildCards() {
vr.AddError("services cannot have wildcard subject: %q", i.Subject)
}
if i.IsStream() && i.To.HasWildCards() {
vr.AddError("streams cannot have wildcard to subject: %q", i.Subject)
}
var act *ActivationClaims
@@ -136,3 +137,15 @@ func (i *Imports) Validate(acctPubKey string, vr *ValidationResults) {
func (i *Imports) Add(a ...*Import) {
*i = append(*i, a...)
}
func (i Imports) Len() int {
return len(i)
}
func (i Imports) Swap(j, k int) {
i[j], i[k] = i[k], i[j]
}
func (i Imports) Less(j, k int) bool {
return i[j].Subject < i[k].Subject
}

View File

@@ -16,8 +16,8 @@ install:
before_script:
- $(exit $(go fmt ./... | wc -l))
- go vet ./...
- misspell -error -locale US .
- find . -type f -name "*.go" | xargs misspell -error -locale US
- staticcheck ./...
script:
- go test -i -race ./...
- if [[ "$TRAVIS_GO_VERSION" =~ 1.12 ]]; then ./scripts/cov.sh TRAVIS; else go test -race ./...; fi
- if [[ "$TRAVIS_GO_VERSION" =~ 1.12 ]]; then ./scripts/cov.sh TRAVIS; else go test -race -v -p=1 ./... --failfast; fi

View File

@@ -15,6 +15,20 @@ go get github.com/nats-io/nats.go/
go get github.com/nats-io/nats-server
```
When using or transitioning to Go modules support:
```bash
# Go client latest or explicit version
go get github.com/nats-io/nats.go/@latest
go get github.com/nats-io/nats.go/@v1.9.1
# For latest NATS Server, add /v2 at the end
go get github.com/nats-io/nats-server/v2
# NATS Server v1 is installed otherwise
# go get github.com/nats-io/nats-server
```
## Basic Usage
```go
@@ -33,7 +47,7 @@ nc.Subscribe("foo", func(m *nats.Msg) {
// Responding to a request message
nc.Subscribe("request", func(m *nats.Msg) {
m.Respond([]byte("answer is 42")
m.Respond([]byte("answer is 42"))
})
// Simple Sync Subscriber
@@ -55,7 +69,7 @@ sub.Drain()
msg, err := nc.Request("help", []byte("help me"), 10*time.Millisecond)
// Replies
nc.Subscribe("help", func(m *Msg) {
nc.Subscribe("help", func(m *nats.Msg) {
nc.Publish(m.Reply, []byte("I can help!"))
})
@@ -102,12 +116,12 @@ c.Publish("hello", me)
// Unsubscribe
sub, err := c.Subscribe("foo", nil)
...
// ...
sub.Unsubscribe()
// Requests
var response string
err := c.Request("help", "help me", &response, 10*time.Millisecond)
err = c.Request("help", "help me", &response, 10*time.Millisecond)
if err != nil {
fmt.Printf("Request failed: %v\n", err)
}
@@ -127,7 +141,7 @@ This requires server with version >= 2.0.0
NATS servers have a new security and authentication mechanism to authenticate with user credentials and Nkeys.
The simplest form is to use the helper method UserCredentials(credsFilepath).
```go
nc, err := nats.Connect(url, UserCredentials("user.creds"))
nc, err := nats.Connect(url, nats.UserCredentials("user.creds"))
```
The helper methods creates two callback handlers to present the user JWT and sign the nonce challenge from the server.
@@ -136,12 +150,12 @@ The helper will load and wipe and erase memory it uses for each connect or recon
The helper also can take two entries, one for the JWT and one for the NKey seed file.
```go
nc, err := nats.Connect(url, UserCredentials("user.jwt", "user.nk"))
nc, err := nats.Connect(url, nats.UserCredentials("user.jwt", "user.nk"))
```
You can also set the callback handlers directly and manage challenge signing directly.
```go
nc, err := nats.Connect(url, UserJWT(jwtCB, sigCB))
nc, err := nats.Connect(url, nats.UserJWT(jwtCB, sigCB))
```
Bare Nkeys are also supported. The nkey seed should be in a read only file, e.g. seed.txt
@@ -160,7 +174,7 @@ opt, err := nats.NkeyOptionFromSeed("seed.txt")
nc, err := nats.Connect(serverUrl, opt)
// Direct
nc, err := nats.Connect(serverUrl, Nkey(pubNkey, sigCB))
nc, err := nats.Connect(serverUrl, nats.Nkey(pubNkey, sigCB))
```
## TLS

View File

@@ -43,29 +43,7 @@ func (nc *Conn) RequestWithContext(ctx context.Context, subj string, data []byte
return nc.oldRequestWithContext(ctx, subj, data)
}
// Do setup for the new style.
if nc.respMap == nil {
nc.initNewResp()
}
// Create literal Inbox and map to a chan msg.
mch := make(chan *Msg, RequestChanLen)
respInbox := nc.newRespInbox()
token := respToken(respInbox)
nc.respMap[token] = mch
createSub := nc.respMux == nil
ginbox := nc.respSub
nc.mu.Unlock()
if createSub {
// Make sure scoped subscription is setup only once.
var err error
nc.respSetup.Do(func() { err = nc.createRespMux(ginbox) })
if err != nil {
return nil, err
}
}
err := nc.PublishRequest(subj, respInbox, data)
mch, token, err := nc.createNewRequestAndSend(subj, data)
if err != nil {
return nil, err
}
@@ -140,7 +118,7 @@ func (s *Subscription) NextMsgWithContext(ctx context.Context) (*Msg, error) {
select {
case msg, ok = <-mch:
if !ok {
return nil, ErrConnectionClosed
return nil, s.getNextMsgErr()
}
if err := s.processNextMsgDelivered(msg); err != nil {
return nil, err
@@ -153,7 +131,7 @@ func (s *Subscription) NextMsgWithContext(ctx context.Context) (*Msg, error) {
select {
case msg, ok = <-mch:
if !ok {
return nil, ErrConnectionClosed
return nil, s.getNextMsgErr()
}
if err := s.processNextMsgDelivered(msg); err != nil {
return nil, err

View File

@@ -33,7 +33,7 @@ type Encoder interface {
var encMap map[string]Encoder
var encLock sync.Mutex
// Indexe names into the Registered Encoders.
// Indexed names into the Registered Encoders.
const (
JSON_ENCODER = "json"
GOB_ENCODER = "gob"
@@ -109,7 +109,7 @@ func (c *EncodedConn) PublishRequest(subject, reply string, v interface{}) error
// Request will create an Inbox and perform a Request() call
// with the Inbox reply for the data v. A response will be
// decoded into the vPtrResponse.
// decoded into the vPtr Response.
func (c *EncodedConn) Request(subject string, v interface{}, vPtr interface{}, timeout time.Duration) error {
b, err := c.Enc.Encode(subject, v)
if err != nil {

View File

@@ -1,6 +1,7 @@
module github.com/nats-io/nats.go
require (
github.com/nats-io/nkeys v0.0.2
github.com/nats-io/jwt v0.3.0
github.com/nats-io/nkeys v0.1.0
github.com/nats-io/nuid v1.0.1
)

View File

@@ -1,6 +1,13 @@
github.com/nats-io/nkeys v0.0.2 h1:+qM7QpgXnvDDixitZtQUBDY9w/s9mu1ghS+JIbsrx6M=
github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
github.com/nats-io/jwt v0.3.0 h1:xdnzwFETV++jNc4W1mw//qFyJGb2ABOombmZJQS4+Qo=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/nkeys v0.1.0 h1:qMd4+pRHgdr1nAClu+2h/2a5F2TmKcCzjCDazVgRoX4=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -28,7 +28,8 @@ import (
"math/rand"
"net"
"net/url"
"regexp"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
@@ -36,6 +37,7 @@ import (
"sync/atomic"
"time"
"github.com/nats-io/jwt"
"github.com/nats-io/nats.go/util"
"github.com/nats-io/nkeys"
"github.com/nats-io/nuid"
@@ -43,7 +45,7 @@ import (
// Default Constants
const (
Version = "1.8.1"
Version = "1.9.1"
DefaultURL = "nats://127.0.0.1:4222"
DefaultPort = 4222
DefaultMaxReconnect = 60
@@ -67,6 +69,9 @@ const (
// AUTHORIZATION_ERR is for when nats server user authorization has failed.
AUTHORIZATION_ERR = "authorization violation"
// AUTHENTICATION_EXPIRED_ERR is for when nats server user authorization has expired.
AUTHENTICATION_EXPIRED_ERR = "user authentication expired"
)
// Errors
@@ -80,10 +85,12 @@ var (
ErrBadSubscription = errors.New("nats: invalid subscription")
ErrTypeSubscription = errors.New("nats: invalid subscription type")
ErrBadSubject = errors.New("nats: invalid subject")
ErrBadQueueName = errors.New("nats: invalid queue name")
ErrSlowConsumer = errors.New("nats: slow consumer, messages dropped")
ErrTimeout = errors.New("nats: timeout")
ErrBadTimeout = errors.New("nats: timeout invalid")
ErrAuthorization = errors.New("nats: authorization violation")
ErrAuthExpired = errors.New("nats: authentication expired")
ErrNoServers = errors.New("nats: no servers available for connection")
ErrJsonParse = errors.New("nats: connect message, json parse error")
ErrChanArg = errors.New("nats: argument needs to be a channel type")
@@ -166,7 +173,8 @@ type UserJWTHandler func() (string, error)
// SignatureHandler is used to sign a nonce from the server while
// authenticating with nkeys. The user should sign the nonce and
// return the base64 encoded signature.
// return the raw signature. The client will base64 encode this to
// send to the server.
type SignatureHandler func([]byte) ([]byte, error)
// AuthTokenHandler is used to generate a new token.
@@ -339,6 +347,11 @@ type Options struct {
// UseOldRequestStyle forces the old method of Requests that utilize
// a new Inbox and a new Subscription for each request.
UseOldRequestStyle bool
// NoCallbacksAfterClientClose allows preventing the invocation of
// callbacks after Close() is called. Client won't receive notifications
// when Close is invoked by user code. Default is to invoke the callbacks.
NoCallbacksAfterClientClose bool
}
const (
@@ -363,6 +376,7 @@ const (
// A Conn represents a bare connection to a nats-server.
// It can send and receive []byte payloads.
// The connection is safe to use in multiple Go routines concurrently.
type Conn struct {
// Keep all members for which we use atomic at the beginning of the
// struct and make sure they are all 64bits (or use padding if necessary).
@@ -394,13 +408,15 @@ type Conn struct {
ps *parseState
ptmr *time.Timer
pout int
ar bool // abort reconnect
// New style response handler
respSub string // The wildcard subject
respScanf string // The scanf template to extract mux token
respMux *Subscription // A single response subscription
respMap map[string]chan *Msg // Request map for the response msg channels
respSetup sync.Once // Ensures response subscription occurs once
respRand *rand.Rand // Used for generating suffix.
respRand *rand.Rand // Used for generating suffix
}
// A Subscription represents interest in a given subject.
@@ -475,6 +491,7 @@ type srv struct {
didConnect bool
reconnects int
lastAttempt time.Time
lastErr error
isImplicit bool
tlsName string
}
@@ -872,6 +889,16 @@ func UseOldRequestStyle() Option {
}
}
// NoCallbacksAfterClientClose is an Option to disable callbacks when user code
// calls Close(). If close is initiated by any other condition, callbacks
// if any will be invoked.
func NoCallbacksAfterClientClose() Option {
return func(o *Options) error {
o.NoCallbacksAfterClientClose = true
return nil
}
}
// Handler processing
// SetDisconnectHandler will set the disconnect event handler.
@@ -1445,6 +1472,7 @@ func (nc *Conn) connect() error {
if err == nil {
nc.srvPool[i].didConnect = true
nc.srvPool[i].reconnects = 0
nc.current.lastErr = nil
returnedErr = nil
break
} else {
@@ -1614,7 +1642,6 @@ func normalizeErr(line string) string {
// applicable. Will wait for a flush to return from the server for error
// processing.
func (nc *Conn) sendConnect() error {
// Construct the CONNECT protocol string
cProto, err := nc.connectProto()
if err != nil {
@@ -1668,6 +1695,17 @@ func (nc *Conn) sendConnect() error {
if strings.HasPrefix(proto, _ERR_OP_) {
// Remove -ERR, trim spaces and quotes, and convert to lower case.
proto = normalizeErr(proto)
// Check if this is an auth error
if authErr := checkAuthError(strings.ToLower(proto)); authErr != nil {
// This will schedule an async error if we are in reconnect,
// and keep track of the auth error for the current server.
// If we have got the same error twice, this sets nc.ar to true to
// indicate that the reconnect should be aborted (will be checked
// in doReconnect()).
nc.processAuthError(authErr)
}
return errors.New("nats: " + proto)
}
@@ -1841,6 +1879,11 @@ func (nc *Conn) doReconnect(err error) {
// Process connect logic
if nc.err = nc.processConnectInit(); nc.err != nil {
// Check if we should abort reconnect. If so, break out
// of the loop and connection will be closed.
if nc.ar {
break
}
nc.status = RECONNECTING
// Reset the buffered writer to the pending buffer
// (was set to a buffered writer on nc.conn in createConn)
@@ -1848,6 +1891,10 @@ func (nc *Conn) doReconnect(err error) {
continue
}
// Clear possible lastErr under the connection lock after
// a successful processConnectInit().
nc.current.lastErr = nil
// Clear out server stats for the server we connected to..
cur.didConnect = true
cur.reconnects = 0
@@ -1883,6 +1930,7 @@ func (nc *Conn) doReconnect(err error) {
if nc.Opts.ReconnectedCB != nil {
nc.ach.push(func() { nc.Opts.ReconnectedCB(nc) })
}
// Release lock here, we will return below.
nc.mu.Unlock()
@@ -1897,7 +1945,7 @@ func (nc *Conn) doReconnect(err error) {
nc.err = ErrNoServers
}
nc.mu.Unlock()
nc.Close()
nc.close(CLOSED, true, nil)
}
// processOpErr handles errors from reading or parsing the protocol.
@@ -1932,7 +1980,7 @@ func (nc *Conn) processOpErr(err error) {
nc.status = DISCONNECTED
nc.err = err
nc.mu.Unlock()
nc.Close()
nc.close(CLOSED, true, nil)
}
// dispatch is responsible for calling any async callbacks
@@ -2132,8 +2180,8 @@ func (nc *Conn) processMsg(data []byte) {
nc.subsMu.RLock()
// Stats
nc.InMsgs++
nc.InBytes += uint64(len(data))
atomic.AddUint64(&nc.InMsgs, 1)
atomic.AddUint64(&nc.InBytes, uint64(len(data)))
sub := nc.subs[nc.ps.ma.sid]
if sub == nil {
@@ -2239,15 +2287,24 @@ func (nc *Conn) processPermissionsViolation(err string) {
nc.mu.Unlock()
}
// processAuthorizationViolation is called when the server signals a user
// authorization violation.
func (nc *Conn) processAuthorizationViolation(err string) {
nc.mu.Lock()
nc.err = ErrAuthorization
if nc.Opts.AsyncErrorCB != nil {
nc.ach.push(func() { nc.Opts.AsyncErrorCB(nc, nil, ErrAuthorization) })
// processAuthError generally processing for auth errors. We want to do retries
// unless we get the same error again. This allows us for instance to swap credentials
// and have the app reconnect, but if nothing is changing we should bail.
// This function will return true if the connection should be closed, false otherwise.
// Connection lock is held on entry
func (nc *Conn) processAuthError(err error) bool {
nc.err = err
if !nc.initc && nc.Opts.AsyncErrorCB != nil {
nc.ach.push(func() { nc.Opts.AsyncErrorCB(nc, nil, err) })
}
nc.mu.Unlock()
// We should give up if we tried twice on this server and got the
// same error.
if nc.current.lastErr == err {
nc.ar = true
} else {
nc.current.lastErr = err
}
return nc.ar
}
// flusher is a separate Go routine that will process flush requests for the write
@@ -2417,6 +2474,18 @@ func (nc *Conn) LastError() error {
return err
}
// Check if the given error string is an auth error, and if so returns
// the corresponding ErrXXX error, nil otherwise
func checkAuthError(e string) error {
if strings.HasPrefix(e, AUTHORIZATION_ERR) {
return ErrAuthorization
}
if strings.HasPrefix(e, AUTHENTICATION_EXPIRED_ERR) {
return ErrAuthExpired
}
return nil
}
// processErr processes any error messages from the server and
// sets the connection's lastError.
func (nc *Conn) processErr(ie string) {
@@ -2425,18 +2494,25 @@ func (nc *Conn) processErr(ie string) {
// convert to lower case.
e := strings.ToLower(ne)
close := false
// FIXME(dlc) - process Slow Consumer signals special.
if e == STALE_CONNECTION {
nc.processOpErr(ErrStaleConnection)
} else if strings.HasPrefix(e, PERMISSIONS_ERR) {
nc.processPermissionsViolation(ne)
} else if strings.HasPrefix(e, AUTHORIZATION_ERR) {
nc.processAuthorizationViolation(ne)
} else if authErr := checkAuthError(e); authErr != nil {
nc.mu.Lock()
close = nc.processAuthError(authErr)
nc.mu.Unlock()
} else {
close = true
nc.mu.Lock()
nc.err = errors.New("nats: " + ne)
nc.mu.Unlock()
nc.Close()
}
if close {
nc.close(CLOSED, true, nil)
}
}
@@ -2572,21 +2648,32 @@ func (nc *Conn) publish(subj, reply string, data []byte) error {
// the appropriate channel based on the last token and place
// the message on the channel if possible.
func (nc *Conn) respHandler(m *Msg) {
rt := respToken(m.Subject)
nc.mu.Lock()
// Just return if closed.
if nc.isClosed() {
nc.mu.Unlock()
return
}
var mch chan *Msg
// Grab mch
mch := nc.respMap[rt]
// Delete the key regardless, one response only.
// FIXME(dlc) - should we track responses past 1
// just statistics wise?
delete(nc.respMap, rt)
rt := nc.respToken(m.Subject)
if rt != _EMPTY_ {
mch = nc.respMap[rt]
// Delete the key regardless, one response only.
delete(nc.respMap, rt)
} else if len(nc.respMap) == 1 {
// If the server has rewritten the subject, the response token (rt)
// will not match (could be the case with JetStream). If that is the
// case and there is a single entry, use that.
for k, v := range nc.respMap {
mch = v
delete(nc.respMap, k)
break
}
}
nc.mu.Unlock()
// Don't block, let Request timeout instead, mch is
@@ -2610,11 +2697,43 @@ func (nc *Conn) createRespMux(respSub string) error {
return err
}
nc.mu.Lock()
nc.respScanf = strings.Replace(respSub, "*", "%s", -1)
nc.respMux = s
nc.mu.Unlock()
return nil
}
// Helper to setup and send new request style requests. Return the chan to receive the response.
func (nc *Conn) createNewRequestAndSend(subj string, data []byte) (chan *Msg, string, error) {
// Do setup for the new style if needed.
if nc.respMap == nil {
nc.initNewResp()
}
// Create new literal Inbox and map to a chan msg.
mch := make(chan *Msg, RequestChanLen)
respInbox := nc.newRespInbox()
token := respInbox[respInboxPrefixLen:]
nc.respMap[token] = mch
createSub := nc.respMux == nil
ginbox := nc.respSub
nc.mu.Unlock()
if createSub {
// Make sure scoped subscription is setup only once.
var err error
nc.respSetup.Do(func() { err = nc.createRespMux(ginbox) })
if err != nil {
return nil, token, err
}
}
if err := nc.PublishRequest(subj, respInbox, data); err != nil {
return nil, token, err
}
return mch, token, nil
}
// Request will send a request payload and deliver the response message,
// or an error, including a timeout if no message was received properly.
func (nc *Conn) Request(subj string, data []byte, timeout time.Duration) (*Msg, error) {
@@ -2629,29 +2748,8 @@ func (nc *Conn) Request(subj string, data []byte, timeout time.Duration) (*Msg,
return nc.oldRequest(subj, data, timeout)
}
// Do setup for the new style.
if nc.respMap == nil {
nc.initNewResp()
}
// Create literal Inbox and map to a chan msg.
mch := make(chan *Msg, RequestChanLen)
respInbox := nc.newRespInbox()
token := respToken(respInbox)
nc.respMap[token] = mch
createSub := nc.respMux == nil
ginbox := nc.respSub
nc.mu.Unlock()
if createSub {
// Make sure scoped subscription is setup only once.
var err error
nc.respSetup.Do(func() { err = nc.createRespMux(ginbox) })
if err != nil {
return nil, err
}
}
if err := nc.PublishRequest(subj, respInbox, data); err != nil {
mch, token, err := nc.createNewRequestAndSend(subj, data)
if err != nil {
return nil, err
}
@@ -2754,9 +2852,16 @@ func (nc *Conn) NewRespInbox() string {
}
// respToken will return the last token of a literal response inbox
// which we use for the message channel lookup.
func respToken(respInbox string) string {
return respInbox[respInboxPrefixLen:]
// which we use for the message channel lookup. This needs to do a
// scan to protect itself against the server changing the subject.
// Lock should be held.
func (nc *Conn) respToken(respInbox string) string {
var token string
n, err := fmt.Sscanf(respInbox, nc.respScanf, &token)
if err != nil || n != 1 {
return ""
}
return token
}
// Subscribe will express interest in the given subject. The subject
@@ -2822,11 +2927,37 @@ func (nc *Conn) QueueSubscribeSyncWithChan(subj, queue string, ch chan *Msg) (*S
return nc.subscribe(subj, queue, nil, ch, false)
}
// badSubject will do quick test on whether a subject is acceptable.
// Spaces are not allowed and all tokens should be > 0 in len.
func badSubject(subj string) bool {
if strings.ContainsAny(subj, " \t\r\n") {
return true
}
tokens := strings.Split(subj, ".")
for _, t := range tokens {
if len(t) == 0 {
return true
}
}
return false
}
// badQueue will check a queue name for whitespace.
func badQueue(qname string) bool {
return strings.ContainsAny(qname, " \t\r\n")
}
// subscribe is the internal subscribe function that indicates interest in a subject.
func (nc *Conn) subscribe(subj, queue string, cb MsgHandler, ch chan *Msg, isSync bool) (*Subscription, error) {
if nc == nil {
return nil, ErrInvalidConnection
}
if badSubject(subj) {
return nil, ErrBadSubject
}
if queue != "" && badQueue(queue) {
return nil, ErrBadQueueName
}
nc.mu.Lock()
// ok here, but defer is generally expensive
defer nc.mu.Unlock()
@@ -2902,7 +3033,6 @@ func (nc *Conn) removeSub(s *Subscription) {
s.mch = nil
// Mark as invalid
s.conn = nil
s.closed = true
if s.pCond != nil {
s.pCond.Broadcast()
@@ -2939,7 +3069,7 @@ func (s *Subscription) IsValid() bool {
}
s.mu.Lock()
defer s.mu.Unlock()
return s.conn != nil
return s.conn != nil && !s.closed
}
// Drain will remove interest but continue callbacks until all messages
@@ -2964,8 +3094,12 @@ func (s *Subscription) Unsubscribe() error {
}
s.mu.Lock()
conn := s.conn
closed := s.closed
s.mu.Unlock()
if conn == nil {
if conn == nil || conn.IsClosed() {
return ErrConnectionClosed
}
if closed {
return ErrBadSubscription
}
if conn.IsDraining() {
@@ -3021,8 +3155,9 @@ func (s *Subscription) AutoUnsubscribe(max int) error {
}
s.mu.Lock()
conn := s.conn
closed := s.closed
s.mu.Unlock()
if conn == nil {
if conn == nil || closed {
return ErrBadSubscription
}
return conn.unsubscribe(s, max, false)
@@ -3069,8 +3204,8 @@ func (nc *Conn) unsubscribe(sub *Subscription, max int, drainMode bool) error {
}
// NextMsg will return the next message available to a synchronous subscriber
// or block until one is available. A timeout can be used to return when no
// message has been delivered.
// or block until one is available. An error is returned if the subscription is invalid (ErrBadSubscription),
// the connection is closed (ErrConnectionClosed), or the timeout is reached (ErrTimeout).
func (s *Subscription) NextMsg(timeout time.Duration) (*Msg, error) {
if s == nil {
return nil, ErrBadSubscription
@@ -3094,7 +3229,7 @@ func (s *Subscription) NextMsg(timeout time.Duration) (*Msg, error) {
select {
case msg, ok = <-mch:
if !ok {
return nil, ErrConnectionClosed
return nil, s.getNextMsgErr()
}
if err := s.processNextMsgDelivered(msg); err != nil {
return nil, err
@@ -3113,7 +3248,7 @@ func (s *Subscription) NextMsg(timeout time.Duration) (*Msg, error) {
select {
case msg, ok = <-mch:
if !ok {
return nil, ErrConnectionClosed
return nil, s.getNextMsgErr()
}
if err := s.processNextMsgDelivered(msg); err != nil {
return nil, err
@@ -3150,6 +3285,18 @@ func (s *Subscription) validateNextMsgState() error {
return nil
}
// This is called when the sync channel has been closed.
// The error returned will be either connection or subscription
// closed depending on what caused NextMsg() to fail.
func (s *Subscription) getNextMsgErr() error {
s.mu.Lock()
defer s.mu.Unlock()
if s.connClosed {
return ErrConnectionClosed
}
return ErrBadSubscription
}
// processNextMsgDelivered takes a message and applies the needed
// accounting to the stats from the subscription, returning an
// error in case we have the maximum number of messages have been
@@ -3197,7 +3344,7 @@ func (s *Subscription) Pending() (int, int, error) {
}
s.mu.Lock()
defer s.mu.Unlock()
if s.conn == nil {
if s.conn == nil || s.closed {
return -1, -1, ErrBadSubscription
}
if s.typ == ChanSubscription {
@@ -3213,7 +3360,7 @@ func (s *Subscription) MaxPending() (int, int, error) {
}
s.mu.Lock()
defer s.mu.Unlock()
if s.conn == nil {
if s.conn == nil || s.closed {
return -1, -1, ErrBadSubscription
}
if s.typ == ChanSubscription {
@@ -3229,7 +3376,7 @@ func (s *Subscription) ClearMaxPending() error {
}
s.mu.Lock()
defer s.mu.Unlock()
if s.conn == nil {
if s.conn == nil || s.closed {
return ErrBadSubscription
}
if s.typ == ChanSubscription {
@@ -3254,7 +3401,7 @@ func (s *Subscription) PendingLimits() (int, int, error) {
}
s.mu.Lock()
defer s.mu.Unlock()
if s.conn == nil {
if s.conn == nil || s.closed {
return -1, -1, ErrBadSubscription
}
if s.typ == ChanSubscription {
@@ -3271,7 +3418,7 @@ func (s *Subscription) SetPendingLimits(msgLimit, bytesLimit int) error {
}
s.mu.Lock()
defer s.mu.Unlock()
if s.conn == nil {
if s.conn == nil || s.closed {
return ErrBadSubscription
}
if s.typ == ChanSubscription {
@@ -3291,7 +3438,7 @@ func (s *Subscription) Delivered() (int64, error) {
}
s.mu.Lock()
defer s.mu.Unlock()
if s.conn == nil {
if s.conn == nil || s.closed {
return -1, ErrBadSubscription
}
return int64(s.delivered), nil
@@ -3307,7 +3454,7 @@ func (s *Subscription) Dropped() (int, error) {
}
s.mu.Lock()
defer s.mu.Unlock()
if s.conn == nil {
if s.conn == nil || s.closed {
return -1, ErrBadSubscription
}
return s.dropped, nil
@@ -3529,8 +3676,14 @@ func (nc *Conn) close(status Status, doCBs bool, err error) {
nc.stopPingTimer()
nc.ptmr = nil
// Go ahead and make sure we have flushed the outbound
if nc.conn != nil {
// Need to close and set tcp conn to nil if reconnect loop has stopped,
// otherwise we would incorrectly invoke Disconnect handler (if set)
// down below.
if nc.ar && nc.conn != nil {
nc.conn.Close()
nc.conn = nil
} else if nc.conn != nil {
// Go ahead and make sure we have flushed the outbound
nc.bw.Flush()
defer nc.conn.Close()
}
@@ -3583,7 +3736,7 @@ func (nc *Conn) close(status Status, doCBs bool, err error) {
// all blocking calls, such as Flush() and NextMsg()
func (nc *Conn) Close() {
if nc != nil {
nc.close(CLOSED, true, nil)
nc.close(CLOSED, !nc.Opts.NoCallbacksAfterClientClose, nil)
}
}
@@ -3662,12 +3815,12 @@ func (nc *Conn) drainConnection() {
err := nc.Flush()
if err != nil {
pushErr(err)
nc.Close()
nc.close(CLOSED, true, nil)
return
}
// Move to closed state.
nc.Close()
nc.close(CLOSED, true, nil)
}
// Drain will put a connection into a drain state. All subscriptions will
@@ -3773,18 +3926,16 @@ func (nc *Conn) isDrainingPubs() bool {
// Stats will return a race safe copy of the Statistics section for the connection.
func (nc *Conn) Stats() Statistics {
// Stats are updated either under connection's mu or subsMu mutexes.
// Lock both to safely get them.
// Stats are updated either under connection's mu or with atomic operations
// for inbound stats in processMsg().
nc.mu.Lock()
nc.subsMu.RLock()
stats := Statistics{
InMsgs: nc.InMsgs,
InBytes: nc.InBytes,
InMsgs: atomic.LoadUint64(&nc.InMsgs),
InBytes: atomic.LoadUint64(&nc.InBytes),
OutMsgs: nc.OutMsgs,
OutBytes: nc.OutBytes,
Reconnects: nc.Reconnects,
}
nc.subsMu.RUnlock()
nc.mu.Unlock()
return stats
}
@@ -3902,70 +4053,74 @@ 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))`)
// Just wipe slice with 'x', for clearing contents of creds or nkey seed file.
func wipeSlice(buf []byte) {
for i := range buf {
buf[i] = 'x'
}
}
func userFromFile(userFile string) (string, error) {
contents, err := ioutil.ReadFile(userFile)
path, err := expandPath(userFile)
if err != nil {
return _EMPTY_, fmt.Errorf("nats: %v", err)
}
contents, err := ioutil.ReadFile(path)
if err != nil {
return _EMPTY_, fmt.Errorf("nats: %v", err)
}
defer wipeSlice(contents)
return jwt.ParseDecoratedJWT(contents)
}
items := nscDecoratedRe.FindAllSubmatch(contents, -1)
if len(items) == 0 {
return string(contents), nil
func homeDir() (string, error) {
if runtime.GOOS == "windows" {
homeDrive, homePath := os.Getenv("HOMEDRIVE"), os.Getenv("HOMEPATH")
userProfile := os.Getenv("USERPROFILE")
var home string
if homeDrive == "" || homePath == "" {
if userProfile == "" {
return _EMPTY_, errors.New("nats: failed to get home dir, require %HOMEDRIVE% and %HOMEPATH% or %USERPROFILE%")
}
home = userProfile
} else {
home = filepath.Join(homeDrive, homePath)
}
return home, 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
home := os.Getenv("HOME")
if home == "" {
return _EMPTY_, errors.New("nats: failed to get home dir, require $HOME")
}
return home, nil
}
func expandPath(p string) (string, error) {
p = os.ExpandEnv(p)
if !strings.HasPrefix(p, "~") {
return p, nil
}
home, err := homeDir()
if err != nil {
return _EMPTY_, err
}
return filepath.Join(home, p[1:]), nil
}
func nkeyPairFromSeedFile(seedFile string) (nkeys.KeyPair, error) {
var seed []byte
contents, err := ioutil.ReadFile(seedFile)
if err != nil {
return nil, fmt.Errorf("nats: %v", err)
}
defer wipeSlice(contents)
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)
}
kp, err := nkeys.FromSeed(seed)
if err != nil {
return nil, err
}
return kp, nil
return jwt.ParseDecoratedNKey(contents)
}
// Sign authentication challenges from the server.
@@ -3982,13 +4137,6 @@ func sigHandler(nonce []byte, seedFile string) ([]byte, error) {
return sig, nil
}
// Just wipe slice with 'x', for clearing contents of nkey seed file.
func wipeSlice(buf []byte) {
for i := range buf {
buf[i] = 'x'
}
}
type timeoutWriter struct {
timeout time.Duration
conn net.Conn

View File

@@ -17,7 +17,7 @@ Ed25519 is fast and resistant to side channel attacks. Generation of a seed key
The NATS system will utilize Ed25519 keys, meaning that NATS systems will never store or even have access to any private keys. Authentication will utilize a random challenge response mechanism.
Dealing with 32 byte and 64 byte raw keys can be challenging. NKEYS is designed to formulate keys in a much friendlier fashion and references work done in cryptocurrencies, specifically [Stellar](https://www.stellar.org/). Bitcoin and others used a form of Base58 (or Base58Check) to endode raw keys. Stellar utilized a more traditonal Base32 with a CRC16 and a version or prefix byte. NKEYS utilizes a similar format where the prefix will be 1 byte for public and private keys and will be 2 bytes for seeds. The base32 encoding of these prefixes will yield friendly human readbable prefixes, e.g. '**N**' = server, '**C**' = cluster, '**O**' = operator, '**A**' = account, and '**U**' = user. '**P**' is used for private keys. For seeds, the first encoded prefix is '**S**', and the second character will be the type for the public key, e.g. "**SU**" is a seed for a user key pair, "**SA**" is a seed for an account key pair.
Dealing with 32 byte and 64 byte raw keys can be challenging. NKEYS is designed to formulate keys in a much friendlier fashion and references work done in cryptocurrencies, specifically [Stellar](https://www.stellar.org/). Bitcoin and others used a form of Base58 (or Base58Check) to encode raw keys. Stellar utilized a more traditional Base32 with a CRC16 and a version or prefix byte. NKEYS utilizes a similar format where the prefix will be 1 byte for public and private keys and will be 2 bytes for seeds. The base32 encoding of these prefixes will yield friendly human readable prefixes, e.g. '**N**' = server, '**C**' = cluster, '**O**' = operator, '**A**' = account, and '**U**' = user. '**P**' is used for private keys. For seeds, the first encoded prefix is '**S**', and the second character will be the type for the public key, e.g. "**SU**" is a seed for a user key pair, "**SA**" is a seed for an account key pair.
## Installation
@@ -69,4 +69,4 @@ Unless otherwise noted, the NATS source files are distributed
under the Apache Version 2.0 license found in the LICENSE file.
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fnats-io%2Fnkeys.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fnats-io%2Fnkeys?ref=badge_large)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fnats-io%2Fnkeys.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fnats-io%2Fnkeys?ref=badge_large)

View File

@@ -19,8 +19,8 @@ import (
"errors"
)
// Version
const Version = "0.1.0"
// Version is our current version
const Version = "0.1.3"
// Errors
var (
@@ -33,6 +33,7 @@ var (
ErrInvalidSignature = errors.New("nkeys: signature verification failed")
ErrCannotSign = errors.New("nkeys: can not sign, no private key available")
ErrPublicKeyOnly = errors.New("nkeys: no seed or private key available")
ErrIncompatibleKey = errors.New("nkeys: incompatible key")
)
// KeyPair provides the central interface to nkeys.
@@ -93,7 +94,7 @@ func FromSeed(seed []byte) (KeyPair, error) {
return &kp{copy}, nil
}
// Create a KeyPair from the raw 32 byte seed for a given type.
// FromRawSeed will create a KeyPair from the raw 32 byte seed for a given type.
func FromRawSeed(prefix PrefixByte, rawSeed []byte) (KeyPair, error) {
seed, err := EncodeSeed(prefix, rawSeed)
if err != nil {

View File

@@ -17,7 +17,6 @@ import (
"bytes"
"encoding/base32"
"encoding/binary"
"golang.org/x/crypto/ed25519"
)
@@ -47,7 +46,7 @@ const (
PrefixByteUser PrefixByte = 20 << 3 // Base32-encodes to 'U...'
// PrefixByteUnknown is for unknown prefixes.
PrefixByteUknown PrefixByte = 23 << 3 // Base32-encodes to 'X...'
PrefixByteUnknown PrefixByte = 23 << 3 // Base32-encodes to 'X...'
)
// Set our encoding to not include padding '=='
@@ -188,10 +187,11 @@ func DecodeSeed(src []byte) (PrefixByte, []byte, error) {
return PrefixByte(b2), raw[2:], nil
}
// Prefix returns PrefixBytes of its input
func Prefix(src string) PrefixByte {
b, err := decode([]byte(src))
if err != nil {
return PrefixByteUknown
return PrefixByteUnknown
}
prefix := PrefixByte(b[0])
err = checkValidPrefixByte(prefix)
@@ -203,7 +203,7 @@ func Prefix(src string) PrefixByte {
if PrefixByte(b1) == PrefixByteSeed {
return PrefixByteSeed
}
return PrefixByteUknown
return PrefixByteUnknown
}
// IsValidPublicKey will decode and verify that the string is a valid encoded public key.
@@ -288,3 +288,19 @@ func (p PrefixByte) String() string {
}
return "unknown"
}
// CompatibleKeyPair returns an error if the KeyPair doesn't match expected PrefixByte(s)
func CompatibleKeyPair(kp KeyPair, expected ...PrefixByte) error {
pk, err := kp.PublicKey()
if err != nil {
return err
}
pkType := Prefix(pk)
for _, k := range expected {
if pkType == k {
return nil
}
}
return ErrIncompatibleKey
}

6
vendor/modules.txt vendored
View File

@@ -1,10 +1,10 @@
# github.com/nats-io/jwt v0.3.0
# github.com/nats-io/jwt v0.3.2
github.com/nats-io/jwt
# github.com/nats-io/nats.go v1.8.1
# github.com/nats-io/nats.go v1.9.1
github.com/nats-io/nats.go
github.com/nats-io/nats.go/encoders/builtin
github.com/nats-io/nats.go/util
# github.com/nats-io/nkeys v0.1.0
# github.com/nats-io/nkeys v0.1.3
github.com/nats-io/nkeys
# github.com/nats-io/nuid v1.0.1
github.com/nats-io/nuid