nats-io / nats-server

High-Performance server for NATS.io, the cloud and edge native messaging system.
https://nats.io
Apache License 2.0
15.92k stars 1.41k forks source link

Simple nkey authentication does not cover consumer creation and message pulling #3295

Closed svzaharov closed 7 months ago

svzaharov commented 2 years ago

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:

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())));
        }
derekcollison commented 2 years ago

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>.>

bruth commented 7 months ago

@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.