Python调包侠之阿里云堡垒机添加主机

  要不是为了过等保,🐶 才用马云家的堡垒机。天下苦其久矣。

前言

  为了过等保,不得不将部分服务器的登陆方式从先前自建的跳板机换成了阿里云的堡垒机,登陆堡垒机需要使用 RAM 账户,同时要求 RAM 账户开启两步验证(即动态验证码)。 这不仅给登陆服务器造成了极大的不便,导入几百台服务器资产也是个头疼的事情,导入服务器、导入服务器用户与密钥、导入主机组、主机组授权用户组,几轮下来,心态有点容易受不了。(用过阿里云的同学应该都体会过一个控制台卡得浏览器崩溃的情况),好在上周终于放出了 V3.2.17 版本,从这个版本开始,我们终于能够通过接口去完成一些事情了,姑且用接口与 salt 来减轻一点堡垒机迁移的工作量吧。
  当然,这个版本当前(2021-03-19)还无法直接升级,需要提交工单或是在高级企业服务群中@技术支持帮忙手动升级。 同时阿里云文档中心的堡垒机接口文档暂时还没有更新,待放出的接口文档根据其法律声明,暂时也不能在此放出(可通过工单向阿里云申请索要.)ps: 看看后面的代码引用的包不也就都有了?(逃。

需求实现

  时间仓促,能用就行的原则,实现了通过主机名与 IP,导入主机至堡垒机,并导入对应的用户与用户密钥,加入到对应的主机组中。(这个主机组应按需到云盾堡垒机中配置权限与用户)。

主机端

  服务器需要有供堡垒机登陆的用户,可以利用 SaltStack 或 Ansible 等工具向服务器创建堡垒机登陆所用的系统用户及密钥。 这里顺带贴一下我的 salt state 文件:

创建服务器系统用户 create_hac_users.sls

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
---
# 创建 aliyun 用户,并添加到 wheel 组
aliyun:
user.present:
- fullname: aliyun
- shell: /bin/bash
- home: /home/aliyun
- groups:
- wheel

# 添加云盾堡垒机网段白名单
# 这里注意! hosts.allow 这个配置文件不能使用类似sshd:192.168.0.0/24:allow这种格式,而是应该使用sshd:192.168.0.:allow 这种形式,已经踩过坑了。。。😂
allow-hac:
cmd.run:
- name: "if [ `cat /etc/hosts.allow | grep 'sshd:192.168.0.:allow' -q ; echo $?` -ne 0 ];then echo 'sshd:192.168.:allow' >> /etc/hosts.allow ;fi"

# 将公钥 aliyun_pub_key 传到 /home/aliyun/.ssh/authorized_keys
/home/aliyun/.ssh/authorized_keys:
file.managed:
- source: salt://aliyun_pub_key # 这个路径其实看你 salt master 的 /etc/salt/master 中 file_root 字段
- user: aliyun
- group: aliyun
- mode: 400
- makedirs: True

sudoer 权限配置,rectify_sudoers.sls:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
---
/etc/sudoers:
file.managed:
- source: salt://sudoers_files/sudoers # 这个路径其实看你 salt master 的 /etc/salt/master 中 file_root 字段
- user: root
- group: root
- mode: '400'

/etc/sudoers.d:
file.recurse:
- source: salt://sudoers_files/sudoers.d # 这个路径其实看你 salt master 的 /etc/salt/master 中 file_root 字段
- user: root
- group: root
- dir_mode: '0750'
- file_mode: '0640'
- include_empty: True

总配置 add.sls:

1
2
3
4
---
include:
- init.hac.rectify_sudoers
- init.hac.create_hac_users

在 salt master 的 /etc/salt/master 中 file_root 配置的路径下:

1
2
3
4
5
6
7
8
9
.
├── add.sls
├── create_hac_users.sls
├── rectify_sudoers.sls
├── aliyun_pub_key # 系统用户 'aliyun' 的公钥
└── sudoers_files
├── sudoers
└── sudoers.d
└── aliyun_hac

sudoers 内容配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Defaults !visiblepw
Defaults always_set_home
Defaults match_group_by_gid
Defaults always_query_group_plugin
Defaults env_reset,pwfeedback
Defaults env_keep = "COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS"
Defaults env_keep += "MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE"
Defaults env_keep += "LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES"
Defaults env_keep += "LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE"
Defaults env_keep += "LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY"

# 开启日志
Defaults logfile="/var/log/sudo.log"
Defaults log_host, log_year, logfile="/var/log/sudo.log"

Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin
Defaults passwd_tries=5

root ALL=(ALL) ALL
%wheel ALL=(ALL) NOPASSWD: ALL

# 配置文件独立目录
#includedir /etc/sudoers.d

other_user 内容配置如下:

1
2
3
4
5
6
7
8
9
10
# 命令 alias
Cmnd_Alias BASE=/usr/bin/free, /usr/bin/iostat, /usr/bin/top, /bin/ifconfig, /bin/netstat, /bin/tail, /bin/cat, /bin/less, /bin/netstat, /usr/sbin/ip, /bin/grep

Cmnd_Alias TEST=/usr/local/java, /usr/bin/node

Cmnd_Alias VIEWER=/usr/local/java

# 对用户组的 sudo 权限做配置
%tester ALL=(ALL) NOPASSWD : BASE, TEST
%viewer ALL=(ALL) NOPASSWD : BASE, VIEWER

这里之后,我们只需要通过 salt <hostname> state.sls add 即可完成 aliyun 用户的添加。

堡垒机端

  这里直接丢一下脚本吧:
add_to_hac.py:

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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#!/usr/bin/env python3
# coding=utf-8
# Alliot
# blog: www.iots.vip
import json
import base64
import sys
import subprocess
from libs.tools import async_run_command, sync_run_command


def async_run_command(cmd, cwd=None):
"""
运行命令, 异步,不阻塞
:param: cmd: 命令
:param: cwd: 目录
"""
# 非阻塞,不获取返回的内容
subprocess.Popen(
cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True,
cwd=cwd)


class HacManagement():
def __init__(self):
from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.request import CommonRequest

# 阿里云密钥
ID = ''
Secret = ''
# 堡垒机区域
region_id = 'cn-beijing'
# 堡垒机实例ID
hac_instance_id = "bastionhost-cn-xxx"
self.client = AcsClient(ID, Secret, region_id)
self.request = CommonRequest()
self.request.set_accept_format('json')
self.request.set_domain('yundun-bastionhost.aliyuncs.com')
self.request.set_method('POST')
self.request.set_protocol_type('https') # https | http
self.request.set_version('2019-12-09')
self.request.add_query_param('RegionID', region_id)
self.request.add_query_param('InstanceId', hac_instance_id)

def create_host(self, **kwargs):
"""
调用 CreateHost 接口在堡垒机中创建需要运维的主机。
:param: kwargs: HostNmae: 主机名、Source: 主机来源(Local、Ecs、 Rds)、OSType: 操作系统类型(
Linux、Windows)、ActiveAddressType: 地址类型(Private、Public)、HostPrivateAddress: 私网 IP 地址(当 address_type 为 Private
时必选)、host_public_address: 公网 IP 地址(当 address_type 为 Public 时必选)、Comment: 备注(可选)、SourceInstanceId:
指定新创建的ECS实例ID或专属集群主机ID(当 source 选择 Ecs 或 Rds 时,该参数为必填项)、InstanceRegionId: 指定新创建的ECS实例或专属集群主机所属区域ID当 source 选择
Ecs 或 Rds 时,该参数为必填项) :return: HostId: 创建的主机的 ID
"""
for i in kwargs.keys():
self.request.add_query_param(i, kwargs[i])
self.request.set_action_name('CreateHost')
response = self.client.do_action_with_exception(self.request)

# 调用 salt 添加用户到目标服务器系统
async_run_command(cmd=f'salt {kwargs["HostName"]} state.sls add')
return json.loads(response)['HostId']

def create_host_account(self, **kwargs):
"""
调用 CreateHostAccount 创建堡垒机中的主机账号
:param kwargs: HostId: 主机 ID、ProtocolName: 协议名(SSH、RDP)、HostAccountName: 用户名、Password: 密码、PrivateKey: 私钥、PassPhrase: 私钥口令
:return: HostAccountId: 主机账号ID
"""
self.request.set_action_name('CreateHostAccount')
for i in kwargs.keys():
self.request.add_query_param(i, kwargs[i])
response = self.client.do_action_with_exception(self.request)
return json.loads(response)['HostAccountId']

def modify_hosts_port(self, *args, protocol_name, port):
"""
调用 ModifyHostsPort 修改主机指定协议的端口
:param args: HostIds 主机ID(要求为列表)
:param protocol_name: 协议名(SSH、RDP)
:param port: 端口
:return:
"""
self.request.set_action_name('ModifyHostsPort')
self.request.add_query_param('HostIds', *args)
self.request.add_query_param('ProtocolName', protocol_name)
self.request.add_query_param('Port', port)
response = self.client.do_action_with_exception(self.request)
return str(response, encoding='utf-8')

def add_hosts_to_group(self, *args, host_group_id):
"""
调用 AddHostsToGroup 添加指定主机到指定主机组
:param args: HostIds 主机ID(要求为列表)
:param host_group_id: 主机组ID
:return:
"""
self.request.set_action_name('AddHostsToGroup')
self.request.add_query_param('HostIds', *args)
self.request.add_query_param('HostGroupId', host_group_id)
response = self.client.do_action_with_exception(self.request)
return str(response, encoding='utf-8')



def add_host_and_users(hostname, ip, comment):
aliyun_key = base64.b64encode('''-----BEGIN RSA PRIVATE KEY-----
# 这里填 系统用户 'aliyun' 的私钥
-----END RSA PRIVATE KEY-----'''.encode('utf-8'))

print('添加主机到堡垒机\n')
host_id = HacManagement().create_host(
HostName=hostname,
Source="Local",
OSType="Linux",
ActiveAddressType="Private",
HostPrivateAddress=ip,
Comment=comment)

print('添加主机账户aliyun\n')
HacManagement().create_host_account(
HostId=host_id,
ProtocolName='SSH',
HostAccountName='aliyun',
PrivateKey=aliyun_key
)

print('修改主机ssh端口为9999\n') # 根据服务器的 SSH 服务的端口修改
HacManagement().modify_hosts_port(
f'[{host_id}]',
protocol_name='SSH',
port=9999
)

print('添加主机到泛组\n') # 这里需要自己手动去“云盾堡垒机”控制台创建一个组(
HacManagement().add_hosts_to_group(
f'[{host_id}]',
host_group_id='1' # 创建组的时候,F12打开浏览器控制台抓一下请求,看看GroupId即可
)
print(f'主机已添加到堡垒机,主机ID为{host_id}\n')


if __name__ == '__main__':
add_host_and_users(sys.argv[1], sys.argv[2], sys.argv[3])

依赖为:

1
2
alibabacloud-yundun-bastionhost20191209==1.0.1
aliyun-python-sdk-core==2.13.30

执行:

1
python3 add_to_hac.py <主机名> <IP> <备注>

即可完成添加堡垒机操作。