Updates for operator based configurations.

Added update to parse and load operator JWTs.
Changed to add in signing keys from operator JWT to list of trusted keys.
Added URL account resolver.
Added account claim updates by system messages.

Signed-off-by: Derek Collison <derek@nats.io>
This commit is contained in:
Derek Collison
2018-12-02 20:34:33 -08:00
parent 54d505201f
commit f4f3d3baf1
21 changed files with 847 additions and 109 deletions

View File

@@ -0,0 +1,3 @@
-----BEGIN TEST OPERATOR JWT-----
eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJhdWQiOiJURVNUUyIsImV4cCI6MTg1OTEyMTI3NSwianRpIjoiWE5MWjZYWVBIVE1ESlFSTlFPSFVPSlFHV0NVN01JNVc1SlhDWk5YQllVS0VRVzY3STI1USIsImlhdCI6MTU0Mzc2MTI3NSwiaXNzIjoiT0NBVDMzTVRWVTJWVU9JTUdOR1VOWEo2NkFIMlJMU0RBRjNNVUJDWUFZNVFNSUw2NU5RTTZYUUciLCJuYW1lIjoiU3luYWRpYSBDb21tdW5pY2F0aW9ucyBJbmMuIiwibmJmIjoxNTQzNzYxMjc1LCJzdWIiOiJPQ0FUMzNNVFZVMlZVT0lNR05HVU5YSjY2QUgyUkxTREFGM01VQkNZQVk1UU1JTDY1TlFNNlhRRyIsInR5cGUiOiJvcGVyYXRvciIsIm5hdHMiOnsic2lnbmluZ19rZXlzIjpbIk9EU0tSN01ZRlFaNU1NQUo2RlBNRUVUQ1RFM1JJSE9GTFRZUEpSTUFWVk40T0xWMllZQU1IQ0FDIiwiT0RTS0FDU1JCV1A1MzdEWkRSVko2NTdKT0lHT1BPUTZLRzdUNEhONk9LNEY2SUVDR1hEQUhOUDIiLCJPRFNLSTM2TFpCNDRPWTVJVkNSNlA1MkZaSlpZTVlXWlZXTlVEVExFWjVUSzJQTjNPRU1SVEFCUiJdfX0.hyfz6E39BMUh0GLzovFfk3wT4OfualftjdJ_eYkLfPvu5tZubYQ_Pn9oFYGCV_6yKy3KMGhWGUCyCdHaPhalBw
------END TEST OPERATOR JWT------

View File

@@ -0,0 +1,20 @@
########################################################
# TESTS ONLY #
########################################################
# These are the public signing keys
-----BEGIN SIGNING KEYS-----
ODSKR7MYFQZ5MMAJ6FPMEETCTE3RIHOFLTYPJRMAVVN4OLV2YYAMHCAC
ODSKACSRBWP537DZDRVJ657JOIGOPOQ6KG7T4HN6OK4F6IECGXDAHNP2
ODSKI36LZB44OY5IVCR6P52FZJZYMYWZVWNUDTLEZ5TK2PN3OEMRTABR
------END SIGNING KEYS------
# These are the seeds.
----BEGIN SIGNING SEEDS-----
SOAO7RDW6CLJORHHBS4DPYYIIIAASEIUJ5WWS5FMWLNTFHUCKQ5CAC45AA
SOAEL3NFOTU6YK3DBTEKQYZ2C5IWSVZWWZCQDASBUOHJKBFLVANK27JMMQ
SOACSMP662P2BZDKVF6WCB6FIQYORADDWWWEAI55QY24CQRTY4METUING4
------END SIGING SEEDS------

View File

@@ -0,0 +1,7 @@
########################################################
# TESTS ONLY #
########################################################
-----BEGIN TEST OPERATOR SEED-----
SOAFYNORQLQFJYBYNUGC5D7SH2MXMUX5BFEWWGHN3EK4VGG5TPT5DZP7QU
------END TEST OPERATOR SEED------

View File

@@ -0,0 +1,16 @@
# Server that loads an operator JWT
listen: 127.0.0.1:22222
# Can be an array of filenames as well.
# Key can be operator, operators, roots, root, root_operators, root_operator
operator = "./configs/nkeys/op.jwt"
# This is for account resolution.
# Can be MEMORY (Testing) or can be URL(url).
# The resolver will append the account name to url for retrieval.
# E.g.
# resolver = URL("https://api.synadia.com/ngs/v1/accounts/jwt")
#
resolver = MEMORY

235
test/operator_test.go Normal file
View File

@@ -0,0 +1,235 @@
// Copyright 2018 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.
package test
import (
"fmt"
"testing"
"github.com/nats-io/gnatsd/server"
"github.com/nats-io/go-nats"
"github.com/nats-io/jwt"
"github.com/nats-io/nkeys"
)
const testOpConfig = "./configs/operator.conf"
// This matches ./configs/nkeys_jwts/test.seed
// Test operator seed.
var oSeed = []byte("SOAFYNORQLQFJYBYNUGC5D7SH2MXMUX5BFEWWGHN3EK4VGG5TPT5DZP7QU")
// This is a signing key seed.
var skSeed = []byte("SOAEL3NFOTU6YK3DBTEKQYZ2C5IWSVZWWZCQDASBUOHJKBFLVANK27JMMQ")
func checkKeys(t *testing.T, opts *server.Options, opc *jwt.OperatorClaims, expected int) {
// We should have filled in the TrustedKeys here.
if len(opts.TrustedKeys) != expected {
t.Fatalf("Should have %d trusted keys, got %d", expected, len(opts.TrustedKeys))
}
// Check that we properly placed all keys from the opc into TrustedKeys
chkMember := func(s string) {
for _, c := range opts.TrustedKeys {
if s == c {
return
}
}
t.Fatalf("Expected %q to be in TrustedKeys", s)
}
chkMember(opc.Issuer)
for _, sk := range opc.SigningKeys {
chkMember(sk)
}
}
// This will test that we enforce certain restrictions when you use trusted operators.
// Like auth is always true, can't define accounts or users, required to define an account resolver, etc.
func TestOperatorRestrictions(t *testing.T) {
opts, err := server.ProcessConfigFile(testOpConfig)
if err != nil {
t.Fatalf("Error processing config file: %v", err)
}
if _, err := server.NewServer(opts); err != nil {
t.Fatalf("Expected to create a server successfully")
}
// TrustedKeys get defined when processing from above, trying again with
// same opts should not work.
if _, err := server.NewServer(opts); err == nil {
t.Fatalf("Expected an error with TrustedKeys defined")
}
// Must wipe and rebuild to succeed.
wipeOpts := func() {
opts.TrustedKeys = nil
opts.Accounts = nil
opts.Users = nil
opts.Nkeys = nil
opts.AllowNewAccounts = false
}
wipeOpts()
opts.Accounts = []*server.Account{&server.Account{Name: "TEST"}}
if _, err := server.NewServer(opts); err == nil {
t.Fatalf("Expected an error with Accounts defined")
}
wipeOpts()
opts.Users = []*server.User{&server.User{Username: "TEST"}}
if _, err := server.NewServer(opts); err == nil {
t.Fatalf("Expected an error with Users defined")
}
wipeOpts()
opts.Nkeys = []*server.NkeyUser{&server.NkeyUser{Nkey: "TEST"}}
if _, err := server.NewServer(opts); err == nil {
t.Fatalf("Expected an error with Nkey Users defined")
}
wipeOpts()
opts.AllowNewAccounts = true
if _, err := server.NewServer(opts); err == nil {
t.Fatalf("Expected an error with AllowNewAccounts set to true")
}
wipeOpts()
opts.AccountResolver = nil
if _, err := server.NewServer(opts); err == nil {
t.Fatalf("Expected an error without an AccountResolver defined")
}
}
func TestOperatorConfig(t *testing.T) {
opts, err := server.ProcessConfigFile(testOpConfig)
if err != nil {
t.Fatalf("Error processing config file: %v", err)
}
// Check we have the TrustedOperators
if len(opts.TrustedOperators) != 1 {
t.Fatalf("Expected to load the operator")
}
_, err = server.NewServer(opts)
if err != nil {
t.Fatalf("Expected to create a server: %v", err)
}
// We should have filled in the TrustedKeys here.
// Our master key (issuer) plus the signing keys (3).
checkKeys(t, opts, opts.TrustedOperators[0], 4)
}
func runOperatorServer(t *testing.T) (*server.Server, *server.Options) {
return RunServerWithConfig(testOpConfig)
}
func createAccountForOperatorKey(t *testing.T, s *server.Server, seed []byte) (*server.Account, nkeys.KeyPair) {
t.Helper()
okp, _ := nkeys.FromSeed(seed)
akp, _ := nkeys.CreateAccount()
pub, _ := akp.PublicKey()
nac := jwt.NewAccountClaims(pub)
jwt, _ := nac.Encode(okp)
if err := s.AccountResolver().Store(pub, jwt); err != nil {
t.Fatalf("Account Resolver returned an error: %v", err)
}
return s.LookupAccount(pub), akp
}
func createAccount(t *testing.T, s *server.Server) (*server.Account, nkeys.KeyPair) {
t.Helper()
return createAccountForOperatorKey(t, s, oSeed)
}
func createUserCreds(t *testing.T, s *server.Server, akp nkeys.KeyPair) nats.Option {
t.Helper()
kp, _ := nkeys.CreateUser()
pub, _ := kp.PublicKey()
nuc := jwt.NewUserClaims(pub)
ujwt, err := nuc.Encode(akp)
if err != nil {
t.Fatalf("Error generating user JWT: %v", err)
}
userCB := func() (string, error) {
return ujwt, nil
}
sigCB := func(nonce []byte) ([]byte, error) {
sig, _ := kp.Sign(nonce)
return sig, nil
}
return nats.UserJWT(userCB, sigCB)
}
func TestOperatorServer(t *testing.T) {
s, opts := runOperatorServer(t)
defer s.Shutdown()
url := fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port)
if _, err := nats.Connect(url); err == nil {
t.Fatalf("Expected to fail with no credentials")
}
_, akp := createAccount(t, s)
nc, err := nats.Connect(url, createUserCreds(t, s, akp))
if err != nil {
t.Fatalf("Error on connect: %v", err)
}
nc.Close()
// Now create an account from another operator, this should fail.
okp, _ := nkeys.CreateOperator()
seed, _ := okp.Seed()
_, akp = createAccountForOperatorKey(t, s, seed)
_, err = nats.Connect(url, createUserCreds(t, s, akp))
if err == nil {
t.Fatalf("Expected error on connect")
}
}
func TestOperatorSystemAccount(t *testing.T) {
s, _ := runOperatorServer(t)
defer s.Shutdown()
// Create an account from another operator, this should fail if used as a system account.
okp, _ := nkeys.CreateOperator()
seed, _ := okp.Seed()
acc, _ := createAccountForOperatorKey(t, s, seed)
if err := s.SetSystemAccount(acc.Name); err == nil {
t.Fatalf("Expected this to fail")
}
if acc := s.SystemAccount(); acc != nil {
t.Fatalf("Expected no account to be set for system account")
}
acc, _ = createAccount(t, s)
if err := s.SetSystemAccount(acc.Name); err != nil {
t.Fatalf("Expected this succeed, got %v", err)
}
if sysAcc := s.SystemAccount(); sysAcc != acc {
t.Fatalf("Did not get matching account for system account")
}
}
func TestOperatorSigningKeys(t *testing.T) {
s, opts := runOperatorServer(t)
defer s.Shutdown()
// Create an account with a signing key, not the master key.
acc, akp := createAccountForOperatorKey(t, s, skSeed)
// Make sure we can set system account.
if err := s.SetSystemAccount(acc.Name); err != nil {
t.Fatalf("Expected this succeed, got %v", err)
}
// Make sure we can create users with it too.
url := fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port)
nc, err := nats.Connect(url, createUserCreds(t, s, akp))
if err != nil {
t.Fatalf("Error on connect: %v", err)
}
nc.Close()
}

View File

@@ -53,9 +53,9 @@ func RunServer(opts *server.Options) *server.Server {
if opts == nil {
opts = &DefaultTestOptions
}
s := server.New(opts)
if s == nil {
panic("No NATS Server object returned.")
s, err := server.NewServer(opts)
if err != nil || s == nil {
panic(fmt.Sprintf("No NATS Server object returned: %v", err))
}
// Run server in Go routine.