通过bash连接CISCO Anyconnect VPN

16

如标题所述,我正在尝试通过bash连接VPN。以下脚本似乎最接近我要寻找的答案:

#!/bin/bash
/opt/cisco/anyconnect/bin/vpn -s << EOF
connect https://your.cisco.vpn.hostname/vpn_name
here_goes_your_username
here_goes_your_passwordy
EOF

当我运行这个命令时,VPN会启动,但是没有错误消息并且无法连接。这似乎是由于参数“-s”引起的。如果我删除此参数,VPN将会启动,但是用户名、密码等命令将不会被输入。根据我的阅读,选项“-s”将允许传递用户名/密码。


如果您正在尝试编写交互式程序脚本,您可能需要了解expect(1) - Carl Norum
谢谢!使用expect解决了问题。如果其他人有类似的问题,这是我使用的教程(我甚至不知道expect,所以它非常基础):http://www.thegeekstuff.com/2010/10/expect-examples/ - Brayden Hancock
这也是您可以轻松使用OpenConnect而不是使用官方的AnyConnect客户端来完成的事情。 - Charles Duffy
5个回答

19

我必须下载所需的软件包(yum install expect)。这是我用来自动连接 VPN 的代码。

#!/usr/bin/expect

eval spawn /opt/cisco/anyconnect/bin/vpn connect vpn.domain.com

expect "Username: " { send "username\r" }
expect "Password: " { send "password\r" }

set timeout 60
expect "VPN>"

真的很容易!:D


13
尽管expect可以更加简洁,但并非必需。假设/opt/cisco/anyconnect/bin/vpnagentd已自动运行:
要连接:
printf "USERNAME\nPASSWORD\ny" | /opt/cisco/anyconnect/bin/vpn -s connect HOST

替换USERNAMEPASSWORDHOST。结尾处的\ny是为了接受登录横幅—这是针对我的主机,因此您可能不需要它。

我明白这种方法存在明显的安全问题;这只是为了说明目的。

要获取状态

/opt/cisco/anyconnect/bin/vpn state

断开连接:

/opt/cisco/anyconnect/bin/vpn disconnect

这是使用AnyConnect v3.1.05160进行测试的。


参考:在我的 VPN CLI 手册选项中,"-s 从标准输入读取命令。例如:vpncli.exe -s < MyScript.txt" - Weishi Z
我很好奇这里的管道符“|”是如何工作的。为什么“vpn connect HOST”会从管道中读取密码?我尝试在ssh中使用相同的方法,但它没有起作用。“printf“password”| ssh user@HOST”会出现错误“伪终端将不会分配,因为标准输入不是终端”。 - Weishi Z
1
后来我发现,对于SSH来说,它的安全特性防止它从不安全的来源(如文件或println)读取密码。 - Weishi Z

3
如果你正在使用macOS操作系统,我建议你将VPN密码保存在钥匙串中,并从Anyconnect脚本中请求它。
例如,假设我想使用帐户foo和密码bar连接到foo.bar.com
1. 使用名称fookey在Keychain(登录而不是iCloud)中保存foobar的组合。 2. 运行以下bash脚本以连接。
/opt/cisco/anyconnect/bin/vpn connect foo.bar.com -s << EOM
0    # foo.bar.com doesn't require two factor authorization
foo  # vpn account
$(sudo security find-generic-password -ws fookey)  # vpn password
EOM

使用这种方法,您不需要每次输入vpn密码,也不会将您的密码写入没有加密的文件中。
如果您不熟悉bash脚本,请阅读以下说明:
  • /opt/cisco/anyconnect/bin/vpn connect -s 进入非交互模式。
  • << EOM ... EOM 被称为here-docs,它使用一个字符串替换文件。通过将每个响应写成新行,可用于编写交互式CLI的脚本。
  • security 是一个很好的工具,可以从命令行访问您的Keychain。

1
//file = @"C:\Program Files (x86)\Cisco\Cisco AnyConnect Secure Mobility Client\vpncli.exe"
var file = vpnInfo.ExecutablePath;
var host = vpnInfo.Host;
var profile = vpnInfo.ProfileName;
var user = vpnInfo.User;
var pass = vpnInfo.Password;
var confirm = "y";

var proc = new Process
{
    StartInfo = new ProcessStartInfo
    {
        FileName = file,
        Arguments = string.Format("-s"),
        UseShellExecute = false,
        RedirectStandardInput = true,
        RedirectStandardOutput = true,
        RedirectStandardError = true,
    }
};

proc.OutputDataReceived += (s, a) => stdOut.AppendLine(a.Data);
proc.ErrorDataReceived += (s, a) => stdOut.AppendLine(a.Data);

//make sure it is not running, otherwise connection will fail
var procFilter = new HashSet<string>() { "vpnui", "vpncli" };
var existingProcs = Process.GetProcesses().Where(p => procFilter.Contains(p.ProcessName));
if (existingProcs.Any())
{
    foreach (var p in existingProcs)
    {
        p.Kill();
    }
}

proc.Start();
proc.BeginOutputReadLine();

//simulate profile file
var simProfile = string.Format("{1}{0}{2}{0}{3}{0}{4}{0}{5}{0}"
    , Environment.NewLine
    , string.Format("connect {0}", host)
    , profile
    , user
    , pass
    , confirm
    );

proc.StandardInput.Write(simProfile);
proc.StandardInput.Flush();

//todo: these should be configurable values
var waitTime = 500; //in ms
var maxWait = 10;

var count = 0;
var output = stdOut.ToString();
while (!output.Contains("state: Connected"))
{
    if (count > maxWait)
        throw new Exception("Unable to connect to VPN.");

    Thread.Sleep(waitTime);
    output = stdOut.ToString();
    count++;
}
stdOut.Append("VPN connection established! ...");

1
建立在Brayden Hancock的回答基础上,我建立了一个解决方案,该方案从macOS Keychain读取密码。 首先,我通过Keychain Access应用程序添加了一个新的密码项,其中account字段设置为mycompany-vpn。脚本的第一部分从钥匙串中读取该项,并使用ruby片段提取密码,expect脚本部分完成其余操作。
#!/usr/bin/env bash
get_pw () {
    security 2>&1 >/dev/null find-generic-password -ga mycompany-vpn \
    |ruby -e 'print $1 if STDIN.gets =~ /^password: "(.*)"$/'
}

USER=username
ADDR=vpn.company.com
PASSWORD=$(get_pw)

/usr/bin/expect -f - <<EOD
set timeout 10

spawn /opt/cisco/anyconnect/bin/vpn connect $ADDR
expect "\r\nUsername:*" {send -- "$USER\r"}
expect "Password: " {send -- "$PASSWORD\r"}
expect "Connected"
EOD


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接