TW-55894 Add universal solution for custom ssl certificates on an agent side
authorMikhail Khorkov <mikhail.khorkov@jetbrains.com>
Mon, 9 Jul 2018 15:37:54 +0000 (22:37 +0700)
committerMikhail Khorkov <mikhail.khorkov@jetbrains.com>
Mon, 20 Aug 2018 12:04:07 +0000 (19:04 +0700)
21 files changed:
git-agent/src/jetbrains/buildServer/buildTriggers/vcs/git/agent/UpdaterImpl.java
git-agent/src/jetbrains/buildServer/buildTriggers/vcs/git/agent/UpdaterWithMirror.java
git-agent/src/jetbrains/buildServer/buildTriggers/vcs/git/agent/command/SetConfigCommand.java
git-agent/src/jetbrains/buildServer/buildTriggers/vcs/git/agent/command/impl/SetConfigCommandImpl.java
git-agent/src/jetbrains/buildServer/buildTriggers/vcs/git/agent/ssl/SSLInvestigator.java [new file with mode: 0644]
git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/AgentSideSparseCheckoutTest.java
git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/AgentSslCheckoutTest.java [new file with mode: 0644]
git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/AgentSupportBuilder.java
git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/AgentVcsSupportTest.java
git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/AutoCheckoutTest.java
git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/BaseRemoteRepositoryTest.java
git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/DiffWithUpperLimitRevisionTest.java
git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/GitCommandProxy.java
git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/GitCommandProxyCallback.java
git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/HttpAuthTest.java
git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/HttpUrlWithUsernameTest.java
git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/SSLInvestigatorTest.java [new file with mode: 0644]
git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/SSLTestUtil.java [new file with mode: 0644]
git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/builders/AgentRunningBuildBuilder.java
git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/builders/BuildAgentConfigurationBuilder.java
git-tests/src/native-git-testng.xml

index 0c243056f1b678a552dacd28b8ae2a09480e3233..63c4728019d62b05e51e7efc19820db92c2d7c69 100644 (file)
@@ -22,7 +22,6 @@ import jetbrains.buildServer.agent.AgentRunningBuild;
 import jetbrains.buildServer.agent.BuildDirectoryCleanerCallback;
 import jetbrains.buildServer.agent.BuildProgressLogger;
 import jetbrains.buildServer.agent.SmartDirectoryCleaner;
-import jetbrains.buildServer.agent.ssl.TrustedCertificatesDirectory;
 import jetbrains.buildServer.buildTriggers.vcs.git.*;
 import jetbrains.buildServer.buildTriggers.vcs.git.agent.command.*;
 import jetbrains.buildServer.buildTriggers.vcs.git.agent.command.impl.CommandUtil;
@@ -30,6 +29,7 @@ import jetbrains.buildServer.buildTriggers.vcs.git.agent.command.impl.RefImpl;
 import jetbrains.buildServer.buildTriggers.vcs.git.agent.errors.GitExecTimeout;
 import jetbrains.buildServer.buildTriggers.vcs.git.agent.errors.GitIndexCorruptedException;
 import jetbrains.buildServer.buildTriggers.vcs.git.agent.errors.GitOutdatedIndexException;
+import jetbrains.buildServer.buildTriggers.vcs.git.agent.ssl.SSLInvestigator;
 import jetbrains.buildServer.log.Loggers;
 import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.util.StringUtil;
@@ -81,6 +81,7 @@ public class UpdaterImpl implements Updater {
   protected final AgentGitVcsRoot myRoot;
   protected final String myFullBranchName;
   protected final AgentRunningBuild myBuild;
+  protected final SSLInvestigator mySSLInvestigator;
   private final CheckoutRules myRules;
   private final CheckoutMode myCheckoutMode;
   protected final MirrorManager myMirrorManager;
@@ -111,6 +112,8 @@ public class UpdaterImpl implements Updater {
     myRules = rules;
     myCheckoutMode = checkoutMode;
     myMirrorManager = mirrorManager;
+    mySSLInvestigator = new SSLInvestigator(myRoot.getRepositoryFetchURL(), myBuild.getAgentTempDirectory().getPath(),
+                                            myBuild.getAgentConfiguration().getAgentHomeDirectory().getPath());
   }
 
 
@@ -187,6 +190,7 @@ public class UpdaterImpl implements Updater {
         initDirectory(true);
       }
     }
+    mySSLInvestigator.setCertificateOptions(myGitFactory.create(myTargetDirectory));
     removeOrphanedIdxFiles(new File(myTargetDirectory, ".git"));
   }
 
@@ -368,7 +372,7 @@ public class UpdaterImpl implements Updater {
 
 
   private void addSubmoduleUsernames(@NotNull File repositoryDir, @NotNull Config gitModules)
-    throws IOException, ConfigInvalidException, VcsException {
+    throws IOException, VcsException {
     if (!myPluginConfig.isUseMainRepoUserForSubmodules())
       return;
 
@@ -455,9 +459,8 @@ public class UpdaterImpl implements Updater {
       if (scheme == null || "git".equals(scheme)) //no auth for anonymous protocol and for local repositories
         return false;
       String user = uri.getUser();
-      if (user != null) //respect a user specified in config
-        return false;
-      return true;
+      //respect a user specified in config
+      return user == null;
     } catch (URISyntaxException e) {
       return false;
     }
@@ -707,14 +710,7 @@ public class UpdaterImpl implements Updater {
   }
 
   @NotNull
-  private FetchCommand getFetch(@NotNull File repositoryDir, @NotNull String refspec, boolean shallowClone, boolean silent, int timeout)
-    throws VcsException {
-    /* set config property for path where custom ssl certificates are stored */
-    if ("https".equals(myRoot.getRepositoryFetchURL().getScheme())) {
-      final String homeDirectory = myBuild.getAgentConfiguration().getAgentHomeDirectory().getPath();
-      final String certDirectory = TrustedCertificatesDirectory.getAllCertificatesDirectoryFromHome(homeDirectory);
-      myGitFactory.create(repositoryDir).setConfig().setPropertyName("http.sslCAPath").setValue(certDirectory).call();
-    }
+  private FetchCommand getFetch(@NotNull File repositoryDir, @NotNull String refspec, boolean shallowClone, boolean silent, int timeout) {
     FetchCommand result = myGitFactory.create(repositoryDir).fetch()
       .setAuthSettings(myRoot.getAuthSettings())
       .setUseNativeSsh(myPluginConfig.isUseNativeSSH())
@@ -824,7 +820,8 @@ public class UpdaterImpl implements Updater {
 
     myTargetDirectory.mkdirs();
     myLogger.message("The .git directory is missing in '" + myTargetDirectory + "'. Running 'git init'...");
-    myGitFactory.create(myTargetDirectory).init().call();
+    final GitFacade gitFacade = myGitFactory.create(myTargetDirectory);
+    gitFacade.init().call();
     validateUrls();
     configureRemoteUrl(new File(myTargetDirectory, ".git"));
 
@@ -832,7 +829,7 @@ public class UpdaterImpl implements Updater {
     URIish url = myRoot.getRepositoryPushURL();
     String pushUrl = url == null ? null : url.toString();
     if (pushUrl != null && !pushUrl.equals(fetchUrl.toString())) {
-      myGitFactory.create(myTargetDirectory).setConfig().setPropertyName("remote.origin.pushurl").setValue(pushUrl).call();
+      gitFacade.setConfig().setPropertyName("remote.origin.pushurl").setValue(pushUrl).call();
     }
     setupNewRepository();
     configureSparseCheckout();
index 589b306e1840c6a92b6501a4e7627cf75f6099cb..5263040105fbce1c98322f5d1d0365c2d567c89f 100644 (file)
@@ -16,6 +16,7 @@
 
 package jetbrains.buildServer.buildTriggers.vcs.git.agent;
 
+import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.util.io.FileUtil;
 import jetbrains.buildServer.agent.AgentRunningBuild;
 import jetbrains.buildServer.agent.SmartDirectoryCleaner;
@@ -26,7 +27,6 @@ import jetbrains.buildServer.util.StringUtil;
 import jetbrains.buildServer.vcs.CheckoutRules;
 import jetbrains.buildServer.vcs.VcsException;
 import jetbrains.buildServer.vcs.VcsRoot;
-import com.intellij.openapi.diagnostic.Logger;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.RepositoryBuilder;
@@ -88,16 +88,18 @@ public class UpdaterWithMirror extends UpdaterImpl {
     } else {
       FileUtil.delete(bareRepositoryDir);
     }
+    final GitFacade git = myGitFactory.create(bareRepositoryDir);
     boolean newMirror = false;
     if (!bareRepositoryDir.exists()) {
       LOG.info("Init " + mirrorDescription);
       bareRepositoryDir.mkdirs();
-      GitFacade git = myGitFactory.create(bareRepositoryDir);
       git.init().setBare(true).call();
       configureRemoteUrl(bareRepositoryDir);
+      mySSLInvestigator.setCertificateOptions(git);
       newMirror = true;
     } else {
       configureRemoteUrl(bareRepositoryDir);
+      mySSLInvestigator.setCertificateOptions(git);
       boolean outdatedTagsFound = removeOutdatedRefs(bareRepositoryDir);
       if (!outdatedTagsFound) {
         LOG.debug("Try to find revision " + myRevision + " in " + mirrorDescription);
@@ -115,7 +117,6 @@ public class UpdaterWithMirror extends UpdaterImpl {
     if (!fetchRequired && fetchHeadsMode != FetchHeadsMode.ALWAYS)
       return;
     if (!newMirror && optimizeMirrorBeforeFetch()) {
-      GitFacade git = myGitFactory.create(bareRepositoryDir);
       git.gc().call();
       git.repack().call();
     }
@@ -183,6 +184,7 @@ public class UpdaterWithMirror extends UpdaterImpl {
         GitFacade git = myGitFactory.create(repositoryDir);
         git.init().setBare(true).call();
         configureRemoteUrl(repositoryDir);
+        mySSLInvestigator.setCertificateOptions(git);
         fetch(repositoryDir, refspec, false);
       } else {
         LOG.info("Failed to delete repository " + repositoryDir + " after failed checkout, clone repository in another directory");
index 92d20f0b79fae24e18603c6e612fe6144a04fbc8..b266a4dc9981fba49e7aca776f16e09a9c093fe3 100644 (file)
@@ -27,6 +27,9 @@ public interface SetConfigCommand extends BaseCommand {
   @NotNull
   SetConfigCommand setValue(@NotNull String value);
 
+  @NotNull
+  SetConfigCommand unSet();
+
   void call() throws VcsException;
 
 }
index c2cb4e6a0c8483548d7665ce75a09ca41446a886..5f31d64c5efbd57e488e47fe4254e3d519b0bacb 100644 (file)
@@ -25,6 +25,7 @@ import org.jetbrains.annotations.NotNull;
 public class SetConfigCommandImpl extends BaseCommandImpl implements SetConfigCommand {
   private String myPropertyName;
   private String myValue;
+  private boolean myUnSet = false;
 
   public SetConfigCommandImpl(@NotNull GitCommandLine cmd) {
     super(cmd);
@@ -42,9 +43,19 @@ public class SetConfigCommandImpl extends BaseCommandImpl implements SetConfigCo
     return this;
   }
 
+  @NotNull
+  public SetConfigCommand unSet() {
+    this.myUnSet = true;
+    return this;
+  }
+
   public void call() throws VcsException {
     GitCommandLine cmd = getCmd();
-    cmd.addParameters("config", myPropertyName, myValue);
+    if (myUnSet) {
+      cmd.addParameters("config", "--unset", myPropertyName);
+    } else {
+      cmd.addParameters("config", myPropertyName, myValue);
+    }
     ExecResult r = CommandUtil.runCommand(cmd);
     CommandUtil.failIfNotEmptyStdErr(cmd, r);
   }
diff --git a/git-agent/src/jetbrains/buildServer/buildTriggers/vcs/git/agent/ssl/SSLInvestigator.java b/git-agent/src/jetbrains/buildServer/buildTriggers/vcs/git/agent/ssl/SSLInvestigator.java
new file mode 100644 (file)
index 0000000..c8251ca
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.git.agent.ssl;
+
+import jetbrains.buildServer.agent.ssl.TrustedCertificatesDirectory;
+import jetbrains.buildServer.buildTriggers.vcs.git.agent.GitFacade;
+import jetbrains.buildServer.serverSide.TeamCityProperties;
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.util.StringUtil;
+import jetbrains.buildServer.util.ssl.TrustStoreIO;
+import org.apache.commons.codec.CharEncoding;
+import org.apache.log4j.Logger;
+import org.eclipse.jgit.transport.URIish;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.net.ssl.*;
+import java.io.File;
+import java.io.IOException;
+import java.security.*;
+
+/**
+ * Component for investigate either we need use custom ssl certificates for git fetch or not.
+ *
+ * @author Mikhail Khorkov
+ * @since 2018.1.2
+ */
+public class SSLInvestigator {
+
+  private final static Logger LOG = Logger.getLogger(SSLInvestigator.class);
+
+  private final static String CERT_FILE = "git_custom_certificates.crt";
+
+  private final URIish myFetchURL;
+  private final String myTempDirectory;
+  private final String myHomeDirectory;
+  private final SSLChecker mySSLChecker;
+  private final SSLContextRetriever mySSLContextRetriever;
+
+  private volatile Boolean myNeedCustomCertificate = null;
+  private volatile String myCAInfoPath = null;
+
+  public SSLInvestigator(@NotNull final URIish fetchURL, @NotNull final String tempDirectory, @NotNull final String homeDirectory) {
+    this(fetchURL, tempDirectory, homeDirectory, new SSLCheckerImpl(), new SSLContextRetrieverImpl());
+  }
+
+  public SSLInvestigator(@NotNull final URIish fetchURL, @NotNull final String tempDirectory, @NotNull final String homeDirectory,
+                         @NotNull final SSLChecker sslChecker, @NotNull final SSLContextRetriever sslContextRetriever) {
+    myFetchURL = fetchURL;
+    myTempDirectory = tempDirectory;
+    myHomeDirectory = homeDirectory;
+    mySSLChecker = sslChecker;
+    mySSLContextRetriever = sslContextRetriever;
+
+    if (!"https".equals(myFetchURL.getScheme())) {
+      myNeedCustomCertificate = false;
+    }
+    if (!TeamCityProperties.getBooleanOrTrue("teamcity.ssl.useCustomTrustStore.git")) {
+      myNeedCustomCertificate = false;
+    }
+  }
+
+  public void setCertificateOptions(@NotNull final GitFacade gitFacade) {
+    if (!isNeedCustomCertificates()) {
+      deleteSslOption(gitFacade);
+      return;
+    }
+
+    final String caInfoPath = caInfoPath();
+    if (caInfoPath != null) {
+      setSslOption(gitFacade, caInfoPath);
+    }
+  }
+
+  @Nullable
+  private String caInfoPath() {
+    String caInfoPath = myCAInfoPath;
+    if (caInfoPath == null) {
+      synchronized (this) {
+        caInfoPath = myCAInfoPath;
+        if (caInfoPath != null) {
+          return caInfoPath;
+        }
+
+        caInfoPath = generateCertificateFile();
+        myCAInfoPath = caInfoPath;
+      }
+    }
+    return caInfoPath;
+  }
+
+  @Nullable
+  private String generateCertificateFile() {
+    try {
+      final String certDirectory = TrustedCertificatesDirectory.getAllCertificatesDirectoryFromHome(myHomeDirectory);
+      final String pemContent = TrustStoreIO.pemContentFromDirectory(certDirectory);
+      if (!pemContent.isEmpty()) {
+        final File file = new File(myTempDirectory, CERT_FILE);
+        FileUtil.writeFile(file, pemContent, CharEncoding.UTF_8);
+        return file.getPath();
+      }
+    } catch (IOException e) {
+      LOG.error("Can not write file with certificates", e);
+    }
+    return null;
+  }
+
+  private boolean isNeedCustomCertificates() {
+    Boolean need = myNeedCustomCertificate;
+    if (need == null) {
+      synchronized (this) {
+        need = myNeedCustomCertificate;
+        if (need != null) {
+          return need;
+        }
+
+        need = doesCanConnectWithCustomCertificate();
+        myNeedCustomCertificate = need;
+      }
+    }
+    return need;
+  }
+
+  private boolean doesCanConnectWithCustomCertificate() {
+    try {
+      final SSLContext sslContext = mySSLContextRetriever.retrieve(myHomeDirectory);
+      if (sslContext == null) {
+        /* there are no custom certificate */
+        return false;
+      }
+
+      final int port = myFetchURL.getPort() > 0 ? myFetchURL.getPort() : 443;
+      return mySSLChecker.canConnect(sslContext, myFetchURL.getHost(), port);
+
+    } catch (Exception e) {
+      LOG.error("Unexpected error while try to connect to git server " + myFetchURL.toString(), e);
+      /* unexpected error. do not use custom certificate then */
+      return false;
+    }
+  }
+
+  private void deleteSslOption(@NotNull final GitFacade gitFacade) {
+    try {
+      final String previous = gitFacade.getConfig().setPropertyName("http.sslCAInfo").call();
+      if (!StringUtil.isEmptyOrSpaces(previous)) {
+        /* do not need custom certificate then remove corresponding options if exists */
+        gitFacade.setConfig().setPropertyName("http.sslCAInfo").unSet().call();
+      }
+    } catch (Exception e) {
+      /* option was not exist, ignore exception then */
+    }
+  }
+
+  private void setSslOption(@NotNull final GitFacade gitFacade, @NotNull final String path) {
+    try {
+      gitFacade.setConfig().setPropertyName("http.sslCAInfo").setValue(path).call();
+    } catch (Exception e) {
+      LOG.error("Error while setting sslCAInfo git option");
+    }
+  }
+
+  public interface SSLContextRetriever {
+    @Nullable
+    SSLContext retrieve(@NotNull String homeDirectory) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException;
+  }
+
+  public static class SSLContextRetrieverImpl implements SSLContextRetriever {
+
+    @Override
+    @Nullable
+    public SSLContext retrieve(@NotNull final String homeDirectory)
+      throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
+      final X509TrustManager manager = trustManager(homeDirectory);
+      if (manager == null) {
+        return null;
+      }
+
+      final SSLContext context = SSLContext.getInstance("TLS");
+      context.init(null, new TrustManager[]{manager}, new SecureRandom());
+
+      return context;
+    }
+
+    @Nullable
+    private X509TrustManager trustManager(@NotNull final String homeDirectory) throws NoSuchAlgorithmException, KeyStoreException {
+      final KeyStore trustStore = trustStore(homeDirectory);
+      if (trustStore == null) {
+        return null;
+      }
+
+      final TrustManagerFactory manager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+      manager.init(trustStore);
+
+      return (X509TrustManager)manager.getTrustManagers()[0];
+    }
+
+    @Nullable
+    private KeyStore trustStore(@NotNull final String homeDirectory) {
+      final String certDirectory = TrustedCertificatesDirectory.getAllCertificatesDirectoryFromHome(homeDirectory);
+      return TrustStoreIO.readTrustStoreFromDirectory(certDirectory);
+    }
+  }
+
+  public interface SSLChecker {
+    boolean canConnect(@NotNull final SSLContext sslContext, @NotNull final String host, int port) throws Exception;
+  }
+
+  public static class SSLCheckerImpl implements SSLChecker {
+    @Override
+    public boolean canConnect(@NotNull final SSLContext sslContext, @NotNull final String host, final int port) throws Exception {
+      final SSLSocket socket = (SSLSocket)sslContext.getSocketFactory().createSocket(host, port);
+      socket.setSoTimeout(TeamCityProperties.getInteger("teamcity.ssl.checkTimeout.git", 10 * 1000));
+      try {
+        socket.startHandshake();
+        socket.close();
+      } catch (Exception e) {
+        /* can't connect with custom certificate */
+        return false;
+      }
+      return true;
+    }
+  }
+}
index c3bf0e82f52b81f2e86854b374e3114307b1cf72..2d09830960fda11bfdcda57b712a56c71a50da4b 100644 (file)
@@ -69,7 +69,8 @@ public class AgentSideSparseCheckoutTest extends BaseRemoteRepositoryTest {
 
   public void update_files_after_checkout_rules_change() throws Exception {
     String version = "465ad9f630e451b9f2b782ffb09804c6a98c4bb9";
-    AgentRunningBuild build = runningBuild().sharedConfigParams(PluginConfigImpl.USE_SPARSE_CHECKOUT, "true").build();
+    AgentRunningBuild build = runningBuild().sharedConfigParams(PluginConfigImpl.USE_SPARSE_CHECKOUT, "true")
+      .withAgentConfiguration(myAgentConfiguration).build();
     CheckoutRules rules = new CheckoutRules("+:dir");
     myVcsSupport.updateSources(myRoot, rules, version, myCheckoutDir, build, false);
     then(myCheckoutDir.list()).contains("dir").doesNotContain("readme.txt");
@@ -82,7 +83,8 @@ public class AgentSideSparseCheckoutTest extends BaseRemoteRepositoryTest {
 
   public void update_files_after_switching_to_default_rules() throws Exception {
     String version = "465ad9f630e451b9f2b782ffb09804c6a98c4bb9";
-    AgentRunningBuild build = runningBuild().sharedConfigParams(PluginConfigImpl.USE_SPARSE_CHECKOUT, "true").build();
+    AgentRunningBuild build = runningBuild().sharedConfigParams(PluginConfigImpl.USE_SPARSE_CHECKOUT, "true")
+      .withAgentConfiguration(myAgentConfiguration).build();
     CheckoutRules rules = new CheckoutRules("+:dir");
     myVcsSupport.updateSources(myRoot, rules, version, myCheckoutDir, build, false);
     then(myCheckoutDir.list()).contains("dir").doesNotContain("readme.txt");
@@ -169,7 +171,8 @@ public class AgentSideSparseCheckoutTest extends BaseRemoteRepositoryTest {
   private void checkRules(@NotNull String version, @NotNull CheckoutRules rules, String... files) throws VcsException {
     FileUtil.delete(myCheckoutDir);
     myCheckoutDir.mkdirs();
-    AgentRunningBuild build = runningBuild().sharedConfigParams(PluginConfigImpl.USE_SPARSE_CHECKOUT, "true").build();
+    AgentRunningBuild build = runningBuild().sharedConfigParams(PluginConfigImpl.USE_SPARSE_CHECKOUT, "true")
+      .withAgentConfiguration(myAgentConfiguration).build();
     myVcsSupport.updateSources(myRoot, rules, version, myCheckoutDir, build, true);
     then(listFiles(myCheckoutDir)).containsOnly(files);
   }
diff --git a/git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/AgentSslCheckoutTest.java b/git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/AgentSslCheckoutTest.java
new file mode 100644 (file)
index 0000000..225ab5e
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.git.tests;
+
+import jetbrains.buildServer.agent.AgentRunningBuild;
+import jetbrains.buildServer.buildTriggers.vcs.git.agent.*;
+import jetbrains.buildServer.buildTriggers.vcs.git.tests.builders.AgentRunningBuildBuilder;
+import jetbrains.buildServer.util.StringUtil;
+import jetbrains.buildServer.vcs.CheckoutRules;
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.VcsRoot;
+import org.jetbrains.annotations.NotNull;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import static jetbrains.buildServer.buildTriggers.vcs.git.agent.PluginConfigImpl.USE_ALTERNATES;
+import static jetbrains.buildServer.buildTriggers.vcs.git.agent.PluginConfigImpl.USE_MIRRORS;
+import static jetbrains.buildServer.buildTriggers.vcs.git.tests.GitVersionProvider.getGitPath;
+import static jetbrains.buildServer.buildTriggers.vcs.git.tests.VcsRootBuilder.vcsRoot;
+import static jetbrains.buildServer.buildTriggers.vcs.git.tests.builders.AgentRunningBuildBuilder.runningBuild;
+import static org.assertj.core.api.BDDAssertions.then;
+
+@Test
+public class AgentSslCheckoutTest extends BaseRemoteRepositoryTest {
+
+  private GitAgentVcsSupport myVcsSupport;
+  private File myCheckoutDir;
+  private File myHomeDirectory;
+  private VcsRoot myRoot;
+  private AgentSupportBuilder myAgentSupportBuilder;
+
+  public AgentSslCheckoutTest() {
+    super();
+  }
+
+  @Override
+  @BeforeMethod
+  public void setUp() throws Exception {
+    super.setUp();
+
+    myHomeDirectory = myAgentConfiguration.getTempDirectory();
+    myCheckoutDir = myTempFiles.createTempDir();
+    myAgentSupportBuilder = new AgentSupportBuilder(myTempFiles);
+    myVcsSupport = myAgentSupportBuilder.build();
+    String pathToGit = getGitPath();
+    myRoot = vcsRoot()
+      .withAgentGitPath(pathToGit)
+      .withFetchUrl("https://github.com/JetBrains/teamcity-commit-hooks.git")
+      .withBranch("master")
+      .build();
+  }
+
+  @DataProvider(name = "github-data")
+  public static Object[][] invariants() {
+    return new Object[][]{
+      /* (write cert | use mirrors | use alternative) */
+      new Object[]{false, false, false},
+      new Object[]{false, true, false},
+      new Object[]{false, false, true},
+
+      new Object[]{true, false, false},
+      new Object[]{true, true, false},
+      new Object[]{true, false, false},
+    };
+  }
+
+  @Test(dataProvider = "github-data")
+  public void githubTest(boolean writeCert, boolean useMirrors, boolean useAlternative) throws Exception {
+    if (writeCert) {
+      writeCertificate();
+    }
+    final AgentRunningBuildBuilder runningBuild = runningBuild();
+    if (useMirrors) {
+      runningBuild.sharedConfigParams(USE_MIRRORS, "true");
+    }
+    if (useAlternative) {
+      runningBuild.sharedConfigParams(USE_ALTERNATES, "true");
+    }
+    fetchGitHub(runningBuild.withAgentConfiguration(myAgentConfiguration).build());
+  }
+
+  private void fetchGitHub(final AgentRunningBuild build) throws VcsException {
+    String versionFirst = "1d45e81f92970f025a8699915b32496a0b6885bd";
+    CheckoutRules rules = new CheckoutRules("");
+    myVcsSupport.updateSources(myRoot, rules, versionFirst, myCheckoutDir, build, false);
+    then(myCheckoutDir.list()).contains("README.md");
+    checkConfig(false, build);
+
+    String versionNext = "ec6baf656c6f3a4f090ed245b423b893e0226264";
+    myVcsSupport.updateSources(myRoot, rules, versionNext, myCheckoutDir, build, false);
+    then(myCheckoutDir.list()).contains("README.md");
+    checkConfig(false, build);
+  }
+
+  private void writeCertificate() throws Exception {
+    new SSLTestUtil().writeAnotherCert(myHomeDirectory);
+  }
+
+  private void checkConfig(boolean shouldCAInfo, AgentRunningBuild build) throws VcsException {
+    final GitFacade gitFacade = gitFacade(build);
+    try {
+      final String sslCAInfo = gitFacade.getConfig().setPropertyName("http.sslCAInfo").call();
+      if (shouldCAInfo) {
+        Assert.assertNotNull(sslCAInfo);
+      } else {
+        Assert.assertNotNull(StringUtil.nullIfEmpty(sslCAInfo));
+      }
+    } catch (Exception e) {
+      if (shouldCAInfo) {
+        Assert.fail("ssl CA info have to be but not exists");
+      }
+    }
+  }
+
+  private GitFacade gitFacade(AgentRunningBuild build) throws VcsException {
+    final AgentPluginConfig config = pluginConfigFactory().createConfig(build, myRoot);
+    final Map<String, String> env = getGitCommandEnv(config, build);
+    final GitFactory gitFactory = gitMetaFactory().createFactory(getGitAgentSSHService(), config, getLogger(build, config),
+                                                                 build.getBuildTempDirectory(), env, new BuildContext(build, config));
+    return gitFactory.create(myCheckoutDir);
+  }
+
+  private GitAgentSSHService getGitAgentSSHService() {
+    return myAgentSupportBuilder.getGitAgentSSHService();
+  }
+
+  private GitMetaFactory gitMetaFactory() {
+    return myAgentSupportBuilder.getGitMetaFactory();
+  }
+
+  private PluginConfigFactory pluginConfigFactory() {
+    return myAgentSupportBuilder.getPluginConfigFactory();
+  }
+
+  private Map<String, String> getGitCommandEnv(@NotNull AgentPluginConfig config, @NotNull AgentRunningBuild build) {
+    if (config.isRunGitWithBuildEnv()) {
+      return build.getBuildParameters().getEnvironmentVariables();
+    } else {
+      return new HashMap<>(0);
+    }
+  }
+
+  @NotNull
+  private GitBuildProgressLogger getLogger(@NotNull AgentRunningBuild build, @NotNull AgentPluginConfig config) {
+    return new GitBuildProgressLogger(build.getBuildLogger().getFlowLogger("-1"), config.getGitProgressMode());
+  }
+}
index 11d2b6774b7c3b0ae0a80305ff6815b8a8ac2621..96b6fddeb85751d04eb481b7d97ce47c4168168c 100644 (file)
@@ -37,6 +37,7 @@ class AgentSupportBuilder {
   private GitMetaFactory myGitMetaFactory;
   private BuildAgent myAgent;
   private FS myFS;
+  private GitAgentSSHService myGitAgentSSHService;
 
   AgentSupportBuilder(@NotNull TempFiles tempFiles) {
     myTempFiles = tempFiles;
@@ -74,8 +75,9 @@ class AgentSupportBuilder {
         return false;
       }
     };
-    return new GitAgentVcsSupport(myFS, new MockDirectoryCleaner(),
-                                  new GitAgentSSHService(myAgent, myAgentConfiguration, new MockGitPluginDescriptor(), mySshKeyProvider, buildTracker),
+    myGitAgentSSHService =
+      new GitAgentSSHService(myAgent, myAgentConfiguration, new MockGitPluginDescriptor(), mySshKeyProvider, buildTracker);
+    return new GitAgentVcsSupport(myFS, new MockDirectoryCleaner(), myGitAgentSSHService,
                                   myPluginConfigFactory, myMirrorManager, myGitMetaFactory);
   }
 
@@ -112,4 +114,16 @@ class AgentSupportBuilder {
   BuildAgentConfiguration getAgentConfiguration() {
     return myAgentConfiguration;
   }
+
+  PluginConfigFactoryImpl getPluginConfigFactory() {
+    return myPluginConfigFactory;
+  }
+
+  GitMetaFactory getGitMetaFactory() {
+    return myGitMetaFactory;
+  }
+
+  GitAgentSSHService getGitAgentSSHService() {
+    return myGitAgentSSHService;
+  }
 }
index 71c247c3efd029a3ffa4fdb151f1b53644c2da1b..c38c4ef24a56f3cbc1cdc0593ba8c965b64e3a69 100644 (file)
@@ -56,6 +56,7 @@ import java.io.IOException;
 import java.lang.reflect.Method;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.regex.Matcher;
@@ -639,9 +640,10 @@ public class AgentVcsSupportTest {
     AtomicInteger invocationCount = new AtomicInteger(0);
     loggingFactory.addCallback(FetchCommand.class.getName() + ".call", new GitCommandProxyCallback() {
       @Override
-      public void call(final Method method, final Object[] args) throws VcsException {
+      public Optional<Object> call(final Method method, final Object[] args) throws VcsException {
         if (invocationCount.getAndIncrement() == 0)
           throw new VcsException("TEST ERROR");
+        return null;
       }
     });
     File mirror = myBuilder.getMirrorManager().getMirrorDir(GitUtils.toURL(remoteRepo));
@@ -653,6 +655,7 @@ public class AgentVcsSupportTest {
     AgentRunningBuild build = runningBuild()
       .useLocalMirrors(true)
       .sharedConfigParams("teamcity.git.fetchMirrorRetryTimeouts", "")
+      .withAgentConfiguration(myBuilder.getAgentConfiguration())
       .build();
     myVcsSupport.updateSources(root2, CheckoutRules.DEFAULT, "d47dda159b27b9a8c4cee4ce98e4435eb5b17168", myCheckoutDir, build, false);
     File mirrorAfterBuild = myBuilder.getMirrorManager().getMirrorDir(GitUtils.toURL(remoteRepo));
@@ -683,9 +686,10 @@ public class AgentVcsSupportTest {
     AtomicInteger invocationCount = new AtomicInteger(0);
     loggingFactory.addCallback(FetchCommand.class.getName() + ".call", new GitCommandProxyCallback() {
       @Override
-      public void call(final Method method, final Object[] args) throws VcsException {
+      public Optional<Object> call(final Method method, final Object[] args) throws VcsException {
         if (invocationCount.getAndIncrement() <= 1)
           throw new VcsException("TEST ERROR");
+        return null;
       }
     });
     File mirror = myBuilder.getMirrorManager().getMirrorDir(GitUtils.toURL(remoteRepo));
@@ -696,6 +700,7 @@ public class AgentVcsSupportTest {
     VcsRootImpl root2 = vcsRoot().withAgentGitPath(getGitPath()).withBranch("refs/heads/personal").withFetchUrl(GitUtils.toURL(remoteRepo)).build();
     AgentRunningBuild build = runningBuild()
       .useLocalMirrors(true)
+      .withAgentConfiguration(myBuilder.getAgentConfiguration())
       .sharedConfigParams("teamcity.git.fetchMirrorRetryTimeouts", "0,0")
       .build();
     myVcsSupport.updateSources(root2, CheckoutRules.DEFAULT, "d47dda159b27b9a8c4cee4ce98e4435eb5b17168", myCheckoutDir, build, false);
@@ -729,12 +734,13 @@ public class AgentVcsSupportTest {
       volatile boolean thrown = false;
 
       @Override
-      public void call(final Method method, final Object[] args) throws VcsException {
+      public Optional<Object> call(final Method method, final Object[] args) throws VcsException {
         if (!thrown) {
           thrown = true;
           throw new GitExecTimeout();
         }
         fail("Should not try to fetch again");
+        return null;
       }
     });
     File mirror = myBuilder.getMirrorManager().getMirrorDir(GitUtils.toURL(remoteRepo));
@@ -745,6 +751,7 @@ public class AgentVcsSupportTest {
     AgentRunningBuild build = runningBuild()
       .useLocalMirrors(true)
       .sharedConfigParams("teamcity.git.fetchMirrorRetryTimeouts", "0,0")
+      .withAgentConfiguration(myBuilder.getAgentConfiguration())
       .build();
 
     try {
@@ -754,8 +761,7 @@ public class AgentVcsSupportTest {
     }
 
     //try again, should succeed without remapping, means previous code has not changed mirror directory
-    loggingFactory.addCallback(FetchCommand.class.getName() + ".call", (method, args) -> {
-    });
+    loggingFactory.addCallback(FetchCommand.class.getName() + ".call", (method, args) -> null);
     myVcsSupport.updateSources(root2, CheckoutRules.DEFAULT, "d47dda159b27b9a8c4cee4ce98e4435eb5b17168", myCheckoutDir, build, false);
 
     File mirrorAfterBuild = myBuilder.getMirrorManager().getMirrorDir(GitUtils.toURL(remoteRepo));
@@ -781,7 +787,7 @@ public class AgentVcsSupportTest {
 
     loggingFactory.addCallback(LsRemoteCommand.class.getName() + ".call", new GitCommandProxyCallback() {
       @Override
-      public void call(final Method method, final Object[] args) throws VcsException {
+      public Optional<Object> call(final Method method, final Object[] args) throws VcsException {
         throw new VcsException("TEST ERROR");
       }
     });
@@ -824,7 +830,8 @@ public class AgentVcsSupportTest {
     myRoot = vcsRoot().withAgentGitPath(getGitPath()).withFetchUrl(unreachableRepository).build();
     try {
       String revision = "abababababababababababababababababababab";
-      AgentRunningBuild build = runningBuild().useLocalMirrors(true).sharedConfigParams("teamcity.git.idle.timeout.seconds", "1").build();
+      AgentRunningBuild build = runningBuild().useLocalMirrors(true)
+        .withAgentConfiguration(myBuilder.getAgentConfiguration()).sharedConfigParams("teamcity.git.idle.timeout.seconds", "1").build();
       myVcsSupport.updateSources(myRoot, CheckoutRules.DEFAULT, revision, myCheckoutDir, build, false);
       fail("update on unreachable repository should fail");
     } catch (VcsException e) {
@@ -865,9 +872,10 @@ public class AgentVcsSupportTest {
     AtomicInteger invocationCount = new AtomicInteger(0);
     loggingFactory.addCallback(FetchCommand.class.getName() + ".call", new GitCommandProxyCallback() {
       @Override
-      public void call(final Method method, final Object[] args) throws VcsException {
-        if (invocationCount.getAndIncrement() <= 1)
+      public Optional<Object> call(final Method method, final Object[] args) throws VcsException {
+        if (invocationCount.getAndIncrement() == 0)
           throw new VcsException("TEST ERROR");
+        return Optional.empty();
       }
     });
     File mirror = myBuilder.getMirrorManager().getMirrorDir(GitUtils.toURL(remoteRepo));
@@ -878,6 +886,7 @@ public class AgentVcsSupportTest {
       AgentRunningBuild build = runningBuild()
         .useLocalMirrors(true)
         .sharedConfigParams(AgentRuntimeProperties.FAIL_ON_CLEAN_CHECKOUT, "true")
+        .withAgentConfiguration(myBuilder.getAgentConfiguration())
         .sharedConfigParams("teamcity.git.fetchMirrorRetryTimeouts", "0")
         .build();
       myVcsSupport.updateSources(root2, CheckoutRules.DEFAULT, "d47dda159b27b9a8c4cee4ce98e4435eb5b17168", myCheckoutDir, build, false);
@@ -1312,12 +1321,12 @@ public class AgentVcsSupportTest {
 
 
   private AgentRunningBuild createRunningBuild(boolean useLocalMirrors) {
-    return runningBuild().useLocalMirrors(useLocalMirrors).build();
+    return runningBuild().useLocalMirrors(useLocalMirrors).withAgentConfiguration(myBuilder.getAgentConfiguration()).build();
   }
 
 
   private AgentRunningBuild createRunningBuild(final Map<String, String> sharedConfigParameters) {
-    return runningBuild().sharedConfigParams(sharedConfigParameters).build();
+    return runningBuild().sharedConfigParams(sharedConfigParameters).withAgentConfiguration(myBuilder.getAgentConfiguration()).build();
   }
 
 
index 61f37974b59e2b7c0713b62d8872b242846177ce..4f0e31f40380cc2215c19890a3d648481d257312 100644 (file)
@@ -65,14 +65,15 @@ public class AutoCheckoutTest extends BaseRemoteRepositoryTest {
 
     VcsRoot vcsRoot = vcsRootWithAgentGitPath();
 
-    verifyCanCheckout(vcsRoot, CheckoutRules.DEFAULT, runningBuild().addRoot(vcsRoot).build());
+    verifyCanCheckout(vcsRoot, CheckoutRules.DEFAULT, runningBuild().addRoot(vcsRoot).withAgentConfiguration(myAgentConfiguration).build());
   }
 
   public void client_found_by_path_from_environment() throws IOException, VcsException {
     myVcsSupport = vcsSupportWithRealGit();
 
     VcsRoot vcsRoot = vcsRootWithAgentGitPath(null);
-    AgentRunningBuild build = runningBuild().sharedEnvVariable(Constants.TEAMCITY_AGENT_GIT_PATH, getGitPath()).addRoot(vcsRoot).build();
+    AgentRunningBuild build = runningBuild().sharedEnvVariable(Constants.TEAMCITY_AGENT_GIT_PATH, getGitPath()).addRoot(vcsRoot)
+      .withAgentConfiguration(myAgentConfiguration).build();
 
     verifyCanCheckout(vcsRoot, CheckoutRules.DEFAULT, build);
   }
index 44b4d4fe1d1be5b67473986ef7cc30a96e5aa9ec..10eedf06fbf887046411dd897f13f12e197833f4 100644 (file)
@@ -18,7 +18,9 @@ package jetbrains.buildServer.buildTriggers.vcs.git.tests;
 
 import jetbrains.buildServer.TempFiles;
 import jetbrains.buildServer.TestInternalProperties;
+import jetbrains.buildServer.agent.BuildAgentConfiguration;
 import jetbrains.buildServer.buildTriggers.vcs.git.GitUtils;
+import jetbrains.buildServer.buildTriggers.vcs.git.tests.builders.BuildAgentConfigurationBuilder;
 import jetbrains.buildServer.log.LogInitializer;
 import org.jetbrains.annotations.NotNull;
 import org.testng.annotations.AfterMethod;
@@ -39,6 +41,7 @@ public abstract class BaseRemoteRepositoryTest {
 
   private Set<String> myPropertiesToClean;
   protected TempFiles myTempFiles;
+  protected BuildAgentConfiguration myAgentConfiguration;
   private String[] myRepositories;
   private Map<String, File> myRemoteRepositories;
 
@@ -63,6 +66,8 @@ public abstract class BaseRemoteRepositoryTest {
       copyRepository(dataFile(r), remoteRepository);
       myRemoteRepositories.put(r, remoteRepository);
     }
+    myAgentConfiguration = BuildAgentConfigurationBuilder.agentConfiguration(myTempFiles.createTempDir(), myTempFiles.createTempDir())
+      .build();
   }
 
   @AfterMethod
index 41c6239118762215a9744ebe94c8d043fefa805d..8681acce490c26fc9b18f5fde0f742149369ae66 100644 (file)
 
 package jetbrains.buildServer.buildTriggers.vcs.git.tests;
 
-import jetbrains.buildServer.BuildProblemData;
 import jetbrains.buildServer.agent.AgentRunningBuild;
-import jetbrains.buildServer.agent.BuildProgressLogger;
-import jetbrains.buildServer.agent.FlowLogger;
 import jetbrains.buildServer.agent.NullBuildProgressLogger;
 import jetbrains.buildServer.buildTriggers.vcs.git.agent.GitAgentVcsSupport;
 import jetbrains.buildServer.buildTriggers.vcs.git.agent.PluginConfigImpl;
-import jetbrains.buildServer.messages.BuildMessage1;
-import jetbrains.buildServer.messages.Status;
 import jetbrains.buildServer.vcs.CheckoutRules;
-import jetbrains.buildServer.vcs.VcsException;
 import jetbrains.buildServer.vcs.VcsRoot;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -34,7 +28,10 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import java.io.File;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 import static jetbrains.buildServer.buildTriggers.vcs.git.tests.GitVersionProvider.getGitPath;
 import static jetbrains.buildServer.buildTriggers.vcs.git.tests.VcsRootBuilder.vcsRoot;
@@ -114,7 +111,7 @@ public class DiffWithUpperLimitRevisionTest extends BaseRemoteRepositoryTest {
       params.put("teamcity.upperLimitRevision." + rootExtId, upperLimitRevision);
     }
     params.putAll(map(additionalParams));
-    return runningBuild().sharedConfigParams(params).withBuildLogger(myBuildLogger).build();
+    return runningBuild().sharedConfigParams(params).withBuildLogger(myBuildLogger).withAgentConfiguration(myAgentConfiguration).build();
   }
 
 
index de4e0428936396c90930e17aee4cca5542ef900a..802f2ccbe73f2ffc3ab6058ffe346118930cfbfa 100644 (file)
@@ -23,6 +23,7 @@ import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 public class GitCommandProxy implements InvocationHandler {
   private final Object myGitCommand;
@@ -43,7 +44,10 @@ public class GitCommandProxy implements InvocationHandler {
     myInvokedMethods.add(method.getName());
     GitCommandProxyCallback callback = myCallbacks.get(myGitCommandClass.getName() + "." + method.getName());
     if (callback != null) {
-      callback.call(method, args);
+      final Optional<Object> call = callback.call(method, args);
+      if (call != null) {
+        return call.isPresent() ? call.get() : null;
+      }
     }
     Object result = method.invoke(myGitCommand, args);
     if (myGitCommandClass.isInstance(result)) {//case of chaining
index b27f4a3394d9156473fff495abeff4505ba0dc01..a5e07cdb54451f99f35f8b0aac8755516c3ccf7e 100644 (file)
 package jetbrains.buildServer.buildTriggers.vcs.git.tests;
 
 import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.Nullable;
 
 import java.lang.reflect.Method;
+import java.util.Optional;
 
 public interface GitCommandProxyCallback {
-  void call(Method method, Object[] args) throws VcsException;
+  /**
+   * Callback which will be called before or instead of origin method
+   *
+   * @param method origin method name
+   * @param args   origin args
+   * @return return null if you what to call origin method next; return Optional with value which will be used as original method return
+   */
+  @Nullable
+  Optional<Object> call(Method method, Object[] args) throws VcsException;
 }
index b62b9dd7810e7f9fc05c9dfa486278f3a69c0167..c68cfab8f585e7c519d1f43e030974e9a94e68aa 100644 (file)
@@ -100,6 +100,7 @@ public class HttpAuthTest extends BaseRemoteRepositoryTest {
     AgentRunningBuild build = runningBuild()
       .sharedEnvVariable(Constants.TEAMCITY_AGENT_GIT_PATH, git.getPath())
       .sharedConfigParams(PluginConfigImpl.USE_ALTERNATES, "true")
+      .withAgentConfiguration(myAgentConfiguration)
       .build();
 
     //run first build to initialize mirror:
@@ -146,6 +147,7 @@ public class HttpAuthTest extends BaseRemoteRepositoryTest {
     File buildDir = myTempFiles.createTempDir();
     AgentRunningBuild build = runningBuild()
       .sharedEnvVariable(Constants.TEAMCITY_AGENT_GIT_PATH, git.getPath())
+      .withAgentConfiguration(myAgentConfiguration)
       .build();
 
     Checkout checkout = new Checkout(root, "add81050184d3c818560bdd8839f50024c188586", buildDir, build);
index 5874da5eb999e661b05eeb7c25d2ed8c53472269..4e8059b47645ff6dfba7ccc7062b860905472324 100644 (file)
@@ -180,6 +180,7 @@ public class HttpUrlWithUsernameTest extends BaseRemoteRepositoryTest {
     return runningBuild()
       .sharedEnvVariable(Constants.TEAMCITY_AGENT_GIT_PATH, myGitPath)
       .sharedConfigParams(configParams)
+      .withAgentConfiguration(myAgentConfiguration)
       .build();
   }
 
diff --git a/git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/SSLInvestigatorTest.java b/git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/SSLInvestigatorTest.java
new file mode 100644 (file)
index 0000000..87e3ce8
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.git.tests;
+
+import com.sun.net.httpserver.HttpsServer;
+import jetbrains.buildServer.TempFiles;
+import jetbrains.buildServer.buildTriggers.vcs.git.agent.*;
+import jetbrains.buildServer.buildTriggers.vcs.git.agent.command.GetConfigCommand;
+import jetbrains.buildServer.buildTriggers.vcs.git.agent.command.SetConfigCommand;
+import jetbrains.buildServer.buildTriggers.vcs.git.agent.ssl.SSLInvestigator;
+import jetbrains.buildServer.serverSide.BasePropertiesModel;
+import jetbrains.buildServer.serverSide.TeamCityProperties;
+import org.eclipse.jgit.transport.URIish;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.testng.annotations.*;
+
+import java.io.File;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Optional;
+
+import static org.testng.Assert.*;
+
+/**
+ * Unit tests for {@link SSLInvestigator}.
+ *
+ * @author Mikhail Khorkov
+ * @since 2018.1.2
+ */
+@Test
+public class SSLInvestigatorTest {
+
+  private TempFiles myTempFiles = new TempFiles();
+  private File myHomeDirectory;
+  private File myTempDirectory;
+  private Mockery myMockery;
+  private LoggingGitMetaFactory myLoggingFactory;
+
+  private HttpsServer myServer;
+  private SSLTestUtil mySSLTestUtil;
+  private int myServerPort;
+
+  private enum Plot {FEATURE_OFF, GOOD_CERT, BAD_CERT, NO_CERT}
+
+  private enum Result {ONLY_GET, ONLY_SET, GET_AND_SET, GET_AND_UNSET}
+
+  @BeforeClass
+  public void init() throws Exception {
+    mySSLTestUtil = new SSLTestUtil();
+    myServer = mySSLTestUtil.getHttpsServer();
+    myServerPort = mySSLTestUtil.getServerPort();
+    myServer.start();
+  }
+
+  @AfterClass
+  public void down() {
+    myServer.stop(0);
+  }
+
+  @BeforeMethod
+  public void setUp() throws Exception {
+    myHomeDirectory = myTempFiles.createTempDir();
+    myTempDirectory = myTempFiles.createTempDir();
+
+    new TeamCityProperties() {{
+      setModel(new BasePropertiesModel() {
+      });
+    }};
+
+    myMockery = new Mockery() {{
+      setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+    myLoggingFactory = new LoggingGitMetaFactory();
+  }
+
+  @AfterMethod
+  public void tearDown() {
+    myTempFiles.cleanup();
+  }
+
+  @DataProvider(name = "invariants")
+  public static Object[][] invariants() {
+    return new Object[][] {
+      /* plot of  prerequisites | custom flag is set already | expected result */
+      new Object[]{Plot.FEATURE_OFF, false, Result.ONLY_GET},
+      new Object[]{Plot.FEATURE_OFF, true, Result.GET_AND_UNSET},
+
+      new Object[]{Plot.BAD_CERT, false, Result.ONLY_GET},
+      new Object[]{Plot.BAD_CERT, true, Result.GET_AND_UNSET},
+
+      new Object[]{Plot.NO_CERT, false, Result.ONLY_GET},
+      new Object[]{Plot.NO_CERT, true, Result.GET_AND_UNSET},
+
+      new Object[]{Plot.GOOD_CERT, false, Result.ONLY_SET},
+      new Object[]{Plot.GOOD_CERT, true, Result.ONLY_SET},
+    };
+  }
+
+  @Test(dataProvider = "invariants")
+  public void allTest(Plot plot, boolean alreadySet, Result result) throws Exception {
+    switch (plot) {
+      case FEATURE_OFF: {
+        System.setProperty("teamcity.ssl.useCustomTrustStore.git", "false");
+        break;
+      }
+      case NO_CERT: {
+        System.setProperty("teamcity.ssl.useCustomTrustStore.git", "true");
+        break;
+      }
+      case BAD_CERT: {
+        System.setProperty("teamcity.ssl.useCustomTrustStore.git", "true");
+        myTempFiles.registerAsTempFile(mySSLTestUtil.writeAnotherCert(myHomeDirectory));
+        break;
+      }
+      case GOOD_CERT: {
+        System.setProperty("teamcity.ssl.useCustomTrustStore.git", "true");
+        myTempFiles.registerAsTempFile(mySSLTestUtil.writeServerCert(myHomeDirectory));
+        break;
+      }
+    }
+
+    final String alreadyInProperties = alreadySet ? "something" : "";
+    myLoggingFactory.addCallback(GetConfigCommand.class.getName() + ".call",
+                                 (method, args) -> Optional.of(alreadyInProperties));
+    myLoggingFactory.addCallback(SetConfigCommand.class.getName() + ".call",
+                                 (method, args) -> Optional.empty());
+
+    final SSLInvestigator instance = createInstance();
+
+    instance.setCertificateOptions(createFactory().create(myTempDirectory));
+
+    switch (result) {
+      case ONLY_GET: {
+        assertEquals(myLoggingFactory.getNumberOfCalls(GetConfigCommand.class), 1);
+        assertEquals(myLoggingFactory.getNumberOfCalls(SetConfigCommand.class), 0);
+        break;
+      }
+      case ONLY_SET: {
+        assertEquals(myLoggingFactory.getNumberOfCalls(GetConfigCommand.class), 0);
+        assertEquals(myLoggingFactory.getNumberOfCalls(SetConfigCommand.class), 1);
+        assertFalse(myLoggingFactory.getInvokedMethods(SetConfigCommand.class).contains("unSet"));
+        break;
+      }
+      case GET_AND_SET: {
+        assertEquals(myLoggingFactory.getNumberOfCalls(GetConfigCommand.class), 1);
+        assertEquals(myLoggingFactory.getNumberOfCalls(SetConfigCommand.class), 1);
+        assertFalse(myLoggingFactory.getInvokedMethods(SetConfigCommand.class).contains("unSet"));
+        break;
+      }
+      case GET_AND_UNSET: {
+        assertEquals(myLoggingFactory.getNumberOfCalls(GetConfigCommand.class), 1);
+        assertEquals(myLoggingFactory.getNumberOfCalls(SetConfigCommand.class), 1);
+        assertTrue(myLoggingFactory.getInvokedMethods(SetConfigCommand.class).contains("unSet"));
+        break;
+      }
+    }
+  }
+
+  private SSLInvestigator createInstance() throws Exception {
+    return new SSLInvestigator(new URIish(new URL("https://localhost:" + myServerPort)), myTempDirectory.getPath(), myHomeDirectory.getPath());
+  }
+
+  private GitFactory createFactory() throws Exception {
+    final GitAgentSSHService ssh = myMockery.mock(GitAgentSSHService.class);
+    final AgentPluginConfig pluginConfig = myMockery.mock(AgentPluginConfig.class);
+    final Context context = myMockery.mock(Context.class);
+    myMockery.checking(new Expectations() {{
+      atLeast(1).of(pluginConfig).getPathToGit();
+      will(returnValue("git"));
+      atLeast(1).of(pluginConfig).getGitVersion();
+      will(returnValue(GitVersion.MIN));
+      atLeast(1).of(pluginConfig).isDeleteTempFiles();
+      will(returnValue(false));
+      atLeast(1).of(pluginConfig).getGitExec();
+      will(returnValue(myMockery.mock(GitExec.class)));
+    }});
+    final GitProgressLogger logger = myMockery.mock(GitProgressLogger.class);
+    return myLoggingFactory.createFactory(ssh, pluginConfig, logger, myTempFiles.createTempDir(), Collections.emptyMap(), context);
+  }
+}
diff --git a/git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/SSLTestUtil.java b/git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/SSLTestUtil.java
new file mode 100644 (file)
index 0000000..9afdf49
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.git.tests;
+
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsParameters;
+import com.sun.net.httpserver.HttpsServer;
+import com.sun.xml.internal.messaging.saaj.util.ByteInputStream;
+import jetbrains.buildServer.NetworkUtil;
+import jetbrains.buildServer.agent.ssl.TrustedCertificatesDirectory;
+import jetbrains.buildServer.util.FileUtil;
+
+import javax.net.ssl.*;
+import java.io.File;
+import java.net.InetSocketAddress;
+import java.security.KeyStore;
+import java.security.cert.Certificate;
+import java.util.Base64;
+
+class SSLTestUtil {
+
+  static final char[] KEY_STORE_PASSWD = "123456".toCharArray();
+  static final String KEY_STORE =
+    "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";
+
+  static final String CERT_PUBLIC = "-----BEGIN CERTIFICATE-----\n" +
+                                           "MIICQTCCAaoCCQCgsSqblM1uHDANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJD\n" +
+                                           "TjELMAkGA1UECAwCQ1MxCzAJBgNVBAcMAkxOMQswCQYDVQQKDAJPTjEMMAoGA1UE\n" +
+                                           "CwwDT1VOMQswCQYDVQQDDAJDTjEUMBIGCSqGSIb3DQEJARYFYW1haWwwHhcNMTgw\n" +
+                                           "NzI1MDc0MTQyWhcNMTkwNzI1MDc0MTQyWjBlMQswCQYDVQQGEwJDTjELMAkGA1UE\n" +
+                                           "CAwCQ1MxCzAJBgNVBAcMAkxOMQswCQYDVQQKDAJPTjEMMAoGA1UECwwDT1VOMQsw\n" +
+                                           "CQYDVQQDDAJDTjEUMBIGCSqGSIb3DQEJARYFYW1haWwwgZ8wDQYJKoZIhvcNAQEB\n" +
+                                           "BQADgY0AMIGJAoGBALd6XvMOgLUrjioJxgudKQlcqbPbihcWWha1SvfY491Ya93Q\n" +
+                                           "q3R8AiLybJqidfdlDZFA/fiXsIs+LnQD9S+uFdC83u2gpzqlIim7A7w/X4B8JClP\n" +
+                                           "wNS8AebnAcn8FEgi9AOHsoBb/mitke6gUkf5TUAwsdsTDi2YV7Rdmy1Ux6GVAgMB\n" +
+                                           "AAEwDQYJKoZIhvcNAQEFBQADgYEAPPh1TupBgST6RVyWvJvXlcnPm3LOH3J8Jd3V\n" +
+                                           "+bm6+W4zs1TLjZgOzGLoTR05INISahYDjAlYZm2v0aOYm2MdxZepxSec/47K4HL2\n" +
+                                           "gr3hMGf7xFwTNLwxNmiTiBneuTcxfinGxAp+grq9jMaZXGWKorS1ATnyWmpXfQ8j\n" +
+                                           "ESLNmVw=\n" +
+                                           "-----END CERTIFICATE-----";
+
+  private KeyStore myServerKeyStore;
+  private Certificate myServerCertificate;
+  private HttpsServer myHttpsServer;
+  private int myServerPort = 0;
+
+  File writeAnotherCert(final File homeDirectory) throws Exception {
+    final String certDirectory = TrustedCertificatesDirectory.getAllCertificatesDirectoryFromHome(homeDirectory.getPath());
+    final File cert = new File(certDirectory, "cert.pem");
+    cert.getParentFile().mkdirs();
+    FileUtil.writeFile(cert, CERT_PUBLIC, "UTF-8");
+    return cert;
+  }
+
+  File writeServerCert(final File homeDirectory) throws Exception {
+    final String certDirectory = TrustedCertificatesDirectory.getAllCertificatesDirectoryFromHome(homeDirectory.getPath());
+    final File cert = new File(certDirectory, "cert.pem");
+    cert.getParentFile().mkdirs();
+    FileUtil.writeToFile(cert, getServerCertificate().getEncoded());
+    return cert;
+  }
+
+  KeyStore getServerKeyStore() throws Exception {
+    if (myServerKeyStore != null) {
+      return myServerKeyStore;
+    }
+    final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+    final byte[] keyStoreData = Base64.getDecoder().decode(SSLTestUtil.KEY_STORE);
+    final char[] password = "123456".toCharArray();
+    try (final ByteInputStream in = new ByteInputStream(keyStoreData, keyStoreData.length)) {
+      keyStore.load(in, password);
+    }
+    myServerKeyStore = keyStore;
+    return myServerKeyStore;
+  }
+
+  Certificate getServerCertificate() throws Exception {
+    if (myServerCertificate != null) {
+      return myServerCertificate;
+    }
+    myServerCertificate = getServerKeyStore().getCertificate("git_plugin");
+    return myServerCertificate;
+  }
+
+  HttpsServer getHttpsServer() throws Exception {
+    if (myHttpsServer != null) {
+      return myHttpsServer;
+    }
+    final int freePort = getServerPort();
+    final HttpsServer server = HttpsServer.create(new InetSocketAddress("localhost", freePort), 0);
+    final SSLContext sslContext = SSLContext.getInstance("TLS");
+
+    final KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+    kmf.init(getServerKeyStore(), KEY_STORE_PASSWD);
+    // setup the trust manager factory
+    final TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+    tmf.init(getServerKeyStore());
+    // setup the HTTPS context and parameters
+    sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+    server.setHttpsConfigurator(new HttpsConfigurator(sslContext) {
+      public void configure(HttpsParameters params) {
+        try {
+          // initialise the SSL context
+          SSLContext c = SSLContext.getDefault();
+          SSLEngine engine = c.createSSLEngine();
+          params.setNeedClientAuth(false);
+          params.setCipherSuites(engine.getEnabledCipherSuites());
+          params.setProtocols(engine.getEnabledProtocols());
+
+          // get the default parameters
+          SSLParameters defaultSSLParameters = c.getDefaultSSLParameters();
+          params.setSSLParameters(defaultSSLParameters);
+        } catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      }
+    });
+
+    myHttpsServer = server;
+    return myHttpsServer;
+  }
+
+  public int getServerPort() {
+    if (myServerPort != 0) {
+      return myServerPort;
+    }
+    myServerPort = NetworkUtil.getFreePort(1025);
+    return myServerPort;
+  }
+}
index f7bc5423f767808fe9e8ee198cd1cfbc5926a87d..9b805bee6571b156d080d6642317da4559f1d3d2 100644 (file)
@@ -43,6 +43,7 @@ public class AgentRunningBuildBuilder {
   private Map<String, String> mySharedBuildParameters = new HashMap<String, String>();
   private List<VcsRootEntry> myRootEntries = null;
   private BuildProgressLogger myBuildLogger = null;
+  private BuildAgentConfiguration myConfiguration;
 
   public static AgentRunningBuildBuilder runningBuild() {
     return new AgentRunningBuildBuilder();
@@ -93,6 +94,11 @@ public class AgentRunningBuildBuilder {
     return this;
   }
 
+  public AgentRunningBuildBuilder withAgentConfiguration(@NotNull final BuildAgentConfiguration configuration) {
+    myConfiguration = configuration;
+    return this;
+  }
+
 
   public AgentRunningBuild build() {
     return new AgentRunningBuild() {
@@ -321,12 +327,12 @@ public class AgentRunningBuildBuilder {
 
       @NotNull
       public File getAgentTempDirectory() {
-        throw new UnsupportedOperationException();
+        return new File(FileUtil.getTempDirectory());
       }
 
       @NotNull
       public BuildAgentConfiguration getAgentConfiguration() {
-        throw new UnsupportedOperationException();
+        return myConfiguration;
       }
 
       public <T> T getBuildTypeOptionValue(final Option<T> option) {
index 051a7e81bd74cbc9f8f2586bf77461836a2e3f9a..8fcb453848ae3a9631d9839c651fa8a8cc96639d 100644 (file)
@@ -127,7 +127,7 @@ public class BuildAgentConfigurationBuilder {
 
       @NotNull
       public File getAgentHomeDirectory() {
-        throw new UnsupportedOperationException();
+        return myAgentTempDir;
       }
 
       @NotNull
index d8dbe517c8e943535b30b9e8fc6a31bcb2b6fff5..d16b07f7734c5505d220ef11b9928840ea92c2d6 100644 (file)
@@ -27,6 +27,8 @@
       <class name="jetbrains.buildServer.buildTriggers.vcs.git.tests.RemoteRepositoryConfiguratorTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.git.tests.AutoCheckoutTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.git.tests.DiffWithUpperLimitRevisionTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.git.tests.AgentSslCheckoutTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.git.tests.SSLInvestigatorTest"/>
     </classes>
   </test>
 </suite>
\ No newline at end of file