Skip to content

Commit a31ba45

Browse files
authored
Add support for scoping credentials to SYSTEM (#222)
1 parent 03849ad commit a31ba45

File tree

8 files changed

+57
-16
lines changed

8 files changed

+57
-16
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -422,15 +422,15 @@ az keyvault secret set --vault-name my-vault \
422422

423423
With the System Property or Environment variable being set in this example, only the usernamePassword `testUserWithLabel` will be present in your Jenkins instance.
424424

425-
#### Jenkins Credential ID and Description
425+
#### Jenkins Credential ID, Description and Scope
426426

427-
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.
427+
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.
428428

429429
```bash
430430
az keyvault secret set --vault-name my-vault \
431431
--name testUserWithLabel \
432432
--value example2 \
433-
--tags jenkinsID=myCred description="This is my credential"
433+
--tags jenkinsID=myCred description="This is my credential" scope=system
434434
```
435435

436436
### SecretSource

src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/AzureCredentialsProvider.java

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@
44
import com.azure.security.keyvault.secrets.models.SecretProperties;
55
import com.cloudbees.plugins.credentials.Credentials;
66
import com.cloudbees.plugins.credentials.CredentialsProvider;
7+
import com.cloudbees.plugins.credentials.CredentialsScope;
78
import com.cloudbees.plugins.credentials.CredentialsStore;
89
import com.cloudbees.plugins.credentials.common.IdCredentials;
10+
import com.cloudbees.plugins.credentials.domains.DomainRequirement;
911
import com.github.benmanes.caffeine.cache.Caffeine;
1012
import com.github.benmanes.caffeine.cache.LoadingCache;
1113
import com.google.common.annotations.VisibleForTesting;
1214
import com.microsoft.jenkins.keyvault.SecretClientCache;
1315
import edu.umd.cs.findbugs.annotations.NonNull;
1416
import edu.umd.cs.findbugs.annotations.Nullable;
1517
import hudson.Extension;
18+
import hudson.model.Item;
1619
import hudson.model.ItemGroup;
1720
import hudson.model.ModelObject;
1821
import hudson.security.ACL;
@@ -27,6 +30,7 @@
2730
import java.util.HashMap;
2831
import java.util.List;
2932
import java.util.Map;
33+
import java.util.Objects;
3034
import java.util.function.Supplier;
3135
import java.util.logging.Level;
3236
import java.util.logging.Logger;
@@ -45,6 +49,7 @@ public class AzureCredentialsProvider extends CredentialsProvider {
4549

4650
private static final String CACHE_KEY = "key";
4751
private static final String DEFAULT_TYPE = "string";
52+
private static final String DEFAULT_SCOPE = "GLOBAL";
4853

4954
private final AzureCredentialsStore store = new AzureCredentialsStore(this);
5055

@@ -57,7 +62,6 @@ public class AzureCredentialsProvider extends CredentialsProvider {
5762
public void refreshCredentials() {
5863
cache.invalidateAll();
5964
}
60-
6165
@NonNull
6266
@Override
6367
public <C extends Credentials> List<C> getCredentials(@NonNull Class<C> aClass, @Nullable ItemGroup itemGroup,
@@ -72,10 +76,15 @@ public <C extends Credentials> List<C> getCredentials(@NonNull Class<C> aClass,
7276

7377
for (IdCredentials credential : credentials) {
7478
if (aClass.isAssignableFrom(credential.getClass())) {
75-
// cast to keep generics happy even though we are assignable
76-
list.add(aClass.cast(credential));
79+
if (CredentialsScope.SYSTEM == credential.getScope() && !(itemGroup instanceof Jenkins)) {
80+
LOG.log(Level.FINEST, "getCredentials {0} has SYSTEM scope but the context is not Jenkins. Ignoring credential", credential.getId());
81+
} else if (aClass.isAssignableFrom(credential.getClass())) {
82+
// cast to keep generics happy even though we are assignable
83+
list.add(aClass.cast(credential));
84+
} else {
85+
LOG.log(Level.FINEST, "getCredentials {0} does not match", credential.getId());
86+
}
7787
}
78-
LOG.log(Level.FINEST, "getCredentials {0} does not match", credential.getId());
7988
}
8089
} catch (RuntimeException e) {
8190
LOG.log(Level.WARNING, "Error retrieving secrets from Azure KeyVault: " + e.getMessage(), e);
@@ -87,6 +96,25 @@ public <C extends Credentials> List<C> getCredentials(@NonNull Class<C> aClass,
8796
return Collections.emptyList();
8897
}
8998

99+
@Override
100+
@NonNull
101+
public <C extends Credentials> List<C> getCredentials(@NonNull Class<C> type,
102+
@NonNull Item item,
103+
Authentication authentication) {
104+
// scoping to Items is not supported so using null to not expose SYSTEM credentials to Items.
105+
Objects.requireNonNull(item);
106+
return getCredentials(type, (ItemGroup)null, authentication);
107+
}
108+
109+
@Override
110+
public <C extends Credentials> List<C> getCredentials(@NonNull Class<C> type,
111+
@NonNull Item item,
112+
Authentication authentication,
113+
List<DomainRequirement> domainRequirements) {
114+
// domain requirements not supported
115+
return getCredentials(type, item, authentication);
116+
}
117+
90118
@VisibleForTesting
91119
static String getSecretName(String itemId) {
92120
if (StringUtils.isEmpty(itemId)) {
@@ -136,6 +164,13 @@ private static Collection<IdCredentials> fetchCredentials() {
136164
String type = tags.getOrDefault("type", DEFAULT_TYPE);
137165
String jenkinsID = tags.getOrDefault("jenkinsID", getSecretName(id));
138166
String description = tags.getOrDefault("description", "");
167+
String labelScope = tags.getOrDefault("scope", DEFAULT_SCOPE).toUpperCase();
168+
169+
CredentialsScope scope = CredentialsScope.GLOBAL;
170+
171+
if (tags.containsKey("scope") && labelScope.equals("SYSTEM")) {
172+
scope = CredentialsScope.SYSTEM;
173+
}
139174

140175
// initial implementation didn't require a type
141176
if (tags.containsKey("username") && type.equals(DEFAULT_TYPE)) {
@@ -144,13 +179,13 @@ private static Collection<IdCredentials> fetchCredentials() {
144179

145180
switch (type) {
146181
case "string": {
147-
AzureSecretStringCredentials cred = new AzureSecretStringCredentials(jenkinsID, description, new KeyVaultSecretRetriever(client, id));
182+
AzureSecretStringCredentials cred = new AzureSecretStringCredentials(scope, jenkinsID, description, new KeyVaultSecretRetriever(client, id));
148183
credentials.add(cred);
149184
break;
150185
}
151186
case "username": {
152187
AzureUsernamePasswordCredentials cred = new AzureUsernamePasswordCredentials(
153-
jenkinsID, tags.get("username"), description, new KeyVaultSecretRetriever(client, id)
188+
scope, jenkinsID, tags.get("username"), description, new KeyVaultSecretRetriever(client, id)
154189
);
155190
credentials.add(cred);
156191
break;
@@ -173,7 +208,7 @@ private static Collection<IdCredentials> fetchCredentials() {
173208

174209
}
175210
AzureSSHUserPrivateKeyCredentials cred = new AzureSSHUserPrivateKeyCredentials(
176-
jenkinsID, description, tags.get("username"), usernameSecret, passphrase, new KeyVaultSecretRetriever(client, id)
211+
scope, jenkinsID, description, tags.get("username"), usernameSecret, passphrase, new KeyVaultSecretRetriever(client, id)
177212
);
178213
credentials.add(cred);
179214
break;

src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/sshuserprivatekey/AzureSSHUserPrivateKeyCredentials.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey;
44
import com.cloudbees.jenkins.plugins.sshcredentials.impl.Messages;
55
import com.cloudbees.plugins.credentials.CredentialsProvider;
6+
import com.cloudbees.plugins.credentials.CredentialsScope;
67
import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials;
78
import edu.umd.cs.findbugs.annotations.NonNull;
89
import hudson.Extension;
@@ -24,14 +25,15 @@ public class AzureSSHUserPrivateKeyCredentials extends BaseStandardCredentials i
2425
private final Secret passphrase;
2526

2627
public AzureSSHUserPrivateKeyCredentials(
28+
CredentialsScope scope,
2729
String id,
2830
String description,
2931
String username,
3032
boolean usernameSecret,
3133
Secret passphrase,
3234
Supplier<Secret> privateKey
3335
) {
34-
super(id, description);
36+
super(scope, id, description);
3537
this.username = username;
3638
this.usernameSecret = usernameSecret;
3739
this.passphrase = passphrase;

src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/sshuserprivatekey/AzureSSHUserPrivateKeyCredentialsSnapshotTaker.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public Class<AzureSSHUserPrivateKeyCredentials> type() {
1717
public AzureSSHUserPrivateKeyCredentials snapshot(AzureSSHUserPrivateKeyCredentials credential) {
1818
SecretSnapshot secretSnapshot = new SecretSnapshot(credential.getSecretValue());
1919
return new AzureSSHUserPrivateKeyCredentials(
20+
credential.getScope(),
2021
credential.getId(),
2122
credential.getDescription(),
2223
credential.getUsername(),

src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/string/AzureSecretStringCredentials.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.jenkinsci.plugins.azurekeyvaultplugin.credentials.string;
22

33
import com.cloudbees.plugins.credentials.CredentialsProvider;
4+
import com.cloudbees.plugins.credentials.CredentialsScope;
45
import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials;
56
import edu.umd.cs.findbugs.annotations.NonNull;
67
import hudson.Extension;
@@ -15,8 +16,8 @@ public class AzureSecretStringCredentials extends BaseStandardCredentials implem
1516

1617
private final Supplier<Secret> value;
1718

18-
public AzureSecretStringCredentials(String id, String description, Supplier<Secret> value) {
19-
super(id, description);
19+
public AzureSecretStringCredentials(CredentialsScope scope, String id, String description, Supplier<Secret> value) {
20+
super(scope, id, description);
2021
this.value = value;
2122
}
2223

src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/string/AzureSecretStringCredentialsSnapshotTaker.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public Class<AzureSecretStringCredentials> type() {
1616
@Override
1717
public AzureSecretStringCredentials snapshot(AzureSecretStringCredentials credential) {
1818
SecretSnapshot secretSnapshot = new SecretSnapshot(credential.getSecret());
19-
return new AzureSecretStringCredentials(credential.getId(), credential.getDescription(), secretSnapshot);
19+
return new AzureSecretStringCredentials(credential.getScope(), credential.getId(), credential.getDescription(), secretSnapshot);
2020
}
2121

2222
private static class SecretSnapshot extends Snapshot<Secret> {

src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/usernamepassword/AzureUsernamePasswordCredentials.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.jenkinsci.plugins.azurekeyvaultplugin.credentials.usernamepassword;
22

33
import com.cloudbees.plugins.credentials.CredentialsProvider;
4+
import com.cloudbees.plugins.credentials.CredentialsScope;
45
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
56
import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials;
67
import com.cloudbees.plugins.credentials.impl.Messages;
@@ -18,12 +19,13 @@ public class AzureUsernamePasswordCredentials extends BaseStandardCredentials im
1819
private final String username;
1920

2021
public AzureUsernamePasswordCredentials(
22+
CredentialsScope scope,
2123
String id,
2224
String username,
2325
String description,
2426
Supplier<Secret> password
2527
) {
26-
super(id, description);
28+
super(scope, id, description);
2729
this.password = password;
2830
this.username = Util.fixNull(username);
2931
}

src/main/java/org/jenkinsci/plugins/azurekeyvaultplugin/credentials/usernamepassword/AzureUsernamePasswordCredentialsSnapshotTaker.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public Class<AzureUsernamePasswordCredentials> type() {
1616
@Override
1717
public AzureUsernamePasswordCredentials snapshot(AzureUsernamePasswordCredentials credential) {
1818
SecretSnapshot secretSnapshot = new SecretSnapshot(credential.getPassword());
19-
return new AzureUsernamePasswordCredentials(credential.getId(), credential.getUsername(), credential.getDescription(), secretSnapshot);
19+
return new AzureUsernamePasswordCredentials(credential.getScope(), credential.getId(), credential.getUsername(), credential.getDescription(), secretSnapshot);
2020
}
2121

2222
private static class SecretSnapshot extends Snapshot<Secret> {

0 commit comments

Comments
 (0)