├── .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 | ![image](docs/images/apikey.png) 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 | ![image](docs/images/static.png) 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 | ![Fetching a Rotated Secret](docs/images/rotated.png) 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 | ![Issuing an SSH Certificate](docs/images/sshcert.png) 156 | 157 | ### Issuing a PKI Certificate 158 | * The following example will generate **PKI Certificate** using predefind **Certificate Signing Request**: 159 | 160 | ![Issuing a PKI Certificate](docs/images/pkicert.png) 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> 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 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 | --------------------------------------------------------------------------------