blob: 41170bebe4f342d8a8e345e185309e4d78fda5e3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.servicecomb.authentication.provider;
import java.beans.PropertyDescriptor;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang3.StringUtils;
import org.apache.servicecomb.core.Const;
import org.apache.servicecomb.registry.api.registry.Microservice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.config.ConcurrentCompositeConfiguration;
import com.netflix.config.DynamicPropertyFactory;
/**
* Add black / white list control to service access
*/
public class AccessController {
static class ConfigurationItem {
static final String CATEGORY_PROPERTY = "property";
String category;
String propertyName;
String rule;
}
private static final Logger LOG = LoggerFactory.getLogger(AccessController.class);
private static final String KEY_WHITE_LIST_PREFIX = "servicecomb.publicKey.accessControl.white";
private static final String KEY_BLACK_LIST_PREFIX = "servicecomb.publicKey.accessControl.black";
private static final String KEY_PROPERTY_NAME = "%s.%s.propertyName";
private static final String KEY_CATEGORY = "%s.%s.category";
private static final String KEY_RULE_POSTFIX = ".rule";
private Map<String, ConfigurationItem> whiteList = new HashMap<>();
private Map<String, ConfigurationItem> blackList = new HashMap<>();
public AccessController() {
loadConfigurations(KEY_BLACK_LIST_PREFIX);
loadConfigurations(KEY_WHITE_LIST_PREFIX);
}
public boolean isAllowed(Microservice microservice) {
return whiteAllowed(microservice) && !blackDenied(microservice);
}
private boolean whiteAllowed(Microservice microservice) {
if (whiteList.isEmpty()) {
return true;
}
return matchFound(microservice, whiteList);
}
private boolean blackDenied(Microservice microservice) {
if (blackList.isEmpty()) {
return false;
}
return matchFound(microservice, blackList);
}
private boolean matchFound(Microservice microservice, Map<String, ConfigurationItem> ruleList) {
boolean matched = false;
for (ConfigurationItem item : ruleList.values()) {
if (ConfigurationItem.CATEGORY_PROPERTY.equals(item.category)) {
// we support to configure properties, e.g. serviceName, appId, environment, alias, version and so on, also support key in properties.
if (matchMicroserviceField(microservice, item) || matchMicroserviceProperties(microservice, item)) {
return true;
}
}
}
return matched;
}
private boolean matchMicroserviceProperties(Microservice microservice, ConfigurationItem item) {
Map<String, String> properties = microservice.getProperties();
for (Entry<String, String> entry : properties.entrySet()) {
if (!entry.getKey().equals(item.propertyName)) {
continue;
}
return isPatternMatch(entry.getValue(), item.rule);
}
return false;
}
private boolean matchMicroserviceField(Microservice microservice, ConfigurationItem item) {
Object fieldValue = null;
try {
fieldValue = new PropertyDescriptor(item.propertyName, Microservice.class).getReadMethod().invoke(microservice);
} catch (Exception e) {
if (DynamicPropertyFactory.getInstance().getBooleanProperty(Const.PRINT_SENSITIVE_ERROR_MESSAGE,
false).get()) {
LOG.warn("can't find propertyname: {} in microservice field, will search in microservice properties.",
item.propertyName, e);
} else {
LOG.warn("can't find propertyname: {} in microservice field, will search in microservice properties.",
item.propertyName);
}
return false;
}
if (fieldValue.getClass().getName().equals(String.class.getName())) {
return isPatternMatch((String) fieldValue, item.rule);
}
return false;
}
private boolean isPatternMatch(String value, String pattern) {
if (pattern.startsWith("*")) {
return value.endsWith(pattern.substring(1));
}
if (pattern.endsWith("*")) {
return value.startsWith(pattern.substring(0, pattern.length() - 1));
}
return value.equals(pattern);
}
private void loadConfigurations(String prefix) {
ConcurrentCompositeConfiguration config = (ConcurrentCompositeConfiguration) DynamicPropertyFactory
.getBackingConfigurationSource();
loadConfigurations(config, prefix);
config.addConfigurationListener(event -> {
if (event.getPropertyName().startsWith(prefix)) {
LOG.info("Access rule have been changed. Reload configurations. Event=" + event.getType());
loadConfigurations(config, prefix);
}
});
}
private void loadConfigurations(ConcurrentCompositeConfiguration config, String prefix) {
Map<String, ConfigurationItem> configurations = new HashMap<>();
Iterator<String> configsItems = config.getKeys(prefix);
while (configsItems.hasNext()) {
String pathKey = configsItems.next();
if (pathKey.endsWith(KEY_RULE_POSTFIX)) {
ConfigurationItem configurationItem = new ConfigurationItem();
String rule = DynamicPropertyFactory.getInstance()
.getStringProperty(pathKey, null).get();
if (StringUtils.isEmpty(rule)) {
continue;
}
configurationItem.rule = rule;
String pathKeyItem = pathKey
.substring(prefix.length() + 1, pathKey.length() - KEY_RULE_POSTFIX.length());
configurationItem.propertyName = DynamicPropertyFactory.getInstance()
.getStringProperty(String.format(KEY_PROPERTY_NAME, prefix, pathKeyItem), null).get();
if (StringUtils.isEmpty(configurationItem.propertyName)) {
continue;
}
configurationItem.category = DynamicPropertyFactory.getInstance()
.getStringProperty(String.format(KEY_CATEGORY, prefix, pathKeyItem), null).get();
if (StringUtils.isEmpty(configurationItem.category)) {
continue;
}
configurations.put(pathKeyItem, configurationItem);
}
}
if (KEY_WHITE_LIST_PREFIX.equals(prefix)) {
this.whiteList = configurations;
logConfigurations(configurations, true);
} else {
this.blackList = configurations;
logConfigurations(configurations, false);
}
}
private void logConfigurations(Map<String, ConfigurationItem> configurations, boolean isWhite) {
configurations.forEach((key, item) -> LOG.info((isWhite ? "White list " : "Black list ") + "config item: key=" + key
+ ";category=" + item.category
+ ";propertyName=" + item.propertyName
+ ";rule=" + item.rule));
}
}