mirror of
https://github.com/YunoHost/check-http.git
synced 2024-09-03 19:56:42 +02:00
Implement nonce mechanism
This commit is contained in:
parent
b593e6ba77
commit
9a68ffabc1
1 changed files with 35 additions and 7 deletions
42
server.py
42
server.py
|
@ -1,3 +1,4 @@
|
||||||
|
import re
|
||||||
import time
|
import time
|
||||||
import asyncio
|
import asyncio
|
||||||
import aiodns
|
import aiodns
|
||||||
|
@ -65,8 +66,13 @@ async def query_dns(host, dns_entry_type):
|
||||||
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
|
||||||
server is hosted on our infrastructure. The expected request body is:
|
server is hosted on our infrastructure. The request is expected to be a
|
||||||
{"domain": "domain-to-check.tld"} and the method POST
|
POST request with a body like {"domain": "domain-to-check.tld",
|
||||||
|
"nonce": "1234567890abcdef" }
|
||||||
|
|
||||||
|
The nonce value is a single-use ID, and we will try to reach
|
||||||
|
http://domain.tld/.well-known/ynh-{nonce} which should return 200 if we
|
||||||
|
are indeed reaching the right server.
|
||||||
|
|
||||||
The general workflow is the following:
|
The general workflow is the following:
|
||||||
|
|
||||||
|
@ -75,7 +81,7 @@ async def check_http(request):
|
||||||
- get json from body and domain from it
|
- get json from body and domain from it
|
||||||
- check for domain based rate limit (see RATE_LIMIT_SECONDS value)
|
- check for domain based rate limit (see RATE_LIMIT_SECONDS value)
|
||||||
- check domain is in valid format
|
- check domain is in valid format
|
||||||
- now try to do an http request on the ip using the domain as target host
|
- try to do an http request on the ip (using the domain as target host) for the page /.well-known/ynh-diagnosis/{nonce}
|
||||||
- answer saying if the domain can be reached
|
- answer saying if the domain can be reached
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -83,6 +89,10 @@ async def check_http(request):
|
||||||
now = time.time()
|
now = time.time()
|
||||||
clear_rate_limit_db(now)
|
clear_rate_limit_db(now)
|
||||||
|
|
||||||
|
# ############################################# #
|
||||||
|
# Validate request and extract the parameters #
|
||||||
|
# ############################################# #
|
||||||
|
|
||||||
ip = request.ip
|
ip = request.ip
|
||||||
|
|
||||||
check_rate_limit_ip = check_rate_limit(ip, now)
|
check_rate_limit_ip = check_rate_limit(ip, now)
|
||||||
|
@ -99,12 +109,12 @@ async def check_http(request):
|
||||||
"content": "Invalid usage, body isn't proper json",
|
"content": "Invalid usage, body isn't proper json",
|
||||||
}, status=400)
|
}, status=400)
|
||||||
|
|
||||||
if not data or "domain" not in data:
|
if not data or "domain" not in data or "nonce" not in data:
|
||||||
logger.info(f"Unvalid request didn't specified a domain (body is : {request.body}")
|
logger.info(f"Unvalid request didn't specified a domain and a nonce id (body is : {request.body}")
|
||||||
return json_response({
|
return json_response({
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"code": "error_no_domain",
|
"code": "error_no_domain",
|
||||||
"content": "Request must specify a domain",
|
"content": "Request must specify a domain and a nonce",
|
||||||
}, status=400)
|
}, status=400)
|
||||||
|
|
||||||
domain = data["domain"]
|
domain = data["domain"]
|
||||||
|
@ -121,14 +131,32 @@ async def check_http(request):
|
||||||
"content": "domain is not in the right format (do not include http:// or https://)",
|
"content": "domain is not in the right format (do not include http:// or https://)",
|
||||||
}, status=400)
|
}, status=400)
|
||||||
|
|
||||||
|
nonce = data["nonce"]
|
||||||
|
|
||||||
|
# nonce id is arbitrarily defined to be a
|
||||||
|
# 16-digit hexadecimal string
|
||||||
|
if not re.match(r"^[a-f0-9]{16}$", nonce):
|
||||||
|
logger.info(f"Invalid request, is not in the right format (nonce is : {nonce})")
|
||||||
|
return json_response({
|
||||||
|
"status": "error",
|
||||||
|
"code": "error_nonce_bad_format",
|
||||||
|
"content": "nonce is not in the right format (it should be a 16-digit hexadecimal string)",
|
||||||
|
}, status=400)
|
||||||
|
|
||||||
|
# ############################################# #
|
||||||
|
# Run the actual check #
|
||||||
|
# ############################################# #
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
try:
|
try:
|
||||||
async with session.get("http://" + ip,
|
url = "http://" + ip + "/.well-known/ynh-diagnosis/" + nonce
|
||||||
|
async with session.get(url,
|
||||||
headers={"Host": domain},
|
headers={"Host": domain},
|
||||||
timeout=aiohttp.ClientTimeout(total=30)) as response:
|
timeout=aiohttp.ClientTimeout(total=30)) as response:
|
||||||
# XXX in the futur try to do a double check with the server to
|
# XXX in the futur try to do a double check with the server to
|
||||||
# see if the correct content is get
|
# see if the correct content is get
|
||||||
await response.text()
|
await response.text()
|
||||||
|
assert response.status == 200
|
||||||
logger.info(f"Success when checking http access for {domain} asked by {ip}")
|
logger.info(f"Success when checking http access for {domain} asked by {ip}")
|
||||||
# TODO various kind of errors
|
# TODO various kind of errors
|
||||||
except aiohttp.client_exceptions.ClientConnectorError:
|
except aiohttp.client_exceptions.ClientConnectorError:
|
||||||
|
|
Loading…
Reference in a new issue