Skip to content

Commit d1dd38b

Browse files
Merge pull request #1 from mateuszskoczek/dev
Dev
2 parents 00c7875 + 465b393 commit d1dd38b

11 files changed

+372
-0
lines changed

.github/config/gitversion.yml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
next-version: 1.0.0
2+
assembly-versioning-scheme: MajorMinorPatch
3+
assembly-file-versioning-scheme: MajorMinorPatch
4+
5+
branches:
6+
master:
7+
regex: ^master$
8+
mode: ContinuousDelivery
9+
increment: Patch
10+
tag: ''
11+
is-release-branch: true
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Chack code on dev pull request
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- "dev"
7+
paths:
8+
- "ipsec_exporter.py"
9+
- "src/*"
10+
11+
jobs:
12+
build:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v3
17+
- name: Set up Python
18+
uses: actions/setup-python@v3
19+
with:
20+
python-version: "3.10"
21+
- name: Install dependencies
22+
run: |
23+
python -m pip install --upgrade pip
24+
pip install pylint
25+
- name: Analysing the code with pylint
26+
run: pylint $(git ls-files '*.py')
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Chack code on master pull request
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- "master"
7+
paths:
8+
- "ipsec_exporter.py"
9+
- "src/*"
10+
11+
jobs:
12+
build:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v3
17+
- name: Set up Python
18+
uses: actions/setup-python@v3
19+
with:
20+
python-version: "3.10"
21+
- name: Install dependencies
22+
run: |
23+
python -m pip install --upgrade pip
24+
pip install pylint
25+
- name: Analysing the code with pylint
26+
run: pylint $(git ls-files '*.py')

.github/workflows/push_dev.yml

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Chack code on dev push
2+
3+
on:
4+
push:
5+
branches:
6+
- "dev"
7+
paths:
8+
- "ipsec_exporter.py"
9+
- "src/*"
10+
11+
jobs:
12+
build:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v3
17+
- name: Set up Python
18+
uses: actions/setup-python@v3
19+
with:
20+
python-version: "3.10"
21+
- name: Install dependencies
22+
run: |
23+
python -m pip install --upgrade pip
24+
pip install pylint
25+
- name: Analysing the code with pylint
26+
run: pylint $(git ls-files '*.py')

.github/workflows/push_master.yml

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: Chack code and publish on master push
2+
3+
on:
4+
push:
5+
branches:
6+
- "master"
7+
paths:
8+
- "*.py"
9+
10+
jobs:
11+
check:
12+
name: Code check
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v3
17+
- name: Set up Python
18+
uses: actions/setup-python@v3
19+
with:
20+
python-version: "3.10"
21+
- name: Install dependencies
22+
run: |
23+
python -m pip install --upgrade pip
24+
pip install pylint
25+
- name: Analysing the code with pylint
26+
run: pylint --exit-zero $(git ls-files '*.py')
27+
publish:
28+
needs: check
29+
runs-on: ubuntu-latest
30+
steps:
31+
- name: Checkout
32+
uses: actions/checkout@v3
33+
with:
34+
fetch-depth: 0
35+
- name: Setup GitVersion
36+
uses: gittools/actions/gitversion/[email protected]
37+
with:
38+
versionSpec: 5.x
39+
- name: Determine Version
40+
uses: gittools/actions/gitversion/[email protected]
41+
id: gitversion
42+
with:
43+
useConfigFile: true
44+
configFilePath: ./.github/config/gitversion.yml
45+
- name: Create zip
46+
uses: ihiroky/archive-action@v1
47+
with:
48+
root_dir: ./
49+
file_path: ipsec_exporter_${{steps.gitversion.outputs.version}}.zip
50+
- name: Create tar.gz
51+
uses: ihiroky/archive-action@v1
52+
with:
53+
root_dir: ./
54+
file_path: ipsec_exporter_${{steps.gitversion.outputs.version}}.tar.gz
55+
- name: Create Release
56+
id: create_release
57+
uses: actions/create-release@v1
58+
env:
59+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
60+
with:
61+
tag_name: ${{steps.gitversion.outputs.version}}
62+
release_name: ${{steps.gitversion.outputs.version}}
63+
body_path: ./RELEASE.md
64+
draft: false
65+
prerelease: false
66+
- name: Upload zip archive
67+
uses: actions/upload-release-asset@v1
68+
env:
69+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
70+
with:
71+
upload_url: ${{ steps.create_release.outputs.upload_url }}
72+
asset_path: ./ipsec_exporter_${{steps.gitversion.outputs.version}}.zip
73+
asset_name: ipsec_exporter_${{steps.gitversion.outputs.version}}.zip
74+
asset_content_type: application/zip
75+
- name: Upload tar.gz archive
76+
uses: actions/upload-release-asset@v1
77+
env:
78+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
79+
with:
80+
upload_url: ${{ steps.create_release.outputs.upload_url }}
81+
asset_path: ./ipsec_exporter_${{steps.gitversion.outputs.version}}.tar.gz
82+
asset_name: ipsec_exporter_${{steps.gitversion.outputs.version}}.tar.gz
83+
asset_content_type: application/gzip
84+

RELEASE.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Changelog
2+
3+
- Initial version

ipsec_exporter.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from src.app import App
2+
3+
if __name__ == "__main__":
4+
app = App()
5+
app.main()

src/app.py

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from argparse import *
2+
from src.prometheus_metrics_server import *
3+
from src.metrics_source import *
4+
5+
class App:
6+
args: Namespace
7+
8+
def __init__(self):
9+
parser = ArgumentParser(description="IPsec Prometheus exporter for Libreswan")
10+
parser.add_argument("-a", "--address", dest="address", required=False, type=str, default="0.0.0.0", help="Server IP address")
11+
parser.add_argument("-p", "--port", dest="port", required=False, type=int, default=9446, help="Server port")
12+
parser.add_argument("-i", "--interval", dest="interval", required=False, type=int, default=1, help="Metrics read interval (in seconds)")
13+
self.args = parser.parse_args()
14+
15+
def main(self):
16+
server = PrometheusMetricsServer(self.args.port, "IPsec exporter")
17+
server.address = self.args.address
18+
server.interval = self.args.interval
19+
20+
globalstatus_source = CommandMetricsSource("sudo ipsec globalstatus")
21+
globalstatus_source.add_metric("ipsec_current_states", r"current\.states\.(?P<type>\w+)=(?P<VALUE>\d+)")
22+
globalstatus_source.add_metric("ipsec_current_states_iketype", r"current\.states\.iketype\.(?P<type>\w+)=(?P<VALUE>\d+)")
23+
globalstatus_source.add_metric("ipsec_current_states_enumerate", r"current\.states\.enumerate\.(?P<type>\w+)=(?P<VALUE>\d+)")
24+
globalstatus_source.add_metric("ipsec_total_ipsec_type", r"total\.ipsec\.type\.(?P<type>\w+)=(?P<VALUE>\d+)")
25+
globalstatus_source.add_metric("ipsec_total_traffic", r"total\.(?P<type>\w+)\.traffic\.(?P<direction>\w+)=(?P<VALUE>\d+)")
26+
globalstatus_source.add_metric("ipsec_total_ike", r"total\.ike\.(?P<version>\w+)\.(?P<status>\w+)=(?P<VALUE>\d+)")
27+
globalstatus_source.add_metric("ipsec_total_ikev2_redirect", r"total\.ike\.ikev2\.redirect\.(?P<status>\w+)=(?P<VALUE>\d+)")
28+
globalstatus_source.add_metric("ipsec_total_pamauth", r"total\.pamauth\.(?P<status>\w+)=(?P<VALUE>\d+)")
29+
globalstatus_source.add_metric("ipsec_total_iketcp", r"total\.iketcp\.(?P<type>\w+)\.(?P<status>\w+)=(?P<VALUE>\d+)")
30+
globalstatus_source.add_metric("ipsec_total_ike_encr", r"total\.(?P<version>\w+)\.encr\.(?P<status>\w+)=(?P<VALUE>\d+)")
31+
globalstatus_source.add_metric("ipsec_total_ike_integ", r"total\.(?P<version>\w+)\.integ\.(?P<status>\w+)=(?P<VALUE>\d+)")
32+
globalstatus_source.add_metric("ipsec_total_ike_group", r"total\.(?P<version>\w+)\.group\.(?P<status>\w+)=(?P<VALUE>\d+)")
33+
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+)")
34+
globalstatus_source.add_metric("ipsec_total_ikev2_notifies_status", r"total\.ikev2\.(?P<direction>\w+)\.notifies\.status\.(?P<status>\w+)=(?P<VALUE>\d+)")
35+
server.add_metrics_source(globalstatus_source)
36+
37+
custom_metrics_source = CustomMetricsSource()
38+
custom_metrics_source.add_metric(IPsecTrafficCustomMetric("ipsec_traffic"))
39+
server.add_metrics_source(custom_metrics_source)
40+
41+
server.run()
42+

src/metric.py

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from abc import abstractmethod
2+
from prometheus_client import *
3+
from re import Pattern
4+
import re
5+
import os
6+
7+
8+
9+
class Metric:
10+
gauge: Gauge
11+
12+
def __init__(self, name: str, labels: list[str], description: str = ""):
13+
self.gauge = Gauge(
14+
name,
15+
description,
16+
labels
17+
)
18+
19+
20+
class CommandMetric(Metric):
21+
regex: Pattern
22+
23+
def __init__(self, name: str, regex: str, description: str = ""):
24+
self.regex = re.compile(regex)
25+
26+
labels = list(self.regex.groupindex.keys())
27+
labels.remove("VALUE")
28+
29+
super().__init__(name, labels, description)
30+
31+
def update(self, command_output: str):
32+
self.gauge.clear()
33+
34+
results = re.finditer(self.regex, command_output)
35+
36+
for result in results:
37+
groups = result.groupdict()
38+
value = groups.pop("VALUE")
39+
self.gauge.labels(*list(groups.values())).set(int(value))
40+
41+
42+
class CustomMetric(Metric):
43+
def __init__(self, name: str, labels: list[str], description: str = ""):
44+
super().__init__(name, labels, description)
45+
46+
@abstractmethod
47+
def update(self):
48+
pass
49+
50+
51+
class IPsecTrafficCustomMetric(CustomMetric):
52+
def __init__(self, name: str, description: str = ""):
53+
labels = [
54+
"lease",
55+
"connection",
56+
"direction"
57+
]
58+
super().__init__(name, labels, description)
59+
60+
def update(self):
61+
self.gauge.clear()
62+
63+
trafficstatus = os.popen("sudo ipsec trafficstatus").read()
64+
65+
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)
66+
67+
for result in trafficstatus_results:
68+
lease = result.groupdict()["lease"]
69+
connection = result.groupdict()["connection"]
70+
in_value = result.groupdict()["IN_VALUE"]
71+
out_value = result.groupdict()["OUT_VALUE"]
72+
73+
self.gauge.labels(lease, connection, "in").set(int(in_value))
74+
self.gauge.labels(lease, connection, "out").set(int(out_value))

src/metrics_source.py

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from abc import ABC, abstractmethod
2+
from src.metric import *
3+
import os
4+
5+
6+
7+
class MetricsSource:
8+
_metrics : list[Metric]
9+
10+
def __init__(self):
11+
self._metrics = []
12+
13+
@abstractmethod
14+
def update(self):
15+
pass
16+
17+
18+
class CommandMetricsSource(MetricsSource):
19+
command : str
20+
21+
def __init__(self, command: str):
22+
super().__init__()
23+
self.command = command
24+
25+
def add_metric(self, name: str, regex: str, description: str = ""):
26+
self._metrics.append(CommandMetric(name, regex, description))
27+
28+
def update(self):
29+
output = os.popen(self.command).read()
30+
for metric in self._metrics:
31+
metric.update(output)
32+
33+
34+
class CustomMetricsSource(MetricsSource):
35+
def __init__(self):
36+
super().__init__()
37+
38+
def add_metric(self, custom_metric : CustomMetric):
39+
self._metrics.append(custom_metric)
40+
41+
def update(self):
42+
for metric in self._metrics:
43+
metric.update()

src/prometheus_metrics_server.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import prometheus_client
2+
import time
3+
from src.metrics_source import *
4+
5+
6+
7+
class PrometheusMetricsServer:
8+
_metrics_sources: list[MetricsSource]
9+
10+
address: str
11+
port: int
12+
interval: int
13+
server_name: str
14+
15+
def __init__(self, port: int, server_name: str):
16+
self._metrics_sources = []
17+
18+
self.address = "0.0.0.0"
19+
self.port = port
20+
self.interval = 1
21+
self.server_name = server_name
22+
23+
def add_metrics_source(self, source: MetricsSource):
24+
self._metrics_sources.append(source)
25+
26+
def run(self):
27+
prometheus_client.start_http_server(self.port, addr=self.address)
28+
print(f"{self.server_name} is running on {self.address}:{self.port}")
29+
while True:
30+
for source in self._metrics_sources:
31+
source.update()
32+
time.sleep(self.interval)

0 commit comments

Comments
 (0)