From code to clicks: My journey from Python to Tines

Written by Jesse Strivelli

Published on December 21, 2023

We recently weighed in on the debate between full-code, low-code and no-code automation. In this post, developer and sales engineer Jesse Strivelli shares his perspective on using the native Tines builder to create automated workflows.

I've been immersed in the world of programming for years, having pursued a computer science degree at university and accumulating extensive experience in the field, primarily with major Fortune 500 companies. Over time, I honed my skills as a Python developer and security engineer. Shortly after I decided to pivot towards becoming a sales engineer, I stumbled upon Tines.

Having secured an interview with Tines, I spent some time using platform. Almost instantly, I was captivated by its user-friendly nature. I couldn't help but notice the remarkable speed at which I could develop automation processes using Tines, far surpassing the pace I could achieve with Python.

In my eagerness to impress the interviewer, I decided to undertake a side-by-side comparison of Tines and Python by crafting a Tines workflow and a corresponding Python code snippet. (It worked - I started my role as Sales Engineer at Tines a few weeks later!)

Let's take a closer look.

The assignment - detecting malicious activity: 

In this scenario, the IT security team of a e-commerce company is responsible for maintaining the security of customer data and the website. The online store processes a substantial volume of customer transactions daily, underscoring the critical importance of their work.

In this example, we're monitoring for malicious behavior by taking a customer's information and enriching it through various threat intelligence sources. If there's a threat, a sys admin must be notified.

Minimum Requirements:

  1. IP Address Generation: Utilize an IP generating API or method to generate a random IP address.

  2. Custom Enrichment: Implement a custom enrichment process that retrieves threat intelligence data from multiple sources, including AbuseIPDB.

  3. Risk Assessment: Develop a risk assessment system that evaluates the risk score associated with the IP address.

  4. Email Notification: Configure the story to send an email notification to the administrator, regardless of the threat severity. This email should contain comprehensive details about the IP address and the threat intelligence data.

Detecting malicious activity: the Tines approach 

Imagine a scenario where the e-commerce company encounters a cyberattack. A large volume of suspicious traffic is detected, and the threat severity is classified as high.

Here's how our enhanced Tines workflow would be beneficial:

  1. SMS Notifications: The Tines story incorporates a responsive SMS notification feature utilizing Twilio's API service. When the system classifies a threat as high severity, it promptly triggers an SMS alert. This SMS is directed to the designated system administrator, whose contact information is securely stored within a dedicated Tines resource. This immediate SMS alert mechanism ensures that critical threats are addressed without delay. The system administrator can swiftly take action, significantly reducing the mean time to response and enhancing the company's ability to mitigate high-impact security incidents.

  2. Data Storage: The Tines story includes a data storage capability that archives historical records of IP addresses, along with their associated threat intelligence data, in an AWS S3 Bucket. This offers several benefits for the e-commerce company:

    • Forensic analysis: Storing historical records allows the security team to conduct in-depth forensic analysis in the aftermath of a security incident. They can trace the origins of threats, analyze attack patterns, and identify potential vulnerabilities in the system. This valuable information aids in understanding the nature of the attack and can guide security improvements.

    • Pattern recognition: Over time, patterns and trends in threat activity may emerge from the stored data. Recognizing these patterns can help the company proactively address emerging threats and enhance its security posture.

    • Compliance and reporting: Many industries and jurisdictions require detailed incident reporting and compliance with specific data retention policies. Storing historical data ensures the company can meet these legal and regulatory requirements efficiently.

Detecting malicious activity: the Python approach 

Additionally, I took the initiative to write the entire story logic in Python. This involved coding the entire process from IP address generation to threat intelligence retrieval and risk assessment.

import requests
import json
import boto3
import yaml
from pathlib import Path

#   Prereq
#       AWS Credentials
#           Validation of SES Email
#           aws_access_key_id = ""
#           aws_secret_access_key = ""
#       Twilio
#         account_sid = ""
#         auth_token = ""
#         account_id = ""
#       AbuseIPDB
#           key = ""

config = yaml.load(Path("config.yml").read_text(), Loader=yaml.Loader)

ses_client = boto3.client(
    'ses',
    aws_access_key_id="",
    aws_secret_access_key="",
)

def send_email(random_ip, abuse_ipdb_response, ip_geolocation_response, country_details_response,
               sunrise_sunset_response):
    message_html = f"""
        <h2>IP Address Inforamtion</h2>

                        <table>
                          <tr>
                            <td>IP Address</td>
                            <td>{random_ip}</td>
                          </tr>
                          <tr>
                            <td>ISP</td>
                            <td>{abuse_ipdb_response['isp']}</td>
                          </tr>
                          <tr>
                            <td>City</td>
                            <td>{ip_geolocation_response['city']}</td>
                          </tr>
                          <tr>
                            <td>Country</td>
                            <td>{ip_geolocation_response['country']}</td>
                          </tr>
                          <tr>
                            <td>Continent</td>
                            <td>{country_details_response[0]['region']}</td>
                          </tr>
                            <tr>
                            <td>Surise</td>
                            <td>{sunrise_sunset_response['results']['sunrise']}</td>
                          </tr>
                            <tr>
                            <td>Sunset</td>
                            <td>{sunrise_sunset_response['results']['sunset']}</td>
                          </tr>
                            <tr>
                            <td>Population</td>
                            <td>{country_details_response[0]['population']}</td>
                          </tr>
                          </tr>
                            <tr>
                            <td>Flag</td>
                            <td>{country_details_response[0]['flag']}</td>
                          </tr>
                        </table>
                        """

    response = ses_client.send_email(
        Source='',
        Destination={
            'ToAddresses': config["email_send"],
        },
        Message={
            'Subject': {
                'Data': 'dry-pines-9865.tines.io',
                'Charset': 'utf-8'
            },
            'Body': {
                'Text': {
                    'Data': message_html,
                    'Charset': 'utf-8'
                },
                'Html': {
                    'Data': message_html,
                    'Charset': 'utf-8'
                }
            }
        }
    )

def ip_address_details(random_ip):
    print("\\nRequest Geo Location...\\n")
    ip_geolocation_request = requests.get(f"<http://ip-api.com/json/{random_ip}>")
    ip_geolocation_response = json.loads(ip_geolocation_request.content.decode('utf-8'))
    print(ip_geolocation_request.content.decode('utf-8'))

    print("\\nRequest Country Details...\\n")
    country_details_request = requests.get(f"<https://restcountries.com/v3.1/name/{ip_geolocation_response['country']}>")
    country_details_response = json.loads(country_details_request.content.decode('utf-8'))
    print(country_details_request.content.decode('utf-8'))

    print("\\nRequest Sunrise/Sunset\\n")
    sunrise_sunset_request = requests.get(
        f"https://api.sunrise-sunset.org/json?lat={ip_geolocation_response['lat']}>&lng={ip_geolocation_response['lon']}")
    sunrise_sunset_response = json.loads(sunrise_sunset_request.content.decode('utf-8'))
    print(sunrise_sunset_request.content.decode('utf-8'))

    return ip_geolocation_response, country_details_response, sunrise_sunset_response

def main():
    print("\\nRequest Random IP...\\n")
    random_request = requests.get(
        "<https://coding-exercise.tines.io/webhook/62c0fc4aff10bfd90e6f1eee7fdb24cc/fba237296c8d74343e62c65126fa0394>")
    random_ip = json.loads(random_request.content.decode('utf-8'))['ip']
    print(random_request.content.decode('utf-8'))

    print("\\nRequest Abuse IPDB...\\n")
    abuse_ipdb_payload = {
        "maxAgeInDays": "90",
        "ipAddress": random_ip,
        "verbose": "true"
    }
    abuse_ipdb_headers = {"key": ""}
    abuse_ipdb_request = requests.get("<https://api.abuseipdb.com/api/v2/check>", params=abuse_ipdb_payload,
                                      headers=abuse_ipdb_headers)
    abuse_ipdb_response = json.loads(abuse_ipdb_request.content.decode('utf-8'))['data']
    print(abuse_ipdb_request.content.decode('utf-8'))

    ip_geolocation_response, country_details_response, sunrise_sunset_response = ip_address_details(random_ip)

    if abuse_ipdb_response['abuseConfidenceScore'] >= 10:
        account_sid = ""
        auth_token = ""
        account_id = ""
        twilio_payload = {
            "Body": f"High Risk IP Address Alert\\n IP Address:{random_ip}  \\nISP: {abuse_ipdb_response['isp']}",
            "From": "",
            "To": config["twilio_send_number"]
        }
        print("\\nSending SMS Alert to Sys Admin...\\n")
        twilio_response = requests.post(
            f'<https://api.twilio.com/2010-04-01/Accounts/{account_id}/Messages.json>',
            data=twilio_payload,
            auth=(account_sid, auth_token),
        )
        print(twilio_response.content.decode('utf-8'))

    send_email(random_ip, abuse_ipdb_response, ip_geolocation_response, country_details_response,
               sunrise_sunset_response)

if __name__ == "__main__":
    main()

This Python code effectively accomplishes all the tasks outlined in the assignment, including the additional enhancements I incorporated.

Throughout the development process, I encountered several challenges and considerations:

  • Email service configuration: Setting up an external email service using AWS SES required a foundational understanding of email service configuration and management. Moreover, I had to prioritize the secure storage of credentials essential for seamless communication with this service.

  • Library integration: The integration of various Python libraries, notably boto3 for AWS interaction, introduced additional complexity to the code. These libraries, while necessary, also brought about increased memory usage and necessitated careful code management.

  • Configuration management: To enhance flexibility and maintainability, I contemplated the option of employing a separate configuration file for managing settings and credentials. This approach allows for greater customization based on specific design preferences.

  • Execution environment selection: While considering various execution environments, such as AWS EC2 or Lambda, I ultimately opted for local execution. This choice was made to facilitate easier testing and troubleshooting during the development phase, ensuring a smoother development experience.

  • Conciseness: The single Python file consists of 162 lines of code, which, although well-organized from a developer's standpoint, may present challenges when attempting to explain each step clearly to individuals without a programming background. This doesn’t even include the configuration file or a readme explaining how to execute the code.

A closer look at the Tines workflow 

The Tines story mirrors the exact process outlined in the Python code. A few noteworthy aspects of this implementation are:

  • Credential management: Tines provided a secure environment to store and reference credentials, ensuring their safety and ease of use throughout the story's execution.

  • Documentation: The story features clear documentation on the left-hand side, outlining the requirements for running it effectively. This documentation enhances the story's usability and clarity.

  • Simplicity: With just 9 actions in total (including 2 within a group), the story presents a streamlined and straightforward process. This simplicity makes it easy to visualize and understand the steps involved.

  • Execution History: Tines enables the visualization of the execution history, allowing for a transparent view of how many times the story has been executed.

Python v Tines: a comparison