Add NPM endpoint verification
This commit is contained in:
@@ -6,9 +6,11 @@ from ...adapters import (
|
||||
Capability,
|
||||
CapabilityOperation,
|
||||
DiscoveredObject,
|
||||
NormalizedObject,
|
||||
ObjectReference,
|
||||
)
|
||||
from ...npm_client import NpmClient
|
||||
from ...object_normalizer import normalize_discovered_object
|
||||
|
||||
|
||||
_PROXY_HOST_FIELDS = (
|
||||
@@ -36,10 +38,18 @@ class Plugin:
|
||||
PROXY_HOSTS,
|
||||
CapabilityOperation.DISCOVER,
|
||||
),
|
||||
Capability(
|
||||
PROXY_HOSTS,
|
||||
CapabilityOperation.VERIFY,
|
||||
),
|
||||
Capability(
|
||||
CERTIFICATES,
|
||||
CapabilityOperation.DISCOVER,
|
||||
),
|
||||
Capability(
|
||||
CERTIFICATES,
|
||||
CapabilityOperation.VERIFY,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -74,92 +84,156 @@ class Plugin:
|
||||
f"object type {object_type!r}"
|
||||
)
|
||||
|
||||
def _discover_proxy_hosts(self) -> list[DiscoveredObject]:
|
||||
discovered: list[DiscoveredObject] = []
|
||||
def _proxy_host_from_row(
|
||||
self,
|
||||
row: dict[str, Any],
|
||||
) -> DiscoveredObject:
|
||||
payload = {
|
||||
field: row.get(field)
|
||||
for field in _PROXY_HOST_FIELDS
|
||||
}
|
||||
|
||||
for row in self.client.proxy_hosts():
|
||||
payload = {
|
||||
field: row.get(field)
|
||||
for field in _PROXY_HOST_FIELDS
|
||||
}
|
||||
certificate_id = row.get("certificate_id")
|
||||
certificate_id = row.get("certificate_id")
|
||||
|
||||
if certificate_id in (None, "", 0, "0"):
|
||||
references = ()
|
||||
else:
|
||||
if isinstance(certificate_id, bool):
|
||||
raise RuntimeError(
|
||||
"NPM proxy host has invalid certificate_id"
|
||||
)
|
||||
|
||||
certificate_local_key = str(
|
||||
certificate_id
|
||||
).strip()
|
||||
|
||||
if not certificate_local_key:
|
||||
raise RuntimeError(
|
||||
"NPM proxy host has invalid certificate_id"
|
||||
)
|
||||
|
||||
references = (
|
||||
ObjectReference(
|
||||
relation="certificate",
|
||||
object_type=CERTIFICATES,
|
||||
local_key=certificate_local_key,
|
||||
),
|
||||
if certificate_id in (None, "", 0, "0"):
|
||||
references = ()
|
||||
else:
|
||||
if isinstance(certificate_id, bool):
|
||||
raise RuntimeError(
|
||||
"NPM proxy host has invalid certificate_id"
|
||||
)
|
||||
|
||||
discovered.append(
|
||||
DiscoveredObject(
|
||||
object_type=PROXY_HOSTS,
|
||||
local_key=self._local_key(row, "proxy host"),
|
||||
payload=payload,
|
||||
references=references,
|
||||
certificate_local_key = str(
|
||||
certificate_id
|
||||
).strip()
|
||||
|
||||
if not certificate_local_key:
|
||||
raise RuntimeError(
|
||||
"NPM proxy host has invalid certificate_id"
|
||||
)
|
||||
|
||||
references = (
|
||||
ObjectReference(
|
||||
relation="certificate",
|
||||
object_type=CERTIFICATES,
|
||||
local_key=certificate_local_key,
|
||||
),
|
||||
)
|
||||
|
||||
return discovered
|
||||
return DiscoveredObject(
|
||||
object_type=PROXY_HOSTS,
|
||||
local_key=self._local_key(
|
||||
row,
|
||||
"proxy host",
|
||||
),
|
||||
payload=payload,
|
||||
references=references,
|
||||
)
|
||||
|
||||
def _discover_certificates(self) -> list[DiscoveredObject]:
|
||||
discovered: list[DiscoveredObject] = []
|
||||
def _certificate_from_row(
|
||||
self,
|
||||
row: dict[str, Any],
|
||||
) -> DiscoveredObject:
|
||||
meta = row.get("meta")
|
||||
if not isinstance(meta, dict):
|
||||
meta = {}
|
||||
|
||||
for row in self.client.certificates():
|
||||
meta = row.get("meta")
|
||||
if not isinstance(meta, dict):
|
||||
meta = {}
|
||||
|
||||
payload = {
|
||||
return DiscoveredObject(
|
||||
object_type=CERTIFICATES,
|
||||
local_key=self._local_key(
|
||||
row,
|
||||
"certificate",
|
||||
),
|
||||
payload={
|
||||
"domain_names": list(
|
||||
row.get("domain_names") or []
|
||||
),
|
||||
"provider": str(row.get("provider") or ""),
|
||||
"nice_name": str(row.get("nice_name") or ""),
|
||||
"provider": str(
|
||||
row.get("provider") or ""
|
||||
),
|
||||
"nice_name": str(
|
||||
row.get("nice_name") or ""
|
||||
),
|
||||
"valid_from": row.get("valid_from"),
|
||||
"expires_on": row.get("expires_on"),
|
||||
"cluster_managed": (
|
||||
meta.get("cluster_managed") is True
|
||||
),
|
||||
"acme_renew": meta.get("acme_renew") is True,
|
||||
"acme_renew": (
|
||||
meta.get("acme_renew") is True
|
||||
),
|
||||
"dns_challenge": (
|
||||
meta.get("dns_challenge") is True
|
||||
),
|
||||
}
|
||||
discovered.append(
|
||||
DiscoveredObject(
|
||||
object_type=CERTIFICATES,
|
||||
local_key=self._local_key(row, "certificate"),
|
||||
payload=payload,
|
||||
)
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
return discovered
|
||||
def _discover_proxy_hosts(self) -> list[DiscoveredObject]:
|
||||
return [
|
||||
self._proxy_host_from_row(row)
|
||||
for row in self.client.proxy_hosts()
|
||||
]
|
||||
|
||||
def _discover_certificates(self) -> list[DiscoveredObject]:
|
||||
return [
|
||||
self._certificate_from_row(row)
|
||||
for row in self.client.certificates()
|
||||
]
|
||||
|
||||
def apply(self, action):
|
||||
raise RuntimeError(
|
||||
f"{self.endpoint.identity} does not support apply yet"
|
||||
)
|
||||
|
||||
def verify(self, object):
|
||||
def verify(self, object: NormalizedObject) -> bool:
|
||||
if object.local_key is None:
|
||||
return False
|
||||
|
||||
if object.object_type == PROXY_HOSTS:
|
||||
row = self.client.proxy_host(
|
||||
object.local_key
|
||||
)
|
||||
actual = normalize_discovered_object(
|
||||
self._proxy_host_from_row(row)
|
||||
)
|
||||
|
||||
return actual == object
|
||||
|
||||
if object.object_type == CERTIFICATES:
|
||||
row = self.client.certificate(
|
||||
object.local_key
|
||||
)
|
||||
actual = normalize_discovered_object(
|
||||
self._certificate_from_row(row)
|
||||
)
|
||||
|
||||
if actual.local_key != object.local_key:
|
||||
return False
|
||||
|
||||
if actual.logical_key != object.logical_key:
|
||||
return False
|
||||
|
||||
# Provider, display name, and renewal flags describe how
|
||||
# this NPM endpoint manages the certificate. They are not
|
||||
# portable certificate identity. A copied Let's Encrypt
|
||||
# certificate becomes provider="other" on the recipient.
|
||||
#
|
||||
# The portable verification state is the domain identity
|
||||
# plus the certificate validity period exposed by NPM.
|
||||
for field in (
|
||||
"domain_names",
|
||||
"valid_from",
|
||||
"expires_on",
|
||||
):
|
||||
if (
|
||||
actual.payload.get(field)
|
||||
!= object.payload.get(field)
|
||||
):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
raise RuntimeError(
|
||||
f"{self.endpoint.identity} does not support verify yet"
|
||||
f"{self.endpoint.identity} cannot verify "
|
||||
f"object type {object.object_type!r}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user