Skip to content

Commit

Permalink
Port most of the OAUTH2 support from Trino to Presto
Browse files Browse the repository at this point in the history
Not a cherry pick directly however the following commits are part of this
trinodb/trino@3879f45
trinodb/trino@cd3da24
trinodb/trino@dcb6f0b
trinodb/trino@7cdd133
trinodb/trino@8b8b0be
trinodb/trino@15e53ff

Co-authored-by: lukasz-walkiewicz <[email protected]>
Co-authored-by: Nik Hodgkinson <[email protected]>
  • Loading branch information
3 people authored and auden-woolfson committed Jan 23, 2025
1 parent 15fd5d8 commit d3514e1
Show file tree
Hide file tree
Showing 62 changed files with 5,307 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.facebook.airlift.discovery.client.ServiceAnnouncement;
import com.facebook.airlift.event.client.HttpEventModule;
import com.facebook.airlift.event.client.JsonEventModule;
import com.facebook.airlift.http.server.HttpServerModule;
import com.facebook.airlift.jaxrs.JaxrsModule;
import com.facebook.airlift.jmx.JmxHttpModule;
import com.facebook.airlift.jmx.JmxModule;
Expand Down Expand Up @@ -51,8 +52,10 @@
import com.facebook.presto.security.AccessControlManager;
import com.facebook.presto.security.AccessControlModule;
import com.facebook.presto.server.security.PasswordAuthenticatorManager;
import com.facebook.presto.server.security.SecurityConfig;
import com.facebook.presto.server.security.PrestoAuthenticatorManager;
import com.facebook.presto.server.security.ServerSecurityModule;
import com.facebook.presto.server.security.oauth2.OAuth2Client;
import com.facebook.presto.sql.analyzer.FeaturesConfig;
import com.facebook.presto.sql.parser.SqlParserOptions;
import com.facebook.presto.sql.planner.sanity.PlanCheckerProviderManager;
Expand Down Expand Up @@ -83,6 +86,7 @@
import static com.facebook.airlift.json.JsonBinder.jsonBinder;
import static com.facebook.presto.server.PrestoSystemRequirements.verifyJvmRequirements;
import static com.facebook.presto.server.PrestoSystemRequirements.verifySystemTimeIsReasonable;
import static com.facebook.presto.server.security.SecurityConfig.AuthenticationType.OAUTH2;
import static com.google.common.base.Strings.nullToEmpty;
import static java.util.Objects.requireNonNull;

Expand Down Expand Up @@ -198,6 +202,11 @@ public void run()
injector.getInstance(ClientRequestFilterManager.class).loadClientRequestFilters();
startAssociatedProcesses(injector);

SecurityConfig securityConfig = injector.getInstance(SecurityConfig.class);
if (securityConfig.getAuthenticationTypes().contains(OAUTH2)) {
injector.getInstance(OAuth2Client.class).load();
}

injector.getInstance(Announcer.class).start();

log.info("======== SERVER STARTED ========");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
*/
package com.facebook.presto.server;

import com.facebook.presto.server.security.oauth2.OAuthWebUiCookie;

import javax.annotation.security.RolesAllowed;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
Expand All @@ -21,15 +23,19 @@
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import java.util.Optional;

import static com.facebook.presto.server.security.RoleType.ADMIN;
import static com.facebook.presto.server.security.oauth2.OAuth2Utils.getLastURLParameter;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.net.HttpHeaders.X_FORWARDED_PROTO;
import static javax.ws.rs.core.Response.Status.MOVED_PERMANENTLY;

@Path("/")
@Path(WebUiResource.UI_ENDPOINT)
@RolesAllowed(ADMIN)
public class WebUiResource
{
public static final String UI_ENDPOINT = "/";

@GET
public Response redirectIndexHtml(
@HeaderParam(X_FORWARDED_PROTO) String proto,
Expand All @@ -38,9 +44,30 @@ public Response redirectIndexHtml(
if (isNullOrEmpty(proto)) {
proto = uriInfo.getRequestUri().getScheme();
}
Optional<String> lastURL = getLastURLParameter(uriInfo.getQueryParameters());
if (lastURL.isPresent()) {
return Response
.seeOther(uriInfo.getRequestUriBuilder().scheme(proto).uri(lastURL.get()).build())
.build();
}

return Response
.seeOther(uriInfo.getRequestUriBuilder().scheme(proto).path("/ui/").replaceQuery("").build())
.build();
}

return Response.status(MOVED_PERMANENTLY)
.location(uriInfo.getRequestUriBuilder().scheme(proto).path("/ui/").build())
@GET
@Path("/logout")
public Response logout(
@HeaderParam(X_FORWARDED_PROTO) String proto,
@Context UriInfo uriInfo)
{
if (isNullOrEmpty(proto)) {
proto = uriInfo.getRequestUri().getScheme();
}
return Response
.seeOther(uriInfo.getBaseUriBuilder().scheme(proto).path("/ui/logout.html").build())
.cookie(OAuthWebUiCookie.delete())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.facebook.presto.ClientRequestFilterManager;
import com.facebook.presto.spi.ClientRequestFilter;
import com.facebook.presto.spi.PrestoException;
import com.facebook.presto.server.security.oauth2.OAuth2Authenticator;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
Expand Down Expand Up @@ -46,8 +47,12 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static com.facebook.presto.spi.StandardErrorCode.HEADER_MODIFICATION_ATTEMPT;
import static com.facebook.presto.server.WebUiResource.UI_ENDPOINT;
import static com.facebook.presto.server.security.oauth2.OAuth2CallbackResource.CALLBACK_ENDPOINT;
import static com.facebook.presto.server.security.oauth2.OAuth2TokenExchangeResource.TOKEN_ENDPOINT;
import static com.google.common.io.ByteStreams.copy;
import static com.google.common.io.ByteStreams.nullOutputStream;
import static com.google.common.net.HttpHeaders.WWW_AUTHENTICATE;
Expand All @@ -62,16 +67,22 @@ public class AuthenticationFilter
{
private static final String HTTPS_PROTOCOL = "https";
private final List<Authenticator> authenticators;
private final boolean allowForwardedHttps;
private static boolean allowForwardedHttps;
private final ClientRequestFilterManager clientRequestFilterManager;
private final List<String> headersBlockList = ImmutableList.of("X-Presto-Transaction-Id", "X-Presto-Started-Transaction-Id", "X-Presto-Clear-Transaction-Id", "X-Presto-Trace-Token");
private final WebUiAuthenticationManager webUiAuthenticationManager;
private final boolean isOauth2Enabled;

@Inject
public AuthenticationFilter(List<Authenticator> authenticators, SecurityConfig securityConfig, ClientRequestFilterManager clientRequestFilterManager)
public AuthenticationFilter(List<Authenticator> authenticators, SecurityConfig securityConfig, WebUiAuthenticationManager webUiAuthenticationManager, ClientRequestFilterManager clientRequestFilterManager)
{
allowForwardedHttps = requireNonNull(securityConfig, "securityConfig is null").getAllowForwardedHttps();

this.authenticators = ImmutableList.copyOf(requireNonNull(authenticators, "authenticators is null"));
this.allowForwardedHttps = requireNonNull(securityConfig, "securityConfig is null").getAllowForwardedHttps();
this.clientRequestFilterManager = requireNonNull(clientRequestFilterManager, "clientRequestFilterManager is null");
this.webUiAuthenticationManager = requireNonNull(webUiAuthenticationManager, "webUiAuthenticationManager is null");
this.isOauth2Enabled = this.authenticators.stream()
.anyMatch(a -> a.getClass().equals(OAuth2Authenticator.class));
}

@Override
Expand All @@ -86,7 +97,12 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo
{
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;

// Check if it's a request going to the web UI side.
if (isWebUiRequest(request) && isOauth2Enabled) {
// call web authenticator
this.webUiAuthenticationManager.handleRequest(request, response, nextFilter);
return;
}
// skip authentication if non-secure or not configured
if (!doesRequestSupportAuthentication(request)) {
nextFilter.doFilter(request, response);
Expand Down Expand Up @@ -118,6 +134,10 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo
// authentication failed
skipRequestBody(request);

// Browsers have special handling for the BASIC challenge authenticate header so we need to filter them out if the WebUI Oauth Token is present.
if (isOauth2Enabled && OAuth2Authenticator.extractTokenFromCookie(request).isPresent()) {
authenticateHeaders = authenticateHeaders.stream().filter(value -> value.contains("x_token_server")).collect(Collectors.toSet());
}
for (String value : authenticateHeaders) {
response.addHeader(WWW_AUTHENTICATE, value);
}
Expand Down Expand Up @@ -195,7 +215,7 @@ private boolean doesRequestSupportAuthentication(HttpServletRequest request)
return false;
}

private static ServletRequest withPrincipal(HttpServletRequest request, Principal principal)
public static ServletRequest withPrincipal(HttpServletRequest request, Principal principal)
{
requireNonNull(principal, "principal is null");
return new HttpServletRequestWrapper(request)
Expand All @@ -208,6 +228,12 @@ public Principal getUserPrincipal()
};
}

public static boolean isPublic(HttpServletRequest request)
{
return request.getPathInfo().startsWith(TOKEN_ENDPOINT)
|| request.getPathInfo().startsWith(CALLBACK_ENDPOINT);
}

private static void skipRequestBody(HttpServletRequest request)
throws IOException
{
Expand Down Expand Up @@ -258,6 +284,25 @@ public Enumeration<String> getHeaders(String name)
return enumeration(ImmutableList.of(customHeaders.get(name)));
}
return super.getHeaders(name);
}

private boolean doesRequestSupportAuthentication(HttpServletRequest request)
{
if (isPublic(request)) {
return false;
}
if (authenticators.isEmpty()) {
return false;
}
if (request.isSecure()) {
return true;
}
return allowForwardedHttps && Strings.nullToEmpty(request.getHeader(HttpHeaders.X_FORWARDED_PROTO)).equalsIgnoreCase(HTTPS_PROTOCOL);
}

private boolean isWebUiRequest(HttpServletRequest request)
{
String pathInfo = request.getPathInfo();
return pathInfo == null || pathInfo.equals(UI_ENDPOINT) || pathInfo.startsWith("/ui");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Licensed 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 com.facebook.presto.server.security;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

public class DefaultWebUiAuthenticationManager
implements WebUiAuthenticationManager
{
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response, FilterChain nextFilter)
throws IOException, ServletException
{
nextFilter.doFilter(request, response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public enum AuthenticationType
KERBEROS,
PASSWORD,
JWT,
OAUTH2,
CUSTOM
}

Expand All @@ -57,7 +58,7 @@ public SecurityConfig setAuthenticationTypes(List<AuthenticationType> authentica
}

@Config("http-server.authentication.type")
@ConfigDescription("Authentication types (supported types: CERTIFICATE, KERBEROS, PASSWORD, JWT, CUSTOM)")
@ConfigDescription("Authentication types (supported types: CERTIFICATE, KERBEROS, PASSWORD, JWT, OAUTH2, CUSTOM)")
public SecurityConfig setAuthenticationTypes(String types)
{
if (types == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
import com.facebook.airlift.http.server.KerberosAuthenticator;
import com.facebook.airlift.http.server.KerberosConfig;
import com.facebook.presto.server.security.SecurityConfig.AuthenticationType;
import com.facebook.presto.server.security.oauth2.OAuth2AuthenticationSupportModule;
import com.facebook.presto.server.security.oauth2.OAuth2Authenticator;
import com.facebook.presto.server.security.oauth2.OAuth2Config;
import com.facebook.presto.server.security.oauth2.Oauth2WebUiAuthenticationManager;
import com.google.inject.Binder;
import com.google.inject.Scopes;
import com.google.inject.multibindings.Multibinder;
Expand All @@ -39,6 +43,10 @@ public class ServerSecurityModule
@Override
protected void setup(Binder binder)
{
newOptionalBinder(binder, WebUiAuthenticationManager.class).setDefault().to(DefaultWebUiAuthenticationManager.class).in(Scopes.SINGLETON);
newSetBinder(binder, Filter.class, TheServlet.class).addBinding()
.to(AuthenticationFilter.class).in(Scopes.SINGLETON);

binder.bind(PasswordAuthenticatorManager.class).in(Scopes.SINGLETON);
binder.bind(PrestoAuthenticatorManager.class).in(Scopes.SINGLETON);

Expand All @@ -63,6 +71,13 @@ else if (authType == JWT) {
else if (authType == CUSTOM) {
authBinder.addBinding().to(CustomPrestoAuthenticator.class).in(Scopes.SINGLETON);
}
else if (authType == OAUTH2) {
newOptionalBinder(binder, WebUiAuthenticationManager.class).setBinding().to(Oauth2WebUiAuthenticationManager.class).in(Scopes.SINGLETON);
install(new OAuth2AuthenticationSupportModule());
binder.bind(OAuth2Authenticator.class).in(Scopes.SINGLETON);
configBinder(binder).bindConfig(OAuth2Config.class);
authBinder.addBinding().to(OAuth2Authenticator.class).in(Scopes.SINGLETON);
}
else {
throw new AssertionError("Unhandled auth type: " + authType);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Licensed 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 com.facebook.presto.server.security;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

public interface WebUiAuthenticationManager
{
void handleRequest(HttpServletRequest request, HttpServletResponse response, FilterChain nextFilter)
throws IOException, ServletException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Licensed 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 com.facebook.presto.server.security.oauth2;

public class ChallengeFailedException
extends Exception
{
public ChallengeFailedException(String message)
{
super(message);
}

public ChallengeFailedException(String message, Throwable cause)
{
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Licensed 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 com.facebook.presto.server.security.oauth2;

import com.google.inject.BindingAnnotation;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Retention(RUNTIME)
@Target({FIELD, PARAMETER, METHOD})
@BindingAnnotation
public @interface ForOAuth2
{
}
Loading

0 comments on commit d3514e1

Please sign in to comment.