/*
* This file is part of a module with proprietary Enterprise Features.
*
* Licensed to Crate.io Inc. ("Crate.io") under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* Unauthorized copying of this file, via any medium is strictly prohibited.
*
* To use this file, Crate.io must have given you permission to enable and
* use such Enterprise Features and you must have a valid Enterprise or
* Subscription Agreement with Crate.io. If you enable or use the Enterprise
* Features, you represent and warrant that you have a valid Enterprise or
* Subscription Agreement with Crate.io. Your use of the Enterprise Features
* if governed by the terms and conditions of your Enterprise or Subscription
* Agreement with Crate.io.
*/
package io.crate.operation.auth;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import org.elasticsearch.common.settings.Settings;
import org.jboss.netty.handler.ipfilter.CIDR4;
import javax.annotation.Nullable;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
public class HostBasedAuthentication implements Authentication {
private static final String DEFAULT_AUTH_METHOD = "trust";
private static final String KEY_USER = "user";
private static final String KEY_ADDRESS = "address";
private static final String KEY_METHOD = "method";
private static final String KEY_PROTOCOL = "protocol";
/*
* The cluster state contains the hbaConf from the setting in this format:
{
"<ident>": {
"address": "<cidr>",
"method": "auth",
"user": "<username>"
"protocol": "pg"
},
...
}
*/
private Map<String, Map<String, String>> hbaConf;
private boolean enabled;
private final Map<String, Supplier<AuthenticationMethod>> authMethodRegistry = new HashMap<>();
HostBasedAuthentication(Settings settings) {
enabled = AuthenticationProvider.AUTH_HOST_BASED_ENABLED_SETTING.setting().get(settings);
hbaConf = convertHbaSettingsToHbaConf(AuthenticationProvider.AUTH_HOST_BASED_CONFIG_SETTING.setting().get(settings));
registerAuthMethod(TrustAuthentication.NAME, TrustAuthentication::new);
}
void updateHbaConfig(Map<String, Map<String, String>> hbaMap) {
hbaConf = hbaMap;
}
private Map<String, Map<String, String>> convertHbaSettingsToHbaConf(Settings hbaSetting) {
if (hbaSetting.isEmpty()) {
return Collections.emptyMap();
}
ImmutableMap.Builder<String, Map<String, String>> hostBasedConf = ImmutableMap.builder();
for (Map.Entry<String, Settings> entry : hbaSetting.getAsGroups().entrySet()) {
hostBasedConf.put(entry.getKey(), entry.getValue().getAsMap());
}
return hostBasedConf.build();
}
void registerAuthMethod(String name, Supplier<AuthenticationMethod> supplier) {
authMethodRegistry.put(name, supplier);
}
@Override
public boolean enabled() {
return enabled;
}
@Override
@Nullable
public AuthenticationMethod resolveAuthenticationType(String user, InetAddress address, HbaProtocol protocol) {
assert hbaConf != null : "hba configuration is missing";
Optional<Map.Entry<String, Map<String, String>>> entry = getEntry(user, address, protocol);
if (entry.isPresent()) {
String methodName = entry.get()
.getValue()
.getOrDefault(KEY_METHOD, DEFAULT_AUTH_METHOD);
Supplier<AuthenticationMethod> supplier = authMethodRegistry.get(methodName);
if (supplier != null) {
return supplier.get();
}
}
return null;
}
@VisibleForTesting
Map<String, Map<String, String>> hbaConf() {
return hbaConf;
}
@VisibleForTesting
Optional<Map.Entry<String, Map<String, String>>> getEntry(String user, InetAddress address, HbaProtocol protocol) {
if (user == null || address == null || protocol == null) {
return Optional.empty();
}
return hbaConf.entrySet().stream()
.filter(e -> Matchers.isValidUser(e, user))
.filter(e -> Matchers.isValidAddress(e, address))
.filter(e -> Matchers.isValidProtocol(e.getValue().get(KEY_PROTOCOL), protocol))
.findFirst();
}
static class Matchers {
static boolean isValidUser(Map.Entry<String, Map<String, String>> entry, String user) {
String hbaUser = entry.getValue().get(KEY_USER);
return hbaUser == null || user.equals(hbaUser);
}
static boolean isValidAddress(Map.Entry<String, Map<String, String>> entry, InetAddress address) {
String hbaAddress = entry.getValue().get(KEY_ADDRESS);
if (hbaAddress == null) {
// no IP/CIDR --> 0.0.0.0/0 --> match all
return true;
} else if (!hbaAddress.contains("/")) {
// if IP format --> add 32 mask bits
hbaAddress += "/32";
}
try {
return CIDR4.newCIDR(hbaAddress).contains(address);
} catch (UnknownHostException e) {
// this should not happen because we add the required network mask upfront
}
return false;
}
static boolean isValidProtocol(String hbaProtocol, HbaProtocol protocol) {
return hbaProtocol == null || hbaProtocol.equals(protocol.toString());
}
}
}