Access Control schema¶
Currently data pertaining to access control is stored in the main database, with an XML namespace of http://jackjansen.nl/igor/authentication (usually encoded with the xmlns:au prefix).
This data is hidden from normal Igor access (unless query parameter .VARIANT=raw is used). In principle this should be safe,
because an external call cannot modify the capabilities and there is no secret information contained in the capability data.
There is however an issue that a call with the right permissions can accidentally delete a capability be replacing a subtree (with PUT).
Capability structure¶
A capability is stored in an au:capability element.
commenttextual description, to keep us sane during development.cidunique ID of this capability.childone entry for each child (delegated) capability of this capability.parentparent of this capability.delegateboolean, iftruethis capability can be delegated. If the value is the stringexternalthis capability can be the parent of any capability as long as that new capability has anaudfield.objan XPath referencing a single element (or a nonexisting element with a single existing parent element) to which this capability refers. Rights on that object and its descendants are governed by a number of other fields:getTogether withobjdefines on which elements this capability grantsGETrights:- empty (or non-existent): none.
selfthe element itself only.descendant-or-selfthe whole subtree rooted at the element (the element itself, its children, its grandchildren, etc).descendantthe whole subtree rooted at the element except the element itself.childdirect children of the element.- More values may be added later.
putTogether withobjdefines on which elements this capability grantsPUTrights. Values as forget.postTogether withobjdefines on which elements this capability grantsPOSTrights. Values as forget.deleteTogether withobjdefines on which elements this capability grantsDELETErights. Values as forget.
The obj field will usually be an absolute XPath (starting with /data) but there are a number of other values used for non-database accesses (REST and other):
/actionis the virtual object tree of actions (the REST/actionentrypoints)/internalis the virtual object tree of internal actions (the REST/internalentrypoints)/pluginis the virtual object tree of plugins (the REST/pluginentrypoint)/filesystemis the right to do operations that modify the filesystem. Checking this capability is currently only implemented for installing plugins.
Capabilities that have an external representation may have a few extra fields:
issIssuer of this capability. Usually the URL of/issueron this igor.audAudience of this capability. Required for capabilities that Igor will encode as a JWT (Json Web Token, https://en.wikipedia.org/wiki/JSON_Web_Token) in an outgoingAuthentication: Bearerheader.subSubject of this capability. Required for capabilities that Igor receives as JWT in an incomingAuthentication: Bearerheader.- For outgoing capabilities there may be other fields that are meaningful to the audience of the capability.
External capabilities are protected using a symmetric key that is shared between Issuer and Audience (for outgoing capabilities) or Issuer and Subject (for incoming keys). This key is used to sign the JWT.
Database schema additions¶
/internal/accessControl¶
Not really part of the database, but this is the entry point to manage (list, delegate, revoke) capabilities.
/data/au:access¶
Required for further schema requirements.
/data/au:access/au:defaultCapabilities¶
Capabilities that will be used for any action, user or request that has no Authentication: Bearer header. For users and actions this set of capabilities is also valid if they have their own set. In other words: their own set augments the set of capabilities, it does not replace it.
These capabilities should be here:
- get(descendant-or-self), /data/environment
- get(descendant-or-self), /data/status
- get(descendant-or-self), /data/services/igor
- get(child), /static
- get(child), /internal/accessControl
- get(descendant-or-self)/put(descendant)/post(descendant)/delete(descendant), /data/sandbox
/data/au:access/au:exportedCapabilities¶
Stores each au:capability for which an external representation has been created. Mainly so that each capability has an “owner”.
/data/au:access/au:revokedCapabilities¶
Stores all external capabilities that have been revoced. For each such capability there is a au:revokedCapability with at least a field cid that holds the capability ID. Optionally there is an nva field, Not Valid After, copied from the original capability, that indicates when this revoked capability can be cleaned up because the original is no longer valid.
/data/au:access/au:unusedCapabilities¶
This is an optional area to store capabilities that are valid but currently not used, and that have no owner. For Igor development, really.
/data/identities¶
Capabilities carried by all users that are logged in. Contains at least:
- get(descendent-or-self), /data/people
/data/identities/admin¶
User that holds the master capabilities, capabilities with fairly unlimited access from which more limited capabilities are descended (through delegation).
There are at least the following capabilities, of which most other capabilities are descended (through delegation and narrowing the scope):
- get(descendant-or-self)+put(descendant)+post(descendant)+delete(descendant), /data
- get(descendant), /action
- get(descendant), /plugin
- get(descendant), /pluginscript
- get(descendant), /internal
- an empty capability (no rights, no object) with
cid=rootand no parent. This is the root of the capability tree.
/data/identities/user¶
Capabilities this user will carry when logged in. Contains at least:
- get(descendent-or-self)+put(descendent)+post(descendent)+delete(descendent), /data/identities/user
- put(descendent)+post(descendent)+delete(descendent), /data/people/user
/data/actions¶
Capabilities that are carried by all actions. Contains at least:
- get(descendant), /plugin
- get(child), /action
/data/actions/action¶
Capabilities this action will carry when executing.
/data/plugindata/pluginname/au:capability¶
Capabilities this plugin will carry when executing. Also available to the template pages and scripts for this plugin.
Shadow database¶
The main igor database .igor/database.xml does not contain any secret information, so that access control secrets cannot be leaked accidentally through the REST interface.
Therefore, all secret information is kept in a separate database .igor/shadow.xml which has in principle the same structure as the main database, but only contains secret information.
In practice, the shadow database contains only the shared secret keys:
/data/au:access/au:sharedKeys¶
Stores symmetric keys shared between Igor and a single external party. These keys are used to sign outgoing capabilities (and check incoming capabilities). Each key is stored in an au:sharedKey element with the following fields:
issIssuer.aud(optional) Audience.sub(optional) Subject.externalKeySymmteric key to use.
Keys are looked up either by the combination of iss and aud (for outgoing keys) or iss and sub (for incoming keys).
Implementation details¶
This section lists some of the ideas that came up when designing the capability structure. They may not be true anymore, but the text is kept here because it is not currently stored anywhere else.
Capability consistency checks¶
Capabilities need to be checked for consistency, and for adherence to the schema.
The following checks are done as a first order check, and ensure the base infrastructure for the schema is in place:
/data/au:accessexists./data/au:access/au:defaultCapabilitiesexists./data/au:access/au:exportedCapabilitiesexists./data/au:access/au:revokedCapabilitiesexists./data/au:access/au:unusedCapabilitiesexists./data/au:access/au:sharedKeysexists./data/identitiesexists./data/identities/adminexists./data/identities/admin/au:capability[cid='root']exists./data/actionsexists.
As a second check we test that the default set of capabilities (as per the schema above) exist and are in their correct location.
As a third check we enumerate all capabilities and check the following assertions. These ensure that the tree of all capabilities is consistent:
- Each capability must have a
cid. - This
cidmust be unique. - Each capability (except
cid=root) must have an existingparent, if not the capability is givenparent=root. - Each capability must have its
cidlisted in the parentchildfields. If not it is added. - Each
childof each capability must exist. If not thechildis removed.
As a fourth check we check that every capability is in an expected location. In other words, the DOM parent of the capability is one of:
- Any of the containers in the first check, or
/data/identities/*/data/plugindata/*/data/actions/action
Capabilities that fail this check are moved into /data/au:access/au:unusedCapabilities.
Actions on adding a new user¶
To be refined, but at least:
- Create
/data/peopleentry. - Create
/data/identitiesentry,- Fill with capabilities mentioned above
- Create password
The API will need at least name and password. Because of access control policies it is implied that only the admin user can call this API (or any agent that the admin user has granted the corresponding capabilities to).
Actions on deleting a user¶
- Move any non-standard capabilities (really: any capability with
audnot the current Igor) to a safe place (probably the admin user). - Delete
/data/peopleand/data/identitiesentries.
The API will need just the user name, and the same access control rules as for adding users will apply.
Actions on adding a new device¶
- Create SSL key with
igorCA(oriotsa/extras/make-igor-signed-cert.shor via /plugin/ca) and copy the key and certificate to the device. - Create a shared secret key with new device as audience, via /capabilities.html or /internal/accessControl, and copy the secret key to the device.
- Create an initial “allow all API actions” capability for the device (TBD) and store it in some users’ space (current user? admin user?)
- Igor should automatically pick up the correct secret key and encode the capability with it, when talking to the device.
- If the device is also a sensor, i.e. if it can also trigger actions in Igor, all of the sensor actions must also be done.
Actions on adding a new sensor¶
- Create a shared secret key with the new sensor as subject, via /capabilities.html or /internal/accessControl, and copy the secret key to the device.
- Create a capability (with the sensor as subject and audience Igor) for each action the sensor should be able to trigger.
- Export these capabilities (Igor will pick up the correct secret key based on the subject) and copy them to the sensor.
Generalized API for adding a device or sensor¶
Data to be supplied to this action:
- Name of the device/sensor.
- Boolean isSensor.
- Boolean isDevice.
- Hostname or IP address of the sensor (defaults to name with .local appended).
- if isDevice this will be used as the audience of the first shared key.
- If isSensor this will be used as the subject of the second shared key.
- if isDevice: Partial URL of the API of this device (such as /api). Will be the object of the device access capability stored in the users’ identities entry.
- if isSensor: List of (name, verb, object) this sensor will contact (or empty for non-sensor devices). If non-empty the sensor shared key (audience Igor, subject the sensor) will be used to sign these.
Data returned:
- if isDevice:
- SSL key
- SSL certificate
- shared device key
- if isSensor:
- List of (name, verb, url, signed capability).
Data saved in Igor database:
- Shared keys (in the hidden area)
- if isDevice: Capability for accessing the device
- Entry in either
/data/devicesor/data/sensors.
It needs to be worked out what the access control rights are that are needed for this API. It seems as though no special rights are needed for devices, and for sensors the caller needs to have capabilities (with delegate=true) for each of the verb/object combinations.
It also needs to be worked out whether the user (or other agent) that calls this API gets permissions to the /data/devices or /data/sensors areas.
Deleting a device or sensor¶
- The entries in
/data/devicesand/data/sensorsshould be deleted. - The shared keys should be deleted.
- The SSL certificate should be revoked.
Other actions on agent changes¶
To be determined what is needed when adding/removing/changing plugins and actions.
Also to be determined whether anything needs to be done when certificates expire or are revoked.
Also to be determined what to do when secret keys are deleted and re-added.