mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-04 02:04:06 +00:00
feat(sdk): add AWS Amplify app secret scanning check (#11825)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
This commit is contained in:
co-authored by
Daniel Barranquero
parent
cc6c731af6
commit
24b670ac36
@@ -4,6 +4,8 @@
|
||||
{
|
||||
"Action": [
|
||||
"account:Get*",
|
||||
"amplify:ListApps",
|
||||
"amplify:ListBranches",
|
||||
"appstream:Describe*",
|
||||
"appstream:List*",
|
||||
"backup:List*",
|
||||
|
||||
@@ -176,6 +176,8 @@ Resources:
|
||||
Effect: Allow
|
||||
Action:
|
||||
- "account:Get*"
|
||||
- "amplify:ListApps"
|
||||
- "amplify:ListBranches"
|
||||
- "appstream:Describe*"
|
||||
- "appstream:List*"
|
||||
- "backup:List*"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
`amplify_app_no_secrets_in_environment` check for AWS provider, scanning Amplify app and branch environment variables and build settings for hardcoded secrets
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "amplify_app_no_secrets_in_environment",
|
||||
"CheckTitle": "Amplify app has no sensitive credentials in environment variables or build settings",
|
||||
"CheckType": [
|
||||
"Software and Configuration Checks/AWS Security Best Practices",
|
||||
"TTPs/Credential Access",
|
||||
"Effects/Data Exposure",
|
||||
"Sensitive Data Identifications/Security"
|
||||
],
|
||||
"ServiceName": "amplify",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:partition:amplify:region:account-id:apps/app-id",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AwsAmplifyApp",
|
||||
"ResourceGroup": "security",
|
||||
"Description": "AWS Amplify apps and their branches are inspected for hardcoded secrets, such as API keys, tokens, or passwords embedded in environment variables or build settings (buildSpec).",
|
||||
"Risk": "Plaintext secrets in Amplify app environment variables or build configurations can be viewed by anyone with read access to the Amplify console, or may leak during the build process, exposing downstream resources and integrations.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://docs.aws.amazon.com/amplify/latest/userguide/environment-variables.html",
|
||||
"https://docs.prowler.com/developer-guide/secret-scanning-checks"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "aws amplify update-app --app-id <app-id> --environment-variables <key1=value1,key2=value2>",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. Access the AWS Amplify console.\n2. Navigate to your app settings, choose Environment variables, and check if any secrets are stored in plaintext.\n3. For actual secrets, migrate them to environment secrets or AWS Secrets Manager / Parameter Store and reference them securely during the build phase.\n4. Clean the variables or buildSpec configuration.",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Avoid storing secrets in plaintext environment variables or build specifications for AWS Amplify apps. Store sensitive settings securely in AWS Systems Manager Parameter Store or AWS Secrets Manager.",
|
||||
"Url": "https://hub.prowler.com/check/amplify_app_no_secrets_in_environment"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"secrets"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
import json
|
||||
|
||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||
from prowler.lib.utils.utils import (
|
||||
SecretsScanError,
|
||||
annotate_verified_secrets,
|
||||
detect_secrets_scan_batch,
|
||||
)
|
||||
from prowler.providers.aws.services.amplify.amplify_client import amplify_client
|
||||
|
||||
|
||||
class amplify_app_no_secrets_in_environment(Check):
|
||||
"""Check that AWS Amplify apps contain no hardcoded secrets in their environment variables or build settings."""
|
||||
|
||||
def execute(self) -> list[Check_Report_AWS]:
|
||||
findings = []
|
||||
secrets_ignore_patterns = amplify_client.audit_config.get(
|
||||
"secrets_ignore_patterns", []
|
||||
)
|
||||
validate = amplify_client.audit_config.get("secrets_validate", False)
|
||||
apps = list(amplify_client.apps.values())
|
||||
line_context_by_app = {}
|
||||
|
||||
payloads_list = []
|
||||
for app_index, app in enumerate(apps):
|
||||
payload, line_context = _build_app_payload(app)
|
||||
line_context_by_app[app_index] = line_context
|
||||
if payload:
|
||||
payloads_list.append((app_index, payload))
|
||||
|
||||
scan_error = None
|
||||
try:
|
||||
batch_results = detect_secrets_scan_batch(
|
||||
payloads_list,
|
||||
excluded_secrets=secrets_ignore_patterns,
|
||||
validate=validate,
|
||||
)
|
||||
except SecretsScanError as error:
|
||||
batch_results = {}
|
||||
scan_error = error
|
||||
|
||||
for app_index, app in enumerate(apps):
|
||||
report = Check_Report_AWS(metadata=self.metadata(), resource=app)
|
||||
report.resource_tags = app.tags
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"No secrets found in Amplify app {app.name} environment variables or build settings."
|
||||
|
||||
line_context = line_context_by_app.get(app_index, {})
|
||||
if line_context:
|
||||
if scan_error:
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = (
|
||||
f"Could not scan Amplify app {app.name} environment variables "
|
||||
f"for secrets: {scan_error}; manual review is required."
|
||||
)
|
||||
findings.append(report)
|
||||
continue
|
||||
|
||||
detect_secrets_output = batch_results.get(app_index)
|
||||
if detect_secrets_output:
|
||||
secrets_string = ", ".join(
|
||||
[
|
||||
f"{secret['type']} in {line_context.get(secret['line_number'], 'environment variables/build settings')}"
|
||||
for secret in detect_secrets_output
|
||||
]
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"Potential {'secrets' if len(detect_secrets_output) > 1 else 'secret'} "
|
||||
f"found in Amplify app {app.name} environment variables or build settings -> {secrets_string}."
|
||||
)
|
||||
annotate_verified_secrets(report, detect_secrets_output)
|
||||
|
||||
findings.append(report)
|
||||
return findings
|
||||
|
||||
|
||||
def _build_app_payload(app) -> tuple[str, dict[int, str]]:
|
||||
"""Build a line-oriented scan payload and map each line to a field context."""
|
||||
lines = []
|
||||
line_context = {}
|
||||
|
||||
def add_line(context: str, value: str) -> None:
|
||||
if value is None:
|
||||
return
|
||||
lines.append(json.dumps({context: value}))
|
||||
line_context[len(lines)] = context
|
||||
|
||||
# App environment variables
|
||||
for var_name, var_value in app.environment_variables.items():
|
||||
add_line(f"app environment variable '{var_name}'", var_value)
|
||||
|
||||
# App buildSpec
|
||||
if app.build_spec:
|
||||
for idx, line in enumerate(app.build_spec.splitlines(), start=1):
|
||||
add_line(f"app buildSpec line {idx}", line)
|
||||
|
||||
# Branch environment variables
|
||||
for branch in app.branches:
|
||||
for var_name, var_value in branch.environment_variables.items():
|
||||
add_line(
|
||||
f"branch '{branch.name}' environment variable '{var_name}'", var_value
|
||||
)
|
||||
|
||||
return "\n".join(lines), line_context
|
||||
@@ -0,0 +1,4 @@
|
||||
from prowler.providers.aws.services.amplify.amplify_service import Amplify
|
||||
from prowler.providers.common.provider import Provider
|
||||
|
||||
amplify_client = Amplify(Provider.get_global_provider())
|
||||
@@ -0,0 +1,97 @@
|
||||
from botocore.exceptions import ClientError
|
||||
from pydantic.v1 import BaseModel, Field
|
||||
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.scan_filters.scan_filters import is_resource_filtered
|
||||
from prowler.providers.aws.lib.service.service import AWSService
|
||||
|
||||
|
||||
class Branch(BaseModel):
|
||||
"""Represents an AWS Amplify App Branch."""
|
||||
|
||||
name: str
|
||||
arn: str
|
||||
environment_variables: dict = Field(default_factory=dict)
|
||||
|
||||
|
||||
class App(BaseModel):
|
||||
"""Represents an AWS Amplify App."""
|
||||
|
||||
id: str
|
||||
name: str
|
||||
arn: str
|
||||
region: str
|
||||
environment_variables: dict = Field(default_factory=dict)
|
||||
build_spec: str = ""
|
||||
branches: list[Branch] = Field(default_factory=list)
|
||||
tags: list[dict] = Field(default_factory=list)
|
||||
|
||||
|
||||
class Amplify(AWSService):
|
||||
"""AWS Amplify service class."""
|
||||
|
||||
def __init__(self, provider):
|
||||
super().__init__(__class__.__name__, provider)
|
||||
self.apps = {}
|
||||
self.__threading_call__(self._list_apps)
|
||||
if self.apps:
|
||||
self.__threading_call__(self._list_branches, self.apps.values())
|
||||
|
||||
def _list_apps(self, regional_client) -> None:
|
||||
logger.info("Amplify - Listing apps...")
|
||||
try:
|
||||
list_apps_paginator = regional_client.get_paginator("list_apps")
|
||||
for page in list_apps_paginator.paginate():
|
||||
for app in page.get("apps", []):
|
||||
app_id = app.get("appId")
|
||||
app_name = app.get("name")
|
||||
app_arn = app.get("appArn")
|
||||
if not self.audit_resources or is_resource_filtered(
|
||||
app_arn, self.audit_resources
|
||||
):
|
||||
tags = app.get("tags", {})
|
||||
tags_list = [tags] if tags else []
|
||||
self.apps[app_arn] = App(
|
||||
id=app_id,
|
||||
name=app_name,
|
||||
arn=app_arn,
|
||||
region=regional_client.region,
|
||||
environment_variables=app.get("environmentVariables", {}),
|
||||
build_spec=app.get("buildSpec", ""),
|
||||
tags=tags_list,
|
||||
)
|
||||
except ClientError as error:
|
||||
logger.error(
|
||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def _list_branches(self, app: App) -> None:
|
||||
logger.info(f"Amplify - Listing branches for app {app.name}...")
|
||||
try:
|
||||
regional_client = self.regional_clients[app.region]
|
||||
list_branches_paginator = regional_client.get_paginator("list_branches")
|
||||
for page in list_branches_paginator.paginate(appId=app.id):
|
||||
for branch in page.get("branches", []):
|
||||
branch_name = branch.get("branchName")
|
||||
branch_arn = branch.get("branchArn")
|
||||
app.branches.append(
|
||||
Branch(
|
||||
name=branch_name,
|
||||
arn=branch_arn,
|
||||
environment_variables=branch.get(
|
||||
"environmentVariables", {}
|
||||
),
|
||||
)
|
||||
)
|
||||
except ClientError as error:
|
||||
logger.error(
|
||||
f"{app.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{app.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
from unittest import mock
|
||||
|
||||
from prowler.lib.utils.utils import SecretsScanError
|
||||
from prowler.providers.aws.services.amplify.amplify_service import App, Branch
|
||||
from tests.providers.aws.utils import (
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_REGION_US_EAST_1,
|
||||
set_mocked_aws_provider,
|
||||
)
|
||||
|
||||
|
||||
class Test_amplify_app_no_secrets_in_environment:
|
||||
def test_no_apps(self):
|
||||
amplify_client = mock.MagicMock()
|
||||
amplify_client.apps = {}
|
||||
amplify_client.audit_config = {"secrets_ignore_patterns": []}
|
||||
|
||||
result = _execute_check(amplify_client)
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
def test_app_with_no_secrets(self):
|
||||
app = _build_app(
|
||||
environment_variables={"key1": "val1"},
|
||||
build_spec="version: 1\nfrontend:\n phases:\n build:\n commands:\n - echo hello",
|
||||
branches=[
|
||||
Branch(
|
||||
name="main",
|
||||
arn=f"arn:aws:amplify:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:apps/app-12345/branches/main",
|
||||
environment_variables={"branch_key": "branch_val"},
|
||||
)
|
||||
],
|
||||
)
|
||||
amplify_client = mock.MagicMock()
|
||||
amplify_client.apps = {app.arn: app}
|
||||
amplify_client.audit_config = {"secrets_ignore_patterns": []}
|
||||
|
||||
result = _execute_check(amplify_client)
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "No secrets found in Amplify app test-app environment variables or build settings."
|
||||
)
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
assert result[0].resource_id == "app-12345"
|
||||
assert result[0].resource_arn == app.arn
|
||||
|
||||
def test_app_with_secrets_in_app_variables(self):
|
||||
app = _build_app(
|
||||
environment_variables={
|
||||
"db_pass": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U"
|
||||
},
|
||||
build_spec="",
|
||||
branches=[],
|
||||
)
|
||||
amplify_client = mock.MagicMock()
|
||||
amplify_client.apps = {app.arn: app}
|
||||
amplify_client.audit_config = {"secrets_ignore_patterns": []}
|
||||
|
||||
result = _execute_check(amplify_client)
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "app environment variable 'db_pass'" in result[0].status_extended
|
||||
|
||||
def test_app_with_secrets_in_branch_variables(self):
|
||||
app = _build_app(
|
||||
environment_variables={},
|
||||
build_spec="",
|
||||
branches=[
|
||||
Branch(
|
||||
name="dev",
|
||||
arn=f"arn:aws:amplify:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:apps/app-12345/branches/dev",
|
||||
environment_variables={
|
||||
"api_key": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U"
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
amplify_client = mock.MagicMock()
|
||||
amplify_client.apps = {app.arn: app}
|
||||
amplify_client.audit_config = {"secrets_ignore_patterns": []}
|
||||
|
||||
result = _execute_check(amplify_client)
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
"branch 'dev' environment variable 'api_key'" in result[0].status_extended
|
||||
)
|
||||
|
||||
def test_app_with_secrets_in_build_spec(self):
|
||||
app = _build_app(
|
||||
environment_variables={},
|
||||
build_spec="version: 1\nfrontend:\n phases:\n build:\n commands:\n - export JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U",
|
||||
branches=[],
|
||||
)
|
||||
amplify_client = mock.MagicMock()
|
||||
amplify_client.apps = {app.arn: app}
|
||||
amplify_client.audit_config = {"secrets_ignore_patterns": []}
|
||||
|
||||
result = _execute_check(amplify_client)
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "app buildSpec line 6" in result[0].status_extended
|
||||
|
||||
def test_app_scan_error_marks_manual(self):
|
||||
app = _build_app(
|
||||
environment_variables={"key1": "val1"},
|
||||
build_spec="version: 1",
|
||||
branches=[],
|
||||
)
|
||||
amplify_client = mock.MagicMock()
|
||||
amplify_client.apps = {app.arn: app}
|
||||
amplify_client.audit_config = {"secrets_ignore_patterns": []}
|
||||
|
||||
result = _execute_check_with_mocked_scan(
|
||||
amplify_client,
|
||||
side_effect=SecretsScanError("Scanner failure"),
|
||||
)
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "MANUAL"
|
||||
assert (
|
||||
"Could not scan Amplify app test-app environment variables for secrets: Scanner failure"
|
||||
in result[0].status_extended
|
||||
)
|
||||
|
||||
|
||||
def _build_app(environment_variables: dict, build_spec: str, branches: list) -> App:
|
||||
app_id = "app-12345"
|
||||
app_name = "test-app"
|
||||
app_arn = (
|
||||
f"arn:aws:amplify:{AWS_REGION_US_EAST_1}:" f"{AWS_ACCOUNT_NUMBER}:apps/{app_id}"
|
||||
)
|
||||
return App(
|
||||
id=app_id,
|
||||
name=app_name,
|
||||
arn=app_arn,
|
||||
region=AWS_REGION_US_EAST_1,
|
||||
environment_variables=environment_variables,
|
||||
build_spec=build_spec,
|
||||
branches=branches,
|
||||
tags=[],
|
||||
)
|
||||
|
||||
|
||||
def _execute_check(amplify_client):
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.amplify.amplify_app_no_secrets_in_environment.amplify_app_no_secrets_in_environment.amplify_client",
|
||||
amplify_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.amplify.amplify_app_no_secrets_in_environment.amplify_app_no_secrets_in_environment import (
|
||||
amplify_app_no_secrets_in_environment,
|
||||
)
|
||||
|
||||
check = amplify_app_no_secrets_in_environment()
|
||||
return check.execute()
|
||||
|
||||
|
||||
def _execute_check_with_mocked_scan(
|
||||
amplify_client, return_value=None, side_effect=None
|
||||
):
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.amplify.amplify_app_no_secrets_in_environment.amplify_app_no_secrets_in_environment.amplify_client",
|
||||
amplify_client,
|
||||
),
|
||||
):
|
||||
import prowler.providers.aws.services.amplify.amplify_app_no_secrets_in_environment.amplify_app_no_secrets_in_environment as check_module
|
||||
|
||||
with mock.patch.object(
|
||||
check_module,
|
||||
"detect_secrets_scan_batch",
|
||||
return_value=return_value,
|
||||
side_effect=side_effect,
|
||||
):
|
||||
check = check_module.amplify_app_no_secrets_in_environment()
|
||||
return check.execute()
|
||||
@@ -0,0 +1,91 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
import botocore
|
||||
from moto import mock_aws
|
||||
|
||||
from prowler.providers.aws.services.amplify.amplify_service import Amplify, App, Branch
|
||||
from tests.providers.aws.utils import (
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_REGION_US_EAST_1,
|
||||
set_mocked_aws_provider,
|
||||
)
|
||||
|
||||
app_id = "app-12345"
|
||||
app_name = "test-app"
|
||||
app_arn = f"arn:aws:amplify:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:apps/{app_id}"
|
||||
branch_name = "main"
|
||||
branch_arn = f"{app_arn}/branches/{branch_name}"
|
||||
|
||||
app_environment_variables = {"app_key": "app_val"}
|
||||
branch_environment_variables = {"branch_key": "branch_val"}
|
||||
build_spec = "version: 1"
|
||||
app_tags = {"tag_key": "tag_val"}
|
||||
|
||||
make_api_call = botocore.client.BaseClient._make_api_call
|
||||
|
||||
|
||||
def mock_make_api_call(self, operation_name, kwarg):
|
||||
if operation_name == "ListApps":
|
||||
return {
|
||||
"apps": [
|
||||
{
|
||||
"appId": app_id,
|
||||
"name": app_name,
|
||||
"appArn": app_arn,
|
||||
"environmentVariables": app_environment_variables,
|
||||
"buildSpec": build_spec,
|
||||
"tags": app_tags,
|
||||
}
|
||||
]
|
||||
}
|
||||
if operation_name == "ListBranches":
|
||||
return {
|
||||
"branches": [
|
||||
{
|
||||
"branchArn": branch_arn,
|
||||
"branchName": branch_name,
|
||||
"environmentVariables": branch_environment_variables,
|
||||
}
|
||||
]
|
||||
}
|
||||
return make_api_call(self, operation_name, kwarg)
|
||||
|
||||
|
||||
def mock_generate_regional_clients(provider, service):
|
||||
regional_client = provider._session.current_session.client(
|
||||
service, region_name=AWS_REGION_US_EAST_1
|
||||
)
|
||||
regional_client.region = AWS_REGION_US_EAST_1
|
||||
return {AWS_REGION_US_EAST_1: regional_client}
|
||||
|
||||
|
||||
@patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call)
|
||||
@patch(
|
||||
"prowler.providers.aws.aws_provider.AwsProvider.generate_regional_clients",
|
||||
new=mock_generate_regional_clients,
|
||||
)
|
||||
class TestAmplifyService:
|
||||
@mock_aws
|
||||
def test_amplify_service(self):
|
||||
amplify = Amplify(set_mocked_aws_provider([AWS_REGION_US_EAST_1]))
|
||||
|
||||
assert amplify.session.__class__.__name__ == "Session"
|
||||
assert amplify.service == "amplify"
|
||||
assert len(amplify.apps) == 1
|
||||
assert isinstance(amplify.apps[app_arn], App)
|
||||
|
||||
app = amplify.apps[app_arn]
|
||||
assert app.id == app_id
|
||||
assert app.name == app_name
|
||||
assert app.arn == app_arn
|
||||
assert app.region == AWS_REGION_US_EAST_1
|
||||
assert app.environment_variables == app_environment_variables
|
||||
assert app.build_spec == build_spec
|
||||
assert app.tags == [app_tags]
|
||||
|
||||
assert len(app.branches) == 1
|
||||
branch = app.branches[0]
|
||||
assert isinstance(branch, Branch)
|
||||
assert branch.name == branch_name
|
||||
assert branch.arn == branch_arn
|
||||
assert branch.environment_variables == branch_environment_variables
|
||||
Reference in New Issue
Block a user