Skip to content

Commit 0cd8779

Browse files
Finetune Azure Key Vault acceptance tests (#1130)
* chore: Update Azure Acceptance test * chore: Update AzureKeyVaultTest to not rely on known key/secret names -- The AT assumes that at least two Secrets and Keys are pre-created in Azure Vault * chore: Simplify AzureKeyVaultSignerTest by not relying on known key names * chore: Delete Azure multivalue test as existing test already covers it * chore: Fix Azure AT to fetch Secrets and Keys at runtime * chore: Readme for Azure acceptance test key vault setup * try/resource executor service * Update acceptance-tests/README.md --------- Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
1 parent 645e1b3 commit 0cd8779

File tree

10 files changed

+431
-359
lines changed

10 files changed

+431
-359
lines changed

acceptance-tests/README.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
## Acceptance Tests
2+
3+
Following instructions are required to setup environment for running acceptance tests for external vault providers:
4+
5+
### Azure Key Vault
6+
In order to run Azure Key Vault acceptance tests, you need to set the following environment variables:
7+
```
8+
export AZURE_CLIENT_ID=<YOUR AZURE CLIENT ID>
9+
export AZURE_CLIENT_SECRET=<YOUR AZURE CLIENT SECRET>
10+
export AZURE_KEY_VAULT_NAME=<YOUR AZURE KEY VAULT NAME>
11+
export AZURE_TENANT_ID=<YOUR AZURE TENANT ID>
12+
```
13+
14+
### Azure Key Vault BLS Test Keys
15+
The "Secret" object contains at least two entries for BLS private keys. The first entry is a multi-line secret and the
16+
second one is a single line secret. For example, Web3Signer `BLSTestUtil.java` can generate following keys for
17+
non-production/test environment which can be imported into Azure Key Vault as secrets:
18+
19+
```
20+
cat << EOF > bls-test-keys.txt
21+
0x60b420bb3851d9d47acb933dbe70399bf6c92da33af01d4fb770e98c0325f41d
22+
0x73d51abbd89cb8196f0efb6892f94d68fccc2c35f0b84609e5f12c55dd85aba8
23+
0x39722cbbf8b91a4b9045c5e6175f1001eac32f7fcd5eccda5c6e62fc4e638508
24+
0x4c9326bb9805fa8f85882c12eae724cef0c62e118427f5948aefa5c428c43c93
25+
0x384a62688ee1d9a01c9d58e303f2b3c9bc1885e8131565386f75f7ae6ca8d147
26+
0x4b6b5c682f2db7e510e0c00ed67ac896c21b847acadd8df29cf63a77470989d2
27+
0x13086d684f4b1a1632178a8c5be08a2fb01287c4a78313c41373701eb8e66232
28+
0x25296867ee96fa5b275af1b72f699efcb61586565d4c3c7e41f4b3e692471abd
29+
0x10e1a313e573d96abe701d8848742cf88166dd2ded38ac22267a05d1d62baf71
30+
0x0bdeebbad8f9b240192635c42f40f2d02ee524c5a3fe8cda53fb4897b08c66fe
31+
EOF
32+
```
33+
34+
Then import the above keys into Azure Key Vault as a secret (assuming Azure CLI is installed and logged in):
35+
36+
```
37+
az keyvault secret set --vault-name "YOUR VAULT NAME" --name "BLS-TEST-KEYS" --file "./bls-test-keys.txt"
38+
```
39+
40+
Finally, one more with single-line secret with a tag value ENV=TEST:
41+
42+
```
43+
az keyvault secret set --vault-name "YOUR VAULT NAME" --name "BLS-TEST-TAGGED-KEY" --tags ENV=TEST --value 0x5e8d5667ce78982a07242739ab03dc63c91e830c80a5b6adca777e3f216a405d
44+
```
45+
46+
### Azure Key Vault SECP Test Keys
47+
48+
The tests perform SECP remote signing operations using Key Vault "Key" objects. The tests assume that you have imported
49+
following SECP256K1 private keys into Azure Key Vault (test keys are adapted from [ethpandaops](https://github.com/ethpandaops/ethereum-package/blob/main/src/prelaunch_data_generator/genesis_constants/genesis_constants.star)
50+
where at least 1 key is tagged with `ENV=TEST`.
51+
```
52+
# m/44'/60'/0'/0/18
53+
new_prefunded_account(
54+
"0xD9211042f35968820A3407ac3d80C725f8F75c14",
55+
"a492823c3e193d6c595f37a18e3c06650cf4c74558cc818b16130b293716106f",
56+
),
57+
# m/44'/60'/0'/0/19
58+
new_prefunded_account(
59+
"0xD8F3183DEF51A987222D845be228e0Bbb932C222",
60+
"c5114526e042343c6d1899cad05e1c00ba588314de9b96929914ee0df18d46b2",
61+
),
62+
# m/44'/60'/0'/0/20
63+
new_prefunded_account(
64+
"0xafF0CA253b97e54440965855cec0A8a2E2399896",
65+
"04b9f63ecf84210c5366c66d68fa1f5da1fa4f634fad6dfc86178e4d79ff9e59",
66+
),
67+
```
68+
69+
In order to import the above keys, you need to use Azure CLI commands like below:
70+
- Key 18
71+
```sh
72+
PRIVATE_KEY_HEX="a492823c3e193d6c595f37a18e3c06650cf4c74558cc818b16130b293716106f"
73+
74+
# Create DER format then convert to PEM
75+
(echo "302e0201010420${PRIVATE_KEY_HEX}a00706052b8104000a" | xxd -r -p) | \
76+
openssl ec -inform DER -outform PEM -out private-key.pem
77+
78+
# Verify it
79+
openssl ec -in private-key.pem -text -noout
80+
81+
# Import into Azure (using your own values for vault-name)
82+
az keyvault key import --curve P-256K --kty EC --vault-name "YOUR VAULT NAME" --name "SECP-18" --pem-file ./private-key.pem
83+
```
84+
85+
- Key 19
86+
87+
```sh
88+
PRIVATE_KEY_HEX="c5114526e042343c6d1899cad05e1c00ba588314de9b96929914ee0df18d46b2"
89+
90+
# Create DER format then convert to PEM
91+
(echo "302e0201010420${PRIVATE_KEY_HEX}a00706052b8104000a" | xxd -r -p) | \
92+
openssl ec -inform DER -outform PEM -out private-key.pem
93+
94+
# Verify it
95+
openssl ec -in private-key.pem -text -noout
96+
97+
# Import into Azure (using your own values for vault-name)
98+
az keyvault key import --curve P-256K --kty EC --vault-name "YOUR VAULT NAME" --name "SECP-19" --pem-file ./private-key.pem
99+
```
100+
101+
- Key 20 (tagged with ENV=TEST)
102+
103+
```sh
104+
PRIVATE_KEY_HEX="04b9f63ecf84210c5366c66d68fa1f5da1fa4f634fad6dfc86178e4d79ff9e59"
105+
106+
# Create DER format then convert to PEM
107+
(echo "302e0201010420${PRIVATE_KEY_HEX}a00706052b8104000a" | xxd -r -p) | \
108+
openssl ec -inform DER -outform PEM -out private-key.pem
109+
110+
# Verify it
111+
openssl ec -in private-key.pem -text -noout
112+
113+
# Import into Azure (using your own values for vault-name)
114+
az keyvault key import --curve P-256K --kty EC --vault-name "YOUR VAULT NAME" --name "SECP-20-TAGGED" --pem-file ./private-key.pem --tags ENV=TEST
115+
116+
```

acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/utils/MetadataFileHelpers.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,13 @@ public void createAzureKeyYamlFileAt(
173173
final String clientId,
174174
final String clientSecret,
175175
final String keyVaultName,
176-
final String tenantId) {
176+
final String tenantId,
177+
final String keyName) {
177178
try {
178179
final Map<String, String> signingMetadata = new HashMap<>();
179180
signingMetadata.put("type", "azure-key");
180181
signingMetadata.put("vaultName", keyVaultName);
181-
signingMetadata.put("keyName", "TestKey2");
182+
signingMetadata.put("keyName", keyName);
182183
signingMetadata.put("clientId", clientId);
183184
signingMetadata.put("clientSecret", clientSecret);
184185
signingMetadata.put("tenantId", tenantId);

acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/bulkloading/AzureKeyVaultAcceptanceTest.java

Lines changed: 100 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -13,47 +13,43 @@
1313
package tech.pegasys.web3signer.tests.bulkloading;
1414

1515
import static org.assertj.core.api.Assertions.assertThat;
16+
import static org.hamcrest.Matchers.containsInAnyOrder;
1617
import static org.hamcrest.Matchers.equalTo;
17-
import static org.hamcrest.Matchers.hasItems;
1818
import static org.hamcrest.Matchers.hasSize;
1919
import static org.junit.jupiter.api.Assumptions.assumeTrue;
2020
import static tech.pegasys.web3signer.core.config.HealthCheckNames.KEYS_CHECK_AZURE_BULK_LOADING;
2121
import static tech.pegasys.web3signer.dsl.utils.HealthCheckResultUtil.getHealtcheckKeysLoaded;
2222
import static tech.pegasys.web3signer.dsl.utils.HealthCheckResultUtil.getHealthcheckErrorCount;
23+
import static tech.pegasys.web3signer.keystorage.azure.AzureKeyVault.createUsingClientSecretCredentials;
2324

25+
import tech.pegasys.teku.bls.BLSSecretKey;
2426
import tech.pegasys.web3signer.dsl.signer.SignerConfigurationBuilder;
27+
import tech.pegasys.web3signer.keystorage.azure.AzureKeyVault;
2528
import tech.pegasys.web3signer.signing.KeyType;
2629
import tech.pegasys.web3signer.signing.config.AzureKeyVaultParameters;
2730
import tech.pegasys.web3signer.signing.config.DefaultAzureKeyVaultParameters;
2831
import tech.pegasys.web3signer.tests.AcceptanceTestBase;
2932

33+
import java.util.List;
3034
import java.util.Map;
31-
import java.util.stream.Stream;
35+
import java.util.concurrent.ExecutorService;
36+
import java.util.concurrent.Executors;
3237

33-
import com.fasterxml.jackson.core.JsonProcessingException;
38+
import com.google.common.annotations.VisibleForTesting;
3439
import io.restassured.http.ContentType;
3540
import io.restassured.response.Response;
3641
import org.apache.commons.lang3.StringUtils;
42+
import org.apache.tuweni.bytes.Bytes32;
3743
import org.junit.jupiter.api.BeforeAll;
3844
import org.junit.jupiter.params.ParameterizedTest;
39-
import org.junit.jupiter.params.provider.Arguments;
4045
import org.junit.jupiter.params.provider.EnumSource;
41-
import org.junit.jupiter.params.provider.MethodSource;
4246

4347
public class AzureKeyVaultAcceptanceTest extends AcceptanceTestBase {
4448

4549
private static final String CLIENT_ID = System.getenv("AZURE_CLIENT_ID");
4650
private static final String CLIENT_SECRET = System.getenv("AZURE_CLIENT_SECRET");
4751
private static final String TENANT_ID = System.getenv("AZURE_TENANT_ID");
4852
private static final String VAULT_NAME = System.getenv("AZURE_KEY_VAULT_NAME");
49-
private static final String BLS_KEY =
50-
"0x989d34725a2bfc3f15105f3f5fc8741f436c25ee1ee4f948e425d6bcb8c56bce6e06c269635b7e985a7ffa639e2409bf";
51-
private static final String BLS_TAGGED_KEY =
52-
"0xb3b6fb8dab2a4c9d00247c18c4b7e91c62da3f7ad31c822c00097f93ac8ff2c4a526611f7d0a9c85946e93f371852c69";
53-
private static final String SECP_KEY =
54-
"0xa95663509e608da3c2af5a48eb4315321f8430cbed5518a44590cc9d367f01dc72ebbc583fc7d94f9fdc20eb6e162c9f8cb35be8a91a3b1d32a63ecc10be4e08";
55-
private static final String SECP_TAGGED_KEY =
56-
"0x234053dbe014ebe573e5e8f6eab5e0417bf705466009f7c15b8d23593abd1bda426593d92b32efb240afe6efa46d5679fad0dc427e0aa0fc61c2464ce93c7c5e";
5753

5854
@BeforeAll
5955
public static void setup() {
@@ -63,10 +59,84 @@ public static void setup() {
6359
assumeTrue(!StringUtils.isEmpty(VAULT_NAME), "Set AZURE_KEY_VAULT_NAME environment variable");
6460
}
6561

62+
/**
63+
* These keys are expected to be pre-created in Azure keystore. The first secret is multivalue
64+
* with 10 keys. The second secret is created with single value/key and tagged with ENV:TEST.
65+
*
66+
* @return list of expected BLS public keys in hex format
67+
*/
68+
static List<String> expectedBLSPubKeys() {
69+
return getBLSSecretsFromAzureVault().stream()
70+
.flatMap(azureSecret -> azureSecret.values().stream())
71+
.map(
72+
secret ->
73+
BLSSecretKey.fromBytes(Bytes32.fromHexString(secret)).toPublicKey().toHexString())
74+
.toList();
75+
}
76+
77+
static List<String> expectedBLSPubKeyWithTag(final String tagKey, final String tagValue) {
78+
return getBLSSecretsFromAzureVault().stream()
79+
.filter(
80+
azureSecret ->
81+
azureSecret.tags() != null
82+
&& azureSecret.tags().containsKey(tagKey)
83+
&& azureSecret.tags().get(tagKey).equals(tagValue))
84+
.flatMap(azureSecret -> azureSecret.values().stream())
85+
.map(
86+
secret ->
87+
BLSSecretKey.fromBytes(Bytes32.fromHexString(secret)).toPublicKey().toHexString())
88+
.toList();
89+
}
90+
91+
/**
92+
* Expected SECP256K1 public keys pre-created in Azure keystore.
93+
*
94+
* @return list of expected SECP256K1 public keys in hex format
95+
*/
96+
static List<String> expectedSECPPubKeys() {
97+
return getSECPKeysFromAzureVault().stream().map(AzureKeyVault.AzureKey::publicKeyHex).toList();
98+
}
99+
100+
static List<String> expectedSECPPubKeyWithTag(final String tagKey, final String tagValue) {
101+
return getSECPKeysFromAzureVault().stream()
102+
.filter(
103+
azureSecret ->
104+
azureSecret.tags() != null
105+
&& azureSecret.tags().containsKey(tagKey)
106+
&& azureSecret.tags().get(tagKey).equals(tagValue))
107+
.map(AzureKeyVault.AzureKey::publicKeyHex)
108+
.toList();
109+
}
110+
111+
@VisibleForTesting
112+
public static List<AzureKeyVault.AzureKey> getSECPKeysFromAzureVault() {
113+
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
114+
final AzureKeyVault azureKeyVault =
115+
createUsingClientSecretCredentials(
116+
CLIENT_ID, CLIENT_SECRET, TENANT_ID, VAULT_NAME, executor, 60);
117+
118+
final var azureKeys = azureKeyVault.getAzureKeys();
119+
assertThat(azureKeys).isNotEmpty();
120+
return azureKeys;
121+
}
122+
}
123+
124+
public static List<AzureKeyVault.AzureSecret> getBLSSecretsFromAzureVault() {
125+
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
126+
final AzureKeyVault azureKeyVault =
127+
createUsingClientSecretCredentials(
128+
CLIENT_ID, CLIENT_SECRET, TENANT_ID, VAULT_NAME, executor, 60);
129+
130+
return azureKeyVault.getAzureSecrets();
131+
}
132+
}
133+
66134
@ParameterizedTest
67135
@EnumSource(KeyType.class)
68-
void ensureSecretsInKeyVaultAreLoadedAndReportedViaPublicKeysApi(final KeyType keyType)
69-
throws JsonProcessingException {
136+
void ensureSecretsInKeyVaultAreLoadedAndReportedViaPublicKeysApi(final KeyType keyType) {
137+
final List<String> expectedPubKeys =
138+
keyType == KeyType.BLS ? expectedBLSPubKeys() : expectedSECPPubKeys();
139+
70140
final AzureKeyVaultParameters azureParams =
71141
new DefaultAzureKeyVaultParameters(VAULT_NAME, CLIENT_ID, TENANT_ID, CLIENT_SECRET);
72142

@@ -83,7 +153,7 @@ void ensureSecretsInKeyVaultAreLoadedAndReportedViaPublicKeysApi(final KeyType k
83153
.then()
84154
.statusCode(200)
85155
.contentType(ContentType.JSON)
86-
.body("", hasItems(expectedKey(keyType, false)));
156+
.body("", containsInAnyOrder(expectedPubKeys.toArray()));
87157

88158
final Response healthcheckResponse = signer.healthcheck();
89159
healthcheckResponse
@@ -92,36 +162,36 @@ void ensureSecretsInKeyVaultAreLoadedAndReportedViaPublicKeysApi(final KeyType k
92162
.contentType(ContentType.JSON)
93163
.body("status", equalTo("UP"));
94164

95-
// BLS keys include additional multi-line key with 200 keys
96-
final int expectedKeyLoaded = keyType == KeyType.BLS ? 202 : 2;
97-
98165
final String jsonBody = healthcheckResponse.body().asString();
99166
final int keysLoaded = getHealtcheckKeysLoaded(jsonBody, KEYS_CHECK_AZURE_BULK_LOADING);
100-
assertThat(keysLoaded).isEqualTo(expectedKeyLoaded);
167+
assertThat(keysLoaded).isEqualTo(expectedPubKeys.size());
101168
}
102169

103-
@ParameterizedTest(name = "{index} - KeyType: {0}, using config file: {1}")
104-
@MethodSource("azureSecretsViaTag")
105-
void azureSecretsViaTag(final KeyType keyType, boolean useConfigFile) {
170+
@ParameterizedTest(name = "{index} - KeyType: {0}")
171+
@EnumSource(KeyType.class)
172+
void azureSecretsViaTag(final KeyType keyType) {
106173
final AzureKeyVaultParameters azureParams =
107174
new DefaultAzureKeyVaultParameters(
108175
VAULT_NAME, CLIENT_ID, TENANT_ID, CLIENT_SECRET, Map.of("ENV", "TEST"));
109176

110177
final SignerConfigurationBuilder configBuilder =
111178
new SignerConfigurationBuilder()
112179
.withMode(calculateMode(keyType))
113-
.withUseConfigFile(useConfigFile)
114180
.withAzureKeyVaultParameters(azureParams)
115181
.withUseConfigFile(true);
116182

117183
startSigner(configBuilder.build());
118184

185+
final List<String> expectedPubKey =
186+
keyType == KeyType.BLS
187+
? expectedBLSPubKeyWithTag("ENV", "TEST")
188+
: expectedSECPPubKeyWithTag("ENV", "TEST");
119189
final Response response = signer.callApiPublicKeys(keyType);
120190
response
121191
.then()
122192
.statusCode(200)
123193
.contentType(ContentType.JSON)
124-
.body("", hasItems(expectedKey(keyType, true)));
194+
.body("", containsInAnyOrder(expectedPubKey.toArray()));
125195

126196
// the tag filter will return only valid keys. The healthcheck should be UP
127197
final Response healthcheckResponse = signer.healthcheck();
@@ -131,22 +201,14 @@ void azureSecretsViaTag(final KeyType keyType, boolean useConfigFile) {
131201
.contentType(ContentType.JSON)
132202
.body("status", equalTo("UP"));
133203

134-
// keys loaded should be 1 as well.
204+
// keys loaded should be >= 1 and error count should be 0
135205
final String jsonBody = healthcheckResponse.body().asString();
136206
final int keysLoaded = getHealtcheckKeysLoaded(jsonBody, KEYS_CHECK_AZURE_BULK_LOADING);
137207
final int errorCount = getHealthcheckErrorCount(jsonBody, KEYS_CHECK_AZURE_BULK_LOADING);
138-
assertThat(keysLoaded).isOne();
208+
assertThat(keysLoaded).isNotZero();
139209
assertThat(errorCount).isZero();
140210
}
141211

142-
private static Stream<Arguments> azureSecretsViaTag() {
143-
return Stream.of(
144-
Arguments.arguments(KeyType.BLS, false),
145-
Arguments.arguments(KeyType.BLS, true),
146-
Arguments.arguments(KeyType.SECP256K1, false),
147-
Arguments.arguments(KeyType.SECP256K1, true));
148-
}
149-
150212
@ParameterizedTest
151213
@EnumSource(KeyType.class)
152214
void invalidVaultParametersFailsToLoadKeys(final KeyType keyType) {
@@ -194,16 +256,12 @@ void envVarsAreUsedToDefaultAzureParams(final KeyType keyType) {
194256
startSigner(configBuilder.build());
195257

196258
final Response response = signer.callApiPublicKeys(keyType);
259+
final List<String> expectedPubKeys =
260+
keyType == KeyType.BLS ? expectedBLSPubKeys() : expectedSECPPubKeys();
197261
response
198262
.then()
199263
.statusCode(200)
200264
.contentType(ContentType.JSON)
201-
.body("", hasItems(expectedKey(keyType, false)));
202-
}
203-
204-
private String expectedKey(final KeyType keyType, final boolean tagged) {
205-
return keyType == KeyType.BLS
206-
? tagged ? BLS_TAGGED_KEY : BLS_KEY
207-
: tagged ? SECP_TAGGED_KEY : SECP_KEY;
265+
.body("", containsInAnyOrder(expectedPubKeys.toArray()));
208266
}
209267
}

0 commit comments

Comments
 (0)