Featured image of post 使用expect脚本一键登录跳板机

使用expect脚本一键登录跳板机

非常方便的一键登录跳板机教程

背景

为了安全,多数公司会采用跳板机的方式访问内网服务器,登录需要输入AD密码和谷歌验证码,非常繁琐,这里提供一个基于 expect 脚本的方法,实现一键登录跳板机

注:笔者在 MacOSUbuntu20.04 环境下测试ok,其他环境大同小异,按需修改即可。

方法

此方式思路是利用 expect 脚本交互式输入预设的AD密码,输入实时获取的谷歌验证码(使用 oathtoolpython authenticator 等工具)

自动获取谷歌验证码

所需依赖

安装以下工具,各平台安装方法请自行搜索:

  • expect
  • oathtool(推荐)
  • 或者使用 python
    • python
    • pip
    • authenticator

方法一(推荐):oathtool

使用指南:Ubuntu man1/oathtool.1.html

安装

sudo apt install -y oathtool

使用

  1. 获取谷歌验证码的 SECRET 字符串
  2. 执行 oathtool --totp -b ${SECRET} 即可获取到当前时间的动态码

方法二:python authenticator

安装

pip install authenticator 若不成功,请先升级 pip 到最新版本

配置

  1. 执行 authenticator add $user(AD账号无邮箱后缀)
  2. 提示 Enter passphrase 这里输入2次AD密码
  3. 提示 Enter shared secret 需要把谷歌验证码的 SECRET 字符串输入到这里
  4. 到这里就配置ok了,执行 authenticator generate ,输入AD密码,看到谷歌验证码正常输出就是成功了

能够自动获取谷歌验证码之后就可以使用expect脚本来实现自动登录了

注意点

多数公司的AD密码可能需要每隔一段时间更新一次,在更新AD密码后,使用 authenticator 仍然需要输入旧密码,所以下述脚本内相应的做了一下兼容

expect脚本

功能

1
2
3
4
5
6
7
$ jump --help
Usage: jump [--user <username>] [--auth <auth_type>] [--type <link_type>] [--zone <jump_zone>] [--help]
--user <username>    Specify the username to use (default: $DEFAULT_USER)
--auth <auth_type>   Specify the auth type, 'py' for using authenticator (default: oathtool)
--type <link_type>   Specify the link type, 'ssh' or 'sftp' (default: ssh)
--zone <jump_zone>   Specify the jump server zone, 'bj' or 'sh' (default: sh)
--help               Display this help message

使用

  1. users 相关的几个变量填写好
    • PASSWORD AD密码
    • PASSWORD_GEN 生成密码
    • SECRET 密钥
  2. 假设保存文件为 ~/jump.exp
  3. 执行脚本即可自动化登录到跳板机 expect ~/jump.exp
    1. 也可以给脚本加上可执行权限 chmod a+x ~/jump.exp
    2. 然后直接执行即可 ~/jump.exp

代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#!/usr/bin/expect

# 设置超时
set timeout 1000
set JUMP_ZONE sh
set LINK_TYPE ssh
set DEFAULT_USER userAAA

# 扁平化关联数组,使用键名结合用户名
array set users {
    userBBB_PASSWORD "xxxxxxxxxx"
    userBBB_PASSWORD_GEN "xxxxxxxxxx"
    userBBB_SECRET "xxxxxxxxxx"
    userAAA_PASSWORD "xxxxxxxxxx"
    userAAA_PASSWORD_GEN "xxxxxxxxxx"
    userAAA_SECRET "xxxxxxxxxx"
}

# 初始化变量
set USERNAME $DEFAULT_USER
set use_authenticator 0

# 打印 usage 信息
proc print_usage {} {
    puts {Usage: jump [--user <username>] [--auth <auth_type>] [--type <link_type>] [--help]}
    puts {--user <username>    Specify the username to use (default: $DEFAULT_USER)}
    puts {--auth <auth_type>   Specify the auth type, 'py' for using authenticator (default: oathtool)}
    puts {--type <link_type>   Specify the link type, 'ssh' or 'sftp' (default: ssh)}
    puts {--zone <jump_zone>   Specify the jump server zone, 'bj' or 'sh' (default: sh)}
    puts {--help               Display this help message}
    exit 0
}

# 解析命令行参数
for {set i 0} {$i < [llength $argv]} {incr i} {
    if {[lindex $argv $i] eq "--user"} {
        if {[expr {$i + 1}] < [llength $argv]} {
            set USERNAME [lindex $argv [expr $i+1]]
        } else {
            puts "Error: --user requires an argument"
            exit 1
        }
    } elseif {[lindex $argv $i] eq "--auth"} {
        if {[expr {$i + 1}] < [llength $argv]} {
            if {[lindex $argv [expr $i+1]] eq "py"} {
                set use_authenticator 1
            }
        } else {
            puts "Error: --auth requires an argument"
            exit 1
        }
    } elseif {[lindex $argv $i] eq "--type"} {
        if {[expr {$i + 1}] < [llength $argv]} {
            set LINK_TYPE [lindex $argv [expr $i+1]]
            if {$LINK_TYPE ne "ssh" && $LINK_TYPE ne "sftp"} {
                puts "Error: Invalid link type. Use 'ssh' or 'sftp'."
                exit 1
            }
        } else {
            puts "Error: --type requires an argument"
            exit 1
        }
    } elseif {[lindex $argv $i] eq "--zone"} {
        if {[expr {$i + 1}] < [llength $argv]} {
            set JUMP_ZONE [lindex $argv [expr $i+1]]
            if {$JUMP_ZONE ne "sh" && $JUMP_ZONE ne "bj"} {
                puts "Error: Invalid jump zone. Use 'sh' or 'bj'."
                exit 1
            }
        } else {
            puts "Error: --zone requires an argument"
            exit 1
        }
    } elseif {[lindex $argv $i] eq "--help"} {
        print_usage
    }
}

# 检查用户名是否存在
if {[info exists users(${USERNAME}_PASSWORD)]} {
    set PASSWORD $users(${USERNAME}_PASSWORD)
    set PASSWORD_GEN $users(${USERNAME}_PASSWORD_GEN)
    set SECRET $users(${USERNAME}_SECRET)
} else {
    puts "Error: User $USERNAME not found"
    exit 1
}

# 获取验证代码
if {$use_authenticator} {
    # 使用 authenticator generate 生成验证码
    spawn authenticator generate
    expect {
        "passphrase:" { send "$PASSWORD_GEN\r" }
        timeout { puts "Error: Authenticator timeout"; exit 1 }
    }
    expect {
        "seconds" {
            set found [regexp {([0-9]{6})} $expect_out(buffer) match verify_code]
            if {$found == 1} {
                send \x03
            } else {
                puts "Error: Invalid verification code"
                exit 1
            }
        }
        timeout { puts "Error: Failed to retrieve verification code"; exit 1 }
    }
} else {
    # 使用 oathtool 生成验证码
    set verify_code [exec oathtool --totp -b $SECRET]
    if {$verify_code eq ""} {
        puts "Error: Failed to generate verification code"
        exit 1
    }
}

# 启动连接并处理交互
spawn ${LINK_TYPE} ${USERNAME}@jump-${JUMP_ZONE}.sensetime.com
expect {
    "yes/no" { send "yes\r"; exp_continue }
    "Verification code:" { send "$verify_code\r"; exp_continue }
    "assword:" { send "$PASSWORD\r"; exp_continue }
    "MFA auth" { send "$verify_code\r"; exp_continue }
    "Opt>" { }
    "sftp>" {
        send "ls\r"
        expect "sftp>"
    }
    timeout { puts "Error: Connection timeout"; exit 1 }
    eof { puts "Error: Unexpected EOF"; exit 1 }
}

# 进入交互模式
interact
使用 Hugo 构建
主题 StackJimmy 设计