Skip to content

feat: github app env-var fallback for AWS Secrets Manager compatibility - #495

Merged
OlivierTrudeau merged 7 commits into
mainfrom
harryyang2005/dev-1198-github-app-does-not-work-with-aws-secret-manager
Jun 11, 2026
Merged

OlivierTrudeau merged 7 commits into
mainfrom
harryyang2005/dev-1198-github-app-does-not-work-with-aws-secret-manager

Conversation

@Harrio-6

@Harrio-6 Harrio-6 commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Add can_handle_ref() check so the active backend gracefully declines incompatible secret references
Add GITHUB_APP_PRIVATE_KEY env var as fallback when Vault is not the secrets backend
Align get_app_private_key() with the try-backend-then-env pattern already used by get_app_webhook_secret()

Summary by CodeRabbit

  • New Features

    • GitHub App credentials now use a configurable secrets backend; AWS Secrets Manager supported alongside Vault
    • Private key must be read from the secrets backend (no env fallback); webhook secret prefers backend with existing env fallback preserved
    • Private key resolution is cached for performance
  • Documentation

    • Updated GitHub App setup and troubleshooting to cover both Vault and AWS Secrets Manager backends
  • Tests

    • Added regression tests covering backend reference mapping, resolution, fallback, and caching behavior

@Harrio-6
Harrio-6 requested a review from a team as a code owner June 10, 2026 19:08
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Refactors GitHub App secret provisioning from Vault-only to a backend-agnostic flow via SecretsBackend.build_system_ref; private key is read from the active backend (cached), webhook secret is read from backend with an environment-variable fallback, and tests/docs updated for Vault and AWS Secrets Manager.

Changes

Backend-Agnostic GitHub App Secrets

Layer / File(s) Summary
System-Secret Backend Contract and Implementation
server/utils/secrets/base.py, server/utils/secrets/vault_backend.py, server/utils/secrets/aws_sm_backend.py
Base SecretsBackend adds build_system_ref(logical_name) contract. VaultSecretsBackend and AWSSecretsManagerBackend implement it, using a shared system/ namespace and AWSSM_SYSTEM_PREFIX for AWS.
GitHub App Secret Reading Refactor
server/connectors/github_connector/vault_keys.py
Rewrites module docs and constants to backend-agnostic logical names. Introduces _read_system_secret() using build_system_ref() + get_secret() and consistent error wrapping. get_app_private_key() reads exclusively from the backend with caching; get_app_webhook_secret() tries backend first then falls back to env vars.
System-Secret Resolution Test Suite
server/tests/secrets/test_github_app_system_secret.py
New tests validate build_system_ref() formats and parsing for Vault and AWS, private-key backend-only resolution and caching, webhook-secret backend resolution with env fallback preservation, and error handling for empty/no-secret cases.
Documentation and Related Code References
server/utils/auth/github_app_jwt.py, website/docs/configuration/environment.md, website/docs/integrations/connectors.md
Docstring and error-message updates to reference secret helpers; docs updated to state secrets backend precedence and provide parallel setup instructions for Vault and AWS Secrets Manager, including PEM handling and troubleshooting guidance.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Arvo-AI/aurora#192: Introduces AWSSecretsManagerBackend and SECRETS_BACKEND dispatch; related to adding build_system_ref support and multi-backend secret resolution.

Suggested reviewers

  • isiddharthsingh
  • beng360

Poem

🐰 I swapped my Vault key for a wondrous trail,
secrets now follow a backend-agnostic tale.
Webhooks peek envs when backends nap,
private PEMs stay in the secure map.
Tests hop along — the rabbit claps a tail.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main change: enabling GitHub App secret handling to work with AWS Secrets Manager through an env-var fallback pattern, which is the core objective achieved across the refactoring.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch harryyang2005/dev-1198-github-app-does-not-work-with-aws-secret-manager

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@isiddharthsingh

Copy link
Copy Markdown
Contributor

The approach needs to change. Right now the PR makes GitHub App work on AWS SM by falling back to a GITHUB_APP_PRIVATE_KEY env var when the backend can't handle the hardcoded vault: ref. That means the private key never actually lives in AWS Secrets Manager — it's just an env var, and AWS SM is bypassed. We want the key stored in and read from the active secrets backend (Vault or AWS SM), the same way every other connector is backend-agnostic. So: drop the env-var fallback and make the system-secret reference be built by the active backend.

⚠️ Note: just deleting GITHUB_APP_PRIVATE_KEY won't work — that brings back the original failure (the hardcoded vault: ref still can't be parsed by AWS SM, and now with no fallback). You have to add the backend-agnostic read below.

  1. Add build_system_ref() to the secrets backend

server/utils/secrets/base.py — add to SecretsBackend:
def build_system_ref(self, logical_name: str) -> str:
"""Build a reference to a system-scoped (non per-user) secret.

System secrets (e.g. the GitHub App private key) are operator-provisioned
and read-only at runtime. Each backend maps the logical name to its own
reference format so callers never hardcode a backend-specific prefix.
"""
raise NotImplementedError

server/utils/secrets/vault_backend.py — override (this reproduces today's exact Vault ref, so existing Vault deployments are unaffected):
def build_system_ref(self, logical_name: str) -> str:
# vault:kv/data/{mount}/system/{logical_name}
return f"{VAULT_REF_PREFIX}{self.mount_point}/system/{logical_name}"
With the default mount aurora and logical_name="github-app/private-key", this yields vault:kv/data/aurora/system/github-app/private-key — identical to the current hardcoded constant.

server/utils/secrets/aws_sm_backend.py — add a system prefix constant near AWSSM_REF_PREFIX and override:
AWSSM_SYSTEM_PREFIX = "aurora/system" # system secrets, separate from AWS_SM_PREFIX (aurora/users)

def build_system_ref(self, logical_name: str) -> str:
# awssm:{region}:aurora/system/{logical_name}
return f"{AWSSM_REF_PREFIX}{self.region}:{AWSSM_SYSTEM_PREFIX}/{logical_name}"
Don't reuse self.prefix (aurora/users) — system secrets get their own aurora/system namespace. get_secret() already reads the SecretId verbatim from the ref and validates the region, so no other changes are needed there.

  1. Rewrite vault_keys.py to use it (and remove the env-var fallback)

server/connectors/github_connector/vault_keys.py:

  • Replace the two hardcoded _..._SECRET_REF constants with logical names:
    _PRIVATE_KEY_LOGICAL_NAME = "github-app/private-key"
    _WEBHOOK_SECRET_LOGICAL_NAME = "github-app/webhook-secret"
  • Rename _read_from_backend → _read_system_secret(logical_name, *, secret_label); build the ref from the active backend instead of taking a hardcoded ref, and drop the can_handle_ref gate (now redundant — the ref always comes from the active backend):
    def _read_system_secret(logical_name: str, *, secret_label: str) -> str:
    backend = get_secrets_backend()
    if not backend.is_available():
    raise GitHubAppConfigError(
    f"Secrets backend is unavailable while reading GitHub App {secret_label}."
    )
    secret_ref = backend.build_system_ref(logical_name)
    try:
    secret_value = backend.get_secret(secret_ref)
    except GitHubAppConfigError:
    raise
    except Exception as exc:
    raise GitHubAppConfigError(
    f"Failed to read GitHub App {secret_label} from secrets backend."
    ) from exc
    if not secret_value:
    raise GitHubAppConfigError(
    f"GitHub App {secret_label} is empty in secrets backend."
    )
    return secret_value
  • Delete _PRIVATE_KEY_ENV_VARS, _read_private_key_from_env(), and the env-fallback branch in get_app_private_key(). It becomes a straight backend read:
    def get_app_private_key() -> str:
    global _cached_private_key
    if _cached_private_key is not None:
    return _cached_private_key
    _cached_private_key = _read_system_secret(
    _PRIVATE_KEY_LOGICAL_NAME, secret_label="private key",
    )
    return _cached_private_key
  • Keep get_app_webhook_secret()'s existing env fallback (GITHUB_APP_WEBHOOK_SECRET etc.) — that predates this PR and isn't what we're removing. Just point it at _read_system_secret(_WEBHOOK_SECRET_LOGICAL_NAME, ...).
  1. Revert the env/compose additions
  • .env.example — remove the 3 added GITHUB_APP_PRIVATE_KEY lines.
  • docker-compose.yaml and docker-compose.prod-local.yml — remove GITHUB_APP_PRIVATE_KEY: ${GITHUB_APP_PRIVATE_KEY} from the x-common-env block. (No new env vars are introduced by this approach.)
  1. Operator provisioning (goes in the docs, not code)

On AWS SM deployments, the operator creates the two secrets — note the aurora/system/... prefix (NOT aurora/users/AWS_SM_PREFIX) and the region must match AWS_SM_REGION:
aws secretsmanager create-secret --name aurora/system/github-app/private-key
--secret-string file://github-app-private-key.pem --region "$AWS_SM_REGION"
aws secretsmanager create-secret --name aurora/system/github-app/webhook-secret
--secret-string 'your-webhook-secret' --region "$AWS_SM_REGION"
(Storing the PEM in SecretString keeps real newlines, so the \n-unescaping he added isn't needed anymore.)

  1. Add the missing tests + docs (the PR has neither)
  • Tests in server/tests/secrets/: build_system_ref returns the right ref for both backends; with the AWS SM backend mocked, get_app_private_key()/get_app_webhook_secret() resolve from it instead of raising "bad prefix"; Vault default still returns the identical ref (regression guard).
  • Docs: website/docs/integrations/connectors.md (around L396, L442–443, L526) — show the aws secretsmanager create-secret commands next to the existing vault kv put ones; website/docs/configuration/environment.md (L396, L410) — reword "Vault path" → "secrets backend path (aurora/system/github-app/*)".

Verification (all three GitHub auth methods)

  1. SECRETS_BACKEND=vault (default): get_app_private_key() returns the same value/ref as before — no regression.
  2. SECRETS_BACKEND=aws_secrets_manager with the key stored at aurora/system/github-app/private-key: mint_app_jwt() succeeds and an installation token can be fetched (no more "bad prefix").
  3. OAuth connect still works (unchanged); the agent/MCP GitHub tool resolves a token (passes once Update README.md #2 works, since it falls back to App installation tokens).

@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/tests/secrets/test_github_app_system_secret.py`:
- Line 25: The test fixture _FAKE_PEM contains real PEM-like headers/footers
which trigger secret scanners; replace its value with a non-key sentinel string
(e.g. "FAKE_PEM_CONTENT" or any opaque sample text) so tests still have sample
content but no RSA/PEM markers; update the _FAKE_PEM assignment in the test
module (referenced as _FAKE_PEM) to the sanitized sentinel string.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9d538006-7db1-4992-935f-147bac14ac9c

📥 Commits

Reviewing files that changed from the base of the PR and between a81dc5d and ebb8f7f.

📒 Files selected for processing (8)
  • server/connectors/github_connector/vault_keys.py
  • server/tests/secrets/test_github_app_system_secret.py
  • server/utils/auth/github_app_jwt.py
  • server/utils/secrets/aws_sm_backend.py
  • server/utils/secrets/base.py
  • server/utils/secrets/vault_backend.py
  • website/docs/configuration/environment.md
  • website/docs/integrations/connectors.md

Comment thread server/tests/secrets/test_github_app_system_secret.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
server/tests/secrets/test_github_app_system_secret.py (2)

167-205: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider adding a caching test for webhook secrets.

The private key test suite includes test_result_is_cached (lines 154–159) to verify that repeated calls only trigger a single backend read. The webhook secret uses the same caching mechanism (snippet 2 shows _cached_webhook_secret), but this class has no equivalent test.

Adding a test that calls get_app_webhook_secret() twice and asserts backend.get_secret was invoked only once would provide symmetric coverage and guard against future regressions in the caching logic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/tests/secrets/test_github_app_system_secret.py` around lines 167 -
205, Add a caching test analogous to the private key suite: in
TestGetAppWebhookSecret create a test (e.g.,
test_result_is_cached_for_webhook_secret) that configures a mock backend via
get_secrets_backend, ensures backend.available=True and backend.get_secret
returns "whsec", then call vault_keys.get_app_webhook_secret() twice and assert
backend.get_secret was called exactly once; this verifies the
_cached_webhook_secret caching behavior used by get_app_webhook_secret().

81-89: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider testing the round-trip without accessing private methods.

Line 89 directly calls backend._parse_ref(), which couples the test to an implementation detail. If _parse_ref is later refactored or renamed, this test will break even if the public contract remains stable.

While verifying that the extracted secret ID matches expectations adds confidence, consider whether can_handle_ref alone provides sufficient validation of the round-trip behavior, or whether the AWS backend should expose a public method for testing ref parsing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/tests/secrets/test_github_app_system_secret.py` around lines 81 - 89,
The test test_aws_sm_ref_roundtrips_through_parse_ref is relying on the private
method backend._parse_ref which couples the test to implementation details;
change the test to avoid calling the private method by either asserting public
behavior only (use backend.can_handle_ref(ref) plus any public accessor that
returns the SecretId) or add a public parse method on AWSSecretsManagerBackend
(e.g., parse_ref or extract_secret_id) and use that instead of _parse_ref;
update the test to call backend.build_system_ref(_PRIVATE_KEY_LOGICAL), assert
backend.can_handle_ref(ref) and then call the new public method (or otherwise
observe the SecretId via public API) to verify the extracted
"aurora/system/github-app/private-key".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@server/tests/secrets/test_github_app_system_secret.py`:
- Around line 167-205: Add a caching test analogous to the private key suite: in
TestGetAppWebhookSecret create a test (e.g.,
test_result_is_cached_for_webhook_secret) that configures a mock backend via
get_secrets_backend, ensures backend.available=True and backend.get_secret
returns "whsec", then call vault_keys.get_app_webhook_secret() twice and assert
backend.get_secret was called exactly once; this verifies the
_cached_webhook_secret caching behavior used by get_app_webhook_secret().
- Around line 81-89: The test test_aws_sm_ref_roundtrips_through_parse_ref is
relying on the private method backend._parse_ref which couples the test to
implementation details; change the test to avoid calling the private method by
either asserting public behavior only (use backend.can_handle_ref(ref) plus any
public accessor that returns the SecretId) or add a public parse method on
AWSSecretsManagerBackend (e.g., parse_ref or extract_secret_id) and use that
instead of _parse_ref; update the test to call
backend.build_system_ref(_PRIVATE_KEY_LOGICAL), assert
backend.can_handle_ref(ref) and then call the new public method (or otherwise
observe the SecretId via public API) to verify the extracted
"aurora/system/github-app/private-key".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 59fe6b4b-81e7-48cc-8529-3a30ccbe08bb

📥 Commits

Reviewing files that changed from the base of the PR and between ebb8f7f and 6d7555a.

📒 Files selected for processing (1)
  • server/tests/secrets/test_github_app_system_secret.py

@OlivierTrudeau
OlivierTrudeau merged commit a33c3b5 into main Jun 11, 2026
18 checks passed
@OlivierTrudeau
OlivierTrudeau deleted the harryyang2005/dev-1198-github-app-does-not-work-with-aws-secret-manager branch June 11, 2026 15:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants