NATS Weekly #38
Byron Ruth
Published on Aug 8th, 2022
Week of August 1 - 7, 2022
🗞 Announcements, writings, and projects
A short list of announcements, blog posts, projects updates and other news.
⚡Releases
Official releases from NATS repos and others in the ecosystem.
- nats.rs - async v0.18.0 and non-async v0.23.0
- k8s/nats - v0.17.4
- k8s/nak - v0.17.4
⚙️ Projects️
Open Source and commercial projects (or products) that use or extends NATS.
- BlizzTom/playground-nats - NATS playground
💬 Discussions
Github Discussions from various NATS repositories.
🧑🎓 Examples
New or updated examples on NATS by Example.
- Private Inbox (CLI)
- Private Inbox using JWT (CLI)
- Pull Consumers (Rust)
💡 Recently asked questions
Questions sourced from Slack, Twitter, or individuals. Responses and examples are in my own words, unless otherwise noted.
What exactly does the allow_responses
permission do?
When defining user permissions as part of the server config or as part of a user JWT, you can optionally allow
or deny
the ability to publish
and/or subscribe
to specific subjects (including wildcards). By default, if no permissions are set, a user can publish and subscribe to all subjects (except for those reserved for exclusive use by the system account... which will be discussed another time 🙂).
Once an explicit publish
or subscribe
permission is defined (allow or deny), then the permission restriction kicks in. For example, here is a user without permissions (users
could be scoped under the top-level authorization
block or under an explicit account, e.g. APP
under the accounts
block.)
users: [
{user: joe, password: s3cret}
]
This effectively allows publishing and subscribing to all subjects (barring system-account subjects by default). However, adding even one allow
permission, will result in only those subjects are allowed implicitly.
For example, given these two allow
permissions, now the user joe
cannot publish or subscribe to any other subject. It switches from an implicit deny all + allow.
users: [
{user: joe, password: s3cret, permissions: {
subscribe: {
allow: ["events.service.>"]
},
publish: {
allow: ["events.joe.>"]
}
}}
]
Adding an explicit deny
takes precedence, so messages published to events.service.secret.>
would never be received by joe
even if the subscription was on a more general form of events.service.>
.
users: [
{user: joe, password: s3cret, permissions: {
subscribe: {
allow: ["events.service.>"],
deny: ["events.service.secret.>"]
},
publish: {
allow: ["events.joe.>"]
}
}}
]
What about services that implement the request-reply pattern? For example:
nc.QueueSubscribe("services.greeter", func(msg *nats.Msg) {
reply := fmt.Sprintf("hello %s!", string(msg.Data))
msg.Respond([]byte(reply))
})
As a client, I can call it using the helper method.
nc.Request("services.greeter", []byte("joe"), time.Second)
Internally, this relies on a unique INBOX.>
subject generated by the client and includes it in the message for the service (subscription) to send the reply back to. This works great unless you need to lock-down cross-user snooping of messages. Specifically, you can simply subscribe to _INBOX.>
and listen in on all of the replies send back from the service.
The private inbox pattern can be leveraged to require unique inbox prefixes set per client. Then the permissions can be set to enforce that.
But what does this mean for a user defined for a service? This will also need to have permissions to prevent it from doing everything. Does it now need to have explicit permission for every known inbox prefix that will be used? Not at all, this is what the allow_responses
permission is for.
users: [
{user: greeter, password: s3cret2, permissions: {
subscribe: {
allow: ["services.greeter"]
},
allow_responses: true
}}
]
It essentially provides a dynamic, one-time (by default) allowance of a publish back to the reply subject (an inbox) in order to respond to the request. Beyond that, it can no longer arbitrarily publish to that same subject.
How can you recover from a stuck consumer on a interests-based stream?
Quoting the docs, an interests-based stream is retention policy where:
Messages are kept as long as there are Consumers on the stream (matching the message's subject if they are filtered consumers) for which the message has not yet been ACKed. Once all currently defined consumers have received explicit acknowledgement from a subscribing application for the message it is then removed from the stream.
A situation came up in Slack where a user had a consumer that was in an odd state where messages were no longer being sent the subscriptions for processing. The concern was that if the consumer was removed, the interest for that consumer would be gone, which means all messages would be removed (per the definition of the retention policy).
The solution was to create a temporary consumer that had a start sequence of the last unprocessed message (un-acked) of the misbehaving consumer. From the stream's perspective, this is new interest.
At this point it is safe to delete the first consumer and recreate it starting at the same sequence in order to restart processing. Finally the temporary consumer can be deleted since the original consumer is working again.
As an aside, although this pattern is useful for unrecoverable cases, it turned out the solution to unstick the consumer was to force a leader step down on the consumer. This can be done using nats consumer cluster step-down <stream> <consumer>
.