一、OpenAPI概述
行云管家的OpenAPI(以下简称OpenAPI或API),在SaaS和私有部署上均可使用。API提供的方法和调用方式都是一致的,区别是client使用的AK和endpoint不同。
-
a) SaaS环境的endpoint是:https://openapi.cloudbility.com,有需要通过OpenAPI集成行云管家功能的,请您发送邮件到 support@cloudbility.com,邮件内容请填写:“公司名称” 、“联系人”、“手机号码” 、 “团队名称”、“用户名称”、“API用途”,行云管家SaaS运营人员审核通过后,会以邮件发送AK予您;
-
b) 私有部署的endpoint,可以在管理控制台的菜单:系统设置->OpenAPI中查看,点击“启用API”之后会生成所需的AK。
API以http RESTful的方式提供,API调用的认证方式有两种:
-
a) 使用AK生成签名;
-
b) 使用Token。
API有一个可以在线调试的页面,即不用写一行代码,也可以完成大部分的API调用。SAAS环境的API的文档页面在https://openapi.cloudbility.com/doc/index.html,也是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.cloudbility.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样例代码(请先解压下载所得的cloudbility-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】