Skip to content

Commit d1bd633

Browse files
authored
Document hidden environment variable loading (#44685)
1 parent 0ad7e5b commit d1bd633

File tree

3 files changed

+66
-5
lines changed

3 files changed

+66
-5
lines changed

doc/dev/tests.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,7 @@ There are two primary ways to keep secrets from being written into recordings:
445445

446446
1. The `EnvironmentVariableLoader` will automatically sanitize the values of captured environment variables with the
447447
provided fake values.
448+
1. For sensitive variables, also [use the `options` argument][env_var_options] to shield values in test logs.
448449
2. Additional sanitizers can be registered via `add_*_sanitizer` methods in `devtools_testutils`. For example, the general-use
449450
method for sanitizing recording bodies, headers, and URIs is `add_general_string_sanitizer`. Other sanitizers are
450451
available for more specific scenarios and can be found at [devtools_testutils/sanitizers.py][py_sanitizers].
@@ -582,6 +583,7 @@ For information about more advanced testing scenarios, refer to the [advanced te
582583
[central_conftest]: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/conftest.py
583584
[env_var_docs]: https://github.com/Azure/azure-sdk-for-python/tree/main/eng/tools/azure-sdk-tools/devtools_testutils#use-the-environmentvariableloader
584585
[env_var_loader]: https://github.com/Azure/azure-sdk-for-python/blob/main/eng/tools/azure-sdk-tools/devtools_testutils/envvariable_loader.py
586+
[env_var_options]: https://github.com/Azure/azure-sdk-for-python/tree/main/eng/tools/azure-sdk-tools/devtools_testutils#hide-secret-environment-variables-in-test-logs
585587
[get_credential]: https://github.com/Azure/azure-sdk-for-python/blob/4df650d2ce4c292942009ed648cae21eb9c2121d/eng/tools/azure-sdk-tools/devtools_testutils/azure_recorded_testcase.py#L78
586588
[git_setup]: https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup
587589
[kv_test_resources]: https://github.com/Azure/azure-sdk-for-python/blob/fbdb860630bcc13c1e355828231161849a9bd5a4/sdk/keyvault/test-resources.json

eng/tools/azure-sdk-tools/devtools_testutils/README.md

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Devtools Testutils
22

33
## Objects in this package for use with Azure Testing
4+
45
* [`AzureMgmtPreparer`][azure_mgmt_preparer]: Base class for Management-plane resource preparers
56
* [`is_live`][is_live]: Helper method for determining whether a test run is in live or playback mode
67
* [`get_region_override`][get_region_override]: Helper method for determining resource region
@@ -28,10 +29,16 @@ This loader is meant to be paired with the PowerShell test resource management c
2829
resource management.
2930

3031
The `EnvironmentVariableLoader` accepts a positional `directory` argument and arbitrary keyword-only arguments:
31-
- `directory` is the name of your package's service as it appears in the Python repository; i.e. `service` in `azure-sdk-for-python/sdk/service/azure-service-package`.
32-
- For example, for `azure-keyvault-keys`, the value of `directory` is `keyvault`.
33-
- For each environment variable you want to provide to tests, pass in a keyword argument with the pattern `environment_variable_name="sanitized-value"`.
34-
- For example, to fetch the value of `STORAGE_ENDPOINT` and sanitize this value in recordings as `fake-endpoint`, provide `storage_endpoint="fake-endpoint"` to the `EnvironmentVariableLoader` constructor.
32+
33+
* `directory` is the name of your package's service as it appears in the Python repository; i.e. `service` in `azure-sdk-for-python/sdk/service/azure-service-package`.
34+
* For example, for `azure-keyvault-keys`, the value of `directory` is `keyvault`.
35+
* For each environment variable you want to provide to tests, pass in a keyword argument with the pattern `environment_variable_name="sanitized-value"`.
36+
* For example, to fetch the value of `STORAGE_ENDPOINT` and sanitize this value in recordings as `fake-endpoint`, provide `storage_endpoint="fake-endpoint"` to the `EnvironmentVariableLoader` constructor.
37+
38+
Additionally, the loader accepts an `EnvironmentVariableOptions` object via the `options` kwarg. This should be used
39+
whenever fetching sensitive environment variables like connection strings or account keys. See
40+
[Hide secret environment variables in test logs](#hide-secret-environment-variables-in-test-logs) for details and usage
41+
examples.
3542

3643
Decorated test methods will have the values of environment variables passed to them as keyword arguments, and these
3744
values will automatically have sanitizers registered with the test proxy. More specifically, the true values of
@@ -71,16 +78,67 @@ variables in recordings.
7178
> `{SERVICE}_TENANT_ID`, `{SERVICE}_CLIENT_ID`, and `{SERVICE}_CLIENT_SECRET` for a service principal when using this
7279
> class.
7380
81+
### Hide secret environment variables in test logs
82+
83+
Pytest will log local test variables, including test method parameters, whenever a test fails. This can lead to secret
84+
leak warnings in pipelines even if test resources are transient or mocked. To avoid leaks, you should shield sensitive
85+
variables with the `options` keyword argument.
86+
87+
`options` accepts an [EnvironmentVariableOptions][options] instance, with which you can provide a case insensitive list
88+
of environment variables that should be hidden in tests. [For example:][options_use]
89+
90+
```python
91+
DataLakePreparer = functools.partial(
92+
EnvironmentVariableLoader, "storage",
93+
datalake_storage_account_name="storagename",
94+
datalake_storage_account_key=STORAGE_ACCOUNT_FAKE_KEY,
95+
storage_data_lake_soft_delete_account_name="storagesoftdelname",
96+
storage_data_lake_soft_delete_account_key=STORAGE_ACCOUNT_FAKE_KEY,
97+
options=EnvironmentVariableOptions(
98+
hide_secrets=["datalake_storage_account_key", "storage_data_lake_soft_delete_account_key"]
99+
),
100+
)
101+
```
102+
103+
The loader will raise an error if a variable specified in `hide_secrets` doesn't match any of the variables requested
104+
for loading.
105+
106+
Hidden variables will be passed to tests as an [EnvironmentVariable][environmentvariable] instance instead of a plain
107+
string; the variable's value should be fetched with the `.secret` string attribute. **It's important to avoid assigning
108+
the secret to a local variable.** As mentioned in the start of this section, Pytest logs the values of local variables
109+
upon test failure. Instead, `<variable>.secret` should be accessed only when the value is used; e.g.
110+
[when providing the secret to a credential constructor][environmentvariable_use].
111+
112+
```python
113+
class TestDatalakeCpk(StorageRecordedTestCase):
114+
def _setup(self, account_name: str, account_key: EnvironmentVariable):
115+
url = self.account_url(account_name, 'dfs')
116+
self.dsc = DataLakeServiceClient(url, credential=account_key.secret)
117+
...
118+
119+
# Note that DataLakePreparer passes account name as a plain string, but hides account key in an EnvironmentVariable
120+
@DataLakePreparer()
121+
@recorded_by_proxy
122+
def test_create_directory_cpk(
123+
self, datalake_storage_account_name: str, datalake_storage_account_key: EnvironmentVariable
124+
):
125+
self._setup(datalake_storage_account_name, datalake_storage_account_key)
126+
...
127+
```
74128

75129
<!-- LINKS -->
76130
[azure_mgmt_preparer]: https://github.com/Azure/azure-sdk-for-python/blob/4df650d2ce4c292942009ed648cae21eb9c2121d/eng/tools/azure-sdk-tools/devtools_testutils/mgmt_testcase.py#L42
77131
[credscan]: https://aka.ms/credscan
78132
[credscan_guide]: https://github.com/Azure/azure-sdk-for-python/blob/18611efee7ecf4e591d59b61ba3762d6bdd86304/doc/dev/credscan_process.md
79133
[env_loader]: https://github.com/Azure/azure-sdk-for-python/blob/main/eng/tools/azure-sdk-tools/devtools_testutils/envvariable_loader.py
134+
[environmentvariable]: https://github.com/Azure/azure-sdk-for-python/blob/b6b227edbe318ee79a6a987d063e9823608f3c0a/eng/tools/azure-sdk-tools/devtools_testutils/envvariable_loader.py#L209
135+
[environmentvariable_use]: https://github.com/Azure/azure-sdk-for-python/blob/b6b227edbe318ee79a6a987d063e9823608f3c0a/sdk/storage/azure-storage-file-datalake/tests/test_cpk.py#L27
80136
[fake_credentials]: https://github.com/Azure/azure-sdk-for-python/blob/main/eng/tools/azure-sdk-tools/devtools_testutils/fake_credentials.py
81137
[fake_credentials_async]: https://github.com/Azure/azure-sdk-for-python/blob/main/eng/tools/azure-sdk-tools/devtools_testutils/fake_credentials_async.py
82138
[get_region_override]: https://github.com/Azure/azure-sdk-for-python/blob/4df650d2ce4c292942009ed648cae21eb9c2121d/eng/tools/azure-sdk-tools/devtools_testutils/azure_testcase.py#L52
83139
[is_live]: https://github.com/Azure/azure-sdk-for-python/blob/4df650d2ce4c292942009ed648cae21eb9c2121d/eng/tools/azure-sdk-tools/devtools_testutils/azure_testcase.py#L42
140+
[options]: https://github.com/Azure/azure-sdk-for-python/blob/b6b227edbe318ee79a6a987d063e9823608f3c0a/eng/tools/azure-sdk-tools/devtools_testutils/envvariable_loader.py#L20
141+
[options_use]: https://github.com/Azure/azure-sdk-for-python/blob/b6b227edbe318ee79a6a987d063e9823608f3c0a/sdk/storage/azure-storage-file-datalake/tests/settings/testcase.py#L45
84142
[retry_counter]: https://github.com/Azure/azure-sdk-for-python/blob/4df650d2ce4c292942009ed648cae21eb9c2121d/eng/tools/azure-sdk-tools/devtools_testutils/helpers.py#L119
85143
[response_callback]: https://github.com/Azure/azure-sdk-for-python/blob/4df650d2ce4c292942009ed648cae21eb9c2121d/eng/tools/azure-sdk-tools/devtools_testutils/helpers.py#L127
86144
[test_resources]: https://github.com/Azure/azure-sdk-for-python/tree/main/eng/common/TestResources#readme

eng/tools/azure-sdk-tools/devtools_testutils/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
)
1717

1818
# cSpell:disable
19-
from .envvariable_loader import EnvironmentVariableLoader, EnvironmentVariableOptions
19+
from .envvariable_loader import EnvironmentVariable, EnvironmentVariableLoader, EnvironmentVariableOptions
2020
from .exceptions import AzureTestError, ReservedResourceNameError
2121
from .proxy_fixtures import environment_variables, recorded_test, variable_recorder
2222
from .proxy_startup import start_test_proxy, stop_test_proxy, test_proxy
@@ -102,6 +102,7 @@
102102
"CachedResourceGroupPreparer",
103103
"PemCertificate",
104104
"PowerShellPreparer",
105+
"EnvironmentVariable",
105106
"EnvironmentVariableLoader",
106107
"EnvironmentVariableOptions",
107108
"environment_variables",

0 commit comments

Comments
 (0)