mirror of
https://github.com/YunoHost/check-http.git
synced 2024-09-03 19:56:42 +02:00
Add API to check ports
This commit is contained in:
parent
9a68ffabc1
commit
faa81c1ee3
1 changed files with 82 additions and 1 deletions
83
server.py
83
server.py
|
@ -4,6 +4,7 @@ import asyncio
|
||||||
import aiodns
|
import aiodns
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import validators
|
import validators
|
||||||
|
import socket
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.log import logger
|
from sanic.log import logger
|
||||||
|
@ -48,6 +49,14 @@ def check_rate_limit(key, now):
|
||||||
RATE_LIMIT_DB[key] = time.time()
|
RATE_LIMIT_DB[key] = time.time()
|
||||||
|
|
||||||
|
|
||||||
|
async def check_port_is_open(ip, port):
|
||||||
|
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock.settimeout(2)
|
||||||
|
result = sock.connect_ex((ip, port))
|
||||||
|
return result == 0
|
||||||
|
|
||||||
|
|
||||||
async def query_dns(host, dns_entry_type):
|
async def query_dns(host, dns_entry_type):
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
dns_resolver = aiodns.DNSResolver(loop=loop)
|
dns_resolver = aiodns.DNSResolver(loop=loop)
|
||||||
|
@ -62,7 +71,7 @@ async def query_dns(host, dns_entry_type):
|
||||||
logger.error("Unhandled error while resolving DNS entry")
|
logger.error("Unhandled error while resolving DNS entry")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/check/", methods=["POST"])
|
@app.route("/check-http/", methods=["POST"])
|
||||||
async def check_http(request):
|
async def check_http(request):
|
||||||
"""
|
"""
|
||||||
This function received an HTTP request from a YunoHost instance while this
|
This function received an HTTP request from a YunoHost instance while this
|
||||||
|
@ -178,6 +187,78 @@ async def check_http(request):
|
||||||
return json_response({"status": "ok"})
|
return json_response({"status": "ok"})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/check-ports/", methods=["POST"])
|
||||||
|
async def check_ports(request):
|
||||||
|
"""
|
||||||
|
This function received an HTTP request from a YunoHost instance while this
|
||||||
|
server is hosted on our infrastructure. The request is expected to be a
|
||||||
|
POST request with a body like {"ports": [80,443,22,25]}
|
||||||
|
|
||||||
|
The general workflow is the following:
|
||||||
|
|
||||||
|
- grab the ip from the request
|
||||||
|
- check for ip based rate limit (see RATE_LIMIT_SECONDS value)
|
||||||
|
- get json from body and ports list from it
|
||||||
|
- check ports are opened or closed
|
||||||
|
- answer the list of opened / closed ports
|
||||||
|
"""
|
||||||
|
|
||||||
|
# this is supposed to be a fast operation if run often enough
|
||||||
|
now = time.time()
|
||||||
|
clear_rate_limit_db(now)
|
||||||
|
|
||||||
|
# ############################################# #
|
||||||
|
# Validate request and extract the parameters #
|
||||||
|
# ############################################# #
|
||||||
|
|
||||||
|
ip = request.ip
|
||||||
|
|
||||||
|
check_rate_limit_ip = check_rate_limit(ip, now)
|
||||||
|
if check_rate_limit_ip:
|
||||||
|
return check_rate_limit_ip
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = request.json
|
||||||
|
except InvalidUsage:
|
||||||
|
logger.info(f"Invalid json in request, body is : {request.body}")
|
||||||
|
return json_response({
|
||||||
|
"status": "error",
|
||||||
|
"code": "error_bad_json",
|
||||||
|
"content": "Invalid usage, body isn't proper json",
|
||||||
|
}, status=400)
|
||||||
|
|
||||||
|
def is_port_number(p):
|
||||||
|
return isinstance(p, int) and p > 0 and p < 65535
|
||||||
|
|
||||||
|
# Check "ports" exist in request and is a list of port
|
||||||
|
if not data or "ports" not in data:
|
||||||
|
logger.info(f"Unvalid request didn't specified a ports list (body is : {request.body}")
|
||||||
|
return json_response({
|
||||||
|
"status": "error",
|
||||||
|
"code": "error_no_ports_list",
|
||||||
|
"content": "Request must specify a list of ports to check",
|
||||||
|
}, status=400)
|
||||||
|
elif not isinstance(data["ports"], list) or any(not is_port_number(p) for p in data["ports"]) or len(data["ports"]) > 30 or data["ports"] == []:
|
||||||
|
logger.info(f"Invalid request, ports list is not an actual list of ports, or is too long : {request.body}")
|
||||||
|
return json_response({
|
||||||
|
"status": "error",
|
||||||
|
"code": "error_invalid_ports_list",
|
||||||
|
"content": "This is not an acceptable port list : ports must be between 0 and 65535 and at most 30 ports can be checked",
|
||||||
|
}, status=400)
|
||||||
|
|
||||||
|
ports = set(data["ports"]) # Keep only a set so that we get unique ports
|
||||||
|
|
||||||
|
# ############################################# #
|
||||||
|
# Run the actual check #
|
||||||
|
# ############################################# #
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
for port in ports:
|
||||||
|
result[port] = await check_port_is_open(ip, port)
|
||||||
|
|
||||||
|
return json_response({"status": "ok", "ports": result})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
async def main(request):
|
async def main(request):
|
||||||
return html("You aren't really supposed to use this website using your browser.<br><br>It's a small server to check if a YunoHost instance can be reached by http before trying to instal a LE certificate.")
|
return html("You aren't really supposed to use this website using your browser.<br><br>It's a small server to check if a YunoHost instance can be reached by http before trying to instal a LE certificate.")
|
||||||
|
|
Loading…
Reference in a new issue