| // Copyright (C) 2021 The Android Open Source Project |
| // |
| // 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 com.google.gerrit.acceptance; |
| |
| import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| import static java.nio.file.Files.createTempDirectory; |
| |
| import com.google.common.io.CharSink; |
| import com.google.common.io.Files; |
| import com.google.common.io.MoreFiles; |
| import com.google.gerrit.acceptance.testsuite.account.TestAccount; |
| import com.google.gerrit.acceptance.testsuite.account.TestSshKeys; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.io.Reader; |
| import java.net.InetSocketAddress; |
| import java.nio.charset.StandardCharsets; |
| import java.security.GeneralSecurityException; |
| import java.security.InvalidAlgorithmParameterException; |
| import java.security.KeyPairGenerator; |
| import java.security.spec.InvalidKeySpecException; |
| import java.util.Arrays; |
| import java.util.Scanner; |
| import org.apache.sshd.common.cipher.ECCurves; |
| import org.apache.sshd.common.config.keys.KeyUtils; |
| import org.apache.sshd.common.config.keys.writer.openssh.OpenSSHKeyPairResourceWriter; |
| import org.apache.sshd.common.util.security.SecurityUtils; |
| import org.eclipse.jgit.transport.SshSessionFactory; |
| import org.eclipse.jgit.transport.URIish; |
| import org.eclipse.jgit.transport.sshd.DefaultProxyDataFactory; |
| import org.eclipse.jgit.transport.sshd.JGitKeyCache; |
| import org.eclipse.jgit.transport.sshd.SshdSession; |
| import org.eclipse.jgit.transport.sshd.SshdSessionFactory; |
| import org.eclipse.jgit.util.FS; |
| |
| public class SshSessionMina extends SshSession { |
| private static final int TIMEOUT = 100000; |
| |
| private SshdSession session; |
| |
| public static void initClient() { |
| JGitKeyCache keyCache = new JGitKeyCache(); |
| SshdSessionFactory factory = new SshdSessionFactory(keyCache, new DefaultProxyDataFactory()); |
| SshSessionFactory.setInstance(factory); |
| } |
| |
| public static KeyPairGenerator initKeyPairGenerator() |
| throws GeneralSecurityException, InvalidKeySpecException, InvalidAlgorithmParameterException { |
| int size = 256; |
| KeyPairGenerator gen = SecurityUtils.getKeyPairGenerator(KeyUtils.EC_ALGORITHM); |
| ECCurves curve = ECCurves.fromCurveSize(size); |
| if (curve == null) { |
| throw new InvalidKeySpecException("Unknown curve for key size=" + size); |
| } |
| gen.initialize(curve.getParameters()); |
| return gen; |
| } |
| |
| public SshSessionMina(TestSshKeys sshKeys, InetSocketAddress addr, TestAccount account) { |
| super(sshKeys, addr, account); |
| } |
| |
| @Override |
| public void open() throws Exception { |
| @SuppressWarnings("unused") |
| var unused = getMinaSession(); |
| } |
| |
| @Override |
| public void close() { |
| if (session != null) { |
| session.disconnect(); |
| session = null; |
| } |
| } |
| |
| @SuppressWarnings("resource") |
| @Override |
| public String exec(String command) throws Exception { |
| Process process = getMinaSession().exec(command, TIMEOUT); |
| InputStream in = process.getInputStream(); |
| InputStream err = process.getErrorStream(); |
| |
| Scanner s = new Scanner(err, UTF_8.name()).useDelimiter("\\A"); |
| error = s.hasNext() ? s.next() : null; |
| |
| s = new Scanner(in, UTF_8.name()).useDelimiter("\\A"); |
| return s.hasNext() ? s.next() : ""; |
| } |
| |
| @SuppressWarnings("resource") |
| @Override |
| public int execAndReturnStatus(String command) throws Exception { |
| Process process = getMinaSession().exec(command, 0); |
| InputStream err = process.getErrorStream(); |
| |
| Scanner s = new Scanner(err, UTF_8.name()).useDelimiter("\\A"); |
| error = s.hasNext() ? s.next() : null; |
| |
| try { |
| return process.exitValue(); |
| } catch (IllegalThreadStateException e) { |
| // SSH command was interrupted |
| return -1; |
| } |
| } |
| |
| @Override |
| public Reader execAndReturnReader(String command) throws Exception { |
| return new InputStreamReader( |
| getMinaSession().exec(command, 0).getInputStream(), StandardCharsets.UTF_8); |
| } |
| |
| private SshdSession getMinaSession() throws Exception { |
| if (session == null) { |
| String username = getUsername(); |
| |
| URIish uri = |
| new URIish( |
| "ssh://" |
| + username |
| + "@" |
| + addr.getAddress().getHostAddress() |
| + ":" |
| + addr.getPort()); |
| |
| // TODO(davido): Switch to memory only key resolving mode. |
| File userhome = createTempDirectory("home-").toFile(); |
| |
| FS fs = FS.DETECTED.setUserHome(userhome); |
| File sshDir = new File(userhome, ".ssh"); |
| sshDir.mkdir(); |
| OpenSSHKeyPairResourceWriter keyPairWriter = new OpenSSHKeyPairResourceWriter(); |
| try (OutputStream out = new FileOutputStream(new File(sshDir, "id_ecdsa"))) { |
| keyPairWriter.writePrivateKey(sshKeys.getKeyPair(account), null, null, out); |
| } |
| |
| // TODO(davido): Disable programmatically host key checking: "StrictHostKeyChecking: no" mode. |
| CharSink configFile = Files.asCharSink(new File(sshDir, "config"), UTF_8); |
| configFile.writeLines(Arrays.asList("Host *", "StrictHostKeyChecking no")); |
| |
| JGitKeyCache keyCache = new JGitKeyCache(); |
| try (SshdSessionFactory factory = |
| new SshdSessionFactory(keyCache, new DefaultProxyDataFactory())) { |
| factory.setHomeDirectory(userhome); |
| factory.setSshDirectory(sshDir); |
| |
| session = factory.getSession(uri, null, fs, TIMEOUT); |
| |
| session.addCloseListener( |
| future -> { |
| try { |
| MoreFiles.deleteRecursively(userhome.toPath(), ALLOW_INSECURE); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| throw new RuntimeException("Failed to cleanup userhome", e); |
| } |
| }); |
| } |
| } |
| return session; |
| } |
| } |