From a83c90b552576dad65f5b4ecf6f71873c599c073 Mon Sep 17 00:00:00 2001 From: rahul-chekuri Date: Mon, 25 Sep 2023 19:50:54 +0530 Subject: [PATCH] OP-21175: Publish user details as events to rabbitmq. --- gate-web/gate-web.gradle | 4 +- .../spinnaker/gate/config/CamelConfig.java | 51 +++++++++++++++++++ .../gate/config/CamelRouteConfig.java | 21 ++++++++ .../gate/config/MessageBrokerProperties.java | 49 ++++++++++++++++++ .../spinnaker/gate/config/RabbitMQConfig.java | 51 +++++++++++++++++++ .../gate/config/UserActivityRouteBuilder.java | 41 +++++++++++++++ .../spinnaker/gate/audit/AuditHandler.java | 2 +- .../gate/audit/AuditRestApiHandler.java | 9 +++- .../audit/AuthenticationAuditListener.java | 46 ++++++++++++++--- .../gate/audit/UserActivityAuditListener.java | 30 +++++++++-- .../gate/constant/CamelEndpointConstant.java | 21 ++++++++ .../opsmx/spinnaker/gate/model/AuditData.java | 8 +-- 12 files changed, 316 insertions(+), 17 deletions(-) create mode 100644 gate-web/src/main/java/com/netflix/spinnaker/gate/config/CamelConfig.java create mode 100644 gate-web/src/main/java/com/netflix/spinnaker/gate/config/CamelRouteConfig.java create mode 100644 gate-web/src/main/java/com/netflix/spinnaker/gate/config/MessageBrokerProperties.java create mode 100644 gate-web/src/main/java/com/netflix/spinnaker/gate/config/RabbitMQConfig.java create mode 100644 gate-web/src/main/java/com/netflix/spinnaker/gate/config/UserActivityRouteBuilder.java create mode 100644 gate-web/src/main/java/com/opsmx/spinnaker/gate/constant/CamelEndpointConstant.java diff --git a/gate-web/gate-web.gradle b/gate-web/gate-web.gradle index b6002655e7..b9a6210bcd 100644 --- a/gate-web/gate-web.gradle +++ b/gate-web/gate-web.gradle @@ -56,7 +56,9 @@ dependencies { implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-openfeign', version: '2.2.4.RELEASE' - + implementation 'org.apache.camel:camel-core:3.14.1' + implementation 'org.apache.camel:camel-rabbitmq:3.14.1' + implementation 'org.apache.camel:camel-jackson:3.14.1' runtimeOnly "io.spinnaker.kork:kork-runtime" runtimeOnly "org.springframework.boot:spring-boot-properties-migrator" diff --git a/gate-web/src/main/java/com/netflix/spinnaker/gate/config/CamelConfig.java b/gate-web/src/main/java/com/netflix/spinnaker/gate/config/CamelConfig.java new file mode 100644 index 0000000000..b42b37e9bc --- /dev/null +++ b/gate-web/src/main/java/com/netflix/spinnaker/gate/config/CamelConfig.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015 OpsMx, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.gate.config; + +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; +import org.apache.camel.CamelContext; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.impl.DefaultCamelContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Slf4j +@Configuration +@ConditionalOnExpression("${message-broker.enabled:true}") +public class CamelConfig { + + @Autowired private UserActivityRouteBuilder userActivityRouteBuilder; + + @Bean + public CamelContext camelContext() throws Exception { + CamelContext camelContext = new DefaultCamelContext(); + camelContext.addRoutes(userActivityRouteBuilder); + camelContext.getShutdownStrategy().setShutdownNowOnTimeout(true); + camelContext.getShutdownStrategy().setTimeout(5); + camelContext.getShutdownStrategy().setTimeUnit(TimeUnit.SECONDS); + camelContext.start(); + return camelContext; + } + + @Bean + public ProducerTemplate producerTemplate(CamelContext camelContext) { + return camelContext.createProducerTemplate(); + } +} diff --git a/gate-web/src/main/java/com/netflix/spinnaker/gate/config/CamelRouteConfig.java b/gate-web/src/main/java/com/netflix/spinnaker/gate/config/CamelRouteConfig.java new file mode 100644 index 0000000000..fb3491dc44 --- /dev/null +++ b/gate-web/src/main/java/com/netflix/spinnaker/gate/config/CamelRouteConfig.java @@ -0,0 +1,21 @@ +/* + * Copyright 2015 OpsMx, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.gate.config; + +public interface CamelRouteConfig { + String getUserActivityQueueEndPoint(); +} diff --git a/gate-web/src/main/java/com/netflix/spinnaker/gate/config/MessageBrokerProperties.java b/gate-web/src/main/java/com/netflix/spinnaker/gate/config/MessageBrokerProperties.java new file mode 100644 index 0000000000..3ff3bcf893 --- /dev/null +++ b/gate-web/src/main/java/com/netflix/spinnaker/gate/config/MessageBrokerProperties.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015 OpsMx, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.gate.config; + +import lombok.Data; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +@ConfigurationProperties(prefix = "message-broker") +@ConditionalOnExpression("${message-broker.enabled:true}") +@EnableConfigurationProperties({ + MessageBrokerProperties.class, + MessageBrokerProperties.Endpoint.class +}) +public class MessageBrokerProperties { + + private boolean enabled; + private String username; + private String password; + private String host; + private String port; + private Endpoint endpoint; + + @Data + @Configuration + @ConditionalOnExpression("${message-broker.enabled:true}") + @ConfigurationProperties(prefix = "message-broker.endpoint") + public static class Endpoint { + private String name; + } +} diff --git a/gate-web/src/main/java/com/netflix/spinnaker/gate/config/RabbitMQConfig.java b/gate-web/src/main/java/com/netflix/spinnaker/gate/config/RabbitMQConfig.java new file mode 100644 index 0000000000..02c9b680bc --- /dev/null +++ b/gate-web/src/main/java/com/netflix/spinnaker/gate/config/RabbitMQConfig.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015 OpsMx, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.gate.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConditionalOnExpression("${message-broker.enabled:true}") +@ConditionalOnProperty(value = "message-broker.endpoint.name", havingValue = "rabbitmq") +public class RabbitMQConfig implements CamelRouteConfig { + + @Autowired private MessageBrokerProperties messageBrokerProperties; + private static final String isdExchange = "isd.userLoginDetails"; + private String directUserActivity = "userLoginDetails"; + + @Override + public String getUserActivityQueueEndPoint() { + return messageBrokerProperties.getEndpoint().getName() + + ":" + + isdExchange + + "?queue=" + + directUserActivity + + "&autoDelete=false&routingKey=" + + directUserActivity + + "&declare=true&durable=true&exchangeType=direct&hostname=" + + messageBrokerProperties.getHost() + + "&portNumber=" + + messageBrokerProperties.getPort() + + "&username=" + + messageBrokerProperties.getUsername() + + "&password=" + + messageBrokerProperties.getPassword(); + } +} diff --git a/gate-web/src/main/java/com/netflix/spinnaker/gate/config/UserActivityRouteBuilder.java b/gate-web/src/main/java/com/netflix/spinnaker/gate/config/UserActivityRouteBuilder.java new file mode 100644 index 0000000000..56693c172a --- /dev/null +++ b/gate-web/src/main/java/com/netflix/spinnaker/gate/config/UserActivityRouteBuilder.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015 OpsMx, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.gate.config; + +import static com.opsmx.spinnaker.gate.constant.CamelEndpointConstant.directUserActivity; + +import org.apache.camel.builder.RouteBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConditionalOnExpression("${message-broker.enabled:true}") +public class UserActivityRouteBuilder extends RouteBuilder { + + private final String userActivity = "userActivity"; + @Autowired private CamelRouteConfig camelRouteConfig; + + @Override + public void configure() throws Exception { + + from(directUserActivity) + .id(userActivity) + .to(camelRouteConfig.getUserActivityQueueEndPoint()) + .end(); + } +} diff --git a/gate-web/src/main/java/com/opsmx/spinnaker/gate/audit/AuditHandler.java b/gate-web/src/main/java/com/opsmx/spinnaker/gate/audit/AuditHandler.java index b764c5499c..be8955cee8 100644 --- a/gate-web/src/main/java/com/opsmx/spinnaker/gate/audit/AuditHandler.java +++ b/gate-web/src/main/java/com/opsmx/spinnaker/gate/audit/AuditHandler.java @@ -20,5 +20,5 @@ public interface AuditHandler { - void publishEvent(AuditEventType auditEventType, Object auditData); + String publishEvent(AuditEventType auditEventType, Object auditData); } diff --git a/gate-web/src/main/java/com/opsmx/spinnaker/gate/audit/AuditRestApiHandler.java b/gate-web/src/main/java/com/opsmx/spinnaker/gate/audit/AuditRestApiHandler.java index 5a794b1c32..eb525825dd 100644 --- a/gate-web/src/main/java/com/opsmx/spinnaker/gate/audit/AuditRestApiHandler.java +++ b/gate-web/src/main/java/com/opsmx/spinnaker/gate/audit/AuditRestApiHandler.java @@ -16,10 +16,12 @@ package com.opsmx.spinnaker.gate.audit; +import com.google.gson.Gson; import com.opsmx.spinnaker.gate.enums.AuditEventType; import com.opsmx.spinnaker.gate.feignclient.AuditService; import com.opsmx.spinnaker.gate.model.OesAuditModel; import java.util.UUID; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.cloud.openfeign.EnableFeignClients; @@ -28,16 +30,21 @@ @Component @EnableFeignClients(basePackageClasses = AuditService.class) @ConditionalOnExpression("${services.auditservice.enabled:true}") +@Slf4j public class AuditRestApiHandler implements AuditHandler { @Autowired private AuditService auditService; + Gson gson = new Gson(); @Override - public void publishEvent(AuditEventType auditEventType, Object auditData) { + public String publishEvent(AuditEventType auditEventType, Object auditData) { OesAuditModel oesAuditModel = new OesAuditModel(); oesAuditModel.setEventId(UUID.randomUUID().toString()); oesAuditModel.setAuditData(auditData); oesAuditModel.setEventType(auditEventType); auditService.publishAuditData(oesAuditModel, "OES"); + String model = gson.toJson(oesAuditModel, OesAuditModel.class); + log.debug("model: {}", model); + return model; } } diff --git a/gate-web/src/main/java/com/opsmx/spinnaker/gate/audit/AuthenticationAuditListener.java b/gate-web/src/main/java/com/opsmx/spinnaker/gate/audit/AuthenticationAuditListener.java index 3bf753bed6..c60938d6d2 100644 --- a/gate-web/src/main/java/com/opsmx/spinnaker/gate/audit/AuthenticationAuditListener.java +++ b/gate-web/src/main/java/com/opsmx/spinnaker/gate/audit/AuthenticationAuditListener.java @@ -16,15 +16,18 @@ package com.opsmx.spinnaker.gate.audit; +import com.google.gson.Gson; +import com.opsmx.spinnaker.gate.constant.CamelEndpointConstant; import com.opsmx.spinnaker.gate.enums.AuditEventType; import com.opsmx.spinnaker.gate.model.AuditData; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import com.opsmx.spinnaker.gate.model.OesAuditModel; +import java.util.*; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import org.apache.camel.ProducerTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.security.AbstractAuthenticationAuditListener; +import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.security.authentication.AbstractAuthenticationToken; @@ -38,6 +41,9 @@ public class AuthenticationAuditListener extends AbstractAuthenticationAuditListener { @Autowired private AuditHandler auditHandler; + @Autowired @Lazy private ProducerTemplate template; + + Gson gson = new Gson(); @Async @Override @@ -56,11 +62,15 @@ public void onApplicationEvent(AbstractAuthenticationEvent event) { if (event.getAuthentication().isAuthenticated() && event instanceof AuthenticationSuccessEvent) { log.debug("publishEvent AuthenticationSuccessEvent"); - auditHandler.publishEvent(AuditEventType.AUTHENTICATION_SUCCESSFUL_AUDIT, event); + template.asyncSendBody( + CamelEndpointConstant.directUserActivity, + auditHandler.publishEvent(AuditEventType.AUTHENTICATION_SUCCESSFUL_AUDIT, event)); } else if (!event.getAuthentication().isAuthenticated() && event instanceof AbstractAuthenticationFailureEvent) { log.debug("publishEvent AbstractAuthenticationFailureEvent"); - auditHandler.publishEvent(AuditEventType.AUTHENTICATION_FAILURE_AUDIT, event); + template.asyncSendBody( + CamelEndpointConstant.directUserActivity, + auditHandler.publishEvent(AuditEventType.AUTHENTICATION_FAILURE_AUDIT, event)); } else if (event instanceof LogoutSuccessEvent) { if (event .getAuthentication() @@ -73,10 +83,16 @@ public void onApplicationEvent(AbstractAuthenticationEvent event) { } log.debug("publishEvent LogoutSuccessEvent"); auditHandler.publishEvent(AuditEventType.SUCCESSFUL_USER_LOGOUT_AUDIT, event); + AbstractAuthenticationToken auth = (AbstractAuthenticationToken) event.getAuthentication(); + String name = auth.getName(); + template.asyncSendBody( + CamelEndpointConstant.directUserActivity, + getOesAuditModel( + name, event.getTimestamp(), AuditEventType.SUCCESSFUL_USER_LOGOUT_AUDIT)); } } catch (Exception e) { - log.error("Exception occured while capturing audit events : {}", e); + log.error("Exception occurred while capturing audit events : {}", e); } } @@ -88,7 +104,23 @@ private void handleAuthenticationEvent( Optional.ofNullable(auth.getAuthorities()).orElse(new ArrayList<>()).stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList()); - AuditData data = new AuditData(name, roles); + AuditData data = new AuditData(name, roles, event.getTimestamp()); auditHandler.publishEvent(eventType, data); + template.asyncSendBody( + CamelEndpointConstant.directUserActivity, + getOesAuditModel(name, event.getTimestamp(), eventType)); + } + + private String getOesAuditModel(String name, Long timestamp, AuditEventType eventType) { + OesAuditModel oesAuditModel = new OesAuditModel(); + Map date = new HashMap<>(); + date.put("userName", name); + date.put("timestamp", timestamp); + oesAuditModel.setEventId(UUID.randomUUID().toString()); + oesAuditModel.setAuditData(date); + oesAuditModel.setEventType(eventType); + String model = gson.toJson(oesAuditModel, OesAuditModel.class); + log.debug("model: {}", model); + return model; } } diff --git a/gate-web/src/main/java/com/opsmx/spinnaker/gate/audit/UserActivityAuditListener.java b/gate-web/src/main/java/com/opsmx/spinnaker/gate/audit/UserActivityAuditListener.java index 579beb9471..3b3f87b285 100644 --- a/gate-web/src/main/java/com/opsmx/spinnaker/gate/audit/UserActivityAuditListener.java +++ b/gate-web/src/main/java/com/opsmx/spinnaker/gate/audit/UserActivityAuditListener.java @@ -16,11 +16,16 @@ package com.opsmx.spinnaker.gate.audit; +import com.google.gson.Gson; +import com.opsmx.spinnaker.gate.constant.CamelEndpointConstant; import com.opsmx.spinnaker.gate.enums.AuditEventType; import com.opsmx.spinnaker.gate.enums.OesServices; +import com.opsmx.spinnaker.gate.model.OesAuditModel; import java.util.HashMap; import java.util.Map; +import java.util.UUID; import lombok.extern.slf4j.Slf4j; +import org.apache.camel.ProducerTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; @@ -35,10 +40,12 @@ @EnableAsync public class UserActivityAuditListener implements ApplicationListener { - // @Autowired private AuditHandler auditHandler; - private AuditHandler auditHandler; + @Autowired @Lazy private ProducerTemplate template; + + Gson gson = new Gson(); + @Autowired public UserActivityAuditListener(@Lazy AuditHandler auditHandler) { this.auditHandler = auditHandler; @@ -56,18 +63,33 @@ public void onApplicationEvent(ApplicationEvent event) { log.debug("request is authenticated"); String baseUrl = getBaseUrl(servletRequestHandledEvent.getRequestUrl()); log.debug("base url : {}", baseUrl); + Map auditData = populateAuditData(servletRequestHandledEvent); if (isOesActivity(baseUrl)) { - Map auditData = populateAuditData(servletRequestHandledEvent); log.debug("publishing the event to audit service : {}", auditData); auditHandler.publishEvent(AuditEventType.USER_ACTIVITY_AUDIT, auditData); } + template.asyncSendBody( + CamelEndpointConstant.directUserActivity, getOesAuditModel(auditData)); } } } catch (Exception e) { - log.error("Excepion occured : {}", e); + log.error("Exception occurred : {}", e); } } + private String getOesAuditModel(Map auditData) { + OesAuditModel oesAuditModel = new OesAuditModel(); + Map date = new HashMap<>(); + date.put("userName", auditData.get("userName")); + date.put("timestamp", auditData.get("timestamp")); + oesAuditModel.setEventId(UUID.randomUUID().toString()); + oesAuditModel.setAuditData(date); + oesAuditModel.setEventType(AuditEventType.USER_ACTIVITY_AUDIT); + String model = gson.toJson(oesAuditModel, OesAuditModel.class); + log.info("model: {}", model); + return model; + } + private boolean isOesActivity(String baseUrl) { boolean flag = Boolean.FALSE; diff --git a/gate-web/src/main/java/com/opsmx/spinnaker/gate/constant/CamelEndpointConstant.java b/gate-web/src/main/java/com/opsmx/spinnaker/gate/constant/CamelEndpointConstant.java new file mode 100644 index 0000000000..398e2ab1ac --- /dev/null +++ b/gate-web/src/main/java/com/opsmx/spinnaker/gate/constant/CamelEndpointConstant.java @@ -0,0 +1,21 @@ +/* + * Copyright 2015 OpsMx, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.opsmx.spinnaker.gate.constant; + +public interface CamelEndpointConstant { + String directUserActivity = "direct:userLoginDetails"; +} diff --git a/gate-web/src/main/java/com/opsmx/spinnaker/gate/model/AuditData.java b/gate-web/src/main/java/com/opsmx/spinnaker/gate/model/AuditData.java index 5951c05f9a..f632847712 100644 --- a/gate-web/src/main/java/com/opsmx/spinnaker/gate/model/AuditData.java +++ b/gate-web/src/main/java/com/opsmx/spinnaker/gate/model/AuditData.java @@ -23,18 +23,20 @@ public class AuditData { private Source source; - public AuditData(String name, List roles) { - this.source = new Source(name, roles); + public AuditData(String name, List roles, long timestamp) { + this.source = new Source(name, roles, timestamp); } @Data public class Source { private String name; private Principal principal; + private Long timestamp; - public Source(String name, List roles) { + public Source(String name, List roles, long timestamp) { this.name = name; this.principal = new Principal(roles); + this.timestamp = timestamp; } }