feat: 审计 API(仅 owner.audit)+ work_env 写审计日志
This commit is contained in:
parent
9ab0db53ad
commit
359c57b156
71
wwwroot/api/audit.dspy
Normal file
71
wwwroot/api/audit.dspy
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# audit.dspy - 审计日志 API
|
||||||
|
# 查看/备份/删除 仅 owner.audit 角色(审计独立性:superuser 也无权访问)
|
||||||
|
# action: list / backup / delete
|
||||||
|
|
||||||
|
user_id = await get_user()
|
||||||
|
if not user_id:
|
||||||
|
return json.dumps({'ok': False, 'error': '未登录'}, ensure_ascii=False)
|
||||||
|
|
||||||
|
action = (params_kw or {}).get('action', 'list')
|
||||||
|
|
||||||
|
from pipeline_service.audit_service import (
|
||||||
|
is_audit_role, list_audit_logs, backup_audit_logs, delete_audit_logs,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 查 username 和 client_ip
|
||||||
|
username = user_id
|
||||||
|
try:
|
||||||
|
async with get_sor_context(request._run_ns, 'pipeline') as _sor:
|
||||||
|
_u = await _sor.sqlExe("SELECT username FROM users WHERE id=${u}$", {'u': user_id})
|
||||||
|
if _u:
|
||||||
|
username = getattr(_u[0], 'username', user_id) or user_id
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
client_ip = ''
|
||||||
|
try:
|
||||||
|
client_ip = request.get('client_ip', '') or ''
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 权限校验:所有 action 仅 owner.audit(superuser 也不行)
|
||||||
|
async with get_sor_context(request._run_ns, 'pipeline') as _sor:
|
||||||
|
if not await is_audit_role(_sor, user_id):
|
||||||
|
return json.dumps({'ok': False, 'error': '仅 owner.audit 角色可访问审计日志'}, ensure_ascii=False)
|
||||||
|
|
||||||
|
if action == 'list':
|
||||||
|
filters = {
|
||||||
|
'user_id': (params_kw or {}).get('user_id', ''),
|
||||||
|
'username': (params_kw or {}).get('username', ''),
|
||||||
|
'action': (params_kw or {}).get('act', ''),
|
||||||
|
'from': (params_kw or {}).get('from', ''),
|
||||||
|
'to': (params_kw or {}).get('to', ''),
|
||||||
|
}
|
||||||
|
limit = (params_kw or {}).get('limit', 100)
|
||||||
|
offset = (params_kw or {}).get('offset', 0)
|
||||||
|
try:
|
||||||
|
async with get_sor_context(request._run_ns, 'pipeline') as sor:
|
||||||
|
rows = await list_audit_logs(sor, filters, limit, offset)
|
||||||
|
return json.dumps({'ok': True, 'logs': rows}, ensure_ascii=False)
|
||||||
|
except Exception as e:
|
||||||
|
return json.dumps({'ok': False, 'error': str(e)}, ensure_ascii=False)
|
||||||
|
|
||||||
|
elif action == 'backup':
|
||||||
|
try:
|
||||||
|
async with get_sor_context(request._run_ns, 'pipeline') as sor:
|
||||||
|
r = await backup_audit_logs(sor, user_id, username, None, client_ip)
|
||||||
|
return json.dumps(r, ensure_ascii=False)
|
||||||
|
except Exception as e:
|
||||||
|
return json.dumps({'ok': False, 'error': str(e)}, ensure_ascii=False)
|
||||||
|
|
||||||
|
elif action == 'delete':
|
||||||
|
before = (params_kw or {}).get('before', '')
|
||||||
|
try:
|
||||||
|
async with get_sor_context(request._run_ns, 'pipeline') as sor:
|
||||||
|
r = await delete_audit_logs(sor, user_id, username, before, client_ip)
|
||||||
|
return json.dumps(r, ensure_ascii=False)
|
||||||
|
except Exception as e:
|
||||||
|
return json.dumps({'ok': False, 'error': str(e)}, ensure_ascii=False)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return json.dumps({'ok': False, 'error': '未知 action: ' + str(action)}, ensure_ascii=False)
|
||||||
@ -11,6 +11,13 @@ from pipeline_service.work_env import (
|
|||||||
get_work_env, set_work_env, ensure_remote_bwrap, get_org_pubkey,
|
get_work_env, set_work_env, ensure_remote_bwrap, get_org_pubkey,
|
||||||
)
|
)
|
||||||
from pipeline_service.deploy_account import account_name_for, sanitize_username
|
from pipeline_service.deploy_account import account_name_for, sanitize_username
|
||||||
|
from pipeline_service.audit_service import audit_log
|
||||||
|
|
||||||
|
client_ip = ''
|
||||||
|
try:
|
||||||
|
client_ip = request.get('client_ip', '') or ''
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# 查用户 username(账号名 ag_<username>)和 org_id
|
# 查用户 username(账号名 ag_<username>)和 org_id
|
||||||
username = user_id
|
username = user_id
|
||||||
@ -67,6 +74,11 @@ elif action == 'set':
|
|||||||
try:
|
try:
|
||||||
async with get_sor_context(request._run_ns, 'pipeline') as sor:
|
async with get_sor_context(request._run_ns, 'pipeline') as sor:
|
||||||
r = await set_work_env(sor, owner_type, owner_id, mode, remote_config, account_name)
|
r = await set_work_env(sor, owner_type, owner_id, mode, remote_config, account_name)
|
||||||
|
# 审计:工作环境变更(尤其远程主机变更,高风险)
|
||||||
|
await audit_log(sor, user_id, username, 'work_env_set',
|
||||||
|
target=owner_type + ':' + str(owner_id),
|
||||||
|
detail='mode=' + mode + ' host=' + str(remote_config.get('remote_host', '')),
|
||||||
|
result='ok' if r.get('ok') else 'fail', client_ip=client_ip)
|
||||||
return json.dumps(r, ensure_ascii=False)
|
return json.dumps(r, ensure_ascii=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return json.dumps({'ok': False, 'error': str(e)}, ensure_ascii=False)
|
return json.dumps({'ok': False, 'error': str(e)}, ensure_ascii=False)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user