Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NIFI-4127: Composite User Group Providers #1978

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions nifi-docs/src/main/asciidoc/administration-guide.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,8 @@ The 'authorizers.xml' file is used to define and configure available authorizers
and a AccessPolicyProvider. The users, group, and access policies will be loaded and optionally configured through these providers. The managed authorizer will make all access decisions based on
these provided users, groups, and access policies.

During startup there is a check to ensure that there are no two users/groups with the same identity/name. This check is executed regardless of the configured implementation. This is necessary because this is how users/groups are identified and authorized during access decisions.

The default UserGroupProvider is the FileUserGroupProvider, however, you can develop additional UserGroupProviders as extensions. The FileUserGroupProvider has the following properties:

* Users File - The file where the FileUserGroupProvider stores users and groups. By default, the 'users.xml' in the 'conf' directory is chosen.
Expand Down Expand Up @@ -464,6 +466,17 @@ Another option for the UserGroupProvider is the LdapUserGroupProvider. By defaul
* Group Name Attribute - Attribute to use to extract group name (i.e. cn). Optional. If not set, the entire DN is used.
* Group Member Attribute - Attribute to use to define group membership (i.e. member). Optional. If not set group membership will not be calculated through the groups. Will rely on group member being defined through 'User Group Name Attribute' if set.

Another option for the UserGroupProvider are composite implementations. This means that multiple sources/implementations can be configured and composed. For instance, an admin can configure users/groups to be loaded from a file and a directory server. There are two composite implementations, one that supports multiple UserGroupProviders and one that supports multiple UserGroupProviders and a single configurable UserGroupProvider.

The CompositeUserGroupProvider will provide support for retrieving users and groups from multiple sources. The CompositeUserGroupProvider has the following properties:

* User Group Provider - The identifier of user group providers to load from. The name of each property must be unique, for example: "User Group Provider A", "User Group Provider B", "User Group Provider C" or "User Group Provider 1", "User Group Provider 2", "User Group Provider 3"

The CompositeConfigurableUserGroupProvider will provide support for retrieving users and groups from multiple sources. Additionally, a single configurable user group provider is required. Users from the configurable user group provider are configurable, however users loaded from one of the User Group Provider [unique key] will not be. The CompositeConfigurableUserGroupProvider has the following properties:

* Configurable User Group Provider - A configurable user group provider.
* User Group Provider - The identifier of user group providers to load from. The name of each property must be unique, for example: "User Group Provider A", "User Group Provider B", "User Group Provider C" or "User Group Provider 1", "User Group Provider 2", "User Group Provider 3"

The default AccessPolicyProvider is the FileAccessPolicyProvider, however, you can develop additional AccessPolicyProvider as extensions. The FileAccessPolicyProvider has the following properties:

* User Group Provider - The identifier for an User Group Provider defined above that will be used to access users and groups for use in the managed access policies.
Expand Down Expand Up @@ -621,6 +634,85 @@ Here is an example loading users and groups from LDAP but still using file based

The 'Initial Admin Identity' value would have loaded from the cn from John Smith's entry based on the 'User Identity Attribute' value.

Here is an example composite implementation loading users from LDAP and a local file. The users from LDAP will be read only while the users loaded from the file will be configurable in UI.

----
<authorizers>
<userGroupProvider>
<identifier>file-user-group-provider</identifier>
<class>org.apache.nifi.authorization.FileUserGroupProvider</class>
<property name="Users File">./conf/users.xml</property>
<property name="Legacy Authorized Users File"></property>

<property name="Initial User Identity 1">cn=nifi-node1,ou=servers,dc=example,dc=com</property>
<property name="Initial User Identity 2">cn=nifi-node2,ou=servers,dc=example,dc=com</property>
</userGroupProvider>
<userGroupProvider>
<identifier>ldap-user-group-provider</identifier>
<class>org.apache.nifi.ldap.tenants.LdapUserGroupProvider</class>
<property name="Authentication Strategy">ANONYMOUS</property>

<property name="Manager DN"></property>
<property name="Manager Password"></property>

<property name="TLS - Keystore"></property>
<property name="TLS - Keystore Password"></property>
<property name="TLS - Keystore Type"></property>
<property name="TLS - Truststore"></property>
<property name="TLS - Truststore Password"></property>
<property name="TLS - Truststore Type"></property>
<property name="TLS - Client Auth"></property>
<property name="TLS - Protocol"></property>
<property name="TLS - Shutdown Gracefully"></property>

<property name="Referral Strategy">FOLLOW</property>
<property name="Connect Timeout">10 secs</property>
<property name="Read Timeout">10 secs</property>

<property name="Url">ldap://localhost:10389</property>
<property name="Page Size"></property>
<property name="Sync Interval">30 mins</property>

<property name="User Search Base">ou=users,o=nifi</property>
<property name="User Object Class">person</property>
<property name="User Search Scope">ONE_LEVEL</property>
<property name="User Search Filter"></property>
<property name="User Identity Attribute">cn</property>
<property name="User Group Name Attribute"></property>

<property name="Group Search Base">ou=groups,o=nifi</property>
<property name="Group Object Class">groupOfNames</property>
<property name="Group Search Scope">ONE_LEVEL</property>
<property name="Group Search Filter"></property>
<property name="Group Name Attribute">cn</property>
<property name="Group Member Attribute">member</property>
</userGroupProvider>
<userGroupProvider>
<identifier>composite-user-group-provider</identifier>
<class>org.apache.nifi.authorization.CompositeConfigurableUserGroupProvider</class>
<property name="Configurable User Group Provider">file-user-group-provider</property>
<property name="User Group Provider 1">ldap-user-group-provider</property>
</userGroupProvider>
<accessPolicyProvider>
<identifier>file-access-policy-provider</identifier>
<class>org.apache.nifi.authorization.FileAccessPolicyProvider</class>
<property name="User Group Provider">composite-user-group-provider</property>
<property name="Authorizations File">./conf/authorizations.xml</property>
<property name="Initial Admin Identity">John Smith</property>
<property name="Legacy Authorized Users File"></property>

<property name="Node Identity 1">cn=nifi-node1,ou=servers,dc=example,dc=com</property>
<property name="Node Identity 2">cn=nifi-node2,ou=servers,dc=example,dc=com</property>
</accessPolicyProvider>
<authorizer>
<identifier>managed-authorizer</identifier>
<class>org.apache.nifi.authorization.StandardManagedAuthorizer</class>
<property name="Access Policy Provider">file-access-policy-provider</property>
</authorizer>
</authorizers>
----

In this example, the users and groups are loaded from LDAP but the servers are managed in a local file. The 'Initial Admin Identity' value came from an attribute in a LDAP entry based on the 'User Identity Attribute'. The 'Node Identity' values are established in the local file using the 'Initial User Identity' properties.

[[legacy-authorized-users]]
Legacy Authorized Users (NiFi Instance Upgrade)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,21 @@ public interface ConfigurableAccessPolicyProvider extends AccessPolicyProvider {
*/
AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException;

/**
* Determines whether the specified access policy is configurable. Provides the opportunity for a ConfigurableAccessPolicyProvider to prevent
* editing of a specific access policy. By default, all known access policies are configurable.
*
* @param accessPolicy the access policy
* @return is configurable
*/
default boolean isConfigurable(AccessPolicy accessPolicy) {
if (accessPolicy == null) {
throw new IllegalArgumentException("Access policy cannot be null");
}

return getAccessPolicy(accessPolicy.getIdentifier()) != null;
}

/**
* The policy represented by the provided instance will be updated based on the provided instance.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,21 @@ public interface ConfigurableUserGroupProvider extends UserGroupProvider {
*/
User addUser(User user) throws AuthorizationAccessException;

/**
* Determines whether the specified user is configurable. Provides the opportunity for a ConfigurableUserGroupProvider to prevent
* editing of a specific user. By default, all known users are configurable.
*
* @param user the user
* @return is configurable
*/
default boolean isConfigurable(User user) {
if (user == null) {
throw new IllegalArgumentException("User cannot be null");
}

return getUser(user.getIdentifier()) != null;
}

/**
* The user represented by the provided instance will be updated based on the provided instance.
*
Expand Down Expand Up @@ -94,6 +109,21 @@ public interface ConfigurableUserGroupProvider extends UserGroupProvider {
*/
Group addGroup(Group group) throws AuthorizationAccessException;

/**
* Determines whether the specified group is configurable. Provides the opportunity for a ConfigurableUserGroupProvider to prevent
* editing of a specific group. By default, all known groups are configurable.
*
* @param group the group
* @return is configurable
*/
default boolean isConfigurable(Group group) {
if (group == null) {
throw new IllegalArgumentException("Group cannot be null");
}

return getGroup(group.getIdentifier()) != null;
}

/**
* The group represented by the provided instance will be updated based on the provided instance.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ public AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws Authorizat
}
}

@Override
public boolean isConfigurable(AccessPolicy accessPolicy) {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy);
}
}

@Override
public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,35 @@ public static boolean isConfigurableUserGroupProvider(final Authorizer authorize
return accessPolicyProvider.getUserGroupProvider() instanceof ConfigurableUserGroupProvider;
}

public static boolean isUserConfigurable(final Authorizer authorizer, final User user) {
if (!isConfigurableUserGroupProvider(authorizer)) {
return false;
}

final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) managedAuthorizer.getAccessPolicyProvider().getUserGroupProvider();
return configurableUserGroupProvider.isConfigurable(user);
}

public static boolean isGroupConfigurable(final Authorizer authorizer, final Group group) {
if (!isConfigurableUserGroupProvider(authorizer)) {
return false;
}

final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) managedAuthorizer.getAccessPolicyProvider().getUserGroupProvider();
return configurableUserGroupProvider.isConfigurable(group);
}

public static boolean isAccessPolicyConfigurable(final Authorizer authorizer, final AccessPolicy accessPolicy) {
if (!isConfigurableAccessPolicyProvider(authorizer)) {
return false;
}

final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
final ConfigurableAccessPolicyProvider configurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider();
return configurableAccessPolicyProvider.isConfigurable(accessPolicy);
}

private AuthorizerCapabilityDetection() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,24 @@ public AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws Authorizat
return baseConfigurableAccessPolicyProvider.addAccessPolicy(accessPolicy);
}

@Override
public boolean isConfigurable(AccessPolicy accessPolicy) {
return baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy);
}

@Override
public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
if (!baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy)) {
throw new IllegalArgumentException("The specified access policy is not support modification.");
}
return baseConfigurableAccessPolicyProvider.updateAccessPolicy(accessPolicy);
}

@Override
public AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
if (!baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy)) {
throw new IllegalArgumentException("The specified access policy is not support modification.");
}
return baseConfigurableAccessPolicyProvider.deleteAccessPolicy(accessPolicy);
}

Expand Down Expand Up @@ -170,16 +181,27 @@ public User addUser(User user) throws AuthorizationAccessException {
return baseConfigurableUserGroupProvider.addUser(user);
}

@Override
public boolean isConfigurable(User user) {
return baseConfigurableUserGroupProvider.isConfigurable(user);
}

@Override
public User updateUser(User user) throws AuthorizationAccessException {
if (tenantExists(baseConfigurableUserGroupProvider, user.getIdentifier(), user.getIdentity())) {
throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity()));
}
if (!baseConfigurableUserGroupProvider.isConfigurable(user)) {
throw new IllegalArgumentException("The specified user does not support modification.");
}
return baseConfigurableUserGroupProvider.updateUser(user);
}

@Override
public User deleteUser(User user) throws AuthorizationAccessException {
if (!baseConfigurableUserGroupProvider.isConfigurable(user)) {
throw new IllegalArgumentException("The specified user does not support modification.");
}
return baseConfigurableUserGroupProvider.deleteUser(user);
}

Expand All @@ -191,16 +213,27 @@ public Group addGroup(Group group) throws AuthorizationAccessException {
return baseConfigurableUserGroupProvider.addGroup(group);
}

@Override
public boolean isConfigurable(Group group) {
return baseConfigurableUserGroupProvider.isConfigurable(group);
}

@Override
public Group updateGroup(Group group) throws AuthorizationAccessException {
if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) {
throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName()));
}
if (!baseConfigurableUserGroupProvider.isConfigurable(group)) {
throw new IllegalArgumentException("The specified group does not support modification.");
}
return baseConfigurableUserGroupProvider.updateGroup(group);
}

@Override
public Group deleteGroup(Group group) throws AuthorizationAccessException {
if (!baseConfigurableUserGroupProvider.isConfigurable(group)) {
throw new IllegalArgumentException("The specified group does not support modification.");
}
return baseConfigurableUserGroupProvider.deleteGroup(group);
}

Expand Down
Loading