Skip to content

Commit

Permalink
Add Storage Account Entra ID credentials cache (#271)
Browse files Browse the repository at this point in the history
  • Loading branch information
timja authored Dec 1, 2024
1 parent 2d86cc9 commit 4881e9f
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/main/java/com/microsoft/azure/util/AzureCredentials.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials;
import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl;
import com.microsoft.jenkins.credentials.AzureResourceManagerCache;
import com.microsoft.jenkins.credentials.BlobServiceClientCache;
import com.microsoft.jenkins.keyvault.SecretClientCache;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
Expand Down Expand Up @@ -453,6 +454,7 @@ public AzureCredentials(
data = new ServicePrincipal(subscriptionId, clientId, clientSecret);
SecretClientCache.invalidateCache();
AzureResourceManagerCache.invalidateCache();
BlobServiceClientCache.invalidateCache();

Check warning on line 457 in src/main/java/com/microsoft/azure/util/AzureCredentials.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 457 is not covered by tests
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
Copyright 2024 Tim Jacomb
Licensed 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 com.microsoft.jenkins.credentials;

import com.azure.core.credential.TokenCredential;
import com.azure.storage.blob.BlobServiceClient;
import com.azure.storage.blob.BlobServiceClientBuilder;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.microsoft.azure.util.AzureBaseCredentials;
import com.microsoft.azure.util.AzureCredentialUtil;
import com.microsoft.azure.util.AzureCredentials;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import io.jenkins.plugins.azuresdk.HttpClientRetriever;
import java.time.Duration;
import java.util.Objects;

public final class BlobServiceClientCache {

private static final long MAX_SIZE = 50L;
private static final Duration EXPIRE_AFTER = Duration.ofHours(24);

private static final LoadingCache<BlobServiceClientCache.CacheKey, BlobServiceClient> CACHE = Caffeine.newBuilder()
.maximumSize(MAX_SIZE)
.expireAfterWrite(EXPIRE_AFTER)
.build(BlobServiceClientCache::createClient);

private BlobServiceClientCache() {}

@CheckForNull
public static BlobServiceClient get(String credentialsId, String blobServiceEndpoint) {
return CACHE.get(new CacheKey(credentialsId, blobServiceEndpoint));
}

/**
* Used to notify when credentials change, e.g. service principal secret updated.
*/
public static void invalidateCache() {
// Could be optimised to only invalidate specific keys in the future if required
CACHE.invalidateAll();
}

private static BlobServiceClient createClient(CacheKey key) {
AzureBaseCredentials credential = AzureCredentialUtil.getCredential(null, key.credentialsId);
if (credential == null) {
return null;
}

TokenCredential tokenCredential = AzureCredentials.getTokenCredential(credential);

return new BlobServiceClientBuilder()
.credential(tokenCredential)
.endpoint(key.blobServiceEndpoint)
.httpClient(HttpClientRetriever.get())
.buildClient();
}

private static class CacheKey {
private final String credentialsId;
private final String blobServiceEndpoint;

CacheKey(String credentialsId, String blobServiceEndpoint) {
this.credentialsId = credentialsId;
this.blobServiceEndpoint = blobServiceEndpoint;
}

@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
CacheKey cacheKey = (CacheKey) o;
return Objects.equals(credentialsId, cacheKey.credentialsId)
&& Objects.equals(blobServiceEndpoint, cacheKey.blobServiceEndpoint);
}

@Override
public int hashCode() {
return Objects.hash(credentialsId, blobServiceEndpoint);

Check warning on line 92 in src/main/java/com/microsoft/jenkins/credentials/BlobServiceClientCache.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 34-92 are not covered by tests
}
}
}

0 comments on commit 4881e9f

Please sign in to comment.