Skip to content

Commit 1a8a2f7

Browse files
committed
Improve manage of the connection pool when use multiple ClientCertificate credentials using a library that allow to add identity material for an existing SSLContext.
1 parent 914c46d commit 1a8a2f7

File tree

3 files changed

+68
-29
lines changed

3 files changed

+68
-29
lines changed

pom.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,17 @@
7979
<groupId>io.jenkins.plugins</groupId>
8080
<artifactId>apache-httpcomponents-client-5-api</artifactId>
8181
</dependency>
82+
<dependency>
83+
<groupId>io.github.hakky54</groupId>
84+
<artifactId>sslcontext-kickstart-for-apache5</artifactId>
85+
<version>9.1.1-SNAPSHOT</version>
86+
<exclusions>
87+
<exclusion>
88+
<groupId>org.slf4j</groupId>
89+
<artifactId>slf4j-api</artifactId>
90+
</exclusion>
91+
</exclusions>
92+
</dependency>
8293
<dependency>
8394
<groupId>io.jenkins.plugins</groupId>
8495
<artifactId>commons-lang3-api</artifactId>

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/credentials/BitbucketClientCertificateAuthenticator.java

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,25 +25,23 @@
2525
package com.cloudbees.jenkins.plugins.bitbucket.impl.credentials;
2626

2727
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator;
28-
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketException;
29-
import com.cloudbees.jenkins.plugins.bitbucket.impl.client.BitbucketTlsSocketStrategy;
3028
import com.cloudbees.plugins.credentials.common.StandardCertificateCredentials;
3129
import hudson.util.Secret;
32-
import java.security.KeyManagementException;
3330
import java.security.KeyStore;
34-
import java.security.KeyStoreException;
35-
import java.security.NoSuchAlgorithmException;
36-
import java.security.UnrecoverableKeyException;
37-
import javax.net.ssl.SSLContext;
38-
import org.apache.hc.client5.http.protocol.HttpClientContext;
31+
import java.util.logging.Logger;
32+
import nl.altindag.ssl.SSLFactory;
33+
import nl.altindag.ssl.util.KeyManagerUtils;
34+
import nl.altindag.ssl.util.KeyStoreUtils;
3935
import org.apache.hc.core5.http.HttpHost;
40-
import org.apache.hc.core5.ssl.SSLContextBuilder;
41-
import org.apache.hc.core5.ssl.SSLContexts;
36+
import org.kohsuke.accmod.Restricted;
37+
import org.kohsuke.accmod.restrictions.NoExternalUse;
4238

4339
/**
4440
* Authenticates against Bitbucket using a TLS client certificate
4541
*/
4642
public class BitbucketClientCertificateAuthenticator implements BitbucketAuthenticator {
43+
private static final Logger logger = Logger.getLogger(BitbucketClientCertificateAuthenticator.class.getName());
44+
4745
private final String credentialsId;
4846
private final KeyStore keyStore;
4947
private final Secret password;
@@ -55,23 +53,26 @@ public BitbucketClientCertificateAuthenticator(StandardCertificateCredentials cr
5553
}
5654

5755
/**
58-
* Sets the SSLContext for the builder to one that will connect with the selected certificate.
59-
* @param context The client builder context
56+
* Sets the SSLContext for the builder to one that will connect with the
57+
* selected certificate.
58+
*
59+
* @param sslFactory The client SSL context configured in the connection
60+
* pool
6061
* @param host the target host name
6162
*/
62-
@Override
63-
public void configureContext(HttpClientContext context, HttpHost host) {
64-
try {
65-
context.setAttribute(BitbucketTlsSocketStrategy.SOCKET_FACTORY_REGISTRY, buildSSLContext()); // override SSL registry for this context
66-
} catch (NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException | KeyManagementException e) {
67-
throw new BitbucketException("Failed to set up SSL context from provided client certificate", e);
68-
}
69-
}
70-
71-
private SSLContext buildSSLContext() throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException {
72-
SSLContextBuilder contextBuilder = SSLContexts.custom();
73-
contextBuilder.loadKeyMaterial(keyStore, Secret.toString(password).toCharArray());
74-
return contextBuilder.build();
63+
@Restricted(NoExternalUse.class)
64+
public synchronized /*required to avoid combine the same identity material twice */ void configureContext(SSLFactory sslFactory, HttpHost host) {
65+
sslFactory.getKeyManager().ifPresent(baseKeyManager -> {
66+
for (String alias : KeyStoreUtils.getAliases(keyStore)) {
67+
KeyManagerUtils.addIdentityMaterial(baseKeyManager, keyStore, Secret.toString(password).toCharArray());
68+
try {
69+
KeyManagerUtils.addIdentityRoute(baseKeyManager, alias, host.toString());
70+
} catch (Exception e) {
71+
e.printStackTrace();
72+
}
73+
logger.info(() -> "Add new identity material (keyStore) " + alias + " to the SSLContext.");
74+
}
75+
});
7576
}
7677

7778
@Override

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
import com.cloudbees.jenkins.plugins.bitbucket.client.repository.UserRoleInRepository;
4040
import com.cloudbees.jenkins.plugins.bitbucket.filesystem.BitbucketSCMFile;
4141
import com.cloudbees.jenkins.plugins.bitbucket.impl.client.AbstractBitbucketApi;
42-
import com.cloudbees.jenkins.plugins.bitbucket.impl.client.BitbucketTlsSocketStrategy;
4342
import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketAccessTokenAuthenticator;
4443
import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketClientCertificateAuthenticator;
4544
import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketUsernamePasswordAuthenticator;
@@ -75,6 +74,7 @@
7574
import java.io.InputStream;
7675
import java.lang.reflect.ParameterizedType;
7776
import java.nio.charset.StandardCharsets;
77+
import java.nio.file.Paths;
7878
import java.util.ArrayList;
7979
import java.util.Arrays;
8080
import java.util.Collections;
@@ -88,11 +88,15 @@
8888
import jenkins.scm.api.SCMFile;
8989
import jenkins.scm.api.SCMFile.Type;
9090
import jenkins.scm.impl.avatars.AvatarImage;
91+
import nl.altindag.ssl.SSLFactory;
92+
import nl.altindag.ssl.SSLFactory.Builder;
9193
import org.apache.commons.codec.digest.DigestUtils;
9294
import org.apache.commons.io.IOUtils;
9395
import org.apache.commons.lang3.StringUtils;
9496
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
97+
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
9598
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
99+
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
96100
import org.apache.hc.core5.http.HttpHost;
97101
import org.apache.hc.core5.http.HttpStatus;
98102
import org.apache.hc.core5.http.message.BasicNameValuePair;
@@ -140,10 +144,24 @@ public class BitbucketServerAPIClient extends AbstractBitbucketApi implements Bi
140144
private static final String API_MIRRORS_PATH = "/rest/mirroring/1.0/mirrorServers";
141145
private static final Integer DEFAULT_PAGE_LIMIT = 200;
142146

147+
private static Builder sslFactoryBuilder() {
148+
Builder builder = SSLFactory.builder()
149+
.withSystemTrustMaterial()
150+
.withDefaultTrustMaterial()
151+
.withTrustMaterial(Paths.get("D:\\Download\\JENKINS-75676\\jenkins\\trustStore.jks"), "JENKINS-75676".toCharArray())
152+
.withDummyIdentityMaterial()
153+
.withInflatableIdentityMaterial();
154+
if (System.getProperty("javax.net.ssl.trustStore") != null) {
155+
builder.withSystemPropertyDerivedTrustMaterial();
156+
}
157+
return builder;
158+
}
159+
160+
private static final SSLFactory sslFactory = sslFactoryBuilder().build();
143161
private static final HttpClientConnectionManager connectionManager = connectionManagerBuilder()
144-
.setMaxConnPerRoute(20)
145-
.setMaxConnTotal(40 /* should be 20 * number of server instances */)
146-
.setTlsSocketStrategy(new BitbucketTlsSocketStrategy())
162+
.setMaxConnPerRoute(1) // FIXME restore to 20
163+
.setMaxConnTotal(1 /* should be 20 * number of server instances */) // FIXME restore to 40
164+
.setTlsSocketStrategy(new DefaultClientTlsStrategy(sslFactory.getSslContext()))
147165
.build();
148166

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

183+
165184
public BitbucketServerAPIClient(@NonNull String baseURL, @NonNull String owner, @CheckForNull String repositoryName,
166185
@CheckForNull BitbucketAuthenticator authenticator, boolean userCentric) {
167186
this(baseURL, owner, repositoryName, authenticator, userCentric, BitbucketServerEndpoint.findWebhookImplementation(baseURL));
@@ -182,6 +201,14 @@ public BitbucketServerAPIClient(@NonNull String baseURL, @NonNull String owner,
182201
this.client = setupClientBuilder().build();
183202
}
184203

204+
@Override
205+
protected HttpClientBuilder setupClientBuilder() {
206+
if (getAuthenticator() instanceof BitbucketClientCertificateAuthenticator certificateAuthenticator) {
207+
certificateAuthenticator.configureContext(sslFactory, getHost());
208+
}
209+
return super.setupClientBuilder();
210+
}
211+
185212
@Override
186213
protected boolean isSupportedAuthenticator(@CheckForNull BitbucketAuthenticator authenticator) {
187214
return authenticator == null

0 commit comments

Comments
 (0)