diff --git a/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/AuditClient.java b/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/AuditClient.java index d759a79..7087c69 100644 --- a/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/AuditClient.java +++ b/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/AuditClient.java @@ -59,9 +59,10 @@ public void stopAudit() { // 输出审计事件 List auditEvents = currentAuditContext.getEvents(); - if (CollectionUtils.isNotEmpty(auditEvents)) { - eventExporter.export(auditEvents); + if (CollectionUtils.isEmpty(auditEvents)) { + return; } + eventExporter.export(auditEvents); } finally { LazyAuditContextHolder.get().reset(); } diff --git a/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/DefaultAuditEventBuilder.java b/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/DefaultAuditEventBuilder.java index 200e168..d1c582d 100644 --- a/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/DefaultAuditEventBuilder.java +++ b/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/DefaultAuditEventBuilder.java @@ -41,13 +41,29 @@ public List build() { Object originInstance = safeGetElement(originInstanceList, index); Object instance = safeGetElement(instanceList, index); Map attributes = buildMergedEventAttributes(instanceId, instanceName); - AuditEvent auditEvent = buildAuditEvent(instanceId, instanceName, originInstance, instance, - attributes, actionAuditContext.getExtendData()); + AuditEvent auditEvent = buildAuditEvent( + instanceId, + instanceName, + originInstance, + instance, + attributes, + actionAuditContext.getExtendData(), + actionAuditContext.getScopeType(), + actionAuditContext.getScopeId() + ); events.add(auditEvent); } } else { - AuditEvent auditEvent = buildAuditEvent(null, null, null, null, - actionAuditContext.getAttributes(), actionAuditContext.getExtendData()); + AuditEvent auditEvent = buildAuditEvent( + null, + null, + null, + null, + actionAuditContext.getAttributes(), + actionAuditContext.getExtendData(), + actionAuditContext.getScopeType(), + actionAuditContext.getScopeId() + ); events.add(auditEvent); } return events; @@ -69,12 +85,14 @@ protected T safeGetElement(List list, int index) { return list != null && list.size() > index ? list.get(index) : null; } - protected AuditEvent buildAuditEvent(String instanceId, - String instanceName, - Object originInstance, - Object instance, - Map attributes, - Map extendData) { + private AuditEvent buildAuditEvent(String instanceId, + String instanceName, + Object originInstance, + Object instance, + Map attributes, + Map extendData, + String scopeType, + String scopeId) { AuditEvent auditEvent = buildBasicAuditEvent(); // 审计记录 - 原始数据 @@ -86,6 +104,8 @@ protected AuditEvent buildAuditEvent(String instanceId, auditEvent.setInstanceName(instanceName); auditEvent.setContent(resolveAttributes(actionAuditContext.getContent(), attributes)); auditEvent.setExtendData(extendData); + auditEvent.setScopeType(scopeType); + auditEvent.setScopeId(scopeId); return auditEvent; } diff --git a/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/context/ActionAuditContext.java b/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/context/ActionAuditContext.java index c026c83..cf1b018 100644 --- a/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/context/ActionAuditContext.java +++ b/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/context/ActionAuditContext.java @@ -140,4 +140,13 @@ default AuditRunnable wrapActionRunnable(AuditRunnable auditRunnable) { Object getExtendDataValue(String key); + ActionAuditContext setScopeType(String scopeType); + + ActionAuditContext setScopeId(String scopeType); + + String getScopeType(); + + String getScopeId(); + + } diff --git a/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/context/ActionAuditContextBuilder.java b/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/context/ActionAuditContextBuilder.java index 7de5b6f..14cba01 100644 --- a/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/context/ActionAuditContextBuilder.java +++ b/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/context/ActionAuditContextBuilder.java @@ -50,6 +50,16 @@ public class ActionAuditContextBuilder { */ private Map attributes = new HashMap<>(); + /** + * 管理空间类型(比如 project/biz等) + */ + private String scopeType; + + /** + * 管理空间ID(比如项目ID、cmdb业务ID) + */ + private String scopeId; + public static ActionAuditContextBuilder builder(String actionId) { return new ActionAuditContextBuilder(actionId); } @@ -127,8 +137,18 @@ public ActionAuditContextBuilder addAttribute(String attrName, Object attrValue) return this; } + public ActionAuditContextBuilder setScopeType(String scopeType) { + this.scopeType = scopeType; + return this; + } + + public ActionAuditContextBuilder setScopeId(String scopeId) { + this.scopeId = scopeId; + return this; + } + public ActionAuditContext build() { return new SdkActionAuditContext(actionId, resourceType, instanceIdList, instanceNameList, - originInstanceList, instanceList, content, eventBuilder, attributes); + originInstanceList, instanceList, content, eventBuilder, attributes, scopeType, scopeId); } } diff --git a/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/context/InvalidActionAuditContext.java b/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/context/InvalidActionAuditContext.java index c88a97b..8f43de2 100644 --- a/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/context/InvalidActionAuditContext.java +++ b/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/context/InvalidActionAuditContext.java @@ -152,4 +152,24 @@ public Map getExtendData() { public Object getExtendDataValue(String key) { return null; } + + @Override + public ActionAuditContext setScopeType(String scopeType) { + return this; + } + + @Override + public ActionAuditContext setScopeId(String scopeType) { + return this; + } + + @Override + public String getScopeType() { + return null; + } + + @Override + public String getScopeId() { + return null; + } } diff --git a/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/context/SdkActionAuditContext.java b/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/context/SdkActionAuditContext.java index 2335cf6..cf655d2 100644 --- a/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/context/SdkActionAuditContext.java +++ b/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/context/SdkActionAuditContext.java @@ -81,6 +81,16 @@ public class SdkActionAuditContext implements ActionAuditContext { */ private boolean disabled; + /** + * 管理空间类型(比如 project/biz等) + */ + private String scopeType; + + /** + * 管理空间ID(比如项目ID、cmdb业务ID) + */ + private String scopeId; + SdkActionAuditContext(String actionId, String resourceType, List instanceIdList, @@ -89,7 +99,9 @@ public class SdkActionAuditContext implements ActionAuditContext { List instanceList, String content, Class eventBuilderClass, - Map attributes) { + Map attributes, + String scopeType, + String scopeId) { this.actionId = actionId; this.startTime = System.currentTimeMillis(); this.resourceType = resourceType; @@ -100,6 +112,8 @@ public class SdkActionAuditContext implements ActionAuditContext { this.content = content; this.eventBuilderClass = eventBuilderClass == null ? DefaultAuditEventBuilder.class : eventBuilderClass; this.attributes = (attributes == null ? new HashMap<>() : attributes); + this.scopeType = scopeType; + this.scopeId = scopeId; } @Override @@ -291,6 +305,28 @@ public Object getExtendDataValue(String key) { return extendData == null ? null : extendData.get(key); } + @Override + public ActionAuditContext setScopeType(String scopeType) { + this.scopeType = scopeType; + return this; + } + + @Override + public ActionAuditContext setScopeId(String scopeId) { + this.scopeId = scopeId; + return this; + } + + @Override + public String getScopeType() { + return this.scopeType; + } + + @Override + public String getScopeId() { + return this.scopeId; + } + @Override public String toString() { return new StringJoiner(", ", SdkActionAuditContext.class.getSimpleName() + "[", "]") @@ -308,6 +344,8 @@ public String toString() { .add("events=" + events) .add("disabled=" + disabled) .add("extendData=" + extendData) + .add("scopeType=" + scopeType) + .add("scopeId=" + scopeId) .toString(); } } diff --git a/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/context/SdkAuditContext.java b/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/context/SdkAuditContext.java index 30bffaa..e674605 100644 --- a/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/context/SdkAuditContext.java +++ b/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/context/SdkAuditContext.java @@ -4,9 +4,12 @@ import com.tencent.bk.audit.constants.AuditEventKey; import com.tencent.bk.audit.constants.Constants; import com.tencent.bk.audit.constants.UserIdentifyTypeEnum; +import com.tencent.bk.audit.filter.AuditPostFilter; +import com.tencent.bk.audit.filter.AuditPostFilters; import com.tencent.bk.audit.model.AuditEvent; import com.tencent.bk.audit.model.AuditHttpRequest; import com.tencent.bk.audit.utils.EventIdGenerator; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import java.util.*; @@ -111,6 +114,10 @@ public void addActionAuditContext(ActionAuditContext actionAuditContext) { public void end() { this.endTime = System.currentTimeMillis(); buildAuditEvents(); + List filters = AuditPostFilters.getFilters(); + if (CollectionUtils.isNotEmpty(filters)) { + filters.forEach(filter -> this.events.forEach(filter::map)); + } } private void buildAuditEvents() { diff --git a/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/filter/AuditPostFilter.java b/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/filter/AuditPostFilter.java new file mode 100644 index 0000000..f01e6eb --- /dev/null +++ b/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/filter/AuditPostFilter.java @@ -0,0 +1,18 @@ +package com.tencent.bk.audit.filter; + +import com.tencent.bk.audit.model.AuditEvent; + +/** + * 审计结束之前触发的 Filter,允许修改最终的审计事件。 + */ +public interface AuditPostFilter { + /** + * 修改审计事件 + * + * @param auditEvent 审计事件 + * @return 修改之后的审计事件 + */ + default AuditEvent map(AuditEvent auditEvent) { + return auditEvent; + } +} diff --git a/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/filter/AuditPostFilters.java b/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/filter/AuditPostFilters.java new file mode 100644 index 0000000..744d56a --- /dev/null +++ b/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/filter/AuditPostFilters.java @@ -0,0 +1,24 @@ +package com.tencent.bk.audit.filter; + +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; + +/** + * AuditPostFilter 管理 + */ +@Slf4j +public class AuditPostFilters { + private static final List filters = new ArrayList<>(); + + public static void addFilter(AuditPostFilter filter) { + log.info("Add AuditPostFilter: {}", filter); + filters.add(filter); + } + + public static List getFilters() { + return filters; + } + +} diff --git a/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/model/AuditEvent.java b/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/model/AuditEvent.java index 03f46aa..eea3c59 100644 --- a/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/model/AuditEvent.java +++ b/bk-audit-java-sdk/src/main/java/com/tencent/bk/audit/model/AuditEvent.java @@ -28,7 +28,7 @@ public class AuditEvent { @JsonProperty("event_content") private String content; - /** + /** 6;lL2 * 请求ID */ @JsonProperty("request_id") @@ -150,6 +150,17 @@ public class AuditEvent { @JsonProperty("audit_event_signature") private String auditEventSignature = Constants.AUDIT_EVENT_SIGNATURE; + /** + * 管理空间类型(比如 project/biz等) + */ + @JsonProperty("scope_type") + private String scopeType; + + /** + * 管理空间ID(比如项目ID、cmdb业务ID) + */ + @JsonProperty("scope_id") + private String scopeId; public AuditEvent() { } diff --git a/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/AuditTest.java b/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/AuditTest.java index 54b47c2..5fa0fc1 100644 --- a/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/AuditTest.java +++ b/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/AuditTest.java @@ -5,10 +5,7 @@ import com.tencent.bk.audit.constants.UserIdentifyTypeEnum; import com.tencent.bk.audit.context.ActionAuditContext; import com.tencent.bk.audit.context.AuditContext; -import com.tencent.bk.audit.example.DisableActionAuditContextExample; -import com.tencent.bk.audit.example.MultiAuditEventExample; -import com.tencent.bk.audit.example.SingleAuditEventExample; -import com.tencent.bk.audit.example.SubAuditEventExample; +import com.tencent.bk.audit.example.*; import com.tencent.bk.audit.exporter.EventExporter; import com.tencent.bk.audit.model.AuditEvent; import org.junit.jupiter.api.AfterEach; @@ -76,6 +73,8 @@ void testSingleAuditEvent() { assertEquals(Constants.RESULT_SUCCESS_DESC, auditEvent.getResultContent()); assertNotNull(auditEvent.getStartTime()); assertNotNull(auditEvent.getEndTime()); + assertEquals("biz", auditEvent.getScopeType()); + assertEquals("2", auditEvent.getScopeId()); } @Test @@ -111,6 +110,8 @@ void testMultiAuditEvent() { assertEquals(Constants.RESULT_SUCCESS_DESC, auditEvent.getResultContent()); assertNotNull(auditEvent.getStartTime()); assertNotNull(auditEvent.getEndTime()); + assertEquals("biz", auditEvent.getScopeType()); + assertEquals("2", auditEvent.getScopeId()); }); } @@ -174,6 +175,8 @@ void testSubAuditEvent() { assertEquals(Constants.RESULT_SUCCESS_DESC, auditEvent.getResultContent()); assertNotNull(auditEvent.getStartTime()); assertNotNull(auditEvent.getEndTime()); + assertEquals("biz", auditEvent.getScopeType()); + assertEquals("2", auditEvent.getScopeId()); }); } @@ -196,6 +199,8 @@ void testAuditActionException() { .setInstanceId("1000") .setInstanceName("test_audit_execute_job_plan") .setContent("Execute job plan [{{" + INSTANCE_NAME + "}}]({{" + INSTANCE_ID + "}})") + .setScopeType("biz") + .setScopeId("2") .build() .wrapActionRunnable(() -> { throw new RuntimeException("Execute job plan error"); @@ -239,5 +244,41 @@ void whenDisableActionAuditContextThenDoNotBuildAuditEvent() { verify(eventExporter, never()).export(anyList()); } + @Test + @DisplayName("验证AuditPostFilter") + void testAuditPostFilter() { + AuditPostFilterExample example = new AuditPostFilterExample(auditClient); + example.run(); + + ArgumentCaptor> argument = ArgumentCaptor.forClass(Collection.class); + verify(eventExporter).export(argument.capture()); + verify(eventExporter).export(anyList()); + + Collection auditEvents = argument.getValue(); + assertThat(auditEvents).hasSize(1); + AuditEvent auditEvent = auditEvents.stream().findAny().orElse(null); + assertThat(auditEvent).isNotNull(); + assertNotNull(auditEvent.getId()); + assertEquals("bk_job", auditEvent.getSystemId()); + assertEquals("execute_job_plan", auditEvent.getActionId()); + assertEquals("job_plan", auditEvent.getResourceTypeId()); + assertEquals("1000", auditEvent.getInstanceId()); + assertEquals("test_audit_execute_job_plan", auditEvent.getInstanceName()); + assertEquals("3a84858499bd71d674bc40d4f73cb41a", auditEvent.getRequestId()); + assertEquals("admin", auditEvent.getUsername()); + assertEquals(UserIdentifyTypeEnum.PERSONAL.getValue(), auditEvent.getUserIdentifyType()); + assertEquals("127.0.0.1", auditEvent.getAccessSourceIp()); + assertEquals("Chrome", auditEvent.getAccessUserAgent()); + assertEquals(AccessTypeEnum.CONSOLE.getValue(), auditEvent.getAccessType()); + assertEquals("Execute job plan [test_audit_execute_job_plan](1000)", auditEvent.getContent()); + assertEquals("bk_audit_event", auditEvent.getAuditEventSignature()); + assertEquals(Constants.RESULT_CODE_SUCCESS, auditEvent.getResultCode()); + assertEquals(Constants.RESULT_SUCCESS_DESC, auditEvent.getResultContent()); + assertNotNull(auditEvent.getStartTime()); + assertNotNull(auditEvent.getEndTime()); + // 验证在 filter 中加入的扩展数据 "test" 的值 + assertEquals("AuditPostFilterTest", auditEvent.getExtendData().get("test")); + } + } diff --git a/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/example/AuditPostFilterExample.java b/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/example/AuditPostFilterExample.java new file mode 100644 index 0000000..94902ca --- /dev/null +++ b/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/example/AuditPostFilterExample.java @@ -0,0 +1,61 @@ +package com.tencent.bk.audit.example; + +import com.tencent.bk.audit.AuditClient; +import com.tencent.bk.audit.constants.AccessTypeEnum; +import com.tencent.bk.audit.constants.UserIdentifyTypeEnum; +import com.tencent.bk.audit.context.ActionAuditContext; +import com.tencent.bk.audit.context.AuditContext; +import com.tencent.bk.audit.filter.AuditPostFilter; +import com.tencent.bk.audit.filter.AuditPostFilters; +import com.tencent.bk.audit.model.AuditEvent; + +import static com.tencent.bk.audit.constants.AuditAttributeNames.INSTANCE_ID; +import static com.tencent.bk.audit.constants.AuditAttributeNames.INSTANCE_NAME; + +/** + * AuditPostFilter 的使用 + */ +public class AuditPostFilterExample { + private final AuditClient auditClient; + + public AuditPostFilterExample(AuditClient auditClient) { + this.auditClient = auditClient; + AuditPostFilters.addFilter(new AuditPostFilter() { + @Override + public AuditEvent map(AuditEvent auditEvent) { + auditEvent.addExtendData("test", "AuditPostFilterTest"); + return auditEvent; + } + }); + } + + public void run() { + // 构造审计上下文 + AuditContext auditContext = auditClient.auditContextBuilder("execute_job_plan") + .setSystemId("bk_job") + .setRequestId("3a84858499bd71d674bc40d4f73cb41a") + .setAccessSourceIp("127.0.0.1") + .setAccessType(AccessTypeEnum.CONSOLE) + .setAccessUserAgent("Chrome") + .setUserIdentifyType(UserIdentifyTypeEnum.PERSONAL) + .setUsername("admin") + .build(); + // 对操作进行审计 + auditClient.audit(auditContext, this::action); + } + + private void action() { + // 使用 ActionAuditContext 封装 Action 代码,自动封装审计逻辑 + ActionAuditContext.builder("execute_job_plan") + .setResourceType("job_plan") + .setInstanceId("1000") + .setInstanceName("test_audit_execute_job_plan") + .setContent("Execute job plan [{{" + INSTANCE_NAME + "}}]({{" + INSTANCE_ID + "}})") + .build() + .wrapActionRunnable(() -> { + // action code + ActionAuditContext.current().addAttribute("host_id", "1,2,3,4"); + }) + .run(); + } +} diff --git a/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/example/DisableActionAuditContextExample.java b/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/example/DisableActionAuditContextExample.java index 5194726..de52e95 100644 --- a/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/example/DisableActionAuditContextExample.java +++ b/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/example/DisableActionAuditContextExample.java @@ -41,6 +41,8 @@ private void action() { .setInstanceId("1000") .setInstanceName("test_audit_execute_job_plan") .setContent("Execute job plan [{{" + INSTANCE_NAME + "}}]({{" + INSTANCE_ID + "}})") + .setScopeType("biz") + .setScopeId("2") .build() .wrapActionRunnable(() -> { // action code diff --git a/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/example/MultiAuditEventExample.java b/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/example/MultiAuditEventExample.java index 934a4ac..6e7d2ad 100644 --- a/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/example/MultiAuditEventExample.java +++ b/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/example/MultiAuditEventExample.java @@ -44,6 +44,8 @@ private void batchAction() { .setInstanceIdList(Arrays.asList("1000", "1001")) .setInstanceNameList(Arrays.asList("plan1", "plan2")) .setContent("Edit job plan [{{" + INSTANCE_NAME + "}}]({{" + INSTANCE_ID + "}})") + .setScopeType("biz") + .setScopeId("2") .build() .wrapActionRunnable(() -> { // action code diff --git a/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/example/SingleAuditEventExample.java b/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/example/SingleAuditEventExample.java index 72fd1a3..2877f98 100644 --- a/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/example/SingleAuditEventExample.java +++ b/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/example/SingleAuditEventExample.java @@ -41,6 +41,8 @@ private void action() { .setInstanceId("1000") .setInstanceName("test_audit_execute_job_plan") .setContent("Execute job plan [{{" + INSTANCE_NAME + "}}]({{" + INSTANCE_ID + "}})") + .setScopeType("biz") + .setScopeId("2") .build() .wrapActionRunnable(() -> { // action code diff --git a/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/example/SubAuditEventExample.java b/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/example/SubAuditEventExample.java index 24a0d02..96218b8 100644 --- a/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/example/SubAuditEventExample.java +++ b/bk-audit-java-sdk/src/test/java/com/tencent/bk/audit/example/SubAuditEventExample.java @@ -51,6 +51,8 @@ private void deleteJobTemplate() { .setInstanceId("1000") .setInstanceName("job_template_1") .setContent("Delete job template [{{" + INSTANCE_NAME + "}}]({{" + INSTANCE_ID + "}})") + .setScopeType("biz") + .setScopeId("2") .build() .wrapActionRunnable(() -> { // action code @@ -66,6 +68,8 @@ private void batchDeleteJobPlan() { .setInstanceIdList(Arrays.asList("1001", "1002")) .setInstanceNameList(Arrays.asList("plan1", "plan2")) .setContent("Delete job plan [{{" + INSTANCE_NAME + "}}]({{" + INSTANCE_ID + "}})") + .setScopeType("biz") + .setScopeId("2") .build() .wrapActionRunnable(() -> { // action code diff --git a/docs/usage.md b/docs/usage.md index f5b5467..09eaa52 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -527,6 +527,21 @@ public class JobExecuteService { } ``` +#### AuditPostFilter +如果需要在审计结束之前修改审计事件内容,可以自定义 com.tencent.bk.audit.filter.AuditPostFilter 并声明为 Spring Component. + +``` +@Component +public class CustomAuditPostFilter implements AuditPostFilter { + + @Override + public AuditEvent map(AuditEvent auditEvent) { + // 对于生成的审计事件,添加自定义的扩展数据 + auditEvent.addExtendData("test", "SpringBootAuditPostFilterTest"); + return auditEvent; + } +} +``` ## 示例代码 diff --git a/gradle.properties b/gradle.properties index 13d2ff8..4877ce3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # SDK 版本号 -version=1.0.7-SNAPSHOT +version=1.0.8-SNAPSHOT org.gradle.caching=true org.gradle.parallel=true diff --git a/spring-boot-bk-audit-starter/src/main/java/com/tencent/bk/audit/ActionAuditAspect.java b/spring-boot-bk-audit-starter/src/main/java/com/tencent/bk/audit/ActionAuditAspect.java index 9aa36fd..38294c4 100644 --- a/spring-boot-bk-audit-starter/src/main/java/com/tencent/bk/audit/ActionAuditAspect.java +++ b/spring-boot-bk-audit-starter/src/main/java/com/tencent/bk/audit/ActionAuditAspect.java @@ -149,6 +149,12 @@ private void parseActionAuditRecordSpEL(ProceedingJoinPoint pjp, auditActionContext.addAttribute(auditAttribute.name(), value); } } + if (StringUtils.isNotEmpty(record.scopeType())) { + auditActionContext.setScopeType(parseBySpel(evaluationContext, record.scopeType()).toString()); + } + if (StringUtils.isNotEmpty(record.scopeId())) { + auditActionContext.setScopeId((parseBySpel(evaluationContext, record.scopeId())).toString()); + } } private void parseInstanceIdList(ActionAuditRecord record, diff --git a/spring-boot-bk-audit-starter/src/main/java/com/tencent/bk/audit/AuditApplicationRunner.java b/spring-boot-bk-audit-starter/src/main/java/com/tencent/bk/audit/AuditApplicationRunner.java new file mode 100644 index 0000000..045d931 --- /dev/null +++ b/spring-boot-bk-audit-starter/src/main/java/com/tencent/bk/audit/AuditApplicationRunner.java @@ -0,0 +1,44 @@ +package com.tencent.bk.audit; + +import com.tencent.bk.audit.filter.AuditPostFilter; +import com.tencent.bk.audit.filter.AuditPostFilters; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.MapUtils; +import org.springframework.beans.BeansException; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 审计SDK初始化 + */ +@Slf4j +public class AuditApplicationRunner implements ApplicationRunner, ApplicationContextAware { + + private ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + public void run(ApplicationArguments args) { + log.info("AuditApplicationRunner start"); + Map auditPostFilterMap = + applicationContext.getBeansOfType(AuditPostFilter.class); + if (MapUtils.isEmpty(auditPostFilterMap)) { + return; + } + List filters = new ArrayList<>(auditPostFilterMap.values()); + AnnotationAwareOrderComparator.sort(filters); + filters.forEach(AuditPostFilters::addFilter); + log.info("AuditApplicationRunner run success"); + } +} diff --git a/spring-boot-bk-audit-starter/src/main/java/com/tencent/bk/audit/AuditAspect.java b/spring-boot-bk-audit-starter/src/main/java/com/tencent/bk/audit/AuditAspect.java index d71aa55..ef03048 100644 --- a/spring-boot-bk-audit-starter/src/main/java/com/tencent/bk/audit/AuditAspect.java +++ b/spring-boot-bk-audit-starter/src/main/java/com/tencent/bk/audit/AuditAspect.java @@ -36,7 +36,8 @@ public class AuditAspect { public AuditAspect(AuditClient auditClient, AuditRequestProvider auditRequestProvider, AuditExceptionResolver auditExceptionResolver, - AuditProperties auditProperties, AuditMetrics auditMetrics) { + AuditProperties auditProperties, + AuditMetrics auditMetrics) { this.auditClient = auditClient; this.auditRequestProvider = auditRequestProvider; this.auditExceptionResolver = auditExceptionResolver; diff --git a/spring-boot-bk-audit-starter/src/main/java/com/tencent/bk/audit/annotations/ActionAuditRecord.java b/spring-boot-bk-audit-starter/src/main/java/com/tencent/bk/audit/annotations/ActionAuditRecord.java index 7f04713..bd8fac1 100644 --- a/spring-boot-bk-audit-starter/src/main/java/com/tencent/bk/audit/annotations/ActionAuditRecord.java +++ b/spring-boot-bk-audit-starter/src/main/java/com/tencent/bk/audit/annotations/ActionAuditRecord.java @@ -38,4 +38,14 @@ * 自定义审计事件Builder */ Class builder() default DefaultAuditEventBuilder.class; + + /** + * 管理空间类型(比如 project/biz等) - SpEL表达式 + */ + String scopeType() default ""; + + /** + * 管理空间ID(比如项目ID、cmdb业务ID) - SpEL表达式 + */ + String scopeId() default ""; } diff --git a/spring-boot-bk-audit-starter/src/main/java/com/tencent/bk/audit/config/AuditAutoConfiguration.java b/spring-boot-bk-audit-starter/src/main/java/com/tencent/bk/audit/config/AuditAutoConfiguration.java index d70ea71..fc0ff32 100644 --- a/spring-boot-bk-audit-starter/src/main/java/com/tencent/bk/audit/config/AuditAutoConfiguration.java +++ b/spring-boot-bk-audit-starter/src/main/java/com/tencent/bk/audit/config/AuditAutoConfiguration.java @@ -4,6 +4,7 @@ import com.tencent.bk.audit.constants.ExporterTypeEnum; import com.tencent.bk.audit.exporter.EventExporter; import com.tencent.bk.audit.exporter.LogFileEventExporter; +import com.tencent.bk.audit.filter.AuditPostFilters; import com.tencent.bk.audit.metrics.AuditMetrics; import io.micrometer.core.instrument.MeterRegistry; import lombok.extern.slf4j.Slf4j; @@ -74,4 +75,9 @@ public ActionAuditAspect actionAuditRecordAspect(AuditClient auditClient, AuditM public AuditMetrics auditMetrics(ObjectProvider meterRegistryObjectProvider) { return new AuditMetrics(meterRegistryObjectProvider.getIfAvailable()); } + + @Bean + public AuditApplicationRunner auditApplicationRunner() { + return new AuditApplicationRunner(); + } } diff --git a/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/AuditSpringBootTest.java b/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/AuditSpringBootTest.java index c9ba67f..645b792 100644 --- a/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/AuditSpringBootTest.java +++ b/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/AuditSpringBootTest.java @@ -15,7 +15,6 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; -import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -58,7 +57,7 @@ void reset() { public void testSimpleAudit1() throws Exception { String requestId = UUID.randomUUID().toString(); this.mockMvc.perform( - get("/test/audit/action/getJobTemplateById/template/1") + get("/test/audit/action/scope/biz/2/template/1") .header(DefaultAuditRequestProvider.HEADER_USERNAME, "tyler") .header(DefaultAuditRequestProvider.HEADER_REQUEST_ID, requestId) .header(DefaultAuditRequestProvider.HEADER_ACCESS_TYPE, AccessTypeEnum.WEB.getValue()) @@ -95,7 +94,8 @@ public void testSimpleAudit1() throws Exception { assertEquals(Constants.RESULT_SUCCESS_DESC, auditEvent.getResultContent()); assertNotNull(auditEvent.getStartTime()); assertNotNull(auditEvent.getEndTime()); - + assertEquals("biz", auditEvent.getScopeType()); + assertEquals("2", auditEvent.getScopeId()); } @Test @@ -106,7 +106,7 @@ public void testSimpleAudit2() throws Exception { request.setName("test_audit_create_job_template"); request.setDescription("test_audit_create_job_template_desc"); MvcResult mockResult = this.mockMvc.perform( - post("/test/audit/action/createJobTemplate") + post("/test/audit/action/scope/biz/2/template") .contentType(MediaType.APPLICATION_JSON) .content(JsonUtils.toJson(request)) .header(DefaultAuditRequestProvider.HEADER_USERNAME, "tyler") @@ -150,6 +150,9 @@ public void testSimpleAudit2() throws Exception { assertEquals(Constants.RESULT_SUCCESS_DESC, auditEvent.getResultContent()); assertNotNull(auditEvent.getStartTime()); assertNotNull(auditEvent.getEndTime()); + assertEquals("biz", auditEvent.getScopeType()); + assertEquals("2", auditEvent.getScopeId()); + } @@ -159,7 +162,7 @@ public void testSimpleAudit2() throws Exception { public void testAuditMultiAction() throws Exception { String requestId = UUID.randomUUID().toString(); this.mockMvc.perform( - delete("/test/audit/action/deleteJobTemplate/template/1000") + delete("/test/audit/action/scope/biz/2/template/1000") .contentType(MediaType.APPLICATION_JSON) .header(DefaultAuditRequestProvider.HEADER_USERNAME, "tyler") .header(DefaultAuditRequestProvider.HEADER_REQUEST_ID, requestId) @@ -217,6 +220,8 @@ public void testAuditMultiAction() throws Exception { assertNotNull(auditEvent.getAccessSourceIp()); assertEquals("Chrome", auditEvent.getAccessUserAgent()); assertEquals(AccessTypeEnum.WEB.getValue(), auditEvent.getAccessType()); + assertEquals("biz", auditEvent.getScopeType()); + assertEquals("2", auditEvent.getScopeId()); }); } @@ -272,4 +277,50 @@ public void testCustomAuditEventBuilder() throws Exception { assertNotNull(auditEvent.getStartTime()); assertNotNull(auditEvent.getEndTime()); } + + @Test + @DisplayName("测试 AuditPostFilter 自动注册并生效") + public void testAuditPostFilter() throws Exception { + String requestId = UUID.randomUUID().toString(); + this.mockMvc.perform( + get("/test/audit/action/scope/biz/2/template/1") + .header(DefaultAuditRequestProvider.HEADER_USERNAME, "tyler") + .header(DefaultAuditRequestProvider.HEADER_REQUEST_ID, requestId) + .header(DefaultAuditRequestProvider.HEADER_ACCESS_TYPE, AccessTypeEnum.WEB.getValue()) + .header(DefaultAuditRequestProvider.HEADER_USER_IDENTIFY_TYPE, + UserIdentifyTypeEnum.PERSONAL.getValue()) + .header("User-Agent", "Chrome") + .header(DefaultAuditRequestProvider.HEADER_USERNAME, "tyler")) + .andExpect(status().isOk()); + + ArgumentCaptor> argument = ArgumentCaptor.forClass(Collection.class); + verify(eventExporter).export(argument.capture()); + verify(eventExporter).export(anyList()); + + Collection auditEvents = argument.getValue(); + assertThat(auditEvents).hasSize(1); + AuditEvent auditEvent = auditEvents.stream().findAny().orElse(null); + assertThat(auditEvent).isNotNull(); + assertNotNull(auditEvent.getId()); + assertEquals("bk_job", auditEvent.getSystemId()); + assertEquals("bk_job", auditEvent.getBkAppCode()); + assertEquals("view_job_template", auditEvent.getActionId()); + assertEquals("job_template", auditEvent.getResourceTypeId()); + assertEquals("1", auditEvent.getInstanceId()); + assertEquals("job_template_1", auditEvent.getInstanceName()); + assertEquals(requestId, auditEvent.getRequestId()); + assertEquals("tyler", auditEvent.getUsername()); + assertEquals(UserIdentifyTypeEnum.PERSONAL.getValue(), auditEvent.getUserIdentifyType()); + assertNotNull(auditEvent.getAccessSourceIp()); + assertEquals("Chrome", auditEvent.getAccessUserAgent()); + assertEquals(AccessTypeEnum.WEB.getValue(), auditEvent.getAccessType()); + assertEquals("View job template [job_template_1](1)", auditEvent.getContent()); + assertEquals("bk_audit_event", auditEvent.getAuditEventSignature()); + assertEquals(Constants.RESULT_CODE_SUCCESS, auditEvent.getResultCode()); + assertEquals(Constants.RESULT_SUCCESS_DESC, auditEvent.getResultContent()); + assertNotNull(auditEvent.getStartTime()); + assertNotNull(auditEvent.getEndTime()); + // 验证在 filter 中加入的扩展数据 "test" 的值 + assertEquals("SpringBootAuditPostFilterTest", auditEvent.getExtendData().get("test")); + } } diff --git a/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/CustomAuditPostFilter.java b/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/CustomAuditPostFilter.java new file mode 100644 index 0000000..c4e3210 --- /dev/null +++ b/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/CustomAuditPostFilter.java @@ -0,0 +1,15 @@ +package com.tencent.bk.audit; + +import com.tencent.bk.audit.filter.AuditPostFilter; +import com.tencent.bk.audit.model.AuditEvent; +import org.springframework.stereotype.Component; + +@Component +public class CustomAuditPostFilter implements AuditPostFilter { + + @Override + public AuditEvent map(AuditEvent auditEvent) { + auditEvent.addExtendData("test", "SpringBootAuditPostFilterTest"); + return auditEvent; + } +} diff --git a/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/controller/AuditTestController.java b/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/controller/AuditTestController.java index 95152ee..dc8474e 100644 --- a/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/controller/AuditTestController.java +++ b/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/controller/AuditTestController.java @@ -38,21 +38,29 @@ public AuditTestController(JobTemplateService jobTemplateService, instanceIds = "#templateId", instanceNames = "#$?.name" ), + scopeType = "#scopeType", + scopeId = "#scopeId", content = "View job template [{{" + INSTANCE_NAME + "}}]({{" + INSTANCE_ID + "}})" ) - @GetMapping("/getJobTemplateById/template/{templateId}") - public JobTemplate getJobTemplateById(@PathVariable("templateId") Long templateId) { - return jobTemplateService.getTemplateById(templateId); + @GetMapping("/scope/{scopeType}/{scopeId}/template/{templateId}") + public JobTemplate getJobTemplateById( + @PathVariable("scopeType") String scopeType, + @PathVariable("scopeId") String scopeId, + @PathVariable("templateId") Long templateId) { + return jobTemplateService.getTemplateById(Long.valueOf(scopeId), templateId); } @AuditEntry( actionId = "create_job_template" ) - @PostMapping("/createJobTemplate") - public JobTemplate createJobTemplate(@AuditRequestBody - @RequestBody - CreateJobTemplateRequest request) { - return jobTemplateService.createJobTemplate(request.getName(), request.getDescription()); + @PostMapping("/scope/{scopeType}/{scopeId}/template") + public JobTemplate createJobTemplate( + @PathVariable("scopeType") String scopeType, + @PathVariable("scopeId") String scopeId, + @AuditRequestBody + @RequestBody + CreateJobTemplateRequest request) { + return jobTemplateService.createJobTemplate(Long.valueOf(scopeId), request.getName(), request.getDescription()); } /** @@ -64,9 +72,12 @@ public JobTemplate createJobTemplate(@AuditRequestBody actionId = "delete_job_template", subActionIds = "delete_job_plan" ) - @DeleteMapping("/deleteJobTemplate/template/{templateId}") - public void deleteJobTemplate(@PathVariable("templateId") Long templateId) { - jobTemplateService.deleteJobTemplate(templateId); + @DeleteMapping("/scope/{scopeType}/{scopeId}/template/{templateId}") + public void deleteJobTemplate( + @PathVariable("scopeType") String scopeType, + @PathVariable("scopeId") String scopeId, + @PathVariable("templateId") Long templateId) { + jobTemplateService.deleteJobTemplate(Long.valueOf(scopeId), templateId); } diff --git a/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/model/JobPlan.java b/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/model/JobPlan.java index 96da10e..9c20335 100644 --- a/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/model/JobPlan.java +++ b/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/model/JobPlan.java @@ -1,8 +1,21 @@ package com.tencent.bk.audit.model; +/** + * 作业执行方案 + */ public class JobPlan { + /** + * ID + */ private Long id; + /** + * 执行方案名称 + */ private String name; + /** + * CMDB 业务 ID + */ + private Long bizId; public Long getId() { return id; @@ -19,4 +32,12 @@ public String getName() { public void setName(String name) { this.name = name; } + + public Long getBizId() { + return bizId; + } + + public void setBizId(Long bizId) { + this.bizId = bizId; + } } diff --git a/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/model/JobTemplate.java b/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/model/JobTemplate.java index f8f6827..20e5b47 100644 --- a/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/model/JobTemplate.java +++ b/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/model/JobTemplate.java @@ -1,8 +1,24 @@ package com.tencent.bk.audit.model; +/** + * 作业模版 + */ public class JobTemplate { + /** + * ID + */ private Long id; + /** + * CMDB 业务 ID + */ + private Long bizId; + /** + * 名称 + */ private String name; + /** + * 描述 + */ private String description; public Long getId() { @@ -28,4 +44,12 @@ public String getDescription() { public void setDescription(String description) { this.description = description; } + + public Long getBizId() { + return bizId; + } + + public void setBizId(Long bizId) { + this.bizId = bizId; + } } diff --git a/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/service/JobPlanService.java b/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/service/JobPlanService.java index 1a7d701..789ffa0 100644 --- a/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/service/JobPlanService.java +++ b/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/service/JobPlanService.java @@ -17,10 +17,11 @@ @Service public class JobPlanService { - public JobPlan getPlanById(long planId) { + public JobPlan getPlanById(Long bizId, long planId) { JobPlan plan = new JobPlan(); plan.setId(planId); plan.setName(buildPlanName(planId)); + plan.setBizId(bizId); return plan; } @@ -49,10 +50,12 @@ private String buildPlanName(long planId) { instanceIds = "#planId", instanceNames = "#$?.name" ), + scopeType = "'biz'", + scopeId = "#bizId", content = "Delete job plan [{{" + INSTANCE_NAME + "}}]({{" + INSTANCE_ID + "}})" ) - public JobPlan deleteJobPlan(Long planId) { - JobPlan jobPlan = getPlanById(planId); + public JobPlan deleteJobPlan(Long bizId, Long planId) { + JobPlan jobPlan = getPlanById(bizId, planId); // delete plan code here return jobPlan; } diff --git a/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/service/JobTemplateService.java b/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/service/JobTemplateService.java index d2b1fd8..18e2214 100644 --- a/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/service/JobTemplateService.java +++ b/spring-boot-bk-audit-starter/src/test/java/com/tencent/bk/audit/service/JobTemplateService.java @@ -27,8 +27,9 @@ public JobTemplateService(JobPlanService jobPlanService) { this.jobPlanService = jobPlanService; } - public JobTemplate getTemplateById(long templateId) { + public JobTemplate getTemplateById(Long bizId, long templateId) { JobTemplate template = new JobTemplate(); + template.setBizId(bizId); template.setId(templateId); template.setName(buildTemplateName(templateId)); template.setDescription("job_template_desc_" + templateId); @@ -46,14 +47,17 @@ private String buildTemplateName(long templateId) { instanceIds = "#$?.id", instanceNames = "#$?.name" ), + scopeType = "'biz'", + scopeId = "#bizId", content = "Create job template [{{" + INSTANCE_NAME + "}}]({{" + INSTANCE_ID + "}})" ) - public JobTemplate createJobTemplate(String templateName, String templateDesc) { + public JobTemplate createJobTemplate(Long bizId, String templateName, String templateDesc) { JobTemplate template = new JobTemplate(); long templateId = Math.abs(random.nextLong()); template.setId(templateId); template.setName(templateName); template.setDescription(templateDesc); + template.setBizId(bizId); return template; } @@ -64,17 +68,19 @@ public JobTemplate createJobTemplate(String templateName, String templateDesc) { instanceIds = "#templateId", instanceNames = "#$?.name" ), + scopeType = "'biz'", + scopeId = "#bizId", content = "Delete job template [{{" + INSTANCE_NAME + "}}]({{" + INSTANCE_ID + "}})" ) - public JobTemplate deleteJobTemplate(Long templateId) { - JobTemplate jobTemplate = getTemplateById(templateId); - deleteJobPlansByTemplateId(templateId); + public JobTemplate deleteJobTemplate(Long bizId, Long templateId) { + JobTemplate jobTemplate = getTemplateById(bizId, templateId); + deleteJobPlansByTemplateId(bizId, templateId); return jobTemplate; } - private void deleteJobPlansByTemplateId(long templateId) { + private void deleteJobPlansByTemplateId(Long bizId, long templateId) { List planList = jobPlanService.getPlanByTemplateId(templateId); - planList.forEach(plan -> jobPlanService.deleteJobPlan(plan.getId())); + planList.forEach(plan -> jobPlanService.deleteJobPlan(bizId, plan.getId())); }