Salt-API之python调用
salt-api 基本使用
目前salt API 支持的web模块如下:
- CherryPy
- Tornado
- WSGI
1.安装salt-api
salt 使用 CherryPy来实现restful的api,提供外部调用
yum install -y salt-api
2.添加用户
salt-api 使用eauth 验证系统(使用api所在机器的账户进行验证),最好单独为salt-api添加账号:
#创建系统账户
useradd -M saltapi
passwd saltapi
3.配置salt-api
安全方便的考虑,官方建议使用https进行加密通信(如果使用http协议,忽略这部分。在配置文件上加上disable_ssl: True)。这时候需要生成自签名的证书(如果你用通用证书,可以直接使用)
3.1 生成自签名证书(用于ssl)
创建存放key的目录
mkdir -p /opt/ssl/private
简单方法:
#此部分生成的key和cert证书不能使用-
<!--生成key-->
<!--openssl genrsa -out /opt/ssl/private/key.pem 4096 -->
<!--生成证书-->
<!--openssl req -new -x509 -key /opt/ssl/private/key.pem -out /opt/ssl/private/cert.pem -days 1826-->
<!--查看路径下生成的证书:-->
<!--[root@master private]# ll /opt/ssl/private-->
<!--total 8-->
<!---rw-r--r-- 1 root root 1911 Jun 22 20:56 cert.pem-->
<!---rw-r--r-- 1 root root 3243 Jun 22 20:55 key.pem-->
推荐方法:
#1. 通过ssl生成私钥,不要担心密码问题,现在先输入一个-稍后会取消这个密码
[root@master private]# openssl genrsa -des3 -out server.key 2048
Generating RSA private key, 2048 bit long modulus
.......................................................+++
.....................+++
e is 65537 (0x10001)
Enter pass phrase for server.key:
Verifying - Enter pass phrase for server.key:
#2. 创建一个证书签名请求(CSR)它会询问CA签发证书时关注的几个重要问题,在内部网络中,这些的重要性就低了**
[root@master private]# openssl req -new -key server.key -out server.csr
Enter pass phrase for server.key:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:BeiJing
Locality Name (eg, city) [Default City]:BeiJing
Organization Name (eg, company) [Default Company Ltd]:TEST
Organizational Unit Name (eg, section) []:test
Common Name (eg, your name or your server's hostname) []:test@example.com
Email Address []:test@qq.com
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
#3. 接下来,我们来取消之前私钥中使用的密码。
[root@master private]# openssl rsa -in server.key.org -out server.key
Enter pass phrase for server.key.org:
writing RSA key
#4. 最后,我们创建自签名的证书
[root@master private]# openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
Signature ok
subject=/C=CN/ST=BeiJing/L=BeiJing/O=TEST/OU=test/CN=test@example.com/emailAddress=test@qq.com
Getting Private key
#5. 至此,我们有如下4个文件:
[root@master private]# tree
.
├── server.crt
├── server.csr
├── server.key
└── server.key.org
#6. 接下来将server.crt文件复制到ssl_crt选项指定的路径,server.key 文件复制到ssl_key选项指定的路径
3.2 配置文件
建议为salt-api单独创建配置文件,/etc/salt/master默认会导入master.d/*.conf
touch /etc/salt/master.d/salt-api.conf 添加内容:
#关于用户认证部分
external_auth:
pam:
#指定用户
saltapi:
#指定用户能使用哪些salt模块 例: - test.*
#用户能够访问的runner模块
#用户能够访问的wheel模块
- .*
- '@runnner'
- '@wheel'
rest_cherrypy:
port: 9000
swsl_crt: /opt/ssl/private/cert.pem
ssl_key: /opt/ssl/private/key.pem
#进行重启salt-master服务器
service salt-master restart
3.3调试
测试用户是否登录成功
salt -a pam \* test.ping
查看saltapi用户的错误登录次数
pam_tally2 -u saltapi
清空saltapi用户的错误登录次数
pam_tally2 -r -u saltapi
4 Salt-API初体验
因为我们是第一次使用salt-api ,为了更好的了解它,我们把debug设置为True,
在master端启动Salt-API
#命令salt-api启动
[root@master ~]# salt-api
[23/Jun/2017:15:18:38] ENGINE Listening for SIGHUP.
[23/Jun/2017:15:18:38] ENGINE Listening for SIGTERM.
[23/Jun/2017:15:18:38] ENGINE Listening for SIGUSR1.
[23/Jun/2017:15:18:38] ENGINE Bus STARTING
CherryPy Checker:
'log_file' is obsolete. Use 'log.error_file' instead.
section: [saltopts]
[23/Jun/2017:15:18:38] ENGINE Started monitor thread '_TimeoutMonitor'.
[23/Jun/2017:15:18:38] ENGINE Started monitor thread 'Autoreloader'.
[23/Jun/2017:15:18:38] ENGINE Serving on 0.0.0.0:9000
[23/Jun/2017:15:18:38] ENGINE Bus STARTED
文章找个推荐写法:
curl -si https://127.0.0.1:9000/login -H 'Accept: application/json' -d username='saltapi' -d password='123456' -d eauth='pam'
但是在使用中发现会提示
SSL routines:SSL3_READ_BYTES:sslv3 alert certificate unknown
#SSL例程:SSL3_READ_BYTES:sslv3警告证书未知
自己百度解决:
curl --sslv3 -k -si https://127.0.0.1:9000/login -H 'Accept: application/json' -d username='saltapi' -d password='123456' -d eauth='pam'
参数解释:
--sslv3 指定sslv3版本
-k 忽略证书获取https内容
-s 指定使用静默(silent)方式
-i 指定SaltAPI收到服务器返回的结果同时显示HTTP Header。
-H 指定一个特定的Header给远端服务器,当SaltAPI 需要发送appliton-tion/json Header时。会以我们希望的JSON格式返回结果
-d 想远端服务器发送POST请求,以key=value的格式发送 ,注意key=v时,必须紧挨=号两边
获取到的返回结果:
HTTP/1.1 200 OK
Content-Length: 200
Access-Control-Expose-Headers: GET, POST
Vary: Accept-Encoding
Server: CherryPy/3.2.2
Allow: GET, HEAD, POST
Access-Control-Allow-Credentials: true
Date: Fri, 23 Jun 2017 07:27:47 GMT
Access-Control-Allow-Origin: *
X-Auth-Token: 8267c29f55a2b3b95a35de32ec30de353937c19c
Content-Type: application/json
Set-Cookie: session_id=8267c29f55a2b3b95a35de32ec30de353937c19c; expires=Fri, 23 Jun 2017 17:27:47 GMT; Path=/
{"return": [{"perms": [".*", "@runnner", "@wheel"], "start": 1498202867.8593781, "token": "8267c29f55a2b3b95a35de32ec30de353937c19c", "expire": 1498246067.8593791, "user": "saltapi", "eauth": "pam"}]}
在这个结果中,我们获取到了token,在之后的请求中我们会使用到他。
"token": "8267c29f55a2b3b95a35de32ec30de353937c19c"
我们在获取到token后使用下面的命令来进行操作
curl -k -s https://127.0.0.1:9000/minions -H 'Accept: application/json' -H 'X-Auth-Token: 48590e5819f5bdd1abffed950647c7386f5bd2c5' -d client='local' -d tgt='*' -d fun='test.ping'
要格外注意两点
1. -k 忽略https证书
2. -d 后面的参数 = 号两边不能有空格
获取到的返回结果:
{"_links": {"jobs": [{"href": "/jobs/20170623155752802281"}]}, "return": [{"jid": "20170623155752802281", "minions": ["master"]}]}
为了获取返回的结果,我们需要运行另外一个包含任务ID的命令 ,该命令需要使用GET方法,
curl -k -s https://127.0.0.1:9000/jobs/20170623155752802281 -H 'Accept: application/json' -H 'X-Auth-Token: 48590e5819f5bdd1abffed950647c7386f5bd2c5'
关于salt-api部分python 调用 pycurl https
# -*- coding: utf-8 -*-
#__Author__:"liuhao"
#python2.7
import pycurl
from io import BytesIO
import json
class PyCurl(object):
def __init__(self, url, **kwargs):
# 传入url地址
self.url = url
# 取出header相关信息
self.header = kwargs.get("header", None)
# 创建一个curl对象
self.curl = pycurl.Curl()
# setopt 来设置一些请求选项
# 指定请求的URL
self.curl.setopt(self.curl.URL, self.url)
# 设置代理浏览器
self.curl.setopt(self.curl.HEADER, False)
# 设置请求方式
self.curl.setopt(self.curl.POST, True)
# 设置https方式
self.curl.setopt(pycurl.SSL_VERIFYPEER, 0)
self.curl.setopt(pycurl.SSL_VERIFYHOST, 0)
# 判断header是否存在
if self.header:
# 设置模拟浏览器
self.curl.setopt(self.curl.HTTPHEADER, self.header)
def request(self, data=None, timeout=None):
# 判断对象类型 是否为 str
if isinstance(data, str):
#将数据提交
self.curl.setopt(pycurl.POSTFIELDS, data)
header_buf = BytesIO()
body_buf = BytesIO()
# 强制获取新的连接,即替代缓存中的连接
self.curl.setopt(self.curl.FRESH_CONNECT, True)
# 完成交互后强制断开连接,不重用
self.curl.setopt(self.curl.FORBID_REUSE, True)
if str(timeout).isdigit() and timeout > 0:
# 设置timeout超时时间
self.curl.setopt(self.curl.TIMEOUT, timeout)
# 将返回的HTTP HEADER定向到回调函数header_buf
self.curl.setopt(self.curl.HEADERFUNCTION, header_buf.write)
# 将返回的内容定向到回调函数body_buf
self.curl.setopt(self.curl.WRITEFUNCTION, body_buf.write)
try:
# 服务器返回信息
self.curl.perform()
except pycurl.error:
return False
# 状态码
http_code = self.curl.getinfo(self.curl.HTTP_CODE)
# 关闭连接
self.curl.close()
# 返回状态码 header body
return {"http_code": http_code, "header": header_buf.getvalue(), "body": body_buf.getvalue(), "url": self.url}
class SaltApi(object):
def __init__(self, url,username,password,**kwargs):
# 设置超时时间
self.timeout = kwargs.get("timeout", 300)
# 设置头信息
self.header = kwargs.get("header", ["Content-Type:application/json"])
# 获取url
self.__url = url
# 获取
self.__username = username
self.__password = password
# token id 获取
def token_id(self):
obj = {'eauth': 'pam', 'username': self.__username, 'password': self.__password}
result = self.post(prefix="/login",**obj)
if result:
try:
self.__token_id = result['return'][0]['token']
except KeyError:
raise KeyError
return self.__token_id
def post(self, prefix="/",token=None,**data):
# url拼接
url = self.__url + prefix
print data
# 实例化
self.header.append(str(token))
curl = PyCurl(url, header=self.header)
# 发起请求
result = curl.request(data=json.dumps(data), timeout=self.timeout)
# 判断值
if not result:
return result
# 判断状态码是否等于200
if result["http_code"] != 200:
self.response = "response code %s".format(result["info"]["http_code"])
return self.response
result = json.loads(result["body"].decode())
# 判断是否有error
if "error" in result and result["error"]:
self.response = "%s(%s)" % (result["error"]["data"], result["error"]["code"])
return self.response
#返回正确的数据
return result
def all_key(self):
'''
获取所有的minion_key
'''
token = 'X-Auth-Token:%s'%self.token_id()
obj = {'client': 'wheel', 'fun': 'key.list_all'}
content = self.post(token=token,**obj)
# 取出认证已经通过的
minions = content['return'][0]['data']['return']['minions']
#print('已认证',minions)
# 取出未通过认证的
minions_pre = content['return'][0]['data']['return']['minions_pre']
# print('未认证',minions_pre)
return minions,minions_pre
def accept_key(self,node_name):
'''
如果你想认证某个主机 那么调用此方法
'''
token = 'X-Auth-Token:%s' % self.token_id()
obj = {'client': 'wheel', 'fun': 'key.accept','match':node_name}
content = self.post(token=token,**obj)
print content
ret = content['return'][0]['data']['success']
return ret
# 删除认证方法
def delete_key(self, node_name):
obj = {'client': 'wheel', 'fun': 'key.delete', 'match': node_name}
token = 'X-Auth-Token:%s' % self.token_id()
content = self.post(token=token, **obj)
ret = content['return'][0]['data']['success']
return ret
# 针对主机远程执行模块
def host_remote_func(self, tgt, fun):
''' tgt是主机 fun是模块
写上模块名 返回 可以用来调用基本的资产
例如 curl -k https://ip地址:8080/ \
> -H "Accept: application/x-yaml" \
> -H "X-Auth-Token:b50e90485615309de0d83132cece2906f6193e43" \
> -d client='local' \
> -d tgt='*' \
> -d fun='test.ping' 要执行的模块
return:
- iZ28r91y66hZ: true
node2.minion: true
'''
obj = {'client': 'local', 'tgt': tgt, 'fun': fun}
token = 'X-Auth-Token:%s' % self.token_id()
content = self.post(token=token, **obj)
ret = content['return'][0]
return ret
def group_remote_func(self,tgt,fun):
obj = {'client': 'local', 'tgt': tgt, 'fun': fun,'expr_form': 'nodegroup'}
token = 'X-Auth-Token:%s' % self.token_id()
content = self.post(token=token, **obj)
print content
ret = content['return'][0]
return ret
def host_remote_execution_module(self,tgt,fun,arg):
'执行fun 传入传入参数arg '
obj = {'client': 'local', 'tgt': tgt, 'fun': fun,'arg': arg}
token = 'X-Auth-Token:%s' % self.token_id()
content = self.post(token=token, **obj)
ret = content['return'][0]
return ret
#print(salt_aa.host_remote_execution_module('*', 'cmd.run', 'ifconfig'))
# 基于分组来执行
def group_remote_execution_module(self, tgt, fun, arg):
'''
根据分组来执行
tgt =
'''
obj = {'client': 'local', 'tgt': tgt, 'fun': fun, 'arg': arg, 'expr_form': 'nodegroup'}
token = 'X-Auth-Token:%s' % self.token_id()
content = self.post(token=token, **obj)
jid = content['return'][0]
return jid
def host_sls(self, tgt, arg):
'''主机进行sls'''
obj = {'client': 'local', 'tgt': tgt, 'fun': 'state.sls', 'arg': arg}
token = 'X-Auth-Token:%s' % self.token_id()
content = self.post(token=token, **obj)
return content
def group_sls(self, tgt, arg):
''' 分组进行sls '''
obj = {'client': 'local', 'tgt': tgt, 'fun': 'state.sls', 'arg': arg, 'expr_form': 'nodegroup'}
token = 'X-Auth-Token:%s' % self.token_id()
content = self.post(token=token, **obj)
jid = content['return'][0]['jid']
return jid
def host_sls_async(self, tgt, arg):
'''主机异步sls '''
obj = {'client': 'local_async', 'tgt': tgt, 'fun': 'state.sls', 'arg': arg}
token = 'X-Auth-Token:%s' % self.token_id()
content = self.post(token=token, **obj)
jid = content['return'][0]['jid']
return jid
def group_sls_async(self, tgt, arg):
'''分组异步sls '''
obj = {'client': 'local_async', 'tgt': tgt, 'fun': 'state.sls', 'arg': arg, 'expr_form': 'nodegroup'}
token = 'X-Auth-Token:%s' % self.token_id()
content = self.post(token=token, **obj)
jid = content['return'][0]['jid']
return jid
def server_group_pillar(self, tgt, arg, **kwargs):
'''分组进行sls and pillar'''
obj = {'client': 'local', 'tgt': tgt, 'fun': 'state.sls', 'arg': arg, 'expr_form': 'nodegroup',
'kwarg': kwargs}
token = 'X-Auth-Token:%s' % self.token_id()
content = self.post(token=token, **obj)
jid = content['return'][0]
print jid
def server_hosts_pillar(self, tgt, arg,**kwargs):
'''针对主机执行sls and pillar '''
obj = {"client": "local", "tgt": tgt, "fun": "state.sls", "arg": arg,"kwarg":kwargs}
token = 'X-Auth-Token:%s' % self.token_id()
content = self.post(token=token, **obj)
jid = content['return'][0]
return jid
def jobs_all_list(self):
'''打印所有jid缓存'''
token = 'X-Auth-Token:%s' % self.token_id()
obj = {"client": "runner", "fun": "jobs.list_jobs"}
content = self.post(token=token, **obj)
print content
def jobs_jid_status(self, jid):
'''查看jid运行状态'''
token = 'X-Auth-Token:%s' % self.token_id()
obj = {"client": "runner", "fun": "jobs.lookup_jid", "jid": jid}
content = self.post(token=token, **obj)
print content
return content
if __name__ == '__main__':
salt_aa=SaltApi('https://172.16.40.149:9000','saltapi','123456')
# print(salt_aa.host_remote_func('*','cmd.run "ipconfig"'))
# print(salt_aa.host_remote_execution_module('*
print salt_aa.group_remote_execution_module('LHMJ','cmd.run','ifconfig')