This is an automated email from the ASF dual-hosted git repository.
michaelsmith pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git
The following commit(s) were added to refs/heads/master by this push:
new 100693d5a IMPALA-12093: impala-shell to preserve all cookies
100693d5a is described below
commit 100693d5adce3a5db38bb171cae4e9c0dec5e20e
Author: Michael Smith <[email protected]>
AuthorDate: Fri Jun 21 16:45:56 2024 -0700
IMPALA-12093: impala-shell to preserve all cookies
Updates impala-shell to preserve all cookies by default, defined as
setting 'http_cookie_names=*'. Prior behavior of restricting cookies to
a user-specified list is preserved when 'http_cookie_names' is given any
value besides '*'. Setting 'http_cookie_names=' prevents any cookies
from being preserved.
Adds verbose output that prints all cookies that are preserved by the
HTTP client.
Existing cookie tests with LDAP still work. Adds a test where Impala
returns an extra cookie, and test verifies that verbose mode prints all
expected cookies.
Change-Id: Ic81f790288460b086ab218e6701e8115a996dfa7
Reviewed-on: http://gerrit.cloudera.org:8080/19827
Reviewed-by: Impala Public Jenkins <[email protected]>
Tested-by: Michael Smith <[email protected]>
---
be/src/rpc/authentication.cc | 7 ++++
.../impala/customcluster/LdapImpalaShellTest.java | 45 ++++++++++++++++------
.../LdapSearchBindImpalaShellTest.java | 2 +-
.../LdapSimpleBindImpalaShellTest.java | 4 +-
.../impala/customcluster/RunShellCommand.java | 23 ++++++++---
shell/ImpalaHttpClient.py | 40 ++++++++++++++-----
shell/cookie_util.py | 22 ++++++++++-
shell/impala_client.py | 6 ++-
shell/option_parser.py | 6 ++-
9 files changed, 121 insertions(+), 34 deletions(-)
diff --git a/be/src/rpc/authentication.cc b/be/src/rpc/authentication.cc
index 59dbec614..7fc8c2c74 100644
--- a/be/src/rpc/authentication.cc
+++ b/be/src/rpc/authentication.cc
@@ -218,6 +218,9 @@
DEFINE_bool(enable_group_filter_check_for_authenticated_kerberos_user, false,
"is the same Active Directory server). "
"The default value is false, which provides backwards-compatible
behavior.");
+DEFINE_string_hidden(test_cookie, "",
+ "Adds Set-Cookie: <test_cookie> to BasicAuth for testing.");
+
namespace impala {
// Sasl callbacks. Why are these here? Well, Sasl isn't that bright, and
@@ -674,6 +677,10 @@ bool BasicAuth(ThriftServer::ConnectionContext*
connection_context,
// Create a cookie to return.
connection_context->return_headers.push_back(
Substitute("Set-Cookie: $0", GenerateCookie(username, hash)));
+ if (!FLAGS_test_cookie.empty()) {
+ connection_context->return_headers.push_back(
+ Substitute("Set-Cookie: $0", FLAGS_test_cookie));
+ }
return true;
}
connection_context->return_headers.push_back("WWW-Authenticate: Basic");
diff --git
a/fe/src/test/java/org/apache/impala/customcluster/LdapImpalaShellTest.java
b/fe/src/test/java/org/apache/impala/customcluster/LdapImpalaShellTest.java
index 3f61abe8b..c950d4519 100644
--- a/fe/src/test/java/org/apache/impala/customcluster/LdapImpalaShellTest.java
+++ b/fe/src/test/java/org/apache/impala/customcluster/LdapImpalaShellTest.java
@@ -19,12 +19,15 @@ package org.apache.impala.customcluster;
import static org.apache.impala.testutil.LdapUtil.*;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.google.common.collect.Range;
import java.io.IOException;
import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.annotations.CreateTransport;
@@ -76,7 +79,8 @@ public class LdapImpalaShellTest {
// python -c "import ssl; print hasattr(ssl, 'create_default_context')"
String[] cmd = {
"python", "-c", "import ssl; print(hasattr(ssl,
'create_default_context'))"};
- return Boolean.parseBoolean(RunShellCommand.Run(cmd, true, "",
"").replace("\n", ""));
+ return Boolean.parseBoolean(
+ RunShellCommand.Run(cmd, true, "", "").stdout.replace("\n", ""));
}
/**
@@ -129,33 +133,34 @@ public class LdapImpalaShellTest {
/**
* Tests ldap authentication using impala-shell.
*/
- protected void testShellLdapAuthImpl() throws Exception {
+ protected void testShellLdapAuthImpl(String extra_cookie) throws Exception {
String query = "select logged_in_user()";
// Templated shell commands to test a simple 'show tables' command.
// 1. Valid username, password and default http_cookie_names. Should
succeed with
// cookies are being used.
String[] validCommand = {"impala-shell.sh", "", "--ldap",
"--auth_creds_ok_in_clear",
+ "--verbose",
String.format("--user=%s", TEST_USER_1),
String.format("--ldap_password_cmd=printf %s", TEST_PASSWORD_1),
String.format("--query=%s", query)};
// 2. Valid username, password and matching http_cookie_names. Should
succeed with
// cookies are being used.
String[] validCommandMatchingCookieNames = {"impala-shell.sh", "",
"--ldap",
- "--auth_creds_ok_in_clear", "--http_cookie_names=impala.auth",
+ "--auth_creds_ok_in_clear", "--verbose",
"--http_cookie_names=impala.auth",
String.format("--user=%s", TEST_USER_1),
String.format("--ldap_password_cmd=printf %s", TEST_PASSWORD_1),
String.format("--query=%s", query)};
// 3. Valid username and password, but not matching http_cookie_names.
Should succeed
// with cookies are not being used.
String[] validCommandMismatchingCookieNames = {"impala-shell.sh", "",
"--ldap",
- "--auth_creds_ok_in_clear", "--http_cookie_names=impala.conn",
+ "--auth_creds_ok_in_clear", "--verbose",
"--http_cookie_names=impala.conn",
String.format("--user=%s", TEST_USER_1),
String.format("--ldap_password_cmd=printf %s", TEST_PASSWORD_1),
String.format("--query=%s", query)};
// 4. Valid username and password, but empty http_cookie_names. Should
succeed with
// cookies are not being used.
String[] validCommandEmptyCookieNames = {"impala-shell.sh", "", "--ldap",
- "--auth_creds_ok_in_clear", "--http_cookie_names=",
+ "--auth_creds_ok_in_clear", "--verbose", "--http_cookie_names=",
String.format("--user=%s", TEST_USER_1),
String.format("--ldap_password_cmd=printf %s", TEST_PASSWORD_1),
String.format("--query=%s", query)};
@@ -168,35 +173,53 @@ public class LdapImpalaShellTest {
"impala-shell.sh", "", String.format("--query=%s", query)};
String protocolTemplate = "--protocol=%s";
+ // Sorted list of cookies for validCommand, where all cookies are
preserved.
+ List<String> preservedCookiesList = new ArrayList<>();
+ preservedCookiesList.add("impala.auth");
+ if (extra_cookie != null) {
+ preservedCookiesList.add(extra_cookie);
+ }
+ Collections.sort(preservedCookiesList);
+ String preservedCookies = String.join(", ", preservedCookiesList);
+
for (String p : getProtocolsToTest()) {
String protocol = String.format(protocolTemplate, p);
validCommand[1] = protocol;
- RunShellCommand.Run(validCommand, /*shouldSucceed*/ true, TEST_USER_1,
+ RunShellCommand.Output result = RunShellCommand.Run(validCommand,
+ /*shouldSucceed*/ true, TEST_USER_1,
"Starting Impala Shell with LDAP-based authentication");
if (p.equals("hs2-http")) {
// Check that cookies are being used.
verifyMetrics(Range.atLeast(1L), zero, Range.atLeast(1L), zero);
+ assertTrue(result.stderr,
+ result.stderr.contains("Preserving cookies: " + preservedCookies));
}
validCommandMatchingCookieNames[1] = protocol;
- RunShellCommand.Run(validCommandMatchingCookieNames, /*shouldSucceed*/
true,
- TEST_USER_1, "Starting Impala Shell with LDAP-based authentication");
+ result = RunShellCommand.Run(validCommandMatchingCookieNames,
+ /*shouldSucceed*/ true, TEST_USER_1,
+ "Starting Impala Shell with LDAP-based authentication");
if (p.equals("hs2-http")) {
// Check that cookies are being used.
verifyMetrics(Range.atLeast(2L), zero, Range.atLeast(2L), zero);
+ assertTrue(result.stderr,
+ result.stderr.contains("Preserving cookies: impala.auth"));
}
validCommandMismatchingCookieNames[1] = protocol;
- RunShellCommand.Run(validCommandMismatchingCookieNames,
/*shouldSucceed*/ true,
- TEST_USER_1, "Starting Impala Shell with LDAP-based authentication");
+ result = RunShellCommand.Run(validCommandMismatchingCookieNames,
+ /*shouldSucceed*/ true, TEST_USER_1,
+ "Starting Impala Shell with LDAP-based authentication");
if (p.equals("hs2-http")) {
// Check that cookies are NOT being used.
verifyMetrics(Range.atLeast(2L), zero, Range.atLeast(2L), zero);
+ assertFalse(result.stderr, result.stderr.contains("Preserving
cookies:"));
}
validCommandEmptyCookieNames[1] = protocol;
- RunShellCommand.Run(validCommandEmptyCookieNames, /*shouldSucceed*/ true,
+ result = RunShellCommand.Run(validCommandEmptyCookieNames,
/*shouldSucceed*/ true,
TEST_USER_1, "Starting Impala Shell with LDAP-based authentication");
if (p.equals("hs2-http")) {
// Check that cookies are NOT being used.
verifyMetrics(Range.atLeast(2L), zero, Range.atLeast(2L), zero);
+ assertFalse(result.stderr, result.stderr.contains("Preserving
cookies:"));
}
invalidCommand[1] = protocol;
diff --git
a/fe/src/test/java/org/apache/impala/customcluster/LdapSearchBindImpalaShellTest.java
b/fe/src/test/java/org/apache/impala/customcluster/LdapSearchBindImpalaShellTest.java
index b182e2985..29389cf2b 100644
---
a/fe/src/test/java/org/apache/impala/customcluster/LdapSearchBindImpalaShellTest.java
+++
b/fe/src/test/java/org/apache/impala/customcluster/LdapSearchBindImpalaShellTest.java
@@ -97,7 +97,7 @@ public class LdapSearchBindImpalaShellTest extends
LdapImpalaShellTest {
public void testShellLdapAuth() throws Exception {
setUp("--ldap_user_search_basedn=dc=myorg,dc=com "
+ "--ldap_user_filter=(&(objectClass=person)(cn={0}))");
- testShellLdapAuthImpl();
+ testShellLdapAuthImpl(null);
}
/**
diff --git
a/fe/src/test/java/org/apache/impala/customcluster/LdapSimpleBindImpalaShellTest.java
b/fe/src/test/java/org/apache/impala/customcluster/LdapSimpleBindImpalaShellTest.java
index a8f0255ac..e165a3ef9 100644
---
a/fe/src/test/java/org/apache/impala/customcluster/LdapSimpleBindImpalaShellTest.java
+++
b/fe/src/test/java/org/apache/impala/customcluster/LdapSimpleBindImpalaShellTest.java
@@ -94,8 +94,8 @@ public class LdapSimpleBindImpalaShellTest extends
LdapImpalaShellTest {
*/
@Test
public void testShellLdapAuth() throws Exception {
- setUp("");
- testShellLdapAuthImpl();
+ setUp("--test_cookie=impala.ldap=testShellLdapAuth");
+ testShellLdapAuthImpl("impala.ldap");
}
/**
diff --git
a/fe/src/test/java/org/apache/impala/customcluster/RunShellCommand.java
b/fe/src/test/java/org/apache/impala/customcluster/RunShellCommand.java
index c2ba1abe4..3cda2dad3 100644
--- a/fe/src/test/java/org/apache/impala/customcluster/RunShellCommand.java
+++ b/fe/src/test/java/org/apache/impala/customcluster/RunShellCommand.java
@@ -27,12 +27,25 @@ import java.io.InputStreamReader;
* Helper class to run a shell command.
*/
class RunShellCommand {
+ /**
+ * Tuple wrapping stdout, stderr from Run.
+ */
+ public static class Output {
+ public Output(String out, String err) {
+ stdout = out;
+ stderr = err;
+ }
+
+ public String stdout;
+ public String stderr;
+ }
+
/**
* Run a shell command 'cmd'. If 'shouldSucceed' is true, the command is
expected to
- * succeed, otherwise it is expected to fail. Returns the output (stdout) of
the
+ * succeed, otherwise it is expected to fail. Returns the output (stdout,
stderr) of the
* command.
*/
- public static String Run(String[] cmd, boolean shouldSucceed, String
expectedOut,
+ public static Output Run(String[] cmd, boolean shouldSucceed, String
expectedOut,
String expectedErr) throws Exception {
// run the command with the env variables inherited from the current
process
return Run(cmd, null, shouldSucceed, expectedOut, expectedErr);
@@ -41,10 +54,10 @@ class RunShellCommand {
/**
* Run a shell command 'cmd' with custom 'env' variables.
* If 'shouldSucceed' is true, the command is expected to
- * succeed, otherwise it is expected to fail. Returns the output (stdout) of
the
+ * succeed, otherwise it is expected to fail. Returns the output (stdout,
stderr) of the
* command.
*/
- public static String Run(String[] cmd, String[] env, boolean shouldSucceed,
+ public static Output Run(String[] cmd, String[] env, boolean shouldSucceed,
String expectedOut, String expectedErr) throws
Exception {
Runtime rt = Runtime.getRuntime();
Process process = rt.exec(cmd, env);
@@ -71,6 +84,6 @@ class RunShellCommand {
// If the query succeeds, assert that the output is correct.
String stdout = stdoutBuf.toString();
if (shouldSucceed) assertTrue(stdout, stdout.contains(expectedOut));
- return stdout;
+ return new Output(stdout, stderr);
}
}
diff --git a/shell/ImpalaHttpClient.py b/shell/ImpalaHttpClient.py
index 1197e3c72..5d9c43a33 100644
--- a/shell/ImpalaHttpClient.py
+++ b/shell/ImpalaHttpClient.py
@@ -16,6 +16,7 @@
# specific language governing permissions and limitations
# under the License.
#
+from __future__ import print_function, unicode_literals
from io import BytesIO
import os
@@ -31,7 +32,7 @@ from six.moves import urllib, http_client
from thrift.transport.TTransport import TTransportBase
from shell_exceptions import HttpError, AuthenticationException
-from cookie_util import get_all_matching_cookies, get_cookie_expiry
+from cookie_util import get_all_matching_cookies, get_all_cookies,
get_cookie_expiry
import six
@@ -52,7 +53,8 @@ class ImpalaHttpClient(TTransportBase):
MIN_REQUEST_SIZE_FOR_EXPECT = 1024
def __init__(self, uri_or_host, port=None, path=None, cafile=None,
cert_file=None,
- key_file=None, ssl_context=None, http_cookie_names=None,
socket_timeout_s=None):
+ key_file=None, ssl_context=None, http_cookie_names=None,
socket_timeout_s=None,
+ verbose=False):
"""ImpalaHttpClient supports two different types of construction:
ImpalaHttpClient(host, port, path) - deprecated
@@ -66,7 +68,8 @@ class ImpalaHttpClient(TTransportBase):
http_cookie_names is used to specify a comma-separated list of possible
cookie names
used for cookie-based authentication or session management. If a cookie
with one of
these names is returned in an http response by the server or an
intermediate proxy
- then it will be included in each subsequent request for the same
connection.
+ then it will be included in each subsequent request for the same
connection. If it
+ is set as wildcards, all cookies in an http response will be preserved.
"""
if port is not None:
warnings.warn(
@@ -111,16 +114,22 @@ class ImpalaHttpClient(TTransportBase):
else:
self.realhost = self.realport = self.proxy_auth = None
if (not http_cookie_names) or (str(http_cookie_names).strip() == ""):
+ self.__preserve_all_cookies = False
self.__http_cookie_dict = None
self.__auth_cookie_names = None
+ elif str(http_cookie_names).strip() == "*":
+ self.__preserve_all_cookies = True
+ self.__http_cookie_dict = dict()
+ self.__auth_cookie_names = set()
else:
+ self.__preserve_all_cookies = False
# Build a dictionary that maps cookie name to namedtuple.
cookie_names = http_cookie_names.split(',')
self.__http_cookie_dict = \
{cn: Cookie(cookie=None, expiry_time=None) for cn in cookie_names}
# Store the auth cookie names in __auth_cookie_names.
# Assume auth cookie names end with ".auth".
- self.__auth_cookie_names = [cn for cn in cookie_names if
cn.endswith(".auth")]
+ self.__auth_cookie_names = {cn for cn in cookie_names if
cn.endswith(".auth")}
# Set __are_matching_cookies_found as True if matching cookies are found
in response.
self.__are_matching_cookies_found = False
self.__wbuf = BytesIO()
@@ -134,6 +143,7 @@ class ImpalaHttpClient(TTransportBase):
self.__basic_auth = None
self.__kerb_service = None
self.__add_custom_headers_funcs = []
+ self.__verbose = verbose
@staticmethod
def basic_proxy_auth_header(proxy):
@@ -309,13 +319,25 @@ class ImpalaHttpClient(TTransportBase):
# Extract cookies from response and save those cookies for which the cookie
names
# are in the cookie name list specified in the __init__().
def extractHttpCookiesFromResponse(self):
- if self.__http_cookie_dict:
+ if self.__preserve_all_cookies:
+ matching_cookies = get_all_cookies(self.path, self.headers)
+ elif self.__http_cookie_dict:
matching_cookies = get_all_matching_cookies(
self.__http_cookie_dict.keys(), self.path, self.headers)
- if matching_cookies:
- self.__are_matching_cookies_found = True
- for c in matching_cookies:
- self.__http_cookie_dict[c.key] = Cookie(c, get_cookie_expiry(c))
+ else:
+ matching_cookies = None
+
+ if matching_cookies:
+ if self.__verbose:
+ names = sorted([c.key for c in matching_cookies])
+ print("Preserving cookies: " + ', '.join(names), file=sys.stderr)
+ sys.stderr.flush()
+
+ self.__are_matching_cookies_found = True
+ for c in matching_cookies:
+ self.__http_cookie_dict[c.key] = Cookie(c, get_cookie_expiry(c))
+ if c.key.endswith(".auth"):
+ self.__auth_cookie_names.add(c.key)
# Return True if there are any saved cookies which are sent in previous
request.
def areHttpCookiesSaved(self):
diff --git a/shell/cookie_util.py b/shell/cookie_util.py
index 3f3220226..eeff4e567 100644
--- a/shell/cookie_util.py
+++ b/shell/cookie_util.py
@@ -50,8 +50,7 @@ def get_cookie_expiry(c):
return None
-def get_all_matching_cookies(cookie_names, path, resp_headers):
- matching_cookies = None
+def get_cookies(resp_headers):
if 'Set-Cookie' not in resp_headers:
return None
@@ -63,9 +62,28 @@ def get_all_matching_cookies(cookie_names, path,
resp_headers):
cookie_headers = resp_headers.get_all('Set-Cookie')
for header in cookie_headers:
cookies.load(header)
+ return cookies
except Exception:
return None
+
+def get_all_cookies(path, resp_headers):
+ cookies = get_cookies(resp_headers)
+ if not cookies:
+ return None
+
+ matching_cookies = []
+ for c in cookies.values():
+ if c and cookie_matches_path(c, path):
+ matching_cookies.append(c)
+ return matching_cookies
+
+
+def get_all_matching_cookies(cookie_names, path, resp_headers):
+ cookies = get_cookies(resp_headers)
+ if not cookies:
+ return None
+
matching_cookies = []
for cn in cookie_names:
if cn in cookies:
diff --git a/shell/impala_client.py b/shell/impala_client.py
index b94df8931..8ec1d72ab 100755
--- a/shell/impala_client.py
+++ b/shell/impala_client.py
@@ -414,11 +414,13 @@ class ImpalaClient(object):
url = "https://{0}/{1}".format(host_and_port, self.http_path)
transport = ImpalaHttpClient(url, ssl_context=ssl_ctx,
http_cookie_names=self.http_cookie_names,
- socket_timeout_s=self.http_socket_timeout_s)
+ socket_timeout_s=self.http_socket_timeout_s,
+ verbose=self.verbose)
else:
url = "http://{0}/{1}".format(host_and_port, self.http_path)
transport = ImpalaHttpClient(url,
http_cookie_names=self.http_cookie_names,
- socket_timeout_s=self.http_socket_timeout_s)
+ socket_timeout_s=self.http_socket_timeout_s,
+ verbose=self.verbose)
if self.use_ldap:
# Set the BASIC authorization
diff --git a/shell/option_parser.py b/shell/option_parser.py
index 1fc6fde2d..a587596ce 100755
--- a/shell/option_parser.py
+++ b/shell/option_parser.py
@@ -333,12 +333,14 @@ def get_option_parser(defaults):
"spooling is disabled only a single row batch can be
fetched at a "
"time regardless of the specified fetch_size.")
parser.add_option("--http_cookie_names", dest="http_cookie_names",
- default="impala.auth,impala.session.id",
+ default="*",
help="A comma-separated list of HTTP cookie names that are
supported "
"by the impala-shell. If a cookie with one of these names
is "
"returned in an http response by the server or an
intermediate proxy "
"then it will be included in each subsequent request for
the same "
- "connection.")
+ "connection. If set to wildcard (*), all cookies in an
http response "
+ "will be preserved. The name of an authentication cookie
must end "
+ "with '.auth', for example 'impala.auth'.")
parser.add_option("--no_http_tracing", dest="no_http_tracing",
action="store_true",
help="Tracing http headers 'X-Request-Id',
'X-Impala-Session-Id', "