Skip to content

Test case for the sslsocket-kickstart library that allow to hotswap client certificates for an existing SSLContext #1078

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@
<groupId>io.jenkins.plugins</groupId>
<artifactId>apache-httpcomponents-client-5-api</artifactId>
</dependency>
<dependency>
<groupId>io.github.hakky54</groupId>
<artifactId>sslcontext-kickstart-for-apache5</artifactId>
<version>9.1.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.jenkins.plugins</groupId>
<artifactId>commons-lang3-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,23 @@
package com.cloudbees.jenkins.plugins.bitbucket.impl.credentials;

import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator;
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketException;
import com.cloudbees.jenkins.plugins.bitbucket.impl.client.BitbucketTlsSocketStrategy;
import com.cloudbees.plugins.credentials.common.StandardCertificateCredentials;
import hudson.util.Secret;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import javax.net.ssl.SSLContext;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import java.util.logging.Logger;
import nl.altindag.ssl.SSLFactory;
import nl.altindag.ssl.util.KeyManagerUtils;
import nl.altindag.ssl.util.KeyStoreUtils;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.ssl.SSLContextBuilder;
import org.apache.hc.core5.ssl.SSLContexts;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;

/**
* Authenticates against Bitbucket using a TLS client certificate
*/
public class BitbucketClientCertificateAuthenticator implements BitbucketAuthenticator {
private static final Logger logger = Logger.getLogger(BitbucketClientCertificateAuthenticator.class.getName());

private final String credentialsId;
private final KeyStore keyStore;
private final Secret password;
Expand All @@ -55,23 +53,22 @@ public BitbucketClientCertificateAuthenticator(StandardCertificateCredentials cr
}

/**
* Sets the SSLContext for the builder to one that will connect with the selected certificate.
* @param context The client builder context
* Sets the SSLContext for the builder to one that will connect with the
* selected certificate.
*
* @param sslFactory The client SSL context configured in the connection
* pool
* @param host the target host name
*/
@Override
public void configureContext(HttpClientContext context, HttpHost host) {
try {
context.setAttribute(BitbucketTlsSocketStrategy.SOCKET_FACTORY_REGISTRY, buildSSLContext()); // override SSL registry for this context
} catch (NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException | KeyManagementException e) {
throw new BitbucketException("Failed to set up SSL context from provided client certificate", e);
}
}

private SSLContext buildSSLContext() throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException {
SSLContextBuilder contextBuilder = SSLContexts.custom();
contextBuilder.loadKeyMaterial(keyStore, Secret.toString(password).toCharArray());
return contextBuilder.build();
@Restricted(NoExternalUse.class)
public void configureContext(SSLFactory sslFactory, HttpHost host) {
sslFactory.getKeyManager().ifPresent(baseKeyManager -> {
for (String alias : KeyStoreUtils.getAliases(keyStore)) {
KeyManagerUtils.addIdentityMaterial(baseKeyManager, keyStore, Secret.toString(password).toCharArray());
KeyManagerUtils.addIdentityRoute(baseKeyManager, alias, host.toString());
logger.info(() -> "Add new identity material (keyStore) " + alias + " to the SSLContext.");
}
});
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
import com.cloudbees.jenkins.plugins.bitbucket.client.repository.UserRoleInRepository;
import com.cloudbees.jenkins.plugins.bitbucket.filesystem.BitbucketSCMFile;
import com.cloudbees.jenkins.plugins.bitbucket.impl.client.AbstractBitbucketApi;
import com.cloudbees.jenkins.plugins.bitbucket.impl.client.BitbucketTlsSocketStrategy;
import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketAccessTokenAuthenticator;
import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketClientCertificateAuthenticator;
import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketUsernamePasswordAuthenticator;
Expand Down Expand Up @@ -75,6 +74,7 @@
import java.io.InputStream;
import java.lang.reflect.ParameterizedType;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand All @@ -88,11 +88,15 @@
import jenkins.scm.api.SCMFile;
import jenkins.scm.api.SCMFile.Type;
import jenkins.scm.impl.avatars.AvatarImage;
import nl.altindag.ssl.SSLFactory;
import nl.altindag.ssl.SSLFactory.Builder;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.message.BasicNameValuePair;
Expand Down Expand Up @@ -140,10 +144,24 @@ public class BitbucketServerAPIClient extends AbstractBitbucketApi implements Bi
private static final String API_MIRRORS_PATH = "/rest/mirroring/1.0/mirrorServers";
private static final Integer DEFAULT_PAGE_LIMIT = 200;

private static Builder sslFactoryBuilder() {
Builder builder = SSLFactory.builder()
.withSystemTrustMaterial()
.withDefaultTrustMaterial()
.withTrustMaterial(Paths.get("D:\\Download\\JENKINS-75676\\jenkins\\trustStore.jks"), "JENKINS-75676".toCharArray())
.withDummyIdentityMaterial()
.withInflatableIdentityMaterial();
if (System.getProperty("javax.net.ssl.trustStore") != null) {
builder.withSystemPropertyDerivedTrustMaterial();
}
return builder;
}

private static final SSLFactory sslFactory = sslFactoryBuilder().build();
private static final HttpClientConnectionManager connectionManager = connectionManagerBuilder()
.setMaxConnPerRoute(20)
.setMaxConnTotal(40 /* should be 20 * number of server instances */)
.setTlsSocketStrategy(new BitbucketTlsSocketStrategy())
.setMaxConnPerRoute(1) // FIXME restore to 20
.setMaxConnTotal(1 /* should be 20 * number of server instances */) // FIXME restore to 40
.setTlsSocketStrategy(new DefaultClientTlsStrategy(sslFactory.getSslContext()))
.build();

/**
Expand All @@ -162,6 +180,7 @@ public class BitbucketServerAPIClient extends AbstractBitbucketApi implements Bi
private final BitbucketServerWebhookImplementation webhookImplementation;
private final CloseableHttpClient client;


public BitbucketServerAPIClient(@NonNull String baseURL, @NonNull String owner, @CheckForNull String repositoryName,
@CheckForNull BitbucketAuthenticator authenticator, boolean userCentric) {
this(baseURL, owner, repositoryName, authenticator, userCentric, BitbucketServerEndpoint.findWebhookImplementation(baseURL));
Expand All @@ -182,6 +201,14 @@ public BitbucketServerAPIClient(@NonNull String baseURL, @NonNull String owner,
this.client = setupClientBuilder().build();
}

@Override
protected HttpClientBuilder setupClientBuilder() {
if (getAuthenticator() instanceof BitbucketClientCertificateAuthenticator certificateAuthenticator) {
certificateAuthenticator.configureContext(sslFactory, getHost());
}
return super.setupClientBuilder();
}

@Override
protected boolean isSupportedAuthenticator(@CheckForNull BitbucketAuthenticator authenticator) {
return authenticator == null
Expand Down
Loading