Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RestAPI installer integrated #1519

Merged
merged 51 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
2ce8351
initial install test
NL-TCH Feb 8, 2024
04a443a
add --help option
NL-TCH Feb 8, 2024
2ac9ba5
pip fix
NL-TCH Feb 8, 2024
8357fcb
fix typo
NL-TCH Feb 8, 2024
c43d2ea
fix installer
NL-TCH Feb 8, 2024
08db1e5
bruh, i am stupid
NL-TCH Feb 8, 2024
8c82c41
parsing of restapi var to common
NL-TCH Feb 8, 2024
ccd0d62
why am i stupid?
NL-TCH Feb 9, 2024
8eae2d1
check if python is present
NL-TCH Feb 9, 2024
aec0279
change python -> python3
NL-TCH Feb 9, 2024
910616e
fix path error in servicefile
NL-TCH Feb 9, 2024
1f1fa37
no posts + everything behind auth
NL-TCH Feb 17, 2024
bf41d88
Add constant to config.php + enable with installer
billz Feb 19, 2024
4e258b3
Add sidebar item, template stubs + page actions
billz Feb 19, 2024
2d41f74
Minor: fix caps in title
billz Feb 19, 2024
9d70585
Add start/stop restapi.service to sudoers
billz Feb 19, 2024
7dc2fd6
Implement basic display/save settings
billz Feb 19, 2024
8ecd542
Create api general + status tabs
billz Feb 19, 2024
def8be8
Update dependencies + cp vendor/*/src src/
billz Feb 27, 2024
4bf514c
Add required php-mbstring to _install_dependencies
billz Feb 27, 2024
5150224
Read RASPAP_API_KEY from Dotenv
billz Feb 27, 2024
b835243
Add .env to gitignore
billz Feb 28, 2024
cfa9166
Replace external dependencies w/ native DotEnv class
billz Feb 28, 2024
0b9cbee
Processed w/ phpcbf
billz Feb 28, 2024
2b2cb8f
Update en_US locale messages + compile .mo
billz Feb 28, 2024
5b7b968
Fix: rename systemd service, set serviceStatus flag
billz Mar 7, 2024
95acd49
Move /api dir to $raspap_dir
billz Mar 7, 2024
edc3a42
Replace root user with %i, append --reload
billz Mar 7, 2024
2365c4e
Add python-dotenv dependecy
billz Mar 7, 2024
75be1bf
Add load_dotenv from dotenv
billz Mar 7, 2024
b80151b
Fix SyntaxError: positional argument follows keyword
billz Mar 7, 2024
00f90f1
Sanitize user-provided inputs
billz Mar 8, 2024
b567f56
Disambiguate service name
billz Mar 8, 2024
95ad900
Validate client_config path expression
billz Mar 8, 2024
282b839
Add gen_apikey to template
billz Mar 8, 2024
5d8fed8
Define RASPI_CONFIG_API, update class constructor, load + createEnv
billz Mar 8, 2024
87216bd
Update sudoers .env permissions, systemd service user
billz Mar 8, 2024
ef7b67a
Set serviceLog from systemctl status
billz Mar 8, 2024
2cdf6ef
Sanitize path to prevent directory traversal
billz Mar 8, 2024
79d33db
Revert "Sanitize path to prevent directory traversal"
billz Mar 8, 2024
49780d8
Rename tag hostpost -> hotspot
billz Mar 8, 2024
19fe434
Remove --reload from ExecStart
billz Mar 8, 2024
f61e1b5
Clean up serviceLog output
billz Mar 8, 2024
9c5c0cf
Remove /wireguard/{config} endpoint
billz Mar 8, 2024
1e840ab
Update installer switches
billz Mar 9, 2024
f81c68d
Add restAPI doc link when svc is active
billz Mar 9, 2024
3262e30
Update en_US locale + compile .mo
billz Mar 9, 2024
220709b
fix: mv api files /facepalm
billz Mar 9, 2024
c408a6f
Add message to en_US locale + compile
billz Mar 9, 2024
8d6b817
Bump position of sidebar item
billz Mar 9, 2024
d1be0ca
Restart restapi.sevice on API key save
billz Mar 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ yarn-error.log
includes/config.php
rootCA.pem
vendor
.env
24 changes: 24 additions & 0 deletions api/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import os
from fastapi.security.api_key import APIKeyHeader
from fastapi import Security, HTTPException
from starlette.status import HTTP_403_FORBIDDEN
from dotenv import load_dotenv

load_dotenv()

apikey=os.getenv('RASPAP_API_KEY')
#if env not set, set the api key to "insecure"
if apikey == None:
apikey = "insecure"

print(apikey)
api_key_header = APIKeyHeader(name="access_token", auto_error=False)

async def get_api_key(api_key_header: str = Security(api_key_header)):
if api_key_header ==apikey:
return api_key_header
else:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="403: Unauthorized"
)

156 changes: 156 additions & 0 deletions api/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
from fastapi import FastAPI, Depends
from fastapi.security.api_key import APIKey
import auth

import json

import modules.system as system
import modules.ap as ap
import modules.client as client
import modules.dns as dns
import modules.dhcp as dhcp
import modules.ddns as ddns
import modules.firewall as firewall
import modules.networking as networking
import modules.openvpn as openvpn
import modules.wireguard as wireguard


tags_metadata = [
]
app = FastAPI(
title="API for RaspAP",
openapi_tags=tags_metadata,
version="0.0.1",
license_info={
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
}
)

@app.get("/system", tags=["system"])
async def get_system(api_key: APIKey = Depends(auth.get_api_key)):
return{
'hostname': system.hostname(),
'uptime': system.uptime(),
'systime': system.systime(),
'usedMemory': system.usedMemory(),
'processorCount': system.processorCount(),
'LoadAvg1Min': system.LoadAvg1Min(),
'systemLoadPercentage': system.systemLoadPercentage(),
'systemTemperature': system.systemTemperature(),
'hostapdStatus': system.hostapdStatus(),
'operatingSystem': system.operatingSystem(),
'kernelVersion': system.kernelVersion(),
'rpiRevision': system.rpiRevision()
}

@app.get("/ap", tags=["accesspoint/hotspot"])
async def get_ap(api_key: APIKey = Depends(auth.get_api_key)):
return{
'driver': ap.driver(),
'ctrl_interface': ap.ctrl_interface(),
'ctrl_interface_group': ap.ctrl_interface_group(),
'auth_algs': ap.auth_algs(),
'wpa_key_mgmt': ap.wpa_key_mgmt(),
'beacon_int': ap.beacon_int(),
'ssid': ap.ssid(),
'channel': ap.channel(),
'hw_mode': ap.hw_mode(),
'ieee80211n': ap.ieee80211n(),
'wpa_passphrase': ap.wpa_passphrase(),
'interface': ap.interface(),
'wpa': ap.wpa(),
'wpa_pairwise': ap.wpa_pairwise(),
'country_code': ap.country_code(),
'ignore_broadcast_ssid': ap.ignore_broadcast_ssid()
}

@app.get("/clients/{wireless_interface}", tags=["Clients"])
async def get_clients(wireless_interface, api_key: APIKey = Depends(auth.get_api_key)):
return{
'active_clients_amount': client.get_active_clients_amount(wireless_interface),
'active_clients': json.loads(client.get_active_clients(wireless_interface))
}

@app.get("/dhcp", tags=["DHCP"])
async def get_dhcp(api_key: APIKey = Depends(auth.get_api_key)):
return{
'range_start': dhcp.range_start(),
'range_end': dhcp.range_end(),
'range_subnet_mask': dhcp.range_subnet_mask(),
'range_lease_time': dhcp.range_lease_time(),
'range_gateway': dhcp.range_gateway(),
'range_nameservers': dhcp.range_nameservers()
}

@app.get("/dns/domains", tags=["DNS"])
async def get_domains(api_key: APIKey = Depends(auth.get_api_key)):
return{
'domains': json.loads(dns.adblockdomains())
}

@app.get("/dns/hostnames", tags=["DNS"])
async def get_hostnames(api_key: APIKey = Depends(auth.get_api_key)):
return{
'hostnames': json.loads(dns.adblockhostnames())
}

@app.get("/dns/upstream", tags=["DNS"])
async def get_upstream(api_key: APIKey = Depends(auth.get_api_key)):
return{
'upstream_nameserver': dns.upstream_nameserver()
}

@app.get("/dns/logs", tags=["DNS"])
async def get_dnsmasq_logs(api_key: APIKey = Depends(auth.get_api_key)):
return(dns.dnsmasq_logs())


@app.get("/ddns", tags=["DDNS"])
async def get_ddns(api_key: APIKey = Depends(auth.get_api_key)):
return{
'use': ddns.use(),
'method': ddns.method(),
'protocol': ddns.protocol(),
'server': ddns.server(),
'login': ddns.login(),
'password': ddns.password(),
'domain': ddns.domain()
}

@app.get("/firewall", tags=["Firewall"])
async def get_firewall(api_key: APIKey = Depends(auth.get_api_key)):
return json.loads(firewall.firewall_rules())

@app.get("/networking", tags=["Networking"])
async def get_networking(api_key: APIKey = Depends(auth.get_api_key)):
return{
'interfaces': json.loads(networking.interfaces()),
'throughput': json.loads(networking.throughput())
}

@app.get("/openvpn", tags=["OpenVPN"])
async def get_openvpn(api_key: APIKey = Depends(auth.get_api_key)):
return{
'client_configs': openvpn.client_configs(),
'client_config_names': openvpn.client_config_names(),
'client_config_active': openvpn.client_config_active(),
'client_login_names': openvpn.client_login_names(),
'client_login_active': openvpn.client_login_active()
}

@app.get("/openvpn/{config}", tags=["OpenVPN"])
async def client_config_list(config, api_key: APIKey = Depends(auth.get_api_key)):
return{
'client_config': openvpn.client_config_list(config)
}

@app.get("/wireguard", tags=["WireGuard"])
async def get_wireguard(api_key: APIKey = Depends(auth.get_api_key)):
return{
'client_configs': wireguard.configs(),
'client_config_names': wireguard.client_config_names(),
'client_config_active': wireguard.client_config_active()
}

64 changes: 64 additions & 0 deletions api/modules/ap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import subprocess
import json

def driver():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep driver= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def ctrl_interface():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep ctrl_interface= | cut -d'=' -f2 | head -1", shell=True, capture_output=True, text=True).stdout.strip()

def ctrl_interface_group():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep ctrl_interface_group= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def auth_algs():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep auth_algs= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def wpa_key_mgmt():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep wpa_key_mgmt= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def beacon_int():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep beacon_int= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def ssid():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep ssid= | cut -d'=' -f2 | head -1", shell=True, capture_output=True, text=True).stdout.strip()

def channel():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep channel= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def hw_mode():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep hw_mode= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def ieee80211n():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep ieee80211n= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def wpa_passphrase():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep wpa_passphrase= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def interface():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep interface= | cut -d'=' -f2 | head -1", shell=True, capture_output=True, text=True).stdout.strip()

def wpa():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep wpa= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def wpa_pairwise():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep wpa_pairwise= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def country_code():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep country_code= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def ignore_broadcast_ssid():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep ignore_broadcast_ssid= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def logging():
log_output = subprocess.run(f"cat /tmp/hostapd.log", shell=True, capture_output=True, text=True).stdout.strip()
logs = {}

for line in log_output.split('\n'):
parts = line.split(': ')
if len(parts) >= 2:
interface, message = parts[0], parts[1]
if interface not in logs:
logs[interface] = []
logs[interface].append(message)

return json.dumps(logs, indent=2)
38 changes: 38 additions & 0 deletions api/modules/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import subprocess
import json

def get_active_clients_amount(interface):
arp_output = subprocess.run(['arp', '-i', interface], capture_output=True, text=True)
mac_addresses = arp_output.stdout.splitlines()

if mac_addresses:
grep_pattern = '|'.join(mac_addresses)
output = subprocess.run(['grep', '-iwE', grep_pattern, '/var/lib/misc/dnsmasq.leases'], capture_output=True, text=True)
return len(output.stdout.splitlines())
else:
return 0

def get_active_clients(interface):
arp_output = subprocess.run(['arp', '-i', interface], capture_output=True, text=True)
arp_mac_addresses = set(line.split()[2] for line in arp_output.stdout.splitlines()[1:])

dnsmasq_output = subprocess.run(['cat', '/var/lib/misc/dnsmasq.leases'], capture_output=True, text=True)
active_clients = []

for line in dnsmasq_output.stdout.splitlines():
fields = line.split()
mac_address = fields[1]

if mac_address in arp_mac_addresses:
client_data = {
"timestamp": int(fields[0]),
"mac_address": fields[1],
"ip_address": fields[2],
"hostname": fields[3],
"client_id": fields[4],
}
active_clients.append(client_data)

json_output = json.dumps(active_clients, indent=2)
return json_output

24 changes: 24 additions & 0 deletions api/modules/ddns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import subprocess

def use():
return subprocess.run("cat /etc/ddclient.conf | grep use= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def method():
#get the contents of the line below "use="
return subprocess.run("awk '/^use=/ {getline; print}' /etc/ddclient.conf | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def protocol():
return subprocess.run("cat /etc/ddclient.conf | grep protocol= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def server():
return subprocess.run("cat /etc/ddclient.conf | grep server= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def login():
return subprocess.run("cat /etc/ddclient.conf | grep login= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def password():
return subprocess.run("cat /etc/ddclient.conf | grep password= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def domain():
#get the contents of the line below "password="
return subprocess.run("awk '/^password=/ {getline; print}' /etc/ddclient.conf", shell=True, capture_output=True, text=True).stdout.strip()
30 changes: 30 additions & 0 deletions api/modules/dhcp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import subprocess
import json

def range_start():
return subprocess.run("cat /etc/dnsmasq.d/090_wlan0.conf |grep dhcp-range= |cut -d'=' -f2| cut -d',' -f1", shell=True, capture_output=True, text=True).stdout.strip()

def range_end():
return subprocess.run("cat /etc/dnsmasq.d/090_wlan0.conf |grep dhcp-range= |cut -d'=' -f2| cut -d',' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def range_subnet_mask():
return subprocess.run("cat /etc/dnsmasq.d/090_wlan0.conf |grep dhcp-range= |cut -d'=' -f2| cut -d',' -f3", shell=True, capture_output=True, text=True).stdout.strip()

def range_lease_time():
return subprocess.run("cat /etc/dnsmasq.d/090_wlan0.conf |grep dhcp-range= |cut -d'=' -f2| cut -d',' -f4", shell=True, capture_output=True, text=True).stdout.strip()

def range_gateway():
return subprocess.run("cat /etc/dhcpcd.conf | grep routers | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()

def range_nameservers():
output = subprocess.run("cat /etc/dhcpcd.conf", shell=True, capture_output=True, text=True).stdout.strip()

nameservers = []

lines = output.split('\n')
for line in lines:
if "static domain_name_server" in line:
servers = line.split('=')[1].strip().split()
nameservers.extend(servers)

return nameservers
38 changes: 38 additions & 0 deletions api/modules/dns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import subprocess
import json

def adblockdomains():
output = subprocess.run("cat /etc/raspap/adblock/domains.txt", shell=True, capture_output=True, text=True).stdout.strip()
domains =output.split('\n')
domainlist=[]
for domain in domains:
if domain.startswith('#') or domain=="":
continue
domainlist.append(domain.split('=/')[1])
return domainlist

def adblockhostnames():
output = subprocess.run("cat /etc/raspap/adblock/hostnames.txt", shell=True, capture_output=True, text=True).stdout.strip()
hostnames = output.split('\n')
hostnamelist=[]
for hostname in hostnames:
if hostname.startswith('#') or hostname=="":
continue
hostnamelist.append(hostname.replace('0.0.0.0 ',''))
return hostnamelist

def upstream_nameserver():
return subprocess.run("awk '/nameserver/ {print $2}' /run/dnsmasq/resolv.conf", shell=True, capture_output=True, text=True).stdout.strip()

def dnsmasq_logs():
output = subprocess.run("cat /var/log/dnsmasq.log", shell=True, capture_output=True, text=True).stdout.strip()
log_entries = []
for line in output.split("\n"):
fields = line.split(" ")
log_dict = {
'timestamp': ' '.join(fields[:3]),
'process': fields[3][:-1], # Remove the trailing colon
'message': ' '.join(fields[4:]),
}
log_entries.append(log_dict)
return log_entries
4 changes: 4 additions & 0 deletions api/modules/firewall.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import subprocess

def firewall_rules():
return subprocess.run("cat /etc/raspap/networking/firewall/iptables_rules.json", shell=True, capture_output=True, text=True).stdout.strip()
Loading
Loading