Skip to content

Commit 21f3817

Browse files
committed
Initial commit
1 parent 59a4922 commit 21f3817

File tree

12 files changed

+403
-0
lines changed

12 files changed

+403
-0
lines changed

.dockerignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
README.md
2+
CHANGELOG.md
3+
docker-compose.yml
4+
Dockerfile
5+
.git
6+
.gitattributes
7+
.gitignore
8+
logs
9+
*.log
10+
tests/

.github/workflows/ci.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Create and publish RADIUS Producer Docker image
2+
3+
on:
4+
release:
5+
types: [created]
6+
7+
env:
8+
REGISTRY: ghcr.io
9+
IMAGE_NAME: ${{ github.repository }}
10+
11+
jobs:
12+
build:
13+
runs-on: ubuntu-latest
14+
permissions:
15+
contents: read
16+
packages: write
17+
attestations: write
18+
id-token: write
19+
steps:
20+
- name: Checkout repository
21+
uses: actions/checkout@v4
22+
- name: Log in to the Container registry
23+
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
24+
with:
25+
registry: ${{ env.REGISTRY }}
26+
username: ${{ github.actor }}
27+
password: ${{ secrets.GITHUB_TOKEN }}
28+
- name: Extract metadata (tags, labels) for Docker
29+
id: meta
30+
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
31+
with:
32+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
33+
- name: Build and push Docker image
34+
id: push
35+
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
36+
with:
37+
context: .
38+
push: true
39+
tags: |
40+
${{ steps.meta.outputs.tags }}
41+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}
42+
labels: ${{ steps.meta.outputs.labels }}
43+
- name: Generate artifact attestation
44+
uses: actions/attest-build-provenance@v2
45+
with:
46+
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
47+
subject-digest: ${{ steps.push.outputs.digest }}
48+
push-to-registry: true

Dockerfile

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
FROM freeradius/freeradius-server:3.2.7
2+
3+
ARG KAFKA_BOOTSTRAP_SERVER
4+
5+
# Install dependencies
6+
RUN apt-get update && apt-get install -y \
7+
python3 python3-pip \
8+
&& rm -rf /var/lib/apt/lists/*
9+
10+
# Create the required directory for your custom Python scripts
11+
RUN mkdir -p /cgn_ec_freeradius
12+
13+
ENV PYTHONPATH="/cgn_ec_freeradius:$PYTHONPATH"
14+
15+
RUN /usr/bin/python3 -m pip install confluent-kafka==2.8.2
16+
17+
# Set the virtual environment as default
18+
ENV PATH="/venv/bin:$PATH"
19+
20+
# Copy your custom Python scripts to /cgn_ec_freeradius/mods-config/python3
21+
COPY ./python3 /cgn_ec_freeradius
22+
23+
# Ensure permissions for the custom Python script
24+
RUN chown -R freerad:freerad /cgn_ec_freeradius && \
25+
chmod -R 755 /cgn_ec_freeradius
26+
27+
COPY ./dictionaries/* /usr/share/freeradius
28+
29+
RUN echo '$INCLUDE dictionary.nfware' >> /usr/share/freeradius/dictionary
30+
RUN echo '$INCLUDE dictionary.veesixnetworks' >> /usr/share/freeradius/dictionary

clients.conf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
client cgn_servers {
2+
ipaddr = 172.16.0.0/12
3+
secret = cgnat
4+
}

dictionaries/dictionary.nfware

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# NFWare dictionary.
2+
#
3+
# Version: 1.0 26-Sep-2016
4+
# $Id$
5+
#
6+
VENDOR NFWare 46576
7+
BEGIN-VENDOR NFWare
8+
#
9+
# vCGNAT accounting
10+
#
11+
ATTRIBUTE NFWare-vCGNAT-Protocol 1 integer
12+
ATTRIBUTE NFWare-vCGNAT-Inside-Addr 2 ipaddr
13+
ATTRIBUTE NFWare-vCGNAT-Inside-Port 3 short
14+
ATTRIBUTE NFWare-vCGNAT-NAT-Addr 4 ipaddr
15+
ATTRIBUTE NFWare-vCGNAT-NAT-Port 5 short
16+
ATTRIBUTE NFWare-vCGNAT-Dest-Addr 6 ipaddr
17+
ATTRIBUTE NFWare-vCGNAT-Dest-Port 7 short
18+
ATTRIBUTE NFWare-vCGNAT-NAT-Port-Start 8 short
19+
ATTRIBUTE NFWare-vCGNAT-NAT-Port-End 9 short
20+
VALUE NFWare-vCGNAT-Protocol ICMP 1
21+
VALUE NFWare-vCGNAT-Protocol TCP 6
22+
VALUE NFWare-vCGNAT-Protocol UDP 17
23+
VALUE NFWare-vCGNAT-Protocol GRE 47
24+
VALUE NFWare-vCGNAT-Protocol OTHER 0
25+
ATTRIBUTE NFWare-vCGNAT-Action 10 integer
26+
VALUE NFWare-vCGNAT-Action Port-Allocated 1
27+
VALUE NFWare-vCGNAT-Action Port-Freed 2
28+
VALUE NFWare-vCGNAT-Action Session-Created 3
29+
VALUE NFWare-vCGNAT-Action Session-Deleted 4
30+
VALUE NFWare-vCGNAT-Action Port-Block-Allocated 5
31+
VALUE NFWare-vCGNAT-Action Port-Block-Freed 6
32+
ATTRIBUTE NFWare-vCGNAT-Direction 11 integer
33+
VALUE NFWare-vCGNAT-Direction Outbound 0
34+
VALUE NFWare-vCGNAT-Direction Inbound 1
35+
VALUE NFWare-vCGNAT-Direction None 2
36+
ATTRIBUTE NFWare-vCGNAT-64-Inside-Addr 12 ipv6addr
37+
ATTRIBUTE NFWare-vCGNAT-VRF 13 integer
38+
END-VENDOR NFWare
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
VENDOR VeesixNetworks 214457
2+
BEGIN-VENDOR VeesixNetworks
3+
4+
ATTRIBUTE VeesixNetworks-CGN-EC-Kafka-Topic 1 string
5+
END-VENDOR VeesixNetworks

mods-enabled/always

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# -*- text -*-
2+
#
3+
# $Id$
4+
5+
#
6+
# The "always" module is here for debugging purposes, or
7+
# for use in complex policies.
8+
# Instance simply returns the same result, always, without
9+
# doing anything.
10+
#
11+
# rcode may be one of the following values:
12+
# - reject - Reject the user.
13+
# - fail - Simulate or indicate a failure.
14+
# - ok - Simulate or indicate a success.
15+
# - handled - Indicate that the request has been handled,
16+
# stop processing, and send response if set.
17+
# - invalid - Indicate that the request is invalid.
18+
# - userlock - Indicate that the user account has been
19+
# locked out.
20+
# - notfound - Indicate that a user account can't be found.
21+
# - noop - Simulate a no-op.
22+
# - updated - Indicate that the request has been updated.
23+
#
24+
# If an instance is listed in a session {} section,
25+
# this simulates a user having <integer> sessions.
26+
#
27+
# simulcount = <integer>
28+
#
29+
# If an instance is listed in a session {} section,
30+
# this simulates the user having multilink
31+
# sessions.
32+
#
33+
# mpp = <integer>
34+
#
35+
# An xlat based on the instance name can be called to change the status
36+
# returned by the instance, in this example "always db_status { ... }"
37+
#
38+
# Force the module status to be alive or dead:
39+
#
40+
# %{db_status:alive}
41+
# %{db_status:dead}
42+
#
43+
# Update the rcode returned by an alive module (a dead module returns fail):
44+
#
45+
# %{db_status:ok}
46+
# %{db_status:fail}
47+
# %{db_status:notfound}
48+
# ...
49+
#
50+
# The above xlats expand to the current status of the module. To fetch the
51+
# current status without affecting it call the xlat with an empty argument:
52+
#
53+
# %{db_status:}
54+
#
55+
always reject {
56+
rcode = reject
57+
}
58+
always fail {
59+
rcode = fail
60+
}
61+
always ok {
62+
rcode = ok
63+
}
64+
always handled {
65+
rcode = handled
66+
}
67+
always invalid {
68+
rcode = invalid
69+
}
70+
always userlock {
71+
rcode = userlock
72+
}
73+
always notfound {
74+
rcode = notfound
75+
}
76+
always noop {
77+
rcode = noop
78+
}
79+
always updated {
80+
rcode = updated
81+
}

mods-enabled/python3

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#
2+
# Make sure the PYTHONPATH environmental variable contains the
3+
# directory(s) for the modules listed below.
4+
#
5+
# Uncomment any func_* which are included in your module. If
6+
# rlm_python is called for a section which does not have
7+
# a function defined, it will return NOOP.
8+
#
9+
python3 {
10+
# Path to the python modules
11+
#
12+
# Note that due to limitations on Python, this configuration
13+
# item is GLOBAL TO THE SERVER. That is, you cannot have two
14+
# instances of the python module, each with a different path.
15+
#
16+
python_path="${modconfdir}/${.:name}:/cgn_ec_freeradius:/usr/local/lib/python3.10/dist-packages"
17+
18+
# How to use "python_path"
19+
#
20+
# - "append" - append to system path
21+
# - "prepend" - prepend to the system path
22+
# - "overwrite" - overwrite the system path
23+
#
24+
# Note: Take care when using "prepend" - the paths searched
25+
# should not be writeable by any un-trusted users or services
26+
# to avoid overriding standard functionality with malicious code.
27+
# python_path_mode = append
28+
29+
module = kafka_producer
30+
31+
# Pass all VPS lists as a 6-tuple to the callbacks
32+
# (request, reply, config, state, proxy_req, proxy_reply)
33+
# pass_all_vps = no
34+
35+
# Pass all VPS lists as a dictionary to the callbacks
36+
# Keys: "request", "reply", "config", "session-state", "proxy-request",
37+
# "proxy-reply"
38+
# This option prevales over "pass_all_vps"
39+
pass_all_vps_dict = yes
40+
41+
mod_instantiate = ${.module}
42+
func_instantiate = instantiate
43+
44+
# mod_detach = ${.module}
45+
# func_detach = detach
46+
47+
# mod_authorize = ${.module}
48+
# func_authorize = authorize
49+
50+
# mod_authenticate = ${.module}
51+
# func_authenticate = authenticate
52+
53+
# mod_preacct = ${.module}
54+
# func_preacct = preacct
55+
56+
mod_accounting = ${.module}
57+
func_accounting = accounting
58+
59+
# mod_checksimul = ${.module}
60+
# func_checksimul = checksimul
61+
62+
# mod_pre_proxy = ${.module}
63+
# func_pre_proxy = pre_proxy
64+
65+
# mod_post_proxy = ${.module}
66+
# func_post_proxy = post_proxy
67+
68+
# mod_post_auth = ${.module}
69+
# func_post_auth = post_auth
70+
71+
# mod_recv_coa = ${.module}
72+
# func_recv_coa = recv_coa
73+
74+
# mod_send_coa = ${.module}
75+
# func_send_coa = send_coa
76+
}

policy.d/veesixnetworks

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
populate_cgn_vendor {
2+
if (&NFWare-vCGNAT-Action) {
3+
update config {
4+
VeesixNetworks-CGN-EC-Kafka-Topic = "cgnat.accounting.nfware"
5+
}
6+
7+
# https://docs.nfware.com/en/6.3/nat/logging.html#id6
8+
# NFWare CGN does not expect Accounting-Response
9+
update control {
10+
Response-Packet-Type := "Do-Not-Respond"
11+
}
12+
}
13+
}

python3/kafka_producer.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from confluent_kafka import Producer
2+
import json
3+
import socket
4+
import radiusd
5+
import os
6+
7+
conf = {
8+
"bootstrap.servers": os.environ["KAFKA_BOOTSTRAP_SERVER"],
9+
"client.id": socket.gethostname(),
10+
}
11+
12+
producer = Producer(conf)
13+
14+
15+
def transform_tuple(data):
16+
transformed_dict = {}
17+
18+
for t in data:
19+
k, v = t
20+
transformed_dict[k] = v
21+
22+
return transformed_dict
23+
24+
25+
def instantiate(p):
26+
print("*** instantiate ***")
27+
print(p)
28+
29+
30+
"""
31+
def preacct(p):
32+
print("*** preacct ***")
33+
print(p)
34+
return freeradius.RLM_MODULE_OK
35+
"""
36+
37+
38+
def accounting(p: dict):
39+
print("*** accounting ***")
40+
41+
request = p["request"]
42+
config = transform_tuple(p["config"])
43+
accounting_attributes = transform_tuple(request)
44+
producer.produce(
45+
config.get("VeesixNetworks-CGN-EC-Kafka-Topic", "cgnat.accounting.generic"),
46+
value=json.dumps(accounting_attributes).encode("UTF-8"),
47+
)
48+
radiusd.radlog(radiusd.L_DBG, str(accounting_attributes))
49+
return radiusd.RLM_MODULE_OK

0 commit comments

Comments
 (0)