View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2013 Cybernetica AS
15   * Portions copyright 2014-2016 ForgeRock AS.
16   */
17  package org.forgerock.audit.handlers.syslog;
18  
19  import static org.forgerock.audit.util.ResourceExceptionsUtil.adapt;
20  import static org.forgerock.audit.util.ResourceExceptionsUtil.notSupported;
21  import static org.forgerock.json.resource.Responses.newResourceResponse;
22  
23  import java.net.InetSocketAddress;
24  import javax.inject.Inject;
25  
26  import org.forgerock.audit.Audit;
27  import org.forgerock.audit.events.EventTopicsMetaData;
28  import org.forgerock.audit.events.handlers.AuditEventHandlerBase;
29  import org.forgerock.audit.providers.DefaultLocalHostNameProvider;
30  import org.forgerock.audit.providers.LocalHostNameProvider;
31  import org.forgerock.audit.providers.ProductInfoProvider;
32  import org.forgerock.json.JsonValue;
33  import org.forgerock.json.resource.BadRequestException;
34  import org.forgerock.json.resource.InternalServerErrorException;
35  import org.forgerock.json.resource.NotSupportedException;
36  import org.forgerock.json.resource.QueryRequest;
37  import org.forgerock.json.resource.QueryResourceHandler;
38  import org.forgerock.json.resource.QueryResponse;
39  import org.forgerock.json.resource.ResourceException;
40  import org.forgerock.json.resource.ResourceResponse;
41  import org.forgerock.services.context.Context;
42  import org.forgerock.util.Reject;
43  import org.forgerock.util.promise.Promise;
44  import org.slf4j.Logger;
45  import org.slf4j.LoggerFactory;
46  
47  /**
48   * The handler publishes audit events formatted using {@link SyslogFormatter} to a syslog daemon using
49   * the configured {@link SyslogPublisher}. The publisher is flushed after each write.
50   */
51  public class SyslogAuditEventHandler extends AuditEventHandlerBase {
52  
53      private static final Logger logger = LoggerFactory.getLogger(SyslogAuditEventHandler.class);
54  
55      private final SyslogPublisher publisher;
56      private final SyslogFormatter formatter;
57  
58      /**
59       * Create a new SyslogAuditEventHandler instance.
60       *
61       * @param configuration
62       *          Configuration parameters that can be adjusted by system administrators.
63       * @param eventTopicsMetaData
64       *          Meta-data for all audit event topics.
65       * @param productInfoProvider
66       *          Provides info such as product name.
67       * @param localHostNameProvider
68       *          Provides local host name.
69       */
70      @Inject
71      public SyslogAuditEventHandler(
72              final SyslogAuditEventHandlerConfiguration configuration,
73              final EventTopicsMetaData eventTopicsMetaData,
74              @Audit final ProductInfoProvider productInfoProvider,
75              @Audit final LocalHostNameProvider localHostNameProvider) {
76  
77          super(configuration.getName(), eventTopicsMetaData, configuration.getTopics(), configuration.isEnabled());
78          Reject.ifNull(configuration.getProtocol(),
79                  "Syslog transport 'protocol' of TCP or UDP is required");
80          Reject.ifNull(configuration.getHost(),
81                  "Syslog destination server 'host' is required");
82          Reject.ifTrue(configuration.getPort() < 0 || configuration.getPort() > 65535,
83                  "Syslog destination server 'port' between 0 and 65535 is required");
84          Reject.ifNull(configuration.getFacility(),
85                  "Syslog 'facility' is required");
86          Reject.ifTrue(configuration.getProtocol() == TransportProtocol.TCP && configuration.getConnectTimeout() == 0,
87                  "Syslog 'connectTimeout' is required for TCP connections");
88  
89          InetSocketAddress socketAddress = new InetSocketAddress(configuration.getHost(), configuration.getPort());
90          this.publisher = configuration.getProtocol().getPublisher(socketAddress, configuration);
91          this.formatter = new SyslogFormatter(
92                  eventTopicsMetaData,
93                  configuration,
94                  getLocalHostNameProvider(localHostNameProvider),
95                  getProductNameProvider(productInfoProvider));
96  
97          logger.debug("Successfully configured Syslog audit event handler.");
98      }
99  
100     private ProductInfoProvider getProductNameProvider(ProductInfoProvider productInfoProvider) {
101         if (productInfoProvider != null) {
102             return productInfoProvider;
103         } else {
104             logger.debug("No {} provided; using default.", ProductInfoProvider.class.getSimpleName());
105             return new DefaultProductInfoProvider();
106         }
107     }
108 
109     private LocalHostNameProvider getLocalHostNameProvider(LocalHostNameProvider localHostNameProvider) {
110         if (localHostNameProvider != null) {
111             return localHostNameProvider;
112         } else {
113             logger.debug("No {} provided; using default.", LocalHostNameProvider.class.getSimpleName());
114             return new DefaultLocalHostNameProvider();
115         }
116     }
117 
118     /** {@inheritDoc} */
119     @Override
120     public void startup() {
121         // nothing to do
122     }
123 
124     /**
125      * Closes the connections established by {@link SyslogPublisher}.
126      */
127     @Override
128     public void shutdown() {
129         synchronized (publisher) {
130             publisher.close();
131         }
132     }
133 
134     @Override
135     public Promise<ResourceResponse, ResourceException> publishEvent(Context context, String topic, JsonValue event) {
136 
137         try {
138             final String syslogMessage = formatAsSyslogMessage(topic, event);
139             synchronized (publisher) {
140                 publisher.publishMessage(syslogMessage);
141             }
142 
143             return newResourceResponse(
144                     event.get(ResourceResponse.FIELD_CONTENT_ID).asString(),
145                     null,
146                     event.clone()).asPromise();
147 
148         } catch (Exception ex) {
149             return adapt(ex).asPromise();
150         }
151     }
152 
153     private String formatAsSyslogMessage(String topic, JsonValue auditEvent) throws ResourceException {
154         if (!formatter.canFormat(topic)) {
155             throw new InternalServerErrorException("Unable to format " + topic + " audit event");
156         }
157         try {
158             return formatter.format(topic, auditEvent);
159         } catch (Exception ex) {
160             throw new BadRequestException(ex);
161         }
162     }
163 
164     @Override
165     public Promise<QueryResponse, ResourceException> queryEvents(
166             Context context,
167             String topic,
168             QueryRequest queryRequest,
169             QueryResourceHandler queryResourceHandler) {
170         return notSupported(queryRequest).asPromise();
171     }
172 
173     @Override
174     public Promise<ResourceResponse, ResourceException> readEvent(Context context, String topic, String resourceId) {
175         return new NotSupportedException("query operations are not supported").asPromise();
176     }
177 
178     /**
179      * Default implementation of ProductNameProvider.
180      */
181     private static class DefaultProductInfoProvider implements ProductInfoProvider {
182 
183         @Override
184         public String getProductName() {
185             return null;
186         }
187     }
188 }