<objectCollectionView>
<identifier>allUsers</identifier>
<type>UserType</type>
<action>
<name>reset-ad-password</name>
<display>
<label>Reset AD password</label>
</display>
<taskTemplateRef oid="a8fa6004-1ad7-445b-a546-bdc1a83e4b2b"/>
</action>
<!-- ... -->
</objectCollectionView>
Execution of Trusted Bulk Actions by Unprivileged Users
Since 4.8
This functionality is available since version 4.8.
|
Introduction
Custom GUI Actions
Problem
The custom GUI actions for object lists are currently implemented using task templates. Such a template often invokes a bulk action that calls a Groovy script that does the work needed. For example, the action in question may be "Reset AD password" (for a given user). The Groovy script takes a user, determines their account on specific resource (AD), and then prepares and executes a credentials change for that account.
If the action is executed by a user possessing #all
authorization (e.g., the administrator), everything works fine.
However, less privileged users (e.g., operators) are forbidden from executing that action.
The reason lies in the (simple, safe, but imprecise and thus over-cautious) security model in midPoint 4.7.x and earlier: In general, Groovy scripts are dangerous, because they can be used to execute arbitrary code within a Java virtual machine - and therefore, on the host operating system. The only limits are those of the user running the JVM. As the midPoint before 4.8 did not recognize the origin of the code being executed, it employed a simple rule: To execute a Groovy script (or any other, like Velocity, JavaScript, and so on) from within bulk action, one has to be an administrator.
Solution Overview
The solution is based on determining the origin of any expression or bulk action, and execute it under corresponding expression profile.
Expression profile limits the instruments (bulk actions, expression evaluators, function libraries and their functions, scripting languages, packages, classes, methods) available to given expression or bulk action.
This enables the administrator to give more facilities (even very dangerous ones) into the hands of more knowledgeable and/or trustworthy persons, while still allowing less knowledgeable and/or trustworthy ones to write some expressions and bulk actions, although limiting them to a more safe subset of available features.
Solution Details
A custom action references a task template, like this:
The task template looks like an ordinary task, with the following limitations:
-
It should be activity-based task, with a single activity from the list of supported ones:
-
iterative scripting,
-
iterative change execution,
-
recomputation.
(Some other activities will work in 4.8, but their use does not make much sense in this context. These are: object integrity check, reindexing, trigger scan, focus validity scan, and deletion.)
-
-
The object set definition should be empty. It will be filled-in based on the objects selected.
<task xmlns="http://midpoint.evolveum.com/xml/ns/public/common/common-3"
xmlns:s="http://midpoint.evolveum.com/xml/ns/public/model/scripting-3"
oid="a8fa6004-1ad7-445b-a546-bdc1a83e4b2b">
<name>template-reset-ad-password</name>
<assignment> (3)
<targetRef oid="a314bb8c-969a-4836-b9b5-53c13567faec" type="ArchetypeType"/>
</assignment>
<executionState>waiting</executionState>
<activity>
<work>
<iterativeScripting> (1)
<scriptExecutionRequest>
<s:execute>
<s:script>
<code> (2)
... Groovy code to find the projection on AD resource (if any), and reset the password there ...
</code>
</s:script>
</s:execute>
</scriptExecutionRequest>
</iterativeScripting>
</work>
</activity>
</task>
1 | The activity that will be executed after task template instantiation. |
2 | Groovy code to be executed. Note that this code is prevented from being run if the user is not an administrator. (Unless an expression profile is set.) |
3 | Archetype that specifies a custom expression profile for this template and tasks derived from it. |
During instantiation of this task template, iterativeScripting
will be enhanced by adding objects
item pointing to the object or objects selected by the user.
Before 4.8, running the Groovy code would require administrator privileges.
However, since 4.8 we can specify the expression profile for this task template (and hence for tasks created from it), by using an archetype like this:
<archetype xmlns="http://midpoint.evolveum.com/xml/ns/public/common/common-3"
oid="a314bb8c-969a-4836-b9b5-53c13567faec">
<name>trusted-task</name>
<archetypePolicy>
<expressionProfile>trusted</expressionProfile>
</archetypePolicy>
</archetype>
The trusted
expression profile should be defined in such a way that it would allow Groovy scripts to be run.
It can be done e.g. simply by allowing anything.
The definition is part of the system configuration:
trusted
expression profile in the system configuration<systemConfiguration xmlns="http://midpoint.evolveum.com/xml/ns/public/common/common-3">
<!-- ... -->
<expressions>
<expressionProfile>
<identifier>trusted</identifier>
<decision>allow</decision> (1)
</expressionProfile>
</expressions>
</systemConfiguration>
1 | What is not explicitly specified, is allowed, i.e. everything. |
Discussion
Note that this expression profile does not make the script running with full midPoint privileges.
For example, if the script executes midpoint.getObject
call, the authorizations of the current principal are fully checked.
However, the script has no limitations what it may call.
Hence, it can call e.g. midpoint.repositoryService.getObject
method, and obtain any object without checking the authorizations.
(Not speaking about standard Java API methods to run arbitrary OS command.)
In this way, by creating task templates with the appropriate archetype, you allow even unprivileged users to run arbitrary code - prepared by trusted persons.
The security of this approach rests on the fact that the authorizations do not allow unprivileged users to create tasks with arbitrary activity definitions and arbitrary archetypes. Otherwise, any such user could circumvent the security measures by using an archetype with any expression profile they would wish, allowing to run arbitrary code. |
Improving the Solution: Limiting the Features a Task can Directly Use
To provide additional layer of security, the archetype of the task template may allow a limited set of features. This means that even if an attacker was able to create tasks with this archetype, they would not obtain access to arbitrary script execution.
The most reasonable approach would be to allow only an invocation of a single library function (or a small set of functions), providing the functionality needed.
Let us have a look at such a profile:
trusted-functions-only
expression profile in the system configuration<systemConfiguration xmlns="http://midpoint.evolveum.com/xml/ns/public/common/common-3">
<!-- ... -->
<expressions>
<expressionProfile>
<identifier>trusted-functions-only</identifier>
<decision>deny</decision> (1)
<evaluator>
<type>function</type> (2)
<decision>allow</decision>
</evaluator>
<evaluator> (3)
<type>path</type>
<decision>allow</decision>
</evaluator>
<functionLibrariesProfile>trusted-functions-only</functionLibrariesProfile> (4)
</expressionProfile>
<functionLibrariesProfile>
<identifier>trusted-functions-only</identifier> (4)
<decision>deny</decision> (1)
<library>
<ref oid="17b5b255-c71e-4a67-8e42-349862e295ac"/>
<decision>deny</decision> (1)
<function>
<name>resetAdPassword</name> (5)
<decision>allow</decision>
</function>
</library>
</functionLibrariesProfile>
</expressions>
</systemConfiguration>
1 | What is not explicitly specified, is denied. |
2 | This allows to call library functions (stored in FunctionLibraryType objects). |
3 | This allows to provide parameter values as path expressions (necessary to be able to call any function with parameters). |
4 | Limits the library functions that can be called. |
5 | This is the trusted function that can be called. |
The task template archetype should now reference this new profile:
<archetype xmlns="http://midpoint.evolveum.com/xml/ns/public/common/common-3"
oid="93651a38-b1d5-4e53-8da6-628adfb85941">
<name>trusted-functions-only-task</name>
<archetypePolicy>
<expressionProfile>trusted-functions-only</expressionProfile>
</archetypePolicy>
</archetype>
Of course, the task template itself will now look different. Instead of containing Groovy code directly, it should call a library function to do the work.
<task xmlns="http://midpoint.evolveum.com/xml/ns/public/common/common-3"
xmlns:s="http://midpoint.evolveum.com/xml/ns/public/model/scripting-3"
oid="472ffb27-5b99-43e7-9c5c-fc0f453b3e89">
<name>template-reset-ad-password-improved</name>
<assignment>
<targetRef oid="93651a38-b1d5-4e53-8da6-628adfb85941" type="ArchetypeType"/>
</assignment>
<executionState>waiting</executionState>
<activity>
<work>
<iterativeScripting>
<scriptExecutionRequest>
<s:evaluateExpression>
<s:expression> (1)
<function> (2)
<libraryRef oid="17b5b255-c71e-4a67-8e42-349862e295ac"/>
<name>resetAdPassword</name>
<parameter>
<name>user</name> (3)
<expression>
<path>$input</path>
</expression>
</parameter>
</function>
</s:expression>
</s:evaluateExpression>
</scriptExecutionRequest>
</iterativeScripting>
</work>
</activity>
</task>
1 | Evaluates arbitrary expression (new in 4.8) |
2 | Calls a function resetAdPassword in library 17b5b255-c71e-4a67-8e42-349862e295ac |
3 | Passes the input as the value for the user parameter |
The function library is quite ordinary. It contains the same Groovy code as was present in the original task template.
<functionLibrary xmlns="http://midpoint.evolveum.com/xml/ns/public/common/common-3"
oid="17b5b255-c71e-4a67-8e42-349862e295ac">
<name>library</name>
<function>
<name>resetAdPassword</name>
<parameter>
<name>user</name>
<type>UserType</type>
</parameter>
<script>
<code>
... Groovy code to find the projection on AD resource (if any), and reset the password there ...
</code>
</script>
</function>
</functionLibrary>
Discussion
The security of this approach is improved.
Even if someone would be able to create a task with archetype trusted-functions-only-task
, the only thing they would be able to execute, is the single trusted function.
If privilege elevation is not used, and that function calls only the standard midPoint API, no real harm should be done.
In theory, if the function would be written in such a way that it would take values of its parameters and derive the executable code from them, this could still be a security hole. Fortunately, such a code probably cannot be written "by mistake". Hence, assuming that the author of the library is a trustworthy person, this approach is really safe. |
Improving the Solution Further: Limiting the Privileges Needed
In the above scenario we manipulated the features (scripting, calling library functions, and so on) that the specific code is able to use.
Assuming that standard midPoint API (e.g., midpoint
object) is used, the authorization of logged-in user is still fully applied.
So, if the script is going to execute an action like password reset for an arbitrary user, the logged-in principal must have appropriate authorizations.
This may or may not be convenient. There are situation where we want to limit the general authorizations of the respective users, and allow them to execute specific actions (like resetting AD passwords) only; and, moreover, do that only via that particular GUI action.
This is somewhat similar to "setUid" bit in Unix. We allow anyone to call a specific library function. The function will run with elevated privileges and/or under different user identity. And the function is responsible to make sure that nothing wrong happens, e.g., by strictly enforcing what individual users can and cannot do.
The following example will show such a function:
<functionLibrary xmlns="http://midpoint.evolveum.com/xml/ns/public/common/common-3"
oid="17b5b255-c71e-4a67-8e42-349862e295ac">
<name>library</name>
<function>
<name>resetAdPasswordPrivileged</name>
<parameter>
<name>user</name>
<type>UserType</type>
</parameter>
<privileges> (1)
<runPrivileged>true</runPrivileged>
</privileges>
<script>
<code>
... Groovy code to check the identity of the caller ... (2)
... Groovy code to find the projection on AD resource (if any), and reset the password there ...
</code>
</script>
</function>
</functionLibrary>
1 | Causes the function to execute with full privileges (authorizations). |
2 | Additional code that checks that only appropriate users can execute this function. |
Please see Privilege Elevation (runAsRef, runPrivileged) for more information about privilege elevation feature.
Scripting (Bulk Action) Policy Constraints and Actions
When talking about policy rules, bulk actions can be present in two places: in objectState
/assignmentState
policy constraints and in scriptExecution
policy actions.
Before 4.8, the bulk actions present in policy constraints were executed with all features (including scripts) enabled, whereas the ones present in policy actions were executed with all or limited features, depending on whether the currently logged-in user had #all
authorization.
Starting with 4.8, these actions run under an expression profile that is determined in the standard way. The defaults, however, mirror the behavior before 4.8, as described above.
An example:
<role xmlns="http://midpoint.evolveum.com/xml/ns/public/common/common-3"
xmlns:s="http://midpoint.evolveum.com/xml/ns/public/model/scripting-3"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
oid="128a5458-bcd4-4bf4-b110-664677e73aa4">
<name>role-with-bulk-actions</name>
<assignment> (1)
<targetRef oid="988c28d2-f879-4e07-a3cb-5ea7ad206146" type="ArchetypeType"/>
</assignment>
<assignment>
<policyRule>
<policyConstraints>
<objectState>
<executeScript>
<s:execute> (2)
<s:outputTypeName>xsd:boolean</s:outputTypeName>
<s:forWholeInput>true</s:forWholeInput>
<s:script>
<code>
log.info('object = {}', object)
// ...
true
</code>
</s:script>
</s:execute>
</executeScript>
</objectState>
</policyConstraints>
<policyActions>
<scriptExecution>
<executeScript>
<s:execute> (3)
<s:script>
<code>
log.info('focus = {}', focus)
log.info('input = {}', input)
// ...
</code>
</s:script>
</s:execute>
</executeScript>
</scriptExecution>
</policyActions>
</policyRule>
</assignment>
</role>
1 | An archetype providing the expression profile trusted (see right below) |
2 | The bulk action that executed (in this form) in 4.7 and earlier, because it did run with full feature set enabled. |
3 | However, this bulk action did not run in 4.7; and neither would not do in 4.8, unless the expression profile is set. |
The archetype provides the execution profile, for example:
<archetype xmlns="http://midpoint.evolveum.com/xml/ns/public/common/common-3"
oid="988c28d2-f879-4e07-a3cb-5ea7ad206146">
<name>trusted-role</name>
<archetypePolicy>
<expressionProfile>trusted</expressionProfile>
</archetypePolicy>
</archetype>
The content of policy rules in objects is generally considered trusted, because the default expression profile allows execution of arbitrary Groovy code present in these objects. (The bulk actions were a notable exception to this rule.) Hence, by providing a "fully trusted" expression profile for bulk actions should provide no harm, compared to the state before 4.8. Of course, in the future we plan to support safe definition of roles by less trusted users, where the expression profile specification will be a crucial point in ensuring the security of such a solution.