The NATS Server ACL configuration proxy provides a secure REST interface for modifying access control lists (ACLs), identities (users), and passwords. This proxy is designed to facilitate the development of command line tools and/or user interfaces to remotely update a NATS server configuration.
Only identities and permissions are supported at this time.
go get -u github.com/nats-io/nats-rest-config-proxy
Usage: nats-rest-config-proxy [options...]
Server Options:
-a, --addr <host> Bind to host address (default: 0.0.0.0)
-p, --port <port> Use port for clients (default: 4567)
-d, --dir <directory> Directory for storing data
-c, --config <file> Configuration file
-f, --publish-script <file> Path to an optional script to execute on publish
Logging Options:
-l, --log <file> File to redirect log output
-D, --debug Enable debugging output
-V, --trace Enable trace logging
-DV Debug and trace
TLS Options:
--cert <file> Server certificate file
--key <file> Private key for server certificate
--cacert <file> Client certificate CA for verification
Common Options:
-h, --help Show this message
-v, --version Show version
The NATS REST ACL Proxy supports a configuration file. Authorization based on the subject attributes of a client certificate is also supported.
listen = '0.0.0.0:4567'
data_dir = 'test/data'
logging {
debug = true
trace = true
}
tls {
ca = 'test/certs/ca.pem'
cert = 'test/certs/server.pem'
key = 'test/certs/server-key.pem'
}
auth {
users = [
{ user = "CN=cncf.example.com,OU=CNCF" }
]
}
The NATS REST Configuration proxy operates using a data directory a configuration file, and a publish script.
The process is straightforward:
A script is used for versatility. For some, this could be used as
a step in a github devops flow and the script creates a PR with the new configuration
for human eyes to review. For others, the updated file is copied to remote nodes and
then NATS servers are reloaded with remote commands, e.g. ssh -t gnatsd -sl reload
.
One could even work on an included NATS server file directly, with changes to be picked
up nightly. There are many options.
# Build locally using Go modules
$ GO111MODULE=on go run main.go
[41405] 2019/02/11 16:18:52.713366 [INF] Starting nats-rest-config-proxy v0.0.1
[41405] 2019/02/11 16:18:52.713804 [INF] Listening on 0.0.0.0:4567
# To run the tests
$ go test ./... -v
Note: To test locally, you'll need to add a hostname into your /etc/hosts
file:
127.0.0.1 nats-cluster.default.svc.cluster.local
The NATS configuration proxy will return the following error codes:
Resource | GET | POST | PUT | DELETE |
---|---|---|---|---|
/v1/auth/idents | Get list of identities | 405 | 405 | Delete all permissions |
/v1/auth/idents/(name) | Get specific identity w/ permissions | 405 | Create/Update Identity | Delete named identity |
/v1/auth/perms | Get list of named permissions sets | 405 | 405 | Delete all permissions |
/v1/auth/perms/(name) | Get specific permission set | 405 | Update Permission | Delete named permission |
/v1/auth/accounts | Get list of accounts | 405 | 405 | 400 |
/v1/auth/accounts/(name) | Get specific account | 405 | Create/Update Account | Delete named account |
/v2/auth/jetstream | 405 | 405 | Create/Update JetStream config | Delete JetStream config |
{"username": "alice", "password": "foo"}
NKEY:
{"nkey" : "UC6NLCN7AS34YOJVCYD4PJ3QB7QGLYG5B5IMBT25VW5K4TNUJODM7BOX"}
Certificate subject attributes with permissions:
{"username" : "CN=Application1,OU=SCSS", "permissions" : "normal_user"}
"normal_user" : {
# Can send to foo, bar or baz only.
"publish" : {
"allow" : ["foo", "bar", "baz"]
}
# Can subscribe to everything but $SYSTEM prefixed subjects.
"subscribe" : {
"deny" : ["$SYSTEM.>"]
}
}
Command | GET | POST | PUT | DELETE |
---|---|---|---|---|
/healthz | 200 | 405 | 405 | 405 |
/v1/auth/snapshot?name=foo | 405 | snapshot current config | 405 | deletes named snapshot |
/v1/auth/publish?name=foo | 405 | Saves / invokes script | 405 | 405 |
/v2/auth/snapshot?name=foo | 405 | snapshot current config | 405 | deletes named snapshot |
/v2/auth/publish?name=foo | 405 | Saves / invokes script | 405 | 405 |
/v2/auth/validate | 405 | Validates the config | 405 | 405 |
In addition to /v1/auth/snapshot
, there is also /v2/auth/snapshot
which is
documented below in the v2.0 Accounts section.
Plain permissions.
curl -X PUT http://127.0.0.1:4567/v1/auth/perms/sample-user -d '{
"publish": {
"allow": ["foo.*", "bar.>"]
},
"subscribe": {
"deny": ["quux"]
}
}'
Queue group permissions are supported as well. Here bar.>
is the subject and
fizzgroup
is the queue group.
curl -X PUT http://127.0.0.1:4567/v1/auth/perms/sample-user -d '{
"publish": {
"allow": ["foo.*", "bar.> fizzgroup"]
}
}'
curl http://127.0.0.1:4567/v1/auth/perms/sample-user
curl -X PUT http://127.0.0.1:4567/v1/auth/idents/sample-user -d '{
"username": "sample-user",
"password": "secret",
"account": "sample-account",
"permissions": "sample-user"
}'
curl http://127.0.0.1:4567/v1/auth/idents/sample-user
curl -X PUT http://127.0.0.1:4567/v1/auth/accounts/sample-account -d '{}'
curl -X PUT http://127.0.0.1:4567/v2/auth/jetstream -d '{
"store_dir": "/data/nats-server",
"max_memory": 1073741824,
"max_file": 10737418240,
"max_streams": -1,
"max_consumers": -1
}'
Create an account with JetStream support enabled with 10GB file storage and 1GB memory, as well as infinite streams and consumers.
curl -X PUT http://127.0.0.1:4567/v1/auth/accounts/sample-account -d '{
"jetstream": {
"max_memory": 1073741824,
"max_file": 10737418240,
"max_streams": -1,
"max_consumers": -1
}
}'
Note that in order to use JetStream you need enable it outside of the auth configuration, for example after publishing.
jetstream {
max_file = 20GB
max_mem = 2GB
}
include 'auth.conf'
curl http://127.0.0.1:4567/v1/auth/accounts/sample-account
curl -X DELETE http://127.0.0.1:4567/v1/auth/accounts/sample-account
curl -X POST http://127.0.0.1:4567/v1/auth/snapshot?name=snap1
curl -X POST http://127.0.0.1:4567/v2/auth/publish?name=snap1
In this example, we will create a couple of users with different permissions:
Ident | DN in TLS cert | Permissions |
---|---|---|
acme-user | CN=acme.example.com,OU=ACME | admin |
cncf-user | CN=cncf.example.com,OU=CNCF | guest |
First we will start the server, and use the -d
flag to setup the directory that will contain the users that were created via the proxy:
$ mkdir config
$ nats-rest-config-proxy -DV -d config
[5875] 2019/06/18 14:43:44.826782 [INF] Starting nats-rest-config-proxy v0.1.0
[5875] 2019/06/18 14:43:44.829134 [INF] Listening on 0.0.0.0:4567
Next, let's create the permissions for both guest
and admin
users:
curl -X PUT http://127.0.0.1:4567/v1/auth/perms/guest -d '{
"publish": {
"allow": ["foo.*", "bar.>"]
},
"subscribe": {
"deny": ["quux"]
}
}'
curl -X PUT http://127.0.0.1:4567/v1/auth/perms/admin -d '{
"publish": {
"allow": [">"]
},
"subscribe": {
"allow": [">"]
}
}'
Now that we have created the permissions, let's bind some users to these permissions:
curl -X PUT http://127.0.0.1:4567/v1/auth/idents/cncf-user -d '{
"username": "CN=cncf.example.com,OU=CNCF",
"permissions": "guest"
}'
curl -X PUT http://127.0.0.1:4567/v1/auth/idents/acme-user -d '{
"username": "CN=acme.example.com,OU=ACME",
"permissions": "admin"
}'
We now can create a named snapshot for this setup. Let's create one named v1
:
curl -X POST http://127.0.0.1:4567/v2/auth/snapshot?name=v1
Then publish the configuration:
curl -X POST http://127.0.0.1:4567/v1/auth/publish?name=v1
At this point, we will have the following directory structure in the config directory:
tree config
config
├── current
│ └── auth.json
├── resources
│ ├── permissions
│ │ ├── admin.json
│ │ └── guest.json
│ └── users
│ ├── acme-user.json
│ └── cncf-user.json
└── snapshots
└── v1.json
And the published auth configuration will look like:
$ cat config/current/auth.json
{
"users": [
{
"username": "CN=acme.example.com,OU=ACME",
"permissions": {
"publish": {
"allow": [
">"
]
},
"subscribe": {
"allow": [
">"
]
}
}
},
{
"username": "CN=cncf.example.com,OU=CNCF",
"permissions": {
"publish": {
"allow": [
"foo.*",
"bar.>"
]
},
"subscribe": {
"deny": [
"quux"
]
}
}
}
]
}
This configuration can now be included by a nats-server
. Note that in order to enable checking permissions based on a TLS certificate, it is needed to set verify_and_map=
to true
in the tls
config:
tls {
cert_file = "./certs/server.pem"
key_file = "./certs/server-key.pem"
ca_file = "./certs/ca.pem"
verify_and_map = true
}
authorization {
include "config/current/auth.json"
}
Starting the NATS Server with the configuration:
nats-server -c nats.conf -DV
[6342] 2019/06/18 18:04:38.899054 [INF] Starting nats-server version 2.0.0
[6342] 2019/06/18 18:04:38.899177 [DBG] Go build version go1.12
[6342] 2019/06/18 18:04:38.899557 [INF] Listening for client connections on 0.0.0.0:4222
[6342] 2019/06/18 18:04:38.899570 [INF] TLS required for client connections
[6342] 2019/06/18 18:04:38.899578 [INF] Server id is NCFA6C5OC45PKJOISSDCWBEDQ4YMKOH57WHCWLL6EZ2Y723WAAIUHPJI
[6342] 2019/06/18 18:04:38.899584 [INF] Server is ready
Now if the following app tries to connect and publish to a subject without permissions it won't be able to:
package main
import (
"log"
"github.com/nats-io/nats.go"
)
func main() {
nc, err := nats.Connect("nats://nats-cluster.default.svc.cluster.local:4222",
nats.ErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, err error) {
log.Println("Error:", err)
}),
nats.ClientCert("./certs/cncf-client.pem", "./certs/cnfc-client-key.pem"),
nats.RootCAs("./certs/ca.pem"),
)
if err != nil {
log.Fatal(err)
}
nc.Publish("ng", []byte("first"))
nc.Publish("foo.bar", []byte("second"))
nc.Flush()
nc.Drain()
}
Example logs from the server:
[6404] 2019/06/18 18:10:11.921048 [DBG] 127.0.0.1:55492 - cid:1 - Client connection created
[6404] 2019/06/18 18:10:11.921561 [DBG] 127.0.0.1:55492 - cid:1 - Starting TLS client connection handshake
[6404] 2019/06/18 18:10:11.929261 [DBG] 127.0.0.1:55492 - cid:1 - TLS handshake complete
[6404] 2019/06/18 18:10:11.929367 [DBG] 127.0.0.1:55492 - cid:1 - TLS version 1.2, cipher suite TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
[6404] 2019/06/18 18:10:11.929615 [TRC] 127.0.0.1:55492 - cid:1 - <<- [CONNECT {"verbose":false,"pedantic":false,"tls_required":true,"name":"","lang":"go","version":"1.7.0","protocol":1,"echo":true}]
[6404] 2019/06/18 18:10:11.929782 [DBG] 127.0.0.1:55492 - cid:1 - User in cert [""], not found
[6404] 2019/06/18 18:10:11.929801 [DBG] 127.0.0.1:55492 - cid:1 - Using certificate subject for auth ["CN=cncf.example.com,OU=CNCF"]
[6404] 2019/06/18 18:10:11.929833 [TRC] 127.0.0.1:55492 - cid:1 - <<- [PING]
[6404] 2019/06/18 18:10:11.929843 [TRC] 127.0.0.1:55492 - cid:1 - ->> [PONG]
[6404] 2019/06/18 18:10:11.930454 [TRC] 127.0.0.1:55492 - cid:1 - <<- [PUB ng 5]
[6404] 2019/06/18 18:10:11.930470 [TRC] 127.0.0.1:55492 - cid:1 - <<- MSG_PAYLOAD: ["first"]
[6404] 2019/06/18 18:10:11.930498 [TRC] 127.0.0.1:55492 - cid:1 - ->> [-ERR Permissions Violation for Publish to "ng"]
[6404] 2019/06/18 18:10:11.930567 [ERR] 127.0.0.1:55492 - cid:1 - Publish Violation - User "CN=cncf.example.com,OU=CNCF", Subject "ng"
[6404] 2019/06/18 18:10:11.930583 [TRC] 127.0.0.1:55492 - cid:1 - <<- [PUB foo.bar 6]
[6404] 2019/06/18 18:10:11.930608 [TRC] 127.0.0.1:55492 - cid:1 - <<- MSG_PAYLOAD: ["second"]
[6404] 2019/06/18 18:10:11.930629 [TRC] 127.0.0.1:55492 - cid:1 - <<- [PING]
[6404] 2019/06/18 18:10:11.930661 [TRC] 127.0.0.1:55492 - cid:1 - ->> [PONG]
[6404] 2019/06/18 18:10:11.931113 [DBG] 127.0.0.1:55492 - cid:1 - Client connection closed
In this example, we will create a couple of users on different accounts.
Ident | Account | Permissions |
---|---|---|
foo-1-user | Foo | guest |
foo-2-user | Foo | admin |
bar-1-user | Bar | guest |
bar-2-user | Bar | admin |
Start the server using its own data directory:
$ mkdir config
$ nats-rest-config-proxy -DV -d config
[5875] 2019/06/18 14:43:44.826782 [INF] Starting nats-rest-config-proxy v0.1.0
[5875] 2019/06/18 14:43:44.829134 [INF] Listening on 0.0.0.0:4567
Next, let's create the permissions for both guest
and admin
users:
curl -X PUT http://127.0.0.1:4567/v1/auth/perms/guest -d '{
"publish": {
"allow": ["foo.*", "bar.>"]
},
"subscribe": {
"deny": ["quux"]
}
}'
curl -X PUT http://127.0.0.1:4567/v1/auth/perms/admin -d '{
"publish": {
"allow": [">"]
},
"subscribe": {
"allow": [">"]
}
}'
Let's create some accounts. In this example, the account Foo
will export a stream and a service that account Bar
will be able to import using a different prefix and subject:
curl -X PUT http://127.0.0.1:4567/v1/auth/accounts/Foo -d '{
"exports": [
{ "stream": "Foo.public.>" },
{ "service": "Foo.api" }
]
}
'
curl -X PUT http://127.0.0.1:4567/v1/auth/accounts/Bar -d '{
"imports": [
{ "stream": {"account": "Foo", "subject": "Foo.public.>" }, "prefix": "from" },
{ "service": {"account": "Foo", "subject": "Foo.api" }, "to": "from.Foo.api" }
]
}
'
Now that we have created the permissions, let's bind some users to these permissions:
curl -X PUT http://127.0.0.1:4567/v1/auth/idents/foo-1-user -d '{
"username": "foo-1-user",
"password": "foo-1-secret",
"permissions": "guest",
"account": "Foo"
}'
curl -X PUT http://127.0.0.1:4567/v1/auth/idents/foo-2-user -d '{
"username": "foo-2-user",
"password": "foo-2-secret",
"permissions": "admin",
"account": "Foo"
}'
curl -X PUT http://127.0.0.1:4567/v1/auth/idents/bar-1-user -d '{
"username": "bar-1-user",
"password": "bar-1-secret",
"permissions": "guest",
"account": "Bar"
}'
curl -X PUT http://127.0.0.1:4567/v1/auth/idents/bar-2-user -d '{
"username": "bar-2-user",
"password": "bar-2-secret",
"permissions": "admin",
"account": "Bar"
}'
You can also apply subject mappings to an account.
curl -X PUT http://127.0.0.1:4567/v1/auth/accounts/MappedAccount -d '{
"mappings": {
"foo": [
{
"destination": "foo.mapped",
"weight": "100%"
}
],
"bar": [
{
"destination": "bar.mapped.1",
"weight": "50%"
},
{
"destination": "bar.mapped.2",
"weight": "50%"
}
]
}
}'
Add a user:
curl -X PUT http://127.0.0.1:4567/v1/auth/idents/mapped-account-user -d '{
"username": "mapped-account-user",
"password": "mapped-account-user-secret",
"permissions": "admin",
"account": "MappedAccount"
}'
Messages published to foo
will be mapped to foo.mapped
and messages
published to bar
will be distributed to bar.mapped.1
and bar.mapped.2
roughly equally.
Account subject mappings are maintained alongside import and exports, so be sure to include existing imports and exports as you add, change, or remove subject mappings.
We now can create a named snapshot for this setup. Let's create one named v1
:
curl -X POST http://127.0.0.1:4567/v2/auth/snapshot?name=v1
Then publish the configuration:
curl -X POST http://127.0.0.1:4567/v2/auth/publish?name=v1
At this point, we will have the following directory structure in the config directory:
$ tree config
config
├── current
│ └── accounts
│ ├── auth.conf
│ ├── Bar.json
│ └── Foo.json
├── resources
│ ├── accounts
│ │ ├── Bar.json
│ │ └── Foo.json
│ ├── permissions
│ │ ├── admin.json
│ │ └── guest.json
│ └── users
│ ├── bar-1-user.json
│ ├── bar-2-user.json
│ ├── foo-1-user.json
│ └── foo-2-user.json
└── snapshots
└── v1
├── auth.conf
├── Bar.json
└── Foo.json
8 directories, 14 files
And the published auth configuration will look like:
$ cat config/current/accounts/auth.conf
accounts {
Bar { include 'Bar.json' }
Foo { include 'Foo.json' }
}
This configuration can now be included by a nats-server
in order to define a
couple of variables that can be used as follow:
include "config/current/accounts/auth.conf"
Release v0.4.0
also now includes a nats-rest-config-validator
tool
which can be used to verify whether the resources
are in a valid state
and otherwise report the error.
nats-rest-config-validator -h
Usage: nats-rest-config-validator [options...]
Options:
-d, --dir <directory> Directory for storing data (default is the current directory.)
-h, --help Show this message
-v, --version Show version
For example given the following directory structure:
$ cd data/
$ tree .
.
└── resources
├── accounts
│ ├── bar.json
│ └── foo.json
├── permissions
│ ├── admin.json
│ └── guest.json
└── users
├── user1.json
├── user2.json
└── user3.json
Where one of the defined permissions has an invalid subject:
==> resources/users/user2.json <==
{
"username": "user2",
"password": "user2",
"permissions": "user2",
"account": "bar"
}
==> resources/permissions/user3.json <==
{
"publish": {
"allow": [
"foo.*",
]
},
"subscribe": {
"deny": [
""
]
}
}
Running the tool would build the config and show on which account the error exists:
$ nats-rest-config-validator -d data
Error: On /bar.json : {
"users": [
{
"username": "user2",
"password": "user2",
"permissions": {
"publish": {
"allow": [
"foo.*"
]
},
"subscribe": {
"deny": [
"",
^^^ subject "" is not a valid subject
Release v0.5.0
includes a couple of tools to create and publish snapshots without having to start the server,
the nats-rest-config-snapshot
and nats-rest-config-publish
tools.
For example, first we can create a snapshot:
$ nats-rest-config-snapshot -d data --snapshot my-snapshot
Taking "my-snapshot" snapshot...
OK
And then publish it as well:
$ nats-rest-config-publish -d data --snapshot my-snapshot
Publishing "my-snapshot" snapshot
OK
By default in case no snapshot name was given, the tool will publish the latest configuration:
$ nats-rest-config-publish -d data
Taking "latest" snapshot...
Publishing "latest" snapshot
OK
Many thanks to MasterCard for sponsoring this project. We appreciate MasterCard's support of NATS, CNCF, and the OSS community.
Unless otherwise noted, the NATS source files are distributed under the Apache Version 2.0 license found in the LICENSE file.
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。