github.com/simpleiot/simpleiot@v0.18.3/docs/adr/2-authz.md (about) 1 # Authorization 2 3 - Author: Blake Miner 4 - Issue: https://github.com/simpleiot/simpleiot/issues/268 5 - PR / Discussion: https://github.com/simpleiot/simpleiot/pull/283 6 - Status: Brainstorming 7 8 ## Problem 9 10 SIOT currently does not prevent unauthorized NATS clients from connecting and 11 publishing / subscribing. Presently, any NATS client with access to the NATS 12 server connection can read and write any data over the NATS connection. 13 14 ## Discussion 15 16 This document describes a few mechanisms for how to implement authentication and 17 authorization mechanisms within Simple IoT. 18 19 ### Current Authentication Mechanism 20 21 Currently, SIOT supports 22 [upstream connections](https://docs.simpleiot.org/docs/user/upstream.html) 23 through the use of upstream nodes. The connection to the upstream server can be 24 authenticated using a simple 25 [NATS auth token](https://docs.nats.io/using-nats/developer/connecting/token); 26 however, all NATS clients with knowledge of the auth token can read / write any 27 data over the NATS connection. This will not work well for a multi-tenant 28 application or applications where user access must be closely controlled. 29 30 Similarly, web browsers can access the NATS API using 31 [the WebSocket library](https://github.com/simpleiot/simpleiot/tree/master/frontend/lib), 32 but since they act as another NATS client, no additional security is provided; 33 browsers can read / write all data over the NATS connection. 34 35 ## Proposal 36 37 NATS supports 38 [decentralized user authentication and authorization using NKeys and JSON Web Tokens (JWTs)](https://docs.nats.io/running-a-nats-service/configuration/securing_nats/auth_intro/jwt). 39 While robust, this authentication and authorization mechanism is rather complex 40 and confusing; a detailed explanation follows nonetheless. The end goal is to 41 dynamically add 42 [NATS accounts](https://docs.nats.io/running-a-nats-service/configuration/securing_nats/accounts) 43 to the NATS server because publish / subscribe permissions of NATS subjects can 44 be tied to an account. 45 46 ### Background 47 48 Each user node within SIOT will be linked to a dynamically created 49 [NATS account](https://docs.nats.io/running-a-nats-service/configuration/securing_nats/accounts) 50 (on all [upstream nodes](https://docs.simpleiot.org/docs/user/upstream.html)); 51 each account is generated when the user logs in. Only a single secret is stored 52 in the root node of the SIOT tree. 53 54 NATS has a public-key signature system based on 55 [Ed25519](https://en.wikipedia.org/wiki/EdDSA#Ed25519). These keypairs are 56 called NKeys. Put simply, NKeys allow one to cryptographically sign and verify 57 JWTs. An NKey not only consists of a Ed25519 private key / seed, but it also 58 contains information on the "role" of the key. In NATS, there are three primary 59 roles: operators, accounts, and users. In SIOT, there is one _operator_ for a 60 given NATS server, and there is one _account_ for each user node. 61 62 ### Start-up 63 64 When the SIOT server starts, an NKey for the operator role is loaded from a 65 secret stored as a point in the root node of the tree. This point is always 66 stripped away when clients request the root node, so it's never transmitted over 67 a NATS connection. Once the NATS server is running, SIOT will start an internal 68 NATS client and connect to the local NATS server. This internal client will 69 authenticate to the NATS server with a superuser, whose account has full 70 permissions to publish and subscribe to all subjects. Unauthenticated NATS 71 clients _only_ have permission to publish to `auth`subject and listen for a 72 reply. 73 74 ### Authentication / Login 75 76 External NATS clients (including web browsers over WebSockets) must first log 77 into the NATS server anonymously (using the auth token if needed) and send a 78 request to the `auth` subject with the username and password of a valid user 79 node. The default username is `admin`, and the default password is `admin`. The 80 internal NATS client will subscribe to requests on the `auth` subject, and if 81 the username / password is correct, it will respond with a user NKey and user 82 JWT Token, which are needed to login. The user JWT token will be issued and 83 signed by the account NKey, and the account NKey will be issued and signed by 84 the operator NKey. The NATS connection will then be re-established using the 85 user JWT and signing a server nonce with the user's NKey. 86 87 JWT expiration should be a configurable SIOT option and default to 1 hour. 88 Optionally, when the user JWT token is approaching its expiration, the NATS 89 client can request re-authenticate using the `auth` subject and reconnect using 90 the new user credentials. 91 92 ### Storing NKeys 93 94 As discussed above, in the root node, we store the _seed_ needed to derive the 95 operator NKey. For user nodes, account and user NKeys are computed as-needed 96 from the node ID, the username, and the password. 97 98 ### Authorization 99 100 An authenticated user will have publish / subscribe access to the subject space 101 of `$nodeID.>` where $nodeID is the node ID for the authenticated user. The 102 normal SIOT NATS API will work the same as normal with two notable exceptions: 103 104 - The API subjects are prepended with `$nodeID.` 105 - The "root" node is remapped to the set of parents of the logged in user node 106 107 ### Examples 108 109 #### Example #1 110 111 Imagine the following a SIOT node tree: 112 113 - Root (Device 82ad…28ae) 114 - Power Users (Group b723…008d) 115 - Temperature Sensor (Device 2820…abdc) 116 - Humidity Sensor (Device a89f…eda9) 117 - Blake (User ab12…ef22) 118 - Admin (User 920d…ab21) 119 120 In this case, logging in as "Blake" would reveal the following tree with a 121 single root node: 122 123 - Power Users (Group b723…008d) 124 - Temperature Sensor (Device 2820…abdc) 125 - Humidity Sensor (Device a89f…eda9) 126 - Blake (User ab12…ef22) 127 128 To get points of the humidity sensor, one would send a request to this subject: 129 `ab12...ef22.p.a89f...eda9`. 130 131 #### Example #2 132 133 Imagine the following a SIOT node tree: 134 135 - Root (Device 82ad…28ae) 136 - Temperature Sensor (Device 2820…abdc) 137 - Blake (User ab12…ef22) 138 - Humidity Sensor (Device a89f…eda9) 139 - Blake (User ab12…ef22) 140 - Admin (User 920d…ab21) 141 142 In this case, logging in as "Blake" would reveal the following tree with two 143 root nodes: 144 145 - Temperature Sensor (Device 2820…abdc) 146 - Blake (User ab12…ef22) 147 - Humidity Sensor (Device a89f…eda9) 148 - Blake (User ab12…ef22) 149 150 To get points of the humidity sensor, one would send a request to this subject: 151 `ab12...ef22.p.a89f...eda9`. 152 153 ### Implementation Notes 154 155 ```go 156 // Note: JWT issuer and subject must match an NKey public key 157 // Note: JWT issuer and subject must match roles depending on the claim NKeys 158 159 import ( 160 "github.com/nats-io/jwt/v2" 161 "github.com/nats-io/nkeys" 162 "github.com/nats-io/nats-server/v2/server" 163 ) 164 165 // Example code to start NATS server 166 func StartNatsServer(o Options) { 167 op, err := nkeys.CreateOperator() 168 if err != nil { 169 log.Fatal("Error creating NATS server: ", err) 170 } 171 pubKey, err := op.PublicKey() 172 if err != nil { 173 log.Fatal("Error creating NATS server: ", err) 174 } 175 acctResolver := server.MemAccResolver{} 176 opts := server.Options{ 177 Port: o.Port, 178 HTTPPort: o.HTTPPort, 179 Authorization: o.Auth, 180 // First we trust all operators 181 // Note: DO NOT USE conflicting `TrustedKeys` option 182 TrustedOperators: []{jwt.NewOperatorClaims(pubKey)}, 183 AccountResolver: acctResolver, 184 } 185 } 186 187 // Create an Account 188 acct, err := nkeys.CreateAccount() 189 if err != nil { 190 log.Fatal("Error creating NATS account: ", err) 191 } 192 pubKey, err := acct.PublicKey() 193 if err != nil { 194 log.Fatal("Error creating NATS account: ", err) 195 } 196 claims := jwt.NewAccountClaims{pubKey} 197 claims.DefaultPermissions = Permissions{ 198 // Note: subject `_INBOX.>` allowed for all NATS clients 199 // Note: subject publish on `auth` allowed for all NATS clients 200 Pub: Permission{ 201 Allow: StringList([]string{userNodeID+".>"}), 202 }, 203 Sub: Permission{ 204 Allow: StringList([]string{userNodeID+".>"}), 205 }, 206 } 207 claims.Issuer = opPubKey 208 claims.Name = userNodeID 209 // Sign the JWT with the operator NKey 210 jwt, err := claims.Encode(op) 211 if err != nil { 212 log.Fatal("Error creating NATS account: ", err) 213 } 214 215 acctResolver.Store(userNodeID, jwt) 216 ```