From da2858894863bd8df0f82666f6e74e9474e1fec9 Mon Sep 17 00:00:00 2001 From: bd2019us Date: Mon, 1 Apr 2019 17:24:56 -0500 Subject: [PATCH 1/5] PHOENIX-5224 Change 'Statement' to 'PreparedStatement' for better performance --- .../it/java/org/apache/phoenix/end2end/DeleteIT.java | 11 ++++++++--- .../java/org/apache/phoenix/end2end/DropTableIT.java | 4 +++- .../phoenix/end2end/UpsertSelectAutoCommitIT.java | 6 ++---- .../index/IndexRebuildIncrementDisableCountIT.java | 11 ++++++++--- .../it/java/org/apache/phoenix/tx/TxCheckpointIT.java | 5 +++-- .../end2end/HttpParamImpersonationQueryServerIT.java | 7 +++++-- .../apache/phoenix/end2end/SecureQueryServerIT.java | 7 +++++-- 7 files changed, 34 insertions(+), 17 deletions(-) diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DeleteIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DeleteIT.java index 39210fafb96..096e33deeea 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DeleteIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DeleteIT.java @@ -791,9 +791,11 @@ private void testDeleteCount(boolean autoCommit, Integer limit) throws Exception props.setProperty(QueryServices.ENABLE_SERVER_SIDE_MUTATIONS, allowServerSideMutations); try (Connection conn = DriverManager.getConnection(getUrl(), props)) { conn.createStatement().execute(ddl); - Statement stmt = conn.createStatement(); + String sqlStr = "UPSERT INTO " + tableName + " (pk1, v1) VALUES (?,'value')"; + PreparedStatement stmt = conn.prepareStatement(sqlStr); for (int i = 0; i < numRecords ; i++) { - stmt.executeUpdate("UPSERT INTO " + tableName + " (pk1, v1) VALUES (" + i + ",'value')"); + stmt.setInt(1, i); + stmt.executeUpdate(); } conn.commit(); conn.setAutoCommit(autoCommit); @@ -856,8 +858,11 @@ public void testDeleteShouldNotFailWhenTheRowsMoreThanMaxMutationSize() throws E conn.createStatement().execute(ddl); conn.createStatement().execute(idx1); Statement stmt = conn.createStatement(); + PreparedStatement stmt = conn.papareStatement("UPSERT INTO " + tableName + " VALUES (?, ?, 'value2')"); for(int i = 0; i < 20; i++) { - stmt.executeUpdate("UPSERT INTO " + tableName + " VALUES ("+i+",'value"+i+"', 'value2')"); + stmt.setInt(1, i); + stmt.setString(2, "value"+i); + stmt.executeUpdate(); if (i % 10 == 0) { conn.commit(); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DropTableIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DropTableIT.java index 823605d6004..8f6b5891da7 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DropTableIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DropTableIT.java @@ -22,6 +22,7 @@ import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; +import java.sql.PreparedStatement; import org.junit.Test; @@ -35,8 +36,9 @@ public void testRepeatedDropTable() throws Exception { final Statement stmt = conn.createStatement()) { assertFalse(stmt.execute(String.format("CREATE TABLE %s(pk varchar not null primary key)", tableName))); String dropTable = String.format("DROP TABLE IF EXISTS %s", tableName); + PreparedStatement pstmt = conn.prepareStatement(dropTable); for (int i = 0; i < 5; i++) { - assertFalse(stmt.execute(dropTable)); + assertFalse(stmt.execute()); } } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectAutoCommitIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectAutoCommitIT.java index 89b30012d03..427da08bcf4 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectAutoCommitIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectAutoCommitIT.java @@ -236,11 +236,9 @@ public void testRowCountWithNoAutoCommitOnUpsertSelect() throws Exception { conn.createStatement().execute( "UPSERT INTO " + tableName + " VALUES (NEXT VALUE FOR "+ tableName + "_seq, 1)"); conn.commit(); + PreparedStatement stmt = conn.parareStatement("UPSERT INTO " + tableName + " SELECT NEXT VALUE FOR "+ tableName + "_seq, val FROM " + tableName); for (int i=0; i<6; i++) { - Statement stmt = conn.createStatement(); - int upsertCount = stmt.executeUpdate( - "UPSERT INTO " + tableName + " SELECT NEXT VALUE FOR "+ tableName + "_seq, val FROM " - + tableName); + int upsertCount = stmt.executeUpdate(); conn.commit(); assertEquals((int)Math.pow(2, i), upsertCount); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexRebuildIncrementDisableCountIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexRebuildIncrementDisableCountIT.java index 084bee25f38..ff8a1013c63 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexRebuildIncrementDisableCountIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexRebuildIncrementDisableCountIT.java @@ -24,6 +24,7 @@ import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; +import java.sql.PreparedStatement; import java.util.Collections; import java.util.List; import java.util.Map; @@ -168,10 +169,14 @@ private static void mutateRandomly(Connection conn, String tableName, int maxOrg try { Statement stmt = conn.createStatement(); + String sqlStr = "UPSERT INTO " + tableName + " VALUES(?, ?, ?, ?)"; + PreparedStatement stmt = conn.prepareStatement(sqlStr); for (int i = 0; i < 10000; i++) { - stmt.executeUpdate( - "UPSERT INTO " + tableName + " VALUES('" + getRandomOrgId(maxOrgId) + "'," + i - + "," + (i + 1) + "," + (i + 2) + ")"); + stmt.setString(1, getRandomOrgId(maxOrgId); + stmt.setInt(2, i); + stmt.setInt(3, i + 1); + stmt.setInt(4, i + 2); + stmt.executeUpdate(); } conn.commit(); } catch (Exception e) { diff --git a/phoenix-core/src/it/java/org/apache/phoenix/tx/TxCheckpointIT.java b/phoenix-core/src/it/java/org/apache/phoenix/tx/TxCheckpointIT.java index f8946e5e390..00a4a481890 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/tx/TxCheckpointIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/tx/TxCheckpointIT.java @@ -28,6 +28,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.sql.PreparedStatement; import java.util.Arrays; import java.util.Collection; import java.util.Properties; @@ -104,9 +105,9 @@ public void testUpsertSelectDoesntSeeUpsertedData() throws Exception { conn.createStatement().execute("CREATE "+(localIndex? "LOCAL " : "")+"INDEX " + indexName + " ON " + fullTableName + "(val)"); conn.createStatement().execute("UPSERT INTO " + fullTableName + " VALUES (NEXT VALUE FOR " + seqName + ",1)"); + PreparedStatement stmt = comm.prepareStatement("UPSERT INTO " + fullTableName + " SELECT NEXT VALUE FOR " + seqName + ", val FROM " + fullTableName); for (int i=0; i<6; i++) { - Statement stmt = conn.createStatement(); - int upsertCount = stmt.executeUpdate("UPSERT INTO " + fullTableName + " SELECT NEXT VALUE FOR " + seqName + ", val FROM " + fullTableName); + int upsertCount = stmt.executeUpdate(); assertEquals((int)Math.pow(2, i), upsertCount); } conn.close(); diff --git a/phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/HttpParamImpersonationQueryServerIT.java b/phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/HttpParamImpersonationQueryServerIT.java index db27b9f4e51..0e61ebd9f82 100644 --- a/phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/HttpParamImpersonationQueryServerIT.java +++ b/phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/HttpParamImpersonationQueryServerIT.java @@ -32,6 +32,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.sql.PreparedStatement; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -380,11 +381,13 @@ public void testDisallowedImpersonation() throws Exception { void createTable(String tableName, int numRows) throws Exception { try (Connection conn = DriverManager.getConnection(PQS_URL); - Statement stmt = conn.createStatement()) { + Statement stmt = conn.createStatement(); + PreparedStatement pstmt = conn.prepareStatement("UPSERT INTO " + tableName + " values(?)")) { conn.setAutoCommit(true); assertFalse(stmt.execute("CREATE TABLE " + tableName + "(pk integer not null primary key)")); for (int i = 0; i < numRows; i++) { - assertEquals(1, stmt.executeUpdate("UPSERT INTO " + tableName + " values(" + i + ")")); + pstmt.setInt(1, i); + assertEquals(1, stmt.executeUpdate()); } readRows(stmt, tableName, numRows); } diff --git a/phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/SecureQueryServerIT.java b/phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/SecureQueryServerIT.java index c3ff885362b..455ac4e41e8 100644 --- a/phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/SecureQueryServerIT.java +++ b/phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/SecureQueryServerIT.java @@ -29,6 +29,7 @@ import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; +import java.sql.PreparedStatement; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; @@ -294,12 +295,14 @@ public void testBasicReadWrite() throws Exception { // Phoenix final String tableName = "phx_table1"; try (java.sql.Connection conn = DriverManager.getConnection(PQS_URL); - Statement stmt = conn.createStatement()) { + Statement stmt = conn.createStatement(); + PreparedStatement pstmt = conn.prepareStatement("UPSERT INTO " + tableName + " values(?)"))) { conn.setAutoCommit(true); assertFalse(stmt.execute("CREATE TABLE " + tableName + "(pk integer not null primary key)")); final int numRows = 5; for (int i = 0; i < numRows; i++) { - assertEquals(1, stmt.executeUpdate("UPSERT INTO " + tableName + " values(" + i + ")")); + pstmt.setInt(1, i); + assertEquals(1, stmt.executeUpdate()); } try (ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName)) { From 6e1faac53c2883273deea995fc1b2da8d7674b83 Mon Sep 17 00:00:00 2001 From: bd2019us Date: Sun, 7 Apr 2019 11:15:41 -0500 Subject: [PATCH 2/5] fix compiling errors --- .../index/IndexRebuildIncrementDisableCountIT.java | 2 +- .../java/org/apache/phoenix/tx/TxCheckpointIT.java | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexRebuildIncrementDisableCountIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexRebuildIncrementDisableCountIT.java index ff8a1013c63..dd22ac2dc46 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexRebuildIncrementDisableCountIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexRebuildIncrementDisableCountIT.java @@ -172,7 +172,7 @@ private static void mutateRandomly(Connection conn, String tableName, int maxOrg String sqlStr = "UPSERT INTO " + tableName + " VALUES(?, ?, ?, ?)"; PreparedStatement stmt = conn.prepareStatement(sqlStr); for (int i = 0; i < 10000; i++) { - stmt.setString(1, getRandomOrgId(maxOrgId); + stmt.setString(1, getRandomOrgId(maxOrgId)); stmt.setInt(2, i); stmt.setInt(3, i + 1); stmt.setInt(4, i + 2); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/tx/TxCheckpointIT.java b/phoenix-core/src/it/java/org/apache/phoenix/tx/TxCheckpointIT.java index 00a4a481890..e4f5e25c84d 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/tx/TxCheckpointIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/tx/TxCheckpointIT.java @@ -100,12 +100,14 @@ public void testUpsertSelectDoesntSeeUpsertedData() throws Exception { props.setProperty(QueryServices.SCAN_RESULT_CHUNK_SIZE, Integer.toString(3)); Connection conn = getConnection(props); conn.setAutoCommit(true); - conn.createStatement().execute("CREATE SEQUENCE "+seqName); - conn.createStatement().execute("CREATE TABLE " + fullTableName + "(pk INTEGER PRIMARY KEY, val INTEGER)"+tableDDLOptions); - conn.createStatement().execute("CREATE "+(localIndex? "LOCAL " : "")+"INDEX " + indexName + " ON " + fullTableName + "(val)"); + Statement stmt = conn.createStatement(); + stmt.execute("CREATE SEQUENCE "+seqName); + stmt.execute("CREATE TABLE " + fullTableName + "(pk INTEGER PRIMARY KEY, val INTEGER)"+tableDDLOptions); + stmt.execute("CREATE "+(localIndex? "LOCAL " : "")+"INDEX " + indexName + " ON " + fullTableName + "(val)"); - conn.createStatement().execute("UPSERT INTO " + fullTableName + " VALUES (NEXT VALUE FOR " + seqName + ",1)"); - PreparedStatement stmt = comm.prepareStatement("UPSERT INTO " + fullTableName + " SELECT NEXT VALUE FOR " + seqName + ", val FROM " + fullTableName); + stmt.execute("UPSERT INTO " + fullTableName + " VALUES (NEXT VALUE FOR " + seqName + ",1)"); + String sqlStr = "UPSERT INTO " + fullTableName + " SELECT NEXT VALUE FOR " + seqName + ", val FROM " + fullTableName; + PreparedStatement stmt = conn.prepareStatement(sqlStr); for (int i=0; i<6; i++) { int upsertCount = stmt.executeUpdate(); assertEquals((int)Math.pow(2, i), upsertCount); From 782619ba8736ae17c11b9f2c52f995204fd78396 Mon Sep 17 00:00:00 2001 From: bd2019us Date: Thu, 11 Apr 2019 22:47:38 -0500 Subject: [PATCH 3/5] fix compilation errors --- .../src/it/java/org/apache/phoenix/end2end/DeleteIT.java | 3 +-- .../src/it/java/org/apache/phoenix/end2end/DropTableIT.java | 2 +- .../org/apache/phoenix/end2end/UpsertSelectAutoCommitIT.java | 2 +- .../end2end/index/IndexRebuildIncrementDisableCountIT.java | 1 - .../src/it/java/org/apache/phoenix/tx/TxCheckpointIT.java | 4 ++-- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DeleteIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DeleteIT.java index 4cdc8116592..d599e507df1 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DeleteIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DeleteIT.java @@ -867,8 +867,7 @@ public void testDeleteShouldNotFailWhenTheRowsMoreThanMaxMutationSize() throws E try (Connection conn = DriverManager.getConnection(getUrl(), props)) { conn.createStatement().execute(ddl); conn.createStatement().execute(idx1); - Statement stmt = conn.createStatement(); - PreparedStatement stmt = conn.papareStatement("UPSERT INTO " + tableName + " VALUES (?, ?, 'value2')"); + PreparedStatement stmt = conn.prepareStatement("UPSERT INTO " + tableName + " VALUES (?, ?, 'value2')"); for(int i = 0; i < 20; i++) { stmt.setInt(1, i); stmt.setString(2, "value"+i); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DropTableIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DropTableIT.java index 8f6b5891da7..bbee071b25e 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DropTableIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DropTableIT.java @@ -38,7 +38,7 @@ public void testRepeatedDropTable() throws Exception { String dropTable = String.format("DROP TABLE IF EXISTS %s", tableName); PreparedStatement pstmt = conn.prepareStatement(dropTable); for (int i = 0; i < 5; i++) { - assertFalse(stmt.execute()); + assertFalse(pstmt.execute()); } } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectAutoCommitIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectAutoCommitIT.java index b1c80371648..fcebae6a7dc 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectAutoCommitIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectAutoCommitIT.java @@ -241,7 +241,7 @@ public void testRowCountWithNoAutoCommitOnUpsertSelect() throws Exception { conn.createStatement().execute( "UPSERT INTO " + tableName + " VALUES (NEXT VALUE FOR "+ tableName + "_seq, 1)"); conn.commit(); - PreparedStatement stmt = conn.parareStatement("UPSERT INTO " + tableName + " SELECT NEXT VALUE FOR "+ tableName + "_seq, val FROM " + tableName); + PreparedStatement stmt = conn.prepareStatement("UPSERT INTO " + tableName + " SELECT NEXT VALUE FOR "+ tableName + "_seq, val FROM " + tableName); for (int i=0; i<6; i++) { int upsertCount = stmt.executeUpdate(); conn.commit(); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexRebuildIncrementDisableCountIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexRebuildIncrementDisableCountIT.java index dd22ac2dc46..d6d5a6f2aa6 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexRebuildIncrementDisableCountIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexRebuildIncrementDisableCountIT.java @@ -168,7 +168,6 @@ static String getRandomOrgId(int maxOrgId) { private static void mutateRandomly(Connection conn, String tableName, int maxOrgId) { try { - Statement stmt = conn.createStatement(); String sqlStr = "UPSERT INTO " + tableName + " VALUES(?, ?, ?, ?)"; PreparedStatement stmt = conn.prepareStatement(sqlStr); for (int i = 0; i < 10000; i++) { diff --git a/phoenix-core/src/it/java/org/apache/phoenix/tx/TxCheckpointIT.java b/phoenix-core/src/it/java/org/apache/phoenix/tx/TxCheckpointIT.java index e4f5e25c84d..074cd5093a4 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/tx/TxCheckpointIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/tx/TxCheckpointIT.java @@ -107,9 +107,9 @@ public void testUpsertSelectDoesntSeeUpsertedData() throws Exception { stmt.execute("UPSERT INTO " + fullTableName + " VALUES (NEXT VALUE FOR " + seqName + ",1)"); String sqlStr = "UPSERT INTO " + fullTableName + " SELECT NEXT VALUE FOR " + seqName + ", val FROM " + fullTableName; - PreparedStatement stmt = conn.prepareStatement(sqlStr); + PreparedStatement pstmt = conn.prepareStatement(sqlStr); for (int i=0; i<6; i++) { - int upsertCount = stmt.executeUpdate(); + int upsertCount = pstmt.executeUpdate(); assertEquals((int)Math.pow(2, i), upsertCount); } conn.close(); From 3a0a2aae7924660f947afc5badf1a9dfc5daeac6 Mon Sep 17 00:00:00 2001 From: bd2019us Date: Wed, 11 Sep 2019 11:30:27 -0500 Subject: [PATCH 4/5] rebase merge --- .../org/apache/phoenix/end2end/DeleteIT.java | 11 +- .../apache/phoenix/end2end/DropTableIT.java | 4 +- .../end2end/UpsertSelectAutoCommitIT.java | 6 +- .../IndexRebuildIncrementDisableCountIT.java | 11 +- .../org/apache/phoenix/tx/TxCheckpointIT.java | 5 +- .../HttpParamImpersonationQueryServerIT.java | 441 ++++++++++++++++++ .../phoenix/end2end/SecureQueryServerIT.java | 326 +++++++++++++ 7 files changed, 791 insertions(+), 13 deletions(-) create mode 100644 phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/HttpParamImpersonationQueryServerIT.java create mode 100644 phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/SecureQueryServerIT.java diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DeleteIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DeleteIT.java index 505a5ae2d2d..4cdc8116592 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DeleteIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DeleteIT.java @@ -800,9 +800,11 @@ private void testDeleteCount(boolean autoCommit, Integer limit) throws Exception allowServerSideMutations); try (Connection conn = DriverManager.getConnection(getUrl(), props)) { conn.createStatement().execute(ddl); - Statement stmt = conn.createStatement(); + String sqlStr = "UPSERT INTO " + tableName + " (pk1, v1) VALUES (?,'value')"; + PreparedStatement stmt = conn.prepareStatement(sqlStr); for (int i = 0; i < numRecords ; i++) { - stmt.executeUpdate("UPSERT INTO " + tableName + " (pk1, v1) VALUES (" + i + ",'value')"); + stmt.setInt(1, i); + stmt.executeUpdate(); } conn.commit(); conn.setAutoCommit(autoCommit); @@ -866,8 +868,11 @@ public void testDeleteShouldNotFailWhenTheRowsMoreThanMaxMutationSize() throws E conn.createStatement().execute(ddl); conn.createStatement().execute(idx1); Statement stmt = conn.createStatement(); + PreparedStatement stmt = conn.papareStatement("UPSERT INTO " + tableName + " VALUES (?, ?, 'value2')"); for(int i = 0; i < 20; i++) { - stmt.executeUpdate("UPSERT INTO " + tableName + " VALUES ("+i+",'value"+i+"', 'value2')"); + stmt.setInt(1, i); + stmt.setString(2, "value"+i); + stmt.executeUpdate(); if (i % 10 == 0) { conn.commit(); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DropTableIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DropTableIT.java index 823605d6004..8f6b5891da7 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DropTableIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DropTableIT.java @@ -22,6 +22,7 @@ import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; +import java.sql.PreparedStatement; import org.junit.Test; @@ -35,8 +36,9 @@ public void testRepeatedDropTable() throws Exception { final Statement stmt = conn.createStatement()) { assertFalse(stmt.execute(String.format("CREATE TABLE %s(pk varchar not null primary key)", tableName))); String dropTable = String.format("DROP TABLE IF EXISTS %s", tableName); + PreparedStatement pstmt = conn.prepareStatement(dropTable); for (int i = 0; i < 5; i++) { - assertFalse(stmt.execute(dropTable)); + assertFalse(stmt.execute()); } } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectAutoCommitIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectAutoCommitIT.java index 8bd9ac35711..b1c80371648 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectAutoCommitIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectAutoCommitIT.java @@ -241,11 +241,9 @@ public void testRowCountWithNoAutoCommitOnUpsertSelect() throws Exception { conn.createStatement().execute( "UPSERT INTO " + tableName + " VALUES (NEXT VALUE FOR "+ tableName + "_seq, 1)"); conn.commit(); + PreparedStatement stmt = conn.parareStatement("UPSERT INTO " + tableName + " SELECT NEXT VALUE FOR "+ tableName + "_seq, val FROM " + tableName); for (int i=0; i<6; i++) { - Statement stmt = conn.createStatement(); - int upsertCount = stmt.executeUpdate( - "UPSERT INTO " + tableName + " SELECT NEXT VALUE FOR "+ tableName + "_seq, val FROM " - + tableName); + int upsertCount = stmt.executeUpdate(); conn.commit(); assertEquals((int)Math.pow(2, i), upsertCount); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexRebuildIncrementDisableCountIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexRebuildIncrementDisableCountIT.java index 9b7ba9125f6..57bad247bac 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexRebuildIncrementDisableCountIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexRebuildIncrementDisableCountIT.java @@ -24,6 +24,7 @@ import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; +import java.sql.PreparedStatement; import java.util.Collections; import java.util.List; import java.util.Map; @@ -169,10 +170,14 @@ private static void mutateRandomly(Connection conn, String tableName, int maxOrg try { Statement stmt = conn.createStatement(); + String sqlStr = "UPSERT INTO " + tableName + " VALUES(?, ?, ?, ?)"; + PreparedStatement stmt = conn.prepareStatement(sqlStr); for (int i = 0; i < 10000; i++) { - stmt.executeUpdate( - "UPSERT INTO " + tableName + " VALUES('" + getRandomOrgId(maxOrgId) + "'," + i - + "," + (i + 1) + "," + (i + 2) + ")"); + stmt.setString(1, getRandomOrgId(maxOrgId); + stmt.setInt(2, i); + stmt.setInt(3, i + 1); + stmt.setInt(4, i + 2); + stmt.executeUpdate(); } conn.commit(); } catch (Exception e) { diff --git a/phoenix-core/src/it/java/org/apache/phoenix/tx/TxCheckpointIT.java b/phoenix-core/src/it/java/org/apache/phoenix/tx/TxCheckpointIT.java index 800dcc30b4d..a55dedfc002 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/tx/TxCheckpointIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/tx/TxCheckpointIT.java @@ -28,6 +28,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.sql.PreparedStatement; import java.util.Arrays; import java.util.Collection; import java.util.Properties; @@ -104,9 +105,9 @@ public void testUpsertSelectDoesntSeeUpsertedData() throws Exception { conn.createStatement().execute("CREATE "+(localIndex? "LOCAL " : "")+"INDEX " + indexName + " ON " + fullTableName + "(val)"); conn.createStatement().execute("UPSERT INTO " + fullTableName + " VALUES (NEXT VALUE FOR " + seqName + ",1)"); + PreparedStatement stmt = comm.prepareStatement("UPSERT INTO " + fullTableName + " SELECT NEXT VALUE FOR " + seqName + ", val FROM " + fullTableName); for (int i=0; i<6; i++) { - Statement stmt = conn.createStatement(); - int upsertCount = stmt.executeUpdate("UPSERT INTO " + fullTableName + " SELECT NEXT VALUE FOR " + seqName + ", val FROM " + fullTableName); + int upsertCount = stmt.executeUpdate(); assertEquals((int)Math.pow(2, i), upsertCount); } conn.close(); diff --git a/phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/HttpParamImpersonationQueryServerIT.java b/phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/HttpParamImpersonationQueryServerIT.java new file mode 100644 index 00000000000..0e61ebd9f82 --- /dev/null +++ b/phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/HttpParamImpersonationQueryServerIT.java @@ -0,0 +1,441 @@ +/* + * 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.phoenix.end2end; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.security.PrivilegedAction; +import java.security.PrivilegedExceptionAction; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.PreparedStatement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map.Entry; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HConstants; +import org.apache.hadoop.hbase.LocalHBaseCluster; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; +import org.apache.hadoop.hbase.http.ssl.KeyStoreTestUtil; +import org.apache.hadoop.hbase.security.HBaseKerberosUtils; +import org.apache.hadoop.hbase.security.access.AccessControlClient; +import org.apache.hadoop.hbase.security.access.AccessController; +import org.apache.hadoop.hbase.security.access.Permission.Action; +import org.apache.hadoop.hbase.security.token.TokenProvider; +import org.apache.hadoop.hbase.util.FSUtils; +import org.apache.hadoop.hdfs.DFSConfigKeys; +import org.apache.hadoop.http.HttpConfig; +import org.apache.hadoop.minikdc.MiniKdc; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.authentication.util.KerberosName; +import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData; +import org.apache.phoenix.query.ConfigurationFactory; +import org.apache.phoenix.query.QueryServices; +import org.apache.phoenix.query.QueryServicesOptions; +import org.apache.phoenix.queryserver.client.Driver; +import org.apache.phoenix.queryserver.client.ThinClientUtil; +import org.apache.phoenix.queryserver.server.QueryServer; +import org.apache.phoenix.util.InstanceResolver; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; + +@Category(NeedsOwnMiniClusterTest.class) +public class HttpParamImpersonationQueryServerIT { + private static final Log LOG = LogFactory.getLog(HttpParamImpersonationQueryServerIT.class); + + private static final List SYSTEM_TABLE_NAMES = Arrays.asList(PhoenixDatabaseMetaData.SYSTEM_CATALOG_HBASE_TABLE_NAME, + PhoenixDatabaseMetaData.SYSTEM_MUTEX_HBASE_TABLE_NAME, + PhoenixDatabaseMetaData.SYSTEM_FUNCTION_HBASE_TABLE_NAME, + PhoenixDatabaseMetaData.SYSTEM_SCHEMA_HBASE_TABLE_NAME, + PhoenixDatabaseMetaData.SYSTEM_SEQUENCE_HBASE_TABLE_NAME, + PhoenixDatabaseMetaData.SYSTEM_STATS_HBASE_TABLE_NAME); + + private static final File TEMP_DIR = new File(getTempDirForClass()); + private static final File KEYTAB_DIR = new File(TEMP_DIR, "keytabs"); + private static final List USER_KEYTAB_FILES = new ArrayList<>(); + + private static final String SPNEGO_PRINCIPAL = "HTTP/localhost"; + private static final String PQS_PRINCIPAL = "phoenixqs/localhost"; + private static final String SERVICE_PRINCIPAL = "securecluster/localhost"; + private static File KEYTAB; + + private static MiniKdc KDC; + private static HBaseTestingUtility UTIL = new HBaseTestingUtility(); + private static LocalHBaseCluster HBASE_CLUSTER; + private static int NUM_CREATED_USERS; + + private static ExecutorService PQS_EXECUTOR; + private static QueryServer PQS; + private static int PQS_PORT; + private static String PQS_URL; + + private static String getTempDirForClass() { + StringBuilder sb = new StringBuilder(32); + sb.append(System.getProperty("user.dir")).append(File.separator); + sb.append("target").append(File.separator); + sb.append(HttpParamImpersonationQueryServerIT.class.getSimpleName()); + return sb.toString(); + } + + private static void updateDefaultRealm() throws Exception { + // (at least) one other phoenix test triggers the caching of this field before the KDC is up + // which causes principal parsing to fail. + Field f = KerberosName.class.getDeclaredField("defaultRealm"); + f.setAccessible(true); + // Default realm for MiniKDC + f.set(null, "EXAMPLE.COM"); + } + + private static void createUsers(int numUsers) throws Exception { + assertNotNull("KDC is null, was setup method called?", KDC); + NUM_CREATED_USERS = numUsers; + for (int i = 1; i <= numUsers; i++) { + String principal = "user" + i; + File keytabFile = new File(KEYTAB_DIR, principal + ".keytab"); + KDC.createPrincipal(keytabFile, principal); + USER_KEYTAB_FILES.add(keytabFile); + } + } + + private static Entry getUser(int offset) { + Preconditions.checkArgument(offset > 0 && offset <= NUM_CREATED_USERS); + return Maps.immutableEntry("user" + offset, USER_KEYTAB_FILES.get(offset - 1)); + } + + /** + * Setup the security configuration for hdfs. + */ + private static void setHdfsSecuredConfiguration(Configuration conf) throws Exception { + // Set principal+keytab configuration for HDFS + conf.set(DFSConfigKeys.DFS_NAMENODE_KERBEROS_PRINCIPAL_KEY, SERVICE_PRINCIPAL + "@" + KDC.getRealm()); + conf.set(DFSConfigKeys.DFS_NAMENODE_KEYTAB_FILE_KEY, KEYTAB.getAbsolutePath()); + conf.set(DFSConfigKeys.DFS_DATANODE_KERBEROS_PRINCIPAL_KEY, SERVICE_PRINCIPAL + "@" + KDC.getRealm()); + conf.set(DFSConfigKeys.DFS_DATANODE_KEYTAB_FILE_KEY, KEYTAB.getAbsolutePath()); + conf.set(DFSConfigKeys.DFS_WEB_AUTHENTICATION_KERBEROS_PRINCIPAL_KEY, SPNEGO_PRINCIPAL + "@" + KDC.getRealm()); + // Enable token access for HDFS blocks + conf.setBoolean(DFSConfigKeys.DFS_BLOCK_ACCESS_TOKEN_ENABLE_KEY, true); + // Only use HTTPS (required because we aren't using "secure" ports) + conf.set(DFSConfigKeys.DFS_HTTP_POLICY_KEY, HttpConfig.Policy.HTTPS_ONLY.name()); + // Bind on localhost for spnego to have a chance at working + conf.set(DFSConfigKeys.DFS_NAMENODE_HTTPS_ADDRESS_KEY, "localhost:0"); + conf.set(DFSConfigKeys.DFS_DATANODE_HTTPS_ADDRESS_KEY, "localhost:0"); + + // Generate SSL certs + File keystoresDir = new File(UTIL.getDataTestDir("keystore").toUri().getPath()); + keystoresDir.mkdirs(); + String sslConfDir = KeyStoreTestUtil.getClasspathDir(HttpParamImpersonationQueryServerIT.class); + KeyStoreTestUtil.setupSSLConfig(keystoresDir.getAbsolutePath(), sslConfDir, conf, false); + + // Magic flag to tell hdfs to not fail on using ports above 1024 + conf.setBoolean("ignore.secure.ports.for.testing", true); + } + + private static void ensureIsEmptyDirectory(File f) throws IOException { + if (f.exists()) { + if (f.isDirectory()) { + FileUtils.deleteDirectory(f); + } else { + assertTrue("Failed to delete keytab directory", f.delete()); + } + } + assertTrue("Failed to create keytab directory", f.mkdirs()); + } + + /** + * Setup and start kerberos, hbase + */ + @BeforeClass + public static void setUp() throws Exception { + final Configuration conf = UTIL.getConfiguration(); + // Ensure the dirs we need are created/empty + ensureIsEmptyDirectory(TEMP_DIR); + ensureIsEmptyDirectory(KEYTAB_DIR); + KEYTAB = new File(KEYTAB_DIR, "test.keytab"); + // Start a MiniKDC + KDC = UTIL.setupMiniKdc(KEYTAB); + // Create a service principal and spnego principal in one keytab + // NB. Due to some apparent limitations between HDFS and HBase in the same JVM, trying to + // use separate identies for HBase and HDFS results in a GSS initiate error. The quick + // solution is to just use a single "service" principal instead of "hbase" and "hdfs" + // (or "dn" and "nn") per usual. + KDC.createPrincipal(KEYTAB, SPNEGO_PRINCIPAL, PQS_PRINCIPAL, SERVICE_PRINCIPAL); + // Start ZK by hand + UTIL.startMiniZKCluster(); + + // Create a number of unprivileged users + createUsers(2); + + // Set configuration for HBase + HBaseKerberosUtils.setPrincipalForTesting(SERVICE_PRINCIPAL + "@" + KDC.getRealm()); + HBaseKerberosUtils.setSecuredConfiguration(conf); + setHdfsSecuredConfiguration(conf); + UserGroupInformation.setConfiguration(conf); + conf.setInt(HConstants.MASTER_PORT, 0); + conf.setInt(HConstants.MASTER_INFO_PORT, 0); + conf.setInt(HConstants.REGIONSERVER_PORT, 0); + conf.setInt(HConstants.REGIONSERVER_INFO_PORT, 0); + conf.setStrings(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, AccessController.class.getName()); + conf.setStrings(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, AccessController.class.getName()); + conf.setStrings(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, AccessController.class.getName(), TokenProvider.class.getName()); + + // Secure Phoenix setup + conf.set("phoenix.queryserver.kerberos.http.principal", SPNEGO_PRINCIPAL + "@" + KDC.getRealm()); + conf.set("phoenix.queryserver.http.keytab.file", KEYTAB.getAbsolutePath()); + conf.set("phoenix.queryserver.kerberos.principal", PQS_PRINCIPAL + "@" + KDC.getRealm()); + conf.set("phoenix.queryserver.keytab.file", KEYTAB.getAbsolutePath()); + conf.setBoolean(QueryServices.QUERY_SERVER_DISABLE_KERBEROS_LOGIN, true); + conf.setInt(QueryServices.QUERY_SERVER_HTTP_PORT_ATTRIB, 0); + // Required so that PQS can impersonate the end-users to HBase + conf.set("hadoop.proxyuser.phoenixqs.groups", "*"); + conf.set("hadoop.proxyuser.phoenixqs.hosts", "*"); + // user1 is allowed to impersonate others, user2 is not + conf.set("hadoop.proxyuser.user1.groups", "*"); + conf.set("hadoop.proxyuser.user1.hosts", "*"); + conf.setBoolean(QueryServices.QUERY_SERVER_WITH_REMOTEUSEREXTRACTOR_ATTRIB, true); + + // Clear the cached singletons so we can inject our own. + InstanceResolver.clearSingletons(); + // Make sure the ConnectionInfo doesn't try to pull a default Configuration + InstanceResolver.getSingleton(ConfigurationFactory.class, new ConfigurationFactory() { + @Override + public Configuration getConfiguration() { + return conf; + } + @Override + public Configuration getConfiguration(Configuration confToClone) { + Configuration copy = new Configuration(conf); + copy.addResource(confToClone); + return copy; + } + }); + updateDefaultRealm(); + + // Start HDFS + UTIL.startMiniDFSCluster(1); + // Use LocalHBaseCluster to avoid HBaseTestingUtility from doing something wrong + // NB. I'm not actually sure what HTU does incorrect, but this was pulled from some test + // classes in HBase itself. I couldn't get HTU to work myself (2017/07/06) + Path rootdir = UTIL.getDataTestDirOnTestFS(HttpParamImpersonationQueryServerIT.class.getSimpleName()); + FSUtils.setRootDir(conf, rootdir); + HBASE_CLUSTER = new LocalHBaseCluster(conf, 1); + HBASE_CLUSTER.startup(); + + // Then fork a thread with PQS in it. + startQueryServer(); + } + + private static void startQueryServer() throws Exception { + PQS = new QueryServer(new String[0], UTIL.getConfiguration()); + // Get the PQS ident for PQS to use + final UserGroupInformation ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(PQS_PRINCIPAL, KEYTAB.getAbsolutePath()); + PQS_EXECUTOR = Executors.newSingleThreadExecutor(); + // Launch PQS, doing in the Kerberos login instead of letting PQS do it itself (which would + // break the HBase/HDFS logins also running in the same test case). + PQS_EXECUTOR.submit(new Runnable() { + @Override public void run() { + ugi.doAs(new PrivilegedAction() { + @Override public Void run() { + PQS.run(); + return null; + } + }); + } + }); + PQS.awaitRunning(); + PQS_PORT = PQS.getPort(); + PQS_URL = ThinClientUtil.getConnectionUrl("localhost", PQS_PORT) + ";authentication=SPNEGO"; + } + + @AfterClass + public static void stopKdc() throws Exception { + // Remove our custom ConfigurationFactory for future tests + InstanceResolver.clearSingletons(); + if (PQS_EXECUTOR != null) { + PQS.stop(); + PQS_EXECUTOR.shutdown(); + if (!PQS_EXECUTOR.awaitTermination(5, TimeUnit.SECONDS)) { + LOG.info("PQS didn't exit in 5 seconds, proceeding anyways."); + } + } + if (HBASE_CLUSTER != null) { + HBASE_CLUSTER.shutdown(); + HBASE_CLUSTER.join(); + } + if (UTIL != null) { + UTIL.shutdownMiniZKCluster(); + } + if (KDC != null) { + KDC.stop(); + } + } + + @Test + public void testSuccessfulImpersonation() throws Exception { + final Entry user1 = getUser(1); + final Entry user2 = getUser(2); + // Build the JDBC URL by hand with the doAs + final String doAsUrlTemplate = Driver.CONNECT_STRING_PREFIX + "url=http://localhost:" + PQS_PORT + "?" + + QueryServicesOptions.DEFAULT_QUERY_SERVER_REMOTEUSEREXTRACTOR_PARAM + "=%s;authentication=SPNEGO;serialization=PROTOBUF"; + final String tableName = "POSITIVE_IMPERSONATION"; + final int numRows = 5; + final UserGroupInformation serviceUgi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(SERVICE_PRINCIPAL, KEYTAB.getAbsolutePath()); + serviceUgi.doAs(new PrivilegedExceptionAction() { + @Override public Void run() throws Exception { + createTable(tableName, numRows); + grantUsersToPhoenixSystemTables(Arrays.asList(user1.getKey(), user2.getKey())); + return null; + } + }); + UserGroupInformation user1Ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(user1.getKey(), user1.getValue().getAbsolutePath()); + user1Ugi.doAs(new PrivilegedExceptionAction() { + @Override public Void run() throws Exception { + // This user should not be able to read the table + readAndExpectPermissionError(PQS_URL, tableName, numRows); + // Run the same query with the same credentials, but with a doAs. We should be permitted since the user we're impersonating can run the query + final String doAsUrl = String.format(doAsUrlTemplate, serviceUgi.getShortUserName()); + try (Connection conn = DriverManager.getConnection(doAsUrl); + Statement stmt = conn.createStatement()) { + conn.setAutoCommit(true); + readRows(stmt, tableName, numRows); + } + return null; + } + }); + } + + @Test + public void testDisallowedImpersonation() throws Exception { + final Entry user2 = getUser(2); + // Build the JDBC URL by hand with the doAs + final String doAsUrlTemplate = Driver.CONNECT_STRING_PREFIX + "url=http://localhost:" + PQS_PORT + "?" + + QueryServicesOptions.DEFAULT_QUERY_SERVER_REMOTEUSEREXTRACTOR_PARAM + "=%s;authentication=SPNEGO;serialization=PROTOBUF"; + final String tableName = "DISALLOWED_IMPERSONATION"; + final int numRows = 5; + final UserGroupInformation serviceUgi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(SERVICE_PRINCIPAL, KEYTAB.getAbsolutePath()); + serviceUgi.doAs(new PrivilegedExceptionAction() { + @Override public Void run() throws Exception { + createTable(tableName, numRows); + grantUsersToPhoenixSystemTables(Arrays.asList(user2.getKey())); + return null; + } + }); + UserGroupInformation user2Ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(user2.getKey(), user2.getValue().getAbsolutePath()); + user2Ugi.doAs(new PrivilegedExceptionAction() { + @Override public Void run() throws Exception { + // This user is disallowed to read this table + readAndExpectPermissionError(PQS_URL, tableName, numRows); + // This user is also not allowed to impersonate + final String doAsUrl = String.format(doAsUrlTemplate, serviceUgi.getShortUserName()); + try (Connection conn = DriverManager.getConnection(doAsUrl); + Statement stmt = conn.createStatement()) { + conn.setAutoCommit(true); + readRows(stmt, tableName, numRows); + fail("user2 should not be allowed to impersonate the service user"); + } catch (Exception e) { + LOG.info("Caught expected exception", e); + } + return null; + } + }); + } + + void createTable(String tableName, int numRows) throws Exception { + try (Connection conn = DriverManager.getConnection(PQS_URL); + Statement stmt = conn.createStatement(); + PreparedStatement pstmt = conn.prepareStatement("UPSERT INTO " + tableName + " values(?)")) { + conn.setAutoCommit(true); + assertFalse(stmt.execute("CREATE TABLE " + tableName + "(pk integer not null primary key)")); + for (int i = 0; i < numRows; i++) { + pstmt.setInt(1, i); + assertEquals(1, stmt.executeUpdate()); + } + readRows(stmt, tableName, numRows); + } + } + + void grantUsersToPhoenixSystemTables(List usersToGrant) throws Exception { + // Grant permission to the user to access the system tables + try { + for (String user : usersToGrant) { + for (TableName tn : SYSTEM_TABLE_NAMES) { + AccessControlClient.grant(UTIL.getConnection(), tn, user, null, null, Action.READ, Action.EXEC); + } + } + } catch (Throwable e) { + throw new Exception(e); + } + } + + void readAndExpectPermissionError(String jdbcUrl, String tableName, int numRows) { + try (Connection conn = DriverManager.getConnection(jdbcUrl); + Statement stmt = conn.createStatement()) { + conn.setAutoCommit(true); + readRows(stmt, tableName, numRows); + fail("Expected an exception reading another user's table"); + } catch (Exception e) { + LOG.debug("Caught expected exception", e); + // Avatica doesn't re-create new exceptions across the wire. Need to just look at the contents of the message. + String errorMessage = e.getMessage(); + assertTrue("Expected the error message to contain an HBase AccessDeniedException", errorMessage.contains("org.apache.hadoop.hbase.security.AccessDeniedException")); + // Expecting an error message like: "Insufficient permissions for user 'user1' (table=POSITIVE_IMPERSONATION, action=READ)" + // Being overly cautious to make sure we don't inadvertently pass the test due to permission errors on phoenix system tables. + assertTrue("Expected message to contain " + tableName + " and READ", errorMessage.contains(tableName) && errorMessage.contains("READ")); + } + } + + void readRows(Statement stmt, String tableName, int numRows) throws SQLException { + try (ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName)) { + for (int i = 0; i < numRows; i++) { + assertTrue(rs.next()); + assertEquals(i, rs.getInt(1)); + } + assertFalse(rs.next()); + } + } + + byte[] copyBytes(byte[] src, int offset, int length) { + byte[] dest = new byte[length]; + System.arraycopy(src, offset, dest, 0, length); + return dest; + } +} diff --git a/phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/SecureQueryServerIT.java b/phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/SecureQueryServerIT.java new file mode 100644 index 00000000000..455ac4e41e8 --- /dev/null +++ b/phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/SecureQueryServerIT.java @@ -0,0 +1,326 @@ +/* + * 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.phoenix.end2end; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.security.PrivilegedAction; +import java.security.PrivilegedExceptionAction; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; +import java.sql.PreparedStatement; +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HConstants; +import org.apache.hadoop.hbase.LocalHBaseCluster; +import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; +import org.apache.hadoop.hbase.http.ssl.KeyStoreTestUtil; +import org.apache.hadoop.hbase.security.HBaseKerberosUtils; +import org.apache.hadoop.hbase.security.token.TokenProvider; +import org.apache.hadoop.hbase.util.FSUtils; +import org.apache.hadoop.hdfs.DFSConfigKeys; +import org.apache.hadoop.http.HttpConfig; +import org.apache.hadoop.minikdc.MiniKdc; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.authentication.util.KerberosName; +import org.apache.phoenix.query.ConfigurationFactory; +import org.apache.phoenix.query.QueryServices; +import org.apache.phoenix.queryserver.client.ThinClientUtil; +import org.apache.phoenix.queryserver.server.QueryServer; +import org.apache.phoenix.util.InstanceResolver; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; + +@Category(NeedsOwnMiniClusterTest.class) +public class SecureQueryServerIT { + private static final Log LOG = LogFactory.getLog(SecureQueryServerIT.class); + + private static final File TEMP_DIR = new File(getTempDirForClass()); + private static final File KEYTAB_DIR = new File(TEMP_DIR, "keytabs"); + private static final List USER_KEYTAB_FILES = new ArrayList<>(); + + private static final String SPNEGO_PRINCIPAL = "HTTP/localhost"; + private static final String PQS_PRINCIPAL = "phoenixqs/localhost"; + private static final String SERVICE_PRINCIPAL = "securecluster/localhost"; + private static File KEYTAB; + + private static MiniKdc KDC; + private static HBaseTestingUtility UTIL = new HBaseTestingUtility(); + private static LocalHBaseCluster HBASE_CLUSTER; + private static int NUM_CREATED_USERS; + + private static ExecutorService PQS_EXECUTOR; + private static QueryServer PQS; + private static int PQS_PORT; + private static String PQS_URL; + + private static String getTempDirForClass() { + StringBuilder sb = new StringBuilder(32); + sb.append(System.getProperty("user.dir")).append(File.separator); + sb.append("target").append(File.separator); + sb.append(SecureQueryServerIT.class.getSimpleName()); + return sb.toString(); + } + + private static void updateDefaultRealm() throws Exception { + // (at least) one other phoenix test triggers the caching of this field before the KDC is up + // which causes principal parsing to fail. + Field f = KerberosName.class.getDeclaredField("defaultRealm"); + f.setAccessible(true); + // Default realm for MiniKDC + f.set(null, "EXAMPLE.COM"); + } + + private static void createUsers(int numUsers) throws Exception { + assertNotNull("KDC is null, was setup method called?", KDC); + NUM_CREATED_USERS = numUsers; + for (int i = 1; i <= numUsers; i++) { + String principal = "user" + i; + File keytabFile = new File(KEYTAB_DIR, principal + ".keytab"); + KDC.createPrincipal(keytabFile, principal); + USER_KEYTAB_FILES.add(keytabFile); + } + } + + private static Entry getUser(int offset) { + Preconditions.checkArgument(offset > 0 && offset <= NUM_CREATED_USERS); + return Maps.immutableEntry("user" + offset, USER_KEYTAB_FILES.get(offset - 1)); + } + + /** + * Setup the security configuration for hdfs. + */ + private static void setHdfsSecuredConfiguration(Configuration conf) throws Exception { + // Set principal+keytab configuration for HDFS + conf.set(DFSConfigKeys.DFS_NAMENODE_KERBEROS_PRINCIPAL_KEY, SERVICE_PRINCIPAL + "@" + KDC.getRealm()); + conf.set(DFSConfigKeys.DFS_NAMENODE_KEYTAB_FILE_KEY, KEYTAB.getAbsolutePath()); + conf.set(DFSConfigKeys.DFS_DATANODE_KERBEROS_PRINCIPAL_KEY, SERVICE_PRINCIPAL + "@" + KDC.getRealm()); + conf.set(DFSConfigKeys.DFS_DATANODE_KEYTAB_FILE_KEY, KEYTAB.getAbsolutePath()); + conf.set(DFSConfigKeys.DFS_WEB_AUTHENTICATION_KERBEROS_PRINCIPAL_KEY, SPNEGO_PRINCIPAL + "@" + KDC.getRealm()); + // Enable token access for HDFS blocks + conf.setBoolean(DFSConfigKeys.DFS_BLOCK_ACCESS_TOKEN_ENABLE_KEY, true); + // Only use HTTPS (required because we aren't using "secure" ports) + conf.set(DFSConfigKeys.DFS_HTTP_POLICY_KEY, HttpConfig.Policy.HTTPS_ONLY.name()); + // Bind on localhost for spnego to have a chance at working + conf.set(DFSConfigKeys.DFS_NAMENODE_HTTPS_ADDRESS_KEY, "localhost:0"); + conf.set(DFSConfigKeys.DFS_DATANODE_HTTPS_ADDRESS_KEY, "localhost:0"); + + // Generate SSL certs + File keystoresDir = new File(UTIL.getDataTestDir("keystore").toUri().getPath()); + keystoresDir.mkdirs(); + String sslConfDir = KeyStoreTestUtil.getClasspathDir(SecureQueryServerIT.class); + KeyStoreTestUtil.setupSSLConfig(keystoresDir.getAbsolutePath(), sslConfDir, conf, false); + + // Magic flag to tell hdfs to not fail on using ports above 1024 + conf.setBoolean("ignore.secure.ports.for.testing", true); + } + + private static void ensureIsEmptyDirectory(File f) throws IOException { + if (f.exists()) { + if (f.isDirectory()) { + FileUtils.deleteDirectory(f); + } else { + assertTrue("Failed to delete keytab directory", f.delete()); + } + } + assertTrue("Failed to create keytab directory", f.mkdirs()); + } + + /** + * Setup and start kerberos, hbase + */ + @BeforeClass + public static void setUp() throws Exception { + final Configuration conf = UTIL.getConfiguration(); + // Ensure the dirs we need are created/empty + ensureIsEmptyDirectory(TEMP_DIR); + ensureIsEmptyDirectory(KEYTAB_DIR); + KEYTAB = new File(KEYTAB_DIR, "test.keytab"); + // Start a MiniKDC + KDC = UTIL.setupMiniKdc(KEYTAB); + // Create a service principal and spnego principal in one keytab + // NB. Due to some apparent limitations between HDFS and HBase in the same JVM, trying to + // use separate identies for HBase and HDFS results in a GSS initiate error. The quick + // solution is to just use a single "service" principal instead of "hbase" and "hdfs" + // (or "dn" and "nn") per usual. + KDC.createPrincipal(KEYTAB, SPNEGO_PRINCIPAL, PQS_PRINCIPAL, SERVICE_PRINCIPAL); + // Start ZK by hand + UTIL.startMiniZKCluster(); + + // Create a number of unprivileged users + createUsers(3); + + // Set configuration for HBase + HBaseKerberosUtils.setPrincipalForTesting(SERVICE_PRINCIPAL + "@" + KDC.getRealm()); + HBaseKerberosUtils.setSecuredConfiguration(conf); + setHdfsSecuredConfiguration(conf); + UserGroupInformation.setConfiguration(conf); + conf.setInt(HConstants.MASTER_PORT, 0); + conf.setInt(HConstants.MASTER_INFO_PORT, 0); + conf.setInt(HConstants.REGIONSERVER_PORT, 0); + conf.setInt(HConstants.REGIONSERVER_INFO_PORT, 0); + conf.setStrings(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, + TokenProvider.class.getName()); + + // Secure Phoenix setup + conf.set("phoenix.queryserver.kerberos.http.principal", SPNEGO_PRINCIPAL + "@" + KDC.getRealm()); + conf.set("phoenix.queryserver.http.keytab.file", KEYTAB.getAbsolutePath()); + conf.set("phoenix.queryserver.kerberos.principal", PQS_PRINCIPAL + "@" + KDC.getRealm()); + conf.set("phoenix.queryserver.keytab.file", KEYTAB.getAbsolutePath()); + conf.setBoolean(QueryServices.QUERY_SERVER_DISABLE_KERBEROS_LOGIN, true); + conf.setInt(QueryServices.QUERY_SERVER_HTTP_PORT_ATTRIB, 0); + // Required so that PQS can impersonate the end-users to HBase + conf.set("hadoop.proxyuser.phoenixqs.groups", "*"); + conf.set("hadoop.proxyuser.phoenixqs.hosts", "*"); + + // Clear the cached singletons so we can inject our own. + InstanceResolver.clearSingletons(); + // Make sure the ConnectionInfo doesn't try to pull a default Configuration + InstanceResolver.getSingleton(ConfigurationFactory.class, new ConfigurationFactory() { + @Override + public Configuration getConfiguration() { + return conf; + } + @Override + public Configuration getConfiguration(Configuration confToClone) { + Configuration copy = new Configuration(conf); + copy.addResource(confToClone); + return copy; + } + }); + updateDefaultRealm(); + + // Start HDFS + UTIL.startMiniDFSCluster(1); + // Use LocalHBaseCluster to avoid HBaseTestingUtility from doing something wrong + // NB. I'm not actually sure what HTU does incorrect, but this was pulled from some test + // classes in HBase itself. I couldn't get HTU to work myself (2017/07/06) + Path rootdir = UTIL.getDataTestDirOnTestFS(SecureQueryServerIT.class.getSimpleName()); + FSUtils.setRootDir(conf, rootdir); + HBASE_CLUSTER = new LocalHBaseCluster(conf, 1); + HBASE_CLUSTER.startup(); + + // Then fork a thread with PQS in it. + startQueryServer(); + } + + private static void startQueryServer() throws Exception { + PQS = new QueryServer(new String[0], UTIL.getConfiguration()); + // Get the PQS ident for PQS to use + final UserGroupInformation ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(PQS_PRINCIPAL, KEYTAB.getAbsolutePath()); + PQS_EXECUTOR = Executors.newSingleThreadExecutor(); + // Launch PQS, doing in the Kerberos login instead of letting PQS do it itself (which would + // break the HBase/HDFS logins also running in the same test case). + PQS_EXECUTOR.submit(new Runnable() { + @Override public void run() { + ugi.doAs(new PrivilegedAction() { + @Override public Void run() { + PQS.run(); + return null; + } + }); + } + }); + PQS.awaitRunning(); + PQS_PORT = PQS.getPort(); + PQS_URL = ThinClientUtil.getConnectionUrl("localhost", PQS_PORT) + ";authentication=SPNEGO"; + } + + @AfterClass + public static void stopKdc() throws Exception { + // Remove our custom ConfigurationFactory for future tests + InstanceResolver.clearSingletons(); + if (PQS_EXECUTOR != null) { + PQS.stop(); + PQS_EXECUTOR.shutdown(); + if (!PQS_EXECUTOR.awaitTermination(5, TimeUnit.SECONDS)) { + LOG.info("PQS didn't exit in 5 seconds, proceeding anyways."); + } + } + if (HBASE_CLUSTER != null) { + HBASE_CLUSTER.shutdown(); + HBASE_CLUSTER.join(); + } + if (UTIL != null) { + UTIL.shutdownMiniZKCluster(); + } + if (KDC != null) { + KDC.stop(); + } + } + + @Test + public void testBasicReadWrite() throws Exception { + final Entry user1 = getUser(1); + UserGroupInformation user1Ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(user1.getKey(), user1.getValue().getAbsolutePath()); + user1Ugi.doAs(new PrivilegedExceptionAction() { + @Override public Void run() throws Exception { + // Phoenix + final String tableName = "phx_table1"; + try (java.sql.Connection conn = DriverManager.getConnection(PQS_URL); + Statement stmt = conn.createStatement(); + PreparedStatement pstmt = conn.prepareStatement("UPSERT INTO " + tableName + " values(?)"))) { + conn.setAutoCommit(true); + assertFalse(stmt.execute("CREATE TABLE " + tableName + "(pk integer not null primary key)")); + final int numRows = 5; + for (int i = 0; i < numRows; i++) { + pstmt.setInt(1, i); + assertEquals(1, stmt.executeUpdate()); + } + + try (ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName)) { + for (int i = 0; i < numRows; i++) { + assertTrue(rs.next()); + assertEquals(i, rs.getInt(1)); + } + assertFalse(rs.next()); + } + } + return null; + } + }); + } + + byte[] copyBytes(byte[] src, int offset, int length) { + byte[] dest = new byte[length]; + System.arraycopy(src, offset, dest, 0, length); + return dest; + } +} From 480b0ee9964cf715ae0c4140573354720c65efb5 Mon Sep 17 00:00:00 2001 From: bd2019us Date: Wed, 11 Sep 2019 11:50:24 -0500 Subject: [PATCH 5/5] recover removal of classes before rebase --- .../HttpParamImpersonationQueryServerIT.java | 441 ------------------ .../phoenix/end2end/SecureQueryServerIT.java | 326 ------------- 2 files changed, 767 deletions(-) delete mode 100644 phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/HttpParamImpersonationQueryServerIT.java delete mode 100644 phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/SecureQueryServerIT.java diff --git a/phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/HttpParamImpersonationQueryServerIT.java b/phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/HttpParamImpersonationQueryServerIT.java deleted file mode 100644 index 0e61ebd9f82..00000000000 --- a/phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/HttpParamImpersonationQueryServerIT.java +++ /dev/null @@ -1,441 +0,0 @@ -/* - * 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.phoenix.end2end; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.security.PrivilegedAction; -import java.security.PrivilegedExceptionAction; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.sql.PreparedStatement; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map.Entry; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.Path; -import org.apache.hadoop.hbase.HBaseTestingUtility; -import org.apache.hadoop.hbase.HConstants; -import org.apache.hadoop.hbase.LocalHBaseCluster; -import org.apache.hadoop.hbase.TableName; -import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; -import org.apache.hadoop.hbase.http.ssl.KeyStoreTestUtil; -import org.apache.hadoop.hbase.security.HBaseKerberosUtils; -import org.apache.hadoop.hbase.security.access.AccessControlClient; -import org.apache.hadoop.hbase.security.access.AccessController; -import org.apache.hadoop.hbase.security.access.Permission.Action; -import org.apache.hadoop.hbase.security.token.TokenProvider; -import org.apache.hadoop.hbase.util.FSUtils; -import org.apache.hadoop.hdfs.DFSConfigKeys; -import org.apache.hadoop.http.HttpConfig; -import org.apache.hadoop.minikdc.MiniKdc; -import org.apache.hadoop.security.UserGroupInformation; -import org.apache.hadoop.security.authentication.util.KerberosName; -import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData; -import org.apache.phoenix.query.ConfigurationFactory; -import org.apache.phoenix.query.QueryServices; -import org.apache.phoenix.query.QueryServicesOptions; -import org.apache.phoenix.queryserver.client.Driver; -import org.apache.phoenix.queryserver.client.ThinClientUtil; -import org.apache.phoenix.queryserver.server.QueryServer; -import org.apache.phoenix.util.InstanceResolver; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.experimental.categories.Category; - -import com.google.common.base.Preconditions; -import com.google.common.collect.Maps; - -@Category(NeedsOwnMiniClusterTest.class) -public class HttpParamImpersonationQueryServerIT { - private static final Log LOG = LogFactory.getLog(HttpParamImpersonationQueryServerIT.class); - - private static final List SYSTEM_TABLE_NAMES = Arrays.asList(PhoenixDatabaseMetaData.SYSTEM_CATALOG_HBASE_TABLE_NAME, - PhoenixDatabaseMetaData.SYSTEM_MUTEX_HBASE_TABLE_NAME, - PhoenixDatabaseMetaData.SYSTEM_FUNCTION_HBASE_TABLE_NAME, - PhoenixDatabaseMetaData.SYSTEM_SCHEMA_HBASE_TABLE_NAME, - PhoenixDatabaseMetaData.SYSTEM_SEQUENCE_HBASE_TABLE_NAME, - PhoenixDatabaseMetaData.SYSTEM_STATS_HBASE_TABLE_NAME); - - private static final File TEMP_DIR = new File(getTempDirForClass()); - private static final File KEYTAB_DIR = new File(TEMP_DIR, "keytabs"); - private static final List USER_KEYTAB_FILES = new ArrayList<>(); - - private static final String SPNEGO_PRINCIPAL = "HTTP/localhost"; - private static final String PQS_PRINCIPAL = "phoenixqs/localhost"; - private static final String SERVICE_PRINCIPAL = "securecluster/localhost"; - private static File KEYTAB; - - private static MiniKdc KDC; - private static HBaseTestingUtility UTIL = new HBaseTestingUtility(); - private static LocalHBaseCluster HBASE_CLUSTER; - private static int NUM_CREATED_USERS; - - private static ExecutorService PQS_EXECUTOR; - private static QueryServer PQS; - private static int PQS_PORT; - private static String PQS_URL; - - private static String getTempDirForClass() { - StringBuilder sb = new StringBuilder(32); - sb.append(System.getProperty("user.dir")).append(File.separator); - sb.append("target").append(File.separator); - sb.append(HttpParamImpersonationQueryServerIT.class.getSimpleName()); - return sb.toString(); - } - - private static void updateDefaultRealm() throws Exception { - // (at least) one other phoenix test triggers the caching of this field before the KDC is up - // which causes principal parsing to fail. - Field f = KerberosName.class.getDeclaredField("defaultRealm"); - f.setAccessible(true); - // Default realm for MiniKDC - f.set(null, "EXAMPLE.COM"); - } - - private static void createUsers(int numUsers) throws Exception { - assertNotNull("KDC is null, was setup method called?", KDC); - NUM_CREATED_USERS = numUsers; - for (int i = 1; i <= numUsers; i++) { - String principal = "user" + i; - File keytabFile = new File(KEYTAB_DIR, principal + ".keytab"); - KDC.createPrincipal(keytabFile, principal); - USER_KEYTAB_FILES.add(keytabFile); - } - } - - private static Entry getUser(int offset) { - Preconditions.checkArgument(offset > 0 && offset <= NUM_CREATED_USERS); - return Maps.immutableEntry("user" + offset, USER_KEYTAB_FILES.get(offset - 1)); - } - - /** - * Setup the security configuration for hdfs. - */ - private static void setHdfsSecuredConfiguration(Configuration conf) throws Exception { - // Set principal+keytab configuration for HDFS - conf.set(DFSConfigKeys.DFS_NAMENODE_KERBEROS_PRINCIPAL_KEY, SERVICE_PRINCIPAL + "@" + KDC.getRealm()); - conf.set(DFSConfigKeys.DFS_NAMENODE_KEYTAB_FILE_KEY, KEYTAB.getAbsolutePath()); - conf.set(DFSConfigKeys.DFS_DATANODE_KERBEROS_PRINCIPAL_KEY, SERVICE_PRINCIPAL + "@" + KDC.getRealm()); - conf.set(DFSConfigKeys.DFS_DATANODE_KEYTAB_FILE_KEY, KEYTAB.getAbsolutePath()); - conf.set(DFSConfigKeys.DFS_WEB_AUTHENTICATION_KERBEROS_PRINCIPAL_KEY, SPNEGO_PRINCIPAL + "@" + KDC.getRealm()); - // Enable token access for HDFS blocks - conf.setBoolean(DFSConfigKeys.DFS_BLOCK_ACCESS_TOKEN_ENABLE_KEY, true); - // Only use HTTPS (required because we aren't using "secure" ports) - conf.set(DFSConfigKeys.DFS_HTTP_POLICY_KEY, HttpConfig.Policy.HTTPS_ONLY.name()); - // Bind on localhost for spnego to have a chance at working - conf.set(DFSConfigKeys.DFS_NAMENODE_HTTPS_ADDRESS_KEY, "localhost:0"); - conf.set(DFSConfigKeys.DFS_DATANODE_HTTPS_ADDRESS_KEY, "localhost:0"); - - // Generate SSL certs - File keystoresDir = new File(UTIL.getDataTestDir("keystore").toUri().getPath()); - keystoresDir.mkdirs(); - String sslConfDir = KeyStoreTestUtil.getClasspathDir(HttpParamImpersonationQueryServerIT.class); - KeyStoreTestUtil.setupSSLConfig(keystoresDir.getAbsolutePath(), sslConfDir, conf, false); - - // Magic flag to tell hdfs to not fail on using ports above 1024 - conf.setBoolean("ignore.secure.ports.for.testing", true); - } - - private static void ensureIsEmptyDirectory(File f) throws IOException { - if (f.exists()) { - if (f.isDirectory()) { - FileUtils.deleteDirectory(f); - } else { - assertTrue("Failed to delete keytab directory", f.delete()); - } - } - assertTrue("Failed to create keytab directory", f.mkdirs()); - } - - /** - * Setup and start kerberos, hbase - */ - @BeforeClass - public static void setUp() throws Exception { - final Configuration conf = UTIL.getConfiguration(); - // Ensure the dirs we need are created/empty - ensureIsEmptyDirectory(TEMP_DIR); - ensureIsEmptyDirectory(KEYTAB_DIR); - KEYTAB = new File(KEYTAB_DIR, "test.keytab"); - // Start a MiniKDC - KDC = UTIL.setupMiniKdc(KEYTAB); - // Create a service principal and spnego principal in one keytab - // NB. Due to some apparent limitations between HDFS and HBase in the same JVM, trying to - // use separate identies for HBase and HDFS results in a GSS initiate error. The quick - // solution is to just use a single "service" principal instead of "hbase" and "hdfs" - // (or "dn" and "nn") per usual. - KDC.createPrincipal(KEYTAB, SPNEGO_PRINCIPAL, PQS_PRINCIPAL, SERVICE_PRINCIPAL); - // Start ZK by hand - UTIL.startMiniZKCluster(); - - // Create a number of unprivileged users - createUsers(2); - - // Set configuration for HBase - HBaseKerberosUtils.setPrincipalForTesting(SERVICE_PRINCIPAL + "@" + KDC.getRealm()); - HBaseKerberosUtils.setSecuredConfiguration(conf); - setHdfsSecuredConfiguration(conf); - UserGroupInformation.setConfiguration(conf); - conf.setInt(HConstants.MASTER_PORT, 0); - conf.setInt(HConstants.MASTER_INFO_PORT, 0); - conf.setInt(HConstants.REGIONSERVER_PORT, 0); - conf.setInt(HConstants.REGIONSERVER_INFO_PORT, 0); - conf.setStrings(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, AccessController.class.getName()); - conf.setStrings(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, AccessController.class.getName()); - conf.setStrings(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, AccessController.class.getName(), TokenProvider.class.getName()); - - // Secure Phoenix setup - conf.set("phoenix.queryserver.kerberos.http.principal", SPNEGO_PRINCIPAL + "@" + KDC.getRealm()); - conf.set("phoenix.queryserver.http.keytab.file", KEYTAB.getAbsolutePath()); - conf.set("phoenix.queryserver.kerberos.principal", PQS_PRINCIPAL + "@" + KDC.getRealm()); - conf.set("phoenix.queryserver.keytab.file", KEYTAB.getAbsolutePath()); - conf.setBoolean(QueryServices.QUERY_SERVER_DISABLE_KERBEROS_LOGIN, true); - conf.setInt(QueryServices.QUERY_SERVER_HTTP_PORT_ATTRIB, 0); - // Required so that PQS can impersonate the end-users to HBase - conf.set("hadoop.proxyuser.phoenixqs.groups", "*"); - conf.set("hadoop.proxyuser.phoenixqs.hosts", "*"); - // user1 is allowed to impersonate others, user2 is not - conf.set("hadoop.proxyuser.user1.groups", "*"); - conf.set("hadoop.proxyuser.user1.hosts", "*"); - conf.setBoolean(QueryServices.QUERY_SERVER_WITH_REMOTEUSEREXTRACTOR_ATTRIB, true); - - // Clear the cached singletons so we can inject our own. - InstanceResolver.clearSingletons(); - // Make sure the ConnectionInfo doesn't try to pull a default Configuration - InstanceResolver.getSingleton(ConfigurationFactory.class, new ConfigurationFactory() { - @Override - public Configuration getConfiguration() { - return conf; - } - @Override - public Configuration getConfiguration(Configuration confToClone) { - Configuration copy = new Configuration(conf); - copy.addResource(confToClone); - return copy; - } - }); - updateDefaultRealm(); - - // Start HDFS - UTIL.startMiniDFSCluster(1); - // Use LocalHBaseCluster to avoid HBaseTestingUtility from doing something wrong - // NB. I'm not actually sure what HTU does incorrect, but this was pulled from some test - // classes in HBase itself. I couldn't get HTU to work myself (2017/07/06) - Path rootdir = UTIL.getDataTestDirOnTestFS(HttpParamImpersonationQueryServerIT.class.getSimpleName()); - FSUtils.setRootDir(conf, rootdir); - HBASE_CLUSTER = new LocalHBaseCluster(conf, 1); - HBASE_CLUSTER.startup(); - - // Then fork a thread with PQS in it. - startQueryServer(); - } - - private static void startQueryServer() throws Exception { - PQS = new QueryServer(new String[0], UTIL.getConfiguration()); - // Get the PQS ident for PQS to use - final UserGroupInformation ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(PQS_PRINCIPAL, KEYTAB.getAbsolutePath()); - PQS_EXECUTOR = Executors.newSingleThreadExecutor(); - // Launch PQS, doing in the Kerberos login instead of letting PQS do it itself (which would - // break the HBase/HDFS logins also running in the same test case). - PQS_EXECUTOR.submit(new Runnable() { - @Override public void run() { - ugi.doAs(new PrivilegedAction() { - @Override public Void run() { - PQS.run(); - return null; - } - }); - } - }); - PQS.awaitRunning(); - PQS_PORT = PQS.getPort(); - PQS_URL = ThinClientUtil.getConnectionUrl("localhost", PQS_PORT) + ";authentication=SPNEGO"; - } - - @AfterClass - public static void stopKdc() throws Exception { - // Remove our custom ConfigurationFactory for future tests - InstanceResolver.clearSingletons(); - if (PQS_EXECUTOR != null) { - PQS.stop(); - PQS_EXECUTOR.shutdown(); - if (!PQS_EXECUTOR.awaitTermination(5, TimeUnit.SECONDS)) { - LOG.info("PQS didn't exit in 5 seconds, proceeding anyways."); - } - } - if (HBASE_CLUSTER != null) { - HBASE_CLUSTER.shutdown(); - HBASE_CLUSTER.join(); - } - if (UTIL != null) { - UTIL.shutdownMiniZKCluster(); - } - if (KDC != null) { - KDC.stop(); - } - } - - @Test - public void testSuccessfulImpersonation() throws Exception { - final Entry user1 = getUser(1); - final Entry user2 = getUser(2); - // Build the JDBC URL by hand with the doAs - final String doAsUrlTemplate = Driver.CONNECT_STRING_PREFIX + "url=http://localhost:" + PQS_PORT + "?" - + QueryServicesOptions.DEFAULT_QUERY_SERVER_REMOTEUSEREXTRACTOR_PARAM + "=%s;authentication=SPNEGO;serialization=PROTOBUF"; - final String tableName = "POSITIVE_IMPERSONATION"; - final int numRows = 5; - final UserGroupInformation serviceUgi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(SERVICE_PRINCIPAL, KEYTAB.getAbsolutePath()); - serviceUgi.doAs(new PrivilegedExceptionAction() { - @Override public Void run() throws Exception { - createTable(tableName, numRows); - grantUsersToPhoenixSystemTables(Arrays.asList(user1.getKey(), user2.getKey())); - return null; - } - }); - UserGroupInformation user1Ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(user1.getKey(), user1.getValue().getAbsolutePath()); - user1Ugi.doAs(new PrivilegedExceptionAction() { - @Override public Void run() throws Exception { - // This user should not be able to read the table - readAndExpectPermissionError(PQS_URL, tableName, numRows); - // Run the same query with the same credentials, but with a doAs. We should be permitted since the user we're impersonating can run the query - final String doAsUrl = String.format(doAsUrlTemplate, serviceUgi.getShortUserName()); - try (Connection conn = DriverManager.getConnection(doAsUrl); - Statement stmt = conn.createStatement()) { - conn.setAutoCommit(true); - readRows(stmt, tableName, numRows); - } - return null; - } - }); - } - - @Test - public void testDisallowedImpersonation() throws Exception { - final Entry user2 = getUser(2); - // Build the JDBC URL by hand with the doAs - final String doAsUrlTemplate = Driver.CONNECT_STRING_PREFIX + "url=http://localhost:" + PQS_PORT + "?" - + QueryServicesOptions.DEFAULT_QUERY_SERVER_REMOTEUSEREXTRACTOR_PARAM + "=%s;authentication=SPNEGO;serialization=PROTOBUF"; - final String tableName = "DISALLOWED_IMPERSONATION"; - final int numRows = 5; - final UserGroupInformation serviceUgi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(SERVICE_PRINCIPAL, KEYTAB.getAbsolutePath()); - serviceUgi.doAs(new PrivilegedExceptionAction() { - @Override public Void run() throws Exception { - createTable(tableName, numRows); - grantUsersToPhoenixSystemTables(Arrays.asList(user2.getKey())); - return null; - } - }); - UserGroupInformation user2Ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(user2.getKey(), user2.getValue().getAbsolutePath()); - user2Ugi.doAs(new PrivilegedExceptionAction() { - @Override public Void run() throws Exception { - // This user is disallowed to read this table - readAndExpectPermissionError(PQS_URL, tableName, numRows); - // This user is also not allowed to impersonate - final String doAsUrl = String.format(doAsUrlTemplate, serviceUgi.getShortUserName()); - try (Connection conn = DriverManager.getConnection(doAsUrl); - Statement stmt = conn.createStatement()) { - conn.setAutoCommit(true); - readRows(stmt, tableName, numRows); - fail("user2 should not be allowed to impersonate the service user"); - } catch (Exception e) { - LOG.info("Caught expected exception", e); - } - return null; - } - }); - } - - void createTable(String tableName, int numRows) throws Exception { - try (Connection conn = DriverManager.getConnection(PQS_URL); - Statement stmt = conn.createStatement(); - PreparedStatement pstmt = conn.prepareStatement("UPSERT INTO " + tableName + " values(?)")) { - conn.setAutoCommit(true); - assertFalse(stmt.execute("CREATE TABLE " + tableName + "(pk integer not null primary key)")); - for (int i = 0; i < numRows; i++) { - pstmt.setInt(1, i); - assertEquals(1, stmt.executeUpdate()); - } - readRows(stmt, tableName, numRows); - } - } - - void grantUsersToPhoenixSystemTables(List usersToGrant) throws Exception { - // Grant permission to the user to access the system tables - try { - for (String user : usersToGrant) { - for (TableName tn : SYSTEM_TABLE_NAMES) { - AccessControlClient.grant(UTIL.getConnection(), tn, user, null, null, Action.READ, Action.EXEC); - } - } - } catch (Throwable e) { - throw new Exception(e); - } - } - - void readAndExpectPermissionError(String jdbcUrl, String tableName, int numRows) { - try (Connection conn = DriverManager.getConnection(jdbcUrl); - Statement stmt = conn.createStatement()) { - conn.setAutoCommit(true); - readRows(stmt, tableName, numRows); - fail("Expected an exception reading another user's table"); - } catch (Exception e) { - LOG.debug("Caught expected exception", e); - // Avatica doesn't re-create new exceptions across the wire. Need to just look at the contents of the message. - String errorMessage = e.getMessage(); - assertTrue("Expected the error message to contain an HBase AccessDeniedException", errorMessage.contains("org.apache.hadoop.hbase.security.AccessDeniedException")); - // Expecting an error message like: "Insufficient permissions for user 'user1' (table=POSITIVE_IMPERSONATION, action=READ)" - // Being overly cautious to make sure we don't inadvertently pass the test due to permission errors on phoenix system tables. - assertTrue("Expected message to contain " + tableName + " and READ", errorMessage.contains(tableName) && errorMessage.contains("READ")); - } - } - - void readRows(Statement stmt, String tableName, int numRows) throws SQLException { - try (ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName)) { - for (int i = 0; i < numRows; i++) { - assertTrue(rs.next()); - assertEquals(i, rs.getInt(1)); - } - assertFalse(rs.next()); - } - } - - byte[] copyBytes(byte[] src, int offset, int length) { - byte[] dest = new byte[length]; - System.arraycopy(src, offset, dest, 0, length); - return dest; - } -} diff --git a/phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/SecureQueryServerIT.java b/phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/SecureQueryServerIT.java deleted file mode 100644 index 455ac4e41e8..00000000000 --- a/phoenix-queryserver/src/it/java/org/apache/phoenix/end2end/SecureQueryServerIT.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * 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.phoenix.end2end; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.security.PrivilegedAction; -import java.security.PrivilegedExceptionAction; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.Statement; -import java.sql.PreparedStatement; -import java.util.ArrayList; -import java.util.List; -import java.util.Map.Entry; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.Path; -import org.apache.hadoop.hbase.HBaseTestingUtility; -import org.apache.hadoop.hbase.HConstants; -import org.apache.hadoop.hbase.LocalHBaseCluster; -import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; -import org.apache.hadoop.hbase.http.ssl.KeyStoreTestUtil; -import org.apache.hadoop.hbase.security.HBaseKerberosUtils; -import org.apache.hadoop.hbase.security.token.TokenProvider; -import org.apache.hadoop.hbase.util.FSUtils; -import org.apache.hadoop.hdfs.DFSConfigKeys; -import org.apache.hadoop.http.HttpConfig; -import org.apache.hadoop.minikdc.MiniKdc; -import org.apache.hadoop.security.UserGroupInformation; -import org.apache.hadoop.security.authentication.util.KerberosName; -import org.apache.phoenix.query.ConfigurationFactory; -import org.apache.phoenix.query.QueryServices; -import org.apache.phoenix.queryserver.client.ThinClientUtil; -import org.apache.phoenix.queryserver.server.QueryServer; -import org.apache.phoenix.util.InstanceResolver; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.experimental.categories.Category; - -import com.google.common.base.Preconditions; -import com.google.common.collect.Maps; - -@Category(NeedsOwnMiniClusterTest.class) -public class SecureQueryServerIT { - private static final Log LOG = LogFactory.getLog(SecureQueryServerIT.class); - - private static final File TEMP_DIR = new File(getTempDirForClass()); - private static final File KEYTAB_DIR = new File(TEMP_DIR, "keytabs"); - private static final List USER_KEYTAB_FILES = new ArrayList<>(); - - private static final String SPNEGO_PRINCIPAL = "HTTP/localhost"; - private static final String PQS_PRINCIPAL = "phoenixqs/localhost"; - private static final String SERVICE_PRINCIPAL = "securecluster/localhost"; - private static File KEYTAB; - - private static MiniKdc KDC; - private static HBaseTestingUtility UTIL = new HBaseTestingUtility(); - private static LocalHBaseCluster HBASE_CLUSTER; - private static int NUM_CREATED_USERS; - - private static ExecutorService PQS_EXECUTOR; - private static QueryServer PQS; - private static int PQS_PORT; - private static String PQS_URL; - - private static String getTempDirForClass() { - StringBuilder sb = new StringBuilder(32); - sb.append(System.getProperty("user.dir")).append(File.separator); - sb.append("target").append(File.separator); - sb.append(SecureQueryServerIT.class.getSimpleName()); - return sb.toString(); - } - - private static void updateDefaultRealm() throws Exception { - // (at least) one other phoenix test triggers the caching of this field before the KDC is up - // which causes principal parsing to fail. - Field f = KerberosName.class.getDeclaredField("defaultRealm"); - f.setAccessible(true); - // Default realm for MiniKDC - f.set(null, "EXAMPLE.COM"); - } - - private static void createUsers(int numUsers) throws Exception { - assertNotNull("KDC is null, was setup method called?", KDC); - NUM_CREATED_USERS = numUsers; - for (int i = 1; i <= numUsers; i++) { - String principal = "user" + i; - File keytabFile = new File(KEYTAB_DIR, principal + ".keytab"); - KDC.createPrincipal(keytabFile, principal); - USER_KEYTAB_FILES.add(keytabFile); - } - } - - private static Entry getUser(int offset) { - Preconditions.checkArgument(offset > 0 && offset <= NUM_CREATED_USERS); - return Maps.immutableEntry("user" + offset, USER_KEYTAB_FILES.get(offset - 1)); - } - - /** - * Setup the security configuration for hdfs. - */ - private static void setHdfsSecuredConfiguration(Configuration conf) throws Exception { - // Set principal+keytab configuration for HDFS - conf.set(DFSConfigKeys.DFS_NAMENODE_KERBEROS_PRINCIPAL_KEY, SERVICE_PRINCIPAL + "@" + KDC.getRealm()); - conf.set(DFSConfigKeys.DFS_NAMENODE_KEYTAB_FILE_KEY, KEYTAB.getAbsolutePath()); - conf.set(DFSConfigKeys.DFS_DATANODE_KERBEROS_PRINCIPAL_KEY, SERVICE_PRINCIPAL + "@" + KDC.getRealm()); - conf.set(DFSConfigKeys.DFS_DATANODE_KEYTAB_FILE_KEY, KEYTAB.getAbsolutePath()); - conf.set(DFSConfigKeys.DFS_WEB_AUTHENTICATION_KERBEROS_PRINCIPAL_KEY, SPNEGO_PRINCIPAL + "@" + KDC.getRealm()); - // Enable token access for HDFS blocks - conf.setBoolean(DFSConfigKeys.DFS_BLOCK_ACCESS_TOKEN_ENABLE_KEY, true); - // Only use HTTPS (required because we aren't using "secure" ports) - conf.set(DFSConfigKeys.DFS_HTTP_POLICY_KEY, HttpConfig.Policy.HTTPS_ONLY.name()); - // Bind on localhost for spnego to have a chance at working - conf.set(DFSConfigKeys.DFS_NAMENODE_HTTPS_ADDRESS_KEY, "localhost:0"); - conf.set(DFSConfigKeys.DFS_DATANODE_HTTPS_ADDRESS_KEY, "localhost:0"); - - // Generate SSL certs - File keystoresDir = new File(UTIL.getDataTestDir("keystore").toUri().getPath()); - keystoresDir.mkdirs(); - String sslConfDir = KeyStoreTestUtil.getClasspathDir(SecureQueryServerIT.class); - KeyStoreTestUtil.setupSSLConfig(keystoresDir.getAbsolutePath(), sslConfDir, conf, false); - - // Magic flag to tell hdfs to not fail on using ports above 1024 - conf.setBoolean("ignore.secure.ports.for.testing", true); - } - - private static void ensureIsEmptyDirectory(File f) throws IOException { - if (f.exists()) { - if (f.isDirectory()) { - FileUtils.deleteDirectory(f); - } else { - assertTrue("Failed to delete keytab directory", f.delete()); - } - } - assertTrue("Failed to create keytab directory", f.mkdirs()); - } - - /** - * Setup and start kerberos, hbase - */ - @BeforeClass - public static void setUp() throws Exception { - final Configuration conf = UTIL.getConfiguration(); - // Ensure the dirs we need are created/empty - ensureIsEmptyDirectory(TEMP_DIR); - ensureIsEmptyDirectory(KEYTAB_DIR); - KEYTAB = new File(KEYTAB_DIR, "test.keytab"); - // Start a MiniKDC - KDC = UTIL.setupMiniKdc(KEYTAB); - // Create a service principal and spnego principal in one keytab - // NB. Due to some apparent limitations between HDFS and HBase in the same JVM, trying to - // use separate identies for HBase and HDFS results in a GSS initiate error. The quick - // solution is to just use a single "service" principal instead of "hbase" and "hdfs" - // (or "dn" and "nn") per usual. - KDC.createPrincipal(KEYTAB, SPNEGO_PRINCIPAL, PQS_PRINCIPAL, SERVICE_PRINCIPAL); - // Start ZK by hand - UTIL.startMiniZKCluster(); - - // Create a number of unprivileged users - createUsers(3); - - // Set configuration for HBase - HBaseKerberosUtils.setPrincipalForTesting(SERVICE_PRINCIPAL + "@" + KDC.getRealm()); - HBaseKerberosUtils.setSecuredConfiguration(conf); - setHdfsSecuredConfiguration(conf); - UserGroupInformation.setConfiguration(conf); - conf.setInt(HConstants.MASTER_PORT, 0); - conf.setInt(HConstants.MASTER_INFO_PORT, 0); - conf.setInt(HConstants.REGIONSERVER_PORT, 0); - conf.setInt(HConstants.REGIONSERVER_INFO_PORT, 0); - conf.setStrings(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, - TokenProvider.class.getName()); - - // Secure Phoenix setup - conf.set("phoenix.queryserver.kerberos.http.principal", SPNEGO_PRINCIPAL + "@" + KDC.getRealm()); - conf.set("phoenix.queryserver.http.keytab.file", KEYTAB.getAbsolutePath()); - conf.set("phoenix.queryserver.kerberos.principal", PQS_PRINCIPAL + "@" + KDC.getRealm()); - conf.set("phoenix.queryserver.keytab.file", KEYTAB.getAbsolutePath()); - conf.setBoolean(QueryServices.QUERY_SERVER_DISABLE_KERBEROS_LOGIN, true); - conf.setInt(QueryServices.QUERY_SERVER_HTTP_PORT_ATTRIB, 0); - // Required so that PQS can impersonate the end-users to HBase - conf.set("hadoop.proxyuser.phoenixqs.groups", "*"); - conf.set("hadoop.proxyuser.phoenixqs.hosts", "*"); - - // Clear the cached singletons so we can inject our own. - InstanceResolver.clearSingletons(); - // Make sure the ConnectionInfo doesn't try to pull a default Configuration - InstanceResolver.getSingleton(ConfigurationFactory.class, new ConfigurationFactory() { - @Override - public Configuration getConfiguration() { - return conf; - } - @Override - public Configuration getConfiguration(Configuration confToClone) { - Configuration copy = new Configuration(conf); - copy.addResource(confToClone); - return copy; - } - }); - updateDefaultRealm(); - - // Start HDFS - UTIL.startMiniDFSCluster(1); - // Use LocalHBaseCluster to avoid HBaseTestingUtility from doing something wrong - // NB. I'm not actually sure what HTU does incorrect, but this was pulled from some test - // classes in HBase itself. I couldn't get HTU to work myself (2017/07/06) - Path rootdir = UTIL.getDataTestDirOnTestFS(SecureQueryServerIT.class.getSimpleName()); - FSUtils.setRootDir(conf, rootdir); - HBASE_CLUSTER = new LocalHBaseCluster(conf, 1); - HBASE_CLUSTER.startup(); - - // Then fork a thread with PQS in it. - startQueryServer(); - } - - private static void startQueryServer() throws Exception { - PQS = new QueryServer(new String[0], UTIL.getConfiguration()); - // Get the PQS ident for PQS to use - final UserGroupInformation ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(PQS_PRINCIPAL, KEYTAB.getAbsolutePath()); - PQS_EXECUTOR = Executors.newSingleThreadExecutor(); - // Launch PQS, doing in the Kerberos login instead of letting PQS do it itself (which would - // break the HBase/HDFS logins also running in the same test case). - PQS_EXECUTOR.submit(new Runnable() { - @Override public void run() { - ugi.doAs(new PrivilegedAction() { - @Override public Void run() { - PQS.run(); - return null; - } - }); - } - }); - PQS.awaitRunning(); - PQS_PORT = PQS.getPort(); - PQS_URL = ThinClientUtil.getConnectionUrl("localhost", PQS_PORT) + ";authentication=SPNEGO"; - } - - @AfterClass - public static void stopKdc() throws Exception { - // Remove our custom ConfigurationFactory for future tests - InstanceResolver.clearSingletons(); - if (PQS_EXECUTOR != null) { - PQS.stop(); - PQS_EXECUTOR.shutdown(); - if (!PQS_EXECUTOR.awaitTermination(5, TimeUnit.SECONDS)) { - LOG.info("PQS didn't exit in 5 seconds, proceeding anyways."); - } - } - if (HBASE_CLUSTER != null) { - HBASE_CLUSTER.shutdown(); - HBASE_CLUSTER.join(); - } - if (UTIL != null) { - UTIL.shutdownMiniZKCluster(); - } - if (KDC != null) { - KDC.stop(); - } - } - - @Test - public void testBasicReadWrite() throws Exception { - final Entry user1 = getUser(1); - UserGroupInformation user1Ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(user1.getKey(), user1.getValue().getAbsolutePath()); - user1Ugi.doAs(new PrivilegedExceptionAction() { - @Override public Void run() throws Exception { - // Phoenix - final String tableName = "phx_table1"; - try (java.sql.Connection conn = DriverManager.getConnection(PQS_URL); - Statement stmt = conn.createStatement(); - PreparedStatement pstmt = conn.prepareStatement("UPSERT INTO " + tableName + " values(?)"))) { - conn.setAutoCommit(true); - assertFalse(stmt.execute("CREATE TABLE " + tableName + "(pk integer not null primary key)")); - final int numRows = 5; - for (int i = 0; i < numRows; i++) { - pstmt.setInt(1, i); - assertEquals(1, stmt.executeUpdate()); - } - - try (ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName)) { - for (int i = 0; i < numRows; i++) { - assertTrue(rs.next()); - assertEquals(i, rs.getInt(1)); - } - assertFalse(rs.next()); - } - } - return null; - } - }); - } - - byte[] copyBytes(byte[] src, int offset, int length) { - byte[] dest = new byte[length]; - System.arraycopy(src, offset, dest, 0, length); - return dest; - } -}