Exemplo n.º 1
0
    def test_add_ca_to_dir(self, temp_mock, os_mock, open_mock):
        """ Test the add_ca_to_dir functions. """
        os_mock.path.join.side_effect = os.path.join
        os_mock.path.exists.return_value = False
        cert_pem = TESTCERT_AND_KEY[0]
        cert_hash = TESTCERT_HASH
        # Try just writing a single CA and check it gets written to the
        # correct name.
        res = X509Utils.add_ca_to_dir([cert_pem], "/mydir")
        self.assertEqual(res, "/mydir")
        open_mock.assert_has_calls([
            mock.call("/mydir/%s.0" % cert_hash, "w"),
            mock.call("/mydir/%s.signing_policy" % cert_hash, "w")
        ],
                                   any_order=True)
        # Test directory creation
        temp_mock.mkdtemp.return_value = "/tmpca.test"
        res = X509Utils.add_ca_to_dir([cert_pem])
        self.assertEqual(res, "/tmpca.test")
        # Check that duplicate CA causes an exception
        os_mock.path.exists.return_value = True
        self.assertRaises(Exception, X509Utils.add_ca_to_dir, [cert_pem],
                          "/mydir")

        def pol_exists(path):
            return path.endswith('.signing_policy')

        os_mock.path.exists.side_effect = pol_exists
        self.assertRaises(Exception, X509Utils.add_ca_to_dir, [cert_pem],
                          "/mydir")
Exemplo n.º 2
0
 def __check_entry(self, entry, allow_group):
     """ Checks an entry is in a valid format and expands groups.
         If groups are allowed, then inputting a group entry will
         result in the expanded group entries on the output.
         entry - The entry string to check.
         allow_group - Boolean, on whether to allow group names.
         Raises a ValueError if it isn't valid.
         Returns a list of entries.
         Each returned entry is a tuple of (auth_mode, auth_data).
     """
     if entry == "TOKEN":
         return [(ACLManager.AUTH_MODE_TOKEN, None)]
     elif entry == "CERT":
         return [(ACLManager.AUTH_MODE_X509, None)]
     elif entry == "SESSION":
         return [(ACLManager.AUTH_MODE_SESSION, None)]
     elif entry == "ALL":
         return [(ACLManager.AUTH_MODE_ALLOW_ALL, None)]
     elif entry.startswith("CERT:"):
         raw_dn = entry.split(':', 1)[1]
         if not "=" in raw_dn:
             raise ValueError("Bad CERT DN in ACL rule: '%s'" % entry)
         return [(ACLManager.AUTH_MODE_X509, X509Utils.normalise_dn(raw_dn))
                 ]
     elif allow_group and entry.startswith("@"):
         group_name = entry[1:]
         if not group_name in self.__groups:
             raise ValueError("Unrecognised group used in ACL rule: %s" % \
                              group_name)
         return deepcopy(self.__groups[group_name])
     raise ValueError("Invalid auth entry '%s'." % entry)
Exemplo n.º 3
0
 def __gen_req(self,
               path,
               method="GET",
               auth_mode=ACLManager.AUTH_MODE_NONE,
               auth_data=None,
               cert_ok=True,
               token_ok=True):
     """ Call self.__inst.check_request while generating a fake request
         with the given parameters (without using test_mode on the 
         ACLManager).
         Returns True if the request was successful (i.e. access would
         have been allowed).
     """
     app = Flask("ACLManagertest")
     app.secret_key = "TestKey"  # Required for session support
     token_svc = FakeTokenSVC(token_ok)
     try:
         headers = {}
         enable_session = False
         if auth_mode == ACLManager.AUTH_MODE_X509:
             if cert_ok:
                 headers['Ssl-Client-Verify'] = 'SUCCESS'
             else:
                 headers['Ssl-Client-Verify'] = 'FAILED'
             headers['Ssl-Client-S-Dn'] = auth_data
         elif auth_mode == ACLManager.AUTH_MODE_TOKEN:
             headers['X-Token'] = json.dumps(auth_data)
         elif auth_mode == ACLManager.AUTH_MODE_SESSION:
             enable_session = True
         with app.test_request_context(path=path,
                                       method=method,
                                       headers=headers):
             if enable_session:
                 set_session_state(True)
             # Prepare a standard looking request
             current_app.log = self.__log
             current_app.token_svc = token_svc
             request.uuid = "Test-Test-Test"
             # Call the check function
             self.__inst.check_request()
             # Check that request info was correctly propagated
             if auth_mode == ACLManager.AUTH_MODE_X509:
                 norm_dn = X509Utils.normalise_dn(auth_data)
                 self.assertEqual(request.dn, norm_dn)
             elif auth_mode == ACLManager.AUTH_MODE_TOKEN:
                 if token_ok:
                     self.assertEqual(request.token, auth_data)
                     self.assertEqual(request.raw_token,
                                      json.dumps(auth_data))
                     self.assertTrue(request.token_ok)
                 else:
                     self.assertFalse(request.token_ok)
             elif auth_mode == ACLManager.AUTH_MODE_SESSION:
                 self.assertTrue(request.session_ok)
         # Access was allowed (no exception raised)
         return True
     except Forbidden:
         # Access was denied (Forbidden exception thrown)
         return False
Exemplo n.º 4
0
 def test_add_ca_to_dir_template(self, dir_util_mock, temp_mock):
     """ Test that the add_ca_to_dir template function
         works as expected.
     """
     temp_mock.mkdtemp.return_value = "/new/dir"
     res = X509Utils.add_ca_to_dir([], template_dir="/my/template")
     self.assertEqual(res, "/new/dir")
     dir_util_mock.copy_tree.assert_called_with("/my/template",
                                                "/new/dir",
                                                preserve_symlinks=True)
Exemplo n.º 5
0
 def test_rfc_to_openssl(self):
     """ Test X509Utils.rfc_to_openssl. """
     TEST_PAIRS = [
         # Simple Conversion
         ('C = XX, L = YY, OU = ZZ', '/C=XX/L=YY/OU=ZZ'),
         # Input already in correct format
         ('/C=XX/L=YY/OU=ZZ', '/C=XX/L=YY/OU=ZZ'),
     ]
     for test_in, test_out in TEST_PAIRS:
         self.assertEqual(X509Utils.rfc_to_openssl(test_in), test_out)
     self.assertRaises(ValueError, X509Utils.rfc_to_openssl, "")
Exemplo n.º 6
0
 def __get_fake_request_auth(self):
     """ Fills the request object with te test (fake) authentication
         details.
     """
     if self.__test_mode == ACLManager.AUTH_MODE_X509:
         request.dn = X509Utils.normalise_dn(self.__test_data)
     elif self.__test_mode == ACLManager.AUTH_MODE_TOKEN:
         request.token = self.__test_data
         request.raw_token = self.__test_data
         request.token_ok = True
     elif self.__test_mode == ACLManager.AUTH_MODE_SESSION:
         request.session_ok = True
Exemplo n.º 7
0
 def test_x509name(self):
     """ Test the X509_Name import/export functions. """
     from M2Crypto import X509
     # Convert a DN into an object and back
     TEST_DN = 'C=UK, L=London, O=Test Corp., OU=Security, CN=DN Tester'
     x509_obj = X509Utils.str_to_x509name(TEST_DN)
     self.assertIsInstance(x509_obj, X509.X509_Name)
     self.assertEqual(x509_obj.C, 'UK')
     self.assertEqual(x509_obj.L, 'London')
     self.assertEqual(x509_obj.O, 'Test Corp.')
     self.assertEqual(x509_obj.OU, 'Security')
     self.assertEqual(x509_obj.CN, 'DN Tester')
     self.assertEqual(X509Utils.x509name_to_str(x509_obj), TEST_DN)
     # Bonus: Convert a DN with two similar segments
     TEST_DN = 'C=UK, CN=Test User, CN=Proxy'
     x509_obj = X509Utils.str_to_x509name(TEST_DN)
     self.assertIsInstance(x509_obj, X509.X509_Name)
     # Interface currently prevents access to multiple fields
     # i.e. x509_obj.CN will only return "Test User".
     # Just check that all fields appear if object is converted back:
     self.assertEqual(X509Utils.x509name_to_str(x509_obj), TEST_DN)
Exemplo n.º 8
0
 def test_openssl_to_rfc(self):
     """ Test X509Utils.openssl_to_rfc. """
     # Test some DNs in various formats
     TEST_PAIRS = [
         # Simple conversion
         ('/C=XX/L=YY/OU=ZZ', 'C = XX, L = YY, OU = ZZ'),
         # Input already in correct format
         ('C = XX, L = YY, OU = ZZ', 'C = XX, L = YY, OU = ZZ'),
     ]
     for test_in, test_out in TEST_PAIRS:
         self.assertEqual(X509Utils.openssl_to_rfc(test_in), test_out)
     # Test error cases
     self.assertRaises(ValueError, X509Utils.openssl_to_rfc, "")
Exemplo n.º 9
0
 def __check_test_mode(self, auth_mode, auth_data):
     """ Helper function for testing test_mode.
     """
     self.__inst.test_mode(auth_mode, auth_data)
     app = Flask("ACLManagertest")
     with app.test_request_context(path="/test", method="GET"):
         request.uuid = "Test-Test-Test"
         # Call the check function
         self.__inst.check_request()
         # Check that request info was correctly propagated
         if auth_mode == ACLManager.AUTH_MODE_X509:
             norm_dn = X509Utils.normalise_dn(auth_data)
             self.assertEqual(request.dn, norm_dn)
         elif auth_mode == ACLManager.AUTH_MODE_TOKEN:
             self.assertEqual(request.token, auth_data)
             self.assertEqual(request.raw_token, auth_data)
             self.assertTrue(request.token_ok)
         elif auth_mode == ACLManager.AUTH_MODE_SESSION:
             self.assertTrue(request.session_ok)
Exemplo n.º 10
0
 def __get_real_request_auth():
     """ Fills the details of the presented credentials into the
         request object.
     """
     # Cert auth
     if 'Ssl-Client-Verify' in request.headers \
         and 'Ssl-Client-S-Dn' in request.headers:
         # Request has client cert
         if request.headers['Ssl-Client-Verify'] == 'SUCCESS':
             raw_dn = request.headers['Ssl-Client-S-Dn']
             request.dn = X509Utils.normalise_dn(raw_dn)
     # Token Auth
     if 'X-Token' in request.headers:
         raw_token = request.headers['X-Token']
         try:
             token_value = current_app.token_svc.check(raw_token)
             # Check if this looks like a standard token with an expiry value
             if isinstance(token_value, dict):
                 if 'expiry' in token_value:
                     exp_str = token_value['expiry']
                     exp_value = datetime.strptime(exp_str,
                                                   '%Y-%m-%dT%H:%M:%S.%f')
                     if exp_value < datetime.utcnow():
                         # Token has already expired
                         current_app.log.info(
                             "Request %s token has expired (at %s)",
                             request.uuid, exp_str)
                         return "403 Expired Token", 403
             request.token = token_value
             request.raw_token = raw_token
             request.token_ok = True
         except ValueError:
             # Token decoding failed, it is probably corrupt or has been
             # tampered with.
             current_app.log.info("Request %s token validation failed.",
                                  request.uuid)
             return "403 Invalid Token", 403
     if 'logged_in' in session:
         if session['logged_in']:
             request.session_ok = True
Exemplo n.º 11
0
Arquivo: Worker.py Projeto: ic-hep/pdm
def temporary_ca_dir(cas, dir_path=None, template_dir=None):
    """
    Context for creating a temporary CA directory.

    Temporary directory is automatically removed when exiting context.

    Args:
        cas (list): List of CA certs in string form.
        dir_path (str): Path to use for temporary ca directory. If None (default) then a
                        random dir_path is created.
        template_dir (str): Path to a directory to use as a template for the temporary
                            ca dir. All certs in this directory are duplicated in the new one.
                            If None (default) then don't use a template directory.

    Returns:
        str: The temporary ca dir.
    """
    ca_dir = X509Utils.add_ca_to_dir(cas,
                                     dir_path=dir_path,
                                     template_dir=template_dir)
    yield ca_dir
    shutil.rmtree(ca_dir, ignore_errors=True)
Exemplo n.º 12
0
 def test_dn_normalisation(self):
     """ Test the X509 DN normalisation function. """
     # Test DN in expected output format
     TEST_DN = "C=XX, L=YY, CN=Test CA"
     # Test both RFC and OpenSSL style DNs with increasing amounts of space
     # All should match the TEST_DN after normalisation.
     self.assertEqual(X509Utils.normalise_dn(TEST_DN), TEST_DN)
     self.assertEqual(X509Utils.normalise_dn("/C=XX/L=YY/CN=Test CA"),
                      TEST_DN)
     self.assertEqual(
         X509Utils.normalise_dn("/C=XX / L =  YY /  CN = Test CA  "),
         TEST_DN)
     # Check that leading space doesn't upset the algorithm
     self.assertEqual(
         X509Utils.normalise_dn("   / C =XX / L =  YY /  CN = Test CA  "),
         TEST_DN)
     self.assertEqual(
         X509Utils.normalise_dn("C = XX, L = YY, CN = Test CA"), TEST_DN)
     self.assertEqual(
         X509Utils.normalise_dn("C  =  XX,   L  =  YY,    CN = Test CA   "),
         TEST_DN)
     self.assertEqual(
         X509Utils.normalise_dn(
             "     C  =  XX,   L  =  YY,    CN = Test CA"), TEST_DN)
Exemplo n.º 13
0
 def logon(myproxy_server,
           username,
           password,
           ca_certs=None,
           voms=None,
           hours=12,
           myproxy_bin=None,
           vomses=None,
           log=None):
     """ Runs the myproxy-logon command with the various parameters.
         myproxy_server - Server to contact in hostname:port format.
         username - Username to use a remote site.
         password - Password to use at remote site.
         ca_certs - Either None to use the system CA,
                    A string to use as the path to a CA dir,
                    Or a list of strings containing individual PEM files
                    to use as the CA(s).
         voms - An optional VO name to request a VOMS extension for.
         hours - Number of hours to request as the lifetime of the new
                 credential.
         myproxy_bin - Location of the myproxy-logon executable to use,
                       if unset, $PATH will be searched instead.
         vomses - Location of the vomses directory to use if issuing a VOMS
                  proxy. Inherited from parent process otherwise.
         log - Optional logger object to write debug information to.
         Returns a string with the new credential PEM. Raises a
         RuntimeError exception if anything goes wrong.
     """
     with tempfile.NamedTemporaryFile() as proxy:
         hostname, port = myproxy_server.split(':', 1)
         myproxy_opts = [
             'myproxy-logon',  # Exectuable name
             '-s',
             hostname,  # MyProxy server name
             '-p',
             '%s' % port,  # MyProxy port number
             '-l',
             username,  # Username at remote site
             '-t',
             '%u' % hours,  # Lifetime in hours
             '-o',
             proxy.name,  # Proxy on stdout
             '-q',  # Quiet (output only on error)
             '-S',  # Password on stdin
         ]
         if myproxy_bin:
             myproxy_opts[0] = myproxy_bin
         if voms:
             myproxy_opts.extend(['-m', voms])
         env = copy.deepcopy(os.environ)
         ca_dir = None
         if ca_certs:
             if isinstance(ca_certs, str):
                 # CA certs is a path to a cert dir
                 env["X509_CERT_DIR"] = ca_certs
             else:
                 # ca_certs is a list of PEM strings
                 ca_dir = X509Utils.add_ca_to_dir(ca_certs, None)
                 env["X509_CERT_DIR"] = ca_dir
         if vomses:
             env["VOMS_USERCONF"] = vomses
         # Actually run the command
         if log:
             log.debug("Running myproxy-logon with: %s",
                       " ".join(myproxy_opts))
             log.debug("  myproxy-logon env: %s", str(env))
         proc = Popen(myproxy_opts,
                      shell=False,
                      stdin=PIPE,
                      stdout=PIPE,
                      stderr=PIPE,
                      env=env)
         try:
             stdout, stderr = proc.communicate('%s\n' % password)
         except Exception as err:
             if log:
                 log.warn("myproxy-logon command failed: %s", str(err))
             raise RuntimeError("Logon error: Failed to run myproxy-logon")
         finally:
             # Make sure we tidy up the CA dir if we created one
             if ca_dir:
                 shutil.rmtree(ca_dir, ignore_errors=True)
         # Check the return code
         if proc.returncode != 0:
             # Command failed, attempt to infer the reason
             error_str = "Unknown myproxy failure"
             if "invalid password" in stderr:
                 error_str = "Incorrect password"
             elif "Unable to connect to" in stderr:
                 error_str = "Connection error"
             elif "No credentials exist for username" in stderr:
                 error_str = "Unrecognised user"
             elif "Error in service module" in stderr:
                 error_str = "Unrecognised user/config error"
             if log:
                 log.warn("myproxy-logon command failed with code %u (%s)",
                          proc.returncode, error_str)
                 log.debug("myproxy-logon stderr: %s", stderr)
             raise RuntimeError("Logon error: %s" % error_str)
         # Re-open the file to avoid any buffering
         with open(proxy.name, "r") as proxy_fd:
             proxy_str = proxy_fd.read().strip()
         return proxy_str  # Proxy is just a string on stdout
Exemplo n.º 14
0
 def logon_session(site_id):
     """ Create a session for the current user at a given site. """
     log = current_app.log
     db = request.db
     Site = db.tables.Site
     Cred = db.tables.Cred
     user_id = SiteService.get_current_uid()
     # Decode POST data
     if not request.data:
         log.warn("Missing post data for logon.")
         return "Missing POST data", 400
     cred_data = json.loads(request.data)
     username = cred_data.get("username", None)
     password = cred_data.get("password", None)
     lifetime = cred_data.get("lifetime", None)
     vo_name = cred_data.get("vo", None)
     if not username or not password or not lifetime:
         log.warn("Missing post field in logon.")
         return "Required field missing", 400
     # Check user can see the site
     site = Site.query.filter_by(site_id=site_id).first_or_404()
     is_owner = (site.site_owner == user_id)
     if not (is_owner or site.public):
         log.warn("User %u tried to login to site %u (access denied).",
                  user_id, site_id)
         abort(404)  # This user can't see the requested site
     # Check the site auth configuration
     if site.auth_type == 1:
         # VOMS login
         if not vo_name:
             log.warn(
                 "User %u did not specify required VO name for site %u",
                 user_id, site_id)
             return "VO required", 400
         if not vo_name in current_app.vo_list:
             log.warn(
                 "User %u requested unknown VO '%s' for login to site %u.",
                 user_id, vo_name, site_id)
             return "Unknown VO name", 400
     # Process the different possible CA info combinations
     ca_info = None
     if site.user_ca_cert or site.service_ca_cert:
         ca_info = []
         if site.user_ca_cert:
             ca_info.append(site.user_ca_cert)
         if site.service_ca_cert:
             ca_info.append(site.service_ca_cert)
     elif current_app.cadir:
         ca_info = current_app.cadir
     # Actually run the myproxy command
     try:
         proxy = MyProxyUtils.logon(site.auth_uri,
                                    username,
                                    password,
                                    ca_info,
                                    vo_name,
                                    lifetime,
                                    myproxy_bin=current_app.myproxy_bin,
                                    vomses=current_app.vomses,
                                    log=log)
     except Exception as err:
         log.error("Failed to login user: %s", str(err))
         return "Login failed: %s" % str(err), 400
     # Clear the TZInfo as it should be UTC anyway and the database
     # uses naive date-time formats.
     cred_expiry = X509Utils.get_cert_expiry(proxy).replace(tzinfo=None)
     new_cred = Cred(cred_owner=user_id,
                     site_id=site_id,
                     cred_username=username,
                     cred_expiry=cred_expiry,
                     cred_value=proxy)
     with managed_session(request,
                          message="Database error while storing proxy",
                          http_error_code=500) as session:
         session.merge(new_cred)
     return ""