This is an automated email from the ASF dual-hosted git repository.
amagyar pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git
The following commit(s) were added to refs/heads/trunk by this push:
new a8c5ce2 [AMBARI-23311] Use Ambari CLI to specify which services
should be setup for SSO integration (#765)
a8c5ce2 is described below
commit a8c5ce20444d4cfc88419b84b328ef2f298e3aed
Author: smolnar82 <[email protected]>
AuthorDate: Wed Mar 28 09:47:25 2018 +0200
[AMBARI-23311] Use Ambari CLI to specify which services should be setup for
SSO integration (#765)
* AMBARI-23311. Use Ambari CLI to specify which services should be setup
for SSO integration
* AMBARI-23311. Added Ambari username/pw CLI options for the setup-sso tool
* AMBARI-23311. Saving/fetching 'ambari.sso.manage_services' and add
'Ambari' as a static service to be asked
---
ambari-server/src/main/python/ambari-server.py | 3 +
.../src/main/python/ambari_server/serverUtils.py | 90 +++++++++-
.../src/main/python/ambari_server/setupSecurity.py | 31 +---
.../src/main/python/ambari_server/setupSso.py | 197 ++++++++++++++++++---
ambari-server/src/test/python/TestAmbariServer.py | 14 +-
ambari-server/src/test/python/TestServerUtils.py | 28 ++-
ambari-server/src/test/python/TestSetupSso.py | 185 ++++++++++++++++++-
7 files changed, 482 insertions(+), 66 deletions(-)
diff --git a/ambari-server/src/main/python/ambari-server.py
b/ambari-server/src/main/python/ambari-server.py
index 1715387..2383086 100755
--- a/ambari-server/src/main/python/ambari-server.py
+++ b/ambari-server/src/main/python/ambari-server.py
@@ -574,10 +574,13 @@ def init_ldap_setup_parser_options(parser):
@OsFamilyFuncImpl(OsFamilyImpl.DEFAULT)
def init_setup_sso_options(parser):
parser.add_option('--sso-enabled', default=None, help="Indicates whether to
enable/disable SSO", dest="sso_enabled")
+ parser.add_option('--sso-enabled-services', default=None, help="A comma
separated list of services that are expected to be configured for SSO (you are
allowed to use '*' to indicate ALL services)", dest='sso_enabled_services')
parser.add_option('--sso-provider-url', default=None, help="The URL of SSO
provider; this must be provided when --sso-enabled is set to 'true'",
dest="sso_provider_url")
parser.add_option('--sso-public-cert-file', default=None, help="The path
where the public certificate PEM is located; this must be provided when
--sso-enabled is set to 'true'", dest="sso_public_cert_file")
parser.add_option('--sso-jwt-cookie-name', default=None, help="The name of
the JWT cookie", dest="sso_jwt_cookie_name")
parser.add_option('--sso-jwt-audience-list', default=None, help="A comma
separated list of JWT audience(s)", dest="sso_jwt_audience_list")
+ parser.add_option('--ambari-admin-username', default=None, help="Ambari
Admin username for LDAP setup", dest="ambari_admin_username")
+ parser.add_option('--ambari-admin-password', default=None, help="Ambari
Admin password for LDAP setup", dest="ambari_admin_password")
@OsFamilyFuncImpl(OsFamilyImpl.DEFAULT)
def init_pam_setup_parser_options(parser):
diff --git a/ambari-server/src/main/python/ambari_server/serverUtils.py
b/ambari-server/src/main/python/ambari_server/serverUtils.py
index 4621646..7a0d3e7 100644
--- a/ambari-server/src/main/python/ambari_server/serverUtils.py
+++ b/ambari-server/src/main/python/ambari_server/serverUtils.py
@@ -17,9 +17,13 @@ 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.
'''
-
+import ambari_simplejson as json # simplejson is much faster comparing to
Python 2.6 json module and has the same functions set.
+import base64
import os
+import sys
import time
+import urllib2
+
from ambari_commons.exceptions import FatalException, NonFatalException
from ambari_commons.logging_utils import get_verbose
from ambari_commons.os_family_impl import OsFamilyFuncImpl, OsFamilyImpl
@@ -28,6 +32,8 @@ from ambari_commons.os_utils import run_os_command
from ambari_server.resourceFilesKeeper import ResourceFilesKeeper,
KeeperException
from ambari_server.serverConfiguration import configDefaults, PID_NAME,
get_resources_location, get_stack_location, \
CLIENT_API_PORT, CLIENT_API_PORT_PROPERTY, SSL_API, DEFAULT_SSL_API_PORT,
SSL_API_PORT
+from ambari_server.userInput import get_validated_string_input
+from contextlib import closing
# Ambari server API properties
@@ -133,3 +139,85 @@ def get_ambari_server_api_base(properties):
if api_port_prop is not None:
api_port = api_port_prop
return '{0}://{1}:{2!s}/api/v1/'.format(api_protocol, SERVER_API_HOST,
api_port)
+
+
+def get_ambari_admin_username_password_pair(options):
+ """
+ Returns the Ambari administrator credential.
+ If not supplied via command line options, the user is queried for the
username and password.
+ :param options: the collected command line options
+ :return: the Ambari admin credentials
+ """
+ admin_login = options.ambari_admin_username \
+ if hasattr(options, 'ambari_admin_username') and
options.ambari_admin_username is not None \
+ else get_validated_string_input("Enter Ambari Admin login: ", None, None,
None, False, False)
+ admin_password = options.ambari_admin_password \
+ if hasattr(options, 'ambari_admin_password') and
options.ambari_admin_password is not None \
+ else get_validated_string_input("Enter Ambari Admin password: ", None,
None, None, True, False)
+ return admin_login, admin_password
+
+
+def get_cluster_name(properties, admin_login, admin_password):
+ """
+ Fetches the name of the first cluster (in case there are more)
+ from the response of host:port/api/v1/clusters call
+ """
+ url = get_ambari_server_api_base(properties) + 'clusters'
+ admin_auth = base64.encodestring('%s:%s' % (admin_login,
admin_password)).replace('\n', '')
+ request = urllib2.Request(url)
+ request.add_header('Authorization', 'Basic %s' % admin_auth)
+ request.add_header('X-Requested-By', 'ambari')
+ request.get_method = lambda: 'GET'
+
+ request_in_progress = True
+ cluster_name = None
+ sys.stdout.write('\nFetching cluster name')
+ numOfTries = 0
+ while request_in_progress:
+ numOfTries += 1
+ if (numOfTries == 60):
+ raise FatalException(1, "Could not fetch cluster name within a minute;
giving up!")
+ sys.stdout.write('.')
+ sys.stdout.flush()
+
+ try:
+ with closing(urllib2.urlopen(request)) as response:
+ response_status_code = response.getcode()
+ if response_status_code != 200:
+ request_in_progress = False
+ err = 'Error while fetching cluster name. Http status code - ' +
str(response_status_code)
+ raise FatalException(1, err)
+ else:
+ response_body = json.loads(response.read())
+ items = response_body['items']
+ if len(items) > 0:
+ cluster_name = items[0]['Clusters']['cluster_name']
+ sys.stdout.write('\nFound cluster name:
{0}'.format(cluster_name))
+ if not cluster_name:
+ time.sleep(1)
+ else:
+ request_in_progress = False
+ except Exception as e:
+ request_in_progress = False
+ err = 'Error while fetching cluster name. Error details: %s' % e
+ raise FatalException(1, err)
+
+ return cluster_name
+
+
+def perform_changes_via_rest_api(properties, admin_login, admin_password,
url_postfix, get_method, request_data = None):
+ url = get_ambari_server_api_base(properties) + url_postfix
+ admin_auth = base64.encodestring('%s:%s' % (admin_login,
admin_password)).replace('\n', '')
+ request = urllib2.Request(url)
+ request.add_header('Authorization', 'Basic %s' % admin_auth)
+ request.add_header('X-Requested-By', 'ambari')
+ if request_data is not None:
+ request.add_data(json.dumps(request_data))
+ request.get_method = lambda: get_method
+
+ with closing(urllib2.urlopen(request)) as response:
+ response_status_code = response.getcode()
+ if response_status_code not in (200, 201):
+ err = 'Error while performing changes via Ambari REST API. Http status
code - ' + str(response_status_code)
+ raise FatalException(1, err)
+
diff --git a/ambari-server/src/main/python/ambari_server/setupSecurity.py
b/ambari-server/src/main/python/ambari_server/setupSecurity.py
index f30915b..956468c 100644
--- a/ambari-server/src/main/python/ambari_server/setupSecurity.py
+++ b/ambari-server/src/main/python/ambari_server/setupSecurity.py
@@ -50,7 +50,7 @@ from ambari_server.serverConfiguration import configDefaults,
parse_properties_f
SSL_TRUSTSTORE_PASSWORD_PROPERTY, SSL_TRUSTSTORE_PATH_PROPERTY,
SSL_TRUSTSTORE_TYPE_PROPERTY, \
SSL_API, SSL_API_PORT, DEFAULT_SSL_API_PORT, CLIENT_API_PORT,
JDK_NAME_PROPERTY, JCE_NAME_PROPERTY, JAVA_HOME_PROPERTY, \
get_resources_location, SECURITY_MASTER_KEY_LOCATION, SETUP_OR_UPGRADE_MSG,
CHECK_AMBARI_KRB_JAAS_CONFIGURATION_PROPERTY
-from ambari_server.serverUtils import is_server_runing,
get_ambari_server_api_base
+from ambari_server.serverUtils import is_server_runing,
get_ambari_server_api_base, get_ambari_admin_username_password_pair,
perform_changes_via_rest_api
from ambari_server.setupActions import SETUP_ACTION, LDAP_SETUP_ACTION
from ambari_server.userInput import get_validated_string_input,
get_prompt_default, read_password, get_YN_input, quit_if_has_answer
from ambari_server.serverClassPath import ServerClassPath
@@ -349,6 +349,7 @@ def sync_ldap(options):
err = 'Must specify a sync option (all, existing, users or groups).
Please invoke ambari-server.py --help to print the options.'
raise FatalException(1, err)
+ #TODO: use serverUtils.get_ambari_admin_username_password_pair (requires
changes in ambari-server.py too to modify option names)
admin_login = ldap_sync_options.ldap_sync_admin_name\
if ldap_sync_options.ldap_sync_admin_name is not None and
ldap_sync_options.ldap_sync_admin_name \
else get_validated_string_input(prompt="Enter Ambari Admin login: ",
default=None,
@@ -673,39 +674,17 @@ def init_ldap_properties_list_reqd(properties, options):
]
return ldap_properties
-def get_ambari_admin_username_password_pair(options):
- admin_login = options.ambari_admin_username if options.ambari_admin_username
is not None else get_validated_string_input("Enter Ambari Admin login: ", None,
None, None, False, False)
- admin_password = options.ambari_admin_password if
options.ambari_admin_password is not None else
get_validated_string_input("Enter Ambari Admin password: ", None, None, None,
True, False)
-
- return admin_login, admin_password
-
def update_ldap_configuration(options, properties, ldap_property_value_map):
admin_login, admin_password =
get_ambari_admin_username_password_pair(options)
- url = get_ambari_server_api_base(properties) + SETUP_LDAP_CONFIG_URL
- admin_auth = base64.encodestring('%s:%s' % (admin_login,
admin_password)).replace('\n', '')
- request = urllib2.Request(url)
- request.add_header('Authorization', 'Basic %s' % admin_auth)
- request.add_header('X-Requested-By', 'ambari')
- data = {
+ request_data = {
"Configuration": {
"category": "ldap-configuration",
"properties": {
}
}
}
- data['Configuration']['properties'] = ldap_property_value_map
- request.add_data(json.dumps(data))
- request.get_method = lambda: 'PUT'
-
- try:
- with closing(urllib2.urlopen(request)) as response:
- response_status_code = response.getcode()
- if response_status_code != 200:
- err = 'Error during setup-ldap. Http status code - ' +
str(response_status_code)
- raise FatalException(1, err)
- except Exception as e:
- err = 'Updating LDAP configuration failed. Error details: %s' % e
- raise FatalException(1, err)
+ request_data['Configuration']['properties'] = ldap_property_value_map
+ perform_changes_via_rest_api(properties, admin_login, admin_password,
SETUP_LDAP_CONFIG_URL, 'PUT', request_data)
def setup_ldap(options):
logger.info("Setup LDAP.")
diff --git a/ambari-server/src/main/python/ambari_server/setupSso.py
b/ambari-server/src/main/python/ambari_server/setupSso.py
index 9827761..d065849 100644
--- a/ambari-server/src/main/python/ambari_server/setupSso.py
+++ b/ambari-server/src/main/python/ambari_server/setupSso.py
@@ -17,16 +17,23 @@ 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.
'''
+import ambari_simplejson as json
+import base64
import logging
import re
+import sys
+import time
+import urllib2
from ambari_commons.os_utils import is_root, run_os_command, copy_file,
set_file_permissions, remove_file
from ambari_commons.exceptions import FatalException, NonFatalException
-from ambari_commons.logging_utils import get_silent, print_warning_msg,
print_error_msg
+from ambari_commons.logging_utils import get_silent, print_info_msg
from ambari_server.userInput import get_validated_string_input, get_YN_input,
get_multi_line_input
+from ambari_server.serverUtils import is_server_runing,
get_ambari_server_api_base, get_ambari_admin_username_password_pair,
get_cluster_name, perform_changes_via_rest_api
from ambari_server.setupSecurity import REGEX_HOSTNAME_PORT, REGEX_TRUE_FALSE
from ambari_server.serverConfiguration import get_ambari_properties,
get_value_from_properties, update_properties, \
store_password_file
+from contextlib import closing
logger = logging.getLogger(__name__)
@@ -47,8 +54,16 @@ JWT_PUBLIC_KEY_FILENAME = "jwt-cert.pem"
JWT_PUBLIC_KEY_HEADER = "-----BEGIN CERTIFICATE-----\n"
JWT_PUBLIC_KEY_FOOTER = "\n-----END CERTIFICATE-----\n"
+SSO_MANAGE_SERVICES = "ambari.sso.manage_services"
+SSO_ENABLED_SERVICES = "ambari.sso.enabled_services"
+WILDCARD_FOR_ALL_SERVICES = "*"
+SERVICE_NAME_AMBARI = 'Ambari'
-def validateOptions(options):
+URL_TO_FETCH_SERVICES_ELIGIBLE_FOR_SSO =
"clusters/:CLUSTER_NAME/services?ServiceInfo/sso_integration_supported=true" ##
:CLUSTER_NAME should be replaced
+SETUP_SSO_CONFIG_URL =
'services/AMBARI/components/AMBARI_SERVER/configurations/sso-configuration'
+
+
+def validate_options(options):
errors = []
if options.sso_enabled and not re.search(REGEX_TRUE_FALSE,
options.sso_enabled):
errors.append("--sso-enabled should be to either 'true' or 'false'")
@@ -66,7 +81,7 @@ def validateOptions(options):
raise FatalException(1, error_msg.format(str(errors)))
-def populateSsoProviderUrl(options, properties):
+def populate_sso_provider_url(options, properties):
if not options.sso_provider_url:
provider_url = get_value_from_properties(properties,
JWT_AUTH_PROVIDER_URL, JWT_AUTH_PROVIDER_URL_DEFAULT)
provider_url = get_validated_string_input("Provider URL [URL]
({0}):".format(provider_url), provider_url, REGEX_HOSTNAME_PORT,
@@ -77,7 +92,7 @@ def populateSsoProviderUrl(options, properties):
properties.process_pair(JWT_AUTH_PROVIDER_URL, provider_url)
-def populateSsoPublicCert(options, properties):
+def populate_sso_public_cert(options, properties):
if not options.sso_public_cert_file:
cert_path = properties.get_property(JWT_PUBLIC_KEY)
cert_string = get_multi_line_input("Public Certificate pem
({0})".format('stored' if cert_path else 'empty'))
@@ -93,7 +108,7 @@ def populateSsoPublicCert(options, properties):
properties.process_pair(JWT_PUBLIC_KEY, cert_path)
-def populateJwtCookieName(options, properties):
+def populate_jwt_cookie_name(options, properties):
if not options.sso_jwt_cookie_name:
cookie_name = get_value_from_properties(properties, JWT_COOKIE_NAME,
JWT_COOKIE_NAME_DEFAULT)
cookie_name = get_validated_string_input("JWT Cookie name
({0}):".format(cookie_name), cookie_name, REGEX_ANYTHING,
@@ -104,7 +119,7 @@ def populateJwtCookieName(options, properties):
properties.process_pair(JWT_COOKIE_NAME, cookie_name)
-def populateJwtAudiences(options, properties):
+def populate_jwt_audiences(options, properties):
if not options.sso_jwt_audience_list:
audiences = properties.get_property(JWT_AUDIENCES)
audiences = get_validated_string_input("JWT audiences list
(comma-separated), empty for any ({0}):".format(audiences), audiences,
@@ -113,6 +128,135 @@ def populateJwtAudiences(options, properties):
audiences = options.sso_jwt_audience_list
properties.process_pair(JWT_AUDIENCES, audiences)
+
+def get_eligible_services(properties, admin_login, admin_password,
cluster_name):
+ safe_cluster_name = urllib2.quote(cluster_name)
+ url = get_ambari_server_api_base(properties) +
URL_TO_FETCH_SERVICES_ELIGIBLE_FOR_SSO.replace(":CLUSTER_NAME",
safe_cluster_name)
+ admin_auth = base64.encodestring('%s:%s' % (admin_login,
admin_password)).replace('\n', '')
+ request = urllib2.Request(url)
+ request.add_header('Authorization', 'Basic %s' % admin_auth)
+ request.add_header('X-Requested-By', 'ambari')
+ request.get_method = lambda: 'GET'
+
+ services = [SERVICE_NAME_AMBARI]
+ sys.stdout.write('\nFetching SSO enabled services')
+ numOfTries = 0
+ request_in_progress = True
+ while request_in_progress:
+ numOfTries += 1
+ if (numOfTries == 60):
+ raise FatalException(1, "Could not fetch eligible services within a
minute; giving up!")
+ sys.stdout.write('.')
+ sys.stdout.flush()
+
+ try:
+ with closing(urllib2.urlopen(request)) as response:
+ response_status_code = response.getcode()
+ if response_status_code != 200:
+ request_in_progress = False
+ err = 'Error while fetching eligible services. Http status code - '
+ str(response_status_code)
+ raise FatalException(1, err)
+ else:
+ response_body = json.loads(response.read())
+ items = response_body['items']
+ if len(items) > 0:
+ for item in items:
+ services.append(item['ServiceInfo']['service_name'])
+ if not services:
+ time.sleep(1)
+ else:
+ request_in_progress = False
+ else:
+ request_in_progress = False
+
+ except Exception as e:
+ request_in_progress = False
+ err = 'Error while fetching eligible services. Error details: %s' % e
+ raise FatalException(1, err)
+ if (len(services) == 0):
+ sys.stdout.write('\nThere is no SSO enabled services found\n')
+ else:
+ sys.stdout.write('\nFound SSO enabled services: {0}\n'.format(',
'.join(str(s) for s in services)))
+ return services
+
+def get_services_requires_sso(options, properties, admin_login,
admin_password):
+ if not options.sso_enabled_services:
+ configure_for_all_services = get_YN_input("Use SSO for all services [y/n]
(n): ", False)
+ if configure_for_all_services:
+ services = WILDCARD_FOR_ALL_SERVICES
+ else:
+ cluster_name = get_cluster_name(properties, admin_login, admin_password)
+ eligible_services = get_eligible_services(properties, admin_login,
admin_password, cluster_name)
+ services = ''
+ for service in eligible_services:
+ question = "Use SSO for {0} [y/n] (y): ".format(service)
+ if get_YN_input(question, True):
+ if len(services) > 0:
+ services = services + ", "
+ services = services + service
+ else:
+ services = options.sso_enabled_services
+
+ return services
+
+def get_sso_property_from_db(properties, admin_login, admin_password,
property_name):
+ sso_property = None
+ url = get_ambari_server_api_base(properties) + SETUP_SSO_CONFIG_URL
+ admin_auth = base64.encodestring('%s:%s' % (admin_login,
admin_password)).replace('\n', '')
+ request = urllib2.Request(url)
+ request.add_header('Authorization', 'Basic %s' % admin_auth)
+ request.add_header('X-Requested-By', 'ambari')
+ request.get_method = lambda: 'GET'
+ request_in_progress = True
+
+ sys.stdout.write('\nFetching SSO configuration from DB')
+ numOfTries = 0
+ while request_in_progress:
+ numOfTries += 1
+ if (numOfTries == 60):
+ raise FatalException(1, "Could not fetch SSO configuration within a
minute; giving up!")
+ sys.stdout.write('.')
+ sys.stdout.flush()
+
+ try:
+ with closing(urllib2.urlopen(request)) as response:
+ response_status_code = response.getcode()
+ if response_status_code != 200:
+ request_in_progress = False
+ if response_status_code == 404:
+ ## This means that there is no SSO configuration in the database
yet -> we can not fetch the property (but this is NOT an error)
+ sso_property = None
+ else:
+ err = 'Error while fetching SSO configuration. Http status code -
' + str(response_status_code)
+ raise FatalException(1, err)
+ else:
+ response_body = json.loads(response.read())
+ sso_properties = response_body['Configuration']['properties']
+ sso_property = sso_properties[property_name]
+ if not sso_property:
+ time.sleep(1)
+ else:
+ request_in_progress = False
+ except Exception as e:
+ request_in_progress = False
+ err = 'Error while fetching SSO configuration. Error details: %s' % e
+ raise FatalException(1, err)
+
+ return sso_property
+
+def update_sso_conf(properties, enable_sso, services, admin_login,
admin_password):
+ sso_configuration_properties = {}
+ sso_configuration_properties[SSO_MANAGE_SERVICES] = "true" if enable_sso
else "false"
+ sso_configuration_properties[SSO_ENABLED_SERVICES] = services
+ request_data = {
+ "Configuration": {
+ "category": "sso-configuration",
+ "properties": {
+ }
+ }
+ }
+ request_data['Configuration']['properties'] = sso_configuration_properties
+ perform_changes_via_rest_api(properties, admin_login, admin_password,
SETUP_SSO_CONFIG_URL, 'PUT', request_data)
def setup_sso(options):
@@ -120,33 +264,44 @@ def setup_sso(options):
if not is_root():
raise FatalException(4, 'ambari-server setup-sso should be run with
root-level privileges')
+ server_status, pid = is_server_runing()
+ if not server_status:
+ err = 'Ambari Server is not running.'
+ raise FatalException(1, err)
+
if not get_silent():
- validateOptions(options)
+ validate_options(options)
properties = get_ambari_properties()
- must_setup_params = False
+ admin_login, admin_password =
get_ambari_admin_username_password_pair(options)
+
if not options.sso_enabled:
- sso_enabled = properties.get_property(JWT_AUTH_ENBABLED).lower() in
['true']
+ sso_enabled_from_db = get_sso_property_from_db(properties, admin_login,
admin_password, SSO_MANAGE_SERVICES)
+ sso_enabled = sso_enabled_from_db == None or sso_enabled_from_db in
['true']
+ print_info_msg("SSO is currently {0}".format("not configured" if
sso_enabled_from_db == None else ("enabled" if sso_enabled else "disabled")),
True)
if sso_enabled:
- if get_YN_input("Do you want to disable SSO authentication [y/n]
(n)?", False):
- properties.process_pair(JWT_AUTH_ENBABLED, "false")
+ enable_sso = not get_YN_input("Do you want to disable SSO
authentication [y/n] (n)? ", False)
else:
- if get_YN_input("Do you want to configure SSO authentication [y/n]
(y)?", True):
- properties.process_pair(JWT_AUTH_ENBABLED, "true")
- must_setup_params = True
+ if get_YN_input("Do you want to configure SSO authentication [y/n]
(y)? ", True):
+ enable_sso = True
else:
return False
else:
- properties.process_pair(JWT_AUTH_ENBABLED, options.sso_enabled)
- must_setup_params = options.sso_enabled == 'true'
+ enable_sso = options.sso_enabled == 'true'
+
+ services = ''
+ if enable_sso:
+ populate_sso_provider_url(options, properties)
+ populate_sso_public_cert(options, properties)
+ populate_jwt_cookie_name(options, properties)
+ populate_jwt_audiences(options, properties)
+ services = get_services_requires_sso(options, properties, admin_login,
admin_password)
- if must_setup_params:
- populateSsoProviderUrl(options, properties)
- populateSsoPublicCert(options, properties)
- populateJwtCookieName(options, properties)
- populateJwtAudiences(options, properties)
+ update_sso_conf(properties, enable_sso, services, admin_login,
admin_password)
+ enable_jwt_auth = WILDCARD_FOR_ALL_SERVICES == services or
SERVICE_NAME_AMBARI in services
+ properties.process_pair(JWT_AUTH_ENBABLED, "true" if enable_jwt_auth else
"false")
update_properties(properties)
pass
diff --git a/ambari-server/src/test/python/TestAmbariServer.py
b/ambari-server/src/test/python/TestAmbariServer.py
index b8fb6b7..cbcfff4 100644
--- a/ambari-server/src/test/python/TestAmbariServer.py
+++ b/ambari-server/src/test/python/TestAmbariServer.py
@@ -7332,6 +7332,8 @@ class TestAmbariServer(TestCase):
sys.stdout = out
options = self._create_empty_options_mock()
+ options.ambari_admin_username = 'admin'
+ options.ambari_admin_password = 'admin'
is_server_runing_method.return_value = (True, 0)
search_file_message.return_value = "filepath"
@@ -7362,8 +7364,6 @@ class TestAmbariServer(TestCase):
return 'skip'
if 'URL Port' in args[0]:
return '1'
- if 'Ambari Admin' in args[0]:
- return 'admin'
if args[1] == "true" or args[1] == "false":
return args[1]
else:
@@ -7419,8 +7419,6 @@ class TestAmbariServer(TestCase):
return "valid"
if 'URL Port' in args[0]:
return '1'
- if 'Ambari Admin' in args[0]:
- return 'admin'
if args[1] == "true" or args[1] == "false":
return args[1]
else:
@@ -7502,8 +7500,6 @@ class TestAmbariServer(TestCase):
return 'skip'
if 'URL Port' in args[0]:
return '1'
- if 'Ambari Admin' in args[0]:
- return 'admin'
if 'Primary URL' in args[0]:
return kwargs['answer']
if args[1] == "true" or args[1] == "false":
@@ -7518,6 +7514,8 @@ class TestAmbariServer(TestCase):
urlopen_method.return_value = response
options = self._create_empty_options_mock()
+ options.ambari_admin_username = 'admin'
+ options.ambari_admin_password = 'admin'
options.ldap_url = "a:1"
setup_ldap(options)
@@ -7621,8 +7619,6 @@ class TestAmbariServer(TestCase):
return 'skip'
if 'URL Port' in args[0]:
return '1'
- if 'Ambari Admin' in args[0]:
- return 'admin'
if 'Primary URL' in args[0]:
return kwargs['answer']
if args[1] == "true" or args[1] == "false":
@@ -7637,6 +7633,8 @@ class TestAmbariServer(TestCase):
urlopen_method.return_value = response
options = self._create_empty_options_mock()
+ options.ambari_admin_username = 'admin'
+ options.ambari_admin_password = 'admin'
options.ldap_force_setup = True
setup_ldap(options)
diff --git a/ambari-server/src/test/python/TestServerUtils.py
b/ambari-server/src/test/python/TestServerUtils.py
index 3a5286d..2f982cf 100644
--- a/ambari-server/src/test/python/TestServerUtils.py
+++ b/ambari-server/src/test/python/TestServerUtils.py
@@ -33,7 +33,7 @@ with patch.object(platform, "linux_distribution",
return_value = MagicMock(retur
with patch("os.path.isdir", return_value = MagicMock(return_value=True)):
with patch("os.access", return_value = MagicMock(return_value=True)):
with patch.object(os_utils, "parse_log4j_file",
return_value={'ambari.log.dir': '/var/log/ambari-server'}):
- from ambari_server.serverUtils import get_ambari_server_api_base
+ from ambari_server.serverUtils import get_ambari_server_api_base,
get_ambari_admin_username_password_pair
from ambari_server.serverConfiguration import CLIENT_API_PORT,
CLIENT_API_PORT_PROPERTY, SSL_API, DEFAULT_SSL_API_PORT, SSL_API_PORT
@patch.object(platform, "linux_distribution", new =
MagicMock(return_value=('Redhat', '6.4', 'Final')))
@@ -67,6 +67,32 @@ class TestServerUtils(TestCase):
self.assertEquals(result, 'https://127.0.0.1:8443/api/v1/')
+ def test_get_ambari_admin_credentials_from_cli_options(self):
+ user_name = "admin"
+ password = "s#perS3cr3tP4ssw0d!"
+ options = MagicMock()
+ options.ambari_admin_username = user_name
+ options.ambari_admin_password = password
+ user, pw = get_ambari_admin_username_password_pair(options)
+ self.assertEquals(user, user_name)
+ self.assertEquals(pw, password)
+
+ @patch("ambari_server.serverUtils.get_validated_string_input")
+ def test_get_ambari_admin_credentials_from_user_input(self,
get_validated_string_input_mock):
+ user_name = "admin"
+ password = "s#perS3cr3tP4ssw0d!"
+ options = MagicMock()
+ options.ambari_admin_username = None
+ options.ambari_admin_password = None
+
+ def valid_input_side_effect(*args, **kwargs):
+ return user_name if 'Ambari Admin login' in args[0] else password
+
+ get_validated_string_input_mock.side_effect = valid_input_side_effect
+
+ user, pw = get_ambari_admin_username_password_pair(options)
+ self.assertEquals(user, user_name)
+ self.assertEquals(pw, password)
class FakeProperties(object):
def __init__(self, prop_map):
diff --git a/ambari-server/src/test/python/TestSetupSso.py
b/ambari-server/src/test/python/TestSetupSso.py
index af1ac6f..8e7112c 100644
--- a/ambari-server/src/test/python/TestSetupSso.py
+++ b/ambari-server/src/test/python/TestSetupSso.py
@@ -15,6 +15,7 @@ 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.
'''
+import operator
import os
import platform
import sys
@@ -75,14 +76,35 @@ class TestSetupSso(unittest.TestCase):
pass
+ @patch("ambari_server.setupSso.is_server_runing")
+ @patch("ambari_server.setupSso.is_root")
+ def test_sso_setup_should_fail_if_server_is_not_running(self, is_root_mock,
is_server_runing_mock):
+ out = StringIO.StringIO()
+ sys.stdout = out
+
+ is_root_mock.return_value = True
+ is_server_runing_mock.return_value = (False, 0)
+ options = self._create_empty_options_mock()
+
+ try:
+ setup_sso(options)
+ self.fail("Should fail with non-fatal exception")
+ except FatalException as e:
+ self.assertTrue("Ambari Server is not running" in e.reason)
+ pass
+
+ sys.stdout = sys.__stdout__
+ pass
@patch("ambari_server.setupSso.get_silent")
+ @patch("ambari_server.setupSso.is_server_runing")
@patch("ambari_server.setupSso.is_root")
- def test_silent_mode_is_not_allowed(self, is_root_mock, get_silent_mock):
+ def test_silent_mode_is_not_allowed(self, is_root_mock,
is_server_runing_mock, get_silent_mock):
out = StringIO.StringIO()
sys.stdout = out
is_root_mock.return_value = True
+ is_server_runing_mock.return_value = (True, 0)
get_silent_mock.return_value = True
options = self._create_empty_options_mock()
@@ -99,12 +121,14 @@ class TestSetupSso(unittest.TestCase):
@patch("ambari_server.setupSso.get_silent")
+ @patch("ambari_server.setupSso.is_server_runing")
@patch("ambari_server.setupSso.is_root")
- def test_invalid_sso_enabled_cli_option_should_result_in_error(self,
is_root_mock, get_silent_mock):
+ def test_invalid_sso_enabled_cli_option_should_result_in_error(self,
is_root_mock, is_server_runing_mock, get_silent_mock):
out = StringIO.StringIO()
sys.stdout = out
is_root_mock.return_value = True
+ is_server_runing_mock.return_value = (True, 0)
get_silent_mock.return_value = False
options = self._create_empty_options_mock()
options.sso_enabled = 'not_true_or_false'
@@ -122,12 +146,14 @@ class TestSetupSso(unittest.TestCase):
@patch("ambari_server.setupSso.get_silent")
+ @patch("ambari_server.setupSso.is_server_runing")
@patch("ambari_server.setupSso.is_root")
- def
test_missing_sso_provider_url_cli_option_when_enabling_sso_should_result_in_error(self,
is_root_mock, get_silent_mock):
+ def
test_missing_sso_provider_url_cli_option_when_enabling_sso_should_result_in_error(self,
is_root_mock, is_server_runing_mock, get_silent_mock):
out = StringIO.StringIO()
sys.stdout = out
is_root_mock.return_value = True
+ is_server_runing_mock.return_value = (True, 0)
get_silent_mock.return_value = False
options = self._create_empty_options_mock()
options.sso_enabled = 'true'
@@ -146,12 +172,14 @@ class TestSetupSso(unittest.TestCase):
@patch("ambari_server.setupSso.get_silent")
+ @patch("ambari_server.setupSso.is_server_runing")
@patch("ambari_server.setupSso.is_root")
- def
test_missing_sso_public_cert_file_cli_option_when_enabling_sso_should_result_in_error(self,
is_root_mock, get_silent_mock):
+ def
test_missing_sso_public_cert_file_cli_option_when_enabling_sso_should_result_in_error(self,
is_root_mock, is_server_runing_mock, get_silent_mock):
out = StringIO.StringIO()
sys.stdout = out
is_root_mock.return_value = True
+ is_server_runing_mock.return_value = (True, 0)
get_silent_mock.return_value = False
options = self._create_empty_options_mock()
options.sso_enabled = 'true'
@@ -170,12 +198,14 @@ class TestSetupSso(unittest.TestCase):
@patch("ambari_server.setupSso.get_silent")
+ @patch("ambari_server.setupSso.is_server_runing")
@patch("ambari_server.setupSso.is_root")
- def
test_invalid_sso_provider_url_cli_option_when_enabling_sso_should_result_in_error(self,
is_root_mock, get_silent_mock):
+ def
test_invalid_sso_provider_url_cli_option_when_enabling_sso_should_result_in_error(self,
is_root_mock, is_server_runing_mock, get_silent_mock):
out = StringIO.StringIO()
sys.stdout = out
is_root_mock.return_value = True
+ is_server_runing_mock.return_value = (True, 0)
get_silent_mock.return_value = False
options = self._create_empty_options_mock()
options.sso_enabled = 'true'
@@ -193,21 +223,25 @@ class TestSetupSso(unittest.TestCase):
+ @patch("ambari_server.setupSso.perform_changes_via_rest_api")
@patch("ambari_server.setupSso.update_properties")
@patch("ambari_server.setupSso.get_ambari_properties")
@patch("ambari_server.setupSso.get_silent")
+ @patch("ambari_server.setupSso.is_server_runing")
@patch("ambari_server.setupSso.is_root")
- def test_all_cli_options_are_collected_when_enabling_sso(self, is_root_mock,
get_silent_mock, get_ambari_properties_mock, update_properties_mock):
+ def test_all_cli_options_are_collected_when_enabling_sso(self, is_root_mock,
is_server_runing_mock, get_silent_mock, get_ambari_properties_mock,
update_properties_mock, perform_changes_via_rest_api_mock):
out = StringIO.StringIO()
sys.stdout = out
is_root_mock.return_value = True
+ is_server_runing_mock.return_value = (True, 0)
get_silent_mock.return_value = False
properties = Properties();
get_ambari_properties_mock.return_value = properties
sso_enabled = 'true'
+ sso_enabled_services = 'Ambari, SERVICE1, SERVICE2'
sso_provider_url = 'http://testHost:8080'
sso_public_cert_file = '/test/file/path'
sso_jwt_cookie_name = 'test_cookie'
@@ -218,6 +252,7 @@ class TestSetupSso(unittest.TestCase):
options.sso_public_cert_file = sso_public_cert_file
options.sso_jwt_cookie_name = sso_jwt_cookie_name
options.sso_jwt_audience_list = sso_jwt_audience_list
+ options.sso_enabled_services = sso_enabled_services
setup_sso(options)
@@ -227,21 +262,24 @@ class TestSetupSso(unittest.TestCase):
self.assertEqual(properties.get_property(JWT_PUBLIC_KEY),
sso_public_cert_file)
self.assertEqual(properties.get_property(JWT_COOKIE_NAME),
sso_jwt_cookie_name)
self.assertEqual(properties.get_property(JWT_AUDIENCES),
sso_jwt_audience_list)
+ self.assertTrue(perform_changes_via_rest_api_mock.called)
sys.stdout = sys.__stdout__
pass
-
+ @patch("urllib2.urlopen")
@patch("ambari_server.setupSso.update_properties")
@patch("ambari_server.setupSso.get_ambari_properties")
@patch("ambari_server.setupSso.get_silent")
+ @patch("ambari_server.setupSso.is_server_runing")
@patch("ambari_server.setupSso.is_root")
- def test_only_sso_enabled_cli_option_is_collected_when_disabling_sso(self,
is_root_mock, get_silent_mock, get_ambari_properties_mock,
update_properties_mock):
+ def test_only_sso_enabled_cli_option_is_collected_when_disabling_sso(self,
is_root_mock, is_server_runing_mock, get_silent_mock,
get_ambari_properties_mock, update_properties_mock, urlopen_mock):
out = StringIO.StringIO()
sys.stdout = out
is_root_mock.return_value = True
+ is_server_runing_mock.return_value = (True, 0)
get_silent_mock.return_value = False
properties = Properties();
@@ -259,6 +297,10 @@ class TestSetupSso(unittest.TestCase):
options.sso_jwt_cookie_name = sso_jwt_cookie_name
options.sso_jwt_audience_list = sso_jwt_audience_list
+ response = MagicMock()
+ response.getcode.return_value = 200
+ urlopen_mock.return_value = response
+
setup_sso(options)
self.assertTrue(update_properties_mock.called)
@@ -268,6 +310,130 @@ class TestSetupSso(unittest.TestCase):
self.assertTrue(JWT_COOKIE_NAME not in properties.propertyNames())
self.assertTrue(JWT_AUDIENCES not in properties.propertyNames())
+ sys.stdout = sys.__stdout__
+ pass
+
+
+ @patch("ambari_server.setupSso.perform_changes_via_rest_api")
+ @patch("ambari_server.setupSso.get_YN_input")
+ @patch("ambari_server.setupSso.update_properties")
+ @patch("ambari_server.setupSso.get_ambari_properties")
+ @patch("ambari_server.setupSso.get_silent")
+ @patch("ambari_server.setupSso.is_server_runing")
+ @patch("ambari_server.setupSso.is_root")
+ def test_sso_is_enabled_for_all_services_via_user_input(self, is_root_mock,
is_server_runing_mock, get_silent_mock, get_ambari_properties_mock,
update_properties_mock, get_YN_input_mock,
+
perform_changes_via_rest_api_mock):
+ out = StringIO.StringIO()
+ sys.stdout = out
+
+ is_root_mock.return_value = True
+ is_server_runing_mock.return_value = (True, 0)
+ get_silent_mock.return_value = False
+ get_ambari_properties_mock.return_value = Properties()
+
+ def yn_input_side_effect(*args, **kwargs):
+ if 'all services' in args[0]:
+ return True
+ else:
+ raise Exception("ShouldNotBeInvoked") # only the 'Use SSO for all
services' question should be asked for now
+
+ get_YN_input_mock.side_effect = yn_input_side_effect
+
+ options = self._create_empty_options_mock()
+ options.sso_enabled = 'true'
+ options.sso_provider_url = 'http://testHost:8080'
+ options.sso_public_cert_file = '/test/file/path'
+ options.sso_jwt_cookie_name = 'test_cookie'
+ options.sso_jwt_audience_list = 'test, audience, list'
+
+ setup_sso(options)
+
+ requestCall = perform_changes_via_rest_api_mock.call_args_list[0]
+ args, kwargs = requestCall
+ requestData = args[5]
+ self.assertTrue(isinstance(requestData, dict))
+ ssoProperties = requestData['Configuration']['properties'];
+ properties_updated_in_ambari_db = sorted(ssoProperties.iteritems(),
key=operator.itemgetter(0))
+ properties_should_be_updated_in_ambari_db =
sorted({"ambari.sso.enabled_services": "*", "ambari.sso.manage_services":
"true"}.iteritems(), key=operator.itemgetter(0))
+ self.assertEqual(properties_should_be_updated_in_ambari_db,
properties_updated_in_ambari_db)
+
+ sys.stdout = sys.__stdout__
+ pass
+
+
+ @patch("urllib2.urlopen")
+ @patch("ambari_server.setupSso.perform_changes_via_rest_api")
+ @patch("ambari_server.setupSso.get_cluster_name")
+ @patch("ambari_server.setupSso.get_YN_input")
+ @patch("ambari_server.setupSso.update_properties")
+ @patch("ambari_server.setupSso.get_ambari_properties")
+ @patch("ambari_server.setupSso.get_silent")
+ @patch("ambari_server.setupSso.is_server_runing")
+ @patch("ambari_server.setupSso.is_root")
+ def test_sso_enabled_services_are_collected_via_user_input(self,
is_root_mock, is_server_runing_mock, get_silent_mock,
get_ambari_properties_mock, update_properties_mock, get_YN_input_mock,
+
get_cluster_name_mock, perform_changes_via_rest_api_mock, urlopen_mock):
+ out = StringIO.StringIO()
+ sys.stdout = out
+
+ is_root_mock.return_value = True
+ is_server_runing_mock.return_value = (True, 0)
+ get_silent_mock.return_value = False
+ get_ambari_properties_mock.return_value = Properties()
+ get_cluster_name_mock.return_value = 'cluster1'
+
+ def yn_input_side_effect(*args, **kwargs):
+ if 'all services' in args[0]:
+ return False
+ else:
+ return True
+
+ get_YN_input_mock.side_effect = yn_input_side_effect
+
+ eligible_services = \
+ """
+ {
+ "href":
"http://c7401:8080/api/v1/clusters/cluster1/services?ServiceInfo/sso_integration_supported=true",
+ "items": [
+ {
+ "href":
"http://c7401:8080/api/v1/clusters/cluster1/services/HDFS",
+ "ServiceInfo": {
+ "cluster_name": "cluster1",
+ "service_name": "HDFS"
+ }
+ },
+ {
+ "href":
"http://c7401:8080/api/v1/clusters/cluster1/services/ZOOKEPER",
+ "ServiceInfo": {
+ "cluster_name": "cluster1",
+ "service_name": "ZOOKEPER"
+ }
+ }
+ ]
+ }
+ """
+
+ response = MagicMock()
+ response.getcode.return_value = 200
+ response.read.return_value = eligible_services
+ urlopen_mock.return_value = response
+
+ options = self._create_empty_options_mock()
+ options.sso_enabled = 'true'
+ options.sso_provider_url = 'http://testHost:8080'
+ options.sso_public_cert_file = '/test/file/path'
+ options.sso_jwt_cookie_name = 'test_cookie'
+ options.sso_jwt_audience_list = 'test, audience, list'
+
+ setup_sso(options)
+
+ requestCall = perform_changes_via_rest_api_mock.call_args_list[0]
+ args, kwargs = requestCall
+ requestData = args[5]
+ self.assertTrue(isinstance(requestData, dict))
+ ssoProperties = requestData['Configuration']['properties'];
+ properties_updated_in_ambari_db = sorted(ssoProperties.iteritems(),
key=operator.itemgetter(0))
+ properties_should_be_updated_in_ambari_db =
sorted({"ambari.sso.enabled_services": "Ambari, HDFS, ZOOKEPER",
"ambari.sso.manage_services": "true"}.iteritems(), key=operator.itemgetter(0))
+ self.assertEqual(properties_should_be_updated_in_ambari_db,
properties_updated_in_ambari_db)
sys.stdout = sys.__stdout__
pass
@@ -275,9 +441,10 @@ class TestSetupSso(unittest.TestCase):
def _create_empty_options_mock(self):
options = MagicMock()
options.sso_enabled = None
+ options.sso_enabled_services = None
options.sso_provider_url = None
options.sso_public_cert_file = None
options.sso_jwt_cookie_name = None
- options.sso__jwt_audience_list = None
+ options.sso_jwt_audience_list = None
return options
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
[email protected].