All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.logstash.logback.mask.MaskingJsonGeneratorDecorator Maven / Gradle / Ivy

/*
 * Copyright 2013-2022 the original author or authors.
 *
 * 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 net.logstash.logback.mask;

import static net.logstash.logback.util.StringUtils.commaDelimitedListToStringArray;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import net.logstash.logback.decorate.JsonGeneratorDecorator;

import ch.qos.logback.core.spi.LifeCycle;
import com.fasterxml.jackson.core.JsonGenerator;

/**
 * A {@link JsonGeneratorDecorator} that wraps a {@link JsonGenerator} with a {@link MaskingJsonGenerator},
 * so that sensitive field values can be masked.
 */
public class MaskingJsonGeneratorDecorator implements JsonGeneratorDecorator, LifeCycle {

    /**
     * Paths to mask with the default mask
     */
    private final PathMask pathsWithDefaultMask = new PathMask();

    /**
     * Suppliers of {@link PathMask}s.
     */
    private final List pathMaskSuppliers = new ArrayList<>();

    /**
     * Custom {@link FieldMasker}s.
     */
    private final List fieldMaskers = new ArrayList<>();

    /**
     * Values to mask with the default mask
     */
    private final ValueMask valuesWithDefaultMask = new ValueMask();

    /**
     * Values to mask with specific masks.
     */
    private final List valueMaskSuppliers = new ArrayList<>();

    /**
     * Custom {@link ValueMasker}s.
     */
    private final List valueMaskers = new ArrayList<>();

    /**
     * The decorator to be used to decorate the {@link JsonGenerator}.
     * Will be updated during {@link #start()}.
     */
    private JsonGeneratorDecorator delegate;

    /**
     * True if this decorator is currently started.
     */
    private boolean started;

    /**
     * Supplies a {@link PathMask} dynamically at runtime.
     *
     * 

Use this if the list of paths to mask should be determined * from somewhere other than the logback configuration. * E.g. by dynamically loading them from classes on the classpath.

*/ public interface PathMaskSupplier extends Supplier { } /** * Paths to mask, and the value to write as the mask. */ public static class PathMask { /** * The absolute or partial field paths to mask (see {@link PathBasedFieldMasker} for format) */ private final List paths = new ArrayList<>(); /** * The value to write as a mask for the {@link #paths}. */ private String mask = MaskingJsonGenerator.MASK; public PathMask() { } public PathMask(String path) { this(path, MaskingJsonGenerator.MASK); } public PathMask(String path, String mask) { this(Collections.singletonList(path), mask); } public PathMask(List paths) { this(paths, MaskingJsonGenerator.MASK); } public PathMask(List paths, String mask) { paths.forEach(this::addPath); setMask(mask); } /** * @param path the absolute or partial field path to mask (see {@link PathBasedFieldMasker} for format) */ public void addPath(String path) { PathBasedFieldMasker.validatePathToMask(path); this.paths.add(path); } /** * @param paths a comma-separated string of absolute or partial field paths to mask (see {@link PathBasedFieldMasker} for format) */ public void addPaths(String paths) { for (String path: commaDelimitedListToStringArray(paths)) { addPath(path); } } /** * @param mask the value to write as a mask for any paths that match the {@link #paths}. */ public void setMask(String mask) { this.mask = Objects.requireNonNull(mask); } } /** * Supplies a {@link ValueMask} dynamically at runtime. * *

Use this if the list of values to mask should be determined * from somewhere other than the logback configuration. * E.g. by dynamically loading them from classes on the classpath.

*/ public interface ValueMaskSupplier extends Supplier { } /** * Values to mask, and the value to write as the mask. */ public static class ValueMask { /** * The regexes used to identify values to mask. */ private final List values = new ArrayList<>(); /** * The value to write as a mask for values that match the regex (can contain back references to capture groups in the regex). */ private String mask = MaskingJsonGenerator.MASK; public ValueMask() { } public ValueMask(String values) { this(values, MaskingJsonGenerator.MASK); } public ValueMask(String value, String mask) { this(Collections.singletonList(value), mask); } public ValueMask(List values) { this(values, MaskingJsonGenerator.MASK); } public ValueMask(List values, String mask) { values.forEach(this::addValue); setMask(mask); } /** * @param value the regex used to identify values to mask */ public void addValue(String value) { this.values.add(Objects.requireNonNull(value)); } /** * @param values a comma-separated string of regexes to mask */ public void addValues(String values) { for (String value: commaDelimitedListToStringArray(values)) { addValue(value); } } /** * @param mask the value to write as a mask for values that match the {@link #values} regexes * (can contain back references to capture groups in the regex) */ public void setMask(String mask) { this.mask = Objects.requireNonNull(mask); } } @Override public synchronized boolean isStarted() { return started; } @Override public synchronized void start() { if (!started) { final List effectiveFieldMaskers = getEffectiveFieldMaskers(); final List effectiveValueMaskers = getEffectiveValueMaskers(); if (effectiveFieldMaskers.isEmpty() && effectiveValueMaskers.isEmpty()) { delegate = generator -> generator; } else { delegate = generator -> new MaskingJsonGenerator(generator, effectiveFieldMaskers, effectiveValueMaskers); } started = true; } } @Override public synchronized void stop() { started = false; } private List getEffectiveFieldMaskers() { Map> fieldNamesByMask = new HashMap<>(); List pathFieldMaskers = new ArrayList<>(); Stream.concat(Stream.of(pathsWithDefaultMask), pathMaskSuppliers.stream().map(Supplier::get)) .forEach(pathMask -> pathMask.paths.forEach(path -> { String mask = pathMask.mask; if (PathBasedFieldMasker.isSingleFieldName(path)) { /* * Optimize single field name matching by grouping all single field names * in a Set, to be checked by a FieldNameMatcher * * The FieldNameMatcher is much more efficient than a JsonPathMatcher. */ fieldNamesByMask .computeIfAbsent(mask, r -> new HashSet<>()) .add(PathBasedFieldMasker.unescapeJsonPointerToken(path)); } else { pathFieldMaskers.add(new PathBasedFieldMasker(path, mask)); } }) ); return Collections.unmodifiableList(Stream.concat( fieldNamesByMask.entrySet().stream() .map(entry -> new FieldNameBasedFieldMasker(entry.getValue(), entry.getKey())), Stream.concat( pathFieldMaskers.stream(), fieldMaskers.stream())) .collect(Collectors.toList())); } private List getEffectiveValueMaskers() { return Collections.unmodifiableList(Stream.concat( Stream.concat(Stream.of(valuesWithDefaultMask), valueMaskSuppliers.stream().map(Supplier::get)) .flatMap(valueMask -> valueMask.values.stream() .map(value -> new RegexValueMasker(value, valueMask.mask))), valueMaskers.stream()) .collect(Collectors.toList())); } @Override public JsonGenerator decorate(JsonGenerator generator) { return delegate.decorate(generator); } /** * Sets the default mask value to use for any paths added via {@link #addPath(String)} * and values added via {@link #addValue(String)}. * *

By default, this is {@value MaskingJsonGenerator#MASK}.

* * @param defaultMask the default mask value to be used to mask real values. */ public void setDefaultMask(String defaultMask) { Objects.requireNonNull(defaultMask, "defaultMask must not be null"); this.valuesWithDefaultMask.setMask(defaultMask); this.pathsWithDefaultMask.setMask(defaultMask); } /** * Adds the given path to the paths that will be masked. * *

The {@link #setDefaultMask(String) default mask} value will be substituted for values at the given path.

* * @param pathToMask the path to mask. See {@link PathBasedFieldMasker} for the format. */ public void addPath(String pathToMask) { pathsWithDefaultMask.addPath(pathToMask); } /** * Adds the given comma separated paths to the paths that will be masked. * *

The {@link #setDefaultMask(String) default mask} value will be substituted for values at the given paths.

* * @param pathsToMask comma separate string of paths to mask. See {@link PathBasedFieldMasker} for the format. */ public void addPaths(String pathsToMask) { pathsWithDefaultMask.addPaths(pathsToMask); } /** * Adds the given paths and mask that will be used to determine if a field should be masked. * *

The {@link PathMask#setMask(String) mask} value will be written for any of the {@link PathMask#addPath(String) paths}.

* * @param pathMask a paths used to determine if a value should be masked, and their corresponding mask value. */ public void addPathMask(PathMask pathMask) { addPathMaskSupplier(() -> pathMask); } /** * Adds the given supplier of paths and mask that will be used to determine if a field should be masked. * *

The {@link PathMask#setMask(String) mask} value will be written for any of the {@link PathMask#addPath(String) paths}.

* * @param pathMaskSupplier a supplier of paths used to determine if a value should be masked, and their corresponding mask value. */ public void addPathMaskSupplier(PathMaskSupplier pathMaskSupplier) { this.pathMaskSuppliers.add(pathMaskSupplier); } /** * Add the given {@link FieldMasker} to the maskers used to mask a field. * * @param fieldMasker the masker to add */ public void addFieldMasker(FieldMasker fieldMasker) { fieldMaskers.add(fieldMasker); } /** * Adds the given value regex to the regexes that will be used to determine if a field value should be masked. * *

The {@link #setDefaultMask(String) default mask} value will be substituted for values that match the given regex.

* * @param valueToMask a regular expression used to determine if a value should be masked. */ public void addValue(String valueToMask) { valuesWithDefaultMask.addValue(valueToMask); } /** * Adds the comma separated string of value regexes to the regexes that will be used to determine if a field value should be masked. * *

The {@link #setDefaultMask(String) default mask} value will be substituted for values that match the given regexes.

* * @param valuesToMask comma-separated string of regular expressions used to determine if a value should be masked. */ public void addValues(String valuesToMask) { valuesWithDefaultMask.addValues(valuesToMask); } /** * Adds the given value regexes and mask to the regexes that will be used to determine if a field value should be masked. * *

The {@link ValueMask#setMask(String) mask} value will be written for values that match any of the {@link ValueMask#addValue(String) value regexes}.

* * @param valueMask regular expressions used to determine if a value should be masked, and their corresponding mask value */ public void addValueMask(ValueMask valueMask) { addValueMaskSupplier(() -> valueMask); } /** * Adds the given supplier of value regexes and mask to the regexes that will be used to determine if a field value should be masked. * *

The {@link ValueMask#setMask(String) mask} value will be written for values that match any of the {@link ValueMask#addValue(String) value regexes}.

* * @param valueMaskSupplier a supplier of regular expressions used to determine if a value should be masked, and their corresponding mask value */ public void addValueMaskSupplier(ValueMaskSupplier valueMaskSupplier) { valueMaskSuppliers.add(valueMaskSupplier); } /** * Add the given {@link ValueMasker} to the maskers used to mask a value. * * @param valueMasker the masker to add */ public void addValueMasker(ValueMasker valueMasker) { valueMaskers.add(valueMasker); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy