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

增加登录审计日志 #1251

Merged
merged 8 commits into from
Dec 13, 2021
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
3 changes: 3 additions & 0 deletions common/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,9 @@
</ul>
<!-- /.nav-third-level -->
</li>
<li>
<a href="/audit/">登录审计日志</a>
</li>
</ul>
<!-- /.nav-second-level -->
</li>
Expand Down
10 changes: 9 additions & 1 deletion sql/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
AliyunRdsConfig, CloudAccessKey, ResourceGroup, QueryPrivilegesApply, \
QueryPrivileges, InstanceAccount, InstanceDatabase, ArchiveConfig, \
WorkflowAudit, WorkflowLog, ParamTemplate, ParamHistory, InstanceTag, \
Tunnel
Tunnel, AuditEntry


# 用户管理
Expand Down Expand Up @@ -244,3 +244,11 @@ class ArchiveConfigAdmin(admin.ModelAdmin):
@admin.register(CloudAccessKey)
class CloudAccessKeyAdmin(admin.ModelAdmin):
list_display = ('type', 'key_id', 'key_secret', 'remark')


# 登录审计日志
@admin.register(AuditEntry)
class AuditEntryAdmin(admin.ModelAdmin):
list_display = ('user_id', 'user_name', 'action', 'ip', 'action_time')
list_filter = ('user_id', 'user_name', 'action', 'ip')

72 changes: 72 additions & 0 deletions sql/audit_log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# -*- coding: UTF-8 -*-
import logging
from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
from django.dispatch import receiver
from .models import AuditEntry, Users
from django.utils import timezone
from common.utils.permission import superuser_required
from django.http import HttpResponse
import simplejson as json
from common.utils.extend_json_encoder import ExtendJSONEncoder
from django.db.models import Q

log = logging.getLogger('default')


@superuser_required
def audit_log(request):
"""获取登录审计日志列表"""
limit = int(request.POST.get('limit'))
offset = int(request.POST.get('offset'))
limit = offset + limit
search = request.POST.get('search', '')

# 过滤搜索条件
audit_log_obj = AuditEntry.objects.filter(Q(user_name__icontains=search) | Q(action__icontains=search)| Q(ip__icontains=search))
audit_log_count = audit_log_obj.count()
audit_log_list = audit_log_obj.order_by('-action_time')[offset:limit].values("user_id", "user_name", "ip", "action", "action_time")

# QuerySet 序列化
rows = [row for row in audit_log_list]

result = {"total": audit_log_count, "rows": rows}
# 返回查询结果
return HttpResponse(json.dumps(result, cls=ExtendJSONEncoder, bigint_as_string=True),
content_type='application/json')


def get_client_ip(request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip


@receiver(user_logged_in)
def user_logged_in_callback(sender, request, user, **kwargs):
ip = get_client_ip(request)
now = timezone.now()
AuditEntry.objects.create(action=u'登入', ip=ip, user_id=user.id, user_name=user.username, action_time=now)


@receiver(user_logged_out)
def user_logged_out_callback(sender, request, user, **kwargs):
ip = get_client_ip(request)
now = timezone.now()
AuditEntry.objects.create(action=u'登出', ip=ip, user_id=user.id, user_name=user.username, action_time=now)


@receiver(user_login_failed)
def user_login_failed_callback(sender, credentials, **kwargs):
now = timezone.now()
user_name = credentials.get('username', None)
user_obj = Users.objects.filter(username=user_name)[0:1]
user_count = user_obj.count()
user_id = 0
if user_count > 0:
user_id = user_obj[0].id
AuditEntry.objects.create(action=u'登入失败', user_id=user_id, user_name=user_name
, action_time=now)

25 changes: 25 additions & 0 deletions sql/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -853,3 +853,28 @@ class Meta:
index_together = ('hostname_max', 'ts_min')
verbose_name = u'慢日志明细'
verbose_name_plural = u'慢日志明细'


class AuditEntry(models.Model):
"""
登录审计日志
"""
user_id = models.IntegerField('用户ID')
user_name = models.CharField('用户名称', max_length=255, null=True)
action = models.CharField('动作', max_length=255)
ip = models.GenericIPAddressField('IP', null=True)
action_time = models.DateTimeField('操作时间', auto_now_add=True)

class Meta:
managed = True
db_table = 'audit_log'
verbose_name = u'审计日志'
verbose_name_plural = u'审计日志'

def __unicode__(self):
return '{0} - {1} - {2} - {3} - {4}'.format(self.user_id, self.user_name, self.ip
, self.action, self.action_time)

def __str__(self):
return '{0} - {1} - {2} - {3} - {4}'.format(self.user_id, self.user_name, self.ip
, self.action, self.action_time)
94 changes: 94 additions & 0 deletions sql/templates/audit.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
{% extends "base.html" %}

{% block content %}
<!-- 表格-->
<div class="table-responsive">
<table id="group-list" data-toggle="table" class="table table-striped table-hover"
style="table-layout:inherit;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">
</table>
</div>
{% endblock content %}
{% block js %}
{% load static %}
<script src="{% static 'bootstrap-table/js/bootstrap-table-export.min.js' %}"></script>
<script src="{% static 'bootstrap-table/js/tableExport.min.js' %}"></script>
<script>
//获取列表
function grouplist() {
//采取异步请求
//初始化table
$('#group-list').bootstrapTable('destroy').bootstrapTable({
escape: true,
method: 'post',
contentType: "application/x-www-form-urlencoded",
url: "/audit/log/",
striped: true, //是否显示行间隔色
cache: false, //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*)
pagination: true, //是否显示分页(*)
sortable: true, //是否启用排序
sortOrder: "asc", //排序方式
sidePagination: "server", //分页方式:client客户端分页,server服务端分页(*)
pageNumber: 1, //初始化加载第一页,默认第一页,并记录
pageSize: 14, //每页的记录行数(*)
pageList: [20, 30, 50, 100], //可供选择的每页的行数(*)
showExport: true, //是否显示导出按钮
exportOptions: {
fileName: 'audit_log' //文件名称设置
},
search: true, //是否显示表格搜索
strictSearch: false, //是否全匹配搜索
showColumns: true, //是否显示所有的列(选择显示的列)
showRefresh: true, //是否显示刷新按钮
minimumCountColumns: 2, //最少允许的列数
clickToSelect: true, //是否启用点击选中行
uniqueId: "id", //每一行的唯一标识,一般为主键列
showToggle: true, //是否显示详细视图和列表视图的切换按钮
cardView: false, //是否显示详细视图
detailView: false, //是否显示父子表
locale: 'zh-CN', //本地化
toolbar: "#toolbar", //指明自定义的toolbar
queryParamsType: 'limit',
//请求服务数据时所传参数
queryParams:
function (params) {
return {
limit: params.limit,
offset: params.offset,
search: params.search
}
},
columns: [{
title: '用户',
field: 'user_name',
formatter: function (value, row, index) {
return "<a target=\"_blank\" href=\"/admin/sql/users/" + row.user_id + "/change/\">" + value + "</a>"
}
},{
title: 'IP',
field: 'ip'
},{
title: '动作',
field: 'action'
}, {
title: '操作时间',
field: 'action_time'
}],
onLoadSuccess: function () {
},
onLoadError: function () {
alert("数据加载失败!请检查接口返回信息和错误日志!");
},
onSearch: function (e) {
//传搜索参数给服务器
queryParams(e)
}
});

}

//初始化数据
$(document).ready(function () {
grouplist();
});
</script>
{% endblock %}
6 changes: 6 additions & 0 deletions sql/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,12 @@ def test_group(self):
r = self.client.get(f'/group/', data=data)
self.assertEqual(r.status_code, 200)

def test_audit(self):
"""测试audit页面"""
data = {}
r = self.client.get(f'/audit/', data=data)
self.assertEqual(r.status_code, 200)

def test_groupmgmt(self):
"""测试groupmgmt页面"""
data = {}
Expand Down
7 changes: 5 additions & 2 deletions sql/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import sql.sql_optimize
from common import auth, config, workflow, dashboard, check
from sql import views, sql_workflow, sql_analyze, query, slowlog, instance, instance_account, db_diagnostic, \
resource_group, binlog, data_dictionary, archiver
resource_group, binlog, data_dictionary, archiver, audit_log
from sql.utils import tasks
from common.utils import ding_api

Expand Down Expand Up @@ -55,6 +55,7 @@
path('archive/', views.archive),
path('archive/<int:id>/', views.archive_detail, name='archive_detail'),
path('config/', views.config),
path('audit/', views.audit),

path('authenticate/', auth.authenticate_entry),
path('sqlworkflow_list/', sql_workflow.sql_workflow_list),
Expand Down Expand Up @@ -148,5 +149,7 @@
path('archive/once/', archiver.archive_once),
path('archive/log/', archiver.archive_log),

path('4admin/sync_ding_user/', ding_api.sync_ding_user)
path('4admin/sync_ding_user/', ding_api.sync_ding_user),

path('audit/log/', audit_log.audit_log),
]
6 changes: 6 additions & 0 deletions sql/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,3 +426,9 @@ def dbaprinciples(request):
with open(file, 'r') as f:
md = f.read().replace('\n', '\\n')
return render(request, 'dbaprinciples.html', {'md': md})


@superuser_required
def audit(request):
"""登录审计日志页面"""
return render(request, 'audit.html')
11 changes: 11 additions & 0 deletions src/init_sql/v1.8.3.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- 增加登录审计日志
CREATE TABLE `audit_log` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`user_id` int(11) DEFAULT NULL COMMENT '用户id',
`user_name` varchar(255) DEFAULT NULL COMMENT '用户名称',
`ip` varchar(255) DEFAULT NULL COMMENT '登录ip',
`action` varchar(255) DEFAULT NULL COMMENT '动作',
`action_time` datetime(6) NOT NULL COMMENT '操作时间',
PRIMARY KEY (`id`),
KEY `idx_username` (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='登录审计日志表';