From fef653a7560ec457466b12195d5c7517b42dad37 Mon Sep 17 00:00:00 2001 From: wklken Date: Wed, 28 Jul 2021 10:49:22 +0800 Subject: [PATCH] Develop (#36) * use apigateway instead of call_backend+call_esb * fix lint * add comment * update do_migrate.py to support bk_apigateway_url * change version to 1.1.16 --- docs/usage.md | 21 +++++++ iam/__version__.py | 2 +- iam/api/client.py | 59 ++++++++++++++++--- iam/contrib/iam_migration/utils/do_migrate.py | 29 ++++++++- iam/iam.py | 12 +++- main.py | 2 + release.md | 7 +++ tests/api/test_client.py | 18 +++--- tests/test_iam.py | 2 +- 9 files changed, 131 insertions(+), 21 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 8e4d37c..4049099 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -712,3 +712,24 @@ urlpatterns = [ url(r'^resource/api/v1/$', dispatcher.as_view([login_exempt])) ] ``` + +------------------------------ + +## 4. 切换使用 APIGateway + +未来 APIGateway 高性能网关会作为一个基础的蓝鲸服务, 权限中心将会把后台及 SaaS 的所有开放 API 接入到网关中`bk-iam` + +此时, 对于接入方, 不管是鉴权/申请权限还是其他接口, 都可以通过同一个网关访问到. + +理解成本更低, 且相关的调用日志/文档/流控/监控等都可以在 APIGateway 统一管控. + +网关地址类似: `http://bk-iam.{APIGATEWAY_DOMAIN}/{env}`, 其中 `env`值 `prod(生产)/stage(预发布)` + +SDK 目前做了兼容, 可以修改初始化 SDK 的参数, 切换流量到 APIGateway + +```python +# 测试环境, 使用stage +IAM(app_code, app_secret, bk_apigateway_url="http://bk-iam.{APIGATEWAY_DOMAIN}/stage"): +# 正式环境, 使用prod +IAM(app_code, app_secret, bk_apigateway_url="http://bk-iam.{APIGATEWAY_DOMAIN}/prod"): +``` diff --git a/iam/__version__.py b/iam/__version__.py index d3b670c..718702c 100644 --- a/iam/__version__.py +++ b/iam/__version__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -__version__ = "1.1.15" \ No newline at end of file +__version__ = "1.1.16" diff --git a/iam/api/client.py b/iam/api/client.py index bd41087..d73001e 100644 --- a/iam/api/client.py +++ b/iam/api/client.py @@ -21,6 +21,7 @@ from cachetools import TTLCache, cached from requests.models import PreparedRequest +from iam.exceptions import AuthAPIError from .http import http_delete, http_get, http_post, http_put logger = logging.getLogger("iam") @@ -33,11 +34,29 @@ class Client(object): input: json """ - def __init__(self, app_code, app_secret, bk_iam_host, bk_paas_host): + def __init__(self, app_code, app_secret, bk_iam_host=None, bk_paas_host=None, bk_apigateway_url=None): + """ + 如果有 APIGateway 且权限中心网关接入, 则可以统一API请求全部走APIGateway + - 没有APIGateway的用法: Client(app_code, app_secret, bk_iam_host, bk_paas_host) + - 有APIGateway的用法: Client(app_code, app_secret, bk_apigateway_url) + + NOTE: 未来将会下线`没有 APIGateway的用法` + """ self._app_code = app_code self._app_secret = app_secret - self._host = bk_iam_host - self._bk_paas_host = bk_paas_host + + # enabled apigateay + self._apigateway_on = False + if bk_apigateway_url: + self._apigateway_on = True + # replace the host + self._host = bk_apigateway_url.rstrip("/") + else: + if not (bk_iam_host and bk_paas_host): + raise AuthAPIError("init client fail, bk_iam_host and bk_paas_host should not be empty") + + self._host = bk_iam_host + self._bk_paas_host = bk_paas_host # will add ?debug=true in url, for debug api/policy, show the details isApiDebugEnabled = os.environ.get("IAM_API_DEBUG") == "true" or os.environ.get("BKAPP_IAM_API_DEBUG") == "true" @@ -78,19 +97,45 @@ def _call_api(self, http_func, host, path, data, headers, timeout=None): return True, "ok", _d + def _call_apigateway_api(self, http_func, path, data, timeout=None): + """ + 统一后, 所有接口调用走APIGateway + """ + headers = { + "X-Bkapi-Authorization": json.dumps({"bk_app_code": self._app_code, "bk_app_secret": self._app_secret}), + "X-Bk-IAM-Version": BK_IAM_VERSION, + } + return self._call_api(http_func, self._host, path, data, headers, timeout=timeout) + def _call_iam_api(self, http_func, path, data, timeout=None): + """ + 兼容切换到apigateway, 统一后, 这个方法应该去掉 + """ + if self._apigateway_on: + return self._call_apigateway_api(http_func, path, data, timeout) + + # call directly headers = { "X-BK-APP-CODE": self._app_code, "X-BK-APP-SECRET": self._app_secret, "X-Bk-IAM-Version": BK_IAM_VERSION, } + return self._call_api(http_func, self._host, path, data, headers, timeout=timeout) def _call_esb_api(self, http_func, path, data, bk_token, bk_username, timeout=None): - headers = { - # "BK-APP-CODE": self._app_code, - # "BK-APP-SECRET": self._app_secret, - } + """ + 兼容切换到apigateway, 统一后, 这个方法应该去掉 + """ + if self._apigateway_on: + apigw_path = path.replace("/api/c/compapi/v2/iam/", "/api/v1/open/") + if not path.startswith("/api/v1/open/"): + raise AuthAPIError("can't find the matched apigateway path, the esb api path is %s" % path) + + return self._call_apigateway_api(http_func, apigw_path, data, timeout) + + # call esb + headers = {} data.update( { "bk_app_code": self._app_code, diff --git a/iam/contrib/iam_migration/utils/do_migrate.py b/iam/contrib/iam_migration/utils/do_migrate.py index 7e7a81d..40d9e6b 100644 --- a/iam/contrib/iam_migration/utils/do_migrate.py +++ b/iam/contrib/iam_migration/utils/do_migrate.py @@ -25,6 +25,7 @@ __version__ = "1.0.0" BK_IAM_HOST = os.getenv("BK_IAM_V3_INNER_HOST", "http://bkiam.service.consul:5001") +USE_APIGATEWAY = os.getenv("BK_IAM_USE_APIGATEWAY", "false").lower() == "true" APP_CODE = "" APP_SECRET = "" @@ -143,6 +144,11 @@ def __init__(self, app_code, app_secret, bk_iam_host): # 调用权限中心方法 def _call_iam_api(self, http_func, path, data): headers = {"X-BK-APP-CODE": self.app_code, "X-BK-APP-SECRET": self.app_secret} + if USE_APIGATEWAY: + headers = { + "X-Bkapi-Authorization": json.dumps({"bk_app_code": self.app_code, "bk_app_secret": self.app_secret}), + } + url = "{host}{path}".format(host=self.bk_iam_host, path=path) ok, _data = http_func(url, data, headers=headers) # TODO: add debug here @@ -588,7 +594,14 @@ def do_migrate(data, bk_iam_host=BK_IAM_HOST, app_code=APP_CODE, app_secret=APP_ if __name__ == "__main__": p = argparse.ArgumentParser() p.add_argument( - "-t", action="store", dest="bk_iam_host", help="bk_iam_host, i.e: http://iam.service.consul", required=True + "-t", + action="store", + dest="bk_iam_host", + help=( + "bk_iam_host, i.e: http://iam.service.consul;" + "you can use bk_apigateway_url here, set with the '--apigateway' " + ), + required=True, ) p.add_argument( "-f", @@ -599,9 +612,23 @@ def do_migrate(data, bk_iam_host=BK_IAM_HOST, app_code=APP_CODE, app_secret=APP_ ) p.add_argument("-a", action="store", dest="app_code", help="app code", required=True) p.add_argument("-s", action="store", dest="app_secret", help="app secret", required=True) + + p.add_argument( + "--apigateway", + action="store_true", + dest="use_apigateway", + help="you can use bk_apigateway_url in '-t', should set this flag", + ) args = p.parse_args() BK_IAM_HOST = args.bk_iam_host.rstrip("/") + USE_APIGATEWAY = args.use_apigateway + if USE_APIGATEWAY: + print( + "use apigateway:", + args.use_apigateway, + ", please make sure '-t %s' is a valid bk_apigateway_url" % args.bk_iam_host, + ) if not BK_IAM_HOST.startswith("http://"): BK_IAM_HOST = "http://%s" % BK_IAM_HOST diff --git a/iam/iam.py b/iam/iam.py index 98bb8cd..66868f9 100644 --- a/iam/iam.py +++ b/iam/iam.py @@ -37,8 +37,16 @@ class IAM(object): input: object """ - def __init__(self, app_code, app_secret, bk_iam_host, bk_paas_host): - self._client = Client(app_code, app_secret, bk_iam_host, bk_paas_host) + def __init__(self, app_code, app_secret, bk_iam_host=None, bk_paas_host=None, bk_apigateway_url=None): + """ + 如果有 APIGateway 且权限中心网关接入, 则可以统一API请求全部走APIGateway + - 没有APIGateway的用法: IAM(app_code, app_secret, bk_iam_host, bk_paas_host) + - 有APIGateway的用法: IAM(app_code, app_secret, bk_apigateway_url) + + NOTE: 未来将会下线`没有 APIGateway的用法` + TODO: 切换后, 所有暴露接口将不再依赖 bk_token/bk_username, 需考虑兼容调用方, 并文档说明 + """ + self._client = Client(app_code, app_secret, bk_iam_host, bk_paas_host, bk_apigateway_url) def _do_policy_query(self, request, with_resources=True): data = request.to_dict() diff --git a/main.py b/main.py index adcad2b..3daa9c5 100644 --- a/main.py +++ b/main.py @@ -167,5 +167,7 @@ def convert_example(): print("the request: ", request.to_dict()) iam = IAM("bk_paas", "2353e89a-10a2-4f30-9f6b-8973e9cd1404", "http://127.0.0.1:8080", "https://{PAAS_DOMAIN}") + # recommend if got an APIGateway + # iam = IAM("bk_paas", "2353e89a-10a2-4f30-9f6b-8973e9cd1404", bk_apigateway_url="http://{IAM_APIGATEWAY_URL}") print("is_allowed: ", iam.is_allowed(request)) print("query: ", iam.make_filter(request)) diff --git a/release.md b/release.md index d3cc791..70ef3aa 100644 --- a/release.md +++ b/release.md @@ -1,5 +1,12 @@ 版本日志 =============== +# v1.1.16 + +- add: support call apis via APIGateway + +# v1.1.15 + +- bugfix: 修复当settings没有提供BK_IAM_SKIP变量导致migrate失败的问题 # v1.1.15 diff --git a/tests/api/test_client.py b/tests/api/test_client.py index def424e..6ee0266 100644 --- a/tests/api/test_client.py +++ b/tests/api/test_client.py @@ -43,7 +43,7 @@ def _test_ok_message_data(mock_request, call_func): @patch("iam.api.client.http_post") def test_client_policy_query(mock_post): - c = Client("bk_paas", "", "http://127.0.0.1:1234", "") + c = Client("bk_paas", "", "http://127.0.0.1:1234", "http://127.0.0.1:8000") _test_ok_message_data(mock_post, c.policy_query) @@ -71,7 +71,7 @@ def _test_ok_message(mock_request, call_func, kwargs): @patch("iam.api.client.http_post") def test_create(mock_post): - c = Client("bk_paas", "", "http://127.0.0.1:1234", "") + c = Client("bk_paas", "", "http://127.0.0.1:1234", "http://127.0.0.1:8000") _test_ok_message(mock_post, c.add_system, dict(data={})) @@ -84,7 +84,7 @@ def test_create(mock_post): @patch("iam.api.client.http_put") def test_update(mock_put): - c = Client("bk_paas", "", "http://127.0.0.1:1234", "") + c = Client("bk_paas", "", "http://127.0.0.1:1234", "http://127.0.0.1:8000") _test_ok_message(mock_put, c.update_system, dict(system_id="", data={})) @@ -97,7 +97,7 @@ def test_update(mock_put): @patch("iam.api.client.http_delete") def test_delete(mock_delete): - c = Client("bk_paas", "", "http://127.0.0.1:1234", "") + c = Client("bk_paas", "", "http://127.0.0.1:1234", "http://127.0.0.1:8000") _test_ok_message(mock_delete, c.batch_delete_resource_types, dict(system_id="", data={})) @@ -106,13 +106,13 @@ def test_delete(mock_delete): @patch.dict(os.environ, {"ABC": "true"}) def test_client_extra_url_params_empty(): - c = Client("bk_paas", "", "http://127.0.0.1:1234", "") + c = Client("bk_paas", "", "http://127.0.0.1:1234", "http://127.0.0.1:8000") assert not c._extra_url_params @patch.dict(os.environ, {"IAM_API_DEBUG": "true"}) def test_client_extra_url_params_debug_1(): - c = Client("bk_paas", "", "http://127.0.0.1:1234", "") + c = Client("bk_paas", "", "http://127.0.0.1:1234", "http://127.0.0.1:8000") assert c._extra_url_params assert len(c._extra_url_params) == 1 assert c._extra_url_params.get("debug") == "true" @@ -120,7 +120,7 @@ def test_client_extra_url_params_debug_1(): @patch.dict(os.environ, {"BKAPP_IAM_API_DEBUG": "true"}) def test_client_extra_url_params_debug_2(): - c = Client("bk_paas", "", "http://127.0.0.1:1234", "") + c = Client("bk_paas", "", "http://127.0.0.1:1234", "http://127.0.0.1:8000") assert c._extra_url_params assert len(c._extra_url_params) == 1 assert c._extra_url_params.get("debug") == "true" @@ -128,7 +128,7 @@ def test_client_extra_url_params_debug_2(): @patch.dict(os.environ, {"IAM_API_FORCE": "true"}) def test_client_extra_url_params_force_1(): - c = Client("bk_paas", "", "http://127.0.0.1:1234", "") + c = Client("bk_paas", "", "http://127.0.0.1:1234", "http://127.0.0.1:8000") assert c._extra_url_params assert len(c._extra_url_params) == 1 assert c._extra_url_params.get("force") == "true" @@ -136,7 +136,7 @@ def test_client_extra_url_params_force_1(): @patch.dict(os.environ, {"BKAPP_IAM_API_FORCE": "true"}) def test_client_extra_url_params_force_2(): - c = Client("bk_paas", "", "http://127.0.0.1:1234", "") + c = Client("bk_paas", "", "http://127.0.0.1:1234", "http://127.0.0.1:8000") assert c._extra_url_params assert len(c._extra_url_params) == 1 assert c._extra_url_params.get("force") == "true" diff --git a/tests/test_iam.py b/tests/test_iam.py index 4322bd0..c92774b 100644 --- a/tests/test_iam.py +++ b/tests/test_iam.py @@ -27,7 +27,7 @@ def new_mock_iam(): - return IAM("test", "test", "iam_host", "paas_host") + return IAM("test", "test", "iam_host", "paas_host", None) def new_valid_request():