Skip to content

Commit 528f9ff

Browse files
authored
Add support for storage account RBAC only access (#592)
1 parent 3c1fb33 commit 528f9ff

File tree

13 files changed

+230
-120
lines changed

13 files changed

+230
-120
lines changed

pom.xml

+4-15
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,6 @@
2323
</license>
2424
</licenses>
2525

26-
<developers>
27-
<developer>
28-
<id>xuzhang</id>
29-
<name>Xu Zhang</name>
30-
<email>[email protected]</email>
31-
</developer>
32-
<developer>
33-
<id>timja</id>
34-
<name>Tim Jacomb</name>
35-
</developer>
36-
</developers>
37-
3826
<scm>
3927
<connection>scm:git:https://github.com/${gitHubRepo}</connection>
4028
<developerConnection>scm:git:https://github.com/${gitHubRepo}</developerConnection>
@@ -45,7 +33,8 @@
4533
<properties>
4634
<changelist>9999-SNAPSHOT</changelist>
4735
<gitHubRepo>jenkinsci/azure-vm-agents-plugin</gitHubRepo>
48-
<jenkins.version>2.426.3</jenkins.version>
36+
<jenkins.baseline>2.426</jenkins.baseline>
37+
<jenkins.version>${jenkins.baseline}.3</jenkins.version>
4938
<maven.javadoc.skip>true</maven.javadoc.skip>
5039
<hpi.compatibleSinceVersion>883</hpi.compatibleSinceVersion>
5140
</properties>
@@ -54,7 +43,7 @@
5443
<dependencies>
5544
<dependency>
5645
<groupId>io.jenkins.tools.bom</groupId>
57-
<artifactId>bom-2.426.x</artifactId>
46+
<artifactId>bom-${jenkins.baseline}.x</artifactId>
5847
<version>3208.vb_21177d4b_cd9</version>
5948
<scope>import</scope>
6049
<type>pom</type>
@@ -71,7 +60,7 @@
7160
<dependency>
7261
<groupId>org.jenkins-ci.plugins</groupId>
7362
<artifactId>azure-credentials</artifactId>
74-
<version>293.vb_d506148f506</version>
63+
<version>341.v4881e9f4ffea_</version>
7564
</dependency>
7665
<dependency>
7766
<groupId>org.jenkins-ci.plugins</groupId>

src/main/java/com/microsoft/azure/vmagent/AzureVMAgentCleanUpTask.java

+26-10
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,14 @@ private static class DeploymentInfo implements Serializable {
7979
String resourceGroupName,
8080
String deploymentName,
8181
String scriptUri,
82-
int deleteAttempts) {
82+
int deleteAttempts,
83+
boolean isUseEntraIdForStorageAccount) {
8384
this.cloudName = cloudName;
8485
this.deploymentName = deploymentName;
8586
this.resourceGroupName = resourceGroupName;
8687
this.scriptUri = scriptUri;
8788
this.attemptsRemaining = deleteAttempts;
89+
this.isUseEntraIdForStorageAccount = isUseEntraIdForStorageAccount;
8890
}
8991

9092
String getCloudName() {
@@ -103,6 +105,10 @@ String getScriptUri() {
103105
return scriptUri;
104106
}
105107

108+
public boolean isUseEntraIdForStorageAccount() {
109+
return isUseEntraIdForStorageAccount;
110+
}
111+
106112
boolean hasAttemptsRemaining() {
107113
return attemptsRemaining > 0;
108114
}
@@ -111,11 +117,12 @@ void decrementAttemptsRemaining() {
111117
attemptsRemaining--;
112118
}
113119

114-
private String cloudName;
115-
private String deploymentName;
116-
private String resourceGroupName;
117-
private String scriptUri;
120+
private final String cloudName;
121+
private final String deploymentName;
122+
private final String resourceGroupName;
123+
private final String scriptUri;
118124
private int attemptsRemaining;
125+
private final boolean isUseEntraIdForStorageAccount;
119126
}
120127

121128
private static final int CLEAN_TIMEOUT_IN_MINUTES = 15;
@@ -165,11 +172,13 @@ public ConcurrentLinkedQueue<DeploymentInfo> getDeploymentsToClean() {
165172
public void registerDeployment(String cloudName,
166173
String resourceGroupName,
167174
String deploymentName,
168-
String scriptUri) {
175+
String scriptUri,
176+
boolean isUseEntraIdForStorageAccount) {
169177
LOGGER.log(Level.FINE, "Registering deployment {0} in {1}",
170178
new Object[]{deploymentName, resourceGroupName});
171179
DeploymentInfo newDeploymentToClean =
172-
new DeploymentInfo(cloudName, resourceGroupName, deploymentName, scriptUri, MAX_DELETE_ATTEMPTS);
180+
new DeploymentInfo(cloudName, resourceGroupName, deploymentName, scriptUri, MAX_DELETE_ATTEMPTS,
181+
isUseEntraIdForStorageAccount);
173182
deploymentsToClean.add(newDeploymentToClean);
174183

175184
syncDeploymentsToClean();
@@ -247,7 +256,8 @@ public void cleanDeployments(long successTimeoutInMinutes, long failTimeoutInMin
247256
azureClient.deployments()
248257
.deleteByResourceGroup(info.getResourceGroupName(), info.getDeploymentName());
249258
if (StringUtils.isNotBlank(info.scriptUri)) {
250-
delegate.removeStorageBlob(new URI(info.scriptUri), info.getResourceGroupName());
259+
delegate.removeStorageBlob(new URI(info.scriptUri), info.getResourceGroupName(),
260+
cloud.getAzureCredentialsId(), info.isUseEntraIdForStorageAccount());
251261
}
252262
} else if (state.equalsIgnoreCase("succeeded")
253263
&& diffTimeInMinutes > successTimeoutInMinutes) {
@@ -257,7 +267,8 @@ public void cleanDeployments(long successTimeoutInMinutes, long failTimeoutInMin
257267
azureClient.deployments()
258268
.deleteByResourceGroup(info.getResourceGroupName(), info.getDeploymentName());
259269
if (StringUtils.isNotBlank(info.scriptUri)) {
260-
delegate.removeStorageBlob(new URI(info.scriptUri), info.getResourceGroupName());
270+
delegate.removeStorageBlob(new URI(info.scriptUri), info.getResourceGroupName(),
271+
cloud.getAzureCredentialsId(), info.isUseEntraIdForStorageAccount());
261272
}
262273
} else {
263274
LOGGER.log(getNormalLoggingLevel(), "Deployment newer than timeout, keeping");
@@ -442,7 +453,12 @@ private int getPriority(GenericResource resource) {
442453
new Object[]{resource.name(), resourceGroup});
443454
azureClient.genericResources().deleteById(resource.id());
444455
if (osDiskURI != null) {
445-
serviceDelegate.removeStorageBlob(osDiskURI, resourceGroup);
456+
String jenkinsTemplateTag = resource.tags().get(Constants.AZURE_TEMPLATE_TAG_NAME);
457+
boolean useEntraIdForStorageAccount = cloud
458+
.getTemplate(jenkinsTemplateTag)
459+
.isUseEntraIdForStorageAccount();
460+
serviceDelegate.removeStorageBlob(osDiskURI, resourceGroup,
461+
cloud.getAzureCredentialsId(), useEntraIdForStorageAccount);
446462
}
447463
if (managedOsDiskId != null) {
448464
azureClient.disks().deleteById(managedOsDiskId);

src/main/java/com/microsoft/azure/vmagent/AzureVMAgentTemplate.java

+62-34
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,38 @@ public String getGalleryResourceGroup() {
223223

224224
private static final int GEN_STORAGE_ACCOUNT_UID_LENGTH = 22;
225225

226+
private static final String VALIDATE_DEPLOYMENT_TEMPLATE_MESSAGE = "Verify configuration:\n\t{0}{1}{2}{3}"
227+
+ "resourceGroupName: {4};\n\t"
228+
+ "templateName: {5};\n\t"
229+
+ "labels: {6};\n\t"
230+
+ "location: {7};\n\t"
231+
+ "virtualMachineSize: {8};\n\t"
232+
+ "storageAccountName: {9};\n\t"
233+
+ "noOfParallelJobs: {10};\n\t"
234+
+ "imageTopLevelType: {11};\n\t"
235+
+ "builtInImage: {12};\n\t"
236+
+ "image: {13};\n\t"
237+
+ "osType: {14};\n\t"
238+
+ "id: {15};\n\t"
239+
+ "publisher: {16};\n\t"
240+
+ "offer: {17};\n\t"
241+
+ "sku: {18};\n\t"
242+
+ "version: {19};\n\t"
243+
+ "sshConfig: {20};\n\t"
244+
+ "initScript: {21};\n\t"
245+
+ "credentialsId: {22};\n\t"
246+
+ "virtualNetworkName: {23};\n\t"
247+
+ "virtualNetworkResourceGroupName: {24};\n\t"
248+
+ "subnetName: {25};\n\t"
249+
+ "privateIP: {26};\n\t"
250+
+ "nsgName: {27};\n\t"
251+
+ "jvmOptions: {28};\n\t"
252+
+ "galleryName: {29}\n\t"
253+
+ "galleryImageDefinition: {30}\n\t"
254+
+ "galleryImageVersion: {31}\n\t"
255+
+ "galleryResourceGroup: {32}\n\t"
256+
+ "gallerySubscriptionId: {33}";
257+
226258
// General Configuration
227259
private final String templateName;
228260

@@ -321,7 +353,7 @@ public String getGalleryResourceGroup() {
321353
// If disabled, will not attempt to verify or use
322354
private boolean templateDisabled;
323355

324-
private String templateStatusDetails;
356+
private transient String templateStatusDetails;
325357

326358
private transient AzureVMCloud azureCloud;
327359

@@ -338,6 +370,7 @@ public String getGalleryResourceGroup() {
338370
private boolean enableMSI;
339371

340372
private boolean enableUAMI;
373+
private boolean useEntraIdForStorageAccount;
341374

342375
private String uamiID;
343376

@@ -572,6 +605,15 @@ public void setMaxVirtualMachinesLimit(int maxVirtualMachinesLimit) {
572605
this.maxVirtualMachinesLimit = maxVirtualMachinesLimit;
573606
}
574607

608+
public boolean isUseEntraIdForStorageAccount() {
609+
return useEntraIdForStorageAccount;
610+
}
611+
612+
@DataBoundSetter
613+
public void setUseEntraIdForStorageAccount(boolean useEntraIdForStorageAccount) {
614+
this.useEntraIdForStorageAccount = useEntraIdForStorageAccount;
615+
}
616+
575617
public String getJavaPath() {
576618
return javaPath;
577619
}
@@ -1454,6 +1496,10 @@ public ListBoxModel doFillNsgNameItems(
14541496

14551497
try {
14561498
AzureResourceManager azureClient = AzureResourceManagerCache.get(azureCredentialsId);
1499+
if (azureClient == null) {
1500+
return model;
1501+
}
1502+
14571503
String resourceGroupName = AzureVMCloud.getResourceGroupName(
14581504
resourceGroupReferenceType, newResourceGroupName, existingResourceGroupName);
14591505
PagedIterable<NetworkSecurityGroup> nsgs =
@@ -1634,6 +1680,9 @@ public ListBoxModel doFillExistingStorageAccountNameItems(
16341680

16351681
try {
16361682
AzureResourceManager azureClient = AzureResourceManagerCache.get(azureCredentialsId);
1683+
if (azureClient == null) {
1684+
return model;
1685+
}
16371686

16381687
String resourceGroupName = AzureVMCloud.getResourceGroupName(
16391688
resourceGroupReferenceType, newResourceGroupName, existingResourceGroupName);
@@ -1820,6 +1869,8 @@ public FormValidation doVerifyConfiguration(
18201869
@QueryParameter String storageAccountNameReferenceType,
18211870
@QueryParameter String newStorageAccountName,
18221871
@QueryParameter String existingStorageAccountName,
1872+
@QueryParameter boolean useEntraIdForStorageAccount,
1873+
@QueryParameter String uamiID,
18231874
@QueryParameter String storageAccountType,
18241875
@QueryParameter String noOfParallelJobs,
18251876
@QueryParameter String imageTopLevelType,
@@ -1844,7 +1895,8 @@ public FormValidation doVerifyConfiguration(
18441895
@QueryParameter String subnetName,
18451896
@QueryParameter boolean usePrivateIP,
18461897
@QueryParameter String nsgName,
1847-
@QueryParameter String jvmOptions) {
1898+
@QueryParameter String jvmOptions
1899+
) {
18481900
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
18491901

18501902
ImageReferenceTypeClass image = new ImageReferenceTypeClass(
@@ -1870,38 +1922,7 @@ public FormValidation doVerifyConfiguration(
18701922
cloud.getResourceGroupName(), templateName);
18711923
}
18721924

1873-
LOGGER.log(Level.INFO,
1874-
"Verify configuration:\n\t{0}{1}{2}{3}"
1875-
+ "resourceGroupName: {4};\n\t"
1876-
+ "templateName: {5};\n\t"
1877-
+ "labels: {6};\n\t"
1878-
+ "location: {7};\n\t"
1879-
+ "virtualMachineSize: {8};\n\t"
1880-
+ "storageAccountName: {9};\n\t"
1881-
+ "noOfParallelJobs: {10};\n\t"
1882-
+ "imageTopLevelType: {11};\n\t"
1883-
+ "builtInImage: {12};\n\t"
1884-
+ "image: {13};\n\t"
1885-
+ "osType: {14};\n\t"
1886-
+ "id: {15};\n\t"
1887-
+ "publisher: {16};\n\t"
1888-
+ "offer: {17};\n\t"
1889-
+ "sku: {18};\n\t"
1890-
+ "version: {19};\n\t"
1891-
+ "sshConfig: {20};\n\t"
1892-
+ "initScript: {21};\n\t"
1893-
+ "credentialsId: {22};\n\t"
1894-
+ "virtualNetworkName: {23};\n\t"
1895-
+ "virtualNetworkResourceGroupName: {24};\n\t"
1896-
+ "subnetName: {25};\n\t"
1897-
+ "privateIP: {26};\n\t"
1898-
+ "nsgName: {27};\n\t"
1899-
+ "jvmOptions: {28};\n\t"
1900-
+ "galleryName: {29}\n\t"
1901-
+ "galleryImageDefinition: {30}\n\t"
1902-
+ "galleryImageVersion: {31}\n\t"
1903-
+ "galleryResourceGroup: {32}\n\t"
1904-
+ "gallerySubscriptionId: {33}",
1925+
LOGGER.log(Level.INFO, VALIDATE_DEPLOYMENT_TEMPLATE_MESSAGE,
19051926
new Object[]{
19061927
"",
19071928
"",
@@ -1946,6 +1967,13 @@ public FormValidation doVerifyConfiguration(
19461967
return FormValidation.error(result);
19471968
}
19481969

1970+
if (useEntraIdForStorageAccount) {
1971+
if (StringUtils.isBlank(uamiID)) {
1972+
return FormValidation.error("If using Entra ID authentication for storage account "
1973+
+ "user assigned managed identity must be enabled as well.");
1974+
}
1975+
}
1976+
19491977
AzureSSHLauncher azureComputerLauncher = new AzureSSHLauncher();
19501978
if (StringUtils.isNotBlank(sshConfig)) {
19511979
azureComputerLauncher.setSshConfig(sshConfig);

src/main/java/com/microsoft/azure/vmagent/AzureVMCloud.java

+10-2
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ public AzureVMAgent createProvisionedAgent(
595595
getServiceDelegate().setVirtualMachineDetails(newAgent, template);
596596
return newAgent;
597597
} else {
598-
LOGGER.log(Level.INFO,
598+
LOGGER.log(Level.FINE,
599599
"Deployment {0} not yet finished ({1}): {2}:{3} - waited {4} seconds",
600600
new Object[]{deploymentName, state, type, resource,
601601
(maxTries - triesLeft) * sleepTimeInSeconds});
@@ -873,7 +873,7 @@ public Node call() throws AzureCloudException {
873873
}
874874

875875
try {
876-
LOGGER.log(Level.INFO, "Adding agent {0} to Jenkins nodes",
876+
LOGGER.log(Level.FINE, "Adding agent {0} to Jenkins nodes",
877877
agent.getNodeName());
878878
// Place the node in blocked state while it starts.
879879
try {
@@ -1282,6 +1282,10 @@ public FormValidation doVerifyConfiguration(
12821282
resourceGroupName = Constants.DEFAULT_RESOURCE_GROUP_NAME;
12831283
}
12841284
AzureResourceManager azureClient = AzureResourceManagerCache.get(azureCredentialsId);
1285+
if (azureClient == null) {
1286+
return FormValidation.error("Cannot get Azure client");
1287+
}
1288+
12851289
final String validationResult = AzureVMManagementServiceDelegate
12861290
.getInstance(azureClient, azureCredentialsId)
12871291
.verifyConfiguration(resourceGroupName, deploymentTimeout);
@@ -1317,6 +1321,10 @@ public ListBoxModel doFillExistingResourceGroupNameItems(@QueryParameter String
13171321

13181322
try {
13191323
final AzureResourceManager azureClient = AzureResourceManagerCache.get(azureCredentialsId);
1324+
if (azureClient == null) {
1325+
return model;
1326+
}
1327+
13201328
Set<String> resourceGroupNames = new HashSet<>();
13211329
for (ResourceGroup resourceGroup : azureClient.resourceGroups().list()) {
13221330
resourceGroupNames.add(resourceGroup.name());

0 commit comments

Comments
 (0)