added aws_lambda code

This commit is contained in:
2026-08-16 16:59:37 +05:30
parent 50fded9d06
commit 220b261263
6 changed files with 326 additions and 74 deletions
+62
View File
@@ -0,0 +1,62 @@
"""
This module contains a Lambda function that checks if domains are reachable and retrieves SSL information.
Amazon will bill for the wait time of this function, timeout is set to 10 seconds.
TODO: Implement a loop with asynchronous checking of multiple domains.
"""
import requests
import ssl
import socket
from datetime import datetime
def lambda_handler(event, context):
"""
Args:
event (dict):
The event data passed to the Lambda function. Expects a dictionary
with a "hostnames" key containing a list of all hosts to be checked.
context (LambdaContext):
The runtime information of the Lambda function.
"""
max_timeout = 10
remaining_time_in_seconds = context.get_remaining_time_in_millis() / 1000
timeout_in_seconds = min(remaining_time_in_seconds - 1, max_timeout)
results = {}
for hostname in event["hostnames"]:
url = f"https://{hostname}/"
host_result = {}
host_result["https_get_request"] = get_request(url, timeout=timeout_in_seconds)
if host_result["https_get_request"].get("reachable"):
host_result["ssl"] = ssl_info(hostname)
results[hostname] = host_result
return results
def get_request(url, timeout=10):
result = {}
try:
resp = requests.get(url, timeout=timeout)
result["status_code"] = resp.status_code
result["reason"] = resp.reason
result["reachable"] = resp.status_code == 200
except requests.exceptions.RequestException as e:
result["error"] = str(e)
result["reachable"] = False
return result
def ssl_info(hostname, port=443):
try:
ctx = ssl.create_default_context()
with socket.create_connection((hostname, port), timeout=10) as sock:
with ctx.wrap_socket(sock, server_hostname=hostname) as ssock:
cert = ssock.getpeercert()
issuer = dict(x[0] for x in cert['issuer'])
not_after = datetime.strptime(cert['notAfter'], "%b %d %H:%M:%S %Y %Z")
return {
"issuer": issuer.get("organizationName", "Unknown"),
"expiry": not_after.strftime("%Y-%m-%d %H:%M:%S"),
"valid": not_after > datetime.utcnow()
}
except Exception as e:
return {"error": str(e)}
+11
View File
@@ -0,0 +1,11 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowInvokeDomainCheckLambda",
"Effect": "Allow",
"Action": "lambda:InvokeFunction",
"Resource": "arn:aws:lambda:eu-west-1:458835642486:function:domainCheck"
}
]
}
+99
View File
@@ -0,0 +1,99 @@
# This script sets up AWS Roles Anywhere with a custom CA and an IAM role for Lambda invocation.
# Prerequisites:
# - AWS CLI v2 installed, configured and logged in
# - OpenSSL
#
# https://docs.aws.amazon.com/pdfs/rolesanywhere/latest/userguide/rolesanywhere-guide.pdf
#
# Variables (replace with your own values)
$AccountId = "458835642486"
$Region = "eu-west-1"
$LambdaArn = "arn:aws:lambda:eu-west-1:$AccountId:function:domainCheck"
# Paths for certs
$CertDir = Join-Path $PSScriptRoot "rolesanywhere"
New-Item -ItemType Directory -Force -Path $CertDir | Out-Null
#
$TrustPolicyFile = Join-Path $PSScriptRoot "trust-policy.json"
$LambdaPolicyFile = Join-Path $PSScriptRoot "lambda-policy.json"
function Convert-ToAwsFileUrl {
param(
[Parameter(Mandatory)]
[string]$Path
)
# Expand relative paths and resolve directory separators
$full = (Resolve-Path -LiteralPath $Path).Path
# Convert backslashes to forward slashes
$normalized = $full -replace '\\','/'
# Prepend AWS-style file scheme
"file://$normalized"
}
# Generate CA key + certificate
openssl genrsa -out "$CertDir\ca.key" 2048
openssl req -x509 -new -nodes -key "$CertDir\ca.key" -sha256 -days 365 `
-out "$CertDir\ca.pem" -subj "/CN=MyTestCA" `
-addext "basicConstraints=CA:TRUE" `
-addext "keyUsage=keyCertSign,cRLSign"
# Generate client key + certificate signed by CA
openssl genrsa -out "$CertDir\client.key" 2048
openssl req -new -key "$CertDir\client.key" -out "$CertDir\client.csr" -subj "/CN=MyPC"
openssl x509 -req -in "$CertDir\client.csr" -CA "$CertDir\ca.pem" -CAkey "$CertDir\ca.key" `
-CAcreateserial -out "$CertDir\client.pem" -days 365 -sha256
Write-Host "Certificates generated in $CertDir"
# Create Trust Anchor
$TrustAnchorArn = aws rolesanywhere create-trust-anchor `
--region $Region `
--name MyPCCA `
--source "sourceData={x509CertificateData=$(Get-Content -Raw $CertDir\ca.pem)},sourceType=CERTIFICATE_BUNDLE" `
--query 'trustAnchor.trustAnchorArn' --output text
Write-Host "Trust Anchor ARN: $TrustAnchorArn"
# Create IAM Role
$TrustPolicUrl = Convert-ToAwsFileUrl -Path $TrustPolicyFile
aws iam create-role `
--role-name DomainCheckInvokerRole `
--assume-role-policy-document $TrustPolicUrl
Write-Host "IAM Role created: DomainCheckInvokerRole"
# Attach Lambda policy
$LambdaPolicyUrl = Convert-ToAwsFileUrl -Path $LambdaPolicyFile
aws iam put-role-policy `
--role-name DomainCheckInvokerRole `
--policy-name LambdaInvokeDomainCheck `
--policy-document $LambdaPolicyUrl
Write-Host "Policy attached to role: LambdaInvokeDomainCheck"
# Create Profile
$ProfileArn = aws rolesanywhere create-profile `
--region eu-west-1 `
--name DomainCheckProfile `
--role-arns '["arn:aws:iam::458835642486:role/DomainCheckInvokerRole"]' `
--query "profile.profileArn" `
--output text
Write-Host "Profile ARN: $ProfileArn"
# Step 5: Print AWS config block
Write-Host ""
Write-Host "`nPaste the following into your AWS config (~/.aws/config):`n"
Write-Host "[profile rolesanywhere]"
Write-Host "credential_process = `"C:\path\to\aws_signing_helper.exe`" credential-process ^"
Write-Host " --certificate C:\rolesanywhere\client.pem ^"
Write-Host " --private-key C:\rolesanywhere\client.key ^"
Write-Host " --trust-anchor-arn $TrustAnchorArn ^"
Write-Host " --profile-arn $ProfileArn ^"
Write-Host " --role-arn arn:aws:iam::$AccountId:role/$RoleName"
Write-Host "region = $Region"
+12
View File
@@ -0,0 +1,12 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "rolesanywhere.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
+8
View File
@@ -0,0 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}
+134 -74
View File
@@ -4,6 +4,7 @@ import socket
import boto3 import boto3
import dns.resolver import dns.resolver
import dns.reversename
import requests import requests
from ping3 import ping from ping3 import ping
@@ -19,7 +20,12 @@ public_nameserver_ipv4 = "8.8.8.8" # Google DNS
local_nameserver_ipv4 = "192.168.1.10" # NAS IPv4 DNS local_nameserver_ipv4 = "192.168.1.10" # NAS IPv4 DNS
connectivity_test_addresses = ( reverse_lookup_test_addresses = ([
"192.168.1.10", # nas
"192.168.1.11", # homeassistant
])
connectivity_test_addresses = ([
"192.168.1.1", # Router "192.168.1.1", # Router
"192.168.1.10", # NAS "192.168.1.10", # NAS
"192.168.1.11", # Home Assistant on raspberry pi "192.168.1.11", # Home Assistant on raspberry pi
@@ -31,7 +37,8 @@ connectivity_test_addresses = (
"192.168.1.26", # camera "192.168.1.26", # camera
"192.168.1.27", # camera "192.168.1.27", # camera
"192.168.1.28" # camera "192.168.1.28" # camera
) "192.168.1.29" # camera
])
open_ports = ({ open_ports = ({
"192.168.1.1": [80], # Router "192.168.1.1": [80], # Router
@@ -45,55 +52,52 @@ open_ports = ({
"192.168.1.26": [80, 443], # camera "192.168.1.26": [80, 443], # camera
"192.168.1.27": [80, 443], # camera "192.168.1.27": [80, 443], # camera
"192.168.1.28": [80, 443], # camera "192.168.1.28": [80, 443], # camera
"192.168.1.29": [80, 443], # camera
}) })
local_dns_test_addresses = ( local_dns_test_addresses = ([
"google.com", "google.com",
"homeassistant.local",
"nas.local",
"homeassistant.vaishnavi.house", "homeassistant.vaishnavi.house",
"nas.vaishnavi.house", "calendar.vaishnavi.house",
"vaishnavi.house", "cam.vaishnavi.house",
"rossmanith.family" "contacts.vaishnavi.house",
) "drive.vaishnavi.house",
"git.vaishnavi.house",
public_dns_test_addresses = (
"homeassistant.vaishnavi.house", "homeassistant.vaishnavi.house",
"jellyfin.vaishnavi.house",
"nas.vaishnavi.house", "nas.vaishnavi.house",
"vaishnavi.house", "photos.vaishnavi.house",
"rossmanith.family" "vpn.vaishnavi.house",
) "webdav.vaishnavi.house",
])
webserver_test_urls = ( public_dns_test_addresses = ([
"https://rossmanith.family", "rossmanith.family",
"https://vaishnavi.house", "vaishnavi.house",
"https://nas.vaishnavi.house", "calendar.vaishnavi.house",
"https://homeassistant.vaishnavi.house", "cam.vaishnavi.house",
) "contacts.vaishnavi.house",
"drive.vaishnavi.house",
"git.vaishnavi.house",
"homeassistant.vaishnavi.house",
"jellyfin.vaishnavi.house",
"nas.vaishnavi.house",
"photos.vaishnavi.house",
"vpn.vaishnavi.house",
"webdav.vaishnavi.house",
])
webserver_test_domains = ([
"rossmanith.family",
"vaishnavi.house",
"nas.vaishnavi.house",
"homeassistant.vaishnavi.house",
])
def main(): def main():
logger.info("Starting network check...") logger.info("Starting network check...")
access_key = "AKIAWVVF4JB3NPKIZGG5"
secret_key = "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"
lambda_client = boto3.client("lambda", region_name="eu-west-1", aws_access_key_id=access_key, aws_secret_access_key=secret_key)
payload = {
"domains": ["rossmanith.family", "vaishnavi.house"]
}
response = lambda_client.invoke(
FunctionName="arn:aws:lambda:eu-west-1:458835642486:function:domainCheck",
InvocationType="RequestResponse",
Payload=json.dumps(payload)
)
result = json.loads(response["Payload"].read())
print(json.dumps(result, indent=2))
print() print()
logger.info("Checking IPv4 addresses...") logger.info("Checking IPv4 addresses...")
for host, expected_ip in expected_ipv4.items(): for host, expected_ip in expected_ipv4.items():
@@ -118,22 +122,26 @@ def main():
print() print()
logger.info("Checking DNS resolution of local nameserver %s...", local_nameserver_ipv4) logger.info("Checking DNS resolution of local nameserver %s...", local_nameserver_ipv4)
for addr in local_dns_test_addresses: for addr in local_dns_test_addresses:
check_dns(local_nameserver_ipv4, addr) check_dns_forward_lookup(local_nameserver_ipv4, addr)
print()
logger.info("Checking DNS reverse lookup of local nameserver %s...", local_nameserver_ipv4)
for ip in reverse_lookup_test_addresses:
check_dns_reverse_lookup(local_nameserver_ipv4, ip)
print() print()
logger.info("Checking DNS resolution of public nameserver %s...", public_nameserver_ipv4) logger.info("Checking DNS resolution of public nameserver %s...", public_nameserver_ipv4)
for addr in public_dns_test_addresses: for addr in public_dns_test_addresses:
check_dns(public_nameserver_ipv4, addr) check_dns_forward_lookup(public_nameserver_ipv4, addr)
print() print()
logger.info("Checking webserver availability from LAN...") logger.info("Checking webserver availability from LAN...")
for url in webserver_test_urls: for url in webserver_test_domains:
check_webserver_lan(url) check_webserver_lan("https://" + url)
print() print()
logger.info("Checking webserver availability from WAN...") logger.info("Checking webserver availability from WAN...")
for url in webserver_test_urls: check_webserver_wan(webserver_test_domains)
check_webserver_wan(url)
def check_ipv4(host, expected_ip): def check_ipv4(host, expected_ip):
@@ -141,11 +149,11 @@ def check_ipv4(host, expected_ip):
try: try:
resolved_ip = socket.gethostbyname(host) resolved_ip = socket.gethostbyname(host)
if resolved_ip == expected_ip: if resolved_ip == expected_ip:
logger.info("✅ %s has correct IP: %s", host, resolved_ip) logger.info("%s has correct IP: %s", host, resolved_ip)
else: else:
logger.warning("❌ %s IP mismatch: %s", host, resolved_ip) logger.warning("%s IP mismatch: %s", host, resolved_ip)
except socket.gaierror as e: except socket.gaierror as e:
logger.error("❌ %s DNS resolution error: %s", host, e) logger.error("%s DNS resolution error: %s", host, e)
def ping_check(host_ip): def ping_check(host_ip):
@@ -153,60 +161,112 @@ def ping_check(host_ip):
try: try:
ping_time = ping(host_ip, timeout=2) ping_time = ping(host_ip, timeout=2)
if ping_time is not None: if ping_time is not None:
logger.info("✅ %s reachable (ping time: %.2f ms)", host_ip, ping_time * 1000) logger.info("%s reachable (ping time: %.2f ms)", host_ip, ping_time * 1000)
except Exception as e: except Exception as e:
logger.error("❌ %s unreachable: %s", host_ip, e) logger.error("%s unreachable: %s", host_ip, e)
def check_dns(nameserver, addr):
def check_dns_forward_lookup(nameserver, addr):
"""Check DNS resolution.""" """Check DNS resolution."""
resolver = dns.resolver.Resolver() resolver = dns.resolver.Resolver()
resolver.nameservers = [nameserver] resolver.nameservers = [nameserver]
try: try:
answer_ = resolver.resolve(addr, "A") answer_ = resolver.resolve(addr, "A")
logger.info("✅ DNS %s can resolve %s: %s", nameserver, addr, [r.to_text() for r in answer_]) logger.info("DNS %s can resolve %s: %s", nameserver, addr, [r.to_text() for r in answer_])
except Exception as e: except Exception as e:
logger.error("❌ DNS %s cannot resolve %s: %s", nameserver, addr, e) logger.error("DNS %s cannot resolve %s: %s", nameserver, addr, e)
def check_dns_reverse_lookup(nameserver, ip_address):
# Create a resolver instance
resolver = dns.resolver.Resolver()
resolver.nameservers = [nameserver] # explicitly set the DNS server
# Convert IP to reverse DNS name (e.g., 192.168.1.10 → 10.1.168.192.in-addr.arpa)
rev_name = dns.reversename.from_address(ip_address)
try:
# Query PTR record directly from the specified nameserver
answer = resolver.resolve(rev_name, "PTR")
logger.info("DNS %s can reverse lookup %s: %s", nameserver, ip_address, answer[0])
except socket.herror as e:
logger.error("DNS %s cannot reverse lookup %s: %s", nameserver, ip_address, e)
def check_webserver_lan(url): def check_webserver_lan(url):
try: try:
response = requests.get(url, timeout=5, verify=True) response = requests.get(url, timeout=5, verify=True)
if response.ok: if response.ok:
logger.info("✅ %s responded with %d", url, response.status_code) logger.info("%s responded with %d", url, response.status_code)
else: else:
logger.warning("⚠️ %s responded with %d", url, response.status_code) logger.warning("%s responded with %d", url, response.status_code)
except requests.exceptions.SSLError as e: except requests.exceptions.SSLError as e:
logger.error("❌ SSL certificate error for %s: %s", url, e) logger.error("SSL certificate error for %s: %s", url, e)
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
logger.error("❌ Connection failed: %s not reachable.", url) logger.error("Connection failed: %s not reachable.", url)
except requests.exceptions.Timeout: except requests.exceptions.Timeout:
logger.error("❌ Request timed out for %s.", url) logger.error("Request timed out for %s.", url)
except Exception as e: except Exception as e:
logger.error("❌ Unexpected error for %s: %s", url, e) logger.error("Unexpected error for %s: %s", url, e)
def check_webserver_wan(domain): def check_webserver_wan(webserver_test_domains):
try: """
# Use isup.me style check Check webserver availability from WAN using AWS Lambda.
check_url = f"https://isitup.org/{domain.replace('https://','')}.json" This function invokes a Lambda function that checks if the specified hosts are reachable and retrieves SSL information
resp = requests.get(check_url, timeout=10).json() All hosts are checked in a single Lambda invocation to minimize costs.
if resp["status_code"] == 1: """
print(f"✅ {domain} is externally reachable") access_key = "AKIAWVVF4JB3NPKIZGG5"
else: secret_key = "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"
print(f"❌ {domain} is NOT reachable externally") lambda_client = boto3.client("lambda", region_name="eu-west-1", aws_access_key_id=access_key, aws_secret_access_key=secret_key)
except Exception as e: payload = {
print(f"⚠️ Error checking {domain}: {e}") "hostnames": webserver_test_domains
}
response = lambda_client.invoke(
FunctionName="arn:aws:lambda:eu-west-1:458835642486:function:domainCheck",
InvocationType="RequestResponse",
Payload=json.dumps(payload)
)
Response = json.loads(response["Payload"].read())
if Response.get("errorType"):
logger.error("Error occurred while checking domains: %s", Response.get("errorMessage"))
return
for hostname in webserver_test_domains:
host_response = Response.get(hostname)
https_get_request_result = host_response.get("https_get_request", {})
if not https_get_request_result.get("reachable", False):
logger.error("Host %s is not reachable: %s", hostname, https_get_request_result.get("response", "Unknown error"))
continue
logger.info(
"Host %s is reachable. Status code: %s",
hostname, https_get_request_result["status_code"])
cert_info = host_response.get("ssl", {})
if not cert_info:
logger.error("Host %s has no SSL certificate information.", hostname)
continue
if not cert_info["valid"]:
logger.error(
"Host %s has an invalid SSL certificate. Issuer: %s. Expiry: %s. Valid: %s",
hostname,
cert_info["issuer"],
cert_info["expiry"],
cert_info["valid"])
continue
logger.info(
"Host %s has a valid SSL certificate. Issuer: %s, expiry: %s, valid: %s",
hostname,
cert_info["issuer"],
cert_info["expiry"],
cert_info["valid"])
def check_port(addr, port): def check_port(addr, port):
"""Check if a specific port is open on the host.""" """Check if a specific port is open on the host."""
try: try:
with socket.create_connection((addr, port), timeout=2): with socket.create_connection((addr, port), timeout=2):
logger.info("✅ %s:%d is open", addr, port) logger.info("%s:%d is open", addr, port)
except (socket.timeout, ConnectionRefusedError): except (socket.timeout, ConnectionRefusedError):
logger.warning("⚠️ %s:%d is closed or filtered", addr, port) logger.warning("%s:%d is closed or filtered", addr, port)
except Exception as e: except Exception as e:
logger.error("❌ Error checking %s:%d: %s", addr, port, e) logger.error("Error checking %s:%d: %s", addr, port, e)
if __name__ == "__main__": if __name__ == "__main__":