com.yahoo.vespa.model.container.http.AccessControl Maven / Gradle / Ivy
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.container.http;
import com.yahoo.component.ComponentId;
import com.yahoo.component.ComponentSpecification;
import com.yahoo.vespa.defaults.Defaults;
import com.yahoo.vespa.model.container.ApplicationContainerCluster;
import com.yahoo.vespa.model.container.ContainerCluster;
import com.yahoo.vespa.model.container.component.BindingPattern;
import com.yahoo.vespa.model.container.component.FileStatusHandlerComponent;
import com.yahoo.vespa.model.container.component.Handler;
import com.yahoo.vespa.model.container.component.SystemBindingPattern;
import com.yahoo.vespa.model.container.http.ssl.HostedSslConnectorFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* Helper class for http access control.
*
* @author gjoranv
* @author bjorncs
*/
public class AccessControl {
public enum ClientAuthentication { want, need;}
public static final ComponentId ACCESS_CONTROL_CHAIN_ID = ComponentId.fromString("access-control-chain");
public static final ComponentId ACCESS_CONTROL_EXCLUDED_CHAIN_ID = ComponentId.fromString("access-control-excluded-chain");
public static final ComponentId DEFAULT_CONNECTOR_HOSTED_REQUEST_CHAIN_ID = ComponentId.fromString("default-connector-hosted-request-chain");
private static final int HOSTED_CONTAINER_PORT = 4443;
// Handlers that are excluded from access control
public static final List EXCLUDED_HANDLERS = List.of(
FileStatusHandlerComponent.CLASS,
ContainerCluster.APPLICATION_STATUS_HANDLER_CLASS,
ContainerCluster.BINDINGS_OVERVIEW_HANDLER_CLASS,
ContainerCluster.STATE_HANDLER_CLASS,
ContainerCluster.LOG_HANDLER_CLASS,
ApplicationContainerCluster.METRICS_V2_HANDLER_CLASS,
ApplicationContainerCluster.PROMETHEUS_V1_HANDLER_CLASS
);
public static class Builder {
private final String domain;
private ClientAuthentication clientAuthentication = ClientAuthentication.need;
private final Set excludeBindings = new LinkedHashSet<>();
private Collection handlers = List.of();
public Builder(String domain) {
this.domain = domain;
}
public Builder excludeBinding(BindingPattern binding) {
this.excludeBindings.add(binding);
return this;
}
public Builder setHandlers(ApplicationContainerCluster cluster) {
this.handlers = cluster.getHandlers();
return this;
}
public Builder clientAuthentication(ClientAuthentication clientAuthentication) {
this.clientAuthentication = clientAuthentication;
return this;
}
public AccessControl build() {
return new AccessControl(domain, clientAuthentication, excludeBindings, handlers);
}
}
public final String domain;
public final ClientAuthentication clientAuthentication;
private final Set excludedBindings;
private final Collection handlers;
private AccessControl(String domain,
ClientAuthentication clientAuthentication,
Set excludedBindings,
Collection handlers) {
this.domain = domain;
this.clientAuthentication = clientAuthentication;
this.excludedBindings = Collections.unmodifiableSet(excludedBindings);
this.handlers = handlers;
}
public void configureHttpFilterChains(Http http) {
http.setAccessControl(this);
addAccessControlFilterChain(http);
addAccessControlExcludedChain(http);
addDefaultHostedRequestChain(http);
removeDuplicateBindingsFromAccessControlChain(http);
}
public void configureHostedConnector(HostedSslConnectorFactory connectorFactory) {
connectorFactory.setDefaultRequestFilterChain(ACCESS_CONTROL_CHAIN_ID);
}
public void configureDefaultHostedConnector(Http http) {
// Set default filter chain on local port
http.getHttpServer()
.get()
.getConnectorFactories()
.stream()
.filter(cf -> cf.getListenPort() == Defaults.getDefaults().vespaWebServicePort())
.findFirst()
.orElseThrow(() -> new RuntimeException("Could not find default connector"))
.setDefaultRequestFilterChain(DEFAULT_CONNECTOR_HOSTED_REQUEST_CHAIN_ID);
}
/** returns the excluded bindings as specified in 'access-control' in services.xml **/
public Set excludedBindings() { return excludedBindings; }
/** all handlers (that are known by the access control components) **/
public Collection handlers() { return handlers; }
public static boolean hasHandlerThatNeedsProtection(ApplicationContainerCluster cluster) {
return cluster.getHandlers().stream()
.anyMatch(handler -> ! isExcludedHandler(handler) && hasNonMbusBinding(handler));
}
private void addAccessControlFilterChain(Http http) {
http.getFilterChains().add(createChain(ACCESS_CONTROL_CHAIN_ID));
}
private void addAccessControlExcludedChain(Http http) {
http.getFilterChains().add(createChain(ACCESS_CONTROL_EXCLUDED_CHAIN_ID));
for (BindingPattern excludedBinding : excludedBindings) {
http.getBindings().add(createAccessControlExcludedBinding(excludedBinding));
}
for (Handler handler : handlers) {
if (isExcludedHandler(handler)) {
for (BindingPattern binding : handler.getServerBindings()) {
http.getBindings().add(createAccessControlExcludedBinding(binding));
}
}
}
}
// Add a filter chain used by default hosted connector
private void addDefaultHostedRequestChain(Http http) {
HttpFilterChain chain = createChain(DEFAULT_CONNECTOR_HOSTED_REQUEST_CHAIN_ID);
http.getFilterChains().add(chain);
}
// Remove bindings from access control chain that have binding pattern as a different filter chain
private void removeDuplicateBindingsFromAccessControlChain(Http http) {
removeDuplicateBindingsFromChain(http, ACCESS_CONTROL_EXCLUDED_CHAIN_ID);
}
private void removeDuplicateBindingsFromChain(Http http, ComponentId chainId) {
Set duplicateBindings = new HashSet<>();
for (FilterBinding binding : http.getBindings()) {
if (binding.chainId().toId().equals(chainId)) {
for (FilterBinding otherBinding : http.getBindings()) {
if (effectivelyDuplicateOf(binding, otherBinding)) {
duplicateBindings.add(binding);
}
}
}
}
duplicateBindings.forEach(http.getBindings()::remove);
}
private static boolean effectivelyDuplicateOf(FilterBinding accessControlBinding, FilterBinding other) {
if (accessControlBinding.chainId().equals(other.chainId())) return false; // Same filter chain
if (other.type() == FilterBinding.Type.RESPONSE) return false;
return accessControlBinding.binding().equals(other.binding())
|| (accessControlBinding.binding().path().equals(other.binding().path()) && other.binding().matchesAnyPort());
}
private static FilterBinding createAccessControlExcludedBinding(BindingPattern excludedBinding) {
BindingPattern rewrittenBinding = SystemBindingPattern.fromHttpPortAndPath(
Integer.toString(HOSTED_CONTAINER_PORT), excludedBinding.path()); // only keep path from excluded binding
return FilterBinding.create(
FilterBinding.Type.REQUEST,
new ComponentSpecification(ACCESS_CONTROL_EXCLUDED_CHAIN_ID.stringValue()),
rewrittenBinding);
}
private static HttpFilterChain createChain(ComponentId id) { return new HttpFilterChain(id, HttpFilterChain.Type.SYSTEM); }
private static boolean isExcludedHandler(Handler handler) { return EXCLUDED_HANDLERS.contains(handler.getClassId().getName()); }
private static boolean hasNonMbusBinding(Handler handler) {
return handler.getServerBindings().stream().anyMatch(binding -> ! binding.scheme().equals("mbus"));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy