-
Notifications
You must be signed in to change notification settings - Fork 293
/
Copy pathComplianceConfig.java
573 lines (525 loc) · 22.5 KB
/
ComplianceConfig.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
/*
* Copyright 2015-2018 _floragunn_ GmbH
* 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.
*/
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
package org.opensearch.security.compliance;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.opensearch.common.settings.Settings;
import org.opensearch.core.common.Strings;
import org.opensearch.security.DefaultObjectMapper;
import org.opensearch.security.auditlog.config.AuditConfig;
import org.opensearch.security.support.ConfigConstants;
import org.opensearch.security.support.WildcardMatcher;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import static org.opensearch.security.DefaultObjectMapper.getOrDefault;
/**
* This class represents all configurations for compliance.
* DLS/FLS uses this configuration for filtering and anonymizing fields.
* Audit Logger uses this configuration to post compliance audit logs.
*/
@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ComplianceConfig {
public static Set<String> FIELDS = DefaultObjectMapper.getFields(ComplianceConfig.class);
private static final Logger log = LogManager.getLogger(ComplianceConfig.class);
public static final ComplianceConfig DEFAULT = ComplianceConfig.from(Settings.EMPTY);
private static final int CACHE_SIZE = 1000;
private static final String INTERNAL_OPENSEARCH = "internal_opensearch";
private final boolean logExternalConfig;
private final boolean logInternalConfig;
private final boolean logReadMetadataOnly;
private final boolean logWriteMetadataOnly;
@JsonProperty("write_log_diffs")
private final boolean logDiffsForWrite;
@JsonProperty("read_watched_fields")
private final Map<String, List<String>> watchedReadFields;
@JsonProperty("read_ignore_users")
private final Set<String> ignoredComplianceUsersForRead;
@JsonProperty("write_watched_indices")
private final List<String> watchedWriteIndicesPatterns;
@JsonProperty("write_ignore_users")
private final Set<String> ignoredComplianceUsersForWrite;
private final WildcardMatcher watchedWriteIndicesMatcher;
private final WildcardMatcher ignoredComplianceUsersForReadMatcher;
private final WildcardMatcher ignoredComplianceUsersForWriteMatcher;
private final String securityIndex;
private final Map<WildcardMatcher, Set<String>> readEnabledFields;
private final LoadingCache<String, WildcardMatcher> readEnabledFieldsCache;
private final DateTimeFormatter auditLogPattern;
private final String auditLogIndex;
private final boolean enabled;
private final Supplier<DateTime> dateProvider;
private ComplianceConfig(
final boolean enabled,
final boolean logExternalConfig,
final boolean logInternalConfig,
final boolean logReadMetadataOnly,
final Map<String, List<String>> watchedReadFields,
final Set<String> ignoredComplianceUsersForRead,
final boolean logWriteMetadataOnly,
final boolean logDiffsForWrite,
final List<String> watchedWriteIndicesPatterns,
final Set<String> ignoredComplianceUsersForWrite,
final String securityIndex,
final String destinationType,
final String destinationIndex,
final Supplier<DateTime> dateProvider
) {
this.enabled = enabled;
this.logExternalConfig = logExternalConfig;
this.logInternalConfig = logInternalConfig;
this.logReadMetadataOnly = logReadMetadataOnly;
this.logWriteMetadataOnly = logWriteMetadataOnly;
this.logDiffsForWrite = logDiffsForWrite;
this.watchedWriteIndicesMatcher = WildcardMatcher.from(watchedWriteIndicesPatterns);
this.ignoredComplianceUsersForReadMatcher = WildcardMatcher.from(ignoredComplianceUsersForRead);
this.ignoredComplianceUsersForWriteMatcher = WildcardMatcher.from(ignoredComplianceUsersForWrite);
this.securityIndex = securityIndex;
this.watchedReadFields = watchedReadFields;
this.ignoredComplianceUsersForRead = ignoredComplianceUsersForRead;
this.watchedWriteIndicesPatterns = watchedWriteIndicesPatterns;
this.ignoredComplianceUsersForWrite = ignoredComplianceUsersForWrite;
this.readEnabledFields = watchedReadFields.entrySet()
.stream()
.filter(entry -> !Strings.isNullOrEmpty(entry.getKey()))
.collect(
ImmutableMap.toImmutableMap(entry -> WildcardMatcher.from(entry.getKey()), entry -> ImmutableSet.copyOf(entry.getValue()))
);
DateTimeFormatter auditLogPattern = null;
String auditLogIndex = null;
if (INTERNAL_OPENSEARCH.equalsIgnoreCase(destinationType)) {
try {
auditLogPattern = DateTimeFormat.forPattern(destinationIndex); // throws IllegalArgumentException if no pattern
} catch (IllegalArgumentException e) {
log.warn(
"Unable to translate {} as a DateTimeFormat, will instead treat this as a static audit log index name. Error: {}",
destinationIndex,
e.getMessage()
);
// no pattern
auditLogIndex = destinationIndex;
} catch (Exception e) {
log.error("Unable to check if auditlog index {} is part of compliance setup", destinationIndex, e);
}
}
this.auditLogPattern = auditLogPattern;
this.auditLogIndex = auditLogIndex;
this.readEnabledFieldsCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).build(new CacheLoader<String, WildcardMatcher>() {
@Override
public WildcardMatcher load(String index) throws Exception {
return WildcardMatcher.from(getFieldsForIndex(index));
}
});
this.dateProvider = Optional.ofNullable(dateProvider).orElse(() -> DateTime.now(DateTimeZone.UTC));
}
@VisibleForTesting
public ComplianceConfig(
final boolean enabled,
final boolean logExternalConfig,
final boolean logInternalConfig,
final boolean logReadMetadataOnly,
final Map<String, List<String>> watchedReadFields,
final Set<String> ignoredComplianceUsersForRead,
final boolean logWriteMetadataOnly,
final boolean logDiffsForWrite,
final List<String> watchedWriteIndicesPatterns,
final Set<String> ignoredComplianceUsersForWrite,
final Supplier<DateTime> dateProvider,
Settings settings
) {
this(
enabled,
logExternalConfig,
logInternalConfig,
logReadMetadataOnly,
watchedReadFields,
ignoredComplianceUsersForRead,
logWriteMetadataOnly,
logDiffsForWrite,
watchedWriteIndicesPatterns,
ignoredComplianceUsersForWrite,
settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX),
settings.get(ConfigConstants.SECURITY_AUDIT_TYPE_DEFAULT, null),
settings.get(
ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_OPENSEARCH_INDEX,
"'security-auditlog-'YYYY.MM.dd"
),
dateProvider
);
}
public void log(Logger logger) {
logger.info("Auditing of external configuration is {}.", logExternalConfig ? "enabled" : "disabled");
logger.info("Auditing of internal configuration is {}.", logInternalConfig ? "enabled" : "disabled");
logger.info("Auditing only metadata information for read request is {}.", logReadMetadataOnly ? "enabled" : "disabled");
logger.info("Auditing will watch {} for read requests.", readEnabledFields);
logger.info("Auditing read operation requests from {} users is disabled.", ignoredComplianceUsersForReadMatcher);
logger.info("Auditing only metadata information for write request is {}.", logWriteMetadataOnly ? "enabled" : "disabled");
logger.info("Auditing diffs for write requests is {}.", logDiffsForWrite ? "enabled" : "disabled");
logger.info("Auditing write operation requests from {} users is disabled.", ignoredComplianceUsersForWriteMatcher);
logger.info("Auditing will watch {} for write requests.", watchedWriteIndicesMatcher);
logger.info("{} is used as internal security index.", securityIndex);
logger.info("Internal index used for posting audit logs is {}", auditLogIndex);
}
@VisibleForTesting
@JsonCreator
public static ComplianceConfig from(Map<String, Object> properties, @JacksonInject Settings settings) throws JsonProcessingException {
if (!FIELDS.containsAll(properties.keySet())) {
throw new UnrecognizedPropertyException(
null,
"Invalid property present in the input data for compliance config",
null,
ComplianceConfig.class,
null,
null
);
}
final boolean enabled = getOrDefault(properties, "enabled", true);
final boolean logExternalConfig = getOrDefault(properties, "external_config", false);
final boolean logInternalConfig = getOrDefault(properties, "internal_config", false);
final boolean logReadMetadataOnly = getOrDefault(properties, "read_metadata_only", false);
final Map<String, List<String>> watchedReadFields = getOrDefault(properties, "read_watched_fields", Collections.emptyMap());
final Set<String> ignoredComplianceUsersForRead = ImmutableSet.copyOf(
getOrDefault(properties, "read_ignore_users", AuditConfig.DEFAULT_IGNORED_USERS)
);
final boolean logWriteMetadataOnly = getOrDefault(properties, "write_metadata_only", false);
final boolean logDiffsForWrite = getOrDefault(properties, "write_log_diffs", false);
final List<String> watchedWriteIndicesPatterns = getOrDefault(properties, "write_watched_indices", Collections.emptyList());
final Set<String> ignoredComplianceUsersForWrite = ImmutableSet.copyOf(
getOrDefault(properties, "write_ignore_users", AuditConfig.DEFAULT_IGNORED_USERS)
);
return new ComplianceConfig(
enabled,
logExternalConfig,
logInternalConfig,
logReadMetadataOnly,
watchedReadFields,
ignoredComplianceUsersForRead,
logWriteMetadataOnly,
logDiffsForWrite,
watchedWriteIndicesPatterns,
ignoredComplianceUsersForWrite,
null,
settings
);
}
/**
* Create compliance configuration from Settings defined in opensearch.yml
* @param settings settings
* @return compliance configuration
*/
public static ComplianceConfig from(Settings settings) {
return ComplianceConfig.from(settings, null);
}
/**
* Create compliance configuration from Settings defined in opensearch.yml
* @param settings settings
* @param dateProvider how the current date/time is evalated for audit logs that rollover
* @return compliance configuration
*/
public static ComplianceConfig from(Settings settings, Supplier<DateTime> dateProvider) {
final boolean logExternalConfig = settings.getAsBoolean(
ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED,
false
);
final boolean logInternalConfig = settings.getAsBoolean(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, false);
final boolean logReadMetadataOnly = settings.getAsBoolean(
ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_METADATA_ONLY,
false
);
final boolean logWriteMetadataOnly = settings.getAsBoolean(
ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_METADATA_ONLY,
false
);
final boolean logDiffsForWrite = settings.getAsBoolean(
ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS,
false
);
final List<String> watchedReadFields = settings.getAsList(
ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_WATCHED_FIELDS,
Collections.emptyList(),
false
);
// plugins.security.compliance.pii_fields:
// - indexpattern,fieldpattern,fieldpattern,....
final Map<String, List<String>> readEnabledFields = watchedReadFields.stream()
.map(watchedReadField -> watchedReadField.split(","))
.filter(split -> split.length != 0 && !Strings.isNullOrEmpty(split[0]))
.collect(
ImmutableMap.toImmutableMap(
split -> split[0],
split -> split.length == 1
? ImmutableList.of("*")
: Arrays.stream(split).skip(1).collect(ImmutableList.toImmutableList())
)
);
final List<String> watchedWriteIndices = settings.getAsList(
ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES,
Collections.emptyList()
);
final Set<String> ignoredComplianceUsersForRead = ConfigConstants.getSettingAsSet(
settings,
ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_IGNORE_USERS,
AuditConfig.DEFAULT_IGNORED_USERS,
false
);
final Set<String> ignoredComplianceUsersForWrite = ConfigConstants.getSettingAsSet(
settings,
ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS,
AuditConfig.DEFAULT_IGNORED_USERS,
false
);
return new ComplianceConfig(
true,
logExternalConfig,
logInternalConfig,
logReadMetadataOnly,
readEnabledFields,
ignoredComplianceUsersForRead,
logWriteMetadataOnly,
logDiffsForWrite,
watchedWriteIndices,
ignoredComplianceUsersForWrite,
dateProvider,
settings
);
}
/**
* Checks if config defined in OpenSearch config directory must be logged
* @return true/false
*/
@JsonProperty("external_config")
public boolean shouldLogExternalConfig() {
return logExternalConfig;
}
/**
* Checks if internal config must be logged
* @return true/false
*/
@JsonProperty("internal_config")
public boolean shouldLogInternalConfig() {
return logInternalConfig;
}
/**
* Checks if compliance is enabled
* @return true/false
*/
@JsonProperty
public boolean isEnabled() {
return this.enabled;
}
/**
* Checks if logs diffs must be recorded for write requests
* Log metadata only for write requests must be disabled
* @return true/false
*/
public boolean shouldLogDiffsForWrite() {
return !shouldLogWriteMetadataOnly() && logDiffsForWrite;
}
/**
* Checks if only metadata for write requests should be logged
* @return true/false
*/
@JsonProperty("write_metadata_only")
public boolean shouldLogWriteMetadataOnly() {
return logWriteMetadataOnly;
}
/**
* Checks if only metadata for read requests should be logged
* @return true/false
*/
@JsonProperty("read_metadata_only")
public boolean shouldLogReadMetadataOnly() {
return logReadMetadataOnly;
}
@VisibleForTesting
public WildcardMatcher getIgnoredComplianceUsersForReadMatcher() {
return ignoredComplianceUsersForReadMatcher;
}
/**
* Check if user is excluded from compliance read audit
* @param user
* @return true if user is excluded from compliance read audit
*/
public boolean isComplianceReadAuditDisabled(String user) {
return ignoredComplianceUsersForReadMatcher.test(user);
}
@VisibleForTesting
public WildcardMatcher getIgnoredComplianceUsersForWriteMatcher() {
return ignoredComplianceUsersForWriteMatcher;
}
/**
* Check if user is excluded from compliance write audit
* @param user
* @return true if user is excluded from compliance write audit
*/
public boolean isComplianceWriteAuditDisabled(String user) {
return ignoredComplianceUsersForWriteMatcher.test(user);
}
@VisibleForTesting
public Map<WildcardMatcher, Set<String>> getReadEnabledFields() {
return readEnabledFields;
}
@VisibleForTesting
public WildcardMatcher getWatchedWriteIndicesMatcher() {
return watchedWriteIndicesMatcher;
}
@VisibleForTesting
public String getSecurityIndex() {
return securityIndex;
}
@VisibleForTesting
public String getAuditLogIndex() {
return auditLogIndex;
}
/**
* This function is used for caching the fields
* @param index index to check for fields
* @return set of fields which is used by cache
*/
private Set<String> getFieldsForIndex(String index) {
if (index == null) {
return Collections.emptySet();
}
if (auditLogIndex != null && auditLogIndex.equalsIgnoreCase(index)) {
return Collections.emptySet();
}
if (auditLogPattern != null) {
if (index.equalsIgnoreCase(getExpandedIndexName(auditLogPattern, null))) {
return Collections.emptySet();
}
}
return readEnabledFields.entrySet()
.stream()
.filter(entry -> entry.getKey().test(index))
.flatMap(entry -> entry.getValue().stream())
.collect(ImmutableSet.toImmutableSet());
}
/**
* Get the index name with date pattern for rolling indexes
* @param indexPattern index pattern
* @param index index
* @return index name
*/
private String getExpandedIndexName(DateTimeFormatter indexPattern, String index) {
if (indexPattern == null) {
return index;
}
return indexPattern.print(dateProvider.get());
}
/**
* Check if write history is enabled for the index.
* Does not check for compliance here.
* @param index index
* @return true/false
*/
public boolean writeHistoryEnabledForIndex(String index) {
if (index == null || !isEnabled()) {
return false;
}
// if security index (internal index) check if internal config logging is enabled
if (securityIndex.equals(index)) {
return logInternalConfig;
}
// if the index is used for audit logging, return false
if (auditLogIndex != null && auditLogIndex.equalsIgnoreCase(index)) {
return false;
}
// if the index is used for audit logging (rolling index name), return false
if (auditLogPattern != null) {
if (index.equalsIgnoreCase(getExpandedIndexName(auditLogPattern, null))) {
return false;
}
}
return watchedWriteIndicesMatcher.test(index);
}
/**
* Check if read compliance history is enabled for given index
* Checks if compliance is enabled
* @param index index
* @return true/false
*/
public boolean readHistoryEnabledForIndex(String index) {
if (index == null || !this.isEnabled()) {
return false;
}
// if security index (internal index) check if internal config logging is enabled
if (securityIndex.equals(index)) {
return logInternalConfig;
}
try {
return readEnabledFieldsCache.get(index) != WildcardMatcher.NONE;
} catch (ExecutionException e) {
log.warn("Failed to get index {} fields enabled for read from cache. Bypassing cache.", index, e);
return getFieldsForIndex(index).isEmpty();
}
}
/**
* Check if read compliance history is enabled for given index
* Checks if compliance is enabled
* @param index index
* @return true/false
*/
public boolean readHistoryEnabledForField(String index, String field) {
if (index == null || !this.isEnabled()) {
return false;
}
// if security index (internal index) check if internal config logging is enabled
if (securityIndex.equals(index)) {
return logInternalConfig;
}
WildcardMatcher matcher;
try {
matcher = readEnabledFieldsCache.get(index);
} catch (ExecutionException e) {
log.warn("Failed to get index {} fields enabled for read from cache. Bypassing cache.", index, e);
matcher = WildcardMatcher.from(getFieldsForIndex(index));
}
return matcher.test(field);
}
}