asf-tooling commented on issue #245:
URL: 
https://github.com/apache/tooling-trusted-releases/issues/245#issuecomment-4410414530

   <!-- gofannon-issue-triage-bot v2 -->
   
   **Automated triage** — analyzed at `main@2da7807a`
   
   **Type:** `discussion`  •  **Classification:** `no_action`  •  
**Confidence:** `medium`
   **Application domain(s):** `shared_infrastructure`, `web_api_infrastructure`
   
   ### Summary
   This issue requests integrating ATR secrets with Puppet's eyaml mechanism 
instead of manually managing secrets.ini. The discussion spans configuration 
format (INI vs YAML), mounting strategy (file vs env vars), and hot-reload 
capability. @dave2wave's most recent comment (2026-02-16) assumes writing eyaml 
configuration through to `secrets/curated/secrets.ini` and asks about a 
dedicated ATR SVN ID. Key open questions remain: whether to switch to YAML 
format, what constitutes a 'secret' vs. a configuration value, and how to 
implement hot-reload of secrets at runtime.
   
   ### Where this lives in the code today
   
   #### `atr/config.py` — `_config_secrets` (lines 29-40)
   _needs modification_
   This is the core secrets-loading function that reads from secrets.ini and 
falls back to env vars; it would need modification for YAML support or 
hot-reload.
   
   ```python
   def _config_secrets(key: str, state_dir: str, default: str | None = None, 
cast: type = str) -> str | None:
       secrets_path = os.path.join(state_dir, "secrets", "curated", 
"secrets.ini")
   
       # This code is deprecated and will be removed
       # TODO: Remove this once migrations are no longer likely2026-
       deprecated_path = os.path.join(state_dir, "secrets.ini")
       if os.path.exists(deprecated_path):
           if os.path.exists(secrets_path):
               raise RuntimeError(f"Conflicting secrets files exist: 
{deprecated_path} and {secrets_path}")
           return _config_secrets_get(deprecated_path, key, default, cast, 
allow_not_found=False)
   
       return _config_secrets_get(secrets_path, key, default, cast)
   ```
   
   #### `atr/config.py` — `_config_secrets_get` (lines 43-68)
   _needs modification_
   This function uses decouple.RepositoryIni to parse INI format; would need to 
be replaced or augmented with YAML parsing if format changes.
   
   ```python
   def _config_secrets_get(
       secrets_path: str, key: str, default: str | None = None, cast: type = 
str, allow_not_found: bool = True
   ) -> str | None:
       try:
           repo_ini = decouple.RepositoryIni(secrets_path)
       except FileNotFoundError:
           if allow_not_found is False:
               raise
       else:
           try:
               value = repo_ini[key]
               return cast(value)
           except KeyError:
               pass
   
       # There is no secrets file, or it does not contain the key
       # Try getting the value from environment variables
       sentinel = object()
       # We do not use the cast keyword argument here
       # If we did, it would also be applied to the default sentinel value
       value = decouple.config(key, default=sentinel)
       if value is sentinel:
           return default
       if not isinstance(value, str):
           raise ValueError(f"Secret value for {key} is not a string")
       return cast(value)
   ```
   
   #### `atr/config.py` — `AppConfig` (lines 71-90)
   _needs modification_
   Secrets are resolved at class definition time (module import), making 
hot-reload impossible without restructuring to lazy evaluation or periodic 
refresh.
   
   ```python
   class AppConfig:
       ACCOUNT_CHECK_INTERVAL = decouple.config("ACCOUNT_CHECK_INTERVAL", 
default=300, cast=int)
       ATR_STATUS = decouple.config("ATR_STATUS", default="ALPHA", cast=str)
       DISABLE_CHECK_CACHE = decouple.config("DISABLE_CHECK_CACHE", 
default=False, cast=bool)
       APP_HOST = decouple.config("APP_HOST", default="127.0.0.1")
       SSH_HOST = decouple.config("SSH_HOST", default="0.0.0.0")
       SSH_PORT = decouple.config("SSH_PORT", default=2222, cast=int)
       PROJECT_ROOT = 
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
       STATE_DIR = decouple.config("STATE_DIR", 
default=os.path.join(PROJECT_ROOT, "state"))
       LDAP_BIND_DN = _config_secrets("LDAP_BIND_DN", STATE_DIR, default=None, 
cast=str)
       LDAP_BIND_PASSWORD = _config_secrets("LDAP_BIND_PASSWORD", STATE_DIR, 
default=None, cast=str)
       LOG_LEVEL = decouple.config("LOG_LEVEL", default="INFO", cast=lambda x: 
x.upper())
       LOG_JSON = decouple.config("LOG_JSON", default=False, cast=bool)
       LOG_PUBLIC_KEY = _config_secrets("LOG_PUBLIC_KEY", STATE_DIR, 
default=None, cast=str)
       MAX_SESSION_AGE = decouple.config("MAX_SESSION_AGE", default=60 * 60 * 
72, cast=int)
       PUBSUB_URL = _config_secrets("PUBSUB_URL", STATE_DIR, default=None, 
cast=str)
       PUBSUB_USER = _config_secrets("PUBSUB_USER", STATE_DIR, default=None, 
cast=str)
       PUBSUB_PASSWORD = _config_secrets("PUBSUB_PASSWORD", STATE_DIR, 
default=None, cast=str)
       SVN_TOKEN = _config_secrets("SVN_TOKEN", STATE_DIR, default=None, 
cast=str)
       GITHUB_TOKEN = _config_secrets("GITHUB_TOKEN", STATE_DIR, default=None, 
cast=str)
   ```
   
   #### `atr/server.py` — `_app_dirs_setup` (lines 168-186)
   _currently does this_
   Creates the secrets/curated directory at startup, which is where Puppet 
would mount or write the secrets file.
   
   ```python
   def _app_dirs_setup(state_dir_str: str, hot_reload: bool) -> None:
       """Setup application directories."""
       if not os.path.isdir(state_dir_str):
           raise RuntimeError(f"State directory not found: {state_dir_str}")
       os.chdir(state_dir_str)
       if hot_reload is False:
           print(f"Working directory changed to: {os.getcwd()}")
   
       # Note that the hypercorn directories are not managed by ATR
       directories_to_ensure = [
           pathlib.Path(state_dir_str) / "audit",
           pathlib.Path(state_dir_str) / "cache",
           pathlib.Path(state_dir_str) / "database",
           pathlib.Path(state_dir_str) / "hypercorn" / "logs",
           pathlib.Path(state_dir_str) / "hypercorn" / "secrets",
           pathlib.Path(state_dir_str) / "logs",
           pathlib.Path(state_dir_str) / "runtime",
           pathlib.Path(state_dir_str) / "secrets" / "curated",
           pathlib.Path(state_dir_str) / "secrets" / "generated",
   ```
   
   ### Where new code would go
   - `atr/config.py` — after symbol _config_secrets_get
     A YAML-based secrets reader function would be added here if the team 
decides to switch from INI to YAML format, or a file-watching/reload mechanism 
would be added.
   
   ### Proposed approach
   Based on the discussion, the current direction is for Puppet's eyaml 
mechanism to decrypt secrets and write them to `secrets/curated/secrets.ini` 
(or a YAML equivalent) which is then mounted into the container. The main code 
changes needed are: (1) Potentially switching from INI to YAML parsing in 
`_config_secrets_get` using pyyaml (already a transitive dependency), (2) 
Implementing hot-reload of secrets so changes to the mounted file take effect 
without container restart, as @dave2wave requested. The hot-reload is the 
harder part since `AppConfig` class attributes are evaluated at import time.
   
   However, several design questions remain unresolved: whether to keep INI 
format (Puppet writes decrypted values to INI) or switch to YAML, what the 
policy boundary is for what constitutes a 'secret', whether there's a dedicated 
ASF SVN ID for ATR, and the exact Puppet integration pattern to follow 
(agenda_server was suggested as a model). No diff is proposed because the 
discussion is still converging on a design.
   
   ### Open questions
   - Should the secrets file format change from INI to YAML, or should Puppet 
decrypt eyaml and write INI format to secrets/curated/secrets.ini?
   - How should hot-reload of secrets be implemented given that AppConfig class 
attributes are evaluated at module import time? (Periodic polling, file 
watcher, signal-based?)
   - What is the definitive list of values that belong in secrets vs. regular 
configuration? (@sbp raised this distinction in the Nov 4 comment)
   - Does ATR have a dedicated SVN service account ID? (@dave2wave asked this 
in the most recent comment with no answer yet)
   - Should the agenda_server Puppet pattern (referenced by @dave2wave) be 
followed exactly, or adapted?
   
   _The agent reviewed this issue and is not proposing patches in this run. 
Review the existing-code citations and open questions above before deciding 
next steps._
   
   ### Files examined
   - `atr/config.py`
   - `atr/server.py`
   - `atr/db/__init__.py`
   - `atr/storage/__init__.py`
   - `atr/models/sql.py`
   - `atr/util.py`
   - `atr/principal.py`
   - `atr/noisy.py`
   
   ---
   *Draft from a triage agent. A human reviewer should validate before merging 
any change. The agent did not run tests or verify diffs apply.*


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to