/* (c) 2017 Open Source Geospatial Foundation - all rights reserved * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.security.config; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; import org.apache.commons.lang.SerializationUtils; import org.geoserver.platform.GeoServerEnvironment; import org.geoserver.platform.GeoServerExtensions; import org.geotools.util.logging.Logging; import org.springframework.security.web.util.matcher.IpAddressMatcher; /** * Configuration for brute force attack preventer * * @author Andrea Aime - GeoSolutions */ public class BruteForcePreventionConfig implements SecurityConfig { static final Logger LOGGER = Logging.getLogger(BruteForcePreventionConfig.class); private static final long serialVersionUID = 5774047555637121124L; /** * Default brute force attack configuration */ public static final BruteForcePreventionConfig DEFAULT = new BruteForcePreventionConfig(); boolean enabled; int minDelaySeconds; int maxDelaySeconds; int maxBlockedThreads; List<String> whitelistedMasks; transient List<IpAddressMatcher> whitelistedAddressMatchers; /** * Configuration based on defaults */ public BruteForcePreventionConfig() { this.enabled = true; this.minDelaySeconds = 1; this.maxDelaySeconds = 5; this.whitelistedMasks = new ArrayList<>(); this.whitelistedMasks.add("127.0.0.1"); this.maxBlockedThreads = 100; } public BruteForcePreventionConfig(BruteForcePreventionConfig other) { this.enabled = other.enabled; this.minDelaySeconds = other.minDelaySeconds; this.maxDelaySeconds = other.maxDelaySeconds; this.whitelistedMasks = other.whitelistedMasks != null ? new ArrayList<>(other.whitelistedMasks) : null; this.maxBlockedThreads = other.maxBlockedThreads; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public int getMinDelaySeconds() { return minDelaySeconds; } public void setMinDelaySeconds(int minConfig) { this.minDelaySeconds = minConfig; } public int getMaxDelaySeconds() { return maxDelaySeconds; } public void setMaxDelaySeconds(int maxConfig) { this.maxDelaySeconds = maxConfig; } public List<String> getWhitelistedMasks() { return whitelistedMasks; } public void setWhitelistedMasks(List<String> whitelistedMasks) { this.whitelistedMasks = whitelistedMasks; if (whitelistedMasks == null) { this.whitelistedAddressMatchers = null; } } public List<IpAddressMatcher> getWhitelistAddressMatchers() { try { if(this.getWhitelistedMasks() != null && this.whitelistedAddressMatchers == null) { this.whitelistedAddressMatchers = whitelistedMasks.stream() .map(mask -> new IpAddressMatcher(mask)).collect(Collectors.toList()); } } catch(Exception e) { // an error here and no request can be made, best be cautious (yes, it actually // happened to me) LOGGER.log(Level.SEVERE, "Invalid netmask configuration, will skip it", e); } return this.whitelistedAddressMatchers; } public int getMaxBlockedThreads() { return maxBlockedThreads; } public void setMaxBlockedThreads(int maxBlockedThreads) { this.maxBlockedThreads = maxBlockedThreads; } @Override public SecurityConfig clone(boolean allowEnvParametrization) { BruteForcePreventionConfig clone = new BruteForcePreventionConfig(this); // allow parametrization of the whitelisted masks final GeoServerEnvironment gsEnvironment = GeoServerExtensions.bean(GeoServerEnvironment.class); if (clone!= null) { if (allowEnvParametrization && gsEnvironment != null && GeoServerEnvironment.ALLOW_ENV_PARAMETRIZATION) { List<String> resolvedMasks = new ArrayList<>(); for (String mask : whitelistedMasks) { String resolved = (String) gsEnvironment.resolveValue(mask); if(resolved != null) { Arrays.stream(resolved.split("\\s*,\\s*")).filter(s -> s != null && !s.trim().isEmpty()).forEach(s -> resolvedMasks.add(s)); } } clone.setWhitelistedMasks(resolvedMasks); } } return clone; } }