一、OpenAPI概述
安恒云的OpenAPI(以下简称OpenAPI或API),在SaaS和私有部署上均可使用。API提供的方法和调用方式都是一致的,区别是client使用的AK和endpoint不同。
-
SaaS环境的endpoint是:https://openapi.anhengcloud.com,有需要通过OpenAPI集成安恒云功能的,请您发送邮件到 anhengcloud@dbappsecurity.com.cn,邮件内容请填写:“公司名称” 、“联系人”、“手机号码” 、 “团队名称”、“用户名称”、“API用途”,安恒云SaaS运营人员审核通过后,会将AK通过邮件发送给你。
-
私有部署的endpoint,可以在管理控制台的菜单:“系统设置/OpenAPI”中查看,点击启用API之后会生成所需的AK。
API以HTTP RESTful的方式提供,API调用的认证方式有两种:
-
使用AK生成签名。
-
使用Token。
API有一个可以在线调试的页面,即不用写代码就可以完成大部分的API调用。SAAS环境的API的文档页面在https://product.dbappsecurity.com.cn/api/openapi/,也是API调试页面。
私有部署可以通过管理控制台的菜单:“系统设置/OpenAPI” 查看。在线调试,只能使用Token的方式进行认证。
API的调用的payload参数和返回结果,都是基于JSON的,涉及到日期的字段,都是采用格式:"yyyy-MM-dd'T'HH:mm:ss'Z'",采用UTC时区。
二、OpenAPI调用
2.1、名词解释
ISV:Independent Software Vendors,独立软件开发商,第三方软件提供商,即使用Open API与安恒云交互,以实现导入用户、团队、主机、查询日志、角色管理等功能的第三方厂商。
Asset ID:在安恒云平台中某个已导入或创建的实体(比如用户、团队、主机、云账户等等)的主键,是一个整数(比如teamId,userId,hostId等等),很多API接口都需要指定Asset ID。ISV需要在自己的系统中记录Asset ID。
Endpoint:请求安恒云Open API的HTTP URL地址。安恒云SaaS环境的Endpoint是“https://openapi.anhengcloud.com/”,私有部署的Endpoint请在管理控制台中查看。
OpenAPI AK:由安恒云颁发给开发者的API调用凭证,包含Access Key ID和Access Key Secret,其中Access Key ID用来标识ISV的身份,而Access Key Secret是用于加密签名字符串和服务器端验证签名字符串的密钥,必须严格保密,只有安恒云和ISV知晓,API请求过程中不携带密钥信息。
2.2、认证方式1:参数签名
参数签名是利用Access Key Secret对特定API请求的HTTP method、请求路径、公共参数、API参数以一定规则生成一个HMAC签名,然后追加到URL的请求参数中。
参数说明
1、公共参数
参数名 | 类型 | 是否必须 | 备注 |
---|---|---|---|
timestamp | String | 是 | 请求发起时刻的时间,采用UTC时间,格式为:'yyyy-MM-dd'T'HH:mm:ss'Z' |
version | String | 是 | 当前API的版本,默认值:1 |
nonce | String | 是 | 一个随机字符串,长度不超过10 |
accessKeyID | String | 是 | Access Key ID,OpenAPI的密钥ID |
signature | String | 是 | 签名信息,根据所有请求参数(不包括signature自身)按一定规则计算出的签名字符串 |
2、将请求参数构造规范化请求字符串
(1)参数排序
按照参数名称的字典顺序对请求中所有的请求参数(包括“公共参数”和接口定义中的参数,但不包括“公共参数”中的 signature 参数)进行排序。
注意:这里的请求参数,全部来自URI的参数部分,即URI中“?”之后由“&”连接的部分。
(2)参数编码 对排序之后的请求参数的名称和值分别用 UTF-8 字符集进行 URL 编码。编码的规则如下:
- 对于字符 A~Z、a~z、0~9 以及字符“-”、“_”、“.”、“~”不编码。
- 对于其它字符编码成 %XY 的格式,其中 XY 是字符对应 ASCII 码的 16 进制表示。比如英文的双引号(")对应的编码为 %22。
- 对于扩展的 UTF-8 字符,编码成 %XY%ZA… 的格式。
- 英文空格要编码成 %20,而不是加号(+)。
该编码方式和一般采用的 application/x-www-form-urlencoded MIME 格式编码算法(比如 Java 标准库中的 java.net.URLEncoder 的实现)相似,但又有所不同。实现时,可以先用标准库的方式进行编码,然后把编码后的字符串中的加号(+)替换成 %20;星号(*)替换成 %2A;%7E 替换回波浪号(~)。 即可得到上述规则描述的编码字符串。这个算法可以用下面的 percentEncode 方法来实现:
static String percentEncode(String value) {
try {
return value != null ? URLEncoder.encode(value, DEFAULT_ENCODING).replace("+", "%20").replace("*", "%2A").replace("%7E", "~") : null;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
(3)连接参数
将编码后的参数名称和值用英文等号(=)进行连接,将等号连接得到的参数组合按步骤(1)排好的顺序依次使用“&”符号连接,即得到规范化请求字符串CanonicalizedQueryString。
3、构造代签名字符串
将上一步构造的规范化字符串CanonicalizedQueryString按照下面的规则构造成待签名的字符串:
String StringToSign =
method + "&" +
percentEncode(path) + "&" +
percentEncode(canonicalizedQueryString);
其中: * HTTPMethod 是提交请求用的 HTTP 方法,例如 GET或POST。
-
percentEncode(path) 是按照步骤 (1)、(2) 中描述的 URL 编码规则对请求路径path进行编码得到。
-
percentEncode(canonicalizedQueryString)是对步骤(1)中的规范化参数字符串进行编码。
4、按照 RFC2104 的定义,计算待签名字符串 StringToSign 的 HMAC 值。
注意:计算签名时使用的 Key 就是您持有的 Access Key Secret 加上一个 “&” 字符(ASCII:38),使用的哈希算法是 SHA1。
5、按照 Base64 编码规则把上面的 HMAC 值编码成字符串,即得到签名值(Signature)。
6、将得到的签名值作为 Signature 参数添加到请求参数中,即完成对请求进行签名。
注意:得到的签名值在作为最后的请求参数拼接到URI上时,要和其它参数一样,按照 RFC3986 的规则进行 URL 编码。
签名示例
以“获取查询权限配额使用情况”为例,HTTP method是GET,路径是:/permissionQuota。假设AccessKey ID 和AccessKey Secret 分别是kAMGBOBW1WNboYec
和gH4fAFf11KgjI0oT5KriYIMdFaH3Lh
,请求的permission是TeamAccess
和UserAccess
两个,即请求参数permissions=TeamAccess,UserAccess
。请求URL字符串是:
https://openapi.cloudbility.com/permissionQuota?permissions=TeamAccess,UserAccess×tamp=2018-03-29T12:46:24Z&version=1&nonce=6fcd1eh1x8&accessKeyId=kAMGBOBW1WNboYec
得到的CanonicalizedQueryString是:
accessKeyId=kAMGBOBW1WNboYec&nonce=6fcd1eh1x8&permissions=TeamAccess%2CUserAccess×tamp=2018-03-29T12%3A46%3A24Z&version=1
得到的StringToSign是:
GET&%2FpermissionQuota&accessKeyId%3DkAMGBOBW1WNboYec%26nonce%3D6fcd1eh1x8%26permissions%3DTeamAccess%252CUserAccess%26timestamp%3D2018-03-29T12%253A46%253A24Z%26version%3D1
计算得到的签名是:wN0edRE03rpAvqpdFAM3GHFwOII=
,URL编码后是:wN0edRE03rpAvqpdFAM3GHFwOII%3D
将参数拼接到原始的URL请求中,得到最后的URL是:
https://openapi.cloudbility.com/permissionQuota?permissions=TeamAccess%2CUserAccess&accessKeyId=kAMGBOBW1WNboYec&nonce=6fcd1eh1x8×tamp=2018-03-29T12%3A46%3A24Z&version=1&signature=wN0edRE03rpAvqpdFAM3GHFwOII%3D
2.3、认证方式2:获取OAuth Token
接口说明
OpenAPI有一个接口:GET /oauth?accessKeyId=xxx&accessKeySecret=xxx&expireSeconds=xxx
,可以获取到一个Access Token,根据expireSeconds
可以设置token的有效期,取值范围是2分钟到24小时。
获取到token以后,设置成请求头Authorization:{token}
即可,上文中提到的公共参数都不需要。
由于获取OAuth Token需要发送Access Key Secret,在私有部署没有开启SSL访问时使用OpenAPI服务,不建议采用这种方式,因为存在泄漏的风险。
当token过期后,请求头Authorization:{token}
会失效,所有API请求都会返回异常信息,这时候需要重新获取token。
OAuth Token认证示例
发送HTTP GET请求:
https://openapi.cloudbility.com/oauth?accessKeyId=kAMGBOBW1WNboYec&accessKeySecret=gH4fAFf11KgjI0oT5KriYIMdFaH3Lh&expireSeconds=600
响应内容:
{
"token": "zMDuliMVQbOuqHa5EaAI_w",
"expireTime": "2018-04-17T02:39:43Z"
}
然后在发送请求时,将token设置作为HTTP头“Authorization”的值即可。
还是以“获取查询权限配额使用情况”为例,请求信息:
curl -X GET --header 'Authorization: zMDuliMVQbOuqHa5EaAI_w' 'https://openapi.cloudbility.com/permissionQuota?permissions=TeamAccess%2CUserAccess'
获取到的响应“http code =200”响应体是:
{
"requestId": "G1BrkDxTSFSAEzKR5hk6iA",
"quotaList": [
{
"permission": "TeamAccess",
"total": 100,
"used": 0,
"quotaType": "ByTraffic"
},
{
"permission": "UserAccess",
"total": 100,
"used": 1,
"quotaType": "ByTraffic"
}
]
}
三、OpenAPI处理异常
API请求的响应Content-Type是application/json
,请求和响应均采用UTF-8编码。当前请求成功时,响应的http code是200,当请求失败时,响应的http code是非200。
当前请求失败时,会有类似下面的响应:
{
"requestId": "TtWoVDQ_SkiKLZYxmrwdeA",
"errorCode": "InvalidRequest",
"errorMessage": "token is expired"
}
errorCode
是错误编码,errorMessage
是错误的描述,requestId
是请求的ID。
四、API在线调试
本章节以一个私有部署实例进行介绍:http://188.188.77.136
打开安恒云管理控制台,在菜单栏选择“系统设置/OpenAPI**”,启用API可以获取到AK和endpoint。
获取授权的Token
在调试页面,打开API方法:在other api Get/Oauth ,输入AK和超时时间,点击<试一下>。
调试页面会显示API接口的输入参数列表和输出参数的模型,如果输入的是HTTP Payload JSON,也会显示模型。模型参数会描述每个字段的数据类型和意义。
调试程序会打印请求的详情,用curl命令展示,也会把响应结果显示出来。可以看到,示例中获取的token是:adr51k55S2K7IMNF2Jhe6Q
点击右上角的“Authorize”把token填入 API key的 value字段,点击“Authorize”:
从此之后,当前页面发送的请求就都会带上Token进行认证。刷新页面或者Token超时后会失效。
获取自动登录的授权URL
获取一个以特定用户身份登录系统,并打开特定页面的授权URL:other api get /oauthLogin。下面示例中,获取以用户(id=1)身份进入团队(id=1),并且打开首页菜单的授权链接。
用浏览器打开链接:http://188.188.77.136/api/openapiOAuth?token=Dh8gtA-mTOeZ4Mug8-PUIQ可以看到用户已自动登录并打开首页了。
五、使用Java SDK调用OpenAPI
可以使用Java SDK调用OpenAPI,以实现您的业务功能。请通过【OpenAPI Java SDK】下载获取OpenAPI Java SDK。
下面代码演示使用Java SDK获取自动登录的授权URL,更多样例代码,请在上述下载的OpenAPI Java SDK中获取(请先解压下载所得的cloudbility-openapi-sdk.zip文件,Java样例代码位于解压目录下的java-demo目录中)。
import com.cloudbility.openapi.OpenapiClient;
import com.cloudbility.openapi.OpenapiException;
import com.cloudbility.openapi.client.OpenapiConfig;
import com.cloudbility.openapi.request.OAuthLoginRequest;
import com.cloudbility.openapi.response.OAuthUrlResult;
import com.cloudbility.openapi.vo.RedirectPage;
/**
* 获取自动登录的URL(OAuth login url)
*/
public class GetAutoLoginAuthUrlDemo {
public static final String accessKeyId = "hpOGe8Gw6VOpMhy8";
public static final String accessKeySecret = "Xa3qjkSC-Cvcv2I2hXzd8myqXHLlierG";
public static final String endpoint = "http://188.188.77.136/api/openapi";
public static void main(String[] args) {
//client对象可以复用,可以并发使用
OpenapiClient client = createOpenapiClient(accessKeyId, accessKeySecret, endpoint);
OAuthLoginRequest request = new OAuthLoginRequest();
request.setExpireSeconds(300);//链接300秒有效
request.setOneoff(true);//链接一次有效
request.setUserId(1);//登录用户id=1
request.setTeamId(1);//登录后进入团队id=1
request.setPage(RedirectPage.Home);//登录后打开首页菜单
try {
OAuthUrlResult result = client.otherApi().getOAuthLogin(request);
System.out.println(result.getUrl());
//输出:http://188.188.77.136/api/openapiOAuth?token=kUfssyjZTbO3hPilVq_qZg
} catch (OpenapiException e) {
e.printStackTrace();
}
}
private static OpenapiClient createOpenapiClient(String accessKeyId, String accessKeySecret, String endpoint) {
OpenapiConfig config = new OpenapiConfig();
config.setAccessKeyId(accessKeyId);
config.setAccessKeySecret(accessKeySecret);
config.setEndpoint(endpoint);
return new OpenapiClient(config);
}
}
六、使用Python调用OpenAPI
以下Python代码演示获取Token、按照IP查询主机、创建登录凭证等功能。更多的Python样例代码,请下载【OpenAPI Java SDK】,下载并解压文件,其中包含了更多的Python样例代码(请先解压下载所得的anhengcloud-openapi-sdk.zip文件,Python样例代码位于解压目录下的python-demo目录中)。
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import sys
import requests
import json
import os
apiEndpoint = "http://ent.cloudbility.cn:88/api/openapi"
'''
openApi: function to access api url and post parameters
'''
def openApi(url: str, params: dict = None, headers: dict = None, method: str = "GET", contentType: str = "json", timeout: int = 10) -> dict:
if method == "GET":
response = requests.get(apiEndpoint + url, params=params, headers=headers, timeout=timeout)
elif method == "POST":
if not headers:
headers = dict()
if contentType == "json":
headers["Content-Type"] = "application/json"
params = json.dumps(params) if params else None
response = requests.post(apiEndpoint + url, data=params, headers=headers, timeout=timeout)
elif method == "DELETE":
response = requests.delete(apiEndpoint + url, data=params, headers=headers, timeout=timeout)
else:
return None
if not response:
return None
if not 200 <= response.status_code <= 299:
return None
return json.loads(response.text)
def getToken(accessKeyId, accessKeySecret, expireSeconds=300):
tokenJson = openApi(
"/oauth",
params={
"accessKeyId": accessKeyId,
"accessKeySecret": accessKeySecret,
"expireSeconds": expireSeconds
},
)
return tokenJson["token"]
def getHostByIP(token, ip, teamId=1):
hostJson = openApi(
"/host/findByIp",
params={"teamId": teamId, "ip": ip},
headers={"Authorization": token}
)
return hostJson
def createCredential(token, ip, user, password, **kwargs):
hosts = getHostByIP(token, ip, kwargs.get("TeamId", "1"))
if not hosts or "hosts" not in hosts:
return None
hostId = hosts["hosts"][0]["hostId"]
if not hostId:
return None
credential = openApi(
"/credential",
params={
"account": user,
"authType": kwargs.get("AuthType", "PASSWORD"),
"desktopType": kwargs.get("DesktopType", "SSH"),
"hosts": [
{
"hostId": hostId,
}
],
"name": "host_%s_cre_%s" %(hostId, os.getpid()),
"password": password,
"teamId": kwargs.get("TeamId", "1"),
},
headers={"Authorization": token},
method="POST"
)
return credential["id"]
def main(args, **kwargs):
ip = args[0]
user = args[1]
password = args[2]
accessKeyId = kwargs.get("id")
accessKeySecret = kwargs.get("secret")
if not accessKeyId or not accessKeySecret:
print("AccessKeyId and AccessKeySecret are not present.\n")
return -1
token = getToken(accessKeyId, accessKeySecret)
if not token:
print("Invalid AccessKeyId and AccessKeySecret.\n")
return -1
credentialId = createCredential(token, ip, user, password, **kwargs)
if credentialId:
print("Create credential for %s successed. Credential Id is %s.\n" % (ip, credentialId))
else:
print("Create credential for %s failed.\n" % (ip))
if __name__ == "__main__":
print("Create credential for a host in team(default 1). Before this operation, please do:\n")
print("1: Import the host to cloudbility\n")
print("2: Get the AccessKey Id & Secret from administration console\n")
print(
"Usage: openapi_demo.py ip username password id= secret= [teamId= AuthType= DesktopType=]\n")
if len(sys.argv) != 6:
sys.exit(-1)
sys.exit(main(sys.argv[1:4], **dict(arg.split('=') for arg in sys.argv[4:])))
七、OpenAPI Java SDK Reference
关于详细的OpenAPI Java SDK接口说明,请参考【OpenAPI Java SDK Reference】