diff --git a/plugins/nf-azure/src/main/nextflow/cloud/azure/fusion/AzFusionEnv.groovy b/plugins/nf-azure/src/main/nextflow/cloud/azure/fusion/AzFusionEnv.groovy index a4b9e8e344..8786b2fb30 100644 --- a/plugins/nf-azure/src/main/nextflow/cloud/azure/fusion/AzFusionEnv.groovy +++ b/plugins/nf-azure/src/main/nextflow/cloud/azure/fusion/AzFusionEnv.groovy @@ -49,17 +49,26 @@ class AzFusionEnv implements FusionEnv { throw new IllegalArgumentException("Missing Azure Storage account name") } - if (cfg.storage().accountKey && cfg.storage().sasToken) { - throw new IllegalArgumentException("Azure Storage Access key and SAS token detected. Only one is allowed") + result.AZURE_STORAGE_ACCOUNT = cfg.storage().accountName + + if (cfg.activeDirectory().isConfigured()) { + result.AZURE_TENANT_ID = cfg.activeDirectory().tenantId + result.AZURE_CLIENT_ID = cfg.activeDirectory().servicePrincipalId + result.AZURE_CLIENT_SECRET = cfg.activeDirectory().servicePrincipalSecret + return result } - result.AZURE_STORAGE_ACCOUNT = cfg.storage().accountName - // In theory, generating an impromptu SAS token for authentication methods other than - // `azure.storage.sasToken` should not be necessary, because those methods should already allow sufficient - // access for normal operation. Nevertheless, #5287 heavily implies that failing to do so causes the Azure - // Storage plugin or Fusion to fail. In any case, it may be possible to remove this in the future. - result.AZURE_STORAGE_SAS_TOKEN = getOrCreateSasToken() + if (cfg.managedIdentity().isConfigured()) { + // User-assigned Managed Identity + if (cfg.managedIdentity().clientId) { + result.AZURE_CLIENT_ID = cfg.managedIdentity().clientId + } + // System Managed Identity + return result + } + // Shared Key authentication or Account SAS token + result.AZURE_STORAGE_SAS_TOKEN = getOrCreateSasToken() return result } @@ -76,12 +85,6 @@ class AzFusionEnv implements FusionEnv { return cfg.storage().sasToken } - // For Active Directory and Managed Identity, we cannot generate an *account* SAS token, but we can generate - // a *container* SAS token for the work directory. - if (cfg.activeDirectory().isConfigured() || cfg.managedIdentity().isConfigured()) { - return AzHelper.generateContainerSasWithActiveDirectory(Global.session.workDir, cfg.storage().tokenDuration) - } - // Shared Key authentication can use an account SAS token return AzHelper.generateAccountSasWithAccountKey(Global.session.workDir, cfg.storage().tokenDuration) } diff --git a/plugins/nf-azure/src/test/nextflow/cloud/azure/fusion/AzFusionEnvTest.groovy b/plugins/nf-azure/src/test/nextflow/cloud/azure/fusion/AzFusionEnvTest.groovy index 79774e9a9a..59c3a4bbf3 100644 --- a/plugins/nf-azure/src/test/nextflow/cloud/azure/fusion/AzFusionEnvTest.groovy +++ b/plugins/nf-azure/src/test/nextflow/cloud/azure/fusion/AzFusionEnvTest.groovy @@ -47,7 +47,7 @@ class AzFusionEnvTest extends Specification { env == Collections.emptyMap() } - def 'should return env environment with SAS token config when accountKey is provided'() { + def 'should return env environment with sasToken and accountName config when accountKey is provided'() { given: def NAME = 'myaccount' def KEY = 'myaccountkey' @@ -67,7 +67,7 @@ class AzFusionEnvTest extends Specification { env.size() == 2 } - def 'should return env environment with SAS token config when a Service Principal is provided'() { + def 'should return env environment with tenantID, servicePrincipalId, servicePrincipalSecret, and accountName config when a Service Principal is provided'() { given: def NAME = 'myaccount' def CLIENT_ID = 'myclientid' @@ -90,17 +90,18 @@ class AzFusionEnvTest extends Specification { when: def config = Mock(FusionConfig) - def fusionEnv = Spy(AzFusionEnv) - 1 * fusionEnv.getOrCreateSasToken() >> 'generatedSasToken' - def env = fusionEnv.getEnvironment('az', config) + def env = new AzFusionEnv().getEnvironment('az', config) then: env.AZURE_STORAGE_ACCOUNT == NAME - env.AZURE_STORAGE_SAS_TOKEN == 'generatedSasToken' - env.size() == 2 + env.AZURE_TENANT_ID == TENANT_ID + env.AZURE_CLIENT_ID == CLIENT_ID + env.AZURE_CLIENT_SECRET == CLIENT_SECRET + env.AZURE_STORAGE_SAS_TOKEN == null + env.size() == 4 } - def 'should return env environment with SAS token config when a user-assigned Managed Identity is provided'() { + def 'should return env environment with the clientId and accountName config when a user-assigned Managed Identity is provided'() { given: def NAME = 'myaccount' def CLIENT_ID = 'myclientid' @@ -119,17 +120,16 @@ class AzFusionEnvTest extends Specification { when: def config = Mock(FusionConfig) - def fusionEnv = Spy(AzFusionEnv) - 1 * fusionEnv.getOrCreateSasToken() >> 'generatedSasToken' - def env = fusionEnv.getEnvironment('az', config) + def env = new AzFusionEnv().getEnvironment('az', config) then: env.AZURE_STORAGE_ACCOUNT == NAME - env.AZURE_STORAGE_SAS_TOKEN == 'generatedSasToken' + env.AZURE_CLIENT_ID == CLIENT_ID + env.AZURE_STORAGE_SAS_TOKEN == null env.size() == 2 } - def 'should return env environment with SAS token config when a system-assigned Managed Identity is provided'() { + def 'should return env environment with only the accountName config when a system-assigned Managed Identity is provided'() { given: def NAME = 'myaccount' Global.session = Mock(Session) { @@ -147,17 +147,15 @@ class AzFusionEnvTest extends Specification { when: def config = Mock(FusionConfig) - def fusionEnv = Spy(AzFusionEnv) - 1 * fusionEnv.getOrCreateSasToken() >> 'generatedSasToken' - def env = fusionEnv.getEnvironment('az', config) + def env = new AzFusionEnv().getEnvironment('az', config) then: env.AZURE_STORAGE_ACCOUNT == NAME - env.AZURE_STORAGE_SAS_TOKEN == 'generatedSasToken' - env.size() == 2 + env.AZURE_STORAGE_SAS_TOKEN == null + env.size() == 1 } - def 'should return env environment with SAS token config when a sasToken is provided'() { + def 'should return env environment sasToken and accountName config when a sasToken is provided'() { given: Global.session = Mock(Session) { getConfig() >> [azure: [storage: [accountName: 'x1', sasToken: 'y1']]] @@ -184,16 +182,4 @@ class AzFusionEnvTest extends Specification { thrown(IllegalArgumentException) } - def 'should throw an exception when both account key and SAS token are present'() { - given: - Global.session = Mock(Session) { - getConfig() >> [azure: [storage: [accountName: 'x1', accountKey: 'y1', sasToken: 'z1']]] - } - when: - def config = Mock(FusionConfig) - def env = new AzFusionEnv().getEnvironment('az', Mock(FusionConfig)) - then: - thrown(IllegalArgumentException) - } - }