feat(enrollment): add import_license_package supporting .lic files and seat renewal expansion
This commit is contained in:
@@ -654,16 +654,90 @@ def sign_ca_csr_with_seats(
|
|||||||
return signed_cert.public_bytes(serialization.Encoding.PEM).decode("utf-8")
|
return signed_cert.public_bytes(serialization.Encoding.PEM).decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def import_license_package(
|
||||||
|
lic_content_or_path: str,
|
||||||
|
cert_dir: str = "certs"
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Imports and validates a license package or certificate.
|
||||||
|
Supports:
|
||||||
|
1. Activated JSON .lic package containing 'ca_cert' and optional 'master_root_ca'
|
||||||
|
2. Raw PEM Subordinate CA certificate
|
||||||
|
3. File path pointing to either of the above
|
||||||
|
Saves the signed CA certificate to cert_dir/ca.crt (backing up any old ca.crt),
|
||||||
|
and if 'master_root_ca' is present, saves it to cert_dir/upstream_ca.crt for mTLS trust.
|
||||||
|
Returns extracted license attributes and PEM contents.
|
||||||
|
"""
|
||||||
|
os.makedirs(cert_dir, exist_ok=True)
|
||||||
|
raw = lic_content_or_path
|
||||||
|
if os.path.exists(lic_content_or_path) and os.path.isfile(lic_content_or_path):
|
||||||
|
with open(lic_content_or_path, "r", encoding="utf-8") as f:
|
||||||
|
raw = f.read()
|
||||||
|
|
||||||
|
ca_pem = None
|
||||||
|
master_root_ca_pem = None
|
||||||
|
|
||||||
|
trimmed = raw.strip()
|
||||||
|
if trimmed.startswith("{"):
|
||||||
|
try:
|
||||||
|
data = json.loads(trimmed)
|
||||||
|
ca_pem = data.get("ca_cert") or data.get("cert_pem")
|
||||||
|
master_root_ca_pem = data.get("master_root_ca") or data.get("upstream_ca")
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"Failed to parse JSON .lic package: {e}")
|
||||||
|
elif "-----BEGIN CERTIFICATE-----" in trimmed:
|
||||||
|
ca_pem = trimmed
|
||||||
|
|
||||||
|
if not ca_pem:
|
||||||
|
raise ValueError("Provided license package does not contain a valid 'ca_cert' PEM certificate.")
|
||||||
|
|
||||||
|
info = extract_license_info_from_cert(ca_pem)
|
||||||
|
if not info.get("valid"):
|
||||||
|
raise ValueError(f"Invalid CA certificate in license: {info.get('error')}")
|
||||||
|
|
||||||
|
# Backup existing ca.crt if present
|
||||||
|
ca_path = os.path.join(cert_dir, "ca.crt")
|
||||||
|
if os.path.exists(ca_path):
|
||||||
|
ts = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%d_%H%M%S")
|
||||||
|
try:
|
||||||
|
os.replace(ca_path, f"{ca_path}.{ts}.bak")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
with open(ca_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(ca_pem)
|
||||||
|
|
||||||
|
# Save upstream_ca.crt if master_root_ca is present
|
||||||
|
if master_root_ca_pem:
|
||||||
|
upstream_path = os.path.join(cert_dir, "upstream_ca.crt")
|
||||||
|
with open(upstream_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(master_root_ca_pem)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"ca_cert_pem": ca_pem,
|
||||||
|
"master_root_ca_pem": master_root_ca_pem,
|
||||||
|
"license_info": info,
|
||||||
|
"seats": info.get("seats"),
|
||||||
|
"license_id": info.get("license_id"),
|
||||||
|
"issuer_url": info.get("issuer_url"),
|
||||||
|
"fingerprint": info.get("fingerprint"),
|
||||||
|
"expires_at": info.get("not_valid_after")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def renew_ca_cert_mtls(
|
def renew_ca_cert_mtls(
|
||||||
cert_dir: str = "certs",
|
cert_dir: str = "certs",
|
||||||
license_server_url: str = "https://logar.eibl.tech",
|
license_server_url: str = "https://logar.eibl.tech",
|
||||||
upstream_ca_pem: Optional[str] = None,
|
upstream_ca_pem: Optional[str] = None,
|
||||||
|
requested_seats: Optional[int] = None,
|
||||||
|
seat_increment: int = 0,
|
||||||
timeout: int = 15,
|
timeout: int = 15,
|
||||||
verify_tls: bool = True
|
verify_tls: bool = True
|
||||||
) -> Tuple[bool, str, Optional[str]]:
|
) -> Tuple[bool, str, Optional[str]]:
|
||||||
"""
|
"""
|
||||||
Connects to the master License Authority using mutual TLS (mTLS), authenticating
|
Connects to the master License Authority using mutual TLS (mTLS), authenticating
|
||||||
with the server's current ca.crt and ca.key, and requests an automated renewal.
|
with the server's current ca.crt and ca.key, and requests an automated renewal.
|
||||||
|
Supports requesting seat quota increases or increments during renewal.
|
||||||
If renewed, writes backup of previous ca.crt and saves new ca.crt in cert_dir.
|
If renewed, writes backup of previous ca.crt and saves new ca.crt in cert_dir.
|
||||||
Returns (success, message, new_ca_cert_pem).
|
Returns (success, message, new_ca_cert_pem).
|
||||||
"""
|
"""
|
||||||
@@ -681,11 +755,17 @@ def renew_ca_cert_mtls(
|
|||||||
current_pem = f.read()
|
current_pem = f.read()
|
||||||
info = extract_license_info_from_cert(current_pem)
|
info = extract_license_info_from_cert(current_pem)
|
||||||
|
|
||||||
payload = json.dumps({
|
payload_dict = {
|
||||||
"license_id": info.get("license_id"),
|
"license_id": info.get("license_id"),
|
||||||
"fingerprint": info.get("fingerprint"),
|
"fingerprint": info.get("fingerprint"),
|
||||||
"current_cert": current_pem
|
"current_cert": current_pem
|
||||||
}).encode("utf-8")
|
}
|
||||||
|
if requested_seats is not None:
|
||||||
|
payload_dict["requested_seats"] = requested_seats
|
||||||
|
if seat_increment:
|
||||||
|
payload_dict["seat_increment"] = seat_increment
|
||||||
|
|
||||||
|
payload = json.dumps(payload_dict).encode("utf-8")
|
||||||
|
|
||||||
path = parsed.path or "/api/license/renew"
|
path = parsed.path or "/api/license/renew"
|
||||||
headers = {
|
headers = {
|
||||||
@@ -730,6 +810,13 @@ def renew_ca_cert_mtls(
|
|||||||
with open(ca_cert_path, "w", encoding="utf-8") as f:
|
with open(ca_cert_path, "w", encoding="utf-8") as f:
|
||||||
f.write(new_cert_pem)
|
f.write(new_cert_pem)
|
||||||
|
|
||||||
|
# Update upstream_ca.crt if master root CA certificate was provided
|
||||||
|
master_root_pem = result.get("master_root_ca")
|
||||||
|
if master_root_pem:
|
||||||
|
upstream_path = os.path.join(cert_dir, "upstream_ca.crt")
|
||||||
|
with open(upstream_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(master_root_pem)
|
||||||
|
|
||||||
return True, "Certificate renewed successfully via mTLS", new_cert_pem
|
return True, "Certificate renewed successfully via mTLS", new_cert_pem
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user