Skip to content

Commit 09db042

Browse files
authored
feat: allow EDA credential fields to link to external Secret Management Systems (SMS) (#1349)
AAP allows text fields in a Credential to be connected to an external Secret Management System like Hashicorp Vault. EDA was lacking this feature, this fix tries to address that by providing * Test External SMS for connectivity and existence of secrets * Optionally link text fields to External SMS * Uses the awx-plugins-core to manage the connections to external SMS New API end points added * /api/eda/v1/credential-types/_nnn_/test/ (POST) * /api/eda/v1/eda-credentials/_nnn_/test/ (POST) * /api/eda/v1/eda-credentials/_nnn_/input_sources/ (GET) * /api/eda/v1/credential_input_sources/ (CRUD) The[ awx-plugins-core](https://pypi.org/project/awx-plugins-core/) supports 10 different external systems, for parity we have added the schema for all 10 of them 1. CyberArk Central Credential Provider Lookup 2. AWS Secrets Manager lookup 3. Microsoft Azure Key Vault 4. Centrify Vault Credential Provider Lookup 5. CyberArk Conjur Secrets Manager Lookup 6. HashiCorp Vault Secret Lookup 7. HashiCorp Vault Signed SSH 8. Thycotic DevOps Secrets Vault 9. Thycotic Secret Server 10. GitHub App Installation Access Token Lookup https://issues.redhat.com/browse/AAP-46900 ```mermaid flowchart TD; A[EDA UI] -->|Create Credential| B(EDA API); B --> C{AWX Plugins}; C -->|Fetch/Test| D[fa:fa-vault Hashicorp]; C -->|Fetch/Test| E[fa:fa-vault CyberArk]; C -->|Fetch/Text| F[fa:fa-vault Azure]; ```
1 parent daa1693 commit 09db042

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+3534
-65
lines changed

poetry.lock

Lines changed: 530 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

poetry.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[installer]
2+
# This is related to https://github.com/DelineaXPM/python-tss-sdk/issues/72
3+
# these packages get corrupted at times when installed in parallel.
4+
parallel = false

pyproject.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,18 @@ django-flags = "^5.0.13"
7171
insights-analytics-collector = "^0.3.2"
7272
distro = "^1.9.0"
7373
dispatcherd = { version = "v2025.05.19", extras = ["pg_notify"] }
74+
awx-plugins-core = { version = "^0.0.1a10", extras = [
75+
"credentials-aim",
76+
"credentials-aws-secretsmanager-credential",
77+
"credentials-azure-kv",
78+
"credentials-centrify-vault-kv",
79+
"credentials-conjur",
80+
"credentials-github-app",
81+
"credentials-hashivault-kv",
82+
"credentials-hashivault-ssh",
83+
"credentials-thycotic-dsv",
84+
"credentials-thycotic-tss"
85+
]}
7486

7587

7688
[tool.poetry.group.test.dependencies]

src/aap_eda/analytics/utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from aap_eda.conf import application_settings
2727
from aap_eda.conf.registry import ANALYTICS_GATHER_INTERVAL
2828
from aap_eda.core import enums, models
29-
from aap_eda.core.utils.credentials import inputs_from_store
29+
from aap_eda.core.utils.credentials import get_resolved_secrets
3030

3131
logger = logging.getLogger("aap_eda.analytics")
3232

@@ -225,7 +225,7 @@ def generate_token() -> ServiceToken:
225225
def _get_credential_value(field: str, *setting_envs: Tuple[Any, str]) -> str:
226226
credentials = _get_analytics_credentials()
227227
for credential in credentials:
228-
inputs = inputs_from_store(credential.inputs.get_secret_value())
228+
inputs = get_resolved_secrets(credential)
229229
if value := inputs.get(field):
230230
return value
231231

@@ -314,7 +314,7 @@ def get_analytics_interval() -> int:
314314
def get_analytics_interval_if_exist(credential: models.EdaCredential) -> int:
315315
if credential.credential_type.kind != get_auth_mode():
316316
return 0
317-
inputs = inputs_from_store(credential.inputs.get_secret_value())
317+
inputs = get_resolved_secrets(credential)
318318
return inputs.get("gather_interval", 0)
319319

320320

src/aap_eda/api/exceptions.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,10 @@ class InvalidEventStreamSource(APIException):
118118
"Configuration Error: Event Stream source could not be "
119119
"updated in ruleset"
120120
)
121+
122+
123+
class ExternalSMSError(APIException):
124+
status_code = status.HTTP_503_SERVICE_UNAVAILABLE
125+
default_detail = (
126+
"External SMS Error: not able to fetch secrets from external SMS"
127+
)

src/aap_eda/api/filters/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
ActivationInstanceFilter,
1818
ActivationInstanceLogFilter,
1919
)
20+
from .credential_input_source import CredentialInputSourceFilter
2021
from .credential_type import CredentialTypeFilter
2122
from .decision_environment import DecisionEnvironmentFilter
2223
from .eda_credential import EdaCredentialFilter
@@ -35,6 +36,7 @@
3536
# credential type
3637
"CredentialTypeFilter",
3738
"EdaCredentialFilter",
39+
"CredentialInputSourceFilter",
3840
# decision_environment
3941
"DecisionEnvironmentFilter",
4042
# activation instance
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright 2025 Red Hat, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import django_filters
16+
17+
from aap_eda.core import models
18+
19+
20+
class CredentialInputSourceFilter(django_filters.FilterSet):
21+
source_credential = django_filters.NumberFilter(
22+
field_name="source_credential",
23+
lookup_expr="exact",
24+
label="Filter by Source Credential ID.",
25+
)
26+
target_credential = django_filters.NumberFilter(
27+
field_name="target_credential",
28+
lookup_expr="exact",
29+
label="Filter by Target Credential ID.",
30+
)
31+
32+
class Meta:
33+
model = models.CredentialInputSource
34+
fields = ["target_credential", "source_credential"]

src/aap_eda/api/serializers/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,18 @@
2525
)
2626
from .auth import JWTTokenSerializer, LoginSerializer, RefreshTokenSerializer
2727
from .config import ConfigSerializer
28+
from .credential_input_source import (
29+
CredentialInputSourceCreateSerializer,
30+
CredentialInputSourceReferenceSerializer,
31+
CredentialInputSourceRefSerializer,
32+
CredentialInputSourceSerializer,
33+
CredentialInputSourceUpdateSerializer,
34+
)
2835
from .credential_type import (
2936
CredentialTypeCreateSerializer,
3037
CredentialTypeRefSerializer,
3138
CredentialTypeSerializer,
39+
CredentialTypeTestSerializer,
3240
)
3341
from .decision_environment import (
3442
DecisionEnvironmentCreateSerializer,
@@ -40,6 +48,7 @@
4048
EdaCredentialCopySerializer,
4149
EdaCredentialCreateSerializer,
4250
EdaCredentialSerializer,
51+
EdaCredentialTestSerializer,
4352
EdaCredentialUpdateSerializer,
4453
)
4554
from .event_stream import EventStreamInSerializer, EventStreamOutSerializer

src/aap_eda/api/serializers/activation.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
EDA_SERVER_VAULT_LABEL,
2828
SOURCE_MAPPING_ERROR_KEY,
2929
)
30-
from aap_eda.api.exceptions import InvalidEventStreamSource
30+
from aap_eda.api.exceptions import ExternalSMSError, InvalidEventStreamSource
3131
from aap_eda.api.serializers.decision_environment import (
3232
DecisionEnvironmentRefSerializer,
3333
)
@@ -46,8 +46,11 @@
4646
from aap_eda.api.vault import encrypt_string
4747
from aap_eda.core import models, validators
4848
from aap_eda.core.enums import DefaultCredentialType, ProcessParentType
49-
from aap_eda.core.exceptions import ParseError
50-
from aap_eda.core.utils.credentials import get_secret_fields
49+
from aap_eda.core.exceptions import CredentialPluginError, ParseError
50+
from aap_eda.core.utils.credentials import (
51+
get_resolved_secrets,
52+
get_secret_fields,
53+
)
5154
from aap_eda.core.utils.k8s_service_name import create_k8s_service_name
5255
from aap_eda.core.utils.rulebook import (
5356
build_source_list,
@@ -147,8 +150,10 @@ def _update_extra_vars_from_eda_credentials(
147150
schema_inputs = eda_credential.credential_type.inputs
148151
injectors = eda_credential.credential_type.injectors
149152
secret_fields = get_secret_fields(schema_inputs)
150-
151-
user_inputs = yaml.safe_load(eda_credential.inputs.get_secret_value())
153+
try:
154+
user_inputs = get_resolved_secrets(eda_credential)
155+
except CredentialPluginError as err:
156+
raise ExternalSMSError(str(err))
152157

153158
if creating and any(key in user_inputs for key in secret_fields):
154159
vault_data.password_used = True

0 commit comments

Comments
 (0)