Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [login api] use apigw sdk #2012

Merged
merged 3 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 0 additions & 87 deletions src/bk-login/bklogin/common/middlewares.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,9 @@
#
# We undertake not to change the open source license (MIT license) applicable
# to the current version of the project delivered to anyone in the future.
import base64
import json
import logging
from collections import namedtuple
from typing import Any, Dict

import jwt
from django.conf import settings
from django.db import connections
from sentry_sdk import capture_exception

Expand Down Expand Up @@ -89,85 +84,3 @@ def _set_rollback():
for db in connections.all():
if db.settings_dict["ATOMIC_REQUESTS"] and db.in_atomic_block:
db.set_rollback(True)


class APIGatewayJWTMiddleware:
"""
该中间件读取由 API 网关传输的 JWT 头信息,获取相应的公钥进行解密,将解密结果赋值给 request.app。
默认情况下,通过 settings.BK_APIGW_PUBLIC_KEY 读取 API 网关公钥
"""

JWT_KEY_HEADER_NAME = "HTTP_X_BKAPI_JWT"
ALGORITHM = "RS512"
App = namedtuple("App", ["bk_app_code", "verified"])

def __init__(self, get_response):
self.get_response = get_response

self.public_key = self._get_public_key()

def __call__(self, request):
# 解析 Header 头 APIGateway JWT
jwt_token = request.META.get(self.JWT_KEY_HEADER_NAME, "")
jwt_payload = self._decode_jwt(jwt_token)
if not jwt_payload:
return self.get_response(request)

# App 信息
jwt_app = jwt_payload.get("app") or {}
bk_app_code = jwt_app.get("bk_app_code", jwt_app.get("app_code", None))
app_verified = jwt_app.get("verified", False)

# 将 JWT App 信息赋值给 request.app
request.app = self._make_app(bk_app_code=bk_app_code, verified=app_verified)

return self.get_response(request)

@staticmethod
def _get_public_key() -> str:
"""
获取 APIGW 的 Public Key
由于配置文件里的 public key 是来自环境变量,且使用 base64 编码,因此需要解码
"""
if not settings.BK_APIGW_PUBLIC_KEY:
logger.error("setting `BK_APIGW_PUBLIC_KEY` can not be empty")
return ""

try:
return base64.b64decode(settings.BK_APIGW_PUBLIC_KEY).decode("utf-8")
except (TypeError, ValueError):
logger.exception(
"setting `BK_APIGW_PUBLIC_KEY` must be base64 string, BK_APIGW_PUBLIC_KEY=%s",
settings.BK_APIGW_PUBLIC_KEY,
)

return ""

def _decode_jwt(self, jwt_token: str) -> Dict[str, Any] | None:
"""
解析 JWT
:return
{
"app" : {"app_code" : "app-code-test", "verified" : true, ...},
"user" : {"username" : "user-test", "verified" : false, ...}
}
"""
if not jwt_token:
return None

if not self.public_key:
return None

try:
# 从 JWT 里的 Header 字段里解析出未验证的信息,比如算法,然后用于后续解析 JWT 里的 Payload
jwt_header = jwt.get_unverified_header(jwt_token)
algorithm = jwt_header.get("alg") or self.ALGORITHM
# Note: bk apigw 目前的 issuer 不规范,所以这里不强校验 issuer
return jwt.decode(jwt_token, self.public_key, algorithms=[algorithm], options={"verify_iss": False})
except jwt.PyJWTError:
logger.exception("jwt decode failed, jwt content: %s", jwt_token)

return None

def _make_app(self, bk_app_code: str | None = None, verified: bool = False):
return self.App(bk_app_code=bk_app_code, verified=verified)
8 changes: 4 additions & 4 deletions src/bk-login/bklogin/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"django.contrib.staticfiles",
"corsheaders",
"django_prometheus",
"apigw_manager.apigw",
"bklogin.authentication",
]

Expand All @@ -68,7 +69,8 @@
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"bklogin.common.middlewares.ExceptionHandlerMiddleware",
"bklogin.common.middlewares.APIGatewayJWTMiddleware",
"apigw_manager.apigw.authentication.ApiGatewayJWTGenericMiddleware",
"apigw_manager.apigw.authentication.ApiGatewayJWTAppMiddleware",
"django_prometheus.middleware.PrometheusAfterMiddleware",
]

Expand Down Expand Up @@ -209,9 +211,7 @@

# bk apigw url tmpl
BK_API_URL_TMPL = env.str("BK_API_URL_TMPL", default="")
# Open API 接入 BK APIGateway 后,需要对 APIGW 请求来源认证,使用公钥解开 jwt
# Note: 格式必须是 base64 字符串
BK_APIGW_PUBLIC_KEY = env.str("BK_APIGW_PUBLIC_KEY", default="")
BK_APIGW_NAME = env.str("BK_APIGW_NAME", default="bk-login")

# footer / logo / title 等全局配置存储的共享仓库地址
BK_SHARED_RES_URL = env.str("BK_SHARED_RES_URL", default="")
Expand Down
122 changes: 121 additions & 1 deletion src/bk-login/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/bk-login/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ opentelemetry-instrumentation-celery = "0.46b0"
opentelemetry-instrumentation-logging = "0.46b0"
bk-notice-sdk = "1.3.2"
pyjwt = {version = "2.10.1", extras = ["cryptography"]}
apigw-manager = {version = "4.0.0", extras = ["cryptography"]}

[tool.poetry.group.dev.dependencies]
ruff = "^0.7.1"
Expand Down
3 changes: 3 additions & 0 deletions src/bk-login/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
--index-url https://mirrors.tencent.com/pypi/simple

annotated-types==0.7.0 ; python_version >= "3.11" and python_version < "3.12"
apigw-manager[cryptography]==4.0.0 ; python_version >= "3.11" and python_version < "3.12"
asgiref==3.8.1 ; python_version >= "3.11" and python_version < "3.12"
bk-crypto-python-sdk==2.0.0 ; python_version >= "3.11" and python_version < "3.12"
bk-notice-sdk==1.3.2 ; python_version >= "3.11" and python_version < "3.12"
bkapi-bk-apigateway==1.0.11 ; python_version >= "3.11" and python_version < "3.12"
bkapi-client-core==1.2.0 ; python_version >= "3.11" and python_version < "3.12"
blue-krill==2.0.7 ; python_version >= "3.11" and python_version < "3.12"
certifi==2024.8.30 ; python_version >= "3.11" and python_version < "3.12"
Expand Down Expand Up @@ -59,6 +61,7 @@ pymysql==1.1.1 ; python_version >= "3.11" and python_version < "3.12"
python-editor==1.0.4 ; python_version >= "3.11" and python_version < "3.12"
python-json-logger==2.0.7 ; python_version >= "3.11" and python_version < "3.12"
pywin32==308 ; python_version >= "3.11" and python_version < "3.12" and platform_system == "Windows"
pyyaml==6.0.2 ; python_version >= "3.11" and python_version < "3.12"
requests==2.32.3 ; python_version >= "3.11" and python_version < "3.12"
sentry-sdk==1.45.0 ; python_version >= "3.11" and python_version < "3.12"
setuptools==75.6.0 ; python_version >= "3.11" and python_version < "3.12"
Expand Down
3 changes: 3 additions & 0 deletions src/bk-login/requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
--index-url https://mirrors.tencent.com/pypi/simple

annotated-types==0.7.0 ; python_version >= "3.11" and python_version < "3.12"
apigw-manager[cryptography]==4.0.0 ; python_version >= "3.11" and python_version < "3.12"
asgiref==3.8.1 ; python_version >= "3.11" and python_version < "3.12"
bk-crypto-python-sdk==2.0.0 ; python_version >= "3.11" and python_version < "3.12"
bk-notice-sdk==1.3.2 ; python_version >= "3.11" and python_version < "3.12"
bkapi-bk-apigateway==1.0.11 ; python_version >= "3.11" and python_version < "3.12"
bkapi-client-core==1.2.0 ; python_version >= "3.11" and python_version < "3.12"
blue-krill==2.0.7 ; python_version >= "3.11" and python_version < "3.12"
certifi==2024.8.30 ; python_version >= "3.11" and python_version < "3.12"
Expand Down Expand Up @@ -67,6 +69,7 @@ pytest==8.3.4 ; python_version >= "3.11" and python_version < "3.12"
python-editor==1.0.4 ; python_version >= "3.11" and python_version < "3.12"
python-json-logger==2.0.7 ; python_version >= "3.11" and python_version < "3.12"
pywin32==308 ; python_version >= "3.11" and python_version < "3.12" and platform_system == "Windows"
pyyaml==6.0.2 ; python_version >= "3.11" and python_version < "3.12"
requests==2.32.3 ; python_version >= "3.11" and python_version < "3.12"
ruff==0.7.4 ; python_version >= "3.11" and python_version < "3.12"
sentry-sdk==1.45.0 ; python_version >= "3.11" and python_version < "3.12"
Expand Down
Loading