Package: maltego-trx
Maltego TRX Python LibraryRelease Notes1.6.1: Update cryptography and Flask dependency and deprecate Python 3.71.6.0: Automatically generate am.mtzfor your local transforms1.5.2: Add logging output for invalid / missing params in xml serialization1.5.1: Add ignored files to starter and use README for pypi1.5.0: XML Serialization viaElementTreeinstead of string interpolation1.4.4: Added skeletons for csv export in template dir and made project.py application import compatible with docs1.4.0 + 1.4.1:Both versions are incompatible with python3.7 and lower.1.4.2: Fixed python3.6 incompatibilityGetting StartedNote: Support for Python 2 has been officially discontinued as of July 2021. Please use Python 3.8 or higher to use
up-to-date versions of Maltego TRX.To install the trx library run the following command:pipinstallmaltego-trxAfter installing, you can create a new project by running the following command:maltego-trxstartnew_projectThis will create a folder new_project with the recommended project structure.If you want to copy the starter files to your current directory, run the following command:maltego-trxinitAlternatively, you can copy either thegunicornorapacheexample projects from thedemodirectory. These also
include Dockerfile and corresponding docker-compose configuration files for production deployment.Adding a Transform:Add a new transform by creating a new python file in the "transforms" folder of your directory.Any file in the folder where theclass name matches the filename, and the class inherits from Transform, will
automatically be discovered and added to your server.A simple transform would look like the following:new_project/transforms/GreetPerson.pyfrommaltego_trx.entitiesimportPhrasefrommaltego_trx.transformimportDiscoverableTransformclassGreetPerson(DiscoverableTransform):"""Returns a phrase greeting a person on the graph."""@classmethoddefcreate_entities(cls,request,response):person_name=request.Valueresponse.addEntity(Phrase,"Hi%s, nice to meet you!"%person_name)Running The Transform ServerFor DevelopmentYou can start the development server, by running the following command:pythonproject.pyrunserverThis will start up a development server that automatically reloads every time the code is changed.For ProductionYou can run a gunicorn transform server, after installing gunicorn on the host machine and then running the command:gunicorn--bind=0.0.0.0:8080--threads=25--workers=2project:applicationFor publicly accessible servers, it is recommended to run your Gunicorn server behind proxy servers such as Nginx.Run a Docker Transform serverThedemofolder provides an example project. The Docker files given can be used to set up and run your project in
Docker.The Dockerfile and docker-compose file can be used to easily set up and run a development transform server.If you have copied thedocker-compose.yml,Dockerfileandprod.ymlfiles into your project, then you can use the
following commands to run the server in Docker.Run the following to start the development server:docker-composeupRun the following command to run a production gunicorn server:docker-compose-fprod.ymlup--buildFor publicly accessible servers, it is recommended to run your Gunicorn server behind proxy servers such as Nginx.Local TransformsDocumentationTransforms written using this library can be used as either local or server transforms.To run a local transform from your project, you will need to pass the following arguments:project.pylocal<transform_name>You can find the correct transform_name to use by runningpython project.py list.CaveatsThe following values are not passed to local transforms, and will have dummy values in their place:type:local.Unknownweight: 100slider: 100transformSettings: {}Using the Transform RegistryAdded in 1.4.0 (July 2021)The Transform Registry enables you to annotate Transforms with metadata like display name, description, input and output
entities as well as settings. The Transform Registry will automatically generate CSV files that you can import into the
pTDS and/or your iTDS.Configuring the RegistryYou can configure your registry with all the info you would normally add for every transform/seed on a TDS. We recommend
creating your registry in an extra file, traditionally calledextensions.py, to avoid circular imports.# extensions.pyfrommaltego_trx.decorator_registryimportTransformRegistryregistry=TransformRegistry(owner="ACME Corporation",author="John Doe <johndoe@acme.com>",host_url="https://transforms.acme.org",seed_ids=["demo"])# The rest of these attributes are optional# metadataregistry.version="0.1"# transform suffix to indicate datasourceregistry.display_name_suffix=" [ACME]"# reference OAuth settingsregistry.oauth_settings_id=['github-oauth']Annotating Transforms# transforms/GreetPerson.py...fromextensionsimportregistry@registry.register_transform(display_name='Greet Person',input_entity='maltego.Phrase',description='Returns a phrase greeting a person on the graph.',output_entities=['maltego.Phrase'],disclaimer='This disclaimer is optional and has to be accepted before this transform is run')classGreetPerson(DiscoverableTransform):@classmethoddefcreate_entities(cls,request,response):...Pro Tip:If thedisplay_nameis eitherNoneor"", the registry will try to create a display name from the
class name:DNSToIP'DNS To IP'GreetPerson'Greet Person'Transform SettingsYou can declare transform settings in a central location and add them to the registry.Configuring Global SettingsThese settings will apply to all transforms which can be very helpful for api keys.# settings.pyfrommaltego_trx.decorator_registryimportTransformSettingapi_key_setting=TransformSetting(name='api_key',display_name='API Key',setting_type='string',global_setting=True)# extensions.pyfromsettingsimportapi_key_settingfrommaltego_trx.decorator_registryimportTransformRegistryregistry=TransformRegistry(owner="ACME Corporation",author="John Doe <johndoe@acme.com>",host_url="https://transforms.acme.org",seed_ids=["demo"])registry.global_settings=[api_key_setting]Configuring Settings per TransformSettings that aren't required for every transform have to be added to theregister_transformdecorator explicitly. To
access the setting on the request, use theidproperty, which will have the global prefix if it's a global setting.
Thenameproperty won't work on global settings.# settings.py...language_setting=TransformSetting(name='language',display_name="Language",setting_type='string',default_value='en',optional=True,popup=True)# transforms/GreetPerson.py...fromsettingsimportlanguage_settingfrommaltego_trx.transformimportDiscoverableTransform@registry.register_transform(display_name="Greet Person",input_entity="maltego.Phrase",description='Returns a phrase greeting a person on the graph.',settings=[language_setting])classGreetPerson(DiscoverableTransform):@classmethoddefcreate_entities(cls,request:MaltegoMsg,response:MaltegoTransform):language=request.getTransformSetting(language_setting.id)...Exporting the TDS ConfigurationTo export the configurations, use the registry methodswrite_transforms_config()andwrite_settings_config(). These
methods have to executed after they have been registered with the TRX server.# project.pyimportsysimporttransformsfrommaltego_trx.registryimportregister_transform_function,register_transform_classesfrommaltego_trx.serverimportapplicationfrommaltego_trx.handlerimporthandle_run# register_transform_function(transform_func)fromextensionsimportregistryregister_transform_classes(transforms)registry.write_transforms_config()registry.write_settings_config()handle_run(__name__,sys.argv,application)Generating an.mtzconfig with your local TransformsSincemaltego-trx>=1.6.0you can generate an.mtzconfig file with your local transforms.If you're already using theTransformRegistry, just invoke thewrite_local_config()method.# project.pyregistry.write_local_mtz()This will create a file calledlocal.mtzin the current directory. You can then import this file into Maltego and
start using your local transforms faster. Just remember that settings are not passed to local transforms.The method takes in the same arguments as the interface in the Maltego client.
If you are using avirtualenvenvironment, you might want to change thecommandargument to use that.# project.pyregistry.write_local_mtz(mtz_path:str="./local.mtz",# path to the local .mtz fileworking_dir:str=".",command:str="python3",# for a venv you might want to use `./venv/bin/python3`params:str="project.py",debug:bool=True)Legacy TransformsDocumentationIf you have old TRX transforms that are written as functions, they can be registered with the server using
themaltego_trx.registry.register_transform_functionmethod.In order to port your old transforms, make two changes:Import the MaltegoTransform class from themaltego_trxpackage instead of from a local file.Call theregister_transform_functionin order for the transform to be registered in your project.For exampleIn the legacy transform file, change:fromMaltegoimport*defold_transform(m):To:frommaltego_trx.maltegoimportMaltegoTransformdefold_transform(m):...In theproject.pyfile add the following:frommaltego_trx.registryimportregister_transform_functionfromlegacy_transformimporttrx_DNS2IPregister_transform_function(trx_DNS2IP)CLIThe following commands can be run using the project.py file.Run Serverpythonproject.pyrunserverStart a development server that you can use to develop new transforms.Listpythonproject.pylistList the available transforms together with their transform server URLs and local transform names.ReferenceConstantsThe following constants can be imported frommaltego_trx.maltego.Message Types:UIM_FATALUIM_PARTIALUIM_INFORMUIM_DEBUGPlease take note:You need to enable thedebugfilter option in the Desktop client Output window to viewdebugtransform messages.Bookmark Colors:BOOKMARK_COLOR_NONEBOOKMARK_COLOR_BLUEBOOKMARK_COLOR_GREENBOOKMARK_COLOR_YELLOWBOOKMARK_COLOR_PURPLEBOOKMARK_COLOR_REDLink Styles:LINK_STYLE_NORMALLINK_STYLE_DASHEDLINK_STYLE_DOTTEDLINK_STYLE_DASHDOTEnumsOverlays:Overlays Enums are imported frommaltego_trx.overlaysOverlay OverlayPosition:NORTH = "N"SOUTH = "S"WEST = "W"NORTH_WEST = "NW"SOUTH_WEST = "SW"CENTER = "C"Overlay TypeIMAGE = "image"COLOUR = "colour"TEXT = "text"Request/MaltegoMsgThe request/maltego msg object given to the transform contains the information about the input entity.Attributes:Value: str: The display value of the input entity on the graphWeight: int: The weight of the input entitySlider: int: Results slider setting in the clientType: str: The input entity typeProperties: dict(str: str): A key-value dictionary of the input entity propertiesTransformSettings: dict(str: str): A key-value dictionary of the transform settingsGenealogy: list(dict(str: str)): A key-value dictionary of the Entity genealogy, this is only applicable for
extended entities e.g. Website EntityMethods:getProperty(name: str): Get a property value of the input entitygetTransformSetting(name: str): Get a transform setting valueclearLegacyProperties(): Delete (duplicate) legacy properties from the input entity. This will not result in
property information being lost, it will simply clear out some properties that the TRX library duplicates on all
incoming Transform requests. In older versions of TRX, these Entity properties would have a different internal ID when
sent the server than what the Maltego client would advertise in the Entity Manager UI. For a list of Entities with
such properties and their corresponding legacy and actual IDs, seeentity_property_mapinmaltego_trx/entities.py.
For the majority of projects this distinction can be safely ignored.Response/MaltegoTransformMethods:addEntity(type: str, value: str) -> Entity: Add an entity to the transform response. Returns an Entity object
created by the method.addUIMessage(message: str, messageType='Inform'): Return a UI message to the user. For message type, use a message
type constant.EntityMethods:setType(type: str): Set the entity type (e.g. "Phrase" for maltego.Phrase entity)setValue(value: str): Set the entity valuesetWeight(weight: int): Set the entity weightaddDisplayInformation(content: str, title: str): Add display information for the entity.addProperty(fieldName: str, displayName: str, matchingRule: str, value: str): Add a property to the entity. Matching
rule can bestrictorloose.addOverlay(propertyName: str, position: OverlayPosition, overlay_type: OverlayType): Add an overlay to the entity.OverlayPositionandOverlayTypeare defined in themaltego_trx.overlaysOverlay can be added as Text, Image or Colorperson_name=request.Valueentity=response.addEntity(Phrase,"Hi%s, nice to meet you!"%person_name)# Normally, when we create an overlay, we would reference a property name so that Maltego can then use the# value of that property to create the overlay. Sometimes that means creating a dynamic property, but usually# it's better to either use an existing property, or, if you created the Entity yourself, and only need the# property for the overlay, to use a hidden property. Here's an example of using a dynamic property:entity.addProperty('dynamic_overlay_icon_name',displayName="Name for overlay image",value="Champion"# references an icon in the Maltego client)entity.addOverlay('dynamic_overlay_icon_name',OverlayPosition.WEST,OverlayType.IMAGE)# DISCOURAGED:# You *can* also directly supply the string value of the property, however this is not recommended. Why? If# the entity already has a property of the same ID (in this case, "DE"), then you would in fact be assigning the# value of that property, not the string "DE", which is not the intention. Nevertheless, here's an example:entity.addOverlay('DE',# name of an icon, however, could also accidentally be a property nameOverlayPosition.SOUTH_WEST,OverlayType.IMAGE)# Overlays can also be used to display extra text on an entity:entity.addProperty("exampleDynamicPropertyName","Example Dynamic Property","loose","Maltego Overlay Testing")entity.addOverlay('exampleDynamicPropertyName',OverlayPosition.NORTH,OverlayType.TEXT)# Or a small color indicator:entity.addOverlay('#45e06f',OverlayPosition.NORTH_WEST,OverlayType.COLOUR)setIconURL(url: str): Set the entity icon URLsetBookmark(bookmark: int): Set bookmark color index (e.g. -1 for BOOKMARK_COLOR_NONE, 3 for BOOKMARK_COLOR_PURPLE)setNote(note: str): Set note contentsetGenealogy(genealogy: dict): Set genealogyLink Methods:setLinkColor(color: str): Set the link color (e.g. hex "#0000FF" for blue)setLinkStyle(style: int): Set the link style index (e.g. 0 for LINK_STYLE_NORMAL, 2 for LINK_STYLE_DOTTED)setLinkThickness(thick: int): Set link thickness (default is 1)setLinkLabel(label: str): Set the label of the linkreverseLink(): Reverse the link directionaddCustomLinkProperty(fieldName=None, displayName=None, value=None): Set a custom property for the link