diff --git a/op-eventer/README.md b/op-eventer/README.md new file mode 100644 index 0000000..e69de29 diff --git a/op-eventer/monitoring/monitoring.yaml b/op-eventer/monitoring/monitoring.yaml new file mode 100644 index 0000000..77d1403 --- /dev/null +++ b/op-eventer/monitoring/monitoring.yaml @@ -0,0 +1,51 @@ +Parameters: + Mainnet: + ContractAddress: "0xbEb5Fc579115071764c7423A4f12eDde41f106Ed" + PauseEvent: "0x62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258" + AuthorizedPauseAddresses: ["0x9ba6e03d8b90de867373db8cf1a58d2f7f006b3a"] + Sepolia: + ContractAddress: "0xbEb5Fc579115071764c7423A4f12eDde41f106Ed" + PauseEvent: "0x62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258" + AuthorizedPauseAddresses: ["0x9ba6e03d8b90de867373db8cf1a58d2f7f006b3a"] + +QueriesTemplate: + TransactionPausedByUnauthorizedAddress: | + WITH log_transactions AS ( + SELECT DISTINCT transaction_hash + FROM `bigquery-public-data.crypto_ethereum.logs` + WHERE + LOWER(address) = LOWER('$ContractAddress') + AND topics[OFFSET(0)] = "$PauseEvent" + ) + SELECT t.hash as MaliciousTX + FROM `bigquery-public-data.crypto_ethereum.transactions` t + INNER JOIN log_transactions l + ON t.hash = l.transaction_hash + WHERE LOWER(t.to_address) NOT IN (SELECT LOWER(addr) FROM UNNEST($AuthorizedPauseAddresses) AS addr) + + TransactionPaused: | + WITH log_transactions AS ( + SELECT DISTINCT transaction_hash + FROM `bigquery-public-data.crypto_ethereum.logs` + WHERE + LOWER(address) = LOWER('$ContractAddress') + AND topics[OFFSET(0)] = "$PauseEvent" + ) + SELECT t.hash as TX + FROM `bigquery-public-data.crypto_ethereum.transactions` t + INNER JOIN log_transactions l + ON t.hash = l.transaction_hash + +Checks: + - Path: consumers/metrics.sh + Queries: + Mainnet-TransactionPausedByUnauthorizedAddress: + Parameters: Mainnet + Query: TransactionPausedByUnauthorizedAddress + Priority: P1 + Source: "test" + Mainnet-TransactionPaused: + Parameters: Mainnet + Query: TransactionPaused + Priority: P5 + Source: "test" diff --git a/op-eventer/requirements.txt b/op-eventer/requirements.txt new file mode 100644 index 0000000..01ed13b --- /dev/null +++ b/op-eventer/requirements.txt @@ -0,0 +1 @@ +google.cloud.bigquery diff --git a/op-eventer/setup/alerts/op-eventer.yaml b/op-eventer/setup/alerts/op-eventer.yaml new file mode 100644 index 0000000..a119f2f --- /dev/null +++ b/op-eventer/setup/alerts/op-eventer.yaml @@ -0,0 +1,43 @@ +groups: + - name: op-eventer + rules: + - alert: OpEventerP1 + expr: increase(opEventer_metric{ priority="P1" }[230m]) > 0 + for: 1s + annotations: + summary: OpEventer Emitted Alert" + labels: + team: security + severity: P1 + - alert: OpEventerP2 + expr: increase(opEventer_metric{ priority="P2" }[230m]) > 0 + for: 1s + annotations: + summary: OpEventer Emitted Alert" + labels: + team: security + severity: P2 + - alert: OpEventerP3 + expr: increase(opEventer_metric{ priority="P3" }[230m]) > 0 + for: 1s + annotations: + summary: OpEventer Emitted Alert" + labels: + team: security + severity: P3 + - alert: OpEventerP4 + expr: increase(opEventer_metric{ priority="P3" }[230m]) > 0 + for: 1s + annotations: + summary: OpEventer Emitted Alert" + labels: + team: security + severity: P4 + - alert: OpEventerP5 + expr: increase(opEventer_metric{ priority="P3" }[230m]) > 0 + for: 1s + annotations: + summary: OpEventer Emitted Alert" + labels: + team: security + severity: P5 diff --git a/op-eventer/setup/alerts/setup_alerts.sh b/op-eventer/setup/alerts/setup_alerts.sh new file mode 100755 index 0000000..7d0e9c8 --- /dev/null +++ b/op-eventer/setup/alerts/setup_alerts.sh @@ -0,0 +1,5 @@ +set -o pipefail -o errexit -o nounset + +source ~/alerts_cred.env +mimirtool rules diff --namespaces=op-eventer ./op-eventer.yaml +mimirtool rules sync --namespaces=op-eventer ./op-eventer.yaml diff --git a/op-eventer/setup/consumers/metrics.sh b/op-eventer/setup/consumers/metrics.sh new file mode 100755 index 0000000..c4906c2 --- /dev/null +++ b/op-eventer/setup/consumers/metrics.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -o pipefail -o errexit -o nounset +source ~/op-eventer.env + +function log { + echo $(date "+%Y-%m-%d %H:%M:%S") $* +} + +function metric { + metric_name=$1 + metric_source=$2 + metric_value=$3 + payload="${metric_name},${metric_source},${metric_value}" + + echo "Sending metric: $payload" + + curl \ + -X POST \ + -u $METRICS_USER \ + -H "Content-Type: text/plain" \ + "$METRICS_URL" \ + -d "$payload" +} +source=$1 +value=$2 +# echo metric "${metric_name}" "source=test" "${value} metric=1" + +metric "opEventer" "source=${source}" "${value}" diff --git a/op-eventer/setup/query_exec.py b/op-eventer/setup/query_exec.py new file mode 100644 index 0000000..1327de3 --- /dev/null +++ b/op-eventer/setup/query_exec.py @@ -0,0 +1,89 @@ +import yaml +from string import Template +import sys +import os +from pprint import pprint +from google.cloud import bigquery +import subprocess +import json +import signal +import time + +home = os.path.expanduser("~") +credentials_path = f"{ + home}/.config/gcloud/application_default_credentials.json" + +# Set credentials environment variable +os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = credentials_path + + +def signal_handler(sig, frame): + print('\nExiting gracefully...') + sys.exit(0) + + +def run_query(query): + client = bigquery.Client() + + query_job = client.query(query) + results = query_job.result() + + return results + + +def send_alert(script_path, source, metric_name, priority, fields): + subprocess.run(f"bash {script_path} '{source}' 'Query={metric_name},Priority={ + priority},{fields} metric=1'", shell=True) + + +def process_queries(config): + + # Get all Parameters + Parameters = config['Parameters'] + # Get all QueryTemplate + QueriesTemplate = config['QueriesTemplate'] + # Process Checks + Checks = config['Checks'] + + processed_queries = [] + for check in Checks: + script_path = check['Path'] + queries = check['Queries'] + for query in queries: + query_name = query + query_parameters_name = queries[query_name]['Parameters'] + query_template_name = queries[query_name]['Query'] + priority = queries[query_name]['Priority'] + source = queries[query_name]['Source'] + + query_parameters = Parameters[query_parameters_name] + query_template = Template(QueriesTemplate[query_template_name]) + parsed_query = query_template.safe_substitute(query_parameters) + processed_queries.append((query_name, parsed_query)) + + results = run_query(parsed_query) + + for row in results: + field_string = " ".join( + f"{field}={getattr(row, field)}" for field in row.keys()) + + print(f"\n{field_string} \n") + send_alert(script_path, source, query_name, + priority, field_string) + + return processed_queries + + +if __name__ == "__main__": + signal.signal(signal.SIGINT, signal_handler) + file_path = sys.argv[1] + + while True: + with open(file_path, 'r') as file: + config = yaml.safe_load(file) + + for name, query in process_queries(config): + print(f"\nProcessing {name} ") + print(query) + + time.sleep(10) diff --git a/op-eventer/setup/run.sh b/op-eventer/setup/run.sh new file mode 100755 index 0000000..2efb878 --- /dev/null +++ b/op-eventer/setup/run.sh @@ -0,0 +1,26 @@ +#!/bin/bash -eo pipefail + +# Get script's absolute path +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +VENV_DIR="$SCRIPT_DIR/../../.venv" +MONITORING_DIR="$SCRIPT_DIR/../monitoring" +query_exec="$SCRIPT_DIR/query_exec.py" + +# Activate virtual environment +source "$VENV_DIR/bin/activate" + +export GOOGLE_CLOUD_PROJECT=oplabs-dev-security +# Check where credentials actually are +CRED_FILE=$(gcloud info --format="get(config.paths.global_config_dir)")/application_default_credentials.json + +if [ ! -f "$CRED_FILE" ]; then + echo "No application default credentials found. Running gcloud auth..." + gcloud auth application-default login +else + if ! GOOGLE_APPLICATION_CREDENTIALS="$CRED_FILE" gcloud auth application-default print-access-token &>/dev/null; then + echo "Invalid credentials. Running gcloud auth..." + gcloud auth application-default login + fi +fi + +python $query_exec $MONITORING_DIR/monitoring.yaml