Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ project(':cruise-control-core') {
implementation "org.slf4j:slf4j-api:1.7.36"
implementation "org.apache.logging.log4j:log4j-slf4j-impl:2.17.2"
implementation 'org.apache.commons:commons-math3:3.6.1'
api "org.eclipse.jetty:jetty-servlet:${jettyVersion}"
api "org.eclipse.jetty.ee10:jetty-ee10-servlet:${jettyVersion}"
implementation 'com.github.spotbugs:spotbugs-annotations:4.8.6'

api "io.vertx:vertx-core:${vertxVersion}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import com.linkedin.kafka.cruisecontrol.servlet.ServletRequestHandler;
import com.linkedin.kafka.cruisecontrol.servlet.security.CruiseControlSecurityHandler;
import com.linkedin.kafka.cruisecontrol.servlet.security.SecurityProvider;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.ee10.servlet.security.ConstraintSecurityHandler;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.CustomRequestLog;
import org.eclipse.jetty.server.HttpConfiguration;
Expand All @@ -17,11 +17,11 @@
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.Slf4jRequestLogWriter;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.ee10.servlet.DefaultServlet;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import javax.servlet.ServletException;
import jakarta.servlet.ServletException;
import java.util.List;

public class KafkaCruiseControlServletApp extends KafkaCruiseControlApp {
Expand Down Expand Up @@ -55,7 +55,7 @@ protected ServerConnector setupHttpConnector(String hostname, int port) {
ServerConnector serverConnector;
Boolean webserverSslEnable = _config.getBoolean(WebServerConfig.WEBSERVER_SSL_ENABLE_CONFIG);
if (webserverSslEnable != null && webserverSslEnable) {
SslContextFactory sslServerContextFactory = new SslContextFactory.Server();
SslContextFactory.Server sslServerContextFactory = new SslContextFactory.Server();
sslServerContextFactory.setKeyStorePath(_config.getString(WebServerConfig.WEBSERVER_SSL_KEYSTORE_LOCATION_CONFIG));
sslServerContextFactory.setKeyStorePassword(_config.getPassword(WebServerConfig.WEBSERVER_SSL_KEYSTORE_PASSWORD_CONFIG).value());
sslServerContextFactory.setKeyManagerPassword(_config.getPassword(WebServerConfig.WEBSERVER_SSL_KEY_PASSWORD_CONFIG).value());
Expand Down Expand Up @@ -124,7 +124,7 @@ protected void setupWebUi(ServletContextHandler contextHandler) {
DefaultServlet defaultServlet = new DefaultServlet();
ServletHolder holderWebapp = new ServletHolder("default", defaultServlet);
// holderWebapp.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false");
holderWebapp.setInitParameter("resourceBase", webuiDir);
holderWebapp.setInitParameter("baseResource", webuiDir);
contextHandler.addServlet(holderWebapp, webuiPathPrefix);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
import org.apache.kafka.common.serialization.Deserializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletException;
import jakarta.servlet.ServletException;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSession;


/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import static com.linkedin.kafka.cruisecontrol.config.constants.CruiseControlParametersConfig.*;
import static com.linkedin.kafka.cruisecontrol.config.constants.CruiseControlRequestConfig.*;
import static com.linkedin.kafka.cruisecontrol.servlet.CruiseControlEndPoint.*;
import static com.linkedin.kafka.cruisecontrol.servlet.parameters.ParameterUtils.*;
import static com.linkedin.kafka.cruisecontrol.servlet.response.ResponseUtils.writeErrorResponse;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;


/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
import com.linkedin.kafka.cruisecontrol.servlet.response.ResponseUtils;
import io.vertx.core.MultiMap;
import io.vertx.core.http.impl.headers.HeadersMultiMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
import com.linkedin.kafka.cruisecontrol.KafkaCruiseControlEndPoints;
import com.linkedin.kafka.cruisecontrol.KafkaCruiseControlRequestHandler;
import com.linkedin.kafka.cruisecontrol.async.AsyncKafkaCruiseControl;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

import static com.linkedin.kafka.cruisecontrol.servlet.KafkaCruiseControlServletUtils.handleOptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
package com.linkedin.kafka.cruisecontrol.servlet;

import com.linkedin.cruisecontrol.http.CruiseControlHttpSession;
import javax.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSession;
import java.util.Objects;

public class ServletSession implements CruiseControlHttpSession {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,25 @@

package com.linkedin.kafka.cruisecontrol.servlet;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Set;
import java.util.HashMap;
import java.util.HashSet;
import java.util.stream.Collectors;
import java.util.Collections;
import com.linkedin.kafka.cruisecontrol.config.KafkaCruiseControlConfig;
import org.eclipse.jetty.security.RolePrincipal;
import org.eclipse.jetty.security.UserStore;
import org.eclipse.jetty.security.PropertyUserStore;
import org.eclipse.jetty.security.AbstractLoginService;
import com.linkedin.kafka.cruisecontrol.config.constants.WebServerConfig;
import javax.security.auth.Subject;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -39,23 +46,21 @@ public UserPermissionsManager(KafkaCruiseControlConfig config) {
* @return a map of usernames -> their assigned roles
*/
private Map<String, Set<String>> createRolesPerUsersMap() {
Map<String, Set<String>> rolesPerUsers = new HashMap();
Map<String, Set<String>> rolesPerUsers = new HashMap<>();
boolean securityEnabled = _config.getBoolean(WebServerConfig.WEBSERVER_SECURITY_ENABLE_CONFIG);
if (securityEnabled) {
String privilegedFilePath = _config.getString(WebServerConfig.WEBSERVER_AUTH_CREDENTIALS_FILE_CONFIG);
UserStore userStore = createUserStoreFromFile(privilegedFilePath);
String privilegesFilePath = _config.getString(WebServerConfig.WEBSERVER_AUTH_CREDENTIALS_FILE_CONFIG);
Resource resource = ResourceFactory.of(new ResourceHandler()).newResource(privilegesFilePath);
UserStore userStore = createUserStoreFromResource(resource);
startUserStore(userStore);

Set<String> userNames = userStore.getKnownUserIdentities().keySet();
Set<String> userNames = parseUsernames(resource);

for (String user : userNames) {
Subject userSubject = userStore.getUserIdentity(user).getSubject();
Set<AbstractLoginService.RolePrincipal> roles = userSubject == null
? new HashSet<>()
: userSubject.getPrincipals(AbstractLoginService.RolePrincipal.class);
Set<RolePrincipal> roles = new HashSet<>(userStore.getRolePrincipals(user));

Set<String> roleNames = roles.stream()
.map(AbstractLoginService.RolePrincipal::getName)
.map(RolePrincipal::getName)
.map(String::toUpperCase)
.collect(Collectors.toSet());
rolesPerUsers.put(user, roleNames);
Expand Down Expand Up @@ -94,12 +99,43 @@ public Set<String> getRolesBy(String userName) {

/** Creates UserStore from an external file
*
* @param privilegedFilePath a filepath containing user privileges information
* @param privilegedResource a filepath containing user privileges information
* @return a UserStore object
*/
private UserStore createUserStoreFromFile(String privilegedFilePath) {
private UserStore createUserStoreFromResource(Resource privilegedResource) {
PropertyUserStore userStore = new PropertyUserStore();
userStore.setConfig(privilegedFilePath);
userStore.setConfig(privilegedResource);
return userStore;
}

/** Creates a set of usernames from a Resource
*
* @param resource a Resource containing user privileges information
* @return a Set of usernames parsed from the Resource
*/
private static Set<String> parseUsernames(Resource resource) {
if (!resource.exists() || !resource.isReadable()) {
return Set.of();
}
Set<String> usernames = new HashSet<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.newInputStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) {
continue;
}
int colonIndex = line.indexOf(':');
if (colonIndex != -1) {
String username = line.substring(0, colonIndex).trim();
if (!username.isEmpty()) {
usernames.add(username);
}
}
}
} catch (IOException e) {
throw new UncheckedIOException("Failed to read usernames from " + resource, e);
}
return usernames;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import com.linkedin.kafka.cruisecontrol.servlet.response.JsonResponseClass;
import com.linkedin.kafka.cruisecontrol.servlet.response.JsonResponseField;
import org.apache.kafka.common.utils.Time;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import io.vertx.ext.web.RoutingContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
import static com.linkedin.kafka.cruisecontrol.servlet.purgatory.ReviewStatus.APPROVED;
import static com.linkedin.kafka.cruisecontrol.servlet.purgatory.ReviewStatus.DISCARDED;
import static com.linkedin.kafka.cruisecontrol.servlet.response.ResponseUtils.writeErrorResponse;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;

/**
* The util class for Kafka Cruise Control parameters.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import com.linkedin.cruisecontrol.http.CruiseControlRequestContext;
import java.io.IOException;

import static javax.servlet.http.HttpServletResponse.SC_OK;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;


public abstract class AbstractCruiseControlResponse implements CruiseControlResponse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import static com.linkedin.kafka.cruisecontrol.servlet.response.ResponseUtils.JSON_VERSION;
import static com.linkedin.kafka.cruisecontrol.servlet.response.ResponseUtils.VERSION;
import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED;
import static jakarta.servlet.http.HttpServletResponse.SC_ACCEPTED;

@JsonResponseClass
public class ProgressResult extends AbstractCruiseControlResponse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import com.linkedin.kafka.cruisecontrol.config.constants.WebServerConfig;
import io.vertx.ext.web.RoutingContext;
import org.apache.kafka.common.config.AbstractConfig;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;

/**
* This class defines a HTTP Basic authenticator with a file based {@link HashLoginService} and uses the default
Expand All @@ -27,7 +30,8 @@ public void init(KafkaCruiseControlConfig config) {

@Override
public LoginService loginService() {
return new HashLoginService("DefaultLoginService", _userCredentialsFile);
Resource resource = ResourceFactory.of(new ResourceHandler()).newResource(_userCredentialsFile);
return new HashLoginService("DefaultLoginService", resource);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,17 @@

package com.linkedin.kafka.cruisecontrol.servlet.security;

import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.ee10.servlet.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.Constraint;
import org.eclipse.jetty.server.Request;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* A custom {@link ConstraintSecurityHandler} that converts the request to lowercase to ensure case insensitivity.
*/
public class CruiseControlSecurityHandler extends ConstraintSecurityHandler {

@Override
public void handle(String pathInContext, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
super.handle(pathInContext.toLowerCase(), baseRequest, request, response);
public Constraint getConstraint(String pathInContext, Request request) {
return super.getConstraint(pathInContext.toLowerCase(), request);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.ee10.servlet.security.ConstraintMapping;
import org.eclipse.jetty.security.Constraint;


/**
Expand Down Expand Up @@ -65,10 +65,8 @@ public Set<String> roles() {
}

private ConstraintMapping mapping(CruiseControlEndPoint endpoint, String... roles) {
Constraint constraint = new Constraint();
constraint.setName(Constraint.__BASIC_AUTH);
constraint.setRoles(roles);
constraint.setAuthenticate(true);
Constraint.Builder builder = new Constraint.Builder();
Constraint constraint = builder.roles(roles).name("BASIC").authorization(Constraint.Authorization.SPECIFIC_ROLE).build();
ConstraintMapping mapping = new ConstraintMapping();
mapping.setPathSpec(_webServerApiUrlPrefix.replace("*", endpoint.name().toLowerCase()));
mapping.setConstraint(constraint);
Expand Down
Loading
Loading