Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
import org.apache.amoro.server.dashboard.JavalinJsonMapper;
import org.apache.amoro.server.dashboard.response.ErrorResponse;
import org.apache.amoro.server.dashboard.utils.AmsUtil;
import org.apache.amoro.server.dashboard.utils.CommonUtil;
import org.apache.amoro.server.ha.HighAvailabilityContainer;
import org.apache.amoro.server.ha.HighAvailabilityContainerFactory;
import org.apache.amoro.server.manager.EventsManager;
Expand Down Expand Up @@ -419,15 +418,7 @@ private void initHttpService() {
}
});

httpServer.before(
ctx -> {
String token = ctx.queryParam("token");
if (StringUtils.isNotEmpty(token)) {
CommonUtil.checkSinglePageToken(ctx);
} else {
dashboardServer.preHandleRequest(ctx);
}
});
httpServer.before(dashboardServer::preHandleRequest);
httpServer.exception(
Exception.class,
(e, ctx) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
import org.apache.amoro.server.dashboard.response.OkResponse;
import org.apache.amoro.server.dashboard.response.PageResult;
import org.apache.amoro.server.dashboard.utils.AmsUtil;
import org.apache.amoro.server.dashboard.utils.CommonUtil;
import org.apache.amoro.server.optimizing.OptimizingStatus;
import org.apache.amoro.server.persistence.TableRuntimeMeta;
import org.apache.amoro.server.process.TableProcessMeta;
Expand Down Expand Up @@ -630,20 +629,6 @@ public void getCatalogs(Context ctx) {
ctx.json(OkResponse.of(catalogs));
}

/**
* get single page query token.
*
* @param ctx - context for handling the request and response
*/
public void getTableDetailTabToken(Context ctx) {
String catalog = ctx.pathParam("catalog");
String db = ctx.pathParam("db");
String table = ctx.pathParam("table");

String signCal = CommonUtil.generateTablePageToken(catalog, db, table);
ctx.json(OkResponse.of(signCal));
}

public void getTableTags(Context ctx) {
String catalog = ctx.pathParam("catalog");
String database = ctx.pathParam("db");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,15 @@

package org.apache.amoro.server.dashboard.utils;

import io.javalin.http.Context;
import org.apache.amoro.exception.SignatureCheckException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.telnet.TelnetClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;

public class CommonUtil {
private static final Logger LOG = LoggerFactory.getLogger(CommonUtil.class);

private static final String[] TOKEN_WHITE_LIST = {"/login/current", "/versionInfo"};

/**
* @param addresses support type 127.0.0.1:2181/ddd,host2:2181,host3:2181/service or
* music-hbase64.jd.163.org,music-hbase65.jd.163.org,
Expand Down Expand Up @@ -92,69 +85,4 @@ public static boolean ping(String ip) {
return false;
}
}

/**
* Check single page access token.
*
* @param ctx The context object containing the request information.
* @throws SignatureCheckException If the token is invalid or missing.
*/
public static void checkSinglePageToken(Context ctx) {
// check if query parameters contain token key
String token = ctx.queryParam("token");

if (StringUtils.isNotEmpty(token)) {
// regex extract catalog, db, table
String url = ctx.req.getRequestURI();
for (String whiteListUrl : TOKEN_WHITE_LIST) {
if (url.contains(whiteListUrl)) {
return;
}
}
String catalog = ctx.queryParam("catalog");
String db = ctx.queryParam("db");
String table = ctx.queryParam("table");
if (StringUtils.isEmpty(catalog) && StringUtils.isEmpty(db) && StringUtils.isEmpty(table)) {
String[] splitResult = url.split("/");
for (int i = 0; i < splitResult.length; i++) {
switch (splitResult[i]) {
case "catalogs":
catalog = splitResult[i + 1];
break;
case "dbs":
db = splitResult[i + 1];
break;
case "tables":
table = splitResult[i + 1];
break;
}
}
}
if (StringUtils.isEmpty(catalog)
|| StringUtils.isEmpty(db)
|| StringUtils.isEmpty(table)
|| !token.equals(generateTablePageToken(catalog, db, table))) {
throw new SignatureCheckException();
}
}
}

/**
* Generate the token for single table page access.
*
* @param catalog The catalog name.
* @param db The database name.
* @param table The table name.
* @return The generated token.
*/
public static String generateTablePageToken(String catalog, String db, String table) {
Map<String, String> params = new HashMap<>();
params.put("catalog", catalog);
params.put("db", db);
params.put("table", table);

String paramString = ParamSignatureCalculator.generateParamStringWithValue(params);
String plainText = String.format("%s%s%s", paramString, paramString, paramString);
return ParamSignatureCalculator.getMD5(plainText);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* 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.amoro.server.dashboard;

import org.apache.amoro.server.AmsEnvironment;
import org.apache.amoro.shade.jackson2.com.fasterxml.jackson.databind.JsonNode;
import org.apache.amoro.shade.jackson2.com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class TestDashboardAuthentication {

private static final AmsEnvironment AMS = AmsEnvironment.getIntegrationInstances();
private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

// MD5 of "catalogxdbxtablex"
private static final String FORGED_SINGLE_PAGE_TOKEN = "b383ed05fbc9d4b7e3888a70ed19398c";
private static final String FORGED_TOKEN_QUERY =
"token=" + FORGED_SINGLE_PAGE_TOKEN + "&catalog=x&db=x&table=x";

@BeforeAll
public static void beforeAll() throws Exception {
AMS.start();
}

@AfterAll
public static void afterAll() throws IOException {
AMS.stop();
}

@Test
public void testForgedSinglePageTokenCannotAccessCatalogApi()
throws IOException, InterruptedException {
HttpRequest request =
HttpRequest.newBuilder(
URI.create(AMS.getHttpUrl() + "/api/ams/v1/catalogs?" + FORGED_TOKEN_QUERY))
.GET()
.build();

assertForbidden(
HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()), "Signature check failed");
}

@Test
public void testForgedSinglePageTokenCannotAccessTerminalApi()
throws IOException, InterruptedException {
HttpRequest request =
HttpRequest.newBuilder(
URI.create(
AMS.getHttpUrl()
+ "/api/ams/v1/terminal/catalogs/"
+ AmsEnvironment.INTERNAL_ICEBERG_CATALOG
+ "/execute?"
+ FORGED_TOKEN_QUERY))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"sql\":\"select 1\"}"))
.build();

assertForbidden(
HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()), "Signature check failed");
}

@Test
public void testForgedSinglePageTokenCannotBypassWebSessionAuthentication()
throws IOException, InterruptedException {
HttpRequest request =
HttpRequest.newBuilder(
URI.create(AMS.getHttpUrl() + "/api/ams/v1/catalogs?" + FORGED_TOKEN_QUERY))
.header("X-Request-Source", "Web")
.GET()
.build();

assertForbidden(
HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()), "Please login first");
}

private static void assertForbidden(HttpResponse<String> response, String expectedMessage)
throws IOException {
JsonNode responseBody = OBJECT_MAPPER.readTree(response.body());
Assertions.assertEquals(403, responseBody.get("code").asInt());
Assertions.assertEquals(expectedMessage, responseBody.get("message").asText());
}
}
Loading