You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ex...@apache.org on 2021/03/02 04:35:47 UTC

[nifi] branch main updated: NIFI-8258: Add support for Service Principal authentication in ADLS processors

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

exceptionfactory 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 7876f41  NIFI-8258: Add support for Service Principal authentication in ADLS processors
7876f41 is described below

commit 7876f412fbf1785322c06f6bac32804f28d66512
Author: Peter Turcsanyi <tu...@apache.org>
AuthorDate: Thu Feb 25 09:04:07 2021 +0100

    NIFI-8258: Add support for Service Principal authentication in ADLS processors
    
    - Removed Expression Language support indicators from sensitive properties
    
    This closes #4843
    
    Signed-off-by: David Handermann <ex...@apache.org>
---
 .../AbstractAzureDataLakeStorageProcessor.java     |  51 +++--
 .../storage/ADLSCredentialsControllerService.java  | 155 +++++++++++----
 .../TestADLSCredentialsControllerService.java      | 217 ++++++++++++++++++++-
 .../azure/storage/ADLSCredentialsDetails.java      |  55 +++++-
 4 files changed, 407 insertions(+), 71 deletions(-)

diff --git a/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-processors/src/main/java/org/apache/nifi/processors/azure/AbstractAzureDataLakeStorageProcessor.java b/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-processors/src/main/java/org/apache/nifi/processors/azure/AbstractAzureDataLakeStorageProcessor.java
index bb22c57..e6ca356 100644
--- a/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-processors/src/main/java/org/apache/nifi/processors/azure/AbstractAzureDataLakeStorageProcessor.java
+++ b/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-processors/src/main/java/org/apache/nifi/processors/azure/AbstractAzureDataLakeStorageProcessor.java
@@ -25,6 +25,8 @@ import java.util.Set;
 
 import com.azure.core.credential.AccessToken;
 import com.azure.core.credential.TokenCredential;
+import com.azure.identity.ClientSecretCredential;
+import com.azure.identity.ClientSecretCredentialBuilder;
 import com.azure.identity.ManagedIdentityCredential;
 import com.azure.identity.ManagedIdentityCredentialBuilder;
 import com.azure.storage.common.StorageSharedKeyCredential;
@@ -53,12 +55,12 @@ import static org.apache.nifi.processors.azure.storage.utils.ADLSAttributes.ATTR
 public abstract class AbstractAzureDataLakeStorageProcessor extends AbstractProcessor {
 
     public static final PropertyDescriptor ADLS_CREDENTIALS_SERVICE = new PropertyDescriptor.Builder()
-        .name("adls-credentials-service")
-        .displayName("ADLS Credentials")
-        .description("Controller Service used to obtain Azure Credentials.")
-        .identifiesControllerService(ADLSCredentialsService.class)
-        .required(true)
-        .build();
+            .name("adls-credentials-service")
+            .displayName("ADLS Credentials")
+            .description("Controller Service used to obtain Azure Credentials.")
+            .identifiesControllerService(ADLSCredentialsService.class)
+            .required(true)
+            .build();
 
     public static final PropertyDescriptor FILESYSTEM = new PropertyDescriptor.Builder()
             .name("filesystem-name").displayName("Filesystem Name")
@@ -120,7 +122,7 @@ public abstract class AbstractAzureDataLakeStorageProcessor extends AbstractProc
 
         final ADLSCredentialsService credentialsService = context.getProperty(ADLS_CREDENTIALS_SERVICE).asControllerService(ADLSCredentialsService.class);
 
-        ADLSCredentialsDetails credentialsDetails = credentialsService.getCredentialsDetails(attributes);
+        final ADLSCredentialsDetails credentialsDetails = credentialsService.getCredentialsDetails(attributes);
 
         final String accountName = credentialsDetails.getAccountName();
         final String accountKey = credentialsDetails.getAccountKey();
@@ -128,9 +130,13 @@ public abstract class AbstractAzureDataLakeStorageProcessor extends AbstractProc
         final AccessToken accessToken = credentialsDetails.getAccessToken();
         final String endpointSuffix = credentialsDetails.getEndpointSuffix();
         final boolean useManagedIdentity = credentialsDetails.getUseManagedIdentity();
+        final String servicePrincipalTenantId = credentialsDetails.getServicePrincipalTenantId();
+        final String servicePrincipalClientId = credentialsDetails.getServicePrincipalClientId();
+        final String servicePrincipalClientSecret = credentialsDetails.getServicePrincipalClientSecret();
 
         final String endpoint = String.format("https://%s.%s", accountName,endpointSuffix);
-        DataLakeServiceClient storageClient;
+
+        final DataLakeServiceClient storageClient;
         if (StringUtils.isNotBlank(accountKey)) {
             final StorageSharedKeyCredential credential = new StorageSharedKeyCredential(accountName,
                     accountKey);
@@ -140,17 +146,28 @@ public abstract class AbstractAzureDataLakeStorageProcessor extends AbstractProc
             storageClient = new DataLakeServiceClientBuilder().endpoint(endpoint).sasToken(sasToken)
                     .buildClient();
         } else if (accessToken != null) {
-            TokenCredential credential = tokenRequestContext -> Mono.just(accessToken);
+            final TokenCredential credential = tokenRequestContext -> Mono.just(accessToken);
 
             storageClient = new DataLakeServiceClientBuilder().endpoint(endpoint).credential(credential)
-                .buildClient();
-        } else if(useManagedIdentity){
-            final ManagedIdentityCredential misCrendential = new ManagedIdentityCredentialBuilder()
-                                                                .build();
-            storageClient = new  DataLakeServiceClientBuilder()
-                                    .endpoint(endpoint)
-                                    .credential(misCrendential)
-                                    .buildClient();
+                    .buildClient();
+        } else if (useManagedIdentity) {
+            final ManagedIdentityCredential misCredential = new ManagedIdentityCredentialBuilder()
+                    .build();
+            storageClient = new DataLakeServiceClientBuilder()
+                    .endpoint(endpoint)
+                    .credential(misCredential)
+                    .buildClient();
+        } else if (StringUtils.isNoneBlank(servicePrincipalTenantId, servicePrincipalClientId, servicePrincipalClientSecret)) {
+            final ClientSecretCredential credential = new ClientSecretCredentialBuilder()
+                    .tenantId(servicePrincipalTenantId)
+                    .clientId(servicePrincipalClientId)
+                    .clientSecret(servicePrincipalClientSecret)
+                    .build();
+
+            storageClient = new DataLakeServiceClientBuilder()
+                    .endpoint(endpoint)
+                    .credential(credential)
+                    .buildClient();
         } else {
             throw new IllegalArgumentException("No valid credentials were provided");
         }
diff --git a/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-processors/src/main/java/org/apache/nifi/services/azure/storage/ADLSCredentialsControllerService.java b/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-processors/src/main/java/org/apache/nifi/services/azure/storage/ADLSCredentialsControllerService.java
index ec86f9c..8ecb554 100644
--- a/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-processors/src/main/java/org/apache/nifi/services/azure/storage/ADLSCredentialsControllerService.java
+++ b/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-processors/src/main/java/org/apache/nifi/services/azure/storage/ADLSCredentialsControllerService.java
@@ -26,6 +26,7 @@ import org.apache.nifi.components.ValidationContext;
 import org.apache.nifi.components.ValidationResult;
 import org.apache.nifi.controller.AbstractControllerService;
 import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.expression.ExpressionLanguageScope;
 import org.apache.nifi.processor.util.StandardValidators;
 import org.apache.nifi.processors.azure.storage.utils.AzureStorageUtils;
 
@@ -35,7 +36,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.StringJoiner;
 import java.util.function.BiConsumer;
 import java.util.function.Function;
 
@@ -49,37 +49,81 @@ import java.util.function.Function;
 public class ADLSCredentialsControllerService extends AbstractControllerService implements ADLSCredentialsService {
 
     public static final PropertyDescriptor ACCOUNT_NAME = new PropertyDescriptor.Builder()
-        .fromPropertyDescriptor(AzureStorageUtils.ACCOUNT_NAME)
-        .description(AzureStorageUtils.ACCOUNT_NAME_BASE_DESCRIPTION)
-        .required(true)
-        .build();
+            .fromPropertyDescriptor(AzureStorageUtils.ACCOUNT_NAME)
+            .description(AzureStorageUtils.ACCOUNT_NAME_BASE_DESCRIPTION)
+            .required(true)
+            .expressionLanguageSupported(ExpressionLanguageScope.NONE)
+            .build();
 
     public static final PropertyDescriptor ENDPOINT_SUFFIX = new PropertyDescriptor.Builder()
-        .fromPropertyDescriptor(AzureStorageUtils.ENDPOINT_SUFFIX)
-        .displayName("Endpoint Suffix")
-        .description(
-            "Storage accounts in public Azure always use a common FQDN suffix. " +
-                "Override this endpoint suffix with a different suffix in certain circumstances (like Azure Stack or non-public Azure regions).")
-        .required(true)
-        .defaultValue("dfs.core.windows.net")
-        .build();
+            .fromPropertyDescriptor(AzureStorageUtils.ENDPOINT_SUFFIX)
+            .displayName("Endpoint Suffix")
+            .description("Storage accounts in public Azure always use a common FQDN suffix. " +
+                    "Override this endpoint suffix with a different suffix in certain circumstances (like Azure Stack or non-public Azure regions).")
+            .required(true)
+            .defaultValue("dfs.core.windows.net")
+            .expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY)
+            .build();
+
+    public static final PropertyDescriptor ACCOUNT_KEY = new PropertyDescriptor.Builder()
+            .fromPropertyDescriptor(AzureStorageUtils.ACCOUNT_KEY)
+            .expressionLanguageSupported(ExpressionLanguageScope.NONE)
+            .build();
+
+    public static final PropertyDescriptor SAS_TOKEN = new PropertyDescriptor.Builder()
+            .fromPropertyDescriptor(AzureStorageUtils.PROP_SAS_TOKEN)
+            .expressionLanguageSupported(ExpressionLanguageScope.NONE)
+            .build();
 
     public static final PropertyDescriptor USE_MANAGED_IDENTITY = new PropertyDescriptor.Builder()
-        .name("storage-use-managed-identity")
-        .displayName("Use Azure Managed Identity")
-        .description("Choose whether or not to use the managed identity of Azure VM/VMSS ")
-        .required(false)
-        .defaultValue("false")
-        .allowableValues("true", "false")
-        .addValidator(StandardValidators.BOOLEAN_VALIDATOR)
-        .build();
+            .name("storage-use-managed-identity")
+            .displayName("Use Azure Managed Identity")
+            .description("Choose whether or not to use the managed identity of Azure VM/VMSS ")
+            .required(false)
+            .defaultValue("false")
+            .allowableValues("true", "false")
+            .addValidator(StandardValidators.BOOLEAN_VALIDATOR)
+            .build();
+
+    public static final PropertyDescriptor SERVICE_PRINCIPAL_TENANT_ID = new PropertyDescriptor.Builder()
+            .name("service-principal-tenant-id")
+            .displayName("Service Principal Tenant ID")
+            .description("Tenant ID of the Azure Active Directory hosting the Service Principal. The property is required when Service Principal authentication is used.")
+            .sensitive(true)
+            .required(false)
+            .addValidator(StandardValidators.NON_BLANK_VALIDATOR)
+            .expressionLanguageSupported(ExpressionLanguageScope.NONE)
+            .build();
+
+    public static final PropertyDescriptor SERVICE_PRINCIPAL_CLIENT_ID = new PropertyDescriptor.Builder()
+            .name("service-principal-client-id")
+            .displayName("Service Principal Client ID")
+            .description("Client ID (or Application ID) of the Client/Application having the Service Principal. The property is required when Service Principal authentication is used.")
+            .sensitive(true)
+            .required(false)
+            .addValidator(StandardValidators.NON_BLANK_VALIDATOR)
+            .expressionLanguageSupported(ExpressionLanguageScope.NONE)
+            .build();
+
+    public static final PropertyDescriptor SERVICE_PRINCIPAL_CLIENT_SECRET = new PropertyDescriptor.Builder()
+            .name("service-principal-client-secret")
+            .displayName("Service Principal Client Secret")
+            .description("Password of the Client/Application. The property is required when Service Principal authentication is used.")
+            .sensitive(true)
+            .required(false)
+            .addValidator(StandardValidators.NON_BLANK_VALIDATOR)
+            .expressionLanguageSupported(ExpressionLanguageScope.NONE)
+            .build();
 
     private static final List<PropertyDescriptor> PROPERTIES = Collections.unmodifiableList(Arrays.asList(
-        ACCOUNT_NAME,
-        ENDPOINT_SUFFIX,
-        AzureStorageUtils.ACCOUNT_KEY,
-        AzureStorageUtils.PROP_SAS_TOKEN,
-        USE_MANAGED_IDENTITY
+            ACCOUNT_NAME,
+            ENDPOINT_SUFFIX,
+            ACCOUNT_KEY,
+            SAS_TOKEN,
+            USE_MANAGED_IDENTITY,
+            SERVICE_PRINCIPAL_TENANT_ID,
+            SERVICE_PRINCIPAL_CLIENT_ID,
+            SERVICE_PRINCIPAL_CLIENT_SECRET
     ));
 
     private ConfigurationContext context;
@@ -93,20 +137,41 @@ public class ADLSCredentialsControllerService extends AbstractControllerService
     protected Collection<ValidationResult> customValidate(ValidationContext validationContext) {
         final List<ValidationResult> results = new ArrayList<>();
 
-        boolean accountKeySet = StringUtils.isNotBlank(validationContext.getProperty(AzureStorageUtils.ACCOUNT_KEY).getValue());
-        boolean sasTokenSet = StringUtils.isNotBlank(validationContext.getProperty(AzureStorageUtils.PROP_SAS_TOKEN).getValue());
+        boolean accountKeySet = StringUtils.isNotBlank(validationContext.getProperty(ACCOUNT_KEY).getValue());
+        boolean sasTokenSet = StringUtils.isNotBlank(validationContext.getProperty(SAS_TOKEN).getValue());
         boolean useManagedIdentitySet = validationContext.getProperty(USE_MANAGED_IDENTITY).asBoolean();
 
-        if (!onlyOneSet(accountKeySet, sasTokenSet, useManagedIdentitySet)) {
-            StringJoiner options = new StringJoiner(", ")
-                .add(AzureStorageUtils.ACCOUNT_KEY.getDisplayName())
-                .add(AzureStorageUtils.PROP_SAS_TOKEN.getDisplayName())
-                .add(USE_MANAGED_IDENTITY.getDisplayName());
+        boolean servicePrincipalTenantIdSet = StringUtils.isNotBlank(validationContext.getProperty(SERVICE_PRINCIPAL_TENANT_ID).getValue());
+        boolean servicePrincipalClientIdSet = StringUtils.isNotBlank(validationContext.getProperty(SERVICE_PRINCIPAL_CLIENT_ID).getValue());
+        boolean servicePrincipalClientSecretSet = StringUtils.isNotBlank(validationContext.getProperty(SERVICE_PRINCIPAL_CLIENT_SECRET).getValue());
 
+        boolean servicePrincipalSet = servicePrincipalTenantIdSet || servicePrincipalClientIdSet || servicePrincipalClientSecretSet;
+
+        if (!onlyOneSet(accountKeySet, sasTokenSet, useManagedIdentitySet, servicePrincipalSet)) {
             results.add(new ValidationResult.Builder().subject(this.getClass().getSimpleName())
                 .valid(false)
-                .explanation("one and only one of [" + options + "] should be set")
+                .explanation("one and only one authentication method of [Account Key, SAS Token, Managed Identity, Service Principal] should be used")
                 .build());
+        } else if (servicePrincipalSet) {
+            String template = "'%s' must be set when Service Principal authentication is being configured";
+            if (!servicePrincipalTenantIdSet) {
+                results.add(new ValidationResult.Builder().subject(this.getClass().getSimpleName())
+                        .valid(false)
+                        .explanation(String.format(template, SERVICE_PRINCIPAL_TENANT_ID.getDisplayName()))
+                        .build());
+            }
+            if (!servicePrincipalClientIdSet) {
+                results.add(new ValidationResult.Builder().subject(this.getClass().getSimpleName())
+                        .valid(false)
+                        .explanation(String.format(template, SERVICE_PRINCIPAL_CLIENT_ID.getDisplayName()))
+                        .build());
+            }
+            if (!servicePrincipalClientSecretSet) {
+                results.add(new ValidationResult.Builder().subject(this.getClass().getSimpleName())
+                        .valid(false)
+                        .explanation(String.format(template, SERVICE_PRINCIPAL_CLIENT_SECRET.getDisplayName()))
+                        .build());
+            }
         }
 
         return results;
@@ -129,23 +194,29 @@ public class ADLSCredentialsControllerService extends AbstractControllerService
     public ADLSCredentialsDetails getCredentialsDetails(Map<String, String> attributes) {
         ADLSCredentialsDetails.Builder credentialsBuilder = ADLSCredentialsDetails.Builder.newBuilder();
 
-        setValue(credentialsBuilder, ACCOUNT_NAME, PropertyValue::getValue, ADLSCredentialsDetails.Builder::setAccountName);
-        setValue(credentialsBuilder, AzureStorageUtils.ACCOUNT_KEY, PropertyValue::getValue, ADLSCredentialsDetails.Builder::setAccountKey);
-        setValue(credentialsBuilder, AzureStorageUtils.PROP_SAS_TOKEN, PropertyValue::getValue, ADLSCredentialsDetails.Builder::setSasToken);
-        setValue(credentialsBuilder, ENDPOINT_SUFFIX, PropertyValue::getValue, ADLSCredentialsDetails.Builder::setEndpointSuffix);
-        setValue(credentialsBuilder, USE_MANAGED_IDENTITY, PropertyValue::asBoolean, ADLSCredentialsDetails.Builder::setUseManagedIdentity);
+        setValue(credentialsBuilder, ACCOUNT_NAME, PropertyValue::getValue, ADLSCredentialsDetails.Builder::setAccountName, attributes);
+        setValue(credentialsBuilder, ACCOUNT_KEY, PropertyValue::getValue, ADLSCredentialsDetails.Builder::setAccountKey, attributes);
+        setValue(credentialsBuilder, SAS_TOKEN, PropertyValue::getValue, ADLSCredentialsDetails.Builder::setSasToken, attributes);
+        setValue(credentialsBuilder, ENDPOINT_SUFFIX, PropertyValue::getValue, ADLSCredentialsDetails.Builder::setEndpointSuffix, attributes);
+        setValue(credentialsBuilder, USE_MANAGED_IDENTITY, PropertyValue::asBoolean, ADLSCredentialsDetails.Builder::setUseManagedIdentity, attributes);
+        setValue(credentialsBuilder, SERVICE_PRINCIPAL_TENANT_ID, PropertyValue::getValue, ADLSCredentialsDetails.Builder::setServicePrincipalTenantId, attributes);
+        setValue(credentialsBuilder, SERVICE_PRINCIPAL_CLIENT_ID, PropertyValue::getValue, ADLSCredentialsDetails.Builder::setServicePrincipalClientId, attributes);
+        setValue(credentialsBuilder, SERVICE_PRINCIPAL_CLIENT_SECRET, PropertyValue::getValue, ADLSCredentialsDetails.Builder::setServicePrincipalClientSecret, attributes);
 
         return credentialsBuilder.build();
     }
 
     private <T> void setValue(
-        ADLSCredentialsDetails.Builder credentialsBuilder,
-        PropertyDescriptor propertyDescriptor, Function<PropertyValue, T> getPropertyValue,
-        BiConsumer<ADLSCredentialsDetails.Builder, T> setBuilderValue
+            ADLSCredentialsDetails.Builder credentialsBuilder,
+            PropertyDescriptor propertyDescriptor, Function<PropertyValue, T> getPropertyValue,
+            BiConsumer<ADLSCredentialsDetails.Builder, T> setBuilderValue, Map<String, String> attributes
     ) {
         PropertyValue property = context.getProperty(propertyDescriptor);
 
         if (property.isSet()) {
+            if (propertyDescriptor.isExpressionLanguageSupported()) {
+                property = property.evaluateAttributeExpressions(attributes);
+            }
             T value = getPropertyValue.apply(property);
             setBuilderValue.accept(credentialsBuilder, value);
         }
diff --git a/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-processors/src/test/java/org/apache/nifi/services/azure/storage/TestADLSCredentialsControllerService.java b/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-processors/src/test/java/org/apache/nifi/services/azure/storage/TestADLSCredentialsControllerService.java
index aa51c4c..6837c9e 100644
--- a/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-processors/src/test/java/org/apache/nifi/services/azure/storage/TestADLSCredentialsControllerService.java
+++ b/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-processors/src/test/java/org/apache/nifi/services/azure/storage/TestADLSCredentialsControllerService.java
@@ -16,7 +16,7 @@
  */
 package org.apache.nifi.services.azure.storage;
 
-import org.apache.nifi.processors.azure.storage.utils.AzureStorageUtils;
+import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.reporting.InitializationException;
 import org.apache.nifi.util.NoOpProcessor;
 import org.apache.nifi.util.TestRunner;
@@ -39,7 +39,10 @@ public class TestADLSCredentialsControllerService {
     private static final String ACCOUNT_NAME_VALUE = "AccountName";
     private static final String ACCOUNT_KEY_VALUE = "AccountKey";
     private static final String SAS_TOKEN_VALUE = "SasToken";
-    public static final String END_POINT_SUFFIX_VALUE = "end.point.suffix";
+    private static final String END_POINT_SUFFIX_VALUE = "end.point.suffix";
+    private static final String SERVICE_PRINCIPAL_TENANT_ID_VALUE = "ServicePrincipalTenantID";
+    private static final String SERVICE_PRINCIPAL_CLIENT_ID_VALUE = "ServicePrincipalClientID";
+    private static final String SERVICE_PRINCIPAL_CLIENT_SECRET_VALUE = "ServicePrincipalClientSecret";
 
     private TestRunner runner;
     private ADLSCredentialsControllerService credentialsService;
@@ -86,6 +89,36 @@ public class TestADLSCredentialsControllerService {
     }
 
     @Test
+    public void testNotValidBecauseBothAccountKeyAndServicePrincipalTenantIdSpecified() {
+        configureAccountName();
+
+        configureAccountKey();
+        configureServicePrincipalTenantId();
+
+        runner.assertNotValid(credentialsService);
+    }
+
+    @Test
+    public void testNotValidBecauseBothAccountKeyAndServicePrincipalClientIdSpecified() {
+        configureAccountName();
+
+        configureAccountKey();
+        configureServicePrincipalClientId();
+
+        runner.assertNotValid(credentialsService);
+    }
+
+    @Test
+    public void testNotValidBecauseBothAccountKeyAndServicePrincipalClientSecretSpecified() {
+        configureAccountName();
+
+        configureAccountKey();
+        configureServicePrincipalClientSecret();
+
+        runner.assertNotValid(credentialsService);
+    }
+
+    @Test
     public void testNotValidBecauseBothSasTokenAndUseManagedIdentitySpecified() {
         configureAccountName();
 
@@ -96,12 +129,75 @@ public class TestADLSCredentialsControllerService {
     }
 
     @Test
-    public void testNotValidBecauseAllCredentialsSpecified() {
+    public void testNotValidBecauseBothSasTokenAndServicePrincipalTenantIdSpecified() {
+        configureAccountName();
+
+        configureSasToken();
+        configureServicePrincipalTenantId();
+
+        runner.assertNotValid(credentialsService);
+    }
+
+    @Test
+    public void testNotValidBecauseBothSasTokenAndServicePrincipalClientIdSpecified() {
+        configureAccountName();
+
+        configureSasToken();
+        configureServicePrincipalClientId();
+
+        runner.assertNotValid(credentialsService);
+    }
+
+    @Test
+    public void testNotValidBecauseBothSasTokenAndServicePrincipalClientSecretSpecified() {
+        configureAccountName();
+
+        configureSasToken();
+        configureServicePrincipalClientSecret();
+
+        runner.assertNotValid(credentialsService);
+    }
+
+    @Test
+    public void testNotValidBecauseBothUseManagedIdentityAndServicePrincipalTenantIdSpecified() {
+        configureAccountName();
+
+        configureUseManagedIdentity();
+        configureServicePrincipalTenantId();
+
+        runner.assertNotValid(credentialsService);
+    }
+
+    @Test
+    public void testNotValidBecauseBothUseManagedIdentityAndServicePrincipalClientIdSpecified() {
+        configureAccountName();
+
+        configureUseManagedIdentity();
+        configureServicePrincipalClientId();
+
+        runner.assertNotValid(credentialsService);
+    }
+
+    @Test
+    public void testNotValidBecauseBothUseManagedIdentityAndServicePrincipalClientSecretSpecified() {
+        configureAccountName();
+
+        configureUseManagedIdentity();
+        configureServicePrincipalClientSecret();
+
+        runner.assertNotValid(credentialsService);
+    }
+
+    @Test
+    public void testNotValidBecauseAllCredentialsSpecified() throws Exception {
         configureAccountName();
 
         configureAccountKey();
         configureSasToken();
         configureUseManagedIdentity();
+        configureServicePrincipalTenantId();
+        configureServicePrincipalClientId();
+        configureServicePrincipalClientSecret();
 
         runner.assertNotValid(credentialsService);
     }
@@ -148,6 +244,46 @@ public class TestADLSCredentialsControllerService {
     }
 
     @Test
+    public void testValidWithAccountNameAndServicePrincipalWithClientSecret() {
+        configureAccountName();
+        configureServicePrincipalTenantId();
+        configureServicePrincipalClientId();
+        configureServicePrincipalClientSecret();
+
+        runner.assertValid(credentialsService);
+    }
+
+    @Test
+    public void testNotValidBecauseNoTenantIdSpecifiedForServicePrincipal() {
+        configureAccountName();
+
+        configureServicePrincipalClientId();
+        configureServicePrincipalClientSecret();
+
+        runner.assertNotValid(credentialsService);
+    }
+
+    @Test
+    public void testNotValidBecauseNoClientIdSpecifiedForServicePrincipal() {
+        configureAccountName();
+
+        configureServicePrincipalTenantId();
+        configureServicePrincipalClientSecret();
+
+        runner.assertNotValid(credentialsService);
+    }
+
+    @Test
+    public void testNotValidBecauseNoClientSecretSpecifiedForServicePrincipal() {
+        configureAccountName();
+
+        configureServicePrincipalTenantId();
+        configureServicePrincipalClientId();
+
+        runner.assertNotValid(credentialsService);
+    }
+
+    @Test
     public void testGetCredentialsDetailsWithAccountKey() throws Exception {
         // GIVEN
         configureAccountName();
@@ -164,6 +300,9 @@ public class TestADLSCredentialsControllerService {
         assertNull(actual.getSasToken());
         assertFalse(actual.getUseManagedIdentity());
         assertNotNull(actual.getEndpointSuffix());
+        assertNull(actual.getServicePrincipalTenantId());
+        assertNull(actual.getServicePrincipalClientId());
+        assertNull(actual.getServicePrincipalClientSecret());
     }
 
     @Test
@@ -183,6 +322,9 @@ public class TestADLSCredentialsControllerService {
         assertNull(actual.getAccountKey());
         assertFalse(actual.getUseManagedIdentity());
         assertNotNull(actual.getEndpointSuffix());
+        assertNull(actual.getServicePrincipalTenantId());
+        assertNull(actual.getServicePrincipalClientId());
+        assertNull(actual.getServicePrincipalClientSecret());
     }
 
     @Test
@@ -202,6 +344,33 @@ public class TestADLSCredentialsControllerService {
         assertNull(actual.getAccountKey());
         assertNull(actual.getSasToken());
         assertNotNull(actual.getEndpointSuffix());
+        assertNull(actual.getServicePrincipalTenantId());
+        assertNull(actual.getServicePrincipalClientId());
+        assertNull(actual.getServicePrincipalClientSecret());
+    }
+
+    @Test
+    public void testGetCredentialsDetailsWithServicePrincipalWithClientSecret() throws Exception {
+        // GIVEN
+        configureAccountName();
+        configureServicePrincipalTenantId();
+        configureServicePrincipalClientId();
+        configureServicePrincipalClientSecret();
+
+        runner.enableControllerService(credentialsService);
+
+        // WHEN
+        ADLSCredentialsDetails actual = credentialsService.getCredentialsDetails(new HashMap<>());
+
+        // THEN
+        assertEquals(ACCOUNT_NAME_VALUE, actual.getAccountName());
+        assertNull(actual.getAccountKey());
+        assertNull(actual.getSasToken());
+        assertFalse(actual.getUseManagedIdentity());
+        assertNotNull(actual.getEndpointSuffix());
+        assertEquals(SERVICE_PRINCIPAL_TENANT_ID_VALUE, actual.getServicePrincipalTenantId());
+        assertEquals(SERVICE_PRINCIPAL_CLIENT_ID_VALUE, actual.getServicePrincipalClientId());
+        assertEquals(SERVICE_PRINCIPAL_CLIENT_SECRET_VALUE, actual.getServicePrincipalClientSecret());
     }
 
     @Test
@@ -220,16 +389,32 @@ public class TestADLSCredentialsControllerService {
         assertEquals(END_POINT_SUFFIX_VALUE, actual.getEndpointSuffix());
     }
 
+    @Test
+    public void testGetCredentialsDetailsWithSetEndpointSuffixUsingEL() throws Exception {
+        // GIVEN
+        configureAccountName();
+        configureAccountKey();
+        configureEndpointSuffixUsingEL();
+
+        runner.enableControllerService(credentialsService);
+
+        // WHEN
+        ADLSCredentialsDetails actual = credentialsService.getCredentialsDetails(new HashMap<>());
+
+        // THEN
+        assertEquals(END_POINT_SUFFIX_VALUE, actual.getEndpointSuffix());
+    }
+
     private void configureAccountName() {
         runner.setProperty(credentialsService, ADLSCredentialsControllerService.ACCOUNT_NAME, ACCOUNT_NAME_VALUE);
     }
 
     private void configureAccountKey() {
-        runner.setProperty(credentialsService, AzureStorageUtils.ACCOUNT_KEY, ACCOUNT_KEY_VALUE);
+        runner.setProperty(credentialsService, ADLSCredentialsControllerService.ACCOUNT_KEY, ACCOUNT_KEY_VALUE);
     }
 
     private void configureSasToken() {
-        runner.setProperty(credentialsService, AzureStorageUtils.PROP_SAS_TOKEN, SAS_TOKEN_VALUE);
+        runner.setProperty(credentialsService, ADLSCredentialsControllerService.SAS_TOKEN, SAS_TOKEN_VALUE);
     }
 
     private void configureUseManagedIdentity() {
@@ -239,4 +424,26 @@ public class TestADLSCredentialsControllerService {
     private void configureEndpointSuffix() {
         runner.setProperty(credentialsService, ADLSCredentialsControllerService.ENDPOINT_SUFFIX, END_POINT_SUFFIX_VALUE);
     }
+
+    private void configureEndpointSuffixUsingEL() {
+        String variableName = "endpoint.suffix";
+        configurePropertyUsingEL(ADLSCredentialsControllerService.ENDPOINT_SUFFIX, variableName, END_POINT_SUFFIX_VALUE);
+    }
+
+    private void configureServicePrincipalTenantId() {
+        runner.setProperty(credentialsService, ADLSCredentialsControllerService.SERVICE_PRINCIPAL_TENANT_ID, SERVICE_PRINCIPAL_TENANT_ID_VALUE);
+    }
+
+    private void configureServicePrincipalClientId() {
+        runner.setProperty(credentialsService, ADLSCredentialsControllerService.SERVICE_PRINCIPAL_CLIENT_ID, SERVICE_PRINCIPAL_CLIENT_ID_VALUE);
+    }
+
+    private void configureServicePrincipalClientSecret() {
+        runner.setProperty(credentialsService, ADLSCredentialsControllerService.SERVICE_PRINCIPAL_CLIENT_SECRET, SERVICE_PRINCIPAL_CLIENT_SECRET_VALUE);
+    }
+
+    private void configurePropertyUsingEL(PropertyDescriptor propertyDescriptor, String variableName, String variableValue) {
+        runner.setProperty(credentialsService, propertyDescriptor, String.format("${%s}", variableName));
+        runner.setVariable(variableName, variableValue);
+    }
 }
diff --git a/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-services-api/src/main/java/org/apache/nifi/services/azure/storage/ADLSCredentialsDetails.java b/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-services-api/src/main/java/org/apache/nifi/services/azure/storage/ADLSCredentialsDetails.java
index cd1111e..03143ed 100644
--- a/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-services-api/src/main/java/org/apache/nifi/services/azure/storage/ADLSCredentialsDetails.java
+++ b/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-services-api/src/main/java/org/apache/nifi/services/azure/storage/ADLSCredentialsDetails.java
@@ -29,13 +29,20 @@ public class ADLSCredentialsDetails {
 
     private final boolean useManagedIdentity;
 
+    private final String servicePrincipalTenantId;
+    private final String servicePrincipalClientId;
+    private final String servicePrincipalClientSecret;
+
     public ADLSCredentialsDetails(
-        String accountName,
-        String accountKey,
-        String sasToken,
-        String endpointSuffix,
-        AccessToken accessToken,
-        boolean useManagedIdentity
+            String accountName,
+            String accountKey,
+            String sasToken,
+            String endpointSuffix,
+            AccessToken accessToken,
+            boolean useManagedIdentity,
+            String servicePrincipalTenantId,
+            String servicePrincipalClientId,
+            String servicePrincipalClientSecret
     ) {
         this.accountName = accountName;
         this.accountKey = accountKey;
@@ -43,6 +50,9 @@ public class ADLSCredentialsDetails {
         this.endpointSuffix = endpointSuffix;
         this.accessToken = accessToken;
         this.useManagedIdentity = useManagedIdentity;
+        this.servicePrincipalTenantId = servicePrincipalTenantId;
+        this.servicePrincipalClientId = servicePrincipalClientId;
+        this.servicePrincipalClientSecret = servicePrincipalClientSecret;
     }
 
     public String getAccountName() {
@@ -69,6 +79,18 @@ public class ADLSCredentialsDetails {
         return useManagedIdentity;
     }
 
+    public String getServicePrincipalTenantId() {
+        return servicePrincipalTenantId;
+    }
+
+    public String getServicePrincipalClientId() {
+        return servicePrincipalClientId;
+    }
+
+    public String getServicePrincipalClientSecret() {
+        return servicePrincipalClientSecret;
+    }
+
     public static class Builder {
         private String accountName;
         private String accountKey;
@@ -76,6 +98,9 @@ public class ADLSCredentialsDetails {
         private String endpointSuffix;
         private AccessToken accessToken;
         private boolean useManagedIdentity;
+        private String servicePrincipalTenantId;
+        private String servicePrincipalClientId;
+        private String servicePrincipalClientSecret;
 
         private Builder() {}
 
@@ -113,8 +138,24 @@ public class ADLSCredentialsDetails {
             return this;
         }
 
+        public Builder setServicePrincipalTenantId(String servicePrincipalTenantId) {
+            this.servicePrincipalTenantId = servicePrincipalTenantId;
+            return this;
+        }
+
+        public Builder setServicePrincipalClientId(String servicePrincipalClientId) {
+            this.servicePrincipalClientId = servicePrincipalClientId;
+            return this;
+        }
+
+        public Builder setServicePrincipalClientSecret(String servicePrincipalClientSecret) {
+            this.servicePrincipalClientSecret = servicePrincipalClientSecret;
+            return this;
+        }
+
         public ADLSCredentialsDetails build() {
-            return new ADLSCredentialsDetails(accountName, accountKey, sasToken, endpointSuffix, accessToken, useManagedIdentity);
+            return new ADLSCredentialsDetails(accountName, accountKey, sasToken, endpointSuffix, accessToken, useManagedIdentity,
+                    servicePrincipalTenantId, servicePrincipalClientId, servicePrincipalClientSecret);
         }
     }
 }