Skip to content

[WIP] Make githubapp private key storable in other credential sources#240

Open
Klaas- wants to merge 1 commit intoctrliq:mainfrom
gxmezrct:feature/external_credential_links
Open

[WIP] Make githubapp private key storable in other credential sources#240
Klaas- wants to merge 1 commit intoctrliq:mainfrom
gxmezrct:feature/external_credential_links

Conversation

@Klaas-
Copy link
Contributor

@Klaas- Klaas- commented Feb 24, 2026

SUMMARY

I am not yet sure if this is a good idea :) But essentially this is a follow up to the github app PR, I would like to be able to use a azure keyvault (or other secret manager) secret to store the RSA key for the github app.

I am not really too familiar with the UI/API, while I tested it and it works I am not sure it's a good idea to remove the restrictions :) So I am looking for feedback on this change.

ISSUE TYPE
  • New or Enhanced Feature
COMPONENT NAME
  • API
  • UI
ASCENDER VERSION
25.3.4
ADDITIONAL INFORMATION

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR expands credential input sourcing so that external credentials (e.g., Azure Key Vault) can themselves have fields populated from other credential sources, enabling secret-manager “chaining” (useful for storing GitHub App private keys or external-credential auth inputs in a separate secret store).

Changes:

  • UI: Removes the special-case rendering for credentialType.kind === 'external', so external credential fields now go through the standard CredentialPluginField flow.
  • API/Model: Allows Credential.get_input() to resolve dynamic input sources for all credential kinds (including external), and permits external credentials to be input-source targets.
  • API/Model: Resolves dynamic inputs on the source external credential when executing an input-source lookup, enabling multi-hop external credential resolution.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
awx/ui/src/screens/Credential/shared/CredentialFormFields/CredentialField.js Removes external-only field rendering to allow plugin-based sourcing UI for external credential fields.
awx/main/models/credential/__init__.py Enables external→external input sourcing and resolves dynamic inputs on source credentials during backend lookups.
Comments suppressed due to low confidence (1)

awx/ui/src/screens/Credential/shared/CredentialFormFields/CredentialField.js:226

  • Removing the special-case UI for credentialType.kind === 'external' means external credentials will now render inside CredentialPluginField, allowing form values like inputs.<field> to become plugin objects ({credential, inputs}). ExternalTestModal currently builds the /credentials/:id/test/ payload by copying credentialFormValues.inputs[field.id] verbatim, so it will send these objects for string fields and fail schema validation. The test modal (and/or test endpoint) needs to filter/serialize plugin-sourced fields similarly to save, or otherwise support resolving dynamic inputs when testing external credentials.
  if (credentialType.kind === 'ssh' && fieldOptions.id === 'become_method') {
    return (
      <BecomeMethodField fieldOptions={fieldOptions} isRequired={isRequired} />
    );
  }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1303 to +1308
# Resolve any dynamic inputs on the source credential
# (e.g., a field sourced from another external credential like Azure KV)
for field_name in self.source_credential.dynamic_input_fields:
if field_name not in backend_kwargs:
backend_kwargs[field_name] = self.source_credential._get_dynamic_input(field_name)

Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

Allowing external credentials to be targets (and resolving source_credential.dynamic_input_fields) enables external→external chaining, but there is no cycle detection. A 2-node loop (A sources from B, B sources from A) will recurse indefinitely when resolving inputs and can hang tasks or hit recursion limits. Add validation to prevent cycles (graph walk from source_credential through its input sources) and/or runtime protection (visited set / max depth) during dynamic resolution.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems like a valid concern. Could easily fix with something like this (untested)

def clean_source_credential(self):
    if self.source_credential.credential_type.kind != 'external':
        raise ValidationError(_('Source must be an external credential'))
    
    # Cycle detection: check if adding this link would create a cycle
    visited = set()
    def has_cycle(cred_id, target_id):
        if cred_id == target_id:
            return True
        if cred_id in visited:
            return False
        visited.add(cred_id)
        
        # Check all sources that point to this credential
        for input_source in CredentialInputSource.objects.filter(target_credential_id=cred_id):
            if has_cycle(input_source.source_credential_id, target_id):
                return True
        return False
    
    if has_cycle(self.source_credential_id, self.target_credential_id):
        raise ValidationError(_('This would create a circular dependency between credentials'))
    
    return self.source_credential

Comment on lines 1278 to 1281
def clean_target_credential(self):
if self.target_credential.credential_type.kind == 'external':
raise ValidationError(_('Target must be a non-external credential'))
if self.target_credential == self.source_credential:
raise ValidationError(_('Target and source credentials must be different'))
return self.target_credential
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

This changes API validation semantics: CredentialInputSource.clean_target_credential no longer rejects external target credentials, so existing functional tests (e.g. test_create_credential_input_source_with_external_target_returns_400) and any clients relying on the old error will break. Please update/replace the test expectation and add coverage for the newly-supported external→external input sourcing behavior.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@cigamit cigamit left a comment

Choose a reason for hiding this comment

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

I see no issue with removing the constraints as long as we eliminate the recursion and fix the tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants