11
.github/config/gitversion.yml
vendored
Normal file
11
.github/config/gitversion.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
next-version: 1.0.0
|
||||
assembly-versioning-scheme: MajorMinorPatch
|
||||
assembly-file-versioning-scheme: MajorMinorPatch
|
||||
|
||||
branches:
|
||||
master:
|
||||
regex: ^master$
|
||||
mode: ContinuousDelivery
|
||||
increment: Patch
|
||||
tag: ''
|
||||
is-release-branch: true
|
||||
26
.github/workflows/pull_request_dev.yml
vendored
Normal file
26
.github/workflows/pull_request_dev.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Chack code on dev pull request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "dev"
|
||||
paths:
|
||||
- "ipsec_exporter.py"
|
||||
- "src/*"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pylint
|
||||
- name: Analysing the code with pylint
|
||||
run: pylint $(git ls-files '*.py')
|
||||
26
.github/workflows/pull_request_master.yml
vendored
Normal file
26
.github/workflows/pull_request_master.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Chack code on master pull request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "master"
|
||||
paths:
|
||||
- "ipsec_exporter.py"
|
||||
- "src/*"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pylint
|
||||
- name: Analysing the code with pylint
|
||||
run: pylint $(git ls-files '*.py')
|
||||
26
.github/workflows/push_dev.yml
vendored
Normal file
26
.github/workflows/push_dev.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Chack code on dev push
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "dev"
|
||||
paths:
|
||||
- "ipsec_exporter.py"
|
||||
- "src/*"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pylint
|
||||
- name: Analysing the code with pylint
|
||||
run: pylint $(git ls-files '*.py')
|
||||
84
.github/workflows/push_master.yml
vendored
Normal file
84
.github/workflows/push_master.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
name: Chack code and publish on master push
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
paths:
|
||||
- "*.py"
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Code check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pylint
|
||||
- name: Analysing the code with pylint
|
||||
run: pylint --exit-zero $(git ls-files '*.py')
|
||||
publish:
|
||||
needs: check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup GitVersion
|
||||
uses: gittools/actions/gitversion/setup@v0.9.7
|
||||
with:
|
||||
versionSpec: 5.x
|
||||
- name: Determine Version
|
||||
uses: gittools/actions/gitversion/execute@v0.9.7
|
||||
id: gitversion
|
||||
with:
|
||||
useConfigFile: true
|
||||
configFilePath: ./.github/config/gitversion.yml
|
||||
- name: Create zip
|
||||
uses: ihiroky/archive-action@v1
|
||||
with:
|
||||
root_dir: ./
|
||||
file_path: ipsec_exporter_${{steps.gitversion.outputs.version}}.zip
|
||||
- name: Create tar.gz
|
||||
uses: ihiroky/archive-action@v1
|
||||
with:
|
||||
root_dir: ./
|
||||
file_path: ipsec_exporter_${{steps.gitversion.outputs.version}}.tar.gz
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{steps.gitversion.outputs.version}}
|
||||
release_name: ${{steps.gitversion.outputs.version}}
|
||||
body_path: ./RELEASE.md
|
||||
draft: false
|
||||
prerelease: false
|
||||
- name: Upload zip archive
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./ipsec_exporter_${{steps.gitversion.outputs.version}}.zip
|
||||
asset_name: ipsec_exporter_${{steps.gitversion.outputs.version}}.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload tar.gz archive
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./ipsec_exporter_${{steps.gitversion.outputs.version}}.tar.gz
|
||||
asset_name: ipsec_exporter_${{steps.gitversion.outputs.version}}.tar.gz
|
||||
asset_content_type: application/gzip
|
||||
|
||||
3
RELEASE.md
Normal file
3
RELEASE.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Changelog
|
||||
|
||||
- Initial version
|
||||
5
ipsec_exporter.py
Normal file
5
ipsec_exporter.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from src.app import App
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = App()
|
||||
app.main()
|
||||
42
src/app.py
Normal file
42
src/app.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from argparse import *
|
||||
from src.prometheus_metrics_server import *
|
||||
from src.metrics_source import *
|
||||
|
||||
class App:
|
||||
args: Namespace
|
||||
|
||||
def __init__(self):
|
||||
parser = ArgumentParser(description="IPsec Prometheus exporter for Libreswan")
|
||||
parser.add_argument("-a", "--address", dest="address", required=False, type=str, default="0.0.0.0", help="Server IP address")
|
||||
parser.add_argument("-p", "--port", dest="port", required=False, type=int, default=9446, help="Server port")
|
||||
parser.add_argument("-i", "--interval", dest="interval", required=False, type=int, default=1, help="Metrics read interval (in seconds)")
|
||||
self.args = parser.parse_args()
|
||||
|
||||
def main(self):
|
||||
server = PrometheusMetricsServer(self.args.port, "IPsec exporter")
|
||||
server.address = self.args.address
|
||||
server.interval = self.args.interval
|
||||
|
||||
globalstatus_source = CommandMetricsSource("sudo ipsec globalstatus")
|
||||
globalstatus_source.add_metric("ipsec_current_states", r"current\.states\.(?P<type>\w+)=(?P<VALUE>\d+)")
|
||||
globalstatus_source.add_metric("ipsec_current_states_iketype", r"current\.states\.iketype\.(?P<type>\w+)=(?P<VALUE>\d+)")
|
||||
globalstatus_source.add_metric("ipsec_current_states_enumerate", r"current\.states\.enumerate\.(?P<type>\w+)=(?P<VALUE>\d+)")
|
||||
globalstatus_source.add_metric("ipsec_total_ipsec_type", r"total\.ipsec\.type\.(?P<type>\w+)=(?P<VALUE>\d+)")
|
||||
globalstatus_source.add_metric("ipsec_total_traffic", r"total\.(?P<type>\w+)\.traffic\.(?P<direction>\w+)=(?P<VALUE>\d+)")
|
||||
globalstatus_source.add_metric("ipsec_total_ike", r"total\.ike\.(?P<version>\w+)\.(?P<status>\w+)=(?P<VALUE>\d+)")
|
||||
globalstatus_source.add_metric("ipsec_total_ikev2_redirect", r"total\.ike\.ikev2\.redirect\.(?P<status>\w+)=(?P<VALUE>\d+)")
|
||||
globalstatus_source.add_metric("ipsec_total_pamauth", r"total\.pamauth\.(?P<status>\w+)=(?P<VALUE>\d+)")
|
||||
globalstatus_source.add_metric("ipsec_total_iketcp", r"total\.iketcp\.(?P<type>\w+)\.(?P<status>\w+)=(?P<VALUE>\d+)")
|
||||
globalstatus_source.add_metric("ipsec_total_ike_encr", r"total\.(?P<version>\w+)\.encr\.(?P<status>\w+)=(?P<VALUE>\d+)")
|
||||
globalstatus_source.add_metric("ipsec_total_ike_integ", r"total\.(?P<version>\w+)\.integ\.(?P<status>\w+)=(?P<VALUE>\d+)")
|
||||
globalstatus_source.add_metric("ipsec_total_ike_group", r"total\.(?P<version>\w+)\.group\.(?P<status>\w+)=(?P<VALUE>\d+)")
|
||||
globalstatus_source.add_metric("ipsec_total_ike_notifies_error", r"total\.(?P<version>\w+)\.(?P<direction>\w+)\.notifies\.error\.(?P<status>\w+)=(?P<VALUE>\d+)")
|
||||
globalstatus_source.add_metric("ipsec_total_ikev2_notifies_status", r"total\.ikev2\.(?P<direction>\w+)\.notifies\.status\.(?P<status>\w+)=(?P<VALUE>\d+)")
|
||||
server.add_metrics_source(globalstatus_source)
|
||||
|
||||
custom_metrics_source = CustomMetricsSource()
|
||||
custom_metrics_source.add_metric(IPsecTrafficCustomMetric("ipsec_traffic"))
|
||||
server.add_metrics_source(custom_metrics_source)
|
||||
|
||||
server.run()
|
||||
|
||||
74
src/metric.py
Normal file
74
src/metric.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from abc import abstractmethod
|
||||
from prometheus_client import *
|
||||
from re import Pattern
|
||||
import re
|
||||
import os
|
||||
|
||||
|
||||
|
||||
class Metric:
|
||||
gauge: Gauge
|
||||
|
||||
def __init__(self, name: str, labels: list[str], description: str = ""):
|
||||
self.gauge = Gauge(
|
||||
name,
|
||||
description,
|
||||
labels
|
||||
)
|
||||
|
||||
|
||||
class CommandMetric(Metric):
|
||||
regex: Pattern
|
||||
|
||||
def __init__(self, name: str, regex: str, description: str = ""):
|
||||
self.regex = re.compile(regex)
|
||||
|
||||
labels = list(self.regex.groupindex.keys())
|
||||
labels.remove("VALUE")
|
||||
|
||||
super().__init__(name, labels, description)
|
||||
|
||||
def update(self, command_output: str):
|
||||
self.gauge.clear()
|
||||
|
||||
results = re.finditer(self.regex, command_output)
|
||||
|
||||
for result in results:
|
||||
groups = result.groupdict()
|
||||
value = groups.pop("VALUE")
|
||||
self.gauge.labels(*list(groups.values())).set(int(value))
|
||||
|
||||
|
||||
class CustomMetric(Metric):
|
||||
def __init__(self, name: str, labels: list[str], description: str = ""):
|
||||
super().__init__(name, labels, description)
|
||||
|
||||
@abstractmethod
|
||||
def update(self):
|
||||
pass
|
||||
|
||||
|
||||
class IPsecTrafficCustomMetric(CustomMetric):
|
||||
def __init__(self, name: str, description: str = ""):
|
||||
labels = [
|
||||
"lease",
|
||||
"connection",
|
||||
"direction"
|
||||
]
|
||||
super().__init__(name, labels, description)
|
||||
|
||||
def update(self):
|
||||
self.gauge.clear()
|
||||
|
||||
trafficstatus = os.popen("sudo ipsec trafficstatus").read()
|
||||
|
||||
trafficstatus_results = re.finditer(r""""(?P<connection>.+)"\[\d+\] \d+\.\d+\.\d+\.\d+, type=\w+, add_time=\d+, inBytes=(?P<IN_VALUE>\d+), outBytes=(?P<OUT_VALUE>\d+), maxBytes=.+, id='.+', lease=(?P<lease>\d+\.\d+\.\d+\.\d+\/\d+)""", trafficstatus)
|
||||
|
||||
for result in trafficstatus_results:
|
||||
lease = result.groupdict()["lease"]
|
||||
connection = result.groupdict()["connection"]
|
||||
in_value = result.groupdict()["IN_VALUE"]
|
||||
out_value = result.groupdict()["OUT_VALUE"]
|
||||
|
||||
self.gauge.labels(lease, connection, "in").set(int(in_value))
|
||||
self.gauge.labels(lease, connection, "out").set(int(out_value))
|
||||
43
src/metrics_source.py
Normal file
43
src/metrics_source.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from src.metric import *
|
||||
import os
|
||||
|
||||
|
||||
|
||||
class MetricsSource:
|
||||
_metrics : list[Metric]
|
||||
|
||||
def __init__(self):
|
||||
self._metrics = []
|
||||
|
||||
@abstractmethod
|
||||
def update(self):
|
||||
pass
|
||||
|
||||
|
||||
class CommandMetricsSource(MetricsSource):
|
||||
command : str
|
||||
|
||||
def __init__(self, command: str):
|
||||
super().__init__()
|
||||
self.command = command
|
||||
|
||||
def add_metric(self, name: str, regex: str, description: str = ""):
|
||||
self._metrics.append(CommandMetric(name, regex, description))
|
||||
|
||||
def update(self):
|
||||
output = os.popen(self.command).read()
|
||||
for metric in self._metrics:
|
||||
metric.update(output)
|
||||
|
||||
|
||||
class CustomMetricsSource(MetricsSource):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def add_metric(self, custom_metric : CustomMetric):
|
||||
self._metrics.append(custom_metric)
|
||||
|
||||
def update(self):
|
||||
for metric in self._metrics:
|
||||
metric.update()
|
||||
32
src/prometheus_metrics_server.py
Normal file
32
src/prometheus_metrics_server.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import prometheus_client
|
||||
import time
|
||||
from src.metrics_source import *
|
||||
|
||||
|
||||
|
||||
class PrometheusMetricsServer:
|
||||
_metrics_sources: list[MetricsSource]
|
||||
|
||||
address: str
|
||||
port: int
|
||||
interval: int
|
||||
server_name: str
|
||||
|
||||
def __init__(self, port: int, server_name: str):
|
||||
self._metrics_sources = []
|
||||
|
||||
self.address = "0.0.0.0"
|
||||
self.port = port
|
||||
self.interval = 1
|
||||
self.server_name = server_name
|
||||
|
||||
def add_metrics_source(self, source: MetricsSource):
|
||||
self._metrics_sources.append(source)
|
||||
|
||||
def run(self):
|
||||
prometheus_client.start_http_server(self.port, addr=self.address)
|
||||
print(f"{self.server_name} is running on {self.address}:{self.port}")
|
||||
while True:
|
||||
for source in self._metrics_sources:
|
||||
source.update()
|
||||
time.sleep(self.interval)
|
||||
Reference in New Issue
Block a user