domino-db Security
This page describes security features of domino-db.
Authentication and authorization
Application authentication
If Proton is listening for insecure connections, your application uses a server configuration object like this:
const serverConfig = {
hostName: 'your.server.com', // Host name of your server
connection: {
port: '3002', // Proton port on your server
},
};
Since connection.secure
is not true
, all Proton requests execute as
Anonymous and are limited by database ACL permissions for Anonymous.
If on the other hand, Proton is listening for secure connections, your application uses a server configuration object like this:
const serverConfig = {
hostName: 'your.server.com', // Host name of your server
connection: {
port: '3002', // Proton port on your server
secure: true,
},
credentials: {
rootCertificate,
clientCertificate,
clientKey,
},
};
See Secure network requests
for details on the credentials
object, but it's important to understand
credentials.clientCertificate
identifies your application to Proton. Let's
say the certificate maps to a functional ID named "Workflow App/Your Org".
By default, all Proton requests execute as "Workflow App/Your Org" and are
limited by database ACL permissions for that ID. You may need to modify a
database's ACL before you can use domino-db to access the database.
Act-as-User
When using client certificate authentication, you can choose whether to execute Proton requests as your functional ID (e.g. "Workflow App/Your Org") or as a different user. In the latter case, a user must first delegate authorization to your application. You can use the IAM Client Library -- specifically the Authorization Code flow -- to interact with the IAM service. Ultimately your application will acquire an access token from the IAM service.
Let's say you have acquired an access token and stored it in userAccessToken
.
You add the access token to a bulkReadDocuments
request like this:
const documents = await database.bulkReadDocuments({
query: "Form = 'Contact' and LastName = 'Aardman'",
accessToken: userAccessToken,
});
It's that simple. Now the Proton request executes as the user identified
by the access token. Of course, that user must have at least Reader access
in the target database's ACL. Also, your application's functional ID
must still be listed in the ACL and the functional ID must have the
[_ActAsUser]
role.
And the same is true for other requests. For example, you add an access token
to a replaceItems
request like this:
await document.replaceItems({
replaceItems: { demoMessage: 'This is a demo message' },
accessToken: userAccessToken,
});
You can add the accessToken
property to the options
object you pass to
most Database
functions including createDocument()
, bulkReadDocuments()
,
bulkReadDocumentsByUnid()
, and so on. You can also add the accessToken
property for most Document
functions including read()
,
replaceItems()
, delete()
, and so on. If a function supports accessToken
,
it is listed as an optional property in the Parameters section of the
function description.
By the way, an access token is valid only for a limited time as determined by the IAM service. You can use an access token for multiple requests, but your application is responsible for acquiring a fresh token before an existing access token expires. If you mistakenly use an expired token, your request will fail.
Item encryption and decryption
The domino-db module and Proton support item-level encryption and decryption of documents. These features require your application to authenticate as an identity that has an ID file in a Domino ID vault. Furthermore, the current implementation requires your application to specify the ID file password. This section describes how to encrypt and decrypt items with domino-db.
ID file password
When Proton is configured to accept secure requests (SSL/TLS), you specify your
application's credentials when you call useServer
.
See Secure network requests for
general information about client credentials. If your application needs to encrypt
and/or decrypt document items, your credentials
object must also include an
idFilePassword
property as shown below:
const serverConfig = {
hostName: 'your.server.com', // Host name of your server
connection: {
port: '3002', // Proton port on your server
secure: true,
},
credentials: {
rootCertificate,
clientCertificate,
clientKey,
idFilePassword: 'your-password',
},
};
useServer(serverConfig).then(server => {
// server is available for use. Proton verifies the ID file
// password for all requests from this Server instance.
});
When you specify idFilePassword
in your server configuration, Proton verifies
the password for all subsequent requests. This is true whether or not a specific
request involves encryption or decryption. There is a small performance penalty
associated with password verification, but this approach makes Proton responses
more predictable. If you specify idFilePassword
Proton must be able to both
locate your application's ID file in a vault and use the password to unlock the
ID file. If your application doesn't need to encrypt or decrypt items, you
should omit the idFilePassword
property.
NOTE: As described in Act-as-User, you can execute Proton requests as a user other than your application's functional ID. When a request includes an
accessToken
property, theidFilePassword
property is ignored. In other words, you can only encrypt and decrypt items with your application's functional ID; not on behalf of another user.
Encryption
When you create or update a document with domino-db, you specify one or more
document items. To encrypt an item you must use the canonical format as
described in domino-db document schema and you must
include a boolean encrypt
property as show below:
Description: {
type: 'text',
data: 'Some very very secret string',
encrypt: true
}
That's all there is to it. As long as your server configuration includes the
correct ID file password (as previously mentioned), the Description
item is
encrypted on disk. By default, the item is encrypted so only your application
can decrypt it, but there are two ways to change the audience.
To encrypt items for another identity, you add a PublicEncryptionKeys
item to
the document. For example, the following encrypts the Description
item so only
Joe User can decrypt it:
Description: {
type: 'text',
data: 'Some very very secret string',
encrypt: true
},
PublicEncryptionKeys: 'Joe User/Your Org'
The PublicEncryptionKeys
can be either a string or an array of strings.
However each string must be a valid name. Proton must be able to find each name
in a directory and each name must have an associated public encryption key. If
these conditions are not met, your create (or update) request will fail.
It's also important to understand that PublicEncryptionKeys
literally lists
the entire target audience for the encrypted items. By default, this does not
include your application. To encrypt a document for both Joe User and your
application, you must specify something like this:
Description: {
type: 'text',
data: 'Some very very secret string',
encrypt: true
},
PublicEncryptionKeys: ['Joe User/Your Org', 'Your Application/Your Org']
As an alternative, you can use SecretEncryptionKeys
to encrypt a document for
a group of identities that share a secret key.
Description: {
type: 'text',
data: 'Some very very secret string',
encrypt: true
},
SecretEncryptionKeys: 'Top Secret Key'
This works only if Proton is able to load Top Secret Key from your application's
ID file. If your create (or update) operation succeeds, a user will not be able
to decrypt the Description
item unless the user's ID file also includes Top
Secret Key.
Decryption
Unlike encryption, decryption is mostly a passive act. When you use domino-db to read a document, the response includes a decrypted item only if the conditions allow it. Otherwise, the read request succeeds, but the response doesn't include the decrypted item.
As an example, consider this code:
useServer(serverConfig).then(async server => {
const database = await server.useDatabase(databaseConfig);
const document = await database.useDocument({
unid: '28438659F50E2637852582C600038599',
});
try {
const myDocument = await document.read({ itemNames: ['Description'] });
// myDocument is ready to use
} catch (e) {
// handle error
}
});
This code attempts to read an item named Description
from a single document.
If the Description item exists and it is decrypted you can expect the
following excerpt in the response:
Description: {
type: 'text',
data: 'Some very very secret string',
encrypt: true
}
However, this only happens if:
- Your server configuration included
idFilePassword
as described above. - Proton is able to load and unlock your application's ID file from the vault.
- The Description item is encrypted for your application.
If your server configuration does not include idFilePassword
or the item is
not encrypted for your application, the read request succeeds, but Description
is omitted from the response.