-
Notifications
You must be signed in to change notification settings - Fork 331
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This source will pull users from Lastpass It uses Human as pivot with other modules (GSuite, GitHub, HiBob ...)
- Loading branch information
Showing
16 changed files
with
458 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import logging | ||
|
||
import neo4j | ||
|
||
import cartography.intel.lastpass.users | ||
from cartography.config import Config | ||
from cartography.util import timeit | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
@timeit | ||
def start_lastpass_ingestion(neo4j_session: neo4j.Session, config: Config) -> None: | ||
""" | ||
If this module is configured, perform ingestion of Lastpass data. Otherwise warn and exit | ||
:param neo4j_session: Neo4J session for database interface | ||
:param config: A cartography.config object | ||
:return: None | ||
""" | ||
|
||
if not config.lastpass_cid or not config.lastpass_provhash: | ||
logger.info( | ||
'Lastpass import is not configured - skipping this module. ' | ||
'See docs to configure.', | ||
) | ||
return | ||
|
||
common_job_parameters = { | ||
"UPDATE_TAG": config.update_tag, | ||
"TENANT_ID": config.lastpass_cid, | ||
} | ||
|
||
cartography.intel.lastpass.users.sync( | ||
neo4j_session, | ||
config.lastpass_provhash, | ||
int(config.lastpass_cid), | ||
config.update_tag, | ||
common_job_parameters, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import logging | ||
from typing import Any | ||
from typing import Dict | ||
from typing import List | ||
|
||
import neo4j | ||
from dateutil import parser as dt_parse | ||
from requests import Session | ||
|
||
from cartography.client.core.tx import load | ||
from cartography.graph.job import GraphJob | ||
from cartography.models.lastpass.tenant import LastpassTenantSchema | ||
from cartography.models.lastpass.user import LastpassUserSchema | ||
from cartography.util import timeit | ||
|
||
logger = logging.getLogger(__name__) | ||
# Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts | ||
_TIMEOUT = (60, 60) | ||
|
||
|
||
@timeit | ||
def sync( | ||
neo4j_session: neo4j.Session, | ||
lastpass_provhash: str, | ||
tenant_id: int, | ||
update_tag: int, | ||
common_job_parameters: Dict[str, Any], | ||
) -> None: | ||
users = get(lastpass_provhash, tenant_id) | ||
formated_users = transform(users) | ||
load_users(neo4j_session, formated_users, tenant_id, update_tag) | ||
cleanup(neo4j_session, common_job_parameters) | ||
|
||
|
||
@timeit | ||
def get(lastpass_provhash: str, tenant_id: int) -> Dict[str, Any]: | ||
payload = { | ||
'cid': tenant_id, | ||
'provhash': lastpass_provhash, | ||
'cmd': 'getuserdata', | ||
'data': None, | ||
} | ||
session = Session() | ||
req = session.post('https://lastpass.com/enterpriseapi.php', data=payload, timeout=_TIMEOUT) | ||
req.raise_for_status() | ||
return req.json() | ||
|
||
|
||
@timeit | ||
def transform(api_result: Dict[str, Any]) -> List[Dict[str, Any]]: | ||
result: List[Dict[str, Any]] = [] | ||
for uid, user in api_result['Users'].items(): | ||
n_user = user.copy() | ||
n_user['id'] = int(uid) | ||
for k in ('created', 'last_pw_change', 'last_login'): | ||
n_user[k] = int(dt_parse.parse(user[k]).timestamp() * 1000) if user[k] else None | ||
result.append(n_user) | ||
return result | ||
|
||
|
||
def load_users( | ||
neo4j_session: neo4j.Session, | ||
data: List[Dict[str, Any]], | ||
tenant_id: int, | ||
update_tag: int, | ||
) -> None: | ||
|
||
load( | ||
neo4j_session, | ||
LastpassTenantSchema(), | ||
[{'id': tenant_id}], | ||
lastupdated=update_tag, | ||
) | ||
|
||
load( | ||
neo4j_session, | ||
LastpassUserSchema(), | ||
data, | ||
lastupdated=update_tag, | ||
TENANT_ID=tenant_id, | ||
) | ||
|
||
|
||
def cleanup(neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]) -> None: | ||
GraphJob.from_node_schema(LastpassUserSchema(), common_job_parameters).run(neo4j_session) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
from dataclasses import dataclass | ||
|
||
from cartography.models.core.common import PropertyRef | ||
from cartography.models.core.nodes import CartographyNodeProperties | ||
from cartography.models.core.nodes import CartographyNodeSchema | ||
|
||
|
||
@dataclass(frozen=True) | ||
class LastpassTenantNodeProperties(CartographyNodeProperties): | ||
id: PropertyRef = PropertyRef('id') | ||
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True) | ||
|
||
|
||
@dataclass(frozen=True) | ||
class LastpassTenantSchema(CartographyNodeSchema): | ||
label: str = 'LastpassTenant' | ||
properties: LastpassTenantNodeProperties = LastpassTenantNodeProperties() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
from dataclasses import dataclass | ||
|
||
from cartography.models.core.common import PropertyRef | ||
from cartography.models.core.nodes import CartographyNodeProperties | ||
from cartography.models.core.nodes import CartographyNodeSchema | ||
from cartography.models.core.relationships import CartographyRelProperties | ||
from cartography.models.core.relationships import CartographyRelSchema | ||
from cartography.models.core.relationships import LinkDirection | ||
from cartography.models.core.relationships import make_target_node_matcher | ||
from cartography.models.core.relationships import OtherRelationships | ||
from cartography.models.core.relationships import TargetNodeMatcher | ||
|
||
|
||
@dataclass(frozen=True) | ||
class LastpassUserNodeProperties(CartographyNodeProperties): | ||
id: PropertyRef = PropertyRef('id') | ||
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True) | ||
name: PropertyRef = PropertyRef('fullname') | ||
email: PropertyRef = PropertyRef('username', extra_index=True) | ||
created: PropertyRef = PropertyRef('created') | ||
last_pw_change: PropertyRef = PropertyRef('last_pw_change') | ||
last_login: PropertyRef = PropertyRef('last_login') | ||
neverloggedin: PropertyRef = PropertyRef('neverloggedin') | ||
disabled: PropertyRef = PropertyRef('disabled') | ||
admin: PropertyRef = PropertyRef('admin') | ||
totalscore: PropertyRef = PropertyRef('totalscore') | ||
mpstrength: PropertyRef = PropertyRef('mpstrength') | ||
sites: PropertyRef = PropertyRef('sites') | ||
notes: PropertyRef = PropertyRef('notes') | ||
formfills: PropertyRef = PropertyRef('formfills') | ||
applications: PropertyRef = PropertyRef('applications') | ||
attachments: PropertyRef = PropertyRef('attachments') | ||
password_reset_required: PropertyRef = PropertyRef('password_reset_required') | ||
multifactor: PropertyRef = PropertyRef('multifactor') | ||
|
||
|
||
@dataclass(frozen=True) | ||
class LastpassUserToHumanRelProperties(CartographyRelProperties): | ||
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True) | ||
|
||
|
||
@dataclass(frozen=True) | ||
# (:LastpassUser)<-[:IDENTITY_LASTPASS]-(:Human) | ||
class LastpassHumanToUserRel(CartographyRelSchema): | ||
target_node_label: str = 'Human' | ||
target_node_matcher: TargetNodeMatcher = make_target_node_matcher( | ||
{'email': PropertyRef('username')}, | ||
) | ||
direction: LinkDirection = LinkDirection.INWARD | ||
rel_label: str = "IDENTITY_LASTPASS" | ||
properties: LastpassUserToHumanRelProperties = LastpassUserToHumanRelProperties() | ||
|
||
|
||
@dataclass(frozen=True) | ||
class LastpassTenantToLastpassUserRelProperties(CartographyRelProperties): | ||
lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True) | ||
|
||
|
||
@dataclass(frozen=True) | ||
# (:LastpassTenant)<-[:RESOURCE]-(:LastpassUser) | ||
class LastpassTenantToUserRel(CartographyRelSchema): | ||
target_node_label: str = 'LastpassTenant' | ||
target_node_matcher: TargetNodeMatcher = make_target_node_matcher( | ||
{'id': PropertyRef('TENANT_ID', set_in_kwargs=True)}, | ||
) | ||
direction: LinkDirection = LinkDirection.OUTWARD | ||
rel_label: str = "RESOURCE" | ||
properties: LastpassTenantToLastpassUserRelProperties = LastpassTenantToLastpassUserRelProperties() | ||
|
||
|
||
@dataclass(frozen=True) | ||
class LastpassUserSchema(CartographyNodeSchema): | ||
label: str = 'LastpassUser' | ||
properties: LastpassUserNodeProperties = LastpassUserNodeProperties() | ||
other_relationships: OtherRelationships = OtherRelationships(rels=[LastpassHumanToUserRel()]) | ||
sub_resource_relationship: LastpassTenantToUserRel = LastpassTenantToUserRel() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
## Lastpass Configuration | ||
|
||
.. _lastpass_config: | ||
|
||
Follow these steps to analyze Lastpass objects with Cartography. | ||
|
||
1. Prepare your Lastpass CID & ProvHash key. | ||
1. Get your CID (account number) and ProvHash from Lastpass [Where can I find the CID and API secret?](https://support.lastpass.com/help/where-can-i-find-the-cid-and-api-secret) | ||
1. Populate an environment variable with the CID and Provhash. You can pass the environment variable name via CLI with the `--lastpass-cid-env-var` and `--lastpass-provhash-env-var` parameter. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
Lastpass | ||
######## | ||
|
||
The lastpass module has the following coverage: | ||
|
||
* Users | ||
|
||
.. toctree:: | ||
:hidden: | ||
:glob: | ||
|
||
* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
## Lastpass Schema | ||
|
||
.. _lastpass_schema: | ||
|
||
|
||
### Human | ||
|
||
Lastpass use Human node as pivot with other Identity Providers (GSuite, GitHub ...) | ||
|
||
Human nodes are not created by Lastpass module, link is made using analysis job. | ||
|
||
|
||
#### Relationships | ||
|
||
- Human as an access to Lastpass | ||
``` | ||
(Human)-[IDENTITY_LASTPASS]->(LastpassUser) | ||
``` | ||
|
||
### LastpassUser | ||
|
||
Representation of a single User in Lastpass | ||
|
||
| Field | Description | | ||
|-------|--------------| | ||
| firstseen| Timestamp of when a sync job first created this node | | ||
| lastupdated | Timestamp of the last time the node was updated | | ||
| id | Lastpass ID | | ||
| name | Full name of the user | | ||
| email | User email | | ||
| created | Timestamp of when the account was created | | ||
| last_pw_change | Timestamp of the last master password change | | ||
| last_login | Timestamp of the last login | | ||
| neverloggedin | Flag indicating the user never logged in | | ||
| disabled | Flag indicating accout is disabled | | ||
| admin | Flag for admin account | | ||
| totalscore | Lastpass security score (max 100) | | ||
| mpstrength | Master password strenght (max 100) | | ||
| sites | Number of site credentials stored | | ||
| notes | Number of secured notes stored | | ||
| formfills | Number of forms stored | | ||
| applications | Number of applications (mobile) stored | | ||
| attachments | Number of file attachments stored | | ||
| password_reset_required | Flag indicating user requested password reset | | ||
| multifactor | MFA method (null if None) | |
Empty file.
Oops, something went wrong.