Add fail-closed ACME authority helper injection
This commit is contained in:
+135
-6
@@ -11,6 +11,21 @@ import os
|
||||
|
||||
AUTHORITY_FILE = "/acme_authority/state/acme-authority"
|
||||
|
||||
HELPER_MARKER = "const acmeAuthorityFile ="
|
||||
|
||||
AUTHORITY_HELPER = f"""const acmeAuthorityFile = "{AUTHORITY_FILE}";
|
||||
|
||||
const requireAcmeAuthority = () => {{
|
||||
try {{
|
||||
fs.accessSync(acmeAuthorityFile, fs.constants.R_OK);
|
||||
}} catch {{
|
||||
throw new error.ValidationError(
|
||||
"ACME operations are disabled on this cluster node",
|
||||
);
|
||||
}}
|
||||
}};
|
||||
"""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GateConfig:
|
||||
@@ -65,19 +80,133 @@ def write_source(path: Path, source: str) -> None:
|
||||
path.write_text(source)
|
||||
|
||||
|
||||
def replace_exactly_once(
|
||||
source: str,
|
||||
old: str,
|
||||
new: str,
|
||||
*,
|
||||
description: str,
|
||||
) -> str:
|
||||
count = source.count(old)
|
||||
|
||||
if count != 1:
|
||||
raise RuntimeError(
|
||||
f"{description}: expected anchor exactly once, found {count}"
|
||||
)
|
||||
|
||||
return source.replace(old, new, 1)
|
||||
|
||||
|
||||
def require_fragment(
|
||||
source: str,
|
||||
fragment: str,
|
||||
*,
|
||||
description: str,
|
||||
) -> None:
|
||||
if fragment not in source:
|
||||
raise RuntimeError(
|
||||
f"{description}: required fragment not found"
|
||||
)
|
||||
|
||||
|
||||
def apply_authority_helper(source: str) -> str:
|
||||
"""
|
||||
Install the common requireAcmeAuthority() helper.
|
||||
Install the shared requireAcmeAuthority() helper.
|
||||
|
||||
Implemented in the next development step.
|
||||
The helper is inserted immediately after the top-level fs import so the
|
||||
patch remains close to the dependency it requires.
|
||||
"""
|
||||
raise NotImplementedError("authority helper mutation not implemented yet")
|
||||
|
||||
expected_declaration = (
|
||||
f'const acmeAuthorityFile = "{AUTHORITY_FILE}";'
|
||||
)
|
||||
|
||||
if expected_declaration in source:
|
||||
require_fragment(
|
||||
source,
|
||||
"const requireAcmeAuthority = () => {",
|
||||
description="existing authority helper",
|
||||
)
|
||||
require_fragment(
|
||||
source,
|
||||
"fs.accessSync(acmeAuthorityFile, fs.constants.R_OK);",
|
||||
description="existing authority helper",
|
||||
)
|
||||
|
||||
log("authority_helper=already-present")
|
||||
return source
|
||||
|
||||
if HELPER_MARKER in source:
|
||||
raise RuntimeError(
|
||||
"authority helper already exists but does not match "
|
||||
f"configured authority file {AUTHORITY_FILE}"
|
||||
)
|
||||
|
||||
# Prefer the end of the known ESM import block used by current NPM.
|
||||
# Older CommonJS releases fall back to their fs import.
|
||||
supported_anchors = (
|
||||
'import internalNginx from "./nginx.js";\n',
|
||||
'const fs = require("fs");\n',
|
||||
'const fs = require("node:fs");\n',
|
||||
)
|
||||
|
||||
matches = [
|
||||
anchor
|
||||
for anchor in supported_anchors
|
||||
if anchor in source
|
||||
]
|
||||
|
||||
if len(matches) != 1:
|
||||
raise RuntimeError(
|
||||
"authority helper: expected exactly one supported insertion "
|
||||
f"anchor, found {len(matches)}"
|
||||
)
|
||||
|
||||
anchor = matches[0]
|
||||
replacement = anchor + "\n" + AUTHORITY_HELPER
|
||||
|
||||
source = replace_exactly_once(
|
||||
source,
|
||||
anchor,
|
||||
replacement,
|
||||
description="authority helper",
|
||||
)
|
||||
|
||||
log(f"authority_helper=added anchor={anchor.strip()!r}")
|
||||
return source
|
||||
|
||||
|
||||
def verify(source: str, config: GateConfig) -> None:
|
||||
"""
|
||||
Verify every requested mutation before certificate.js is written.
|
||||
Verify shared patch state.
|
||||
|
||||
Implemented after the individual mutation modules.
|
||||
Individual mutation modules will add their own verification markers as
|
||||
they are implemented.
|
||||
"""
|
||||
raise NotImplementedError("patch verification not implemented yet")
|
||||
|
||||
if config.any_enabled:
|
||||
require_fragment(
|
||||
source,
|
||||
f'const acmeAuthorityFile = "{AUTHORITY_FILE}";',
|
||||
description="authority helper verification",
|
||||
)
|
||||
|
||||
require_fragment(
|
||||
source,
|
||||
"const requireAcmeAuthority = () => {",
|
||||
description="authority helper verification",
|
||||
)
|
||||
|
||||
require_fragment(
|
||||
source,
|
||||
"fs.accessSync(acmeAuthorityFile, fs.constants.R_OK);",
|
||||
description="authority helper verification",
|
||||
)
|
||||
|
||||
require_fragment(
|
||||
source,
|
||||
"ACME operations are disabled on this cluster node",
|
||||
description="authority helper verification",
|
||||
)
|
||||
|
||||
log("verification=passed")
|
||||
|
||||
Reference in New Issue
Block a user