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