LCOV - code coverage report
Current view: top level - acceptance - SshSessionMina.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 58 62 93.5 %
Date: 2022-11-19 15:00:39 Functions: 10 10 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2021 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.acceptance;
      16             : 
      17             : import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE;
      18             : import static java.nio.charset.StandardCharsets.UTF_8;
      19             : import static java.nio.file.Files.createTempDirectory;
      20             : 
      21             : import com.google.common.io.CharSink;
      22             : import com.google.common.io.Files;
      23             : import com.google.common.io.MoreFiles;
      24             : import com.google.gerrit.acceptance.testsuite.account.TestAccount;
      25             : import com.google.gerrit.acceptance.testsuite.account.TestSshKeys;
      26             : import java.io.File;
      27             : import java.io.FileOutputStream;
      28             : import java.io.IOException;
      29             : import java.io.InputStream;
      30             : import java.io.InputStreamReader;
      31             : import java.io.OutputStream;
      32             : import java.io.Reader;
      33             : import java.net.InetSocketAddress;
      34             : import java.nio.charset.StandardCharsets;
      35             : import java.security.GeneralSecurityException;
      36             : import java.security.InvalidAlgorithmParameterException;
      37             : import java.security.KeyPairGenerator;
      38             : import java.security.spec.InvalidKeySpecException;
      39             : import java.util.Arrays;
      40             : import java.util.Scanner;
      41             : import org.apache.sshd.common.cipher.ECCurves;
      42             : import org.apache.sshd.common.config.keys.KeyUtils;
      43             : import org.apache.sshd.common.config.keys.writer.openssh.OpenSSHKeyPairResourceWriter;
      44             : import org.apache.sshd.common.util.security.SecurityUtils;
      45             : import org.eclipse.jgit.transport.SshSessionFactory;
      46             : import org.eclipse.jgit.transport.URIish;
      47             : import org.eclipse.jgit.transport.sshd.DefaultProxyDataFactory;
      48             : import org.eclipse.jgit.transport.sshd.JGitKeyCache;
      49             : import org.eclipse.jgit.transport.sshd.SshdSession;
      50             : import org.eclipse.jgit.transport.sshd.SshdSessionFactory;
      51             : import org.eclipse.jgit.util.FS;
      52             : 
      53             : public class SshSessionMina extends SshSession {
      54             :   private static final int TIMEOUT = 100000;
      55             : 
      56             :   private SshdSession session;
      57             : 
      58             :   public static void initClient() {
      59          13 :     JGitKeyCache keyCache = new JGitKeyCache();
      60          13 :     SshdSessionFactory factory = new SshdSessionFactory(keyCache, new DefaultProxyDataFactory());
      61          13 :     SshSessionFactory.setInstance(factory);
      62          13 :   }
      63             : 
      64             :   public static KeyPairGenerator initKeyPairGenerator()
      65             :       throws GeneralSecurityException, InvalidKeySpecException, InvalidAlgorithmParameterException {
      66          13 :     int size = 256;
      67          13 :     KeyPairGenerator gen = SecurityUtils.getKeyPairGenerator(KeyUtils.EC_ALGORITHM);
      68          13 :     ECCurves curve = ECCurves.fromCurveSize(size);
      69          13 :     if (curve == null) {
      70           0 :       throw new InvalidKeySpecException("Unknown curve for key size=" + size);
      71             :     }
      72          13 :     gen.initialize(curve.getParameters());
      73          13 :     return gen;
      74             :   }
      75             : 
      76             :   public SshSessionMina(TestSshKeys sshKeys, InetSocketAddress addr, TestAccount account) {
      77         132 :     super(sshKeys, addr, account);
      78         132 :   }
      79             : 
      80             :   @Override
      81             :   public void open() throws Exception {
      82          13 :     getMinaSession();
      83          13 :   }
      84             : 
      85             :   @Override
      86             :   public void close() {
      87          13 :     if (session != null) {
      88          13 :       session.disconnect();
      89          13 :       session = null;
      90             :     }
      91          13 :   }
      92             : 
      93             :   @SuppressWarnings("resource")
      94             :   @Override
      95             :   public String exec(String command) throws Exception {
      96           4 :     Process process = getMinaSession().exec(command, TIMEOUT);
      97           4 :     InputStream in = process.getInputStream();
      98           4 :     InputStream err = process.getErrorStream();
      99             : 
     100           4 :     Scanner s = new Scanner(err, UTF_8.name()).useDelimiter("\\A");
     101           4 :     error = s.hasNext() ? s.next() : null;
     102             : 
     103           4 :     s = new Scanner(in, UTF_8.name()).useDelimiter("\\A");
     104           4 :     return s.hasNext() ? s.next() : "";
     105             :   }
     106             : 
     107             :   @SuppressWarnings("resource")
     108             :   @Override
     109             :   public int execAndReturnStatus(String command) throws Exception {
     110           1 :     Process process = getMinaSession().exec(command, 0);
     111           1 :     InputStream err = process.getErrorStream();
     112             : 
     113           1 :     Scanner s = new Scanner(err, UTF_8.name()).useDelimiter("\\A");
     114           1 :     error = s.hasNext() ? s.next() : null;
     115             : 
     116             :     try {
     117           1 :       return process.exitValue();
     118           1 :     } catch (IllegalThreadStateException e) {
     119             :       // SSH command was interrupted
     120           1 :       return -1;
     121             :     }
     122             :   }
     123             : 
     124             :   @Override
     125             :   public Reader execAndReturnReader(String command) throws Exception {
     126           1 :     return new InputStreamReader(
     127           1 :         getMinaSession().exec(command, 0).getInputStream(), StandardCharsets.UTF_8);
     128             :   }
     129             : 
     130             :   private SshdSession getMinaSession() throws Exception {
     131          13 :     if (session == null) {
     132          13 :       String username = getUsername();
     133             : 
     134          13 :       URIish uri =
     135             :           new URIish(
     136             :               "ssh://"
     137             :                   + username
     138             :                   + "@"
     139          13 :                   + addr.getAddress().getHostAddress()
     140             :                   + ":"
     141          13 :                   + addr.getPort());
     142             : 
     143             :       // TODO(davido): Switch to memory only key resolving mode.
     144          13 :       File userhome = createTempDirectory("home-").toFile();
     145             : 
     146          13 :       FS fs = FS.DETECTED.setUserHome(userhome);
     147          13 :       File sshDir = new File(userhome, ".ssh");
     148          13 :       sshDir.mkdir();
     149          13 :       OpenSSHKeyPairResourceWriter keyPairWriter = new OpenSSHKeyPairResourceWriter();
     150          13 :       try (OutputStream out = new FileOutputStream(new File(sshDir, "id_ecdsa"))) {
     151          13 :         keyPairWriter.writePrivateKey(sshKeys.getKeyPair(account), null, null, out);
     152             :       }
     153             : 
     154             :       // TODO(davido): Disable programmatically host key checking: "StrictHostKeyChecking: no" mode.
     155          13 :       CharSink configFile = Files.asCharSink(new File(sshDir, "config"), UTF_8);
     156          13 :       configFile.writeLines(Arrays.asList("Host *", "StrictHostKeyChecking no"));
     157             : 
     158          13 :       JGitKeyCache keyCache = new JGitKeyCache();
     159          13 :       try (SshdSessionFactory factory =
     160             :           new SshdSessionFactory(keyCache, new DefaultProxyDataFactory())) {
     161          13 :         factory.setHomeDirectory(userhome);
     162          13 :         factory.setSshDirectory(sshDir);
     163             : 
     164          13 :         session = factory.getSession(uri, null, fs, TIMEOUT);
     165             : 
     166          13 :         session.addCloseListener(
     167             :             future -> {
     168             :               try {
     169          13 :                 MoreFiles.deleteRecursively(userhome.toPath(), ALLOW_INSECURE);
     170           0 :               } catch (IOException e) {
     171           0 :                 e.printStackTrace();
     172           0 :                 throw new RuntimeException("Failed to cleanup userhome", e);
     173          13 :               }
     174          13 :             });
     175             :       }
     176             :     }
     177          13 :     return session;
     178             :   }
     179             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750