通过ssh.net对每个命令进行第二因素验证

时间:2018-07-20 20:11:32

标签: c# two-factor-authentication ssh.net

我正在使用SSH.NET和以下代码连接到Unix服务器

ssh = new SshClient("myserver.univ.edu", Username, Password);
ssh.Connect();

连接通过,似乎没有异常。服务器设置为需要两因素身份验证,但是我的电话上没有提示(使用该物理身份验证器/ OTP设备)。但是连接似乎还可以。

然后,我用以下代码发出命令:

SshCommand NewLookup = ssh.CreateCommand("newlookup " + IpNameOrAddress.Text))
LogText.Text += NewLookup.Execute().Replace("\n", Environment.NewLine);

然后然后,我将其推送至手机(第二个要素验证请求)。一旦我通过电话接受了验证请求,然后命令就会正常执行。没关系,除了...

问题

对于每个后续命令,我都会推送到手机上,因此,如果我想使用该连接来运行多个命令,则必须坐在我的手机上,为每个命令单击“接受”。因此,如何避免在每条命令上都加一个推键?

1 个答案:

答案 0 :(得分:5)

为了在SSH.NET的单个会话中发送多个命令,您可能需要使用ShellStream。这应将您的2FA批准减少为仅向主持人开放会话。这对于不支持命令通道但确实支持SSH远程终端(例如您可以使用腻子)的设备(例如HPE交换机),以及命令更改(shell)环境等情况也很有用。您需要在工作期间保持会话打开。否则,SSH命令通道是处理此问题的预期方法(也是更好的方法)。

您可以在NuDoc - SSH.NET中找到更多的SSH.NET文档,并且SSH.Net项目的GitHub版本包括一个Windows Help file

这是我写的一些代码,用于将ShellStream包装在另一个对象中,该对象保留StreamReaderStreamWriter并处理从(n HP)开关读取输入并过滤转义序列,以及阅读下一个提示:

public static class SshClientExt {
    public static ExtShellStream CreateExtShellStream(this SshClient sc, string termName, uint rows, uint cols, uint width, uint height, int bufSize) =>
        new ExtShellStream(sc.CreateShellStream(termName, rows, cols, width, height, bufSize));
}

public class ExtShellStream : IDisposable {
    static Regex reEscVT100 = new Regex("\x1B\\[[^A-Z]+[A-Z]", RegexOptions.Compiled);
    static TimeSpan ReadTimeout = new TimeSpan(0, 0, 10);

    ShellStream ssh;
    StreamReader sr;
    StreamWriter sw;

    public ExtShellStream(ShellStream anSSH) {
        ssh = anSSH;
        sr = new StreamReader(ssh);
        sw = new StreamWriter(ssh);
    }

    public List<string> ReadLines() {
        // try to read all in
        long prev;
        do {
            prev = ssh.Length;
            Thread.Sleep(250);
        } while (ssh.Length != prev);

        "-".Repeat(40).Dump();
        var ans = new List<string>();

        while (true) {
            var line = sr.ReadLine();
            if (line != null) {
                line = line.Remove(reEscVT100).TrimEnd();
                $@"""{line}""".Dump();
                if (line.EndsWith("#")) // stop when prompt appears
                    break;
                else
                    ans.Add(line);
            }
            else
                Thread.Sleep(500);
        }

        return ans;
    }

    public void DumpLines() => ReadLines();

    public List<string> DoCommand(string cmd) {
        sw.Write(cmd);
        sw.Write("\r");
        sw.Flush();
        while (!ssh.DataAvailable)
            Thread.Sleep(500);
        return ReadLines().SkipWhile(l => l == cmd).ToList();
    }

    #region IDisposable Support
    private bool disposedValue = false; // To detect redundant calls

    protected virtual void Dispose(bool disposing) {
        if (!disposedValue) {
            if (disposing) {
                // prevent double dispose
                // don't dispose of sr or sw: only disposable resource is ssh
                ssh.Dispose();
            }

            disposedValue = true;
        }
    }

    // This code added to correctly implement the disposable pattern.
    public void Dispose() {
        // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
        Dispose(true);
    }
    #endregion

}

这是一个示例函数,该函数与SSH.Net一起使用该代码从交换机检索屏幕上的配置信息副本:

public static void RetrieveConfigFiles(IDFStack idf) {
    using (var sshClient = new SshClient(idf.IPAddress, username, password)) {
        sshClient.Connect();

        using (var ssh = sshClient.CreateExtShellStream("dumb", 120, 80, 0, 0, 200000)) {
            ssh.DumpLines();
            ssh.DoCommand("no page");

            File.WriteAllLines(idf.ConfigPath, ssh.DoCommand("show running-config structured"));
            File.WriteAllLines(idf.StatusPath, ssh.DoCommand("show interfaces brief"));
            File.WriteAllLines(idf.LLDPPath, ssh.DoCommand("show lldp info remote-device detail"));
        }

        sshClient.Disconnect();
    }
}