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 : }