1"""Sidecar-backed credential broker with a runtime gitignore canary.
3The implementation follows the plan-216 provider sidecar pattern. Raw values are
4accepted by the backend and returned only to the caller that asks for a named
5credential. Diagnostics, printed output, logging, and exception messages carry
10- oc_connect.workspace (credential_path - the default workspace-anchored sidecar path)
11- oc_diagnostics (_dbg structured logger)
12- git (not imported; run as git check-ignore, the canary that proves the sidecar is gitignored)
14from __future__
import annotations
16from contextlib
import contextmanager
20import secrets
as _secrets
23from collections.abc
import Callable, Iterator, Mapping
24from pathlib
import Path
25from typing
import Protocol
27from oc_diagnostics
import _dbg
29from .workspace
import credential_path
31SECRET_FILE_MODE = 0o600
32_NAME_RE = re.compile(
r"^[a-z][a-z0-9_.-]*$")
33_GIT_TIMEOUT_SECONDS = 5.0
37 """The selected credential file cannot be proven gitignored."""
41 """The credential file is unreadable or malformed."""
45 """A requested credential name has no stored value."""
49 """Storage boundary hidden behind :class:`CredentialBroker`."""
51 def get(self, name: str) -> str |
None: ...
53 def set(self, name: str, value: str) ->
None: ...
55 def delete(self, name: str) ->
None: ...
59 def restore(self, snapshot: bytes |
None) ->
None: ...
63 candidate = path
if path.is_dir()
else path.parent
64 while not candidate.is_dir()
and candidate != candidate.parent:
65 candidate = candidate.parent
70 """Ask git whether *path* is ignored; every probe error fails closed."""
71 target = Path(path).expanduser()
73 target = target.resolve()
75 target = target.absolute()
77 result = subprocess.run(
78 [
"git",
"check-ignore",
"--quiet",
"--", str(target)],
82 timeout=_GIT_TIMEOUT_SECONDS,
85 except (OSError, subprocess.SubprocessError)
as exc:
87 "OC_CONNECT.CREDENTIALS",
89 f
"SECURITY_ERROR: gitignore probe failed for {target}: {type(exc).__name__}",
94 covered = result.returncode == 0
96 "OC_CONNECT.CREDENTIALS",
98 f
"gitignore canary path={target} covered={covered} rc={result.returncode}",
106 path.chmod(stat.S_IRUSR | stat.S_IWUSR)
112 """Atomic JSON sidecar backend guarded by a live git check-ignore probe."""
116 path: str | Path |
None =
None,
118 gitignore_check: Callable[[str | Path], bool] = is_gitignored,
119 root: str | Path |
None =
None,
121 self.
path = Path(path)
if path
is not None else credential_path(root)
127 f
"refusing to use connector credentials at {self.path}: the path is not "
128 "covered by gitignore (checked with 'git check-ignore')"
132 if not self.
path.is_file():
136 value = json.loads(self.
path.read_text(encoding=
"utf-8"))
137 except (OSError, UnicodeDecodeError, json.JSONDecodeError)
as exc:
139 f
"could not parse connector credential sidecar at {self.path} "
140 f
"({type(exc).__name__})"
142 if not isinstance(value, Mapping)
or not all(
143 isinstance(name, str)
and isinstance(item, str)
144 for name, item
in value.items()
147 f
"connector credential sidecar at {self.path} must be a string map"
151 def _write(self, values: Mapping[str, str]) ->
None:
153 self.
path.parent.mkdir(parents=
True, exist_ok=
True)
154 temporary = self.
path.with_name(
155 f
".tmp-{self.path.name}-{_secrets.token_hex(8)}"
158 with temporary.open(
"w", encoding=
"utf-8", newline=
"\n")
as handle:
159 json.dump(dict(sorted(values.items())), handle, indent=2, sort_keys=
True)
162 os.fsync(handle.fileno())
164 os.replace(temporary, self.
path)
167 if temporary.exists():
173 def get(self, name: str) -> str |
None:
174 """Return the raw value for *name*, or ``None`` when absent."""
177 def set(self, name: str, value: str) ->
None:
178 """Store one value without exposing it to diagnostics."""
179 values = self.
_load()
184 """Delete one value; absent names are idempotent."""
185 values = self.
_load()
186 if name
not in values:
192 """Return the exact sidecar bytes for an internal transaction rollback."""
193 if not self.
path.is_file():
197 return self.
path.read_bytes()
198 except OSError
as exc:
200 f
"could not snapshot connector credential sidecar at {self.path} "
201 f
"({type(exc).__name__})"
204 def restore(self, snapshot: bytes |
None) ->
None:
205 """Restore an internal snapshot without exposing its bytes."""
209 self.
path.unlink(missing_ok=
True)
210 except OSError
as exc:
212 f
"could not restore connector credential sidecar at {self.path} "
213 f
"({type(exc).__name__})"
216 self.
path.parent.mkdir(parents=
True, exist_ok=
True)
217 temporary = self.
path.with_name(
218 f
".tmp-{self.path.name}-{_secrets.token_hex(8)}"
221 with temporary.open(
"wb")
as handle:
222 handle.write(snapshot)
224 os.fsync(handle.fileno())
226 os.replace(temporary, self.
path)
229 if temporary.exists():
237 """Name-only application boundary around a credential backend."""
241 backend: CredentialBackend |
None =
None,
243 root: str | Path |
None =
None,
249 """Roll the backend back byte-for-byte if the guarded flow fails."""
250 snapshot = self.
backend.snapshot()
253 except BaseException:
256 "OC_CONNECT.CREDENTIALS",
258 "credential transaction rolled back",
265 if not isinstance(name, str)
or _NAME_RE.fullmatch(name)
is None:
267 "credential name must start with a lowercase letter and contain only "
268 "lowercase letters, digits, dots, dashes, or underscores"
272 def store(self, name: str, value: str) ->
None:
273 """Store one named value; diagnostics record only the name."""
275 if not isinstance(value, str)
or not value.strip():
276 raise ValueError(f
"refusing to store an empty value for {checked_name}")
277 self.
backend.set(checked_name, value.strip())
279 "OC_CONNECT.CREDENTIALS",
281 f
"stored connector credential name={checked_name}",
286 """Return one raw value to its transport caller and nowhere else."""
288 value = self.
backend.get(checked_name)
291 f
"connector credential {checked_name!r} is not configured"
294 "OC_CONNECT.CREDENTIALS",
296 f
"retrieved connector credential name={checked_name}",
302 """Delete one named value without exposing its former content."""
306 "OC_CONNECT.CREDENTIALS",
308 f
"deleted connector credential name={checked_name}",
316 "CredentialFileError",
317 "CredentialNotFoundError",
318 "CredentialPathError",
319 "SidecarCredentialBackend",
str|None get(self, str name)
None restore(self, bytes|None snapshot)
bytes|None snapshot(self)
None delete(self, str name)
None set(self, str name, str value)
str retrieve(self, str name)
str _validate_name(str name)
None store(self, str name, str value)
None __init__(self, CredentialBackend|None backend=None, *, str|Path|None root=None)
None delete(self, str name)
Iterator[None] transaction(self)
None _write(self, Mapping[str, str] values)
str|None get(self, str name)
None restore(self, bytes|None snapshot)
None _assert_safe_path(self)
None set(self, str name, str value)
None __init__(self, str|Path|None path=None, *, Callable[[str|Path], bool] gitignore_check=is_gitignored, str|Path|None root=None)
None delete(self, str name)
dict[str, str] _load(self)
bytes|None snapshot(self)
Path _existing_ancestor(Path path)
bool is_gitignored(str|Path path)
None _chmod_private(Path path)