Преглед изворни кода

AMBARI-8899. Knox service components should indicate security state (rlevas)

Robert Levas пре 10 година
родитељ
комит
7159bdc40f

+ 64 - 0
ambari-server/src/main/resources/common-services/KNOX/0.5.0.2.2/package/scripts/knox_gateway.py

@@ -18,6 +18,9 @@ limitations under the License.
 """
 """
 
 
 from resource_management import *
 from resource_management import *
+from resource_management.libraries.functions.security_commons import build_expectations, \
+  cached_kinit_executor, validate_security_config_properties, get_params_from_filesystem, \
+  FILE_TYPE_XML
 import sys
 import sys
 
 
 from knox import knox
 from knox import knox
@@ -97,6 +100,67 @@ class KnoxGateway(Script):
             )
             )
     Execute (format("rm -f {ldap_pid_file}"))
     Execute (format("rm -f {ldap_pid_file}"))
 
 
+  def security_status(self, env):
+    import status_params
+
+    env.set_params(status_params)
+
+    if status_params.security_enabled:
+      expectations = {}
+      expectations.update(build_expectations(
+        'krb5JAASLogin',
+        None,
+        ['keytab', 'principal'],
+        None
+      ))
+      expectations.update(build_expectations(
+        'gateway-site',
+        {
+          "gateway.hadoop.kerberos.secured" : "true"
+        },
+        None,
+        None
+      ))
+
+      security_params = {
+        "krb5JAASLogin":
+          {
+            'keytab': status_params.knox_keytab_path,
+            'principal': status_params.knox_principal_name
+          }
+      }
+      security_params.update(get_params_from_filesystem(status_params.knox_conf_dir,
+        {"gateway-site.xml" : FILE_TYPE_XML}))
+
+      result_issues = validate_security_config_properties(security_params, expectations)
+      if not result_issues:  # If all validations passed successfully
+        try:
+          # Double check the dict before calling execute
+          if ( 'krb5JAASLogin' not in security_params
+               or 'keytab' not in security_params['krb5JAASLogin']
+               or 'principal' not in security_params['krb5JAASLogin']):
+            self.put_structured_out({"securityState": "UNSECURED"})
+            self.put_structured_out({"securityIssuesFound": "Keytab file and principal are not set."})
+            return
+
+          cached_kinit_executor(status_params.kinit_path_local,
+                                status_params.knox_user,
+                                security_params['krb5JAASLogin']['keytab'],
+                                security_params['krb5JAASLogin']['principal'],
+                                status_params.hostname,
+                                status_params.temp_dir)
+          self.put_structured_out({"securityState": "SECURED_KERBEROS"})
+        except Exception as e:
+          self.put_structured_out({"securityState": "ERROR"})
+          self.put_structured_out({"securityStateErrorInfo": str(e)})
+      else:
+        issues = []
+        for cf in result_issues:
+          issues.append("Configuration file %s did not pass the validation. Reason: %s" % (cf, result_issues[cf]))
+        self.put_structured_out({"securityIssuesFound": ". ".join(issues)})
+        self.put_structured_out({"securityState": "UNSECURED"})
+    else:
+      self.put_structured_out({"securityState": "UNSECURED"})
 
 
 
 
 if __name__ == "__main__":
 if __name__ == "__main__":

+ 13 - 0
ambari-server/src/main/resources/common-services/KNOX/0.5.0.2.2/package/scripts/status_params.py

@@ -22,6 +22,19 @@ from resource_management import *
 
 
 config = Script.get_config()
 config = Script.get_config()
 
 
+knox_conf_dir = '/etc/knox/conf'
 knox_pid_dir = config['configurations']['knox-env']['knox_pid_dir']
 knox_pid_dir = config['configurations']['knox-env']['knox_pid_dir']
 knox_pid_file = format("{knox_pid_dir}/gateway.pid")
 knox_pid_file = format("{knox_pid_dir}/gateway.pid")
 ldap_pid_file = format("{knox_pid_dir}/ldap.pid")
 ldap_pid_file = format("{knox_pid_dir}/ldap.pid")
+
+security_enabled = config['configurations']['cluster-env']['security_enabled']
+if security_enabled:
+    knox_keytab_path = config['configurations']['knox-env']['knox_keytab_path']
+    knox_principal_name = config['configurations']['knox-env']['knox_principal_name']
+else:
+    knox_keytab_path = None
+    knox_principal_name = None
+hostname = config['hostname'].lower()
+knox_user = default("/configurations/knox-env/knox_user", "knox")
+kinit_path_local = functions.get_kinit_path(["/usr/bin", "/usr/kerberos/bin", "/usr/sbin"])
+temp_dir = Script.get_tmp_dir()

+ 3 - 1
ambari-server/src/main/resources/stacks/HDP/2.2/services/KNOX/kerberos.json

@@ -10,7 +10,9 @@
               "name": "knox_principal",
               "name": "knox_principal",
               "principal": {
               "principal": {
                 "value": "${knox-env/knox_user}/_HOST@${realm}",
                 "value": "${knox-env/knox_user}/_HOST@${realm}",
-                "configuration": "knox-env/knox_principal_name"
+                "configuration": "knox-env/knox_principal_name",
+                "local_username": "${knox-env/knox_user}"
+
               },
               },
               "keytab": {
               "keytab": {
                 "file": "${keytab_dir}/knox.service.keytab",
                 "file": "${keytab_dir}/knox.service.keytab",

+ 104 - 0
ambari-server/src/test/python/stacks/2.2/KNOX/test_knox_gateway.py

@@ -17,7 +17,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 See the License for the specific language governing permissions and
 limitations under the License.
 limitations under the License.
 '''
 '''
+from resource_management import *
 from stacks.utils.RMFTestCase import *
 from stacks.utils.RMFTestCase import *
+from mock.mock import patch
 
 
 class TestKnoxGateway(RMFTestCase):
 class TestKnoxGateway(RMFTestCase):
   COMMON_SERVICES_PACKAGE_DIR = "KNOX/0.5.0.2.2/package"
   COMMON_SERVICES_PACKAGE_DIR = "KNOX/0.5.0.2.2/package"
@@ -93,3 +95,105 @@ class TestKnoxGateway(RMFTestCase):
     self.assertNoMoreResources()
     self.assertNoMoreResources()
 
 
 
 
+  @patch("resource_management.libraries.functions.security_commons.build_expectations")
+  @patch("resource_management.libraries.functions.security_commons.get_params_from_filesystem")
+  @patch("resource_management.libraries.functions.security_commons.validate_security_config_properties")
+  @patch("resource_management.libraries.functions.security_commons.cached_kinit_executor")
+  @patch("resource_management.libraries.script.Script.put_structured_out")
+  def test_security_status(self, put_structured_out_mock, cached_kinit_executor_mock,
+                           validate_security_config_mock, get_params_mock, build_exp_mock):
+    # Test that function works when is called with correct parameters
+
+    security_params = {
+      "krb5JAASLogin":
+        {
+          'keytab': "/path/to/keytab",
+          'principal': "principal"
+        },
+      "gateway-site" : {
+        "gateway.hadoop.kerberos.secured" : "true"
+      }
+    }
+
+    result_issues = []
+
+    get_params_mock.return_value = security_params
+    validate_security_config_mock.return_value = result_issues
+
+    self.executeScript(self.COMMON_SERVICES_PACKAGE_DIR + "/scripts/knox_gateway.py",
+                       classname = "KnoxGateway",
+                       command="security_status",
+                       config_file="secured.json",
+                       hdp_stack_version = self.STACK_VERSION,
+                       target = RMFTestCase.TARGET_COMMON_SERVICES
+    )
+
+    import status_params
+
+    self.assertTrue(build_exp_mock.call_count, 2)
+    build_exp_mock.assert_called_with('gateway-site', {"gateway.hadoop.kerberos.secured": "true"}, None, None)
+    put_structured_out_mock.assert_called_with({"securityState": "SECURED_KERBEROS"})
+    self.assertTrue(cached_kinit_executor_mock.call_count, 1)
+    cached_kinit_executor_mock.assert_called_with(status_params.kinit_path_local,
+                                                  status_params.knox_user,
+                                                  security_params['krb5JAASLogin']['keytab'],
+                                                  security_params['krb5JAASLogin']['principal'],
+                                                  status_params.hostname,
+                                                  status_params.temp_dir)
+
+    # Testing that the exception throw by cached_executor is caught
+    cached_kinit_executor_mock.reset_mock()
+    cached_kinit_executor_mock.side_effect = Exception("Invalid command")
+
+    try:
+      self.executeScript(self.COMMON_SERVICES_PACKAGE_DIR + "/scripts/knox_gateway.py",
+                         classname = "KnoxGateway",
+                         command="security_status",
+                         config_file="secured.json",
+                         hdp_stack_version = self.STACK_VERSION,
+                         target = RMFTestCase.TARGET_COMMON_SERVICES
+      )
+    except:
+      self.assertTrue(True)
+
+    # Testing with a security_params which doesn't contains krb5JAASLogin
+    empty_security_params = {"krb5JAASLogin" : {}}
+    cached_kinit_executor_mock.reset_mock()
+    get_params_mock.reset_mock()
+    put_structured_out_mock.reset_mock()
+    get_params_mock.return_value = empty_security_params
+
+    self.executeScript(self.COMMON_SERVICES_PACKAGE_DIR + "/scripts/knox_gateway.py",
+                       classname = "KnoxGateway",
+                       command="security_status",
+                       config_file="secured.json",
+                       hdp_stack_version = self.STACK_VERSION,
+                       target = RMFTestCase.TARGET_COMMON_SERVICES
+    )
+    put_structured_out_mock.assert_called_with({"securityIssuesFound": "Keytab file and principal are not set."})
+
+    # Testing with not empty result_issues
+    result_issues_with_params = {'krb5JAASLogin': "Something bad happened"}
+    validate_security_config_mock.reset_mock()
+    get_params_mock.reset_mock()
+    validate_security_config_mock.return_value = result_issues_with_params
+    get_params_mock.return_value = security_params
+
+    self.executeScript(self.COMMON_SERVICES_PACKAGE_DIR + "/scripts/knox_gateway.py",
+                       classname = "KnoxGateway",
+                       command="security_status",
+                       config_file="secured.json",
+                       hdp_stack_version = self.STACK_VERSION,
+                       target = RMFTestCase.TARGET_COMMON_SERVICES
+    )
+    put_structured_out_mock.assert_called_with({"securityState": "UNSECURED"})
+
+    # Testing with security_enable = false
+    self.executeScript(self.COMMON_SERVICES_PACKAGE_DIR + "/scripts/knox_gateway.py",
+                       classname = "KnoxGateway",
+                       command="security_status",
+                       config_file="default.json",
+                       hdp_stack_version = self.STACK_VERSION,
+                       target = RMFTestCase.TARGET_COMMON_SERVICES
+    )
+    put_structured_out_mock.assert_called_with({"securityState": "UNSECURED"})

+ 6 - 0
ambari-server/src/test/python/stacks/2.2/configs/secured.json

@@ -58,6 +58,12 @@
         },
         },
         "slider-env": {
         "slider-env": {
             "content": "envproperties\nline2"
             "content": "envproperties\nline2"
+        },
+        "knox-env": {
+            "knox_master_secret": "sa",
+            "knox_group": "knox",
+            "knox_pid_dir": "/var/run/knox",
+            "knox_user": "knox"
         }
         }
     },
     },
     "configuration_attributes": {},
     "configuration_attributes": {},