nats-server
nats-server copied to clipboard
Simple nkey authentication does not cover consumer creation and message pulling
Hello!
We are testing authorization through nkey and have encountered a problem.
User B
has the following subscription privileges: subscribe: ["test.a", "_INBOX.>"]
Config details:
jetstream {
store_dir=/var/lib/nats/data
}
authorization: {
users = [
{
nkey: UCD55RCRK6T5KTXBHIK2AY3MZQHI4OHAEMVOHFKTI5ZQ2HADRHJXOCAJ
permissions: {
publish: [">"]
subscribe: [">"]
} # admin
}
{
nkey: UASO6B7SZ3IMHQOARGELY3E2TTFRCYSTPSUVHJRBNYWBX3DIDFSVHD2Y
permissions: {
publish: ["test.a"]
}
} # A
{
nkey: UCVIC3B3A6RJAYR3ALTQSPCSNOBEY7MYHYQYDF42KYREP5F6E235CDBL
permissions: {
publish: ["test.a", "$JS.API.STREAM.NAMES", "$JS.API.STREAM.LIST", "$JS.API.CONSUMER.>"]
subscribe: ["test.a", "_INBOX.>"]
}
} # B
]
}
Our case:
-
Streams
defines byadmin
user -
Pull Consumers
- must be created in the application
In the following Java code, we create a pull consumer with subject filter (test.b
) that should not be accessed due authorization configuration.
But the consumer successfully creates and messages pulls without any restrictions...
final String seed = "SUAKO7F6M457ADEU5HIEVYNMDW27O2EQGA2IWCIWMFQ5XLL2GQ5EBVY7FY"; // B
final NKey nkey = NKey.fromSeed(seed.toCharArray());
Options options = new Options.Builder()
.server("nats://localhost:8222")
.authHandler(new AuthHandler() {
@Override
public byte[] sign(byte[] nonce) {
try {
return nkey.sign(nonce);
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e);
}
}
@Override
public char[] getID() {
try {
return nkey.getPublicKey();
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e);
}
}
@Override
public char[] getJWT() {
return new char[0];
}
}).build();
Connection cn = Nats.connect(options);
JetStreamSubscription s = cn.jetStream()
.subscribe("test.b", new PullSubscribeOptions.Builder().durable("app1").build());
while(true) {
List<Message> data = s.fetch(1, Duration.ofSeconds(1));
data.forEach(m -> System.out.println(new String(m.getData())));
}
In JetStream, consuming messages is actually accessing a consumer that has a filtered subject that would be test.a
, but at the core level if you do not want an account to access that consumer via a pull request you need to restrict access to the consumer itself and the pull request.
$JS.API.CONSUMER.MSG.NEXT.<STREAM>.<CONSUMER>
If you want to restrict what consumers and app can create, restrict this subject..
$JS.API.CONSUMER.CREATE.<STREAM> $JS.API.CONSUMER.DURABLE.CREATE.<STREAM>.>
@svzaharov Expanding on this a bit, when creating a consumer (push or pull), there is the ability to set a filter on the underlying stream, e.g. a consumer by user A with a filter on test.a
and a separate one for user B
with test.b
as a filter. In the case of a pull consumer, given a stream named TEST
and two consumers A
and B
, the permission list for A and B would need $JS.API.CONSUMER.MSG.NEXT.TEST.A
and $JS.API.CONSUMER.MSG.NEXT.TEST.B
, respectively. This would then allow the client applications to send these requests requests.
Note, the _INBOX.>
permission would need to be present as well since a one-off random subject is created for delivery of messages per fetch request. If you need [user-level scoped inboxes, there is a way to define custom inbox prefixes in the client and then you can set a permission like _INBOX_A.>
.
Closing this for now since it is an older issue/question. Feel free to follow-up if you have additional questions.