generated from nathanwoodburn/python-webserver-template
Compare commits
2 Commits
bc3fc2862e
...
1c7093307a
Author | SHA1 | Date | |
---|---|---|---|
1c7093307a | |||
a26eecf59e |
@ -1,54 +0,0 @@
|
|||||||
name: Build Docker fast
|
|
||||||
run-name: Build Docker Images fast
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
BuildImage:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
|
|
||||||
- name: Login to Docker Registry
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
registry: git.woodburn.au
|
|
||||||
username: nathanwoodburn
|
|
||||||
password: ${{ secrets.DOCKERGIT_TOKEN }}
|
|
||||||
|
|
||||||
- name: Extract metadata
|
|
||||||
id: meta
|
|
||||||
run: |
|
|
||||||
echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT
|
|
||||||
tag=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}
|
|
||||||
tag=${tag//\//-}
|
|
||||||
echo "tag=$tag" >> $GITHUB_OUTPUT
|
|
||||||
tag_num=${GITHUB_RUN_NUMBER}
|
|
||||||
echo "tag_num=$tag_num" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
if [[ "$tag" == "main" ]]; then
|
|
||||||
echo "final_tag=latest" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "final_tag=${tag}-${tag_num}" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
repo=$GITHUB_REPOSITORY
|
|
||||||
repo=${repo#*/}
|
|
||||||
repo=$(echo $repo | tr '[:upper:]' '[:lower:]')
|
|
||||||
echo "repo=$repo" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v4
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
git.woodburn.au/nathanwoodburn/${{ steps.meta.outputs.repo }}:${{ steps.meta.outputs.final_tag }}
|
|
||||||
git.woodburn.au/nathanwoodburn/${{ steps.meta.outputs.repo }}:${{ steps.meta.outputs.tag_num }}
|
|
||||||
git.woodburn.au/nathanwoodburn/${{ steps.meta.outputs.repo }}:${{ steps.meta.outputs.tag }}
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
@ -22,9 +22,10 @@ function getSSL() {
|
|||||||
history.pushState(null, null, url);
|
history.pushState(null, null, url);
|
||||||
|
|
||||||
// Add a loading spinner
|
// Add a loading spinner
|
||||||
document.getElementById("results").innerHTML = `<div class="spinner-border text-primary" role="status">
|
document.getElementById("ssl-results").innerHTML = `<div style="text-align: center;">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span class="visually-hidden">Loading...</span>
|
||||||
</div>`;
|
</div></div>`;
|
||||||
|
|
||||||
|
|
||||||
// Send a GET request to the API
|
// Send a GET request to the API
|
||||||
@ -34,7 +35,7 @@ function getSSL() {
|
|||||||
// Check if the request was successful
|
// Check if the request was successful
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
// Display the results
|
// Display the results
|
||||||
document.getElementById("results").innerHTML = `
|
document.getElementById("ssl-results").innerHTML = `
|
||||||
<div class="card shadow-sm p-4" style="max-width: 950px;margin: auto;">
|
<div class="card shadow-sm p-4" style="max-width: 950px;margin: auto;">
|
||||||
<h2 class="mb-3">SSL Certificate Details</h2>
|
<h2 class="mb-3">SSL Certificate Details</h2>
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
@ -93,7 +94,7 @@ function getSSL() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="dnssec-details mt-2" style="display:none;">
|
<div class="dnssec-details mt-2" style="display:none;">
|
||||||
<div class="card card-body bg-light"><pre class="mb-0" style="white-space: pre-wrap; text-align: left;">${data.dnssec.errors}${data.dnssec.output}</pre>
|
<div class="card card-body bg-light"><pre class="mb-0" style="white-space: pre-wrap; text-align: left;">${data.dnssec.output}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@ -140,13 +141,13 @@ document.querySelector('.dnssec-toggle').addEventListener('click', function() {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Display an error message
|
// Display an error message
|
||||||
document.getElementById("results").innerHTML = `<h2>Error</h2>
|
document.getElementById("ssl-results").innerHTML = `<h2>Error</h2>
|
||||||
<p>${data.message}</p>`;
|
<p>${data.message}</p>`;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
// Display an error message
|
// Display an error message
|
||||||
document.getElementById("results").innerHTML = `<h2>Error</h2>
|
document.getElementById("ssl-results").innerHTML = `<h2>Error</h2>
|
||||||
<p>${error.message}</p>`;
|
<p>${error.message}</p>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -12,16 +12,53 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="centre" style="margin-top: 20px;">
|
<div class="container py-5">
|
||||||
<h1>HNS Tools</h1>
|
<!-- Header Section -->
|
||||||
<h2>Nathan.Woodburn/</h2>
|
<div class="text-center mb-5">
|
||||||
|
<h1 class="display-4 fw-bold text-primary"><a href="/" class="text-decoration-none text-reset">HNS Tools</a></h1>
|
||||||
|
<h2 class="h4 fw-light text-secondary"><a href="https://nathan.woodburn.au" target="_blank" class="text-decoration-none text-reset">Nathan.Woodburn/</a></h2>
|
||||||
|
<div class="border-bottom mt-4 w-50 mx-auto"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tool Card -->
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card shadow border-0 rounded-lg mb-5">
|
||||||
|
<div class="card-header bg-primary text-white py-3">
|
||||||
|
<h2 class="h4 mb-0 text-center">SSL/DANE Checker</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<p class="text-muted mb-4 text-center">Validate SSL certificates and DANE records for any domain</p>
|
||||||
|
|
||||||
|
<div class="input-group mb-3 shadow-sm">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="domain"
|
||||||
|
placeholder="Enter domain name (e.g., nathan.woodburn firewallet)"
|
||||||
|
class="form-control form-control-lg"
|
||||||
|
aria-label="Domain to check"
|
||||||
|
>
|
||||||
|
<button class="btn btn-primary px-4" id="ssl">
|
||||||
|
<i class="bi bi-shield-check me-2"></i>Validate
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center text-muted small">
|
||||||
|
Enter a complete domain name without any slashes or leading .
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Results Container -->
|
||||||
|
<div id="ssl-results" class="animate__animated animate__fadeIn"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="text-center mt-5 pt-4 text-muted">
|
||||||
|
<small>© 2025 <a href="https://nathan.woodburn.au" target="_blank" class="text-decoration-none text-reset">Nathan.Woodburn/</a> - Handshake Name Services Tools</small>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
<div class="centre">
|
</body>
|
||||||
<h2>SSL/DANE Checker</h2>
|
|
||||||
<input type="text" id="domain" placeholder="Domain to check">
|
|
||||||
<button id="ssl">Validate SSL</button>
|
|
||||||
<div id="results" style="margin-top: 20px;"></div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
</html>
|
154
tools.py
154
tools.py
@ -55,74 +55,84 @@ def check_ssl(domain: str):
|
|||||||
certificates = [cert[cert.find("-----BEGIN CERTIFICATE-----"):] for cert in certificates]
|
certificates = [cert[cert.find("-----BEGIN CERTIFICATE-----"):] for cert in certificates]
|
||||||
|
|
||||||
if not certificates:
|
if not certificates:
|
||||||
returns["message"] = "No certificate found on remote webserver"
|
returns["cert"] = {
|
||||||
return returns
|
"cert":"",
|
||||||
|
"domains": [],
|
||||||
cert = certificates[0]
|
"domain": False,
|
||||||
returns["cert"] = {"cert": cert}
|
"expired": False,
|
||||||
|
"valid": False,
|
||||||
with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_cert_file:
|
"expiry_date": ""
|
||||||
temp_cert_file.write(cert)
|
}
|
||||||
temp_cert_file.seek(0)
|
returns["tlsa"] = {
|
||||||
|
"server": "",
|
||||||
tlsa_command = ["openssl","x509","-in",temp_cert_file.name,"-pubkey","-noout","|","openssl","pkey","-pubin","-outform","der","|","openssl","dgst","-sha256","-binary",]
|
"nameserver": "",
|
||||||
|
"match": False
|
||||||
tlsa_process = subprocess.Popen(" ".join(tlsa_command), shell=True, stdout=subprocess.PIPE)
|
}
|
||||||
tlsa_output, _ = tlsa_process.communicate()
|
|
||||||
|
|
||||||
tlsa_server = "3 1 1 " + binascii.hexlify(tlsa_output).decode("utf-8")
|
|
||||||
|
|
||||||
|
|
||||||
returns["tlsa"] = {
|
|
||||||
"server": tlsa_server,
|
|
||||||
"nameserver": "",
|
|
||||||
"match": False
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Get domains
|
|
||||||
cert_obj = x509.load_pem_x509_certificate(cert.encode("utf-8"), default_backend())
|
|
||||||
|
|
||||||
domains = []
|
|
||||||
for ext in cert_obj.extensions:
|
|
||||||
if ext.oid == x509.ExtensionOID.SUBJECT_ALTERNATIVE_NAME:
|
|
||||||
san_list = ext.value.get_values_for_type(x509.DNSName)
|
|
||||||
domains.extend(san_list)
|
|
||||||
|
|
||||||
# Extract the common name (CN) from the subject
|
|
||||||
common_name = cert_obj.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)
|
|
||||||
if common_name:
|
|
||||||
if common_name[0].value not in domains:
|
|
||||||
domains.append(common_name[0].value)
|
|
||||||
|
|
||||||
|
|
||||||
if domains:
|
|
||||||
cert_domains = []
|
|
||||||
for cn in domains:
|
|
||||||
cert_domains.append(cn)
|
|
||||||
if cn == domain:
|
|
||||||
domain_check = True
|
|
||||||
elif cn.startswith("*."):
|
|
||||||
if domain.endswith(cn[1:]):
|
|
||||||
domain_check = True
|
|
||||||
|
|
||||||
returns["cert"]["domains"] = cert_domains
|
|
||||||
returns["cert"]["domain"] = domain_check
|
|
||||||
|
|
||||||
expiry_date = cert_obj.not_valid_after_utc
|
|
||||||
# Check if expiry date is past
|
|
||||||
if expiry_date < datetime.datetime.now(datetime.timezone.utc):
|
|
||||||
returns["cert"]["expired"] = True
|
|
||||||
returns["cert"]["valid"] = False
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
returns["cert"]["expired"] = False
|
|
||||||
returns["cert"]["valid"] = True if domain_check else False
|
cert = certificates[0]
|
||||||
|
returns["cert"] = {"cert": cert}
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_cert_file:
|
||||||
|
temp_cert_file.write(cert)
|
||||||
|
temp_cert_file.seek(0)
|
||||||
|
|
||||||
returns["cert"]["expiry_date"] = expiry_date.strftime("%d %B %Y %H:%M:%S")
|
tlsa_command = ["openssl","x509","-in",temp_cert_file.name,"-pubkey","-noout","|","openssl","pkey","-pubin","-outform","der","|","openssl","dgst","-sha256","-binary",]
|
||||||
|
|
||||||
|
tlsa_process = subprocess.Popen(" ".join(tlsa_command), shell=True, stdout=subprocess.PIPE)
|
||||||
|
tlsa_output, _ = tlsa_process.communicate()
|
||||||
|
|
||||||
|
tlsa_server = "3 1 1 " + binascii.hexlify(tlsa_output).decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
returns["tlsa"] = {
|
||||||
|
"server": tlsa_server,
|
||||||
|
"nameserver": "",
|
||||||
|
"match": False
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Get domains
|
||||||
|
cert_obj = x509.load_pem_x509_certificate(cert.encode("utf-8"), default_backend())
|
||||||
|
|
||||||
|
domains = []
|
||||||
|
for ext in cert_obj.extensions:
|
||||||
|
if ext.oid == x509.ExtensionOID.SUBJECT_ALTERNATIVE_NAME:
|
||||||
|
san_list = ext.value.get_values_for_type(x509.DNSName)
|
||||||
|
domains.extend(san_list)
|
||||||
|
|
||||||
|
# Extract the common name (CN) from the subject
|
||||||
|
common_name = cert_obj.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)
|
||||||
|
if common_name:
|
||||||
|
if common_name[0].value not in domains:
|
||||||
|
domains.append(common_name[0].value)
|
||||||
|
|
||||||
|
|
||||||
|
if domains:
|
||||||
|
cert_domains = []
|
||||||
|
for cn in domains:
|
||||||
|
cert_domains.append(cn)
|
||||||
|
if cn == domain:
|
||||||
|
domain_check = True
|
||||||
|
elif cn.startswith("*."):
|
||||||
|
if domain.endswith(cn[1:]):
|
||||||
|
domain_check = True
|
||||||
|
|
||||||
|
returns["cert"]["domains"] = cert_domains
|
||||||
|
returns["cert"]["domain"] = domain_check
|
||||||
|
|
||||||
|
expiry_date = cert_obj.not_valid_after_utc
|
||||||
|
# Check if expiry date is past
|
||||||
|
if expiry_date < datetime.datetime.now(datetime.timezone.utc):
|
||||||
|
returns["cert"]["expired"] = True
|
||||||
|
returns["cert"]["valid"] = False
|
||||||
|
|
||||||
|
else:
|
||||||
|
returns["cert"]["expired"] = False
|
||||||
|
returns["cert"]["valid"] = True if domain_check else False
|
||||||
|
|
||||||
|
returns["cert"]["expiry_date"] = expiry_date.strftime("%d %B %Y %H:%M:%S")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Check for TLSA record
|
# Check for TLSA record
|
||||||
@ -131,18 +141,12 @@ def check_ssl(domain: str):
|
|||||||
for record in response:
|
for record in response:
|
||||||
tlsa_records.append(str(record))
|
tlsa_records.append(str(record))
|
||||||
|
|
||||||
if not tlsa_records:
|
if tlsa_records:
|
||||||
returns["message"] = "No TLSA record found on DNS"
|
returns["tlsa"]["nameserver"] = tlsa_records[0]
|
||||||
return returns
|
if tlsa_server == tlsa_records[0]:
|
||||||
|
returns["tlsa"]["match"] = True
|
||||||
returns["tlsa"]["nameserver"] = tlsa_records[0]
|
|
||||||
if tlsa_server == tlsa_records[0]:
|
|
||||||
returns["tlsa"]["match"] = True
|
|
||||||
|
|
||||||
|
|
||||||
except:
|
except:
|
||||||
returns["message"] = "No TLSA record found on DNS"
|
returns["tlsa"]["error"] = "No TLSA record found on DNS"
|
||||||
return returns
|
|
||||||
|
|
||||||
# Check if valid
|
# Check if valid
|
||||||
if returns["cert"]["valid"] and returns["tlsa"]["match"] and returns["dnssec"]["valid"]:
|
if returns["cert"]["valid"] and returns["tlsa"]["match"] and returns["dnssec"]["valid"]:
|
||||||
@ -164,6 +168,6 @@ def validate_dnssec(domain):
|
|||||||
command = f"delv @{resolverIP} -a hsd-ksk {domain} A +rtrace +vtrace"
|
command = f"delv @{resolverIP} -a hsd-ksk {domain} A +rtrace +vtrace"
|
||||||
result = subprocess.run(command, shell=True, capture_output=True, text=True)
|
result = subprocess.run(command, shell=True, capture_output=True, text=True)
|
||||||
if "; fully validated" in result.stdout:
|
if "; fully validated" in result.stdout:
|
||||||
return {"valid": True, "message": "DNSSEC is valid", "output": result.stdout, "errors": result.stderr}
|
return {"valid": True, "message": "DNSSEC is valid", "output": result.stderr + result.stdout}
|
||||||
else:
|
else:
|
||||||
return {"valid": False, "message": "DNSSEC is not valid", "output": result.stdout, "errors": result.stderr}
|
return {"valid": False, "message": "DNSSEC is not valid", "output": result.stderr + result.stdout}
|
Loading…
Reference in New Issue
Block a user