-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathissue_cert.py
More file actions
170 lines (147 loc) · 6.57 KB
/
issue_cert.py
File metadata and controls
170 lines (147 loc) · 6.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
from playwright.sync_api import sync_playwright
import time
import logging
import sys
import subprocess
import os
import re
from urllib.parse import urlparse
from datetime import datetime, timedelta
from cryptography import x509
from cryptography.hazmat.backends import default_backend
import ssl
import socket
# --- Configuration ---
PRINTER_URL = os.environ.get("PRINTER_URL")
PIN = os.environ.get("PRINTER_PIN")
EMAIL = os.environ.get("CERTBOT_EMAIL")
# We hardcode the path to the config dir we will create in setup.sh
CLOUDFLARE_INI_PATH = "/etc/ojp91xx-cert-renew/cloudflare.ini"
RENEWAL_THRESHOLD_DAYS = int(os.environ.get("RENEWAL_THRESHOLD_DAYS", 30))
DOWNLOAD_TIMEOUT = 30
CERT_INVALID_MARKER = "cert_invalid.flag"
if not all([PRINTER_URL, PIN, EMAIL]):
print("CRITICAL: Missing required env vars (PRINTER_URL, PRINTER_PIN, CERTBOT_EMAIL)")
sys.exit(1)
# --- Setup Logger ---
logger = logging.getLogger("hp_certbot")
logger.setLevel(logging.INFO)
handler = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
# --- Certificate Expiration Check on Printer ---
def get_printer_cert_expiration(domain):
try:
context = ssl.create_default_context()
with context.wrap_socket(socket.socket(), server_hostname=domain) as conn:
conn.settimeout(5)
conn.connect((domain, 443))
der_cert = conn.getpeercert(binary_form=True)
cert = x509.load_der_x509_certificate(der_cert, backend=default_backend())
return cert.not_valid_after
except Exception as e:
logger.warning(f"Failed to retrieve printer certificate: {e}")
try:
with open(CERT_INVALID_MARKER, "w") as f:
f.write("cert invalid\n")
except Exception as write_err:
logger.warning(f"Failed to write marker file: {write_err}")
return None
# --- Main Script ---
def generate_csr(verbose=False, staging=False, allow_invalid_cert=False, force_new=False):
if verbose:
logger.setLevel(logging.DEBUG)
parsed_url = urlparse(PRINTER_URL)
printer_domain = parsed_url.hostname
if not printer_domain:
logger.error("Could not extract domain from PRINTER_URL.")
sys.exit(1)
if not force_new:
expiration = get_printer_cert_expiration(printer_domain)
if expiration:
days_left = (expiration - datetime.utcnow()).days
logger.info(f"Current printer certificate expires in {days_left} day(s).")
if days_left >= RENEWAL_THRESHOLD_DAYS:
logger.info("Certificate is still valid. No renewal needed.")
try:
if os.path.exists(CERT_INVALID_MARKER):
os.remove(CERT_INVALID_MARKER)
except Exception as e:
logger.warning(f"Failed to clean up marker file: {e}")
return
else:
logger.warning("Certificate check failed (likely invalid/self-signed). Forcing insecure mode for Playwright.")
allow_invalid_cert = True # <--- Force Playwright to ignore errors
days_left = 0
with sync_playwright() as p:
logger.debug("Launching browser in headless mode...")
browser = p.chromium.launch(headless=True)
context = browser.new_context(accept_downloads=True, ignore_https_errors=allow_invalid_cert)
page = context.new_page()
logger.info("Navigating to printer login page...")
page.goto(PRINTER_URL)
logger.info("Logging in...")
page.get_by_role("menuitem", name="Security").click()
page.locator("#menu-security-certificateManagement").get_by_role("navigation", name="Certificate Management").click()
page.get_by_role("textbox", name="Enter PIN").fill(PIN)
page.get_by_role("button", name="Sign In").click()
logger.info("Navigating to CSR creation...")
page.get_by_text("OU=", exact=False).first.click()
#page.get_by_text(f"CN={printer_domain}", exact=True).click()
page.get_by_text(re.compile(r"CN=.*")).first.click()
page.get_by_text("Create").click()
page.locator("#mat-select-value-5").click()
page.get_by_text("Create Certificate Signing").click()
page.get_by_role("button", name="Next").click()
page.get_by_role("button", name="Create").click()
logger.info("Waiting for CSR download...")
with page.expect_download(timeout=DOWNLOAD_TIMEOUT * 1000) as download_info:
page.get_by_role("button", name="Save").click()
download = download_info.value
csr_path = download.path()
timestamp = int(time.time())
final_csr_path = f"csr_{timestamp}.pem"
download.save_as(final_csr_path)
logger.info(f"CSR saved to: {final_csr_path}")
page.get_by_role("button", name="OK").click()
browser.close()
logger.debug("Browser closed.")
logger.info("Requesting certificate via certbot...")
certbot_cmd = [
"/usr/bin/certbot", "certonly",
"--csr", final_csr_path,
"--dns-cloudflare",
"--dns-cloudflare-credentials", CLOUDFLARE_INI_PATH,
"--email", EMAIL,
"--agree-tos",
"--non-interactive",
"-d", printer_domain
]
if staging:
certbot_cmd.append("--test-cert")
logger.debug("Running certbot command:")
logger.debug(" ".join(certbot_cmd))
result = subprocess.run(" ".join(certbot_cmd), shell=True, capture_output=True, text=True)
if result.returncode != 0:
logger.error("Certbot failed:")
logger.error(result.stdout.strip())
logger.error(result.stderr.strip())
return
logger.info("Certificate issued successfully.")
cwd = os.getcwd()
for fname in os.listdir(cwd):
if fname.startswith("000") and fname.endswith(".pem"):
src_path = os.path.join(cwd, fname)
dst_path = os.path.join(cwd, f"issued_{fname}")
try:
os.rename(src_path, dst_path)
logger.info(f"Moved {fname} to {dst_path}")
except Exception as e:
logger.warning(f"Failed to move {fname}: {e}")
if __name__ == "__main__":
verbose_flag = "--verbose" in sys.argv or "--debug" in sys.argv
staging_flag = "--staging" in sys.argv
allow_invalid_cert_flag = "--insecure" in sys.argv or "--ignore-https-errors" in sys.argv
force_flag = "--force-new" in sys.argv
generate_csr(verbose=verbose_flag, staging=staging_flag, allow_invalid_cert=allow_invalid_cert_flag, force_new=force_flag)