This is an automated email from the ASF dual-hosted git repository.

mosermw pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new 3ba40076ae NIFI-14391 Support to initiate administrative access for 
group of users
3ba40076ae is described below

commit 3ba40076aefa8428ba29ab35e797cabb732e73af
Author: EndzeitBegins <[email protected]>
AuthorDate: Thu Apr 10 15:15:59 2025 +0200

    NIFI-14391 Support to initiate administrative access for group of users
    
    Signed-off-by: Mike Moser <[email protected]>
    This closes #9868
---
 nifi-docker/dockerhub/README.md                    |   8 +-
 nifi-docker/dockerhub/sh/secure.sh                 |   2 +
 .../src/main/asciidoc/administration-guide.adoc    |  13 +-
 .../authorization/FileAccessPolicyProvider.java    |  84 +++++++++-
 .../nifi/authorization/FileUserGroupProvider.java  |  78 ++++++---
 .../apache/nifi/authorization/IdentifierUtil.java  |  35 ----
 .../FileAccessPolicyProviderTest.java              | 182 ++++++++++++++++++++-
 .../authorization/FileUserGroupProviderTest.java   |  53 +++++-
 .../src/main/resources/conf/authorizers.xml        |  21 ++-
 9 files changed, 402 insertions(+), 74 deletions(-)

diff --git a/nifi-docker/dockerhub/README.md b/nifi-docker/dockerhub/README.md
index a3e8388ae5..825b1fc9f8 100644
--- a/nifi-docker/dockerhub/README.md
+++ b/nifi-docker/dockerhub/README.md
@@ -144,7 +144,7 @@ In this configuration, the user will need to provide 
certificates and associated
 if the LDAP provider of interest is operating in LDAPS or START_TLS modes, 
certificates will additionally be needed.
 Of particular note, is the `AUTH` environment variable which is set to `ldap`. 
 Additionally, the user must provide a
 DN as provided by the configured LDAP server in the `INITIAL_ADMIN_IDENTITY` 
environment variable. This value will be
-used to seed the instance with an initial user with administrative privileges. 
 Finally, this command makes use of a
+used to seed the instance with an initial user with administrative privileges. 
Finally, this command makes use of a
 volume to provide certificates on the host system to the container instance.
 
 #### For a minimal, connection to an LDAP server using SIMPLE authentication:
@@ -183,7 +183,8 @@ volume to provide certificates on the host system to the 
container instance.
 In this configuration, the user will need to provide certificates and 
associated configuration information. 
 Of particular note, is the `AUTH` environment variable which is set to `oidc`. 
Additionally, the user must provide a
 in the `INITIAL_ADMIN_IDENTITY` environment variable. This value will be used 
to seed the instance with an initial 
-user with administrative privileges.
+user with administrative privileges. Alternatively, the `INITIAL_ADMIN_GROUP` 
environment variable can be specified 
+to grant access to a group of users instead.
 
 ### For a minimal, connection to an OpenID server
 
@@ -198,6 +199,7 @@ user with administrative privileges.
       -e TRUSTSTORE_PASSWORD=rHkWR1gDNW3R9hgbeRsT3OM3Ue0zwGtQqcFKJD2EXWE \
       -e TRUSTSTORE_TYPE=JKS \
       -e INITIAL_ADMIN_IDENTITY='test' \
+      -e INITIAL_ADMIN_GROUP='myGroup' \
       -e 
NIFI_SECURITY_USER_OIDC_DISCOVERY_URL=http://OPENID_SERVER_URL/auth/realms/OPENID_REALM/.well-known/openid-configuration
 \
       -e NIFI_SECURITY_USER_OIDC_CONNECT_TIMEOUT=10000 \
       -e NIFI_SECURITY_USER_OIDC_READ_TIMEOUT=10000 \
@@ -214,7 +216,7 @@ user with administrative privileges.
       apache/nifi:latest
 
 - Make sure you've created realm, client and user in OpenID Server before with 
the same user name defined in `INITIAL_ADMIN_IDENTITY` environment variable
-- You can read more information about theses Nifi security OIDC configurations 
in this following link: 
[https://nifi.apache.org/docs/nifi-docs/html/administration-guide.html#openid_connect](https://nifi.apache.org/docs/nifi-docs/html/administration-guide.html#openid_connect)
+- You can read more information about these Nifi security OIDC configurations 
in this following link: 
[https://nifi.apache.org/docs/nifi-docs/html/administration-guide.html#openid_connect](https://nifi.apache.org/docs/nifi-docs/html/administration-guide.html#openid_connect)
 
 #### Clustering can be enabled by using the following properties to Docker 
environment variable mappings.
 
diff --git a/nifi-docker/dockerhub/sh/secure.sh 
b/nifi-docker/dockerhub/sh/secure.sh
index 4fff214e8c..62b2eec092 100755
--- a/nifi-docker/dockerhub/sh/secure.sh
+++ b/nifi-docker/dockerhub/sh/secure.sh
@@ -73,6 +73,8 @@ prop_replace 'nifi.security.user.login.identity.provider' 
"${NIFI_SECURITY_USER_
 # Establish initial user and an associated admin identity
 sed -i -e 's|<property name="Initial User Identity 1"></property>|<property 
name="Initial User Identity 1">'"${INITIAL_ADMIN_IDENTITY}"'</property>|'  
${NIFI_HOME}/conf/authorizers.xml
 sed -i -e 's|<property name="Initial Admin Identity"></property>|<property 
name="Initial Admin Identity">'"${INITIAL_ADMIN_IDENTITY}"'</property>|'  
${NIFI_HOME}/conf/authorizers.xml
+sed -i -e 's|<property name="Initial Group Identity 1"></property>|<property 
name="Initial Group Identity 1">'"${INITIAL_ADMIN_GROUP}"'</property>|'  
${NIFI_HOME}/conf/authorizers.xml
+sed -i -e 's|<property name="Initial Admin Group"></property>|<property 
name="Initial Admin Group">'"${INITIAL_ADMIN_GROUP}"'</property>|'  
${NIFI_HOME}/conf/authorizers.xml
 
 if [ -n "${NODE_IDENTITY}" ]; then
     sed -i -e 's|<property name="Node Identity 1"></property>|<property 
name="Node Identity 1">'"${NODE_IDENTITY}"'</property>|'  
${NIFI_HOME}/conf/authorizers.xml
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc 
b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index f8e5a8eb50..01e89e9f6d 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -795,7 +795,8 @@ The default UserGroupProvider is the FileUserGroupProvider, 
however, you can dev
 
 * Users File - The file where the FileUserGroupProvider stores users and 
groups.  By default, the _users.xml_ in the `conf` directory is chosen.
 * Legacy Authorized Users File - The full path to an existing 
_authorized-users.xml_ that will be automatically be used to load the users and 
groups into the Users File.
-* Initial User Identity - The identity of a users and systems to seed the 
Users File. The name of each property must be unique, for example: "Initial 
User Identity A", "Initial User Identity B", "Initial User Identity C" or 
"Initial User Identity 1", "Initial User Identity 2", "Initial User Identity 3"
+* Initial User Identity - The identity of a user or system to seed the Users 
File. The name of each property must be unique, for example: "Initial User 
Identity A", "Initial User Identity B", "Initial User Identity C" or "Initial 
User Identity 1", "Initial User Identity 2", "Initial User Identity 3"
+* Initial Group Identity - The identity of a user group to seed the Users 
File. The name of each property must be unique, for example: "Initial Group 
Identity A", "Initial Group Identity B", "Initial Group Identity C" or "Initial 
Group Identity 1", "Initial Group Identity 2", "Initial Group Identity 3"
 
 ==== LdapUserGroupProvider
 
@@ -911,14 +912,15 @@ The default AccessPolicyProvider is the 
FileAccessPolicyProvider, however, you c
 | Property Name | Description
 |`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.
 |`Authorizations File` | The file where the FileAccessPolicyProvider will 
store policies.
-|`Initial Admin Identity` | The identity of an initial admin user that will be 
granted access to the UI and given the ability to create additional users, 
groups, and policies. The value of this property could be a DN when using 
certificates or LDAP, or a Kerberos principal. This property will only be used 
when there are no other policies defined. If this property is specified then a 
Legacy Authorized Users File can not be specified.
-|`Legacy Authorized Users File` | The full path to an existing 
_authorized-users.xml_ that will be automatically converted to the new 
authorizations model. If this property is specified then an Initial Admin 
Identity can not be specified, and this property will only be used when there 
are no other users, groups, and policies defined.
+|`Initial Admin Identity` | The identity of an initial admin user that will be 
granted access to the UI and given the ability to create additional users, 
groups, and policies. The value of this property could be a DN when using 
certificates or LDAP, or a Kerberos principal. This property will only be used 
when there are no other policies defined. If this property is specified then a 
Legacy Authorized Users File can not be specified. If the property `Initial 
Admin Group` is specified as w [...]
+|`Initial Admin Group` | The identity of an initial admin group that will be 
granted access to the UI and given the ability to create additional users, 
groups, and policies. The value of this property could be a DN when using 
certificates or LDAP, or a Kerberos principal. This property will only be used 
when there are no other policies defined. If this property is specified then a 
Legacy Authorized Users File can not be specified.
+|`Legacy Authorized Users File` | The full path to an existing 
_authorized-users.xml_ that will be automatically converted to the new 
authorizations model. If this property is specified then an Initial Admin 
Identity or Initial Admin Group can not be specified, and this property will 
only be used when there are no other users, groups, and policies defined.
 |`Node Identity` | The identity of a NiFi cluster node. When clustered, a 
property for each node should be defined, so that every node knows about every 
other node. If not clustered these properties can be ignored. The name of each 
property must be unique, for example for a three node cluster: "Node Identity 
A", "Node Identity B", "Node Identity C" or "Node Identity 1", "Node Identity 
2", "Node Identity 3"
 |`Node Group` | The name of a group containing NiFi cluster nodes. The typical 
use for this is when nodes are dynamically added/removed from the cluster.
 
 
|==================================================================================================================================================
 
-NOTE: The identities configured in the Initial Admin Identity, the Node 
Identity properties, or discovered in a Legacy Authorized Users File must be 
available in the configured User Group Provider.
+NOTE: The identities configured in the Initial Admin Identity, Initial Admin 
Group, the Node Identity properties, or discovered in a Legacy Authorized Users 
File must be available in the configured User Group Provider.
 
 NOTE: Any users in the legacy users file must be found in the configured User 
Group Provider.
 
@@ -959,6 +961,7 @@ NOTE: Any identity mapping rules specified in 
_nifi.properties_ will also be app
 ==== Initial Admin Identity  (New NiFi Instance)
 
 If you are setting up a secured NiFi instance for the first time, you must 
manually designate an “Initial Admin Identity” in the _authorizers.xml_ file.  
This initial admin user is granted access to the UI and given the ability to 
create additional users, groups, and policies. The value of this property could 
be a DN (when using certificates or LDAP) or a Kerberos principal.  If you are 
the NiFi administrator, add yourself as the “Initial Admin Identity”.
+Alternatively, specifying an “Initial Admin Group” grants administrative 
access to a group of users, mitigating dependence on a single person or the 
need for a shared account.
 
 After you have edited and saved the _authorizers.xml_ file, restart NiFi.  The 
“Initial Admin Identity” user and administrative policies are added to the 
_users.xml_ and _authorizations.xml_ files during restart. Once NiFi starts, 
the “Initial Admin Identity” user is able to access the UI and begin managing 
users, groups, and policies.
 
@@ -1399,7 +1402,7 @@ The following tables summarize the global and component 
policies assigned to eac
 
 For details on the individual policies in the table, see <<access-policies>>.
 
-NOTE: NiFi fails to restart if values exist for both the `Initial Admin 
Identity` and `Legacy Authorized Users File` properties.  You can specify only 
one of these values to initialize authorizations.
+NOTE: NiFi fails to restart if values exist for both the `Initial Admin 
Identity` (or `Initial Admin Group`) and `Legacy Authorized Users File` 
properties.  You can specify only one of these values to initialize 
authorizations.
 
 NOTE: Do not manually edit the _authorizations.xml_ file. Create 
authorizations only during initial setup and afterwards using the NiFi UI.
 
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java
 
b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java
index 5363bb5619..9ab8de7aaa 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java
@@ -16,6 +16,11 @@
  */
 package org.apache.nifi.authorization;
 
+import jakarta.xml.bind.JAXBContext;
+import jakarta.xml.bind.JAXBElement;
+import jakarta.xml.bind.JAXBException;
+import jakarta.xml.bind.Marshaller;
+import jakarta.xml.bind.Unmarshaller;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.authorization.annotation.AuthorizerContext;
 import org.apache.nifi.authorization.exception.AuthorizationAccessException;
@@ -46,11 +51,6 @@ import org.w3c.dom.NodeList;
 import org.xml.sax.SAXException;
 
 import javax.xml.XMLConstants;
-import jakarta.xml.bind.JAXBContext;
-import jakarta.xml.bind.JAXBElement;
-import jakarta.xml.bind.JAXBException;
-import jakarta.xml.bind.Marshaller;
-import jakarta.xml.bind.Unmarshaller;
 import javax.xml.stream.XMLOutputFactory;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamReader;
@@ -114,6 +114,7 @@ public class FileAccessPolicyProvider implements 
ConfigurableAccessPolicyProvide
     static final String PROP_USER_GROUP_PROVIDER = "User Group Provider";
     static final String PROP_AUTHORIZATIONS_FILE = "Authorizations File";
     static final String PROP_INITIAL_ADMIN_IDENTITY = "Initial Admin Identity";
+    static final String PROP_INITIAL_ADMIN_GROUP = "Initial Admin Group";
     static final Pattern NODE_IDENTITY_PATTERN = 
Pattern.compile(PROP_NODE_IDENTITY_PREFIX + "\\S+");
 
     private Schema authorizationsSchema;
@@ -122,6 +123,7 @@ public class FileAccessPolicyProvider implements 
ConfigurableAccessPolicyProvide
     private File restoreAuthorizationsFile;
     private String rootGroupId;
     private String initialAdminIdentity;
+    private String initialAdminGroup;
     private Set<String> nodeIdentities;
     private String nodeGroupIdentifier;
 
@@ -198,6 +200,10 @@ public class FileAccessPolicyProvider implements 
ConfigurableAccessPolicyProvide
             final PropertyValue initialAdminIdentityProp = 
configurationContext.getProperty(PROP_INITIAL_ADMIN_IDENTITY);
             initialAdminIdentity = initialAdminIdentityProp.isSet() ? 
IdentityMappingUtil.mapIdentity(initialAdminIdentityProp.getValue(), 
identityMappings) : null;
 
+            // get the value of the initial admin group
+            final PropertyValue initialAdminGroupProp = 
configurationContext.getProperty(PROP_INITIAL_ADMIN_GROUP);
+            initialAdminGroup = initialAdminGroupProp.isSet() ? 
IdentityMappingUtil.mapIdentity(initialAdminGroupProp.getValue(), 
identityMappings) : null;
+
             // extract any node identities
             nodeIdentities = new HashSet<>();
             for (Map.Entry<String, String> entry : 
configurationContext.getProperties().entrySet()) {
@@ -566,14 +572,19 @@ public class FileAccessPolicyProvider implements 
ConfigurableAccessPolicyProvide
         final AuthorizationsHolder authorizationsHolder = new 
AuthorizationsHolder(authorizations);
         final boolean emptyAuthorizations = 
authorizationsHolder.getAllPolicies().isEmpty();
         final boolean hasInitialAdminIdentity = (initialAdminIdentity != null 
&& !StringUtils.isBlank(initialAdminIdentity));
+        final boolean hasInitialAdminGroup = (initialAdminGroup != null && 
!StringUtils.isBlank(initialAdminGroup));
 
-        // if we are starting fresh then we might need to populate an initial 
admin or convert legacy users
+        // if we are starting fresh then we might need to populate an initial 
admin, admin group or convert legacy users
         if (emptyAuthorizations) {
             parseFlow();
 
             if (hasInitialAdminIdentity) {
                 logger.info("Populating authorizations for Initial Admin: {}", 
initialAdminIdentity);
-                populateInitialAdmin(authorizations);
+                populateInitialAdmin(authorizations, hasInitialAdminGroup);
+            }
+            if (hasInitialAdminGroup) {
+                logger.info("Populating authorizations for Initial Admin 
Group: {}", initialAdminGroup);
+                populateInitialAdminGroup(authorizations);
             }
 
             populateNodes(authorizations);
@@ -625,14 +636,32 @@ public class FileAccessPolicyProvider implements 
ConfigurableAccessPolicyProvide
     }
 
     /**
-     *  Creates the initial admin user and policies for access the flow and 
managing users and policies.
+     *  Grants the initial admin user the policies for accessing the flow and 
managing users and policies.
+     * <p>
+     *  Either by creating the policies for the user itself or by making it a 
member of the initial admin group.
      */
-    private void populateInitialAdmin(final Authorizations authorizations) {
+    private void populateInitialAdmin(final Authorizations authorizations, 
boolean hasInitialAdminGroup) {
         final User initialAdmin = 
userGroupProvider.getUserByIdentity(initialAdminIdentity);
         if (initialAdmin == null) {
             throw new AuthorizerCreationException("Unable to locate initial 
admin " + initialAdminIdentity + " to seed policies");
         }
 
+        if (hasInitialAdminGroup && userGroupProvider instanceof 
ConfigurableUserGroupProvider configurableProvider) {
+            final Group initialAdminGroup = 
userGroupProvider.getGroupByName(this.initialAdminGroup);
+            if (initialAdminGroup == null) {
+                throw new AuthorizerCreationException("Unable to locate 
initial admin group " + this.initialAdminGroup + " to seed policies");
+            }
+
+            if (configurableProvider.isConfigurable(initialAdminGroup)) {
+                final Group updatedAdminGroup = new 
Group.Builder(initialAdminGroup)
+                        .addUser(initialAdmin.getIdentifier())
+                        .build();
+
+                configurableProvider.updateGroup(updatedAdminGroup);
+                return; // user has access through membership; no need to add 
policies to the user explicitly
+            }
+        }
+
         // grant the user read access to the /flow resource
         addUserToAccessPolicy(authorizations, ResourceType.Flow.getValue(), 
initialAdmin.getIdentifier(), READ_CODE);
 
@@ -661,6 +690,43 @@ public class FileAccessPolicyProvider implements 
ConfigurableAccessPolicyProvide
         addUserToAccessPolicy(authorizations, 
ResourceType.Controller.getValue(), initialAdmin.getIdentifier(), WRITE_CODE);
     }
 
+    /**
+     *  Grants the initial admin group the policies for accessing the flow and 
managing users and policies.
+     */
+    private void populateInitialAdminGroup(final Authorizations 
authorizations) {
+        final Group initialAdminGroup = 
userGroupProvider.getGroupByName(this.initialAdminGroup);
+        if (initialAdminGroup == null) {
+            throw new AuthorizerCreationException("Unable to locate initial 
admin group " + this.initialAdminGroup + " to seed policies");
+        }
+
+        // grant the group read access to the /flow resource
+        addGroupToAccessPolicy(authorizations, ResourceType.Flow.getValue(), 
initialAdminGroup.getIdentifier(), READ_CODE);
+
+        // grant the group read access to the root process group resource
+        if (rootGroupId != null) {
+            addGroupToAccessPolicy(authorizations, 
ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + 
rootGroupId, initialAdminGroup.getIdentifier(), READ_CODE);
+            addGroupToAccessPolicy(authorizations, 
ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + 
rootGroupId, initialAdminGroup.getIdentifier(), WRITE_CODE);
+
+            addGroupToAccessPolicy(authorizations, 
ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, 
initialAdminGroup.getIdentifier(), READ_CODE);
+            addGroupToAccessPolicy(authorizations, 
ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, 
initialAdminGroup.getIdentifier(), WRITE_CODE);
+        }
+
+        // grant the group write to restricted components
+        addGroupToAccessPolicy(authorizations, 
ResourceType.RestrictedComponents.getValue(), 
initialAdminGroup.getIdentifier(), WRITE_CODE);
+
+        // grant the group read/write access to the /tenants resource
+        addGroupToAccessPolicy(authorizations, ResourceType.Tenant.getValue(), 
initialAdminGroup.getIdentifier(), READ_CODE);
+        addGroupToAccessPolicy(authorizations, ResourceType.Tenant.getValue(), 
initialAdminGroup.getIdentifier(), WRITE_CODE);
+
+        // grant the group read/write access to the /policies resource
+        addGroupToAccessPolicy(authorizations, ResourceType.Policy.getValue(), 
initialAdminGroup.getIdentifier(), READ_CODE);
+        addGroupToAccessPolicy(authorizations, ResourceType.Policy.getValue(), 
initialAdminGroup.getIdentifier(), WRITE_CODE);
+
+        // grant the group read/write access to the /controller resource
+        addGroupToAccessPolicy(authorizations, 
ResourceType.Controller.getValue(), initialAdminGroup.getIdentifier(), 
READ_CODE);
+        addGroupToAccessPolicy(authorizations, 
ResourceType.Controller.getValue(), initialAdminGroup.getIdentifier(), 
WRITE_CODE);
+    }
+
     /**
      * Creates a user for each node and gives the nodes write permission to 
/proxy.
      *
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileUserGroupProvider.java
 
b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileUserGroupProvider.java
index c56dce10ca..c72e8b38a2 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileUserGroupProvider.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileUserGroupProvider.java
@@ -16,6 +16,11 @@
  */
 package org.apache.nifi.authorization;
 
+import jakarta.xml.bind.JAXBContext;
+import jakarta.xml.bind.JAXBElement;
+import jakarta.xml.bind.JAXBException;
+import jakarta.xml.bind.Marshaller;
+import jakarta.xml.bind.Unmarshaller;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.authorization.annotation.AuthorizerContext;
 import org.apache.nifi.authorization.exception.AuthorizationAccessException;
@@ -42,11 +47,6 @@ import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
 import javax.xml.XMLConstants;
-import jakarta.xml.bind.JAXBContext;
-import jakarta.xml.bind.JAXBElement;
-import jakarta.xml.bind.JAXBException;
-import jakarta.xml.bind.Marshaller;
-import jakarta.xml.bind.Unmarshaller;
 import javax.xml.stream.XMLOutputFactory;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamReader;
@@ -70,7 +70,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
-import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 public class FileUserGroupProvider implements ConfigurableUserGroupProvider {
@@ -101,15 +100,18 @@ public class FileUserGroupProvider implements 
ConfigurableUserGroupProvider {
     private static final String IDENTITY_ATTR = "identity";
     private static final String NAME_ATTR = "name";
 
-    static final String PROP_INITIAL_USER_IDENTITY_PREFIX = "Initial User 
Identity ";
     static final String PROP_TENANTS_FILE = "Users File";
+    static final String PROP_INITIAL_USER_IDENTITY_PREFIX = "Initial User 
Identity ";
     static final Pattern INITIAL_USER_IDENTITY_PATTERN = 
Pattern.compile(PROP_INITIAL_USER_IDENTITY_PREFIX + "\\S+");
+    static final String PROP_INITIAL_GROUP_IDENTITY_PREFIX = "Initial Group 
Identity ";
+    static final Pattern INITIAL_GROUP_IDENTITY_PATTERN = 
Pattern.compile(PROP_INITIAL_GROUP_IDENTITY_PREFIX + "\\S+");
 
     private Schema tenantsSchema;
     private NiFiProperties properties;
     private File tenantsFile;
     private File restoreTenantsFile;
     private Set<String> initialUserIdentities;
+    private Set<String> initialGroupIdentities;
 
     private final AtomicReference<UserGroupHolder> userGroupHolder = new 
AtomicReference<>();
 
@@ -168,10 +170,19 @@ public class FileUserGroupProvider implements 
ConfigurableUserGroupProvider {
 
             // extract any node identities
             initialUserIdentities = new HashSet<>();
+            initialGroupIdentities = new HashSet<>();
             for (Map.Entry<String, String> entry : 
configurationContext.getProperties().entrySet()) {
-                Matcher matcher = 
INITIAL_USER_IDENTITY_PATTERN.matcher(entry.getKey());
-                if (matcher.matches() && 
!StringUtils.isBlank(entry.getValue())) {
-                    
initialUserIdentities.add(IdentityMappingUtil.mapIdentity(entry.getValue(), 
identityMappings));
+                if 
(INITIAL_USER_IDENTITY_PATTERN.matcher(entry.getKey()).matches()) {
+                    if (StringUtils.isNotBlank(entry.getValue())) {
+                        
initialUserIdentities.add(IdentityMappingUtil.mapIdentity(entry.getValue(), 
identityMappings));
+                    }
+                    continue;
+                }
+
+                if 
(INITIAL_GROUP_IDENTITY_PATTERN.matcher(entry.getKey()).matches()) {
+                    if (StringUtils.isNotBlank(entry.getValue())) {
+                        
initialGroupIdentities.add(IdentityMappingUtil.mapIdentity(entry.getValue(), 
identityMappings));
+                    }
                 }
             }
 
@@ -670,6 +681,7 @@ public class FileUserGroupProvider implements 
ConfigurableUserGroupProvider {
 
         if (emptyTenants) {
             populateInitialUsers(tenants);
+            populateInitialGroups(tenants);
 
             // save any changes that were made and repopulate the holder
             saveAndRefreshHolder(tenants);
@@ -709,6 +721,12 @@ public class FileUserGroupProvider implements 
ConfigurableUserGroupProvider {
         }
     }
 
+    private void populateInitialGroups(final Tenants tenants) {
+        for (String initialGroupIdentity : initialGroupIdentities) {
+            createGroup(tenants, initialGroupIdentity);
+        }
+    }
+
     /**
      * Finds the User with the given identity, or creates a new one and adds 
it to the Tenants.
      *
@@ -720,21 +738,43 @@ public class FileUserGroupProvider implements 
ConfigurableUserGroupProvider {
             return;
         }
 
-        org.apache.nifi.authorization.file.tenants.generated.User foundUser = 
null;
         for (org.apache.nifi.authorization.file.tenants.generated.User user : 
tenants.getUsers().getUser()) {
             if (user.getIdentity().equals(userIdentity)) {
-                foundUser = user;
-                break;
+                return;
             }
         }
 
-        if (foundUser == null) {
-            final String userIdentifier = 
IdentifierUtil.getIdentifier(userIdentity);
-            foundUser = new 
org.apache.nifi.authorization.file.tenants.generated.User();
-            foundUser.setIdentifier(userIdentifier);
-            foundUser.setIdentity(userIdentity);
-            tenants.getUsers().getUser().add(foundUser);
+        final User builtUser = new 
User.Builder().identifierGenerateFromSeed(userIdentity).identity(userIdentity).build();
+        final org.apache.nifi.authorization.file.tenants.generated.User 
newUser =
+                new 
org.apache.nifi.authorization.file.tenants.generated.User();
+        newUser.setIdentifier(builtUser.getIdentifier());
+        newUser.setIdentity(builtUser.getIdentity());
+        tenants.getUsers().getUser().add(newUser);
+    }
+
+    /**
+     * Finds the Group with the given name, or creates a new one and adds it 
to Tenants.
+     *
+     * @param tenants the Tenants reference
+     * @param groupName the name of the group to look for
+     */
+    private void createGroup(final Tenants tenants, final String groupName) {
+        if (StringUtils.isBlank(groupName)) {
+            return;
         }
+
+        for (org.apache.nifi.authorization.file.tenants.generated.Group group 
: tenants.getGroups().getGroup()) {
+            if (group.getName().equals(groupName)) {
+                return;
+            }
+        }
+
+        final Group builtGroup = new 
Group.Builder().identifierGenerateFromSeed(groupName).name(groupName).build();
+        final org.apache.nifi.authorization.file.tenants.generated.Group 
newGroup =
+                new 
org.apache.nifi.authorization.file.tenants.generated.Group();
+        newGroup.setIdentifier(builtGroup.getIdentifier());
+        newGroup.setName(builtGroup.getName());
+        tenants.getGroups().getGroup().add(newGroup);
     }
 
     /**
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/IdentifierUtil.java
 
b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/IdentifierUtil.java
deleted file mode 100644
index 0d4ec0764d..0000000000
--- 
a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/IdentifierUtil.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.authorization;
-
-import org.apache.nifi.util.StringUtils;
-
-import java.nio.charset.StandardCharsets;
-import java.util.UUID;
-
-public final class IdentifierUtil {
-
-    static String getIdentifier(final String seed) {
-        if (StringUtils.isBlank(seed)) {
-            return null;
-        }
-
-        return 
UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString();
-    }
-
-    private IdentifierUtil() { }
-}
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAccessPolicyProviderTest.java
 
b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAccessPolicyProviderTest.java
index 4c4445a54d..2b436d5402 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAccessPolicyProviderTest.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAccessPolicyProviderTest.java
@@ -33,10 +33,12 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -187,6 +189,7 @@ public class FileAccessPolicyProviderTest {
             ParameterLookup.EMPTY));
         
when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_TENANTS_FILE))).thenReturn(new
 StandardPropertyValue(primaryTenants.getPath(), null, ParameterLookup.EMPTY));
         
when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY))).thenReturn(new
 StandardPropertyValue(null, null, ParameterLookup.EMPTY));
+        
when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_GROUP))).thenReturn(new
 StandardPropertyValue(null, null, ParameterLookup.EMPTY));
         
when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_USER_GROUP_PROVIDER))).thenReturn(new
 StandardPropertyValue("user-group-provider", null,
             ParameterLookup.EMPTY));
         when(configurationContext.getProperties()).then((invocation) -> {
@@ -207,6 +210,11 @@ public class FileAccessPolicyProviderTest {
                 
properties.put(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY, 
initialAdmin.getValue());
             }
 
+            final PropertyValue initialAdminGroup = 
configurationContext.getProperty(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_GROUP);
+            if (initialAdminGroup != null) {
+                
properties.put(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_GROUP, 
initialAdminGroup.getValue());
+            }
+
             int i = 1;
             while (true) {
                 final String key = 
FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + i++;
@@ -229,6 +237,17 @@ public class FileAccessPolicyProviderTest {
                 }
             }
 
+            i = 1;
+            while (true) {
+                final String key = 
FileUserGroupProvider.PROP_INITIAL_GROUP_IDENTITY_PREFIX + i++;
+                final PropertyValue value = 
configurationContext.getProperty(key);
+                if (value == null) {
+                    break;
+                } else {
+                    properties.put(key, value.getValue());
+                }
+            }
+
             // ensure the initial admin is seeded into the user provider if 
appropriate
             if 
(properties.containsKey(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)) {
                 i = 0;
@@ -241,6 +260,18 @@ public class FileAccessPolicyProviderTest {
                 }
             }
 
+            // ensure the initial admin group is seeded into the user provider 
if appropriate
+            if 
(properties.containsKey(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_GROUP)) {
+                i = 0;
+                while (true) {
+                    final String key = 
FileUserGroupProvider.PROP_INITIAL_GROUP_IDENTITY_PREFIX + i++;
+                    if (!properties.containsKey(key)) {
+                        properties.put(key, 
properties.get(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_GROUP));
+                        break;
+                    }
+                }
+            }
+
             return properties;
         });
 
@@ -350,7 +381,7 @@ public class FileAccessPolicyProviderTest {
         // setup NiFi properties to return a file that does not exist
         properties = mock(NiFiProperties.class);
         
when(properties.getRestoreDirectory()).thenReturn(restoreAuthorizations.getParentFile());
-        when(properties.getFlowConfigurationFile()).thenReturn(new 
File("src/test/resources/does-not-exist.json.gz"));
+        when(properties.getFlowConfigurationFile()).thenReturn(null);
 
         userGroupProvider.setNiFiProperties(properties);
         accessPolicyProvider.setNiFiProperties(properties);
@@ -385,6 +416,155 @@ public class FileAccessPolicyProviderTest {
         assertFalse(foundRootGroupPolicy);
     }
 
+    @Test
+    public void testOnConfiguredWhenInitialAdminGroupProvided() throws 
Exception {
+        final String adminGroupName = "admin-group";
+
+        
when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_GROUP)))
+                .thenReturn(new StandardPropertyValue(adminGroupName, null, 
ParameterLookup.EMPTY));
+
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        final Set<Group> groups = userGroupProvider.getGroups();
+        final Group adminGroup = groups.iterator().next();
+        assertEquals(adminGroupName, adminGroup.getName());
+
+        final Set<AccessPolicy> policies = 
accessPolicyProvider.getAccessPolicies();
+        assertEquals(12, policies.size());
+
+        final String rootGroupResource = ResourceType.ProcessGroup.getValue() 
+ "/" + ROOT_GROUP_ID;
+
+        boolean foundRootGroupPolicy = false;
+        for (AccessPolicy policy : policies) {
+            if (policy.getResource().equals(rootGroupResource)) {
+                foundRootGroupPolicy = true;
+                break;
+            }
+        }
+
+        assertTrue(foundRootGroupPolicy);
+    }
+
+    @Test
+    public void testOnConfiguredWhenInitialAdminGroupProvidedAndNoFlowExists() 
throws Exception {
+        // setup NiFi properties to return a file that does not exist
+        properties = mock(NiFiProperties.class);
+        
when(properties.getRestoreDirectory()).thenReturn(restoreAuthorizations.getParentFile());
+        when(properties.getFlowConfigurationFile()).thenReturn(new 
File("src/test/resources/does-not-exist.json.gz"));
+
+        userGroupProvider.setNiFiProperties(properties);
+        accessPolicyProvider.setNiFiProperties(properties);
+
+        final String adminGroupName = "admin-group";
+
+        
when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_GROUP)))
+                .thenReturn(new StandardPropertyValue(adminGroupName, null, 
ParameterLookup.EMPTY));
+
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        final Set<Group> groups = userGroupProvider.getGroups();
+        final Group adminGroup = groups.iterator().next();
+        assertEquals(adminGroupName, adminGroup.getName());
+
+        final Set<AccessPolicy> policies = 
accessPolicyProvider.getAccessPolicies();
+        assertEquals(8, policies.size());
+
+        final String rootGroupResource = ResourceType.ProcessGroup.getValue() 
+ "/" + ROOT_GROUP_ID;
+
+        boolean foundRootGroupPolicy = false;
+        for (AccessPolicy policy : policies) {
+            if (policy.getResource().equals(rootGroupResource)) {
+                foundRootGroupPolicy = true;
+                break;
+            }
+        }
+
+        assertFalse(foundRootGroupPolicy);
+    }
+
+    @Test
+    public void testOnConfiguredWhenInitialAdminGroupProvidedAndFlowIsNull() 
throws Exception {
+        // setup NiFi properties to return a file that does not exist
+        properties = mock(NiFiProperties.class);
+        
when(properties.getRestoreDirectory()).thenReturn(restoreAuthorizations.getParentFile());
+        when(properties.getFlowConfigurationFile()).thenReturn(null);
+
+        userGroupProvider.setNiFiProperties(properties);
+        accessPolicyProvider.setNiFiProperties(properties);
+
+        final String adminGroupName = "admin-group";
+
+        
when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_GROUP)))
+                .thenReturn(new StandardPropertyValue(adminGroupName, null, 
ParameterLookup.EMPTY));
+
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        final Set<Group> groups = userGroupProvider.getGroups();
+        final Group adminGroup = groups.iterator().next();
+        assertEquals(adminGroupName, adminGroup.getName());
+
+        final Set<AccessPolicy> policies = 
accessPolicyProvider.getAccessPolicies();
+        assertEquals(8, policies.size());
+
+        final String rootGroupResource = ResourceType.ProcessGroup.getValue() 
+ "/" + ROOT_GROUP_ID;
+
+        boolean foundRootGroupPolicy = false;
+        for (AccessPolicy policy : policies) {
+            if (policy.getResource().equals(rootGroupResource)) {
+                foundRootGroupPolicy = true;
+                break;
+            }
+        }
+
+        assertFalse(foundRootGroupPolicy);
+    }
+
+    @Test
+    public void 
testOnConfiguredWhenBothInitialAdminAndInitialAdminGroupProvided() throws 
Exception {
+        final String adminIdentity = "admin-user";
+        
when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
+                .thenReturn(new StandardPropertyValue(adminIdentity, null, 
ParameterLookup.EMPTY));
+
+        final String adminGroupName = "admin-group";
+        
when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_GROUP)))
+                .thenReturn(new StandardPropertyValue(adminGroupName, null, 
ParameterLookup.EMPTY));
+
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        final Set<User> users = userGroupProvider.getUsers();
+        final User adminUser = users.iterator().next();
+        assertEquals(adminIdentity, adminUser.getIdentity());
+
+        final Set<Group> groups = userGroupProvider.getGroups();
+        final Group adminGroup = groups.iterator().next();
+        assertEquals(adminGroupName, adminGroup.getName());
+        assertEquals(Set.of(adminUser.getIdentifier()), adminGroup.getUsers());
+
+        final Set<AccessPolicy> policies = 
accessPolicyProvider.getAccessPolicies();
+        assertEquals(12, policies.size());
+        // admin user is a member of admin group; no need to grant access 
right to the user itself
+        final Set<String> usersWithPolicies = policies.stream()
+                .flatMap(policy -> policy.getUsers().stream())
+                .collect(Collectors.toUnmodifiableSet());
+        assertEquals(Collections.emptySet(), usersWithPolicies);
+    }
+
     @Test
     public void testOnConfiguredWhenInitialAdminProvidedWithIdentityMapping() 
throws Exception {
         final Properties props = new Properties();
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java
 
b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java
index 54e998d4bd..e499f8a8c4 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java
@@ -125,6 +125,17 @@ public class FileUserGroupProviderTest {
                 }
             }
 
+            int j = 1;
+            while (true) {
+                final String key = 
FileUserGroupProvider.PROP_INITIAL_GROUP_IDENTITY_PREFIX + j++;
+                final PropertyValue value = 
configurationContext.getProperty(key);
+                if (value == null) {
+                    break;
+                } else {
+                    properties.put(key, value.getValue());
+                }
+            }
+
             return properties;
         });
 
@@ -140,12 +151,14 @@ public class FileUserGroupProviderTest {
     }
 
     @Test
-    public void testOnConfiguredWhenInitialUsersNotProvided() throws Exception 
{
+    public void testOnConfiguredWhenInitialUsersAndInitialGroupsNotProvided() 
throws Exception {
         writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
         userGroupProvider.onConfigured(configurationContext);
 
         final Set<User> users = userGroupProvider.getUsers();
         assertEquals(0, users.size());
+        final Set<Group> groups = userGroupProvider.getGroups();
+        assertEquals(0, groups.size());
     }
 
     @Test
@@ -172,6 +185,26 @@ public class FileUserGroupProviderTest {
         assertTrue(users.contains(new 
User.Builder().identifierGenerateFromSeed(nodeIdentity2).identity(nodeIdentity2).build()));
     }
 
+    @Test
+    public void testOnConfiguredWhenInitialGroupsProvided() throws Exception {
+        final String adminGroupIdentity = "admin-group";
+        final String otherGroupIdentity = "other-group";
+
+        
when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_GROUP_IDENTITY_PREFIX
 + "1")))
+                .thenReturn(new StandardPropertyValue(adminGroupIdentity, 
null, ParameterLookup.EMPTY));
+        
when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_GROUP_IDENTITY_PREFIX
 + "2")))
+                .thenReturn(new StandardPropertyValue(otherGroupIdentity, 
null, ParameterLookup.EMPTY));
+
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+
+        final Set<Group> groups = userGroupProvider.getGroups();
+        assertEquals(2, groups.size());
+
+        assertTrue(groups.contains(new 
Group.Builder().identifierGenerateFromSeed(adminGroupIdentity).name(adminGroupIdentity).build()));
+        assertTrue(groups.contains(new 
Group.Builder().identifierGenerateFromSeed(otherGroupIdentity).name(otherGroupIdentity).build()));
+    }
+
     @Test
     public void testOnConfiguredWhenTenantsExistAndInitialUsersProvided() 
throws Exception {
         final String adminIdentity = "admin-user";
@@ -196,6 +229,24 @@ public class FileUserGroupProviderTest {
         assertTrue(users.contains(new 
User.Builder().identifier("user-2").identity("user-2").build()));
     }
 
+    @Test
+    public void testOnConfiguredWhenTenantsExistAndInitialGroupsProvided() 
throws Exception {
+        final String adminGroupIdentity = "admin-group";
+        final String otherGroupIdentity = "other-group";
+
+        // despite setting initial groups, they will not be loaded as the 
tenants file is non-empty
+        
when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_GROUP_IDENTITY_PREFIX
 + "1")))
+                .thenReturn(new StandardPropertyValue(adminGroupIdentity, 
null, ParameterLookup.EMPTY));
+        
when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_GROUP_IDENTITY_PREFIX
 + "2")))
+                .thenReturn(new StandardPropertyValue(otherGroupIdentity, 
null, ParameterLookup.EMPTY));
+
+        writeFile(primaryTenants, SIMPLE_TENANTS_BY_USER);
+        userGroupProvider.onConfigured(configurationContext);
+
+        final Set<Group> groups = userGroupProvider.getGroups();
+        assertEquals(0, groups.size());
+    }
+
     @Test
     public void testOnConfiguredWhenTenantsFileDoesNotExist() throws Exception 
{
         writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml
 
b/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml
index b12ad65b11..4f2fb936ff 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml
@@ -33,18 +33,26 @@
 
         - Users File - The file where the FileUserGroupProvider will store 
users and groups.
 
-        - Initial User Identity [unique key] - The identity of a users and 
systems to seed the Users File. The name of
+        - Initial User Identity [unique key] - The identity of a user or 
system to seed the Users File. The name of
             each property must be unique, for example: "Initial User Identity 
A", "Initial User Identity B",
             "Initial User Identity C" or "Initial User Identity 1", "Initial 
User Identity 2", "Initial User Identity 3"
 
             NOTE: Any identity mapping rules specified in nifi.properties will 
also be applied to the user identities,
             so the values should be the unmapped identities (i.e. full DN from 
a certificate).
+
+        - Initial Group Identity [unique key] - The identity of a user group 
to seed the Users File. The name of
+            each property must be unique, for example: "Initial Group Identity 
A", "Initial Group Identity B",
+            "Initial Group Identity C" or "Initial Group Identity 1", "Initial 
Group Identity 2", "Initial Group Identity 3"
+
+            NOTE: Any identity mapping rules specified in nifi.properties will 
also be applied to the group identities,
+            so the values should be the unmapped identities (i.e. full DN from 
a certificate).
     -->
     <userGroupProvider>
         <identifier>file-user-group-provider</identifier>
         <class>org.apache.nifi.authorization.FileUserGroupProvider</class>
         <property name="Users File">./conf/users.xml</property>
         <property name="Initial User Identity 1"></property>
+        <property name="Initial Group Identity 1"></property>
     </userGroupProvider>
 
     <!--
@@ -264,10 +272,20 @@
             given the ability to create additional users, groups, and 
policies. The value of this property could be
             a DN when using certificates or LDAP, or a Kerberos principal. 
This property will only be used when there
             are no other policies defined.
+            If the property "Initial Admin Group" is specified as well, the 
initial admin user will be a member of that group,
+            in case the configured user group provider supports updating the 
group.
 
             NOTE: Any identity mapping rules specified in nifi.properties will 
also be applied to the initial admin identity,
             so the value should be the unmapped identity. This identity must 
be found in the configured User Group Provider.
 
+        - Initial Admin Group - The identity of an initial admin group that 
will be granted access to the UI and
+            given the ability to create additional users, groups, and 
policies. The value of this property could be
+            a DN when using certificates or LDAP, or a Kerberos principal. 
This property will only be used when there
+            are no other policies defined.
+
+            NOTE: Any identity mapping rules specified in nifi.properties will 
also be applied to the initial admin group,
+            so the value should be the unmapped identity. This identity must 
be found in the configured User Group Provider.
+
         - Node Identity [unique key] - The identity of a NiFi cluster node. 
When clustered, a property for each node
             should be defined, so that every node knows about every other 
node. If not clustered these properties can be ignored.
             The name of each property must be unique, for example for a three 
node cluster:
@@ -288,6 +306,7 @@
         <property name="User Group 
Provider">file-user-group-provider</property>
         <property name="Authorizations 
File">./conf/authorizations.xml</property>
         <property name="Initial Admin Identity"></property>
+        <property name="Initial Admin Group"></property>
         <property name="Node Identity 1"></property>
         <property name="Node Group"></property>
     </accessPolicyProvider>

Reply via email to