├── .github
├── CODEOWNERS
├── release-drafter.yml
├── dependabot.yml
└── workflows
│ ├── cd.yaml
│ └── jenkins-security-scan.yml
├── .mvn
├── maven.config
└── extensions.xml
├── docs
└── images
│ ├── apikey.png
│ ├── pkicert.png
│ ├── rotated.png
│ ├── sshcert.png
│ └── static.png
├── src
├── main
│ ├── resources
│ │ ├── io
│ │ │ └── jenkins
│ │ │ │ └── plugins
│ │ │ │ └── akeyless
│ │ │ │ ├── model
│ │ │ │ ├── AkeylessSecret
│ │ │ │ │ ├── help-path.html
│ │ │ │ │ └── config.jelly
│ │ │ │ ├── AkeylessPKIIssuer
│ │ │ │ │ ├── help-path.html
│ │ │ │ │ ├── help-ttl.html
│ │ │ │ │ └── config.jelly
│ │ │ │ ├── AkeylessSSHIssuer
│ │ │ │ │ ├── help-path.html
│ │ │ │ │ ├── help-ttl.html
│ │ │ │ │ └── config.jelly
│ │ │ │ └── AkeylessSecretValue
│ │ │ │ │ ├── help-isRequired.html
│ │ │ │ │ ├── help-secretKey.html
│ │ │ │ │ └── config.jelly
│ │ │ │ ├── configuration
│ │ │ │ ├── AkeylessConfiguration
│ │ │ │ │ ├── help-gwUrl.html
│ │ │ │ │ └── config.jelly
│ │ │ │ ├── FolderAkeylessConfiguration
│ │ │ │ │ └── config.jelly
│ │ │ │ └── GlobalAkeylessConfiguration
│ │ │ │ │ └── config.jelly
│ │ │ │ ├── credentials
│ │ │ │ ├── AkeylessUniversalIdCredentials
│ │ │ │ │ └── credentials.jelly
│ │ │ │ ├── AkeylessCloudCredentials
│ │ │ │ │ └── credentials.jelly
│ │ │ │ ├── AccessKeyCredentialsImpl
│ │ │ │ │ └── credentials.jelly
│ │ │ │ ├── AkeylessCertCredentials
│ │ │ │ │ └── credentials.jelly
│ │ │ │ └── AkeylessK8SCredentials
│ │ │ │ │ └── credentials.jelly
│ │ │ │ ├── AkeylessBindingStep
│ │ │ │ └── config.jelly
│ │ │ │ └── AkeylessBuildWrapper
│ │ │ │ └── config.jelly
│ │ └── index.jelly
│ └── java
│ │ └── io
│ │ └── jenkins
│ │ └── plugins
│ │ └── akeyless
│ │ ├── cloudid
│ │ ├── CloudIdProvider.java
│ │ ├── CloudIDRetriever.java
│ │ ├── CloudProviderFactory.java
│ │ ├── Utils.java
│ │ ├── GcpCloudIdProvider.java
│ │ ├── AzureCloudIdProvider.java
│ │ ├── AwsCredentialResolver.java
│ │ └── AwsCloudIdProvider.java
│ │ ├── model
│ │ ├── AkeylessSecretBase.java
│ │ ├── AkeylessIssuer.java
│ │ ├── AkeylessSSHIssuer.java
│ │ ├── AkeylessPKIIssuer.java
│ │ ├── AkeylessSecretValue.java
│ │ └── AkeylessSecret.java
│ │ ├── AkeylessPluginException.java
│ │ ├── configuration
│ │ ├── AkeylessConfigResolver.java
│ │ ├── GlobalAkeylessConfiguration.java
│ │ ├── FolderAkeylessConfiguration.java
│ │ └── AkeylessConfiguration.java
│ │ ├── credentials
│ │ ├── CredentialsPayload.java
│ │ ├── AkeylessCredential.java
│ │ ├── AkeylessTokenCredentials.java
│ │ ├── AkeylessUniversalIdCredentials.java
│ │ ├── AbstractAkeylessBaseStandardCredentials.java
│ │ ├── AccessKeyCredentialsImpl.java
│ │ ├── AkeylessCertCredentials.java
│ │ ├── AkeylessK8SCredentials.java
│ │ ├── AkeylessCloudCredentials.java
│ │ └── AkeylessCredentialsProvider.java
│ │ ├── MaskSecretsLogsFilter.java
│ │ ├── AkeylessBuildWrapper.java
│ │ ├── AkeylessBindingStep.java
│ │ └── AkeylessAccessor.java
└── test
│ └── java
│ └── io
│ └── jenkins
│ └── plugins
│ └── akeyless
│ ├── AkeylessBuildWrapperDescriptorTest.java
│ ├── AkeylessBindingStepDescriptorTest.java
│ ├── credentials
│ ├── AkeylessTokenCredentialsTest.java
│ ├── AkeylessCloudCredentialsTest.java
│ ├── AccessKeyCredentialsImplTest.java
│ ├── AkeylessCertCredentialsTest.java
│ └── AkeylessK8SCredentialsTest.java
│ ├── model
│ ├── AkeylessSecretTest.java
│ ├── AkeylessPKIIssuerTest.java
│ ├── AkeylessSSHIssuerTest.java
│ └── AkeylessSecretValueTest.java
│ ├── MaskSecretsLogsFilterTest.java
│ ├── cloudid
│ └── CloudProviderFactoryTest.java
│ ├── configuration
│ ├── AkeylessConfigurationUrlNormalizationTest.java
│ └── AkeylessConfigurationTest.java
│ └── AkeylessAccessorFillObjectValuesTest.java
├── .gitignore
├── Jenkinsfile
├── LICENSE.md
├── pom.xml
└── README.md
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @alikdolg/akeyless-plugin-developers
2 | * @jenkinsci/akeyless-plugin-developers
--------------------------------------------------------------------------------
/.mvn/maven.config:
--------------------------------------------------------------------------------
1 | -Pconsume-incrementals
2 | -Pmight-produce-incrementals
3 | -Dchangelist.format=%d.v%s
4 |
--------------------------------------------------------------------------------
/docs/images/apikey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/akeyless-plugin/main/docs/images/apikey.png
--------------------------------------------------------------------------------
/docs/images/pkicert.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/akeyless-plugin/main/docs/images/pkicert.png
--------------------------------------------------------------------------------
/docs/images/rotated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/akeyless-plugin/main/docs/images/rotated.png
--------------------------------------------------------------------------------
/docs/images/sshcert.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/akeyless-plugin/main/docs/images/sshcert.png
--------------------------------------------------------------------------------
/docs/images/static.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/akeyless-plugin/main/docs/images/static.png
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | # https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc
2 | _extends: .github
3 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/model/AkeylessSecret/help-path.html:
--------------------------------------------------------------------------------
1 |
2 | The full name of the secret
3 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/model/AkeylessPKIIssuer/help-path.html:
--------------------------------------------------------------------------------
1 |
2 | The full name of the certificate issuer
3 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/model/AkeylessSSHIssuer/help-path.html:
--------------------------------------------------------------------------------
1 |
2 | The full name of the certificate issuer
3 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/model/AkeylessPKIIssuer/help-ttl.html:
--------------------------------------------------------------------------------
1 |
2 | Updated certificate lifetime (must be less than the Certificate Issuer default TTL)
3 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/model/AkeylessSSHIssuer/help-ttl.html:
--------------------------------------------------------------------------------
1 |
2 | Updated certificate lifetime (must be less than the Certificate Issuer default TTL)
3 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/model/AkeylessSecretValue/help-isRequired.html:
--------------------------------------------------------------------------------
1 |
2 | If checked, if a Key that does not exist in the item is set, it will fail
3 |
--------------------------------------------------------------------------------
/src/main/resources/index.jelly:
--------------------------------------------------------------------------------
1 |
2 | Plugin to fetch secrets and certificates from
3 |
Akeyless platform
4 |
5 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/configuration/AkeylessConfiguration/help-gwUrl.html:
--------------------------------------------------------------------------------
1 |
2 | The URL of your Gateway API v2 endpoint: https://Your-Gateway-URL:8000/api/v2
3 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/model/AkeylessSecretValue/help-secretKey.html:
--------------------------------------------------------------------------------
1 |
2 | Enter the key name from the JSON secret to retrieve its value. To retrieve all keys, enter 'data'.
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 |
3 | # mvn hpi:run
4 | work
5 |
6 | # IntelliJ IDEA project files
7 | *.iml
8 | *.iws
9 | *.ipr
10 | .idea
11 |
12 | # Eclipse project files
13 | .settings
14 | .classpath
15 | .project
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/cloudid/CloudIdProvider.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.cloudid;
2 |
3 | public interface CloudIdProvider {
4 | public String getCloudId() throws Exception;
5 | }
6 |
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | /*
2 | See the documentation for more options:
3 | https://github.com/jenkins-infra/pipeline-library/
4 | */
5 | buildPlugin(
6 | useArtifactCachingProxy: false,
7 | configurations: [
8 | [platform: 'linux', jdk: 21],
9 | [platform: 'windows', jdk: 17],
10 | ])
11 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/model/AkeylessSecretBase.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.model;
2 |
3 | import java.util.List;
4 |
5 | public interface AkeylessSecretBase {
6 | public List getSecretValues();
7 |
8 | public String getPath();
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/configuration/FolderAkeylessConfiguration/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/configuration/GlobalAkeylessConfiguration/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuring-dependabot-version-updates
2 | ---
3 | version: 2
4 | updates:
5 | - package-ecosystem: "maven"
6 | directory: "/"
7 | schedule:
8 | interval: "monthly"
9 | - package-ecosystem: "github-actions"
10 | directory: "/"
11 | schedule:
12 | interval: "weekly"
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/AkeylessPluginException.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless;
2 |
3 | /**
4 | * @author alexeydolgopyatov
5 | */
6 | public class AkeylessPluginException extends RuntimeException {
7 | public AkeylessPluginException(String s) {
8 | super(s);
9 | }
10 |
11 | public AkeylessPluginException(String s, Throwable throwable) {
12 | super(s, throwable);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.mvn/extensions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | io.jenkins.tools.incrementals
4 | git-changelist-maven-extension
5 | 1.4
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/credentials/AkeylessUniversalIdCredentials/credentials.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/workflows/cd.yaml:
--------------------------------------------------------------------------------
1 | # Note: additional setup is required, see https://www.jenkins.io/redirect/continuous-delivery-of-plugins
2 |
3 | name: cd
4 | on:
5 | workflow_dispatch:
6 | check_run:
7 | types:
8 | - completed
9 |
10 | jobs:
11 | maven-cd:
12 | uses: jenkins-infra/github-reusable-workflows/.github/workflows/maven-cd.yml@v1
13 | secrets:
14 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
15 | MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }}
16 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/credentials/AkeylessCloudCredentials/credentials.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/akeyless/AkeylessBuildWrapperDescriptorTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 |
6 | import org.junit.jupiter.api.Test;
7 |
8 | class AkeylessBuildWrapperDescriptorTest {
9 |
10 | @Test
11 | void descriptorProperties() {
12 | AkeylessBuildWrapper.DescriptorImpl d = new AkeylessBuildWrapper.DescriptorImpl();
13 | assertThat(d.getDisplayName(), is("Akeyless Plugin"));
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/cloudid/CloudIDRetriever.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.cloudid;
2 |
3 | public class CloudIDRetriever {
4 | public static void main(String[] args) {
5 | CloudIdProvider cloudIdProvider = new AzureCloudIdProvider();
6 | try {
7 | String cloudId = cloudIdProvider.getCloudId();
8 | System.out.println("Cloud ID: " + cloudId);
9 | } catch (Exception e) {
10 | System.err.println("Error retrieving Cloud ID: " + e.getMessage());
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/credentials/AccessKeyCredentialsImpl/credentials.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/model/AkeylessSecret/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/configuration/AkeylessConfigResolver.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.configuration;
2 |
3 | import edu.umd.cs.findbugs.annotations.NonNull;
4 | import hudson.ExtensionPoint;
5 | import hudson.model.Item;
6 | import hudson.model.ItemGroup;
7 |
8 | /**
9 | * @author alexeydolgopyatov
10 | */
11 | public abstract class AkeylessConfigResolver implements ExtensionPoint {
12 | @NonNull
13 | public abstract AkeylessConfiguration forJob(@NonNull Item job);
14 |
15 | @NonNull
16 | public abstract AkeylessConfiguration getConfig(@NonNull ItemGroup- itemGroup);
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/credentials/AkeylessCertCredentials/credentials.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/akeyless/AkeylessBindingStepDescriptorTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 |
6 | import org.junit.jupiter.api.Test;
7 |
8 | class AkeylessBindingStepDescriptorTest {
9 |
10 | @Test
11 | void descriptorProperties() {
12 | AkeylessBindingStep.DescriptorImpl d = new AkeylessBindingStep.DescriptorImpl();
13 | assertThat(d.getFunctionName(), is("withAkeyless"));
14 | assertThat(d.getDisplayName(), is("Akeyless Plugin"));
15 | assertThat(d.takesImplicitBlockArgument(), is(true));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/cloudid/CloudProviderFactory.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.cloudid;
2 |
3 | import java.util.Objects;
4 |
5 | public class CloudProviderFactory {
6 | public static CloudIdProvider getCloudIdProvider(String accType) throws RuntimeException {
7 | if (Objects.equals(accType, "aws_iam")) {
8 | return new AwsCloudIdProvider();
9 | } else if (Objects.equals(accType, "azure_ad")) {
10 | return new AzureCloudIdProvider();
11 | } else if (Objects.equals(accType, "gcp")) {
12 | return new GcpCloudIdProvider();
13 | } else {
14 | throw new RuntimeException("Unsupported type: " + accType);
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/model/AkeylessSecretValue/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/akeyless/credentials/AkeylessTokenCredentialsTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.credentials;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 |
6 | import hudson.util.Secret;
7 | import org.junit.jupiter.api.Test;
8 |
9 | class AkeylessTokenCredentialsTest {
10 |
11 | @Test
12 | void payloadContainsToken() {
13 | AkeylessTokenCredentials creds = new AkeylessTokenCredentials(null, null, null);
14 | Secret token = Secret.fromString("tkn");
15 | creds.setToken(token);
16 | CredentialsPayload payload = creds.getCredentialsPayload();
17 | assertThat(payload.getToken().getPlainText(), is("tkn"));
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/akeyless/model/AkeylessSecretTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.model;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 |
6 | import java.util.Collections;
7 | import org.junit.jupiter.api.Test;
8 |
9 | class AkeylessSecretTest {
10 |
11 | @Test
12 | void gettersReturnConstructorValues() {
13 | AkeylessSecretValue value = new AkeylessSecretValue("key");
14 | AkeylessSecret secret = new AkeylessSecret("/path/to/secret", Collections.singletonList(value));
15 | assertThat(secret.getPath(), is("/path/to/secret"));
16 | assertThat(secret.getSecretValues().size(), is(1));
17 | assertThat(secret.getSecretValues().get(0).getSecretKey(), is("key"));
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/AkeylessBindingStep/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
11 |
13 |
14 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/credentials/AkeylessK8SCredentials/credentials.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/AkeylessBuildWrapper/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
11 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.github/workflows/jenkins-security-scan.yml:
--------------------------------------------------------------------------------
1 | # More information about the Jenkins security scan can be found at the developer docs: https://www.jenkins.io/redirect/jenkins-security-scan/
2 | ---
3 | name: Jenkins Security Scan
4 | on:
5 | push:
6 | branches:
7 | - "main"
8 | pull_request:
9 | types: [opened, synchronize, reopened]
10 | workflow_dispatch:
11 |
12 | permissions:
13 | security-events: write
14 | contents: read
15 | actions: read
16 |
17 | jobs:
18 | security-scan:
19 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2
20 | with:
21 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate.
22 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default.
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/akeyless/MaskSecretsLogsFilterTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless;
2 |
3 | import static org.hamcrest.CoreMatchers.containsString;
4 | import static org.hamcrest.CoreMatchers.is;
5 | import static org.hamcrest.MatcherAssert.assertThat;
6 |
7 | import java.util.Arrays;
8 | import org.junit.jupiter.api.Test;
9 |
10 | class MaskSecretsLogsFilterTest {
11 |
12 | @Test
13 | void patternReturnsEmptyForNullInput() {
14 | assertThat(MaskSecretsLogsFilter.getPatternStringForSecrets(null), is(""));
15 | }
16 |
17 | @Test
18 | void patternQuotesAndOrdersByLength() {
19 | String pattern = MaskSecretsLogsFilter.getPatternStringForSecrets(Arrays.asList("a", "abc", "ab"));
20 | // abc should come before ab before a, and be quoted
21 | assertThat(pattern, containsString("\\Qabc\\E|\\Qab\\E|\\Qa\\E"));
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/credentials/CredentialsPayload.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.credentials;
2 |
3 | import hudson.util.Secret;
4 | import io.akeyless.client.model.Auth;
5 |
6 | public class CredentialsPayload {
7 | private Auth auth;
8 | private Secret token;
9 | private boolean isCloudIdNeeded;
10 |
11 | public Auth getAuth() {
12 | return auth;
13 | }
14 |
15 | public void setAuth(Auth auth) {
16 | this.auth = auth;
17 | }
18 |
19 | public Secret getToken() {
20 | return token;
21 | }
22 |
23 | public void setToken(Secret token) {
24 | this.token = token;
25 | }
26 |
27 | public boolean isCloudIdNeeded() {
28 | return isCloudIdNeeded;
29 | }
30 |
31 | public void setCloudIdNeeded(boolean cloudIdNeeded) {
32 | isCloudIdNeeded = cloudIdNeeded;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/credentials/AkeylessCredential.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.credentials;
2 |
3 | import com.cloudbees.plugins.credentials.CredentialsNameProvider;
4 | import com.cloudbees.plugins.credentials.NameWith;
5 | import com.cloudbees.plugins.credentials.common.StandardCredentials;
6 | import edu.umd.cs.findbugs.annotations.NonNull;
7 | import java.io.Serializable;
8 |
9 | /**
10 | * @author alexeydolgopyatov
11 | */
12 | @NameWith(AkeylessCredential.NameProvider.class)
13 | public interface AkeylessCredential extends StandardCredentials, Serializable {
14 | public CredentialsPayload getCredentialsPayload();
15 |
16 | class NameProvider extends CredentialsNameProvider {
17 | @NonNull
18 | public String getName(@NonNull AkeylessCredential credentials) {
19 | return credentials.getDescription();
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/configuration/AkeylessConfiguration/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/akeyless/credentials/AkeylessCloudCredentialsTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.credentials;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 |
6 | import io.akeyless.client.model.Auth;
7 | import org.junit.jupiter.api.Test;
8 |
9 | class AkeylessCloudCredentialsTest {
10 |
11 | @Test
12 | void payloadContainsAuthWithCloudTypeAndRequiresCloudId() {
13 | AkeylessCloudCredentials creds = new AkeylessCloudCredentials(null, null, null);
14 | creds.setAccessId("id");
15 | creds.setCloudType("aws_iam");
16 |
17 | CredentialsPayload payload = creds.getCredentialsPayload();
18 | Auth auth = payload.getAuth();
19 | assertThat(auth.getAccessId(), is("id"));
20 | assertThat(auth.getAccessType(), is("aws_iam"));
21 | assertThat(payload.isCloudIdNeeded(), is(true));
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/model/AkeylessPKIIssuer/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/akeyless/model/AkeylessSSHIssuer/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/akeyless/credentials/AccessKeyCredentialsImplTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.credentials;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 |
6 | import hudson.util.Secret;
7 | import io.akeyless.client.model.Auth;
8 | import org.junit.jupiter.api.Test;
9 |
10 | class AccessKeyCredentialsImplTest {
11 |
12 | @Test
13 | void payloadContainsAuthWithApiKey() {
14 | AccessKeyCredentialsImpl creds = new AccessKeyCredentialsImpl(null, null, null);
15 | creds.setAccessId("id");
16 | creds.setAccessKey(Secret.fromString("key"));
17 |
18 | CredentialsPayload payload = creds.getCredentialsPayload();
19 | Auth auth = payload.getAuth();
20 | assertThat(auth.getAccessId(), is("id"));
21 | assertThat(auth.getAccessType(), is("api_key"));
22 | assertThat(auth.getAccessKey(), is("key"));
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/akeyless/model/AkeylessPKIIssuerTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.model;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 |
6 | import java.util.Collections;
7 | import org.junit.jupiter.api.Test;
8 |
9 | class AkeylessPKIIssuerTest {
10 |
11 | @Test
12 | void gettersReturnConstructorValues() {
13 | AkeylessSecretValue value = new AkeylessSecretValue("data");
14 | AkeylessPKIIssuer issuer = new AkeylessPKIIssuer(
15 | "/p/pki", "name", "user", "pubkey", "csr", 7200L, Collections.singletonList(value));
16 |
17 | assertThat(issuer.getPath(), is("/p/pki"));
18 | assertThat(issuer.getPublicKey(), is("pubkey"));
19 | assertThat(issuer.getCsrBase64(), is("csr"));
20 | assertThat(issuer.getTtl(), is(7200L));
21 | assertThat(issuer.getSecretValues().get(0).getSecretKey(), is("data"));
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/akeyless/model/AkeylessSSHIssuerTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.model;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 |
6 | import java.util.Collections;
7 | import org.junit.jupiter.api.Test;
8 |
9 | class AkeylessSSHIssuerTest {
10 |
11 | @Test
12 | void gettersReturnConstructorValues() {
13 | AkeylessSecretValue value = new AkeylessSecretValue("data");
14 | AkeylessSSHIssuer issuer =
15 | new AkeylessSSHIssuer("/aaa/ssh", "name", "user", "pubkey", 3600L, Collections.singletonList(value));
16 |
17 | assertThat(issuer.getPath(), is("/aaa/ssh"));
18 | assertThat(issuer.getCertUserName(), is("user"));
19 | assertThat(issuer.getPublicKey(), is("pubkey"));
20 | assertThat(issuer.getTtl(), is(3600L));
21 | assertThat(issuer.getSecretValues().get(0).getSecretKey(), is("data"));
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright 2025
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/akeyless/credentials/AkeylessCertCredentialsTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.credentials;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 |
6 | import hudson.util.Secret;
7 | import io.akeyless.client.model.Auth;
8 | import org.junit.jupiter.api.Test;
9 |
10 | class AkeylessCertCredentialsTest {
11 |
12 | @Test
13 | void payloadContainsAuthWithCert() {
14 | AkeylessCertCredentials creds = new AkeylessCertCredentials(null, null, null);
15 | creds.setAccessId("id");
16 | creds.setCertificate("cert-data");
17 | creds.setPrivateKey(Secret.fromString("key"));
18 |
19 | CredentialsPayload payload = creds.getCredentialsPayload();
20 | Auth auth = payload.getAuth();
21 | assertThat(auth.getAccessId(), is("id"));
22 | assertThat(auth.getAccessType(), is("cert"));
23 | assertThat(auth.getCertData(), is("cert-data"));
24 | assertThat(auth.getKeyData(), is("key"));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/cloudid/Utils.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.cloudid;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 | import java.io.InputStreamReader;
7 | import java.nio.charset.StandardCharsets;
8 |
9 | public class Utils {
10 | public static StringBuilder readDataFromStream(InputStream inputStream) {
11 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
12 | StringBuilder response = new StringBuilder();
13 | try {
14 | String line;
15 | while ((line = reader.readLine()) != null) {
16 | response.append(line);
17 | }
18 | } catch (IOException e) {
19 | throw new RuntimeException(e);
20 | } finally {
21 | try {
22 | reader.close();
23 | } catch (IOException e) {
24 | throw new RuntimeException(e);
25 | }
26 | }
27 | return response;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/akeyless/cloudid/CloudProviderFactoryTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.cloudid;
2 |
3 | import static org.hamcrest.CoreMatchers.instanceOf;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 | import static org.junit.jupiter.api.Assertions.assertThrows;
6 |
7 | import org.junit.jupiter.api.Test;
8 |
9 | class CloudProviderFactoryTest {
10 |
11 | @Test
12 | void returnsAwsProvider() {
13 | assertThat(CloudProviderFactory.getCloudIdProvider("aws_iam"), instanceOf(AwsCloudIdProvider.class));
14 | }
15 |
16 | @Test
17 | void returnsAzureProvider() {
18 | assertThat(CloudProviderFactory.getCloudIdProvider("azure_ad"), instanceOf(AzureCloudIdProvider.class));
19 | }
20 |
21 | @Test
22 | void returnsGcpProvider() {
23 | assertThat(CloudProviderFactory.getCloudIdProvider("gcp"), instanceOf(GcpCloudIdProvider.class));
24 | }
25 |
26 | @Test
27 | void throwsOnUnsupportedType() {
28 | assertThrows(RuntimeException.class, () -> CloudProviderFactory.getCloudIdProvider("unknown"));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/akeyless/model/AkeylessSecretValueTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.model;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 |
6 | import org.junit.jupiter.api.Test;
7 |
8 | class AkeylessSecretValueTest {
9 |
10 | @Test
11 | void getEnvVarDefaultsToSecretKey() {
12 | AkeylessSecretValue value = new AkeylessSecretValue("DB_PASSWORD");
13 | assertThat(value.getEnvVar(), is("DB_PASSWORD"));
14 | }
15 |
16 | @Test
17 | void getEnvVarUsesCustomEnvVarWhenProvided() {
18 | AkeylessSecretValue value = new AkeylessSecretValue("password");
19 | value.setEnvVar("DB_PASSWORD");
20 | assertThat(value.getEnvVar(), is("DB_PASSWORD"));
21 | }
22 |
23 | @Test
24 | void isRequiredDefaultsTrueAndCanBeChanged() {
25 | AkeylessSecretValue value = new AkeylessSecretValue("password");
26 | assertThat(value.getIsRequired(), is(true));
27 | value.setIsRequired(false);
28 | assertThat(value.getIsRequired(), is(false));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/akeyless/credentials/AkeylessK8SCredentialsTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.credentials;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 |
6 | import hudson.util.Secret;
7 | import io.akeyless.client.model.Auth;
8 | import org.junit.jupiter.api.Test;
9 |
10 | class AkeylessK8SCredentialsTest {
11 |
12 | @Test
13 | void payloadContainsAuthWithK8sFields() {
14 | AkeylessK8SCredentials creds = new AkeylessK8SCredentials(null, null, null);
15 | creds.setAccessId("id");
16 | creds.setGatewayUrl("https://gw");
17 | creds.setConfigName("conf");
18 | creds.setServiceAccountToken(Secret.fromString("tok"));
19 |
20 | CredentialsPayload payload = creds.getCredentialsPayload();
21 | Auth auth = payload.getAuth();
22 | assertThat(auth.getAccessId(), is("id"));
23 | assertThat(auth.getAccessType(), is("k8s"));
24 | assertThat(auth.getGatewayUrl(), is("https://gw"));
25 | assertThat(auth.getK8sAuthConfigName(), is("conf"));
26 | assertThat(auth.getK8sServiceAccountToken(), is("tok"));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/credentials/AkeylessTokenCredentials.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.credentials;
2 |
3 | import com.cloudbees.plugins.credentials.CredentialsScope;
4 | import edu.umd.cs.findbugs.annotations.NonNull;
5 | import hudson.util.Secret;
6 | import javax.annotation.CheckForNull;
7 | import org.kohsuke.stapler.DataBoundConstructor;
8 | import org.kohsuke.stapler.DataBoundSetter;
9 |
10 | public class AkeylessTokenCredentials extends AbstractAkeylessBaseStandardCredentials implements AkeylessCredential {
11 | private Secret token;
12 |
13 | @DataBoundConstructor
14 | public AkeylessTokenCredentials(
15 | @CheckForNull CredentialsScope scope, @CheckForNull String id, @CheckForNull String description) {
16 | super(scope, id, description);
17 | }
18 |
19 | @NonNull
20 | public Secret getToken() {
21 | return token;
22 | }
23 |
24 | @DataBoundSetter
25 | public void setToken(Secret token) {
26 | this.token = token;
27 | }
28 |
29 | @Override
30 | public CredentialsPayload getCredentialsPayload() {
31 | CredentialsPayload payload = new CredentialsPayload();
32 | payload.setToken(getToken());
33 | return payload;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/credentials/AkeylessUniversalIdCredentials.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.credentials;
2 |
3 | import com.cloudbees.plugins.credentials.CredentialsScope;
4 | import edu.umd.cs.findbugs.annotations.NonNull;
5 | import hudson.Extension;
6 | import io.akeyless.client.model.Auth;
7 | import org.kohsuke.stapler.DataBoundConstructor;
8 |
9 | public class AkeylessUniversalIdCredentials extends AkeylessTokenCredentials {
10 |
11 | @DataBoundConstructor
12 | public AkeylessUniversalIdCredentials(CredentialsScope scope, String id, String description) {
13 | super(scope, id, description);
14 | }
15 |
16 | @Override
17 | public CredentialsPayload getCredentialsPayload() {
18 | CredentialsPayload payload = new CredentialsPayload();
19 | Auth auth = new Auth();
20 | auth.setAccessType("universal_identity");
21 | auth.setUidToken(getToken().getPlainText());
22 | payload.setAuth(auth);
23 | return payload;
24 | }
25 |
26 | @Extension
27 | public static class DescriptorImpl extends BaseStandardCredentialsDescriptor {
28 |
29 | @NonNull
30 | @Override
31 | public String getDisplayName() {
32 | return "Akeyless Universal Identity";
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/model/AkeylessIssuer.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.model;
2 |
3 | import hudson.Util;
4 | import hudson.model.AbstractDescribableImpl;
5 | import java.util.List;
6 |
7 | /**
8 | * @author alexeydolgopyatov
9 | */
10 | public abstract class AkeylessIssuer> extends AbstractDescribableImpl
11 | implements AkeylessSecretBase {
12 | private String path;
13 |
14 | // lgtm[jenkins/plaintext-storage]
15 | private String publicKey;
16 |
17 | private long ttl = 0;
18 |
19 | private List secretValues;
20 |
21 | public AkeylessIssuer(
22 | String path, String name, String publicKey, long ttl, List secretValues) {
23 | this.path = Util.fixEmptyAndTrim(path);
24 | this.secretValues = secretValues;
25 | this.publicKey = publicKey;
26 | this.ttl = ttl;
27 | }
28 |
29 | public String getPath() {
30 | return this.path;
31 | }
32 |
33 | public String getPublicKey() {
34 | return this.publicKey;
35 | }
36 |
37 | public List getSecretValues() {
38 | return this.secretValues;
39 | }
40 |
41 | public long getTtl() {
42 | return this.ttl;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/credentials/AbstractAkeylessBaseStandardCredentials.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.credentials;
2 |
3 | import com.cloudbees.plugins.credentials.CredentialsScope;
4 | import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials;
5 | import edu.umd.cs.findbugs.annotations.NonNull;
6 | import hudson.model.ItemGroup;
7 | import org.kohsuke.stapler.DataBoundSetter;
8 |
9 | /**
10 | * @author alexeydolgopyatov
11 | */
12 | public abstract class AbstractAkeylessBaseStandardCredentials extends BaseStandardCredentials
13 | implements AkeylessCredential {
14 | private String path;
15 | private transient ItemGroup context;
16 |
17 | AbstractAkeylessBaseStandardCredentials(CredentialsScope scope, String id, String description) {
18 | super(scope, id, description);
19 | }
20 |
21 | @NonNull
22 | public String getPath() {
23 | return path;
24 | }
25 |
26 | public String getSecretPath() {
27 | return getPath();
28 | }
29 |
30 | @DataBoundSetter
31 | public void setPath(String path) {
32 | this.path = path;
33 | }
34 |
35 | @DataBoundSetter
36 | public void setContext(@NonNull ItemGroup context) {
37 | this.context = context;
38 | }
39 |
40 | public ItemGroup getContext() {
41 | return this.context;
42 | }
43 |
44 | public String getDisplayName() {
45 | return this.path;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/cloudid/GcpCloudIdProvider.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.cloudid;
2 |
3 | import java.io.BufferedReader;
4 | import java.net.HttpURLConnection;
5 | import java.net.URL;
6 | import java.nio.charset.StandardCharsets;
7 | import java.util.Base64;
8 |
9 | public class GcpCloudIdProvider implements CloudIdProvider {
10 |
11 | private static final String METADATA_URL =
12 | "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=akeyless.io&format=full";
13 |
14 | @Override
15 | public String getCloudId() throws Exception {
16 | String token = fetchIdentityToken();
17 | return Base64.getEncoder().encodeToString(token.getBytes(StandardCharsets.UTF_8));
18 | }
19 |
20 | private String fetchIdentityToken() throws Exception {
21 | HttpURLConnection conn = null;
22 | BufferedReader reader = null;
23 |
24 | try {
25 | URL url = new URL(METADATA_URL);
26 | conn = (HttpURLConnection) url.openConnection();
27 | conn.setRequestMethod("GET");
28 | conn.setRequestProperty("Metadata-Flavor", "Google");
29 | conn.setConnectTimeout(3000);
30 |
31 | int responseCode = conn.getResponseCode();
32 | if (responseCode != 200) {
33 | throw new RuntimeException(
34 | "Failed to retrieve identity token from GCP metadata server. Response code: " + responseCode);
35 | }
36 | return Utils.readDataFromStream(conn.getInputStream()).toString();
37 |
38 | } finally {
39 | if (conn != null) {
40 | conn.disconnect();
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/model/AkeylessSSHIssuer.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.model;
2 |
3 | import hudson.Extension;
4 | import hudson.model.Descriptor;
5 | import java.util.List;
6 | import org.kohsuke.stapler.DataBoundConstructor;
7 |
8 | public class AkeylessSSHIssuer extends AkeylessIssuer {
9 | private String certUserName;
10 |
11 | @DataBoundConstructor
12 | public AkeylessSSHIssuer(
13 | String path,
14 | String name,
15 | String certUserName,
16 | String publicKey,
17 | long ttl,
18 | List secretValues) {
19 | super(path, name, publicKey, ttl, secretValues);
20 | this.certUserName = certUserName;
21 | }
22 |
23 | public String getCertUserName() {
24 | return this.certUserName;
25 | }
26 |
27 | @Extension
28 | public static final class DescriptorImpl extends Descriptor {
29 |
30 | @Override
31 | public String getDisplayName() {
32 | return "Akeyless SSH Issuer";
33 | }
34 |
35 | /** Prepare fields validation - currently doesn't work
36 | *
37 | * public FormValidation doCheckPath(@QueryParameter String value) {
38 | * if (!Strings.isNullOrEmpty(value)) return FormValidation.ok();
39 | * else return FormValidation.error("This field can not be empty");
40 | * }
41 | *
42 | * public FormValidation doCheckCertUserName(@QueryParameter String value) {
43 | * if (!Strings.isNullOrEmpty(value)) return FormValidation.ok();
44 | * else return FormValidation.error("This field can not be empty");
45 | * }
46 | */
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/model/AkeylessPKIIssuer.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.model;
2 |
3 | import hudson.Extension;
4 | import hudson.model.Descriptor;
5 | import java.util.List;
6 | import org.kohsuke.stapler.DataBoundConstructor;
7 |
8 | public class AkeylessPKIIssuer extends AkeylessIssuer {
9 | private String csrBase64;
10 |
11 | @DataBoundConstructor
12 | public AkeylessPKIIssuer(
13 | String path,
14 | String name,
15 | String certUserName,
16 | String publicKey,
17 | String csrBase64,
18 | long ttl,
19 | List secretValues) {
20 | super(path, name, publicKey, ttl, secretValues);
21 | this.csrBase64 = csrBase64;
22 | }
23 |
24 | public String getCsrBase64() {
25 | return this.csrBase64;
26 | }
27 |
28 | @Extension
29 | public static final class DescriptorImpl extends Descriptor {
30 |
31 | @Override
32 | public String getDisplayName() {
33 | return "Akeyless PKI Issuer";
34 | }
35 |
36 | /** Prepare fields validation - currently doesn't work
37 | *
38 | *
39 | * public FormValidation doCheckPath(@QueryParameter String value) {
40 | * if (!Strings.isNullOrEmpty(value)) return FormValidation.ok();
41 | * else return FormValidation.error("This field can not be empty");
42 | * }
43 | *
44 | * public FormValidation doCheckCertUserName(@QueryParameter String value) {
45 | * if (!Strings.isNullOrEmpty(value)) return FormValidation.ok();
46 | * else return FormValidation.error("This field can not be empty");
47 | * }
48 | */
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/model/AkeylessSecretValue.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.model;
2 |
3 | import static hudson.Util.fixEmptyAndTrim;
4 |
5 | import edu.umd.cs.findbugs.annotations.NonNull;
6 | import hudson.Extension;
7 | import hudson.model.AbstractDescribableImpl;
8 | import hudson.model.Descriptor;
9 | import org.apache.commons.lang.StringUtils;
10 | import org.kohsuke.stapler.DataBoundConstructor;
11 | import org.kohsuke.stapler.DataBoundSetter;
12 |
13 | /**
14 | * @author alexeydolgopyatov
15 | */
16 | public class AkeylessSecretValue extends AbstractDescribableImpl {
17 |
18 | private String envVar;
19 | private boolean isRequired = DescriptorImpl.DEFAULT_IS_REQUIRED;
20 |
21 | @SuppressWarnings("lgtm[jenkins/plaintext-storage]")
22 | private final String secretKey;
23 |
24 | @DataBoundConstructor
25 | public AkeylessSecretValue(@NonNull String secretKey) {
26 | this.secretKey = fixEmptyAndTrim(secretKey);
27 | }
28 |
29 | @DataBoundSetter
30 | public void setEnvVar(String envVar) {
31 | this.envVar = envVar;
32 | }
33 |
34 | @DataBoundSetter
35 | public void setIsRequired(boolean isRequired) {
36 | this.isRequired = isRequired;
37 | }
38 |
39 | /**
40 | *
41 | * @return envVar if value is not empty otherwise return vaultKey
42 | */
43 | public String getEnvVar() {
44 | return StringUtils.isEmpty(envVar) ? secretKey : envVar;
45 | }
46 |
47 | public String getSecretKey() {
48 | return secretKey;
49 | }
50 |
51 | public boolean getIsRequired() {
52 | return isRequired;
53 | }
54 |
55 | @Extension
56 | public static final class DescriptorImpl extends Descriptor {
57 |
58 | public static final Boolean DEFAULT_IS_REQUIRED = true;
59 |
60 | @Override
61 | public String getDisplayName() {
62 | return "Environment variable/vault secret value pair";
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/configuration/GlobalAkeylessConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.configuration;
2 |
3 | import edu.umd.cs.findbugs.annotations.NonNull;
4 | import hudson.Extension;
5 | import hudson.ExtensionList;
6 | import hudson.model.Item;
7 | import hudson.model.ItemGroup;
8 | import jenkins.model.GlobalConfiguration;
9 | import net.sf.json.JSONObject;
10 | import org.jenkinsci.Symbol;
11 | import org.kohsuke.stapler.DataBoundSetter;
12 | import org.kohsuke.stapler.StaplerRequest;
13 |
14 | /**
15 | * @author alexeydolgopyatov
16 | */
17 | @Extension
18 | @Symbol("akeyless")
19 | public class GlobalAkeylessConfiguration extends GlobalConfiguration {
20 | private AkeylessConfiguration configuration;
21 |
22 | public static GlobalAkeylessConfiguration get() {
23 | return ExtensionList.lookupSingleton(GlobalAkeylessConfiguration.class);
24 | }
25 |
26 | public GlobalAkeylessConfiguration() {
27 | load();
28 | }
29 |
30 | @Override
31 | public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
32 | req.bindJSON(this, json);
33 | return true;
34 | }
35 |
36 | public AkeylessConfiguration getConfiguration() {
37 | return configuration;
38 | }
39 |
40 | @DataBoundSetter
41 | public void setConfiguration(AkeylessConfiguration configuration) {
42 | this.configuration = configuration;
43 | save();
44 | }
45 |
46 | @Extension(ordinal = 0)
47 | public static class ForJob extends AkeylessConfigResolver {
48 |
49 | @NonNull
50 | @Override
51 | public AkeylessConfiguration forJob(@NonNull Item job) {
52 | return getConfig(job.getParent());
53 | }
54 |
55 | @Override
56 | public AkeylessConfiguration getConfig(@NonNull ItemGroup itemGroup) {
57 | return GlobalAkeylessConfiguration.get().getConfiguration();
58 | }
59 | }
60 |
61 | protected Object readResolve() {
62 | return this;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/akeyless/configuration/AkeylessConfigurationUrlNormalizationTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.configuration;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 |
6 | import org.junit.jupiter.api.Test;
7 |
8 | class AkeylessConfigurationUrlNormalizationTest {
9 |
10 | @Test
11 | void normalizeUrlShouldTrimTrailingSlash() {
12 | AkeylessConfiguration configuration = new AkeylessConfiguration();
13 | configuration.setAkeylessUrl("https://akeyless.io/");
14 | assertThat(configuration.getAkeylessUrl(), is("https://akeyless.io"));
15 | }
16 |
17 | @Test
18 | void normalizeUrlShouldPreserveIfNoTrailingSlash() {
19 | AkeylessConfiguration configuration = new AkeylessConfiguration();
20 | configuration.setAkeylessUrl("https://akeyless.io");
21 | assertThat(configuration.getAkeylessUrl(), is("https://akeyless.io"));
22 | }
23 |
24 | @Test
25 | void normalizeUrlShouldHandleNull() {
26 | AkeylessConfiguration configuration = new AkeylessConfiguration();
27 | configuration.setAkeylessUrl(null);
28 | assertThat(configuration.getAkeylessUrl(), is((String) null));
29 | }
30 |
31 | @Test
32 | void parentPoliciesShouldApplyWhenChildBlank() {
33 | AkeylessConfiguration parent = new AkeylessConfiguration();
34 | parent.setPolicies("team=parent");
35 |
36 | AkeylessConfiguration child = new AkeylessConfiguration();
37 |
38 | AkeylessConfiguration result = child.mergeWithParent(parent);
39 | assertThat(result.getPolicies(), is("team=parent"));
40 | }
41 |
42 | @Test
43 | void parentPoliciesShouldOverrideChildWhenDisabledOverride() {
44 | AkeylessConfiguration parent = new AkeylessConfiguration();
45 | parent.setPolicies("team=parent");
46 | parent.setDisableChildPoliciesOverride(true);
47 |
48 | AkeylessConfiguration child = new AkeylessConfiguration();
49 | child.setPolicies("team=child");
50 |
51 | AkeylessConfiguration result = child.mergeWithParent(parent);
52 | assertThat(result.getPolicies(), is("team=parent"));
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/configuration/FolderAkeylessConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.configuration;
2 |
3 | import com.cloudbees.hudson.plugins.folder.AbstractFolder;
4 | import com.cloudbees.hudson.plugins.folder.AbstractFolderProperty;
5 | import edu.umd.cs.findbugs.annotations.NonNull;
6 | import hudson.Extension;
7 | import hudson.model.Item;
8 | import hudson.model.ItemGroup;
9 | import org.kohsuke.stapler.DataBoundConstructor;
10 |
11 | /**
12 | * @author alexeydolgopyatov
13 | */
14 | public class FolderAkeylessConfiguration extends AbstractFolderProperty> {
15 | private final AkeylessConfiguration configuration;
16 |
17 | public FolderAkeylessConfiguration() {
18 | this.configuration = null;
19 | }
20 |
21 | @DataBoundConstructor
22 | public FolderAkeylessConfiguration(AkeylessConfiguration configuration) {
23 | this.configuration = configuration;
24 | }
25 |
26 | public AkeylessConfiguration getConfiguration() {
27 | return configuration;
28 | }
29 |
30 | @Extension(ordinal = 100)
31 | public static class ForJob extends AkeylessConfigResolver {
32 |
33 | @NonNull
34 | @Override
35 | public AkeylessConfiguration forJob(@NonNull Item job) {
36 | return getConfig(job.getParent());
37 | }
38 |
39 | @Override
40 | public AkeylessConfiguration getConfig(@NonNull ItemGroup itemGroup) {
41 | AkeylessConfiguration resultingConfig = null;
42 | for (ItemGroup g = itemGroup; g instanceof AbstractFolder; g = ((AbstractFolder) g).getParent()) {
43 | FolderAkeylessConfiguration folderProperty =
44 | ((AbstractFolder>) g).getProperties().get(FolderAkeylessConfiguration.class);
45 | if (folderProperty == null) {
46 | continue;
47 | }
48 | if (resultingConfig != null) {
49 | resultingConfig = resultingConfig.mergeWithParent(folderProperty.getConfiguration());
50 | } else {
51 | resultingConfig = folderProperty.getConfiguration();
52 | }
53 | }
54 | return resultingConfig;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/model/AkeylessSecret.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.model;
2 |
3 | import com.google.common.base.Strings;
4 | import hudson.Extension;
5 | import hudson.model.AbstractDescribableImpl;
6 | import hudson.model.Descriptor;
7 | import hudson.model.Item;
8 | import hudson.model.Saveable;
9 | import hudson.util.FormValidation;
10 | import java.util.List;
11 | import org.kohsuke.accmod.Restricted;
12 | import org.kohsuke.accmod.restrictions.NoExternalUse;
13 | import org.kohsuke.stapler.AncestorInPath;
14 | import org.kohsuke.stapler.DataBoundConstructor;
15 | import org.kohsuke.stapler.QueryParameter;
16 | import org.kohsuke.stapler.verb.POST;
17 |
18 | /**
19 | * @author alexeydolgopyatov
20 | */
21 | public class AkeylessSecret extends AbstractDescribableImpl implements AkeylessSecretBase {
22 |
23 | private final String path;
24 | private List secretValues;
25 |
26 | @DataBoundConstructor
27 | public AkeylessSecret(String path, List secretValues) {
28 | this.path = path;
29 | this.secretValues = secretValues;
30 | }
31 |
32 | public String getPath() {
33 | return this.path;
34 | }
35 |
36 | public List getSecretValues() {
37 | return this.secretValues;
38 | }
39 |
40 | @Extension
41 | public static final class DescriptorImpl extends Descriptor implements Saveable {
42 |
43 | @Override
44 | public String getDisplayName() {
45 | return "Akeyless Secret";
46 | }
47 |
48 | @POST
49 | @Restricted(NoExternalUse.class)
50 | public FormValidation doCheckPath(@AncestorInPath Item item, @QueryParameter String value) {
51 | if (item != null && !item.hasPermission(Item.CONFIGURE)) {
52 | // User does not have the necessary permission
53 | return FormValidation.error("You do not have permission to configure this item.");
54 | }
55 |
56 | if (!Strings.isNullOrEmpty(value)) {
57 | return FormValidation.ok();
58 | } else {
59 | return FormValidation.error("This field can not be empty");
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/cloudid/AzureCloudIdProvider.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.cloudid;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import java.io.IOException;
5 | import java.net.HttpURLConnection;
6 | import java.net.URL;
7 | import java.net.URLEncoder;
8 | import java.util.Base64;
9 | import java.util.Map;
10 |
11 | public class AzureCloudIdProvider implements CloudIdProvider {
12 |
13 | private static final String METADATA_URL = "http://169.254.169.254/metadata/identity/oauth2/token";
14 | private static final String API_VERSION = "2018-02-01";
15 | private static final String RESOURCE = "https://management.azure.com/";
16 |
17 | public String getCloudId(String objectId) throws Exception {
18 | String query = String.format("api-version=%s&resource=%s", API_VERSION, URLEncoder.encode(RESOURCE, "UTF-8"));
19 | if (objectId != null && !objectId.isEmpty()) {
20 | query += "&object_id=" + URLEncoder.encode(objectId, "UTF-8");
21 | }
22 |
23 | URL url = new URL(METADATA_URL + "?" + query);
24 | HttpURLConnection conn = (HttpURLConnection) url.openConnection();
25 | conn.setRequestMethod("GET");
26 | conn.setRequestProperty("Metadata", "true");
27 | conn.setRequestProperty("User-Agent", "AKEYLESS");
28 | conn.setConnectTimeout(3000);
29 | conn.setReadTimeout(3000);
30 |
31 | int status = conn.getResponseCode();
32 | if (status != 200) {
33 | throw new RuntimeException("Failed to get token. Status: " + status);
34 | }
35 |
36 | StringBuilder response = Utils.readDataFromStream(conn.getInputStream());
37 | ObjectMapper mapper = new ObjectMapper();
38 | Map json = mapper.readValue(response.toString(), Map.class);
39 | String accessToken = (String) json.get("access_token");
40 |
41 | if (accessToken == null || accessToken.isEmpty()) {
42 | throw new IOException("Access token not found in response");
43 | }
44 | return Base64.getEncoder().encodeToString(accessToken.getBytes(java.nio.charset.StandardCharsets.UTF_8));
45 | }
46 |
47 | @Override
48 | public String getCloudId() throws Exception {
49 | return getCloudId("");
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/credentials/AccessKeyCredentialsImpl.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.credentials;
2 |
3 | import com.cloudbees.plugins.credentials.CredentialsScope;
4 | import edu.umd.cs.findbugs.annotations.NonNull;
5 | import hudson.Extension;
6 | import hudson.util.Secret;
7 | import io.akeyless.client.model.Auth;
8 | import javax.annotation.CheckForNull;
9 | import org.kohsuke.stapler.DataBoundConstructor;
10 | import org.kohsuke.stapler.DataBoundSetter;
11 |
12 | /**
13 | * @author alexeydolgopyatov
14 | */
15 | public class AccessKeyCredentialsImpl extends AbstractAkeylessBaseStandardCredentials implements AkeylessCredential {
16 |
17 | private Secret accessKey;
18 |
19 | @NonNull
20 | @SuppressWarnings("lgtm[jenkins/plaintext-storage]")
21 | private String accessId = "";
22 |
23 | @DataBoundConstructor
24 | public AccessKeyCredentialsImpl(
25 | @CheckForNull CredentialsScope scope, @CheckForNull String id, @CheckForNull String description) {
26 | super(scope, id, description);
27 | }
28 |
29 | public Secret getAccessKey() {
30 | return accessKey;
31 | }
32 |
33 | @DataBoundSetter
34 | public void setAccessKey(Secret accessKey) {
35 | this.accessKey = accessKey;
36 | }
37 |
38 | @NonNull
39 | public String getAccessId() {
40 | return accessId;
41 | }
42 |
43 | @DataBoundSetter
44 | public void setAccessId(String accessId) {
45 | this.accessId = accessId;
46 | }
47 |
48 | @Override
49 | public CredentialsScope getScope() {
50 | return null;
51 | }
52 |
53 | public Auth getAuth() {
54 | Auth auth = new Auth();
55 | auth.setAccessType("api_key");
56 | auth.setAccessKey(accessKey.getPlainText());
57 | auth.setAccessId(accessId);
58 | return auth;
59 | }
60 |
61 | @Override
62 | public CredentialsPayload getCredentialsPayload() {
63 | CredentialsPayload payload = new CredentialsPayload();
64 | payload.setAuth(getAuth());
65 | return payload;
66 | }
67 |
68 | @Extension
69 | public static class DescriptorImpl extends BaseStandardCredentialsDescriptor {
70 |
71 | @NonNull
72 | @Override
73 | public String getDisplayName() {
74 | return "Akeyless API Key";
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/credentials/AkeylessCertCredentials.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.credentials;
2 |
3 | import com.cloudbees.plugins.credentials.CredentialsScope;
4 | import edu.umd.cs.findbugs.annotations.NonNull;
5 | import hudson.Extension;
6 | import hudson.util.Secret;
7 | import io.akeyless.client.model.Auth;
8 | import javax.annotation.CheckForNull;
9 | import org.kohsuke.stapler.DataBoundConstructor;
10 | import org.kohsuke.stapler.DataBoundSetter;
11 |
12 | public class AkeylessCertCredentials extends AbstractAkeylessBaseStandardCredentials implements AkeylessCredential {
13 |
14 | @NonNull
15 | @SuppressWarnings("lgtm[jenkins/plaintext-storage]")
16 | private String accessId = "";
17 |
18 | @NonNull
19 | private String certificate = "";
20 |
21 | @NonNull
22 | private Secret privateKey = Secret.fromString("");
23 |
24 | @DataBoundConstructor
25 | public AkeylessCertCredentials(
26 | @CheckForNull CredentialsScope scope, @CheckForNull String id, @CheckForNull String description) {
27 | super(scope, id, description);
28 | }
29 |
30 | @NonNull
31 | public String getAccessId() {
32 | return accessId;
33 | }
34 |
35 | @NonNull
36 | public String getCertificate() {
37 | return certificate;
38 | }
39 |
40 | @NonNull
41 | public Secret getPrivateKey() {
42 | return privateKey;
43 | }
44 |
45 | @DataBoundSetter
46 | public void setAccessId(String accessId) {
47 | this.accessId = accessId;
48 | }
49 |
50 | @DataBoundSetter
51 | public void setCertificate(String certificate) {
52 | this.certificate = certificate;
53 | }
54 |
55 | @DataBoundSetter
56 | public void setPrivateKey(Secret privateKey) {
57 | this.privateKey = privateKey;
58 | }
59 |
60 | public Auth getAuth() {
61 | Auth auth = new Auth();
62 | auth.setAccessId(accessId);
63 | auth.setAccessType("cert");
64 | auth.setCertData(certificate);
65 | auth.setKeyData(privateKey.getPlainText());
66 | return auth;
67 | }
68 |
69 | @Override
70 | public CredentialsPayload getCredentialsPayload() {
71 | CredentialsPayload payload = new CredentialsPayload();
72 | payload.setAuth(getAuth());
73 | return payload;
74 | }
75 |
76 | @Extension
77 | public static class DescriptorImpl extends BaseStandardCredentialsDescriptor {
78 |
79 | @NonNull
80 | @Override
81 | public String getDisplayName() {
82 | return "Akeyless Certificate";
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/credentials/AkeylessK8SCredentials.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.credentials;
2 |
3 | import com.cloudbees.plugins.credentials.CredentialsScope;
4 | import edu.umd.cs.findbugs.annotations.NonNull;
5 | import hudson.Extension;
6 | import hudson.util.Secret;
7 | import io.akeyless.client.model.Auth;
8 | import javax.annotation.CheckForNull;
9 | import org.kohsuke.stapler.DataBoundConstructor;
10 | import org.kohsuke.stapler.DataBoundSetter;
11 |
12 | public class AkeylessK8SCredentials extends AbstractAkeylessBaseStandardCredentials implements AkeylessCredential {
13 |
14 | @NonNull
15 | @SuppressWarnings("lgtm[jenkins/plaintext-storage]")
16 | private String accessId = "";
17 |
18 | private String gatewayUrl;
19 | private String configName;
20 | private Secret serviceAccountToken;
21 |
22 | @DataBoundConstructor
23 | public AkeylessK8SCredentials(
24 | @CheckForNull CredentialsScope scope, @CheckForNull String id, @CheckForNull String description) {
25 | super(scope, id, description);
26 | }
27 |
28 | @NonNull
29 | public String getAccessId() {
30 | return accessId;
31 | }
32 |
33 | @DataBoundSetter
34 | public void setAccessId(@NonNull String accessId) {
35 | this.accessId = accessId;
36 | }
37 |
38 | @NonNull
39 | public String getGatewayUrl() {
40 | return gatewayUrl;
41 | }
42 |
43 | @DataBoundSetter
44 | public void setGatewayUrl(@NonNull String gatewayUrl) {
45 | this.gatewayUrl = gatewayUrl;
46 | }
47 |
48 | @NonNull
49 | public String getConfigName() {
50 | return configName;
51 | }
52 |
53 | @DataBoundSetter
54 | public void setConfigName(@NonNull String configName) {
55 | this.configName = configName;
56 | }
57 |
58 | @NonNull
59 | public Secret getServiceAccountToken() {
60 | return serviceAccountToken;
61 | }
62 |
63 | @DataBoundSetter
64 | public void setServiceAccountToken(@NonNull Secret serviceAccountToken) {
65 | this.serviceAccountToken = serviceAccountToken;
66 | }
67 |
68 | @Override
69 | public CredentialsPayload getCredentialsPayload() {
70 | CredentialsPayload payload = new CredentialsPayload();
71 | Auth auth = new Auth()
72 | .accessType("k8s")
73 | .accessId(accessId)
74 | .gatewayUrl(gatewayUrl)
75 | .k8sAuthConfigName(configName);
76 | if (serviceAccountToken != null) {
77 | auth.setK8sServiceAccountToken(serviceAccountToken.getPlainText());
78 | }
79 | payload.setAuth(auth);
80 | return payload;
81 | }
82 |
83 | @Extension
84 | public static class DescriptorImpl extends BaseStandardCredentialsDescriptor {
85 |
86 | @NonNull
87 | @Override
88 | public String getDisplayName() {
89 | return "Akeyless Kubernetes Credentials";
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/credentials/AkeylessCloudCredentials.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.credentials;
2 |
3 | import com.cloudbees.plugins.credentials.CredentialsScope;
4 | import edu.umd.cs.findbugs.annotations.NonNull;
5 | import hudson.Extension;
6 | import hudson.model.Item;
7 | import hudson.util.ListBoxModel;
8 | import io.akeyless.client.model.Auth;
9 | import javax.annotation.CheckForNull;
10 | import org.kohsuke.accmod.Restricted;
11 | import org.kohsuke.accmod.restrictions.NoExternalUse;
12 | import org.kohsuke.stapler.AncestorInPath;
13 | import org.kohsuke.stapler.DataBoundConstructor;
14 | import org.kohsuke.stapler.DataBoundSetter;
15 | import org.kohsuke.stapler.verb.POST;
16 |
17 | public class AkeylessCloudCredentials extends AbstractAkeylessBaseStandardCredentials implements AkeylessCredential {
18 |
19 | @NonNull
20 | @SuppressWarnings("lgtm[jenkins/plaintext-storage]")
21 | private String accessId = "";
22 |
23 | @NonNull
24 | private String cloudType = "aws_iam";
25 |
26 | @DataBoundConstructor
27 | public AkeylessCloudCredentials(
28 | @CheckForNull CredentialsScope scope, @CheckForNull String id, @CheckForNull String description) {
29 | super(scope, id, description);
30 | }
31 |
32 | @NonNull
33 | public String getAccessId() {
34 | return accessId;
35 | }
36 |
37 | @NonNull
38 | public String getCloudType() {
39 | return cloudType;
40 | }
41 |
42 | @DataBoundSetter
43 | public void setAccessId(String accessId) {
44 | this.accessId = accessId;
45 | }
46 |
47 | @DataBoundSetter
48 | public void setCloudType(String cloudType) {
49 | this.cloudType = cloudType;
50 | }
51 |
52 | @Override
53 | public CredentialsPayload getCredentialsPayload() {
54 | CredentialsPayload payload = new CredentialsPayload();
55 | Auth auth = new Auth();
56 | auth.setAccessId(accessId);
57 | auth.setAccessType(cloudType);
58 | payload.setAuth(auth);
59 | payload.setCloudIdNeeded(true);
60 | return payload;
61 | }
62 |
63 | @Extension
64 | public static class DescriptorImpl extends BaseStandardCredentialsDescriptor {
65 |
66 | @NonNull
67 | @Override
68 | public String getDisplayName() {
69 | return "Akeyless Cloud Provider Credentials";
70 | }
71 |
72 | @POST
73 | @Restricted(NoExternalUse.class)
74 | public ListBoxModel doFillCloudTypeItems(@AncestorInPath Item context) {
75 | ListBoxModel options = new ListBoxModel(
76 | new ListBoxModel.Option("AWS-IAM", "aws_iam"),
77 | new ListBoxModel.Option("GCP", "gcp"),
78 | new ListBoxModel.Option("Azure-AD", "azure_ad"));
79 |
80 | if (context != null && context.hasPermission(Item.CONFIGURE)) {
81 | ListBoxModel.Option option = new ListBoxModel.Option("Default", "");
82 | options.add(0, option);
83 | }
84 | return options;
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/akeyless/configuration/AkeylessConfigurationTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.configuration;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 |
6 | import org.junit.jupiter.api.Test;
7 |
8 | class AkeylessConfigurationTest {
9 |
10 | private enum ConfigEnum {
11 | ALL,
12 | URL_ONLY,
13 | CREDENTIALS_ONLY
14 | }
15 |
16 | @Test
17 | void handleNullConfiguration() {
18 | AkeylessConfiguration configuration = testConfig("test", ConfigEnum.ALL);
19 | AkeylessConfiguration result = configuration.mergeWithParent(null);
20 | assertThat(result.getAkeylessCredentialId(), is(result.getAkeylessCredentialId()));
21 | assertThat(result.getAkeylessUrl(), is(result.getAkeylessUrl()));
22 | }
23 |
24 | @Test
25 | void childShouldPartlyOverwriteParent() {
26 | AkeylessConfiguration parent = testConfig("parent", ConfigEnum.ALL);
27 | AkeylessConfiguration child = testConfig("child", ConfigEnum.URL_ONLY);
28 | AkeylessConfiguration result = child.mergeWithParent(parent);
29 |
30 | assertThat(result.getAkeylessCredentialId(), is(parent.getAkeylessCredentialId()));
31 | assertThat(result.getAkeylessUrl(), is(child.getAkeylessUrl()));
32 |
33 | parent = testConfig("parent", ConfigEnum.ALL);
34 | child = testConfig("child", ConfigEnum.CREDENTIALS_ONLY);
35 | result = child.mergeWithParent(parent);
36 |
37 | assertThat(result.getAkeylessCredentialId(), is(child.getAkeylessCredentialId()));
38 | assertThat(result.getAkeylessUrl(), is(parent.getAkeylessUrl()));
39 | }
40 |
41 | @Test
42 | void emptyParentShouldBeIgnored() {
43 | AkeylessConfiguration parent = new AkeylessConfiguration();
44 | AkeylessConfiguration child = testConfig("child", ConfigEnum.ALL);
45 | AkeylessConfiguration result = child.mergeWithParent(parent);
46 |
47 | assertThat(result.getAkeylessCredentialId(), is(child.getAkeylessCredentialId()));
48 | assertThat(result.getAkeylessUrl(), is(child.getAkeylessUrl()));
49 | }
50 |
51 | @Test
52 | void childShouldCompletelyOverwriteParent() {
53 | AkeylessConfiguration parent = testConfig("parent", ConfigEnum.ALL);
54 | AkeylessConfiguration child = testConfig("child", ConfigEnum.ALL);
55 | AkeylessConfiguration result = child.mergeWithParent(parent);
56 |
57 | assertThat(result.getAkeylessCredentialId(), is(child.getAkeylessCredentialId()));
58 | assertThat(result.getAkeylessUrl(), is(child.getAkeylessUrl()));
59 | }
60 |
61 | private static AkeylessConfiguration testConfig(String id, ConfigEnum confType) {
62 | AkeylessConfiguration configuration = new AkeylessConfiguration();
63 | switch (confType) {
64 | case ALL:
65 | configuration.setAkeylessUrl("https://akeyless.io/" + id);
66 | configuration.setAkeylessCredentialId(id);
67 | return configuration;
68 | case URL_ONLY:
69 | configuration.setAkeylessUrl("https://akeyless.io/" + id);
70 | return configuration;
71 | case CREDENTIALS_ONLY:
72 | configuration.setAkeylessCredentialId(id);
73 | return configuration;
74 | }
75 | throw new RuntimeException("Unknown config type: " + confType);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/credentials/AkeylessCredentialsProvider.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.credentials;
2 |
3 | import com.cloudbees.hudson.plugins.folder.AbstractFolder;
4 | import com.cloudbees.hudson.plugins.folder.properties.FolderCredentialsProvider;
5 | import com.cloudbees.plugins.credentials.*;
6 | import com.cloudbees.plugins.credentials.domains.DomainCredentials;
7 | import com.cloudbees.plugins.credentials.domains.DomainRequirement;
8 | import hudson.Extension;
9 | import hudson.model.ItemGroup;
10 | import hudson.security.ACL;
11 | import java.util.ArrayList;
12 | import java.util.Collections;
13 | import java.util.List;
14 | import javax.annotation.Nonnull;
15 | import javax.annotation.Nullable;
16 | import jenkins.model.Jenkins;
17 | import org.acegisecurity.Authentication;
18 |
19 | /**
20 | * @author alexeydolgopyatov
21 | */
22 | @Extension(optional = true, ordinal = 1)
23 | public class AkeylessCredentialsProvider extends CredentialsProvider {
24 |
25 | @Nonnull
26 | @Override
27 | public List getCredentials(
28 | @Nonnull Class type, @Nullable ItemGroup itemGroup, @Nullable Authentication authentication) {
29 | return getCredentials(type, itemGroup, authentication, Collections.emptyList());
30 | }
31 |
32 | @Nonnull
33 | @Override
34 | public List getCredentials(
35 | @Nonnull Class type,
36 | @Nullable ItemGroup itemGroup,
37 | @Nullable Authentication authentication,
38 | @Nonnull List domainRequirements) {
39 | CredentialsMatcher matcher = (type != AkeylessCredential.class
40 | ? CredentialsMatchers.instanceOf(AbstractAkeylessBaseStandardCredentials.class)
41 | : CredentialsMatchers.always());
42 | List creds = new ArrayList();
43 | if (ACL.SYSTEM.equals(authentication)) {
44 | for (ItemGroup g = itemGroup; g instanceof AbstractFolder; g = (AbstractFolder.class.cast(g)).getParent()) {
45 | FolderCredentialsProvider.FolderCredentialsProperty property = ((AbstractFolder>) g)
46 | .getProperties()
47 | .get(FolderCredentialsProvider.FolderCredentialsProperty.class);
48 | if (property == null) {
49 | continue;
50 | }
51 |
52 | List folderCreds = DomainCredentials.getCredentials(
53 | property.getDomainCredentialsMap(), type, domainRequirements, matcher);
54 |
55 | if (type != AkeylessCredential.class) {
56 | for (C c : folderCreds) {
57 | ((AbstractAkeylessBaseStandardCredentials) c).setContext(g);
58 | }
59 | }
60 |
61 | creds.addAll(folderCreds);
62 | }
63 |
64 | List globalCreds = DomainCredentials.getCredentials(
65 | SystemCredentialsProvider.getInstance().getDomainCredentialsMap(),
66 | type,
67 | domainRequirements,
68 | matcher);
69 | if (type != AkeylessCredential.class) {
70 | for (C c : globalCreds) {
71 | ((AbstractAkeylessBaseStandardCredentials) c).setContext(Jenkins.get());
72 | }
73 | }
74 | creds.addAll(globalCreds);
75 | }
76 |
77 | return creds;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/MaskSecretsLogsFilter.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless;
2 |
3 | import hudson.console.ConsoleLogFilter;
4 | import hudson.console.LineTransformationOutputStream;
5 | import hudson.model.Run;
6 | import java.io.IOException;
7 | import java.io.OutputStream;
8 | import java.io.Serializable;
9 | import java.util.ArrayList;
10 | import java.util.Arrays;
11 | import java.util.Collection;
12 | import java.util.List;
13 | import java.util.regex.Matcher;
14 | import java.util.regex.Pattern;
15 | import org.apache.commons.lang3.StringUtils;
16 |
17 | /* The class is borrowed from https://github.com/jenkinsci/onepassword-secrets-plugin/ */
18 | public class MaskSecretsLogsFilter extends ConsoleLogFilter implements Serializable {
19 |
20 | private static final long serialVersionUID = 1L;
21 |
22 | private final String charsetName;
23 | private List valuesToMask;
24 |
25 | public MaskSecretsLogsFilter(final String charsetName, List valuesToMask) {
26 | this.charsetName = charsetName;
27 | this.valuesToMask = valuesToMask;
28 | }
29 |
30 | @Override
31 | public OutputStream decorateLogger(Run run, final OutputStream logger) throws IOException, InterruptedException {
32 | return new LineTransformationOutputStream() {
33 | Pattern p;
34 |
35 | @Override
36 | protected void eol(byte[] b, int len) throws IOException {
37 | List splitValuesToMask = new ArrayList();
38 | for (String valueToMask : valuesToMask) {
39 | List splitValues = new ArrayList(Arrays.asList(valueToMask.split("\n")));
40 | splitValuesToMask.addAll(splitValues);
41 | }
42 | p = Pattern.compile(getPatternStringForSecrets(splitValuesToMask));
43 | if (StringUtils.isBlank(p.pattern())) {
44 | logger.write(b, 0, len);
45 | return;
46 | }
47 | Matcher m = p.matcher(new String(b, 0, len, charsetName));
48 | if (m.find()) {
49 | logger.write(m.replaceAll("****").getBytes(charsetName));
50 | } else {
51 | // Avoid byte → char → byte conversion unless we are actually doing something.
52 | logger.write(b, 0, len);
53 | }
54 | }
55 | };
56 | }
57 |
58 | /**
59 | * Utility method for turning a collection of secret strings into a single {@link String} for
60 | * pattern compilation.
61 | *
62 | * @param secrets A collection of secret strings
63 | * @return A {@link String} generated from that collection.
64 | */
65 | public static String getPatternStringForSecrets(Collection secrets) {
66 | if (secrets == null) {
67 | return "";
68 | }
69 | StringBuilder b = new StringBuilder();
70 | List sortedByLength = new ArrayList<>(secrets.size());
71 | for (String secret : secrets) {
72 | if (secret != null) {
73 | sortedByLength.add(secret);
74 | }
75 | }
76 | sortedByLength.sort((o1, o2) -> o2.length() - o1.length());
77 |
78 | for (String secret : sortedByLength) {
79 | if (b.length() > 0) {
80 | b.append('|');
81 | }
82 | b.append(Pattern.quote(secret));
83 | }
84 | return b.toString();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/akeyless/AkeylessAccessorFillObjectValuesTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 | import static org.junit.jupiter.api.Assertions.assertThrows;
6 |
7 | import com.google.gson.internal.LinkedTreeMap;
8 | import hudson.EnvVars;
9 | import io.jenkins.plugins.akeyless.model.AkeylessSecret;
10 | import io.jenkins.plugins.akeyless.model.AkeylessSecretBase;
11 | import io.jenkins.plugins.akeyless.model.AkeylessSecretValue;
12 | import java.io.ByteArrayOutputStream;
13 | import java.io.PrintStream;
14 | import java.util.*;
15 | import org.junit.jupiter.api.Test;
16 |
17 | class AkeylessAccessorFillObjectValuesTest {
18 |
19 | private static PrintStream logger() {
20 | return new PrintStream(new ByteArrayOutputStream());
21 | }
22 |
23 | @Test
24 | void fillsValuesFromPlainStringSecret() {
25 | String path = "/s/plain";
26 | AkeylessSecretValue value = new AkeylessSecretValue("data");
27 | AkeylessSecret secret = new AkeylessSecret(path, Collections.singletonList(value));
28 |
29 | AkeylessAccessor fake = new AkeylessAccessor(null, null) {
30 | @Override
31 | public Map getSecret(String token, AkeylessSecretBase akeylessSecret) {
32 | Map values = new HashMap<>();
33 | values.put(path, "hello\nworld");
34 | return values;
35 | }
36 | };
37 |
38 | Map env = new HashMap<>();
39 | AkeylessAccessor.fillObjectValues(logger(), new EnvVars(), fake, "tkn", Collections.singletonList(secret), env);
40 |
41 | assertThat(env.get("data"), is("hello\nworld"));
42 | }
43 |
44 | @Test
45 | void fillsValuesFromStructuredSecret() {
46 | String path = "/s/json";
47 | AkeylessSecretValue value = new AkeylessSecretValue("username");
48 | value.setEnvVar("DB_USER");
49 | AkeylessSecret secret = new AkeylessSecret(path, Collections.singletonList(value));
50 |
51 | AkeylessAccessor fake = new AkeylessAccessor(null, null) {
52 | @Override
53 | public Map getSecret(String token, AkeylessSecretBase akeylessSecret) {
54 | LinkedTreeMap inner = new LinkedTreeMap<>();
55 | inner.put("username", "admin");
56 | Map values = new HashMap<>();
57 | values.put(path, inner);
58 | return values;
59 | }
60 | };
61 |
62 | Map env = new HashMap<>();
63 | AkeylessAccessor.fillObjectValues(logger(), new EnvVars(), fake, "tkn", Collections.singletonList(secret), env);
64 |
65 | assertThat(env.get("DB_USER"), is("admin"));
66 | }
67 |
68 | @Test
69 | void throwsWhenRequiredValueMissing() {
70 | String path = "/s/missing";
71 | AkeylessSecretValue value = new AkeylessSecretValue("password");
72 | // value is required by default
73 | AkeylessSecret secret = new AkeylessSecret(path, Collections.singletonList(value));
74 |
75 | AkeylessAccessor fake = new AkeylessAccessor(null, null) {
76 | @Override
77 | public Map getSecret(String token, AkeylessSecretBase akeylessSecret) {
78 | LinkedTreeMap inner = new LinkedTreeMap<>();
79 | inner.put("username", "admin");
80 | Map values = new HashMap<>();
81 | values.put(path, inner);
82 | return values;
83 | }
84 | };
85 |
86 | Map env = new HashMap<>();
87 | assertThrows(
88 | IllegalArgumentException.class,
89 | () -> AkeylessAccessor.fillObjectValues(
90 | logger(), new EnvVars(), fake, "tkn", Collections.singletonList(secret), env));
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/AkeylessBuildWrapper.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless;
2 |
3 | import com.google.common.annotations.VisibleForTesting;
4 | import edu.umd.cs.findbugs.annotations.CheckForNull;
5 | import edu.umd.cs.findbugs.annotations.NonNull;
6 | import hudson.*;
7 | import hudson.console.ConsoleLogFilter;
8 | import hudson.model.AbstractProject;
9 | import hudson.model.Run;
10 | import hudson.model.TaskListener;
11 | import hudson.tasks.BuildWrapperDescriptor;
12 | import io.jenkins.plugins.akeyless.configuration.AkeylessConfigResolver;
13 | import io.jenkins.plugins.akeyless.configuration.AkeylessConfiguration;
14 | import io.jenkins.plugins.akeyless.model.AkeylessPKIIssuer;
15 | import io.jenkins.plugins.akeyless.model.AkeylessSSHIssuer;
16 | import io.jenkins.plugins.akeyless.model.AkeylessSecret;
17 | import java.io.IOException;
18 | import java.io.PrintStream;
19 | import java.util.ArrayList;
20 | import java.util.List;
21 | import java.util.Map;
22 | import jenkins.tasks.SimpleBuildWrapper;
23 | import org.kohsuke.stapler.DataBoundConstructor;
24 | import org.kohsuke.stapler.DataBoundSetter;
25 |
26 | /**
27 | * @author alexeydolgopyatov
28 | */
29 | public class AkeylessBuildWrapper extends SimpleBuildWrapper {
30 | protected transient PrintStream logger;
31 | private List akeylessSecrets;
32 | private transient AkeylessAccessor accessor;
33 | private AkeylessConfiguration configuration;
34 | private List valuesToMask = new ArrayList<>();
35 | private List akeylessPKIIssuers;
36 | private List akeylessSSHIssuers;
37 |
38 | @DataBoundConstructor
39 | public AkeylessBuildWrapper(
40 | @CheckForNull List akeylessSecrets,
41 | @CheckForNull List akeylessPKIIssuers,
42 | @CheckForNull List akeylessSSHIssuers) {
43 | this.akeylessSecrets = akeylessSecrets;
44 | this.akeylessPKIIssuers = akeylessPKIIssuers;
45 | this.akeylessSSHIssuers = akeylessSSHIssuers;
46 | }
47 |
48 | public List getAkeylessSecrets() {
49 | return this.akeylessSecrets;
50 | }
51 |
52 | public List getAkeylessPKIIssuers() {
53 | return this.akeylessPKIIssuers;
54 | }
55 |
56 | public List getAkeylessSSHIssuers() {
57 | return this.akeylessSSHIssuers;
58 | }
59 |
60 | @DataBoundSetter
61 | public void setConfiguration(AkeylessConfiguration configuration) {
62 | this.configuration = configuration;
63 | }
64 |
65 | public AkeylessConfiguration getConfiguration() {
66 | return this.configuration;
67 | }
68 |
69 | @VisibleForTesting
70 | public void setAkeylessAccessor(AkeylessAccessor accessor) {
71 | this.accessor = accessor;
72 | }
73 |
74 | @Override
75 | public void setUp(
76 | Context context,
77 | Run, ?> build,
78 | FilePath workspace,
79 | Launcher launcher,
80 | TaskListener listener,
81 | EnvVars initialEnvironment)
82 | throws IOException, InterruptedException {
83 | logger = listener.getLogger();
84 | buildConfiguration(build);
85 | retrieveSecretsAndSetToEnvironments(context, build, initialEnvironment);
86 | }
87 |
88 | private void retrieveSecretsAndSetToEnvironments(Context context, Run, ?> build, EnvVars envVars) {
89 | Map overrides = AkeylessAccessor.retrieveSecrets(
90 | build,
91 | logger,
92 | envVars,
93 | accessor,
94 | getConfiguration(),
95 | getAkeylessSecrets(),
96 | getAkeylessPKIIssuers(),
97 | getAkeylessSSHIssuers());
98 |
99 | for (Map.Entry secret : overrides.entrySet()) {
100 | valuesToMask.add(secret.getValue());
101 | context.env(secret.getKey(), secret.getValue());
102 | }
103 | }
104 |
105 | private void buildConfiguration(Run, ?> build) {
106 | for (AkeylessConfigResolver resolver : ExtensionList.lookup(AkeylessConfigResolver.class)) {
107 | if (configuration != null) {
108 | configuration = configuration.mergeWithParent(resolver.forJob(build.getParent()));
109 | } else {
110 | configuration = resolver.forJob(build.getParent());
111 | }
112 | }
113 | if (configuration == null) {
114 | throw new AkeylessPluginException("No configuration found - please configure the AkeylessPlugin.");
115 | }
116 | }
117 |
118 | @Override
119 | public ConsoleLogFilter createLoggerDecorator(@NonNull final Run, ?> build) {
120 | return new MaskSecretsLogsFilter(build.getCharset().name(), valuesToMask);
121 | }
122 |
123 | @Extension
124 | public static final class DescriptorImpl extends BuildWrapperDescriptor {
125 |
126 | public DescriptorImpl() {
127 | super(AkeylessBuildWrapper.class);
128 | }
129 |
130 | @Override
131 | public boolean isApplicable(AbstractProject, ?> item) {
132 | return true;
133 | }
134 |
135 | /**
136 | * This human-readable name is used in the configuration screen.
137 | */
138 | @Override
139 | public String getDisplayName() {
140 | return "Akeyless Plugin";
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | org.jenkins-ci.plugins
7 | plugin
8 | 5.9
9 |
10 |
11 |
12 | io.jenkins.plugins
13 | akeyless
14 | ${changelist}
15 | hpi
16 |
17 | Akeyless Plugin
18 | https://github.com/jenkinsci/${project.artifactId}-plugin
19 |
20 |
21 | MIT License
22 | https://opensource.org/license/mit/
23 |
24 |
25 |
26 | scm:git:https://github.com/${gitHubRepo}
27 | scm:git:https://github.com/${gitHubRepo}
28 | ${scmTag}
29 | https://github.com/${gitHubRepo}
30 |
31 |
32 |
33 | 2.479
34 | 999999-SNAPSHOT
35 |
36 | ${jenkins.baseline}.3
37 | jenkinsci/${project.artifactId}-plugin
38 | false
39 |
40 |
41 |
42 |
43 |
44 | io.jenkins.tools.bom
45 | bom-2.479.x
46 | 4545.v56392b_7ca_7b_a_
47 | pom
48 | import
49 |
50 |
51 |
52 |
53 |
54 | io.akeyless
55 | akeyless-java
56 | 5.0.8
57 | compile
58 |
59 |
60 | com.fasterxml.jackson.core
61 | *
62 |
63 |
64 | com.google.code.gson
65 | gson
66 |
67 |
68 | com.squareup.okhttp3
69 | logging-interceptor
70 |
71 |
72 | com.squareup.okhttp3
73 | okhttp
74 |
75 |
76 | com.squareup.okio
77 | okio
78 |
79 |
80 | io.swagger
81 | swagger-annotations
82 |
83 |
84 | org.apache.commons
85 | commons-lang3
86 |
87 |
88 | org.jetbrains.kotlin
89 | kotlin-stdlib-common
90 |
91 |
92 | org.jetbrains.kotlin
93 | kotlin-stdlib-jdk8
94 |
95 |
96 |
97 |
98 | io.jenkins.plugins
99 | commons-lang3-api
100 |
101 |
102 | io.jenkins.plugins
103 | gson-api
104 |
105 |
106 | io.jenkins.plugins
107 | okhttp-api
108 |
109 |
110 | org.jenkins-ci.plugins
111 | cloudbees-folder
112 |
113 |
114 | org.jenkins-ci.plugins
115 | credentials-binding
116 |
117 |
118 | org.jenkins-ci.plugins
119 | jackson2-api
120 |
121 |
122 | org.jenkins-ci.plugins
123 | structs
124 |
125 |
126 |
127 |
128 | repo.jenkins-ci.org
129 | https://repo.jenkins-ci.org/public/
130 |
131 |
132 | io.akeyless
133 | https://akeyless.jfrog.io/artifactory/akeyless-java/
134 |
135 |
136 | mvn
137 | https://repo1.maven.org/maven2/
138 |
139 |
140 |
141 |
142 | repo.jenkins-ci.org
143 | https://repo.jenkins-ci.org/public/
144 |
145 |
146 | io.akeyless
147 | https://akeyless.jfrog.io/artifactory/akeyless-java/
148 |
149 |
150 |
151 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/cloudid/AwsCredentialResolver.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.cloudid;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import java.io.BufferedReader;
5 | import java.io.InputStreamReader;
6 | import java.net.HttpURLConnection;
7 | import java.net.URL;
8 | import java.nio.charset.StandardCharsets;
9 | import java.util.Map;
10 |
11 | public class AwsCredentialResolver {
12 |
13 | public static class AwsCredentials {
14 | public final String accessKeyId;
15 | public final String secretAccessKey;
16 | public final String sessionToken;
17 |
18 | public AwsCredentials(String accessKeyId, String secretAccessKey, String sessionToken) {
19 | this.accessKeyId = accessKeyId;
20 | this.secretAccessKey = secretAccessKey;
21 | this.sessionToken = sessionToken;
22 | }
23 | }
24 |
25 | public static AwsCredentials resolve() throws Exception {
26 | // 1. Environment Variables
27 | String accessKey = System.getenv("AWS_ACCESS_KEY_ID");
28 | String secretKey = System.getenv("AWS_SECRET_ACCESS_KEY");
29 | String sessionToken = System.getenv("AWS_SESSION_TOKEN"); // optional
30 |
31 | if (accessKey != null && secretKey != null) {
32 | return new AwsCredentials(accessKey, secretKey, sessionToken);
33 | }
34 |
35 | // 2. ECS Container Credentials (via AWS_CONTAINER_CREDENTIALS_RELATIVE_URI)
36 | String relativeUri = System.getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI");
37 | if (relativeUri != null) {
38 | String ecsUrl = "http://169.254.170.2" + relativeUri;
39 | return fetchCredentialsFromMetadataService(ecsUrl);
40 | }
41 |
42 | // 3. EC2 Instance Metadata (IMDSv2)
43 | String token = fetchImdsV2Token();
44 | String roleName = httpGet("http://169.254.169.254/latest/meta-data/iam/security-credentials/", token);
45 | String credsJson =
46 | httpGet("http://169.254.169.254/latest/meta-data/iam/security-credentials/" + roleName, token);
47 |
48 | // Parse JSON (using manual parsing or Jackson if needed)
49 | Map json = new com.fasterxml.jackson.databind.ObjectMapper().readValue(credsJson, Map.class);
50 |
51 | return new AwsCredentials(
52 | (String) json.get("AccessKeyId"), (String) json.get("SecretAccessKey"), (String) json.get("Token"));
53 | }
54 |
55 | private static String fetchImdsV2Token() throws Exception {
56 | URL url = new URL("http://169.254.169.254/latest/api/token");
57 | HttpURLConnection conn = (HttpURLConnection) url.openConnection();
58 | conn.setConnectTimeout(3000);
59 | conn.setReadTimeout(3000);
60 | conn.setRequestMethod("PUT");
61 | conn.setRequestProperty("X-aws-ec2-metadata-token-ttl-seconds", "21600");
62 |
63 | if (conn.getResponseCode() != 200) {
64 | throw new RuntimeException("Failed to fetch IMDSv2 token");
65 | }
66 | BufferedReader br = null;
67 | try {
68 | br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
69 | String token = br.lines().collect(java.util.stream.Collectors.joining("\n"));
70 | return token;
71 | } catch (Exception e) {
72 | throw new RuntimeException("Failed to read IMDSv2 token response", e);
73 | } finally {
74 | if (br != null) {
75 | try {
76 | br.close();
77 | } catch (Exception e) {
78 | // Ignore
79 | }
80 | }
81 | conn.disconnect();
82 | }
83 | }
84 |
85 | private static String httpGet(String url, String imdsToken) throws Exception {
86 | HttpURLConnection conn = null;
87 | try {
88 | conn = (HttpURLConnection) new URL(url).openConnection();
89 |
90 | conn.setRequestMethod("GET");
91 | if (imdsToken != null) {
92 | conn.setRequestProperty("X-aws-ec2-metadata-token", imdsToken);
93 | }
94 |
95 | if (conn.getResponseCode() != 200) {
96 | throw new RuntimeException("Failed to fetch metadata from " + url);
97 | }
98 | return Utils.readDataFromStream(conn.getInputStream()).toString();
99 | } finally {
100 | if (conn != null) {
101 | conn.disconnect();
102 | }
103 | }
104 | }
105 |
106 | private static AwsCredentials fetchCredentialsFromMetadataService(String urlStr) throws Exception {
107 | URL url = new URL(urlStr);
108 | HttpURLConnection conn = null;
109 | try {
110 | conn = (HttpURLConnection) url.openConnection();
111 | conn.setRequestMethod("GET");
112 | conn.setConnectTimeout(3000);
113 | conn.setReadTimeout(3000);
114 |
115 | if (conn.getResponseCode() != 200) {
116 | throw new RuntimeException("Failed to fetch ECS credentials from " + urlStr);
117 | }
118 | StringBuilder response = Utils.readDataFromStream(conn.getInputStream());
119 | Map json = new ObjectMapper().readValue(response.toString(), Map.class);
120 | return new AwsCredentials(
121 | (String) json.get("AccessKeyId"), (String) json.get("SecretAccessKey"), (String) json.get("Token"));
122 | } finally {
123 | if (conn != null) {
124 | conn.disconnect();
125 | }
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/cloudid/AwsCloudIdProvider.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.cloudid;
2 |
3 | import static java.nio.charset.StandardCharsets.UTF_8;
4 |
5 | import com.fasterxml.jackson.databind.ObjectMapper;
6 | import java.net.URI;
7 | import java.security.MessageDigest;
8 | import java.time.ZoneOffset;
9 | import java.time.ZonedDateTime;
10 | import java.util.*;
11 | import javax.crypto.Mac;
12 | import javax.crypto.spec.SecretKeySpec;
13 |
14 | public class AwsCloudIdProvider implements CloudIdProvider {
15 |
16 | private static final String SERVICE = "sts";
17 | private static final String REGION = "us-east-1";
18 | private static final String ENDPOINT = "https://sts.amazonaws.com/";
19 |
20 | @Override
21 | public String getCloudId() throws Exception {
22 | AwsCredentialResolver.AwsCredentials creds = AwsCredentialResolver.resolve();
23 |
24 | if (creds.accessKeyId == null || creds.secretAccessKey == null) {
25 | throw new IllegalStateException("Missing AWS credentials");
26 | }
27 |
28 | String body = "Action=GetCallerIdentity&Version=2011-06-15";
29 | byte[] bodyBytes = body.getBytes(UTF_8);
30 |
31 | ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
32 | String amzDate = now.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'"));
33 | String dateStamp = now.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"));
34 |
35 | String host = URI.create(ENDPOINT).getHost();
36 | String contentSha256 = toHex(hash(bodyBytes));
37 |
38 | Map> headers = new LinkedHashMap<>();
39 | headers.put("Content-Type", Collections.singletonList("application/x-www-form-urlencoded; charset=utf-8"));
40 | headers.put("Host", Collections.singletonList(host));
41 | headers.put("X-Amz-Date", Collections.singletonList(amzDate));
42 | if (creds.sessionToken != null) {
43 | headers.put("X-Amz-Security-Token", Collections.singletonList(creds.sessionToken));
44 | }
45 |
46 | // Step 1: Create canonical request
47 | StringBuilder canonicalHeaders = new StringBuilder();
48 | List signedHeadersList = new ArrayList<>();
49 | for (Map.Entry> entry : headers.entrySet()) {
50 | String headerValue = entry.getValue().get(0); // take first value from List
51 | canonicalHeaders
52 | .append(entry.getKey().toLowerCase())
53 | .append(":")
54 | .append(headerValue.trim())
55 | .append("\n");
56 | signedHeadersList.add(entry.getKey().toLowerCase());
57 | }
58 |
59 | String signedHeaders = String.join(";", signedHeadersList);
60 | String canonicalRequest = "POST\n/\n\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + contentSha256;
61 |
62 | // Step 2: Create string to sign
63 | String algorithm = "AWS4-HMAC-SHA256";
64 | String credentialScope = dateStamp + "/" + REGION + "/" + SERVICE + "/aws4_request";
65 | String stringToSign = algorithm + "\n" + amzDate
66 | + "\n" + credentialScope
67 | + "\n" + toHex(hash(canonicalRequest.getBytes(UTF_8)));
68 |
69 | // Step 3: Calculate the signature
70 | byte[] signingKey = getSignatureKey(creds.secretAccessKey, dateStamp, REGION, SERVICE);
71 | byte[] signature = hmacSHA256(signingKey, stringToSign);
72 | String signatureHex = toHex(signature);
73 |
74 | // Step 4: Build Authorization header
75 | String authorizationHeader = algorithm + " " + "Credential="
76 | + creds.accessKeyId + "/" + credentialScope + ", " + "SignedHeaders="
77 | + signedHeaders + ", " + "Signature="
78 | + signatureHex;
79 |
80 | headers.put("Authorization", Collections.singletonList(authorizationHeader));
81 |
82 | // Now build the final output (like Go version)
83 | ObjectMapper mapper = new ObjectMapper();
84 | String headersJson = mapper.writeValueAsString(headers);
85 |
86 | Map awsData = new LinkedHashMap<>();
87 | awsData.put("sts_request_method", "POST");
88 | awsData.put("sts_request_url", Base64.getEncoder().encodeToString(ENDPOINT.getBytes(UTF_8)));
89 | awsData.put("sts_request_body", Base64.getEncoder().encodeToString(bodyBytes));
90 | awsData.put("sts_request_headers", Base64.getEncoder().encodeToString(headersJson.getBytes(UTF_8)));
91 |
92 | String awsDataJson = mapper.writeValueAsString(awsData);
93 | return Base64.getEncoder().encodeToString(awsDataJson.getBytes(UTF_8));
94 | }
95 |
96 | // --- Helper functions ---
97 |
98 | private static byte[] hmacSHA256(byte[] key, String data) throws Exception {
99 | String algorithm = "HmacSHA256";
100 | Mac mac = Mac.getInstance(algorithm);
101 | mac.init(new SecretKeySpec(key, algorithm));
102 | return mac.doFinal(data.getBytes(UTF_8));
103 | }
104 |
105 | private static byte[] getSignatureKey(String secretKey, String date, String region, String service)
106 | throws Exception {
107 | byte[] kSecret = ("AWS4" + secretKey).getBytes(UTF_8);
108 | byte[] kDate = hmacSHA256(kSecret, date);
109 | byte[] kRegion = hmacSHA256(kDate, region);
110 | byte[] kService = hmacSHA256(kRegion, service);
111 | return hmacSHA256(kService, "aws4_request");
112 | }
113 |
114 | private static byte[] hash(byte[] data) throws Exception {
115 | MessageDigest md = MessageDigest.getInstance("SHA-256");
116 | return md.digest(data);
117 | }
118 |
119 | private static String toHex(byte[] bytes) {
120 | StringBuilder hex = new StringBuilder(bytes.length * 2);
121 | for (byte b : bytes) {
122 | hex.append(String.format("%02x", b));
123 | }
124 | return hex.toString();
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Akeyless Plugin for Jenkins
2 |
3 | ## Introduction
4 | The **Akeyless Plugin for Jenkins** enables secure integration of Akeyless-managed secrets and certificates within Jenkins pipelines. It supports multiple authentication methods, ensuring seamless and secure access to secrets and certificates.
5 |
6 | Additionally, JSON-structured secrets can be retrieved by specifying specific keys, allowing precise control over the data fetched from Akeyless.
7 |
8 | ## Table of Contents
9 | - [Installation](#Installation)
10 |
11 | - [Supported Authentication Methods](#supported-authentication-methods)
12 |
13 | - [Configuration](#configuration)
14 |
15 | - [Retrieving Items](#Retrieving-Items)
16 |
17 | - [Fetching Secrets](#fetching-secrets)
18 |
19 | - [Fetching Certificates](#fetching-certificates)
20 |
21 | - [Examples](#examples)
22 |
23 | - [Configuring API Key Authentication](#setting-api-key-authentication)
24 |
25 | - [Fetching a Static Secret](#fetching-a-static-secret)
26 |
27 | - [Fetching a Rotated Secret](#fetching-a-rotated-secret-with-specific-keys)
28 |
29 | - [Issuing an SSH Certificate](#issuing-an-ssh-certificate)
30 |
31 | - [Issuing a PKI Certificate](#issuing-a-pki-certificate)
32 |
33 | ---
34 |
35 | ## Installation
36 |
37 | Run the following steps to install the Akeyless plugin for Jenkins:
38 |
39 | 1. Navigate to **Manage Jenkins** → **Plugins**.
40 |
41 | 2. Go to **Available Plugins** and search for **Akeyless**.
42 |
43 | 3. Check the plugin and press **Install**
44 |
45 |
46 | ## Supported Authentication Methods
47 | The plugin supports the following authentication methods:
48 |
49 | - [API Key](https://docs.akeyless.io/docs/api-key)
50 |
51 | - [AWS IAM](https://docs.akeyless.io/docs/aws-iam)
52 |
53 | - [Azure AD](https://docs.akeyless.io/docs/azure-ad)
54 |
55 | - [Certificate](https://docs.akeyless.io/docs/certificate-based-authentication)
56 |
57 | - [Google Cloud Platform (GCP)](https://docs.akeyless.io/docs/gcp-auth-method)
58 |
59 | - [Kubernetes](https://docs.akeyless.io/docs/kubernetes-auth)
60 |
61 | - [Universal Identity](https://docs.akeyless.io/docs/universal-identity)
62 |
63 | - [Email](https://docs.akeyless.io/docs/email)
64 |
65 | ## Configuration
66 | To configure the Akeyless plugin in Jenkins:
67 |
68 | 1. From the **Jenkins Dashboard**, press **New Item**, choose **Freestyle Project**, give it a name and press **ok**.
69 |
70 | 2. Scroll down to **Environment** and check **Akeyless Plugin**
71 |
72 | 3. Set the **Akeyless URL** to your gateway URL, with the `/api/v2` endpoint.
73 |
74 | 4. Add a new **Access Mode**:
75 |
76 | - Under **Vault Credential**, press **Add** > **Jenkins**.
77 |
78 | - Choose the Authentication Method from the **Kind** drop-down:
79 |
80 | * **Username with password** - **Email** Authentication Method.
81 |
82 | * **Akeyless Access Key Credentials** - **API Key** Authentication Method.
83 |
84 | * **Akeyless Certificate Credentials** - **Certificate** Authentication Method.
85 |
86 | * **Akeyless Cloud Provider Credentials** - **AWS, Azure or GCP** Authentication Method.
87 |
88 | * **Akeyless Universal Identity Credentials** - **Universal Identity** Authentication Method.
89 |
90 | * **Akeyless t-Token Credentials** - **t-Token**.
91 |
92 |
93 | - Click **Add** to save the configuration.
94 |
95 | ## Retrieving Items
96 | The Akeyless plugin allows you to retrieve **Static**, **Dynamic**, and **Rotated** secrets and **PKI** and **SSH** certificates.
97 |
98 | ### Retrieving Secrets
99 | To retrieve a secret:
100 |
101 | 1. Click **Add Akeyless Secret**.
102 |
103 | 2. Configure the following parameters:
104 |
105 | - **Path**: Enter the full path of the secret.
106 |
107 | - **Environment Variable**: Define an environment variable to store the secret's value.
108 |
109 | - **Key Name** (for JSON-type secrets): Specify the key to fetch. To retrieve all keys, enter `data`.
110 |
111 | ### Issuing Certificates
112 | To Issue a certificate:
113 |
114 | 1. Click **Add Akeyless Issuer**.
115 |
116 | 2. Configure the following parameters:
117 |
118 | - **Path**: Enter the full path of the certificate issuer.
119 |
120 | - **Output Name**: Name the retrieved certificate.
121 |
122 | - **Certificate User Name**: (For SSH certificates) Enter the username to be signed.
123 |
124 | - **Public Key**: Provide the public key (if required).
125 |
126 | - **CSR in base64**: Provide the Certificate Signing Request (CSR) in base64 format.
127 |
128 | - **Environment Variable**: Define an environment variable to store the certificate.
129 |
130 | - **Key Name**: Specify the key to fetch. To retrieve all keys, enter `data`.
131 |
132 | ## Examples
133 | The following examples demonstrate how to authenticate and retrieve items using the Akeyless Plugin for Jenkins.
134 |
135 | ### Setting API Key Authentication
136 |
137 | * The following configuration utilizes an existing API key in Akeyless for Jenkins authentication.
138 |
139 | 
140 |
141 | ### Fetching a Static Secret
142 |
143 | * The following configuration will fetch a static secret to your pipeline.
144 | This example uses a JSON-Structured secret, where only the **UserName** key of the secret is saved to **User** Environment Variable.
145 | 
146 |
147 | ### Fetching a Rotated Secret with specific Keys:
148 | * The following example will only fetch the **username** of the rotated secret value, and will store it into **User** environment variable:
149 |
150 | 
151 |
152 | ### Issuing an SSH Certificate
153 | * The following above will generate an **SSH Certificate** that will be allowed for **ubuntu** user, using a public key:
154 |
155 | 
156 |
157 | ### Issuing a PKI Certificate
158 | * The following example will generate **PKI Certificate** using predefind **Certificate Signing Request**:
159 |
160 | 
161 |
162 | ---
163 |
164 | ## License
165 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
166 |
167 | ## Contributing
168 | We welcome contributions! Feel free to submit issues and pull requests.
169 |
170 | ## Support
171 | For any issues or questions, please visit our [Akeyless Documentation](https://docs.akeyless.io/) or open an issue on this repository.
172 |
173 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/AkeylessBindingStep.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless;
2 |
3 | import com.google.common.annotations.VisibleForTesting;
4 | import edu.umd.cs.findbugs.annotations.CheckForNull;
5 | import edu.umd.cs.findbugs.annotations.NonNull;
6 | import hudson.EnvVars;
7 | import hudson.Extension;
8 | import hudson.console.ConsoleLogFilter;
9 | import hudson.model.Run;
10 | import hudson.model.TaskListener;
11 | import hudson.util.Secret;
12 | import io.jenkins.plugins.akeyless.configuration.AkeylessConfiguration;
13 | import io.jenkins.plugins.akeyless.model.AkeylessPKIIssuer;
14 | import io.jenkins.plugins.akeyless.model.AkeylessSSHIssuer;
15 | import io.jenkins.plugins.akeyless.model.AkeylessSecret;
16 | import java.io.IOException;
17 | import java.util.*;
18 | import org.jenkinsci.plugins.workflow.steps.*;
19 | import org.kohsuke.stapler.DataBoundConstructor;
20 | import org.kohsuke.stapler.DataBoundSetter;
21 |
22 | /**
23 | * @author alexeydolgopyatov
24 | */
25 | public class AkeylessBindingStep extends Step {
26 |
27 | private AkeylessConfiguration configuration;
28 | private List akeylessSecrets;
29 | private List akeylessPKIIssuers;
30 | private List akeylessSSHIssuers;
31 |
32 | @DataBoundConstructor
33 | public AkeylessBindingStep(
34 | @CheckForNull List akeylessSecrets,
35 | @CheckForNull List akeylessPKIIssuers,
36 | @CheckForNull List akeylessSSHIssuers) {
37 | this.akeylessSecrets = akeylessSecrets;
38 | this.akeylessPKIIssuers = akeylessPKIIssuers;
39 | this.akeylessSSHIssuers = akeylessSSHIssuers;
40 | }
41 |
42 | public List getAkeylessSecrets() {
43 | return akeylessSecrets;
44 | }
45 |
46 | public List getAkeylessPKIIssuers() {
47 | return akeylessPKIIssuers;
48 | }
49 |
50 | public List getAkeylessSSHIssuers() {
51 | return akeylessSSHIssuers;
52 | }
53 |
54 | @DataBoundSetter
55 | public void setConfiguration(AkeylessConfiguration configuration) {
56 | this.configuration = configuration;
57 | }
58 |
59 | public AkeylessConfiguration getConfiguration() {
60 | return configuration;
61 | }
62 |
63 | @DataBoundSetter
64 | public void setAkeylessSecrets(List akeylessSecrets) {
65 | this.akeylessSecrets = akeylessSecrets;
66 | }
67 |
68 | @DataBoundSetter
69 | public void setAkeylessPKIIssuers(List akeylessPKIIssuers) {
70 | this.akeylessPKIIssuers = akeylessPKIIssuers;
71 | }
72 |
73 | @DataBoundSetter
74 | public void setAkeylessSSHIssuers(List akeylessSSHIssuers) {
75 | this.akeylessSSHIssuers = akeylessSSHIssuers;
76 | }
77 |
78 | @Override
79 | public StepExecution start(StepContext context) throws Exception {
80 | return new Execution(this, context);
81 | }
82 |
83 | protected static class Execution extends GeneralNonBlockingStepExecution {
84 |
85 | private static final long serialVersionUID = 1;
86 |
87 | private transient AkeylessBindingStep step;
88 | private transient AkeylessAccessor akeylessAccessor;
89 |
90 | public Execution(AkeylessBindingStep step, StepContext context) {
91 | super(context);
92 | this.step = step;
93 | }
94 |
95 | @VisibleForTesting
96 | public void setAkeylessAccessor(AkeylessAccessor akeylessAccessor) {
97 | this.akeylessAccessor = akeylessAccessor;
98 | }
99 |
100 | @Override
101 | public boolean start() throws Exception {
102 | run(this::doStart);
103 | return false;
104 | }
105 |
106 | private void doStart() throws Exception {
107 | Run, ?> run = getContext().get(Run.class);
108 | TaskListener listener = getContext().get(TaskListener.class);
109 | EnvVars envVars = getContext().get(EnvVars.class);
110 |
111 | Map overrides = AkeylessAccessor.retrieveSecrets(
112 | run,
113 | listener.getLogger(),
114 | envVars,
115 | akeylessAccessor,
116 | step.getConfiguration(),
117 | step.getAkeylessSecrets(),
118 | step.getAkeylessPKIIssuers(),
119 | step.getAkeylessSSHIssuers());
120 |
121 | List secretValues = new ArrayList<>();
122 | secretValues.addAll(overrides.values());
123 |
124 | getContext()
125 | .newBodyInvoker()
126 | .withContext(EnvironmentExpander.merge(
127 | getContext().get(EnvironmentExpander.class), new Overrider(overrides)))
128 | .withContext(BodyInvoker.mergeConsoleLogFilters(
129 | getContext().get(ConsoleLogFilter.class),
130 | new MaskSecretsLogsFilter(run.getCharset().name(), secretValues)))
131 | .withCallback(new Callback())
132 | .start();
133 | }
134 | }
135 |
136 | private static final class Overrider extends EnvironmentExpander {
137 |
138 | private static final long serialVersionUID = 1;
139 |
140 | private final Map overrides = new HashMap();
141 |
142 | Overrider(Map overrides) {
143 | for (Map.Entry override : overrides.entrySet()) {
144 | this.overrides.put(override.getKey(), Secret.fromString(override.getValue()));
145 | }
146 | }
147 |
148 | @Override
149 | public void expand(EnvVars env) throws IOException, InterruptedException {
150 | for (Map.Entry override : overrides.entrySet()) {
151 | env.override(override.getKey(), override.getValue().getPlainText());
152 | }
153 | }
154 |
155 | @Override
156 | public Set getSensitiveVariables() {
157 | return Collections.unmodifiableSet(overrides.keySet());
158 | }
159 | }
160 |
161 | private static class Callback extends BodyExecutionCallback.TailCall {
162 |
163 | @Override
164 | protected void finished(StepContext context) throws Exception {}
165 | }
166 |
167 | @Extension
168 | public static final class DescriptorImpl extends StepDescriptor {
169 |
170 | @Override
171 | public Set extends Class>> getRequiredContext() {
172 | return Collections.unmodifiableSet(
173 | new HashSet<>(Arrays.asList(TaskListener.class, Run.class, EnvVars.class)));
174 | }
175 |
176 | @Override
177 | public boolean takesImplicitBlockArgument() {
178 | return true;
179 | }
180 |
181 | @Override
182 | public String getFunctionName() {
183 | return "withAkeyless";
184 | }
185 |
186 | @NonNull
187 | @Override
188 | public String getDisplayName() {
189 | return "Akeyless Plugin";
190 | }
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/configuration/AkeylessConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless.configuration;
2 |
3 | import static hudson.Util.fixEmptyAndTrim;
4 |
5 | import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
6 | import com.cloudbees.plugins.credentials.domains.DomainRequirement;
7 | import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;
8 | import edu.umd.cs.findbugs.annotations.NonNull;
9 | import hudson.Extension;
10 | import hudson.model.AbstractDescribableImpl;
11 | import hudson.model.Descriptor;
12 | import hudson.model.Item;
13 | import hudson.security.ACL;
14 | import hudson.util.ListBoxModel;
15 | import io.jenkins.plugins.akeyless.credentials.AkeylessCredential;
16 | import java.io.Serializable;
17 | import java.util.List;
18 | import jenkins.model.Jenkins;
19 | import org.apache.commons.lang3.StringUtils;
20 | import org.kohsuke.accmod.Restricted;
21 | import org.kohsuke.accmod.restrictions.NoExternalUse;
22 | import org.kohsuke.stapler.AncestorInPath;
23 | import org.kohsuke.stapler.DataBoundConstructor;
24 | import org.kohsuke.stapler.DataBoundSetter;
25 | import org.kohsuke.stapler.QueryParameter;
26 | import org.kohsuke.stapler.verb.POST;
27 |
28 | /**
29 | * @author alexeydolgopyatov
30 | */
31 | public class AkeylessConfiguration extends AbstractDescribableImpl implements Serializable {
32 | private static final long serialVersionUID = 1L;
33 | private static final int DEFAULT_TIMEOUT = 60;
34 |
35 | // lgtm[jenkins/plaintext-storage]
36 | private String akeylessUrl;
37 | private String akeylessCredentialId;
38 | private AkeylessCredential akeylessCredential;
39 | private Boolean disableChildPoliciesOverride;
40 | private String policies;
41 |
42 | private Boolean skipSslVerification = DescriptorImpl.DEFAULT_SKIP_SSL_VERIFICATION;
43 | private Integer timeout = DEFAULT_TIMEOUT;
44 |
45 | @DataBoundConstructor
46 | public AkeylessConfiguration() {
47 | // no args constructor
48 | }
49 |
50 | public AkeylessConfiguration(AkeylessConfiguration toCopy) {
51 | this.akeylessUrl = toCopy.getAkeylessUrl();
52 | this.akeylessCredentialId = toCopy.getAkeylessCredentialId();
53 | this.akeylessCredential = toCopy.getAkeylessCredential();
54 | this.skipSslVerification = toCopy.skipSslVerification;
55 | this.policies = toCopy.policies;
56 | this.disableChildPoliciesOverride = toCopy.disableChildPoliciesOverride;
57 | this.timeout = toCopy.timeout;
58 | }
59 |
60 | public String getAkeylessUrl() {
61 | return akeylessUrl;
62 | }
63 |
64 | public String getAkeylessCredentialId() {
65 | return akeylessCredentialId;
66 | }
67 |
68 | @DataBoundSetter
69 | public void setAkeylessUrl(String akeylessUrl) {
70 | this.akeylessUrl = normalizeUrl(fixEmptyAndTrim(akeylessUrl));
71 | }
72 |
73 | @DataBoundSetter
74 | public void setAkeylessCredentialId(String akeylessCredentialId) {
75 | this.akeylessCredentialId = fixEmptyAndTrim(akeylessCredentialId);
76 | }
77 |
78 | public AkeylessCredential getAkeylessCredential() {
79 | return akeylessCredential;
80 | }
81 |
82 | @DataBoundSetter
83 | public void setAkeylessCredential(AkeylessCredential akeylessCredential) {
84 | this.akeylessCredential = akeylessCredential;
85 | }
86 |
87 | public AkeylessConfiguration mergeWithParent(AkeylessConfiguration parent) {
88 | if (parent == null) {
89 | return this;
90 | }
91 | AkeylessConfiguration result = new AkeylessConfiguration(this);
92 | if (StringUtils.isBlank(result.getAkeylessCredentialId())) {
93 | result.setAkeylessCredentialId(parent.getAkeylessCredentialId());
94 | }
95 | if (result.akeylessCredential == null) {
96 | result.setAkeylessCredential(parent.getAkeylessCredential());
97 | }
98 | if (StringUtils.isBlank(result.getAkeylessUrl())) {
99 | result.setAkeylessUrl(parent.getAkeylessUrl());
100 | }
101 |
102 | if (StringUtils.isBlank(result.getPolicies())
103 | || (parent.getDisableChildPoliciesOverride() != null && parent.getDisableChildPoliciesOverride())) {
104 | result.setPolicies(parent.getPolicies());
105 | }
106 |
107 | if (result.skipSslVerification == null) {
108 | result.setSkipSslVerification(parent.skipSslVerification);
109 | }
110 | if (result.timeout == null) {
111 | result.setTimeout(parent.getTimeout());
112 | }
113 | return result;
114 | }
115 |
116 | public Boolean getSkipSslVerification() {
117 | return skipSslVerification;
118 | }
119 |
120 | @DataBoundSetter
121 | public void setSkipSslVerification(Boolean skipSslVerification) {
122 | this.skipSslVerification = skipSslVerification;
123 | }
124 |
125 | public Boolean getDisableChildPoliciesOverride() {
126 | return disableChildPoliciesOverride;
127 | }
128 |
129 | @DataBoundSetter
130 | public void setDisableChildPoliciesOverride(Boolean disableChildPoliciesOverride) {
131 | this.disableChildPoliciesOverride = disableChildPoliciesOverride;
132 | }
133 |
134 | public String getPolicies() {
135 | return policies;
136 | }
137 |
138 | @DataBoundSetter
139 | public void setPolicies(String policies) {
140 | this.policies = fixEmptyAndTrim(policies);
141 | }
142 |
143 | public Integer getTimeout() {
144 | return timeout;
145 | }
146 |
147 | @DataBoundSetter
148 | public void setTimeout(Integer timeout) {
149 | this.timeout = timeout;
150 | }
151 |
152 | @Extension
153 | public static class DescriptorImpl extends Descriptor {
154 |
155 | public static final boolean DEFAULT_SKIP_SSL_VERIFICATION = false;
156 |
157 | @Override
158 | @NonNull
159 | public String getDisplayName() {
160 | return "Akeyless Auth Method Configuration";
161 | }
162 |
163 | @SuppressWarnings("unused") // used by stapler
164 | @POST
165 | @Restricted(NoExternalUse.class) // Optional, limits usage to internal only
166 | public ListBoxModel doFillAkeylessCredentialIdItems(
167 | @AncestorInPath Item item, @AncestorInPath Jenkins jenkins, @QueryParameter String uri) {
168 | ListBoxModel result = new StandardListBoxModel().includeEmptyValue();
169 | if (item != null) {
170 | if (!item.hasPermission(Item.CONFIGURE)) {
171 | return result;
172 | }
173 | } else if (jenkins != null) {
174 | if (!jenkins.hasPermission(Jenkins.ADMINISTER)) {
175 | return result;
176 | }
177 | } else {
178 | return result; // should not happen
179 | }
180 |
181 | List domainRequirements =
182 | URIRequirementBuilder.fromUri(uri).build();
183 |
184 | return new StandardListBoxModel()
185 | .includeEmptyValue()
186 | .includeAs(ACL.SYSTEM, item, AkeylessCredential.class, domainRequirements);
187 | }
188 | }
189 |
190 | private String normalizeUrl(String url) {
191 | if (url == null) {
192 | return null;
193 | }
194 |
195 | if (url.endsWith("/")) {
196 | return url.substring(0, url.length() - 1);
197 | }
198 |
199 | return url;
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/akeyless/AkeylessAccessor.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.akeyless;
2 |
3 | import com.cloudbees.plugins.credentials.CredentialsMatchers;
4 | import com.cloudbees.plugins.credentials.CredentialsProvider;
5 | import com.cloudbees.plugins.credentials.CredentialsUnavailableException;
6 | import com.cloudbees.plugins.credentials.matchers.IdMatcher;
7 | import com.google.gson.Gson;
8 | import com.google.gson.internal.LinkedTreeMap;
9 | import hudson.EnvVars;
10 | import hudson.ExtensionList;
11 | import hudson.model.Run;
12 | import hudson.security.ACL;
13 | import io.akeyless.client.ApiClient;
14 | import io.akeyless.client.ApiException;
15 | import io.akeyless.client.Configuration;
16 | import io.akeyless.client.api.V2Api;
17 | import io.akeyless.client.model.*;
18 | import io.jenkins.plugins.akeyless.cloudid.CloudIdProvider;
19 | import io.jenkins.plugins.akeyless.cloudid.CloudProviderFactory;
20 | import io.jenkins.plugins.akeyless.configuration.AkeylessConfigResolver;
21 | import io.jenkins.plugins.akeyless.configuration.AkeylessConfiguration;
22 | import io.jenkins.plugins.akeyless.credentials.AkeylessCredential;
23 | import io.jenkins.plugins.akeyless.credentials.CredentialsPayload;
24 | import io.jenkins.plugins.akeyless.model.*;
25 | import java.io.PrintStream;
26 | import java.io.Serializable;
27 | import java.nio.charset.StandardCharsets;
28 | import java.util.*;
29 | import javax.annotation.Nonnull;
30 | import jenkins.model.Jenkins;
31 | import net.sf.json.JSONObject;
32 | import org.apache.commons.lang.StringEscapeUtils;
33 | import org.apache.commons.lang.StringUtils;
34 |
35 | /**
36 | * @author alexeydolgopyatov
37 | */
38 | public class AkeylessAccessor implements Serializable {
39 | private static final long serialVersionUID = 1L;
40 | private transient V2Api api;
41 | private final AkeylessCredential credential;
42 | public static final String DATA_KEY = "data";
43 | private static final Gson gson = new Gson();
44 |
45 | public AkeylessAccessor(V2Api api, AkeylessCredential credential) {
46 | this.api = api;
47 | this.credential = credential;
48 | }
49 |
50 | public V2Api getApi() {
51 | return api;
52 | }
53 |
54 | public static Map retrieveSecrets(
55 | Run, ?> run,
56 | PrintStream logger,
57 | EnvVars envVars,
58 | AkeylessAccessor accessor,
59 | AkeylessConfiguration configuration,
60 | List akeylessSecrets,
61 | List akeylessPKIIssuers,
62 | List akeylessSSHIssuers) {
63 | Map secrets = new HashMap<>();
64 | AkeylessConfiguration config = pullAndMergeConfiguration(run, configuration);
65 | String url = config.getAkeylessUrl();
66 |
67 | if (StringUtils.isBlank(url)) {
68 | throw new AkeylessPluginException(
69 | "The Akeyless url was not configured - please specify the Akeyless url to use.");
70 | }
71 | AkeylessCredential credential = config.getAkeylessCredential();
72 | if (credential == null) {
73 | credential = retrieveAkeylessCredentials(run, config);
74 | }
75 | if (credential == null) {
76 | throw new AkeylessPluginException("Failed to retrieve Akeyless credential");
77 | }
78 |
79 | ApiClient client = Configuration.getDefaultApiClient();
80 | client.setBasePath(url);
81 | client.setVerifyingSsl(!config.getSkipSslVerification());
82 | client.setConnectTimeout(config.getTimeout());
83 | V2Api api = new V2Api(client);
84 | accessor = new AkeylessAccessor(api, credential);
85 |
86 | CredentialsPayload payload = credential.getCredentialsPayload();
87 | // authenticate
88 | String token = payload.getToken() == null ? null : payload.getToken().getPlainText();
89 | try {
90 | if (token == null || token.isEmpty()) {
91 | Auth auth = payload.getAuth();
92 | if (payload.isCloudIdNeeded()) {
93 | CloudIdProvider idProvider = CloudProviderFactory.getCloudIdProvider(auth.getAccessType());
94 | auth.setCloudId(idProvider.getCloudId());
95 | }
96 | token = api.auth(auth).getToken();
97 | }
98 | } catch (ApiException e) {
99 | throw new AkeylessPluginException("Authentication failed: " + e.getResponseBody(), e);
100 | } catch (Exception e) {
101 | throw new AkeylessPluginException("Authentication failed.", e);
102 | }
103 | fillObjectValues(logger, envVars, accessor, token, akeylessSecrets, secrets);
104 | fillObjectValues(logger, envVars, accessor, token, akeylessPKIIssuers, secrets);
105 | fillObjectValues(logger, envVars, accessor, token, akeylessSSHIssuers, secrets);
106 | return secrets;
107 | }
108 |
109 | public static void fillObjectValues(
110 | PrintStream logger,
111 | EnvVars envVars,
112 | AkeylessAccessor accessor,
113 | String token,
114 | List extends AkeylessSecretBase> akeylessSecrets,
115 | Map secrets) {
116 | if (secrets == null) {
117 | throw new AkeylessPluginException("Akeyless secrets holder should be initialized.");
118 | }
119 | if (akeylessSecrets == null || akeylessSecrets.isEmpty()) {
120 | return;
121 | }
122 | for (AkeylessSecretBase akeylessSecret : akeylessSecrets) {
123 | String path = envVars.expand(akeylessSecret.getPath());
124 | logger.printf("Retrieving secret: %s%n", path);
125 |
126 | Map values = accessor.getSecret(token, akeylessSecret);
127 | // static secret can be flat String in non JSON format
128 | Object tempVal = values.get(path);
129 | if (tempVal instanceof String) {
130 | tempVal = StringEscapeUtils.escapeJava(tempVal.toString());
131 | String newVal = "{\"data\": \"" + tempVal + "\"}";
132 | tempVal = gson.fromJson(JSONObject.fromObject(newVal).toString(), LinkedTreeMap.class);
133 | }
134 | Map innerValues = fillDataValues((LinkedTreeMap) tempVal, values);
135 | for (AkeylessSecretValue value : akeylessSecret.getSecretValues()) {
136 | String key = value.getSecretKey();
137 | Object secret = innerValues.get(key);
138 | if (secret == null && value.getIsRequired()) {
139 | throw new IllegalArgumentException("Required secret " + key + " at " + path
140 | + " is either null or empty. Please check the Secret name and type in Akeyless.");
141 | }
142 | if (secret != null) {
143 | secrets.put(value.getEnvVar(), secret.toString());
144 | }
145 | }
146 | logger.printf("Retrieving secret: %s -- SUCCESS.%n", path);
147 | }
148 | }
149 |
150 | private static Map fillDataValues(LinkedTreeMap innerValues, Map values) {
151 | if (innerValues == null) {
152 | innerValues = (LinkedTreeMap) values.get("value");
153 | if (innerValues == null) {
154 | innerValues = new LinkedTreeMap<>();
155 | innerValues.putAll(values);
156 | }
157 | }
158 | if (innerValues.get(DATA_KEY) == null) {
159 | innerValues.put(DATA_KEY, JSONObject.fromObject(innerValues).toString());
160 | }
161 | return innerValues;
162 | }
163 |
164 | public static AkeylessCredential retrieveAkeylessCredentials(Run build, AkeylessConfiguration config) {
165 | if (Jenkins.getInstanceOrNull() != null) {
166 | String id = config.getAkeylessCredentialId();
167 | if (StringUtils.isEmpty(id)) {
168 | throw new AkeylessPluginException(
169 | "The credential id was not configured - please specify the credentials to use.");
170 | }
171 | List credentials = CredentialsProvider.lookupCredentials(
172 | AkeylessCredential.class, build.getParent(), ACL.SYSTEM, Collections.emptyList());
173 | AkeylessCredential credential = CredentialsMatchers.firstOrNull(credentials, new IdMatcher(id));
174 |
175 | if (credential == null) {
176 | throw new CredentialsUnavailableException(id);
177 | }
178 |
179 | return credential;
180 | }
181 |
182 | return null;
183 | }
184 |
185 | public static AkeylessConfiguration pullAndMergeConfiguration(
186 | Run, ?> build, AkeylessConfiguration buildConfiguration) {
187 | AkeylessConfiguration configuration = buildConfiguration;
188 | for (AkeylessConfigResolver resolver : ExtensionList.lookup(AkeylessConfigResolver.class)) {
189 | if (configuration != null) {
190 | configuration = configuration.mergeWithParent(resolver.forJob(build.getParent()));
191 | } else {
192 | configuration = resolver.forJob(build.getParent());
193 | }
194 | }
195 | if (configuration == null) {
196 | throw new AkeylessPluginException("No configuration found - please configure the Akeyless Plugin.");
197 | }
198 | return configuration;
199 | }
200 |
201 | public Map getSecret(String token, AkeylessSecretBase akeylessSecret) {
202 | DescribeItem describeItem = new DescribeItem();
203 | describeItem.setToken(token);
204 | describeItem.setName(akeylessSecret.getPath());
205 | String type = null;
206 | try {
207 | Item item = getApi().describeItem(describeItem);
208 | type = item.getItemType();
209 | } catch (ApiException e) {
210 | throw new AkeylessPluginException("Failed to describe item: " + e.getResponseBody(), e);
211 | }
212 | try {
213 | switch (type) {
214 | case "STATIC_SECRET":
215 | GetSecretValue body = new GetSecretValue();
216 | body.setToken(token);
217 | body.json(true);
218 | List paths = Collections.singletonList(akeylessSecret.getPath());
219 | body.names(paths);
220 | body.setPrettyPrint(true);
221 | return getApi().getSecretValue(body);
222 | case "DYNAMIC_SECRET":
223 | GetDynamicSecretValue dbody = new GetDynamicSecretValue();
224 | dbody.setToken(token);
225 | dbody.json(true);
226 | dbody.setName(akeylessSecret.getPath());
227 | return getApi().getDynamicSecretValue(dbody);
228 | case "ROTATED_SECRET":
229 | GetRotatedSecretValue rbody = new GetRotatedSecretValue();
230 | rbody.setToken(token);
231 | rbody.json(true);
232 | rbody.setNames(akeylessSecret.getPath());
233 | return getApi().getRotatedSecretValue(rbody);
234 | case "CERTIFICATE":
235 | GetCertificateValue cbody = new GetCertificateValue();
236 | cbody.setToken(token);
237 | cbody.json(true);
238 | cbody.setName(akeylessSecret.getPath());
239 | GetCertificateValueOutput out = getApi().getCertificateValue(cbody);
240 | return gson.fromJson(JSONObject.fromObject(out).toString(), LinkedTreeMap.class);
241 | case "SSH_CERT_ISSUER":
242 | GetSSHCertificate sshCertificate = getSSHCertificateBody(token, (AkeylessSSHIssuer) akeylessSecret);
243 | GetSSHCertificateOutput sshout = getApi().getSSHCertificate(sshCertificate);
244 | Map forJson = new LinkedTreeMap<>();
245 | forJson.put("data", sshout.getData());
246 | return gson.fromJson(JSONObject.fromObject(forJson).toString(), LinkedTreeMap.class);
247 | case "PKI_CERT_ISSUER":
248 | GetPKICertificate pkiCertificate = getPKICertificateBody(token, (AkeylessPKIIssuer) akeylessSecret);
249 | GetPKICertificateOutput pki = getApi().getPKICertificate(pkiCertificate);
250 | forJson = new LinkedTreeMap<>();
251 | forJson.put("data", pki.getData());
252 | return gson.fromJson(JSONObject.fromObject(forJson).toString(), LinkedTreeMap.class);
253 | default:
254 | throw new AkeylessPluginException("Wrong or not supported item type: " + type);
255 | }
256 |
257 | } catch (ApiException e) {
258 | throw new AkeylessPluginException("Failed to retrieve secret: " + e.getResponseBody(), e);
259 | }
260 | }
261 |
262 | @Nonnull
263 | private GetSSHCertificate getSSHCertificateBody(String token, AkeylessSSHIssuer sshIssuer) {
264 | GetSSHCertificate sshCertificate = new GetSSHCertificate();
265 | sshCertificate.setToken(token);
266 | sshCertificate.setJson(true);
267 | sshCertificate.setCertIssuerName(sshIssuer.getPath());
268 | sshCertificate.setPublicKeyData(sshIssuer.getPublicKey());
269 | sshCertificate.setCertUsername(sshIssuer.getCertUserName());
270 | sshCertificate.setTtl(sshIssuer.getTtl());
271 | return sshCertificate;
272 | }
273 |
274 | @Nonnull
275 | private GetPKICertificate getPKICertificateBody(String token, AkeylessPKIIssuer pkiIssuer) {
276 | GetPKICertificate pkiCertificate = new GetPKICertificate();
277 | pkiCertificate.certIssuerName(pkiIssuer.getPath());
278 | pkiCertificate.setToken(token);
279 | pkiCertificate.setTtl(Long.toString(pkiIssuer.getTtl()));
280 | pkiCertificate.setJson(true);
281 | pkiCertificate.setCsrDataBase64(pkiIssuer.getCsrBase64());
282 | pkiCertificate.setKeyDataBase64(
283 | Base64.getEncoder().encodeToString(pkiIssuer.getPublicKey().getBytes(StandardCharsets.UTF_8)));
284 | return pkiCertificate;
285 | }
286 | }
287 |
--------------------------------------------------------------------------------