This is an automated email from the ASF dual-hosted git repository.
arawat 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 8fea75cb5 IMPALA-13312: Use client address from X-Forwarded-For Header
in Ranger Audit Logs
8fea75cb5 is described below
commit 8fea75cb5ce206ad071859bb331fa4811573cf4b
Author: Abhishek Rawat <[email protected]>
AuthorDate: Tue Sep 10 15:50:06 2024 -0700
IMPALA-13312: Use client address from X-Forwarded-For Header in Ranger
Audit Logs
Added backend flag 'use_xff_address_as_origin' for using the client IP
address from 'X-Forwarded-For' HTTP header as the origin of HTTP
connection. The origin IP address in the SessionState is used by the
ranger client for both authorization (RangerAccessRequestImpl) and
auditing (RangerBufferAuditHandler). Impala does not do any verification
or sanitization of this IP address, so its value should only be trusted
if the deployment environment protects against spoofing.
Also, added a new function 'GetXFFOriginClientAddress' for parsing XFF
header with comma separated IP addresses, which is the most common form
of XFF header representing client and intermediate proxies:
X-Forwarded-For: <client>, <proxy1>, <proxy2>
'GetXFFOriginClientAddress' is now also used for getting the client IP
from XFF header in existing use cases such as trusted domain based
authentication for both HS2 HTTP server and web server.
Testing:
- Added unit tests for the new GetXFFOriginClientAddress function for
parsing comma separated IP addresses in XFF header
- Updated existing tests for trusted domain authentication to use
XFF with comma separated IP addresses
- Added custom cluster test which ensures that client IP address from
XFF header is included in the ranger audit logs.
Change-Id: Ib784ad805c649e9576ef34f125509c904b7773ab
Reviewed-on: http://gerrit.cloudera.org:8080/21780
Reviewed-by: Abhishek Rawat <[email protected]>
Tested-by: Impala Public Jenkins <[email protected]>
---
be/src/rpc/authentication-test.cc | 36 ++++++++++
be/src/rpc/authentication-util.cc | 18 +++++
be/src/rpc/authentication-util.h | 5 ++
be/src/rpc/authentication.cc | 23 +++++-
be/src/transport/THttpServer.cpp | 11 ++-
be/src/util/webserver.cc | 13 ++--
.../apache/impala/customcluster/LdapHS2Test.java | 4 +-
.../impala/customcluster/LdapWebserverTest.java | 2 +-
tests/authorization/test_ranger.py | 83 +++++++++++++++++++++
tests/custom_cluster/test_shell_jwt_auth.py | 84 ++++++++--------------
10 files changed, 212 insertions(+), 67 deletions(-)
diff --git a/be/src/rpc/authentication-test.cc
b/be/src/rpc/authentication-test.cc
index b2c78cfcd..26b2db72e 100644
--- a/be/src/rpc/authentication-test.cc
+++ b/be/src/rpc/authentication-test.cc
@@ -20,6 +20,7 @@
#include "common/logging.h"
#include "kudu/security/test/mini_kdc.h"
#include "rpc/authentication.h"
+#include "rpc/authentication-util.h"
#include "rpc/thrift-server.h"
#include "util/auth-util.h"
#include "util/kudu-status-util.h"
@@ -280,6 +281,41 @@ TEST(Auth, LdapKerbAuthCustomFiltersNotAllowed) {
ASSERT_FALSE(ap->is_secure());
}
+TEST(Auth, GetXFFOriginClientAddress) {
+ // Empty XFF address
+ std::string xff_addresses;
+ std::string origin;
+ Status status = GetXFFOriginClientAddress(xff_addresses, origin);
+ ASSERT_EQ(status.msg().msg(), "Empty XFF header");
+
+ // Empty ',' separated XFF address
+ xff_addresses = " ,";
+ status = GetXFFOriginClientAddress(xff_addresses, origin);
+ ASSERT_EQ(status.msg().msg(), "Empty XFF header");
+
+ // Multiple XFF addresses
+ xff_addresses = " 10.96.11.12, 172.45.88.78, 176.45.88.65";
+ status = GetXFFOriginClientAddress(xff_addresses, origin);
+ ASSERT_EQ(origin, "10.96.11.12");
+ ASSERT_TRUE(status.ok());
+
+ // Single XFF address
+ xff_addresses = "10.95.66.23";
+ status = GetXFFOriginClientAddress(xff_addresses, origin);
+ ASSERT_EQ(origin, "10.95.66.23");
+ ASSERT_TRUE(status.ok());
+
+ xff_addresses = " 10.90.11.23, ";
+ status = GetXFFOriginClientAddress(xff_addresses, origin);
+ ASSERT_EQ(origin, "10.90.11.23");
+ ASSERT_TRUE(status.ok());
+
+ xff_addresses = " 10.90.11.23 , ";
+ status = GetXFFOriginClientAddress(xff_addresses, origin);
+ ASSERT_EQ(origin, "10.90.11.23");
+ ASSERT_TRUE(status.ok());
+}
+
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
diff --git a/be/src/rpc/authentication-util.cc
b/be/src/rpc/authentication-util.cc
index 6a9ec41c7..05ddc0982 100644
--- a/be/src/rpc/authentication-util.cc
+++ b/be/src/rpc/authentication-util.cc
@@ -211,6 +211,24 @@ bool IsTrustedDomain(const std::string& origin, const
std::string& trusted_domai
return HasSuffixString(host_name, trusted_domain);
}
+Status GetXFFOriginClientAddress(const std::string_view& xff_addresses,
+ std::string& origin) {
+ if (xff_addresses.empty()) {
+ return Status::Expected("Empty XFF header");
+ }
+ const int pos = xff_addresses.find_first_of(',');
+ if (pos == xff_addresses.npos) {
+ origin = xff_addresses;
+ } else {
+ origin = xff_addresses.substr(0, pos);
+ }
+ StripWhiteSpace(&origin);
+ if (origin.empty()) {
+ return Status::Expected("Empty XFF header");
+ }
+ return Status::OK();
+}
+
Status BasicAuthExtractCredentials(
const string& token, string& username, string& password) {
if (token.empty()) {
diff --git a/be/src/rpc/authentication-util.h b/be/src/rpc/authentication-util.h
index efd3ae5da..eec497612 100644
--- a/be/src/rpc/authentication-util.h
+++ b/be/src/rpc/authentication-util.h
@@ -51,6 +51,11 @@ std::string GetDeleteCookie();
bool IsTrustedDomain(const std::string& origin, const std::string&
trusted_domain,
bool strict_localhost);
+// Returns the origin client address from a comma separated list of addresses.
The first
+// address in the comma separated list is considered the origin client address.
+Status GetXFFOriginClientAddress(const std::string_view& xff_addresses,
+ std::string& origin);
+
// Takes in the base64 encoded token and returns the username and password via
the input
// arguments. Returns an OK status if the token is a valid base64 encoded
string of the
// form <username>:<password>, an error status otherwise.
diff --git a/be/src/rpc/authentication.cc b/be/src/rpc/authentication.cc
index 21031e52b..c27187d77 100644
--- a/be/src/rpc/authentication.cc
+++ b/be/src/rpc/authentication.cc
@@ -127,17 +127,17 @@ DEFINE_string(trusted_domain, "",
"<username>:<password> where the password is not used and can be left
blank.");
DEFINE_bool(trusted_domain_use_xff_header, false,
- "If set to true, this uses the 'X-Forwarded-For' HTML header to check for
origin "
+ "If set to true, this uses the 'X-Forwarded-For' HTTP header to check for
origin "
"while attempting to verify if the connection request originated from a
trusted "
"domain. Only used if '--trusted_domain' is specified. Warning: Only use
this if you "
"trust the incoming connection to have this set correctly.");
DEFINE_bool(trusted_domain_empty_xff_header_use_origin, false,
- "If set to true and the 'X-Forwarded-For' HTML header value is empty in
the request, "
+ "If set to true and the 'X-Forwarded-For' HTTP header value is empty in
the request, "
"then the origin of the the underlying transport is used while attempting
to "
"verify if the connection request originated from a trusted domain. Only
used "
"if '--trusted_domain' and '--trusted_domain_use_xff_header' flags are
specified. "
- "Warning: In case the 'X-Forwarded-For' HTML header is empty or not in the
request, "
+ "Warning: In case the 'X-Forwarded-For' HTTP header is empty or not in the
request, "
"this flag allows a fallback to the default behavior in trusted domain
check "
"(where '--trusted_domain' flag is specified, but
'--trusted_domain_use_xff_header' "
"flag is not set).");
@@ -148,6 +148,14 @@ DEFINE_bool(trusted_domain_strict_localhost, true,
"important for security, because reverse DNS can resolve other non-local
addresses "
"to localhost.");
+DEFINE_bool(use_xff_address_as_origin, false,
+ "If set to true use the address in 'X-Forwarded-For' HTTP header as origin
of the "
+ "connection. If XFF header is not set then the Peer Address on the
underlying socket "
+ "is used as the origin. If XFF header has multiple addresses corresponding
to the "
+ "various intermediate proxies, then the first address is used as the
origin. XFF "
+ "header with multiple IP addresses must be comma separated. Only use this
if you "
+ "trust the incoming connection to have the XFF header set correctly.");
+
// This flag must be used with caution to avoid security risks.
DEFINE_string(trusted_auth_header, "",
"If set as non empty string, Impala will look for this header in the HTTP
headers. "
@@ -639,6 +647,15 @@ bool GetUsernameFromBasicAuthHeader(
bool SetOrigin(
ThriftServer::ConnectionContext* connection_context, const std::string&
origin) {
connection_context->http_origin = origin;
+ if (FLAGS_use_xff_address_as_origin) {
+ std::string origin_client;
+ Status status = GetXFFOriginClientAddress(origin, origin_client);
+ if (!status.ok()) {
+ LOG(ERROR) << "Error parsing XFF header: " << status;
+ } else {
+ connection_context->network_address = MakeNetworkAddress(origin_client);
+ }
+ }
return false;
}
diff --git a/be/src/transport/THttpServer.cpp b/be/src/transport/THttpServer.cpp
index 26481e781..482ec79f8 100644
--- a/be/src/transport/THttpServer.cpp
+++ b/be/src/transport/THttpServer.cpp
@@ -34,6 +34,8 @@
#include "gen-cpp/Frontend_types.h"
#include "util/metrics.h"
#include "common/logging.h"
+#include "common/status.h"
+#include "rpc/authentication-util.h"
DECLARE_bool(trusted_domain_use_xff_header);
DECLARE_bool(trusted_domain_empty_xff_header_use_origin);
@@ -374,8 +376,13 @@ void THttpServer::headersDone() {
// the client started the SAML workflow then it doesn't expect Impala to
succeed with
// another mechanism.
if (!authorized && check_trusted_domain_) {
- string origin =
- FLAGS_trusted_domain_use_xff_header ? origin_ :
transport_->getOrigin();
+ string origin;
+ if (FLAGS_trusted_domain_use_xff_header) {
+ impala::Status status = impala::GetXFFOriginClientAddress(origin_,
origin);
+ if (!status.ok()) LOG(WARNING) << status.GetDetail();
+ } else {
+ origin = transport_->getOrigin();
+ }
StripWhiteSpace(&origin);
if (origin.empty() && FLAGS_trusted_domain_use_xff_header &&
FLAGS_trusted_domain_empty_xff_header_use_origin) {
diff --git a/be/src/util/webserver.cc b/be/src/util/webserver.cc
index daa9a3f78..543a6e1da 100644
--- a/be/src/util/webserver.cc
+++ b/be/src/util/webserver.cc
@@ -742,10 +742,15 @@ sq_callback_result_t
Webserver::BeginRequestCallback(struct sq_connection* conne
// unpredictably costly.
if (!authenticated && check_trusted_domain_) {
const char* xff_origin = sq_get_header(connection, "X-Forwarded-For");
- string xff_origin_string = !xff_origin ? "" : string(xff_origin);
- string origin = FLAGS_trusted_domain_use_xff_header ?
- xff_origin_string :
- GetRemoteAddress(request_info).ToString();
+ std::string_view xff_origin_sv = !xff_origin ? "" : xff_origin;
+ string origin;
+ if (FLAGS_trusted_domain_use_xff_header) {
+ Status status = GetXFFOriginClientAddress(xff_origin_sv, origin);
+ if (!status.ok()) LOG(WARNING) << status.GetDetail();
+ } else {
+ origin = GetRemoteAddress(request_info).ToString();
+ }
+
StripWhiteSpace(&origin);
if (origin.empty() && FLAGS_trusted_domain_use_xff_header &&
FLAGS_trusted_domain_empty_xff_header_use_origin) {
diff --git a/fe/src/test/java/org/apache/impala/customcluster/LdapHS2Test.java
b/fe/src/test/java/org/apache/impala/customcluster/LdapHS2Test.java
index 29bff7d85..3cff62b22 100644
--- a/fe/src/test/java/org/apache/impala/customcluster/LdapHS2Test.java
+++ b/fe/src/test/java/org/apache/impala/customcluster/LdapHS2Test.java
@@ -511,7 +511,7 @@ public class LdapHS2Test {
// Case 1: Authenticate as 'Test1Ldap' without password, send
X-Forwarded-For header
headers.put("Authorization", "Basic VGVzdDFMZGFwOg==");
- headers.put("X-Forwarded-For", "127.0.0.1");
+ headers.put("X-Forwarded-For", "127.0.0.1, 120.76.80.91");
transport.setCustomHeaders(headers);
transport.open();
TCLIService.Iface client = new TCLIService.Client(new
TBinaryProtocol(transport));
@@ -546,7 +546,7 @@ public class LdapHS2Test {
// Case 3: Authenticate as 'Test1Ldap' without password, send
X-Forwarded-For header
// that does not match trusted_domain
headers.put("Authorization", "Basic VGVzdDFMZGFwOg==");
- headers.put("X-Forwarded-For", "126.0.23.1");
+ headers.put("X-Forwarded-For", "126.0.23.1, 127.0.0.1, 127.0.0.6");
transport.setCustomHeaders(headers);
try {
openResp = client.OpenSession(openReq);
diff --git
a/fe/src/test/java/org/apache/impala/customcluster/LdapWebserverTest.java
b/fe/src/test/java/org/apache/impala/customcluster/LdapWebserverTest.java
index 4f6e4dddf..abbaceb18 100644
--- a/fe/src/test/java/org/apache/impala/customcluster/LdapWebserverTest.java
+++ b/fe/src/test/java/org/apache/impala/customcluster/LdapWebserverTest.java
@@ -272,7 +272,7 @@ public class LdapWebserverTest {
verifyTrustedDomainMetrics(Range.closed(1L, 1L));
// Case 2: Authenticate as 'Test1Ldap' without password
- attemptConnection("Basic VGVzdDFMZGFwOg==", "127.0.0.1", false);
+ attemptConnection("Basic VGVzdDFMZGFwOg==", "127.0.0.1, 126.5.6.7", false);
verifyTrustedDomainMetrics(Range.closed(2L, 2L));
// Case 3: Authenticate as 'Test1Ldap' with the right password
diff --git a/tests/authorization/test_ranger.py
b/tests/authorization/test_ranger.py
index 45b3515ea..17de9c31c 100644
--- a/tests/authorization/test_ranger.py
+++ b/tests/authorization/test_ranger.py
@@ -26,6 +26,8 @@ import pytest
import logging
import requests
from subprocess import check_call
+import tempfile
+from time import sleep
from getpass import getuser
from tests.common.custom_cluster_test_suite import CustomClusterTestSuite
@@ -33,6 +35,8 @@ from tests.common.file_utils import copy_files_to_hdfs_dir
from tests.common.skip import SkipIfFS, SkipIfHive2, SkipIf
from tests.common.test_dimensions import (create_client_protocol_dimension,
create_exec_option_dimension, create_orc_dimension)
+from tests.common.test_vector import ImpalaTestVector
+from tests.shell.util import run_impala_shell_cmd
from tests.util.hdfs_util import NAMENODE
from tests.util.calculation_util import get_random_id
from tests.util.filesystem_utils import WAREHOUSE_PREFIX, WAREHOUSE
@@ -69,6 +73,85 @@ class TestRanger(CustomClusterTestSuite):
def get_workload(cls):
return 'functional-query'
+ @pytest.mark.execute_serially
+ @CustomClusterTestSuite.with_args(
+ impala_log_dir=tempfile.mkdtemp(prefix="ranger_audit_xff",
dir=os.getenv("LOG_DIR")),
+ impalad_args=(IMPALAD_ARGS + " --use_xff_address_as_origin=true"),
+ catalogd_args=CATALOGD_ARGS)
+ def test_xff_ranger_audit(self):
+ """
+ Tests XFF client IP is included in ranger audit logs when using hs2-http
protocol
+ """
+ # Iterate over test vector within test function to avoid restarting
cluster.
+ for vector in\
+ [ImpalaTestVector([value]) for value in
create_client_protocol_dimension()]:
+ protocol = vector.get_value("protocol")
+ if protocol != "hs2-http":
+ continue
+
+ # Query with XFF header in client request
+ args = ['--protocol=hs2-http',
+ '--hs2_x_forward= 10.20.30.40 ',
+ '-q', 'select count(1) from functional.alltypes']
+ run_impala_shell_cmd(vector, args)
+
+ # Query with XFF header in client request
+ args = ['--protocol=hs2-http',
+ '--hs2_x_forward=10.20.30.40, 1.1.2.3, 127.0.0.6',
+ '-q', 'select count(1) from functional.alltypes']
+ run_impala_shell_cmd(vector, args)
+
+ # Query with XFF header in client request
+ args = ['--protocol=hs2-http',
+ '--hs2_x_forward=10.20.30.40,1.1.2.3,127.0.0.6',
+ '-q', 'select count(1) from functional.alltypes']
+ run_impala_shell_cmd(vector, args)
+
+ # Query without XFF header in client request
+ args = ['--protocol=hs2-http',
+ '-q', 'select count(2) from functional.alltypes']
+ run_impala_shell_cmd(vector, args)
+
+ # Query with empty XFF header in client request
+ args = ['--protocol=hs2-http',
+ '--hs2_x_forward= ',
+ '-q', 'select count(2) from functional.alltypes']
+ run_impala_shell_cmd(vector, args)
+
+ # Query with invalid XFF header in client request
+ args = ['--protocol=hs2-http',
+ '--hs2_x_forward=foobar',
+ '-q', 'select count(3) from functional.alltypes']
+ run_impala_shell_cmd(vector, args)
+
+ # Shut down cluster to ensure logs flush to disk.
+ sleep(5)
+ self._stop_impala_cluster()
+
+ # Expected audit log string
+ expected_string_valid_xff = (
+ '"access":"select",'
+ '"resource":"functional/alltypes",'
+ '"resType":"@table",'
+ '"action":"select",'
+ '"result":1,'
+ '"agent":"impala",'
+ r'"policy":\d,'
+ '"enforcer":"ranger-acl",'
+ '"cliIP":"%s",'
+ '"reqData":"%s",'
+ '".+":".+","logType":"RangerAudit"'
+ )
+
+ # Ensure audit logs were logged in coordinator logs
+ self.assert_impalad_log_contains("INFO", expected_string_valid_xff %
+ ("10.20.30.40", r"select count\(1\) from functional.alltypes"),
+ expected_count=3)
+ self.assert_impalad_log_contains("INFO", expected_string_valid_xff %
+ ("127.0.0.1", r"select count\(2\) from functional.alltypes"),
expected_count=2)
+ self.assert_impalad_log_contains("INFO", expected_string_valid_xff %
+ ("foobar", r"select count\(3\) from functional.alltypes"),
expected_count=1)
+
@pytest.mark.execute_serially
@CustomClusterTestSuite.with_args(
impalad_args=IMPALAD_ARGS, catalogd_args=CATALOGD_ARGS)
diff --git a/tests/custom_cluster/test_shell_jwt_auth.py
b/tests/custom_cluster/test_shell_jwt_auth.py
index 7999b1c3a..fe62cb1c9 100644
--- a/tests/custom_cluster/test_shell_jwt_auth.py
+++ b/tests/custom_cluster/test_shell_jwt_auth.py
@@ -61,9 +61,10 @@ class TestImpalaShellJWTAuth(CustomClusterTestSuite):
@pytest.mark.execute_serially
@CustomClusterTestSuite.with_args(
- "-log_dir={0} -v 2 -jwks_file_path={1} -jwt_custom_claim_username=sub "
+ impala_log_dir=LOG_DIR_JWT_AUTH_SUCCESS,
+ impalad_args="-v 2 -jwks_file_path={0} -jwt_custom_claim_username=sub "
"-jwt_token_auth=true -jwt_allow_without_tls=true"
- .format(LOG_DIR_JWT_AUTH_SUCCESS, JWKS_JSON_PATH))
+ .format(JWKS_JSON_PATH))
def test_jwt_auth_valid(self, vector):
"""Asserts the Impala shell can authenticate to Impala using JWT
authentication.
Also executes a query to ensure the authentication was successful."""
@@ -82,15 +83,13 @@ class TestImpalaShellJWTAuth(CustomClusterTestSuite):
# Ensure JWT auth was enabled by checking the coordinator startup flags
logged
# in the coordinator's INFO logfile
- expected_strings = [
- '--jwks_file_path={0}'.format(self.JWKS_JSON_PATH),
- 'effective username: test-user',
- 'connected_user (string) = "test-user"',
- ]
-
+ self.assert_impalad_log_contains("INFO",
+ '--jwks_file_path={0}'.format(self.JWKS_JSON_PATH), expected_count=1)
# Ensure JWT auth was successful by checking impala coordinator logs
- self.__assert_log_file(self.LOG_DIR_JWT_AUTH_SUCCESS,
- "impalad.INFO", expected_strings)
+ self.assert_impalad_log_contains("INFO",
+ 'effective username: test-user', expected_count=1)
+ self.assert_impalad_log_contains("INFO",
+ r'connected_user \(string\) = "test-user"', expected_count=1)
# Ensure the query ran successfully.
assert "version()" in result.stdout
@@ -98,9 +97,10 @@ class TestImpalaShellJWTAuth(CustomClusterTestSuite):
@pytest.mark.execute_serially
@CustomClusterTestSuite.with_args(
- "-log_dir={0} -v 2 -jwks_file_path={1} -jwt_custom_claim_username=sub "
+ impala_log_dir=LOG_DIR_JWT_AUTH_FAIL,
+ impalad_args="-v 2 -jwks_file_path={0} -jwt_custom_claim_username=sub "
"-jwt_token_auth=true -jwt_allow_without_tls=true"
- .format(LOG_DIR_JWT_AUTH_FAIL, JWKS_JSON_PATH))
+ .format(JWKS_JSON_PATH))
def test_jwt_auth_expired(self, vector):
"""Asserts the Impala shell fails to authenticate when it presents a JWT
that has a
valid signature but is expired."""
@@ -119,17 +119,16 @@ class TestImpalaShellJWTAuth(CustomClusterTestSuite):
# Ensure JWT auth was enabled by checking the coordinator startup flags
logged
# in the coordinator's INFO logfile
- expected_strings = ['--jwks_file_path={0}'.format(self.JWKS_JSON_PATH)]
- self.__assert_log_file(self.LOG_DIR_JWT_AUTH_FAIL,
- "impalad.INFO", expected_strings)
+ expected_string = '--jwks_file_path={0}'.format(self.JWKS_JSON_PATH)
+ self.assert_impalad_log_contains("INFO", expected_string)
# Ensure JWT auth failed by checking impala coordinator logs
- expected_strings = [
- 'Error verifying JWT token',
+ expected_string = (
+ 'Error verifying JWT token'
+ '.*'
'Error verifying JWT Token: Verification failed, error: token expired'
- ]
- self.__assert_log_file(self.LOG_DIR_JWT_AUTH_FAIL,
- "impalad.ERROR", expected_strings)
+ )
+ self.assert_impalad_log_contains("ERROR", expected_string,
expected_count=-1)
# Ensure the shell login failed.
assert "Error connecting: HttpError" in result.stderr
@@ -138,9 +137,10 @@ class TestImpalaShellJWTAuth(CustomClusterTestSuite):
@pytest.mark.execute_serially
@CustomClusterTestSuite.with_args(
- "-log_dir={0} -v 2 -jwks_file_path={1} -jwt_custom_claim_username=sub "
+ impala_log_dir=LOG_DIR_JWT_AUTH_INVALID_JWK,
+ impalad_args="-v 2 -jwks_file_path={0} -jwt_custom_claim_username=sub "
"-jwt_token_auth=true -jwt_allow_without_tls=true"
- .format(LOG_DIR_JWT_AUTH_INVALID_JWK, JWKS_JSON_PATH))
+ .format(JWKS_JSON_PATH))
def test_jwt_auth_invalid_jwk(self, vector):
"""Asserts the Impala shell fails to authenticate when it presents a JWT
that has a
valid signature but is expired."""
@@ -159,48 +159,22 @@ class TestImpalaShellJWTAuth(CustomClusterTestSuite):
# Ensure JWT auth was enabled by checking the coordinator startup flags
logged
# in the coordinator's INFO logfile
- expected_strings = ['--jwks_file_path={0}'.format(self.JWKS_JSON_PATH)]
- self.__assert_log_file(self.LOG_DIR_JWT_AUTH_INVALID_JWK,
- "impalad.INFO", expected_strings)
+ expected_string = '--jwks_file_path={0}'.format(self.JWKS_JSON_PATH)
+ self.assert_impalad_log_contains("INFO", expected_string)
# Ensure JWT auth failed by checking impala coordinator logs
- expected_strings = [
- 'Error verifying JWT token',
+ expected_string = (
+ 'Error verifying JWT token'
+ '.*'
'Error verifying JWT Token: Invalid JWK ID in the JWT token'
- ]
- self.__assert_log_file(self.LOG_DIR_JWT_AUTH_INVALID_JWK,
- "impalad.ERROR", expected_strings)
+ )
+ self.assert_impalad_log_contains("ERROR", expected_string,
expected_count=-1)
# Ensure the shell login failed.
assert "Error connecting: HttpError" in result.stderr
assert "HTTP code 401: Unauthorized" in result.stderr
assert "Not connected to Impala, could not execute queries." in
result.stderr
- def __assert_log_file(self, log_dir, log_file, expected_strings):
- """Given a list of strings, searches the specified log file for each of
those
- strings ensuring that at least one instance of each string exists within a
- line of the log file
-
- log_dir - path to the directory where the log file exists
- log_file - name of the file within the specified directory that will be
searched
- expected_strings - list of strings to search for within the log file
- """
-
- counter_dict = {}
- for item in expected_strings:
- counter_dict[item] = 0
-
- log_path = os.path.join(log_dir, log_file)
- with open(log_path) as file:
- for line in file:
- for key in counter_dict:
- if line.find(key) >= 0:
- counter_dict[key] += 1
-
- for line, count in counter_dict.items():
- assert count > 0, "Did not find expected string '{0}' in log file '{1}'"
\
- .format(line, log_path)
-
def __assert_success_fail_metric(self, success_count_min=0,
success_count_max=0,
failure_count_min=0, failure_count_max=0):
"""Impala emits metrics that count the number of successful and failed JWT