Skip to content

Commit

Permalink
FIX #28
Browse files Browse the repository at this point in the history
- Deprecated Import#Subject and Import#To, replaced by Import#RemoteSubject and Import#LocalSubject respectively.
- On read, Import#Subject is mapped to Import#RemoteSubject and Import#Subject is cleared
- On read, Import#To is mapped to Import#LocalSubject and Import#To is cleared
- Added Migrated() to claim interface to allow toolchains understand if a JWT was migrated, all code must be updated to refer only to RemoteSubject and LocalSubject.

Fix #30 - Removed restriction where services cannot have wildcards. Of course they can.

Fixed linter warnings about errors and url collisions.
  • Loading branch information
aricart committed Apr 10, 2019
1 parent 8860bd4 commit d7676b8
Show file tree
Hide file tree
Showing 13 changed files with 181 additions and 46 deletions.
25 changes: 25 additions & 0 deletions account_claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package jwt

import (
"errors"
"fmt"

"github.com/nats-io/nkeys"
)
Expand Down Expand Up @@ -129,6 +130,20 @@ func DecodeAccountClaims(token string) (*AccountClaims, error) {
if err := Decode(token, &v); err != nil {
return nil, err
}
// After decoding validation is complete, migrate imports
for _, i := range v.Imports {
if i.Subject != "" || i.To != "" {
if i.RemoteSubject != "" || i.LocalSubject != "" {
return nil, fmt.Errorf("import [%s] uses subject/to and remote/local - only remote/local or subject/to allowed for reading", i.Name)
}
i.RemoteSubject = i.Subject
i.Subject = ""
i.LocalSubject = i.To
i.To = ""
i.migrated = true
}
}

return &v, nil
}

Expand Down Expand Up @@ -165,3 +180,13 @@ func (a *AccountClaims) ExpectedPrefixes() []nkeys.PrefixByte {
func (a *AccountClaims) Claims() *ClaimsData {
return &a.ClaimsData
}

// Migrated returns true if the account claim was migrated during a read
func (a *AccountClaims) Migrated() bool {
for _, i := range a.Imports {
if i.migrated {
return true
}
}
return false
}
5 changes: 1 addition & 4 deletions account_claims_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestNewAccountClaims(t *testing.T) {
account.Expires = time.Now().Add(time.Duration(time.Hour * 24 * 365)).UTC().Unix()

account.Imports = Imports{}
account.Imports.Add(&Import{Subject: "test", Name: "test import", Account: apk2, Token: actJWT, To: "my", Type: Stream})
account.Imports.Add(&Import{RemoteSubject: "test", Name: "test import", Account: apk2, Token: actJWT, LocalSubject: "my", Type: Stream})

vr := CreateValidationResults()
account.Validate(vr)
Expand Down Expand Up @@ -194,9 +194,6 @@ func TestInvalidAccountSubjects(t *testing.T) {
var err error

c := NewAccountClaims(pk)
if i.ok && err != nil {
t.Fatalf("error encoding activation: %v", err)
}
_, err = c.Encode(i.kp)
if i.ok && err != nil {
t.Fatal(fmt.Sprintf("unexpected error for %q: %v", i.name, err))
Expand Down
5 changes: 5 additions & 0 deletions activation_claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ func (a *ActivationClaims) String() string {
return a.ClaimsData.String(a)
}

// Migrated returns true if the activation claim was migrated during a read
func (a *ActivationClaims) Migrated() bool {
return false
}

// HashID returns a hash of the claims that can be used to identify it.
// The hash is calculated by creating a string with
// issuerPubKey.subjectPubKey.<subject> and constructing the sha-256 hash and base32 encoding that.
Expand Down
1 change: 1 addition & 0 deletions claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type Claims interface {
Claims() *ClaimsData
Encode(kp nkeys.KeyPair) (string, error)
ExpectedPrefixes() []nkeys.PrefixByte
Migrated() bool
Payload() interface{}
String() string
Validate(vr *ValidationResults)
Expand Down
5 changes: 5 additions & 0 deletions cluster_claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,8 @@ func (c *ClusterClaims) ExpectedPrefixes() []nkeys.PrefixByte {
func (c *ClusterClaims) Claims() *ClaimsData {
return &c.ClaimsData
}

// Migrated returns true if the cluster claim was migrated during a read
func (c *ClusterClaims) Migrated() bool {
return false
}
4 changes: 1 addition & 3 deletions exports.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func isContainedIn(kind ExportType, subjects []Subject, vr *ValidationResults) {
}

// Validate calls validate on all of the exports
func (e *Exports) Validate(vr *ValidationResults) error {
func (e *Exports) Validate(vr *ValidationResults) {
var serviceSubjects []Subject
var streamSubjects []Subject

Expand All @@ -96,8 +96,6 @@ func (e *Exports) Validate(vr *ValidationResults) error {

isContainedIn(Service, serviceSubjects, vr)
isContainedIn(Stream, streamSubjects, vr)

return nil
}

// HasExportContainingSubject checks if the export list has an export with the provided subject
Expand Down
6 changes: 6 additions & 0 deletions genericlaims.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ func (gc *GenericClaims) Validate(vr *ValidationResults) {
gc.ClaimsData.Validate(vr)
}

// Migrated returns true if the cluster claim was migrated during a read
// GenericClaims will always return false
func (gc *GenericClaims) Migrated() bool {
return false
}

func (gc *GenericClaims) String() string {
return gc.ClaimsData.String(gc)
}
Expand Down
43 changes: 21 additions & 22 deletions imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@ import (

// Import describes a mapping from another account into this one
type Import struct {
Name string `json:"name,omitempty"`
Subject Subject `json:"subject,omitempty"`
Account string `json:"account,omitempty"`
Token string `json:"token,omitempty"`
To Subject `json:"to,omitempty"`
Type ExportType `json:"type,omitempty"`
Account string `json:"account,omitempty"`
LocalSubject Subject `json:"local_subject,omitempty"`
Name string `json:"name,omitempty"`
RemoteSubject Subject `json:"remote_subject,omitempty"`
Token string `json:"token,omitempty"`
Type ExportType `json:"type,omitempty"`
// Deprecated: use Local/Remote
Subject Subject `json:"subject,omitempty"`
// Deprecated: use Local/Remote
To Subject `json:"to,omitempty"`
migrated bool
}

// IsService returns true if the import is of type service
Expand All @@ -52,56 +57,50 @@ func (i *Import) Validate(actPubKey string, vr *ValidationResults) {
vr.AddWarning("account to import from is not specified")
}

i.Subject.Validate(vr)

if i.IsService() {
if i.Subject.HasWildCards() {
vr.AddWarning("services cannot have wildcard subject: %q", i.Subject)
}
}
i.RemoteSubject.Validate(vr)

var act *ActivationClaims

if i.Token != "" {
// Check to see if its an embedded JWT or a URL.
if url, err := url.Parse(i.Token); err == nil && url.Scheme != "" {
if u, err := url.Parse(i.Token); err == nil && u.Scheme != "" {
c := &http.Client{Timeout: 5 * time.Second}
resp, err := c.Get(url.String())
resp, err := c.Get(u.String())
if err != nil {
vr.AddWarning("import %s contains an unreachable token URL %q", i.Subject, i.Token)
vr.AddWarning("import %s contains an unreachable token URL %q", i.RemoteSubject, i.Token)
}

if resp != nil {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
vr.AddWarning("import %s contains an unreadable token URL %q", i.Subject, i.Token)
vr.AddWarning("import %s contains an unreadable token URL %q", i.RemoteSubject, i.Token)
} else {
act, err = DecodeActivationClaims(string(body))
if err != nil {
vr.AddWarning("import %s contains a url %q with an invalid activation token", i.Subject, i.Token)
vr.AddWarning("import %s contains a url %q with an invalid activation token", i.RemoteSubject, i.Token)
}
}
}
} else {
var err error
act, err = DecodeActivationClaims(i.Token)
if err != nil {
vr.AddWarning("import %q contains an invalid activation token", i.Subject)
vr.AddWarning("import %q contains an invalid activation token", i.RemoteSubject)
}
}
}

if act != nil {
if act.Issuer != i.Account {
vr.AddWarning("activation token doesn't match account for import %q", i.Subject)
vr.AddWarning("activation token doesn't match account for import %q", i.RemoteSubject)
}

if act.ClaimsData.Subject != actPubKey {
vr.AddWarning("activation token doesn't match account it is being included in, %q", i.Subject)
vr.AddWarning("activation token doesn't match account it is being included in, %q", i.RemoteSubject)
}
} else {
vr.AddWarning("no activation provided for import %s", i.Subject)
vr.AddWarning("no activation provided for import %s", i.RemoteSubject)
}

}
Expand Down
Loading

0 comments on commit d7676b8

Please sign in to comment.