Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce a new CUSTOM_LOOKUP Account Resolver #6434

Open
ghstahl opened this issue Jan 30, 2025 · 0 comments
Open

Introduce a new CUSTOM_LOOKUP Account Resolver #6434

ghstahl opened this issue Jan 30, 2025 · 0 comments
Labels
proposal Enhancement idea or proposal

Comments

@ghstahl
Copy link

ghstahl commented Jan 30, 2025

Proposed change

The goal is that all accounts and users are handled externally and that the nats configuration started with static accounts.
The creation of accounts is done on demand as part of the authoriztion.auth_callout flows.

The starting point.

host: 0.0.0.0
port: 4222
http_port: 8222

websocket {
  port: 8080
  no_tls: true
}

leafnodes {
  port: 7422
}

server_name: $SERVER_NAME
system_account: SYS

jetstream {
	store_dir: "/nats"
}

accounts: {
    SYS: {

    }
    AUTH: {
      users: [
          {user: auth, password: auth}
      ]
    }

}

authorization {
  # Docs 
  # https://github.com/nats-io/nats.docs/blob/master/running-a-nats-service/configuration/securing_nats/auth_callout.md#auth-callout
  # Example of implementing a custom auth server
  # https://github.com/ConnectEverything/nats-by-example/blob/main/examples/auth/callout/cli/service/main.go
  auth_callout {
    # Generated by running
    # nsc generate nkey --account
    # use this private key to sign JWTs:
    # SAAEXFSYMLINXLKR2TG5FLHCJHLU62B3SK3ESZLGP4B4XGLUNXICW3LGAY
    issuer: "ABNXLUXZG427NJ4YLMDR6HYLWCBHEOMN4TQGYGS2T5XNRUK5Y6ZG6XJ3"
    auth_users: [ auth ]
    account: AUTH
    # Generated by running
    # nsc generate nkey --curve
    # use this private key to encrypt/decrypt traffic between auth service & NATS: 
    # SXAMKSXEE3LCBT4NNMKGEDFRGGO4DDIPO5JQSPW6W5MHLZDMG6N2SKB2ZI
    xkey: "XCND2ELXRACFDAD7CFHXHZE7QPSEHW5IKNLPM5Y2FVFS7PHU6NUDMHKR"
  }
}
# configuration of the nats based resolver

resolver {
    type: CUSTOM_LOOKUP
}

Use case

Given a Centralized Authorization model nats accounts are created on the fly based upon an incoming "AUTH" request of a user.
The minimum client connection shall be done by passing a username/password or an opaque token.

nc, err := nats.Connect(
				appInputs.NatsUrl,
				nats.UserInfo(appInputs.NatsUser, appInputs.NatsPass),
			)

The "AUTH" callout handler shall set the userclaims.Audience to the NKEY Public key of the account the user is to be placed into.
NOTE: this is custom code and is opinionated. In this case I am using a simple user json database.

sysAccount := xid.New().String()
users[sysAccount] = &User{
	Pass:    sysAccount,
	Account: "SYS",
	Permissions: jwt.Permissions{
		Pub: jwt.Permission{
			Allow: jwt.StringList{">"},
		},
		Sub: jwt.Permission{
			Allow: jwt.StringList{">"},
		},
	},
}
....

// Prepare a user JWT.
uc := jwt.NewUserClaims(rc.UserNkey)
uc.Name = rc.ConnectOptions.Username
uc.Audience = userProfile.Account
if uc.Name != sysAccount {
	// this part ensures that we have an account signed by the authorization.auth_callout.issuer keypair and we then pass back the public key.
	landingAccount, err := tryGetAccount(userProfile.Account)
	if err != nil {
		respondMsg(req, userNkey, serverId, "", fmt.Sprintf("error getting account: %s", err))
		return
	}
	uc.Audience = landingAccount.KeyPair.PublicKey
      // we will then get a callback on account lookup where we return the account's JWT

}
...
ncSys, err := nats.Connect(appInputs.NATSUrl,
nats.UserInfo(sysAccount, sysAccount))
if err != nil {
	log.Error().Err(err).Msg("error connecting to NATS")
	return err
}
defer func() {
	defer ncSys.Drain()
}()
sub, err := ncSys.Subscribe("$SYS.REQ.ACCOUNT.*.CLAIMS.LOOKUP", func(msg *nats.Msg) {
	accountId := strings.TrimSuffix(strings.TrimPrefix(msg.Subject, "$SYS.REQ.ACCOUNT."), ".CLAIMS.LOOKUP")

	friendlyName := safeGetAccountNameFublicKey(accountId)
	if fluffycore_utils.IsEmptyOrNil(friendlyName) {
		log.Error().Msgf("account not found: %s", accountId)
		return
	}
	createSimpleAccountResponse, err := tryGetAccount(friendlyName)
	if err != nil {
		log.Error().Err(err).Msg("error getting account")
		return
	}
	jwt := createSimpleAccountResponse.JWT
	err = msg.Respond([]byte(jwt))
	if err != nil {
		log.Error().Err(err).Msg("error responding")
	}
	log.Info().Str("accountJWT", jwt).Msg("accountJWT")

})

Contribution

A PR where a CustomAccLookupResolver inherits from the FULL directory resolver as a POC will be sumbitted for discussion.

@ghstahl ghstahl added the proposal Enhancement idea or proposal label Jan 30, 2025
ghstahl added a commit to mapped/nats-server that referenced this issue Jan 30, 2025
Introduced
type CustomLookupAccResolver struct {
	*DirAccResolver
}
@ghstahl ghstahl changed the title Introduce a new CUSTOM_LOOKUP resolver Introduce a new CUSTOM_LOOKUP Account Resolver Jan 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal Enhancement idea or proposal
Projects
None yet
Development

No branches or pull requests

1 participant