From a31ba451e7d6c4697c457692a446d156e046b35b Mon Sep 17 00:00:00 2001 From: bsloan-icl <85300277+bsloan-icl@users.noreply.github.com> Date: Thu, 19 Oct 2023 12:33:16 +0100 Subject: [PATCH] Add support for scoping credentials to SYSTEM (#222) --- README.md | 6 +-- .../AzureCredentialsProvider.java | 49 ++++++++++++++++--- .../AzureSSHUserPrivateKeyCredentials.java | 4 +- ...serPrivateKeyCredentialsSnapshotTaker.java | 1 + .../string/AzureSecretStringCredentials.java | 5 +- ...eSecretStringCredentialsSnapshotTaker.java | 2 +- .../AzureUsernamePasswordCredentials.java | 4 +- ...rnamePasswordCredentialsSnapshotTaker.java | 2 +- 8 files changed, 57 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index d2cbd85..05b0c29 100644 --- a/README.md +++ b/README.md @@ -422,15 +422,15 @@ az keyvault secret set --vault-name my-vault \ With the System Property or Environment variable being set in this example, only the usernamePassword `testUserWithLabel` will be present in your Jenkins instance. -#### Jenkins Credential ID and Description +#### Jenkins Credential ID, Description and Scope -The ID and description of credentials can be specified with tags on the Azure Key Vault secret. The ID is specified with the tag "jenkinsID" and appears in the Jenkins credentials UI in the "ID" column. This is the credentials ID parameter used in `withCredentials()` calls. The description is specified with the tag "description" and appears in Jenkins credentials UI in the "Name" column. +The ID, description and scope of credentials can be specified with tags on the Azure Key Vault secret. The ID is specified with the tag "jenkinsID" and appears in the Jenkins credentials UI in the "ID" column. This is the credentials ID parameter used in `withCredentials()` calls. The description is specified with the tag "description" and appears in the Jenkins credentials UI in the "Name" column. The scope is specified with the tag "scope" and can be set to "system" or "global". By default the scope will be set to global. ```bash az keyvault secret set --vault-name my-vault \ --name testUserWithLabel \ --value example2 \ - --tags jenkinsID=myCred description="This is my credential" + --tags jenkinsID=myCred description="This is my credential" scope=system ``` ### SecretSource diff --git a/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/AzureCredentialsProvider.java b/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/AzureCredentialsProvider.java index 39af2ea..0b47b38 100644 --- a/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/AzureCredentialsProvider.java +++ b/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/AzureCredentialsProvider.java @@ -4,8 +4,10 @@ import com.azure.security.keyvault.secrets.models.SecretProperties; import com.cloudbees.plugins.credentials.Credentials; import com.cloudbees.plugins.credentials.CredentialsProvider; +import com.cloudbees.plugins.credentials.CredentialsScope; import com.cloudbees.plugins.credentials.CredentialsStore; import com.cloudbees.plugins.credentials.common.IdCredentials; +import com.cloudbees.plugins.credentials.domains.DomainRequirement; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; import com.google.common.annotations.VisibleForTesting; @@ -13,6 +15,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import hudson.Extension; +import hudson.model.Item; import hudson.model.ItemGroup; import hudson.model.ModelObject; import hudson.security.ACL; @@ -27,6 +30,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; @@ -45,6 +49,7 @@ public class AzureCredentialsProvider extends CredentialsProvider { private static final String CACHE_KEY = "key"; private static final String DEFAULT_TYPE = "string"; + private static final String DEFAULT_SCOPE = "GLOBAL"; private final AzureCredentialsStore store = new AzureCredentialsStore(this); @@ -57,7 +62,6 @@ public class AzureCredentialsProvider extends CredentialsProvider { public void refreshCredentials() { cache.invalidateAll(); } - @NonNull @Override public List getCredentials(@NonNull Class aClass, @Nullable ItemGroup itemGroup, @@ -72,10 +76,15 @@ public List getCredentials(@NonNull Class aClass, for (IdCredentials credential : credentials) { if (aClass.isAssignableFrom(credential.getClass())) { - // cast to keep generics happy even though we are assignable - list.add(aClass.cast(credential)); + if (CredentialsScope.SYSTEM == credential.getScope() && !(itemGroup instanceof Jenkins)) { + LOG.log(Level.FINEST, "getCredentials {0} has SYSTEM scope but the context is not Jenkins. Ignoring credential", credential.getId()); + } else if (aClass.isAssignableFrom(credential.getClass())) { + // cast to keep generics happy even though we are assignable + list.add(aClass.cast(credential)); + } else { + LOG.log(Level.FINEST, "getCredentials {0} does not match", credential.getId()); + } } - LOG.log(Level.FINEST, "getCredentials {0} does not match", credential.getId()); } } catch (RuntimeException e) { LOG.log(Level.WARNING, "Error retrieving secrets from Azure KeyVault: " + e.getMessage(), e); @@ -87,6 +96,25 @@ public List getCredentials(@NonNull Class aClass, return Collections.emptyList(); } + @Override + @NonNull + public List getCredentials(@NonNull Class type, + @NonNull Item item, + Authentication authentication) { + // scoping to Items is not supported so using null to not expose SYSTEM credentials to Items. + Objects.requireNonNull(item); + return getCredentials(type, (ItemGroup)null, authentication); + } + + @Override + public List getCredentials(@NonNull Class type, + @NonNull Item item, + Authentication authentication, + List domainRequirements) { + // domain requirements not supported + return getCredentials(type, item, authentication); + } + @VisibleForTesting static String getSecretName(String itemId) { if (StringUtils.isEmpty(itemId)) { @@ -136,6 +164,13 @@ private static Collection fetchCredentials() { String type = tags.getOrDefault("type", DEFAULT_TYPE); String jenkinsID = tags.getOrDefault("jenkinsID", getSecretName(id)); String description = tags.getOrDefault("description", ""); + String labelScope = tags.getOrDefault("scope", DEFAULT_SCOPE).toUpperCase(); + + CredentialsScope scope = CredentialsScope.GLOBAL; + + if (tags.containsKey("scope") && labelScope.equals("SYSTEM")) { + scope = CredentialsScope.SYSTEM; + } // initial implementation didn't require a type if (tags.containsKey("username") && type.equals(DEFAULT_TYPE)) { @@ -144,13 +179,13 @@ private static Collection fetchCredentials() { switch (type) { case "string": { - AzureSecretStringCredentials cred = new AzureSecretStringCredentials(jenkinsID, description, new KeyVaultSecretRetriever(client, id)); + AzureSecretStringCredentials cred = new AzureSecretStringCredentials(scope, jenkinsID, description, new KeyVaultSecretRetriever(client, id)); credentials.add(cred); break; } case "username": { AzureUsernamePasswordCredentials cred = new AzureUsernamePasswordCredentials( - jenkinsID, tags.get("username"), description, new KeyVaultSecretRetriever(client, id) + scope, jenkinsID, tags.get("username"), description, new KeyVaultSecretRetriever(client, id) ); credentials.add(cred); break; @@ -173,7 +208,7 @@ private static Collection fetchCredentials() { } AzureSSHUserPrivateKeyCredentials cred = new AzureSSHUserPrivateKeyCredentials( - jenkinsID, description, tags.get("username"), usernameSecret, passphrase, new KeyVaultSecretRetriever(client, id) + scope, jenkinsID, description, tags.get("username"), usernameSecret, passphrase, new KeyVaultSecretRetriever(client, id) ); credentials.add(cred); break; diff --git a/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/sshuserprivatekey/AzureSSHUserPrivateKeyCredentials.java b/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/sshuserprivatekey/AzureSSHUserPrivateKeyCredentials.java index b50290a..3e44a39 100644 --- a/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/sshuserprivatekey/AzureSSHUserPrivateKeyCredentials.java +++ b/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/sshuserprivatekey/AzureSSHUserPrivateKeyCredentials.java @@ -3,6 +3,7 @@ import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; import com.cloudbees.jenkins.plugins.sshcredentials.impl.Messages; import com.cloudbees.plugins.credentials.CredentialsProvider; +import com.cloudbees.plugins.credentials.CredentialsScope; import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; @@ -24,6 +25,7 @@ public class AzureSSHUserPrivateKeyCredentials extends BaseStandardCredentials i private final Secret passphrase; public AzureSSHUserPrivateKeyCredentials( + CredentialsScope scope, String id, String description, String username, @@ -31,7 +33,7 @@ public AzureSSHUserPrivateKeyCredentials( Secret passphrase, Supplier privateKey ) { - super(id, description); + super(scope, id, description); this.username = username; this.usernameSecret = usernameSecret; this.passphrase = passphrase; diff --git a/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/sshuserprivatekey/AzureSSHUserPrivateKeyCredentialsSnapshotTaker.java b/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/sshuserprivatekey/AzureSSHUserPrivateKeyCredentialsSnapshotTaker.java index 635a516..03a19de 100644 --- a/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/sshuserprivatekey/AzureSSHUserPrivateKeyCredentialsSnapshotTaker.java +++ b/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/sshuserprivatekey/AzureSSHUserPrivateKeyCredentialsSnapshotTaker.java @@ -17,6 +17,7 @@ public Class type() { public AzureSSHUserPrivateKeyCredentials snapshot(AzureSSHUserPrivateKeyCredentials credential) { SecretSnapshot secretSnapshot = new SecretSnapshot(credential.getSecretValue()); return new AzureSSHUserPrivateKeyCredentials( + credential.getScope(), credential.getId(), credential.getDescription(), credential.getUsername(), diff --git a/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/string/AzureSecretStringCredentials.java b/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/string/AzureSecretStringCredentials.java index dc3c520..661b5ce 100644 --- a/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/string/AzureSecretStringCredentials.java +++ b/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/string/AzureSecretStringCredentials.java @@ -1,6 +1,7 @@ package org.jenkinsci.plugins.azurekeyvaultplugin.credentials.string; import com.cloudbees.plugins.credentials.CredentialsProvider; +import com.cloudbees.plugins.credentials.CredentialsScope; import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; @@ -15,8 +16,8 @@ public class AzureSecretStringCredentials extends BaseStandardCredentials implem private final Supplier value; - public AzureSecretStringCredentials(String id, String description, Supplier value) { - super(id, description); + public AzureSecretStringCredentials(CredentialsScope scope, String id, String description, Supplier value) { + super(scope, id, description); this.value = value; } diff --git a/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/string/AzureSecretStringCredentialsSnapshotTaker.java b/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/string/AzureSecretStringCredentialsSnapshotTaker.java index 1565535..b79c0f5 100644 --- a/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/string/AzureSecretStringCredentialsSnapshotTaker.java +++ b/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/string/AzureSecretStringCredentialsSnapshotTaker.java @@ -16,7 +16,7 @@ public Class type() { @Override public AzureSecretStringCredentials snapshot(AzureSecretStringCredentials credential) { SecretSnapshot secretSnapshot = new SecretSnapshot(credential.getSecret()); - return new AzureSecretStringCredentials(credential.getId(), credential.getDescription(), secretSnapshot); + return new AzureSecretStringCredentials(credential.getScope(), credential.getId(), credential.getDescription(), secretSnapshot); } private static class SecretSnapshot extends Snapshot { diff --git a/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/usernamepassword/AzureUsernamePasswordCredentials.java b/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/usernamepassword/AzureUsernamePasswordCredentials.java index 9f6fbdd..d9da9fa 100644 --- a/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/usernamepassword/AzureUsernamePasswordCredentials.java +++ b/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/usernamepassword/AzureUsernamePasswordCredentials.java @@ -1,6 +1,7 @@ package org.jenkinsci.plugins.azurekeyvaultplugin.credentials.usernamepassword; import com.cloudbees.plugins.credentials.CredentialsProvider; +import com.cloudbees.plugins.credentials.CredentialsScope; import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials; import com.cloudbees.plugins.credentials.impl.Messages; @@ -18,12 +19,13 @@ public class AzureUsernamePasswordCredentials extends BaseStandardCredentials im private final String username; public AzureUsernamePasswordCredentials( + CredentialsScope scope, String id, String username, String description, Supplier password ) { - super(id, description); + super(scope, id, description); this.password = password; this.username = Util.fixNull(username); } diff --git a/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/usernamepassword/AzureUsernamePasswordCredentialsSnapshotTaker.java b/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/usernamepassword/AzureUsernamePasswordCredentialsSnapshotTaker.java index 9444521..e097ee6 100644 --- a/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/usernamepassword/AzureUsernamePasswordCredentialsSnapshotTaker.java +++ b/src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/usernamepassword/AzureUsernamePasswordCredentialsSnapshotTaker.java @@ -16,7 +16,7 @@ public Class type() { @Override public AzureUsernamePasswordCredentials snapshot(AzureUsernamePasswordCredentials credential) { SecretSnapshot secretSnapshot = new SecretSnapshot(credential.getPassword()); - return new AzureUsernamePasswordCredentials(credential.getId(), credential.getUsername(), credential.getDescription(), secretSnapshot); + return new AzureUsernamePasswordCredentials(credential.getScope(), credential.getId(), credential.getUsername(), credential.getDescription(), secretSnapshot); } private static class SecretSnapshot extends Snapshot {