Skip to content

Commit

Permalink
Ms365 url fetcher script to update proxy whitelist dynamically
Browse files Browse the repository at this point in the history
Signed-off-by: Enes Öztürk <[email protected]>
  • Loading branch information
enesoztrk committed Oct 17, 2024
1 parent 637bdfd commit ccda4e6
Show file tree
Hide file tree
Showing 2 changed files with 256 additions and 45 deletions.
227 changes: 182 additions & 45 deletions modules/reference/services/proxy-server/3proxy-config.nix
Original file line number Diff line number Diff line change
@@ -1,28 +1,45 @@
# Copyright 2024 TII (SSRC) and the Ghaf contributors
# SPDX-License-Identifier: Apache-2.0
{ config, lib, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.ghaf.reference.services.proxy-server;
inherit (lib) mkEnableOption mkIf;
# use nix-prefetch-url to calculate sha256 checksum
# TODO The urls should be fetched during boot. The script should be implemented in netvm or adminvm
#pkgs.fetchurl {
# url = "https://endpoints.office.com/endpoints/worldwide?clientrequestid=b10c5ed1-bad1-445f-b386-b919946339a7";
# sha256 = "1zly0g23vray4wg6fjxxdys6zzksbymlzggbg75jxqcf8g9j6xnw";
#};
msEndpointsFile = ./ms_urls.json;
# Read and parse the JSON file
msEndpointsData = builtins.fromJSON (builtins.readFile msEndpointsFile);

# Extract URLs from the JSON data based on categories
msExtractUrls = map (x: x.urls or [ ]) (
lib.filter (
x: x.category == "Optimize" || x.category == "Allow" || x.category == "Default"
) msEndpointsData
);

msUrlsFlattened = builtins.concatLists msExtractUrls ++ [ "microsoft365.com" ];

proxyUserName = "proxy-user";
proxyGroupName = "proxy-admin";
proxyAllowListName = "allowlist.txt";
proxyWritableAllowListPath = "/etc/${proxyAllowListName}";
ms-url-fetcher = pkgs.callPackage ./ms_url_fetcher.nix {
allowListPath = proxyWritableAllowListPath;
};

_3proxy-restart = pkgs.writeShellApplication {
name = "3proxy-restart";
runtimeInputs = [
pkgs.systemd
pkgs.coreutils # Provides 'sleep' and other basic utilities
];
text = ''
sleep 2
systemctl stop 3proxy.service
echo "Attempting to start 3proxy service"
# Retry loop for systemctl start 3proxy.service
while ! systemctl is-active --quiet 3proxy.service; do
echo "3proxy is not activated, retrying to start in 5 seconds..."
systemctl start 3proxy.service
sleep 5
done
echo "3proxy service successfully started"
'';
};
tiiUrls = [
#for jira avatars
"*.gravatar.com"
Expand All @@ -48,6 +65,39 @@ let
"ghaflogs.vedenemo.dev"
"himalia.vedenemo.dev"
];

extraMsUrls = [
"microsoft365.com"
"spoppe-b.azureedge.net" # microsoft365 icons

];
# Concatenate the lists and join with commas
concatenatedUrls = builtins.concatStringsSep "," (tiiUrls ++ ssrcUrls ++ extraMsUrls);

config_file_content = ''
# log to stdout
log
nscache 65535
nscache6 65535
auth iponly
#private addresses
deny * * 0.0.0.0/8,127.0.0.0/8,10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,::,::1,fc00::/7
allow * * ${concatenatedUrls} *
#include dynamic whitelist ips
include "${proxyWritableAllowListPath}"
deny * * * *
maxconn 200
proxy -i${netvmAddr} -p${toString cfg.bindPort}
flush
'';

netvmEntry = builtins.filter (x: x.name == "net-vm") config.ghaf.networking.hosts.entries;
netvmAddr = lib.head (builtins.map (x: x.ip) netvmEntry);
in
Expand All @@ -62,10 +112,51 @@ in
};

config = mkIf cfg.enable {
assertions = [
assertions =
[
];
# Define a new group for proxy management
users.groups.${proxyGroupName} = { }; # Create a group named proxy-admin

];
# Define a new user with a specific username
users.users.${proxyUserName} = {
isNormalUser = true;
description = "Proxy User for managing allowlist and services";
extraGroups = [ "${proxyGroupName}" ]; # Adding to 'proxy-admin' for specific access
password = "proxy-user"; # Replace with a secure password
};

# Set up the permissions for allowlist.txt
environment.etc."${proxyAllowListName}" = {
text = '''';
user = "${proxyUserName}"; # Owner is proxy-user
group = "${proxyGroupName}"; # Group is proxy-admin
mode = "0660"; # Permissions: read/write for owner/group, no permissions for others
};

# Allow proxy-admin group to manage specific systemd services without a password
security = {
polkit = {
enable = true;
debug = true;
# Polkit rules for allowing proxy-user to run proxy related systemctl
# commands without sudo and password requirement
extraConfig = ''
polkit.addRule(function(action, subject) {
if ((action.id == "org.freedesktop.systemd1.manage-units" &&
(action.lookup("unit") == "fetchFile.service" ||
action.lookup("unit") == "fetchFile.timer" ||
action.lookup("unit") == "3proxy.service")) &&
subject.user == "${proxyUserName}") {
return polkit.Result.YES;
}
});
'';
};

};

environment.systemPackages = [ ms-url-fetcher ];
#Firewall Settings
networking = {
firewall.enable = true;
Expand All @@ -75,32 +166,78 @@ in
iptables -I INPUT -p udp -s 192.168.100.0/24 --dport ${toString cfg.bindPort} -j ACCEPT
'';
};
# systemd service for fetching the file
systemd.services.fetchFile = {
description = "Fetch a file periodically with retries if internet is available";

serviceConfig = {
ExecStart = "${ms-url-fetcher}/bin/ms-url-fetch";
# Ensure fetchFile starts after the network is up
Type = "simple";
# Retry until systemctl restart 3proxy succeeds
ExecStartPost = "${_3proxy-restart}/bin/3proxy-restart";
# Restart policy on failure
Restart = "on-failure"; # Restart the service if it fails
RestartSec = "10s"; # Wait 10 seconds before restarting
User = "${proxyUserName}";
};
};

# systemd timer to trigger the service every 10 minutes
systemd.timers.fetchFile = {
description = "Run fetch-file periodically";
wantedBy = [ "timers.target" ];
timerConfig = {
User = "${proxyUserName}";
Persistent = true; # Ensures the timer runs after a system reboot
OnCalendar = "hourly"; # Set to your desired schedule
OnBootSec = "60s";
};
};

systemd.services."3proxy".serviceConfig = {
RestartSec = "5s";
User = "${proxyUserName}";
Group = "proxy-admin";
};

services._3proxy = {
enable = true;
services = [
{
type = "proxy";
bindAddress = "${netvmAddr}";
inherit (cfg) bindPort;
maxConnections = 200;
auth = [ "iponly" ];
acl = [
{
rule = "allow";
targets = msUrlsFlattened;
}
{
rule = "allow";
targets = tiiUrls;
}
{
rule = "allow";
targets = ssrcUrls;
}
{ rule = "deny"; }
];
}
];
# Prepend the 'include' directive before the rest of the configuration
confFile = pkgs.writeText "3proxy.conf" ''
${config_file_content}
'';

/*
NOTE allow and deny configurations should must be placed before the other configs
it is not possible to do with extraConfig. Because it appends the file
*/
/*
services = [
{
type = "proxy";
bindAddress = "${netvmAddr}";
inherit (cfg) bindPort;
maxConnections = 200;
auth = [ "iponly" ];
acl = [
{
rule = "allow";
targets = tiiUrls;
}
{
rule = "allow";
targets = ssrcUrls;
}
{
rule = "allow";
targets = extraMsUrls;
}
{ rule = "deny"; }
];
}
];
*/
};

};
Expand Down
74 changes: 74 additions & 0 deletions modules/reference/services/proxy-server/ms_url_fetcher.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
writeShellApplication,
lib,
pkgs,
allowListPath,
...
}:
let
url = "https://endpoints.office.com/endpoints/worldwide?clientrequestid=b10c5ed1-bad1-445f-b386-b919946339a7";
logTag = "ms-url-fetcher";
in
writeShellApplication {
name = "ms-url-fetch";
runtimeInputs = [
pkgs.inetutils
pkgs.curl
pkgs.jq
];
text = ''
# Function to write to the allow list
write_to_allow_list() {
local processedUrls="$1"
local allowListPath="$2"
{
printf "allow * * " || { logger -t ms-url-fetcher "Failed to print prefix"; return 1; }
echo "$processedUrls" || { logger -t ms-url-fetcher "Failed to echo processed URLs"; return 1; }
} > "$allowListPath" || { logger -t ms-url-fetcher "Failed to write to $allowListPath"; return 2; }
return 0 # Indicate success
}
# Check if the device is connected to the internet.
if ping -c 1 8.8.8.8 &> /dev/null; then
logger -t ${logTag} "Fetching the Microsoft URLs from ${url}"
# Fetch the JSON file using curl with retry logic
if curl_output=$(curl -s --retry 5 --retry-delay 10 --retry-connrefused "${url}"); then
msurl_output=$(echo "$curl_output" | jq -r '.[]? | select(.category == "Optimize" or .category == "Allow" or .category == "Default") | .urls[]?' | sort | uniq)
# Check if msurl_output is empty
if [ -z "$msurl_output" ]; then
logger -t ${logTag} "No valid URLs found in the fetched data."
exit 4 # No URLs found error
fi
# Convert the list of URLs into a comma-separated format and save to allowListPath
processedUrls=$(echo "$msurl_output" | tr '\n' ',' | sed 's/,$//');
# Add the prefix once and save to allowListPath
if write_to_allow_list "$processedUrls" "${allowListPath}"; then
logger -t ${logTag} "Microsoft URLs fetched and saved to ${allowListPath} successfully"
exit 0 # Success exit code
else
logger -t ${logTag} "Failed to process Microsoft URLs with jq"
exit 2 # JQ processing error
fi
else
logger -t ${logTag} "Failed to fetch Microsoft URLs after multiple attempts"
exit 1 # Curl fetching error
fi
else
logger -t ${logTag} "No internet connection. Microsoft URLs not fetched."
exit 3 # No internet connection error
fi
'';
meta = with lib; {
description = "
The application is a shell script designed to fetch a list of Microsoft URLs
from a specified endpoint and save them to an allow list file. The script includes error
handling and retry logic to ensure robustness in various network conditions.
";
};
}

0 comments on commit ccda4e6

Please sign in to comment.