-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 28170a7
Showing
18 changed files
with
1,352 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
target | ||
|
||
# mvn hpi:run | ||
work | ||
|
||
# IntelliJ IDEA project files | ||
*.iml | ||
*.iws | ||
*.ipr | ||
.idea | ||
|
||
# Eclipse project files | ||
.settings | ||
.classpath | ||
.project |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
# Jenkins Folder Credentials Importer | ||
|
||
This Jenkins plugin allows importing credentials into a folder from an upstream source. | ||
For example, it can be used to import `SYSTEM` scoped (i.e. inaccessible to jobs) credentials from a specific domain in the system store into a domain in the folder store with `GLOBAL` scope so that they are accessible only to jobs within that folder. Like so e.g. all credentials can be configured in a [JCasC](https://www.jenkins.io/projects/jcasc/) config and then be imported into folders. | ||
|
||
### Usage | ||
|
||
This plugin can only be configured as a folder property with the [Job DSL](https://plugins.jenkins.io/job-dsl/) (or similar) plugin. | ||
|
||
For Job DSL: | ||
|
||
``` | ||
folder { | ||
properties { | ||
importCredentials { | ||
// Whether all other credentials that weren't imported should be removed. | ||
// Default: false. | ||
clear(<bool>) | ||
// Whether credentials should always be re-imported when this property is saved, even if config did not change. | ||
// Default: true. | ||
update(<bool>) | ||
imports { | ||
// Block can be specified multiple times | ||
from { | ||
source { | ||
// One or more ANT glob patterns for matching credentials by id. | ||
// Required. | ||
ids([<pattern>...]) | ||
// One or more ANT glob patterns for matching credentials by domain. | ||
// Optional. | ||
domains([<pattern>...]) | ||
// One or more URIs for matching credentials by their domain's specifications. | ||
// Note that if a domain does not have any specifications its credentials will always match, regardless of the URIs specified here. | ||
// Optional. | ||
uris([<uri>...]) | ||
// One or more strings for matching credentials by scope. | ||
// Valid values: "SYSTEM", "GLOBAL", "USER". | ||
// Default: "GLOBAL", "USER". | ||
scopes([<scope>...]) | ||
// One or more strings for matching credentials by source store. | ||
// Valid values: "SYSTEM" (= system store only), "JOB" (= all stores available to job doing the configuration). | ||
// Default: "SYSTEM", "JOB". | ||
sources([<source>...]) | ||
// Whether the import should fail when no credentials were matched. | ||
required(<bool>) | ||
} | ||
// Optional block | ||
to { | ||
// If specified the imported credentials' scope is set to this value. | ||
// Optional. | ||
scope(<scope>) | ||
// One or more ANT glob patterns specifying which domains of the imported credentials should be copied into the folder store. | ||
// Optional. | ||
copiedDomains([<pattern>...]) | ||
// Domain into which the credentials should be imported if copiedDomains was not specified or did not take effect. The domain is created if it does not exist yet. | ||
// Some other domain specification properties are left out here and can be looked up in the Job DSL domain specification documentation. | ||
// Optional. | ||
defaultDomain { | ||
name(<string>) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
Using this property requires Job DSL jobs to be running with an authentication other than `SYSTEM` (see e.g. [Authorize Project](https://plugins.jenkins.io/authorize-project/)). | ||
|
||
Note that the required permissions depend on the configuration. For example, importing credentials from the `SYSTEM` scope requires the `ADMINISTER` permission, and when using `copiedDomains` or `defaultDomain` the `MANAGE_DOMAINS` permission is required. In any case the credentials `CREATE` and `UPDATE` permissions are also required. | ||
|
||
### Example | ||
|
||
This example imports `SYSTEM` scoped credentials under the 'Source' domain from the system store into the folder store under the 'Target' domain with `GLOBAL` scope. | ||
|
||
``` | ||
importCredentials { | ||
clear(true) | ||
imports { | ||
from { | ||
source { | ||
ids(['*']) | ||
domains(['Source']) | ||
sources(['SYSTEM']) | ||
scopes(['SYSTEM']) | ||
} | ||
to { | ||
scope('GLOBAL') | ||
defaultDomain { | ||
name('Target') | ||
} | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
### Development | ||
|
||
Starting a development Jenkins instance with this plugin: `mvn hpi:run` | ||
|
||
Building the plugin: `mvn package` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||
|
||
<modelVersion>4.0.0</modelVersion> | ||
<parent> | ||
<groupId>org.jenkins-ci.plugins</groupId> | ||
<artifactId>plugin</artifactId> | ||
<version>4.41</version> | ||
<relativePath /> | ||
</parent> | ||
|
||
<groupId>swiss.dasch.plugins</groupId> | ||
<artifactId>folder-credentials-importer</artifactId> | ||
<version>${revision}</version> | ||
<name>Folder Credentials Importer Plugin</name> | ||
|
||
<packaging>hpi</packaging> | ||
|
||
<properties> | ||
<revision>1.0</revision> | ||
|
||
<jenkins.version>2.332.4</jenkins.version> | ||
|
||
<java.level>8</java.level> | ||
</properties> | ||
|
||
<repositories> | ||
<repository> | ||
<id>repo.jenkins-ci.org</id> | ||
<url>https://repo.jenkins-ci.org/public/</url> | ||
</repository> | ||
</repositories> | ||
<pluginRepositories> | ||
<pluginRepository> | ||
<id>repo.jenkins-ci.org</id> | ||
<url>https://repo.jenkins-ci.org/public/</url> | ||
</pluginRepository> | ||
</pluginRepositories> | ||
|
||
<dependencyManagement> | ||
<dependencies> | ||
<dependency> | ||
<!-- Pick up common dependencies for the selected LTS line: https://github.com/jenkinsci/bom#usage --> | ||
<groupId>io.jenkins.tools.bom</groupId> | ||
<artifactId>bom-2.332.x</artifactId> | ||
<version>1451.v15f1fdb_772a_f</version> | ||
<type>pom</type> | ||
<scope>import</scope> | ||
</dependency> | ||
</dependencies> | ||
</dependencyManagement> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.jenkins-ci.plugins</groupId> | ||
<artifactId>structs</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.jenkins-ci.plugins</groupId> | ||
<artifactId>cloudbees-folder</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.jenkins-ci.plugins</groupId> | ||
<artifactId>credentials</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.jenkins-ci.plugins</groupId> | ||
<artifactId>job-dsl</artifactId> | ||
<version>1.79</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.jenkins-ci.plugins</groupId> | ||
<artifactId>authorize-project</artifactId> | ||
<version>1.4.0</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.jenkins-ci.plugins</groupId> | ||
<artifactId>matrix-auth</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.jenkins-ci.plugins</groupId> | ||
<artifactId>credentials-binding</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.jenkins</groupId> | ||
<artifactId>configuration-as-code</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
</project> |
94 changes: 94 additions & 0 deletions
94
...java/swiss/dasch/plugins/foldercredentialsimporter/CredentialsImporterFolderProperty.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package swiss.dasch.plugins.foldercredentialsimporter; | ||
|
||
import java.util.Optional; | ||
import java.util.stream.StreamSupport; | ||
|
||
import org.jenkinsci.Symbol; | ||
import org.kohsuke.stapler.DataBoundConstructor; | ||
import org.kohsuke.stapler.DataBoundSetter; | ||
|
||
import com.cloudbees.hudson.plugins.folder.AbstractFolder; | ||
import com.cloudbees.hudson.plugins.folder.AbstractFolderProperty; | ||
import com.cloudbees.hudson.plugins.folder.AbstractFolderPropertyDescriptor; | ||
import com.cloudbees.plugins.credentials.CredentialsProvider; | ||
import com.cloudbees.plugins.credentials.CredentialsStore; | ||
|
||
import hudson.Extension; | ||
import hudson.model.Executor; | ||
import hudson.model.Queue.Executable; | ||
import hudson.security.ACL; | ||
import jenkins.model.Jenkins; | ||
|
||
public class CredentialsImporterFolderProperty extends AbstractFolderProperty<AbstractFolder<?>> { | ||
|
||
private final Importer importer; | ||
|
||
private boolean initialized; | ||
|
||
@DataBoundConstructor | ||
public CredentialsImporterFolderProperty(Import[] imports) { | ||
Executor executor = Executor.currentExecutor(); | ||
Executable executable = executor != null ? executor.getCurrentExecutable() : null; | ||
|
||
// If there's no executable/job and current authentication is already SYSTEM | ||
// then we import the credentials as SYSTEM (e.g. when property is created by | ||
// JCasC). Otherwise when this is created by a job(-dsl) run then we require an | ||
// authentication that is used for importing the credentials other than SYSTEM. | ||
@SuppressWarnings("deprecation") | ||
boolean isSystemImporter = executable == null && Jenkins.getAuthentication() == ACL.SYSTEM; | ||
|
||
this.importer = new Importer(imports, isSystemImporter); | ||
} | ||
|
||
@DataBoundSetter | ||
public void setClear(boolean clear) { | ||
this.importer.setClear(clear); | ||
} | ||
|
||
@DataBoundSetter | ||
public void setUpdate(boolean update) { | ||
this.importer.setUpdate(update); | ||
} | ||
|
||
@Override | ||
protected synchronized void setOwner(AbstractFolder<?> owner) { | ||
super.setOwner(owner); | ||
|
||
// Only extract credentials once when property is first added/updated | ||
// and not when the property is (re-)loaded from disk | ||
if (this.initialized) { | ||
return; | ||
} | ||
|
||
this.initialized = true; | ||
|
||
this.importer.fill(owner); | ||
} | ||
|
||
public static void importCredentials(AbstractFolder<?> folder) { | ||
CredentialsImporterFolderProperty property = folder.getProperties() | ||
.get(CredentialsImporterFolderProperty.class); | ||
|
||
if (property != null) { | ||
Optional<CredentialsStore> storeLookup = StreamSupport | ||
.stream(CredentialsProvider.lookupStores(folder).spliterator(), false) | ||
.filter(s -> s.getContext() == folder).findFirst(); | ||
|
||
storeLookup.ifPresent(property.importer::into); | ||
} | ||
} | ||
|
||
@Extension | ||
@Symbol("importCredentials") | ||
public static class DescriptorImpl extends AbstractFolderPropertyDescriptor { | ||
@DataBoundConstructor | ||
public DescriptorImpl() { | ||
} | ||
|
||
@Override | ||
public String getDisplayName() { | ||
return Messages.FolderCredentialsImporter_DisplayName(); | ||
} | ||
} | ||
|
||
} |
15 changes: 15 additions & 0 deletions
15
src/main/java/swiss/dasch/plugins/foldercredentialsimporter/CredentialsSource.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package swiss.dasch.plugins.foldercredentialsimporter; | ||
|
||
public enum CredentialsSource { | ||
/** | ||
* Credentials from | ||
* {@link com.cloudbees.plugins.credentials.SystemCredentialsProvider} | ||
*/ | ||
SYSTEM, | ||
|
||
/** | ||
* Credentials available to job (includes {@link CredentialsSource#SYSTEM} | ||
* credentials) | ||
*/ | ||
JOB | ||
} |
46 changes: 46 additions & 0 deletions
46
src/main/java/swiss/dasch/plugins/foldercredentialsimporter/DomainCredentialsBuilder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package swiss.dasch.plugins.foldercredentialsimporter; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.LinkedHashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Map.Entry; | ||
|
||
import com.cloudbees.plugins.credentials.Credentials; | ||
import com.cloudbees.plugins.credentials.domains.Domain; | ||
import com.cloudbees.plugins.credentials.domains.DomainCredentials; | ||
|
||
public class DomainCredentialsBuilder { | ||
|
||
private final Map<Domain, List<Credentials>> credentials = new LinkedHashMap<>(); | ||
|
||
public void addCredentials(Domain domain, List<Credentials> credentials) { | ||
this.credentials.computeIfAbsent(domain, d -> new ArrayList<>()).addAll(credentials); | ||
} | ||
|
||
public void addCredentials(Domain domain, Credentials credentials) { | ||
this.addCredentials(domain, Collections.singletonList(credentials)); | ||
} | ||
|
||
public void addCredentials(DomainCredentials credentials) { | ||
this.addCredentials(credentials.getDomain(), credentials.getCredentials()); | ||
} | ||
|
||
public void addCredentials(List<DomainCredentials> credentials) { | ||
for (DomainCredentials dc : credentials) { | ||
this.addCredentials(dc); | ||
} | ||
} | ||
|
||
public List<DomainCredentials> build() { | ||
List<DomainCredentials> credentials = new ArrayList<>(); | ||
|
||
for (Entry<Domain, List<Credentials>> entry : this.credentials.entrySet()) { | ||
credentials.add(new DomainCredentials(entry.getKey(), entry.getValue())); | ||
} | ||
|
||
return credentials; | ||
} | ||
|
||
} |
Oops, something went wrong.