Password Caching in 4.9.1
We introduced production-ready implementation of shadow caching in 4.9, even with some limitations. One of them is missing password caching. It is being implemented for 4.9.1 (and, therefore, 4.10 as well) now. This document provides main ideas behind the implementation.
TL;DR
See the limitations. |
Use Cases
-
We have a limited feature called prohibited values. [1] While providing it, midPoint needs to be able to check that, e.g., a password being set up (at some place) for given user is not the same as the password on specific (or any) user’s account.
This feature is there since 3.7.
Look for
prohibitedValues
in tests files; especially relevant test istest512JackInitializeAccountMaverickAlligator
inAbstractPasswordTest
. -
The cached value is used to determine if there is a password or not, so that outbound weak mappings are executed correctly.
This is a new feature (since 4.9.1).
See e.g.
test312ChangeUserPassword
inAbstractPasswordTest
. -
The cached value is really used. For example, we may have a database table as a source resource, providing users' initial passwords. We may want to cache these, so that they won’t be repeatedly requested.
This is NOT YET SUPPORTED, and we need to decide about this. The problem is that the passwords are cached always in hashed form. See Enabling Encrypted Passwords? section below.
State Before 4.9.1
We stored the passwords only when writing, i.e., when midPoint was updating the password on the resource. This was somehow sufficient to cover the use case #1 (prohibited values), although only when midPoint was the sole source of password changes. (That means, if the password was changed on the resource, midPoint didn’t get that information.)
Current Design
Now we store the passwords both when writing as well as when reading.
Storing shadow passwords when reading is complicated by the fact that resources usually do not provide actual values of the passwords when shadows are being read.
Sometimes, they return an indication whether the password does exist or not.
Sometimes, they return a hashed value (e.g., like {SSHA}rxNYgQODi95h2bsjYXuBqvYz+I1gjgMkF9f0tA==
for LDAP).
But most of the time they do not return anything at all.
When caching, the current implementation does not store encrypted values. There are only three possible states of the cached password value:
-
no value
-
no value, but the
incomplete
flag beingtrue
-
hashed value
Behavior When Writing
If there is a value that is going to be sent to the resource, store it as hashed.
See:
-
ShadowObjectComputer.preparePasswordForStorage(..)
(forADD
operations on shadows) -
ShadowDeltaComputerRelative.addPasswordValueDelta(..)
(forMODIFY
operations on shadows)
Behavior When Reading
-
If a clear text is returned from the resource, store it in the hashed form.
This serves both use cases #1 and #2, although there is a catch: if the value obtained from the resource is already hashed (like in LDAP), storing it in the shadow breaks down the "prohibited values" feature, because the value hashed by LDAP cannot be used for any comparisons. A reasonable advice is to avoid
readable
setting forpasswordReadStrategy
for LDAP resources. Eitherincomplete
orunreadable
(the default) is OK. -
If an
incomplete
oftrue
flag is returned, and a hashed value is present, keep the hashed value.The hashed value will no longer reflect the current value on the resource (which could have been changed outside midPoint) but this behavior is no worse than it was before 4.9.1.
-
If we expected the password, and got none, the cached value is removed.
-
If we didn’t expect the password (and got none), the state is not changed.
How do we know whether the password is expected? Currently, we assume that if the password is readable (in full or incomplete form), it will be always retrieved - regardless of whether it is returned by default, and regardless of the configured fetch strategy. I believe that the current behavior is faulty, though. This can break password caching if the capability declares password as readable, but the password is actually not readable.
See:
-
ShadowObjectComputer.preparePasswordForStorage(..)
(for newly discovered objects) -
ShadowDeltaComputerAbsolute.updateCachedCredentials(..)
(for reading objects that have existing shadows)
Enabling Encrypted Passwords?
Can we enable storing encrypted passwords, so that the use case #3 could be implemented?
The password storage format is driven by the security policy. I originally thought that we would have to look up the account owner’s policy to determine the format.
But, fortunately, we can define security policy for individual resource object types as well. Currently, it is used to determine the password requirements, which are used when generating and/or checking the passwords. So it seems we could also determine the password storage format from it.
Limitations
-
Use case #1 (prohibited values) cannot be used with readable passwords for resources that return passwords in changed form (typically, as hash for LDAP). In such cases, the connector must be configured to either provide no information about the password, or to provide existence information only. This presents an incompatible change: if you have such a resource, and want to use "prohibited values", make sure that the resource does not return mangled password values. If the connector does not support switching to "incomplete flag only" mode, you have to disable the "password readable" capability manually.
-
Use case #3 (caching real password values) is not supported.