-
Notifications
You must be signed in to change notification settings - Fork 24.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add SAS Token Authentication Support to Azure Repo Plugin #42982
Changes from 3 commits
26c514c
0b6920a
b7045d0
1c5d669
3df99c2
1b9c905
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,8 @@ bin/elasticsearch-keystore add azure.client.default.account | |
bin/elasticsearch-keystore add azure.client.default.key | ||
---------------------------------------------------------------- | ||
|
||
Where `account` is the azure account name and `key` the azure secret key. | ||
Where `account` is the azure account name and `key` the azure secret key. Instead of an azure secret key under `key`, you can alternatively | ||
define a shared access signatures (SAS) token under `sas_token` to use for authentication instead. | ||
These settings are used by the repository's internal azure client. | ||
|
||
Note that you can also define more than one account: | ||
|
@@ -29,14 +30,14 @@ Note that you can also define more than one account: | |
bin/elasticsearch-keystore add azure.client.default.account | ||
bin/elasticsearch-keystore add azure.client.default.key | ||
bin/elasticsearch-keystore add azure.client.secondary.account | ||
bin/elasticsearch-keystore add azure.client.secondary.key | ||
bin/elasticsearch-keystore add azure.client.default.sas_token | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we could have the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah my bad that was just a mistake in copy and pasting, thanks for spotting :) |
||
---------------------------------------------------------------- | ||
|
||
`default` is the default account name which will be used by a repository, | ||
unless you set an explicit one in the | ||
<<repository-azure-repository-settings, repository settings>>. | ||
|
||
Both `account` and `key` storage settings are | ||
The `account`, `key`, and `sas_token` storage settings are | ||
{ref}/secure-settings.html#reloadable-secure-settings[reloadable]. After you | ||
reload the settings, the internal azure clients, which are used to transfer the | ||
snapshot, will utilize the latest settings from the keystore. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -76,6 +76,7 @@ String azureAccount = System.getenv("azure_storage_account") | |
String azureKey = System.getenv("azure_storage_key") | ||
String azureContainer = System.getenv("azure_storage_container") | ||
String azureBasePath = System.getenv("azure_storage_base_path") | ||
String azureSasToken = System.getenv("azure_storage_sas_token") | ||
|
||
test { | ||
exclude '**/AzureStorageCleanupThirdPartyTests.class' | ||
|
@@ -85,10 +86,11 @@ task thirdPartyTest(type: Test) { | |
include '**/AzureStorageCleanupThirdPartyTests.class' | ||
systemProperty 'test.azure.account', azureAccount ? azureAccount : "" | ||
systemProperty 'test.azure.key', azureKey ? azureKey : "" | ||
systemProperty 'test.azure.sas', azureSasToken ? azureSasToken : "" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: can we also use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure :) |
||
systemProperty 'test.azure.container', azureContainer ? azureContainer : "" | ||
systemProperty 'test.azure.base', azureBasePath ? azureBasePath : "" | ||
} | ||
|
||
if (azureAccount || azureKey || azureContainer || azureBasePath) { | ||
if (azureAccount || azureKey || azureContainer || azureBasePath || azureSasToken) { | ||
check.dependsOn(thirdPartyTest) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,12 +29,14 @@ String azureAccount = System.getenv("azure_storage_account") | |
String azureKey = System.getenv("azure_storage_key") | ||
String azureContainer = System.getenv("azure_storage_container") | ||
String azureBasePath = System.getenv("azure_storage_base_path") | ||
String azureSasToken = System.getenv("azure_storage_sas_token") | ||
|
||
if (!azureAccount && !azureKey && !azureContainer && !azureBasePath) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we need to change this too, in order to accommodate with the 3 situations we now have: no sys props, sys prop with key, sys prop with sas token |
||
azureAccount = 'azure_integration_test_account' | ||
azureKey = 'YXp1cmVfaW50ZWdyYXRpb25fdGVzdF9rZXk=' // The key is "azure_integration_test_key" encoded using base64 | ||
azureContainer = 'container_test' | ||
azureBasePath = 'integration_test' | ||
azureSasToken = '' | ||
useFixture = true | ||
} | ||
|
||
|
@@ -63,7 +65,14 @@ integTest { | |
testClusters.integTest { | ||
plugin file(project(':plugins:repository-azure').bundlePlugin.archiveFile) | ||
keystore 'azure.client.integration_test.account', azureAccount | ||
keystore 'azure.client.integration_test.key', azureKey | ||
if (azureKey.isEmpty() == false) { | ||
println "Using access key in external service tests." | ||
keystore 'azure.client.integration_test.key', azureKey | ||
} | ||
if (azureSasToken.isEmpty() == false) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See previous comment, CI might break here as azureSasToken will be just null? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right ... obviously in my local testing I just set the env vars to |
||
println "Using SAS token in external service tests." | ||
keystore 'azure.client.integration_test.sas_token', azureSasToken | ||
} | ||
|
||
if (useFixture) { | ||
tasks.integTest.dependsOn azureStorageFixture | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ | |
|
||
import com.microsoft.azure.storage.LocationMode; | ||
import com.microsoft.azure.storage.RetryPolicy; | ||
import org.elasticsearch.common.Nullable; | ||
import org.elasticsearch.common.Strings; | ||
import org.elasticsearch.common.settings.SecureSetting; | ||
import org.elasticsearch.common.settings.SecureString; | ||
|
@@ -53,6 +54,10 @@ final class AzureStorageSettings { | |
public static final AffixSetting<SecureString> KEY_SETTING = Setting.affixKeySetting(AZURE_CLIENT_PREFIX_KEY, "key", | ||
key -> SecureSetting.secureString(key, null)); | ||
|
||
/** Azure SAS token */ | ||
public static final AffixSetting<SecureString> SAS_TOKEN_SETTING = Setting.affixKeySetting(AZURE_CLIENT_PREFIX_KEY, "sas_token", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you add docs for the new setting as well? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in b7045d0 :) |
||
key -> SecureSetting.secureString(key, null)); | ||
|
||
/** max_retries: Number of retries in case of Azure errors. Defaults to 3 (RetryPolicy.DEFAULT_CLIENT_RETRY_COUNT). */ | ||
public static final Setting<Integer> MAX_RETRIES_SETTING = | ||
Setting.affixKeySetting(AZURE_CLIENT_PREFIX_KEY, "max_retries", | ||
|
@@ -82,29 +87,29 @@ final class AzureStorageSettings { | |
PROXY_HOST_SETTING); | ||
|
||
private final String account; | ||
private final String key; | ||
private final String connectString; | ||
private final String endpointSuffix; | ||
private final TimeValue timeout; | ||
private final int maxRetries; | ||
private final Proxy proxy; | ||
private final LocationMode locationMode; | ||
|
||
// copy-constructor | ||
private AzureStorageSettings(String account, String key, String endpointSuffix, TimeValue timeout, int maxRetries, Proxy proxy, | ||
LocationMode locationMode) { | ||
private AzureStorageSettings(String account, String connectString, String endpointSuffix, TimeValue timeout, int maxRetries, | ||
Proxy proxy, LocationMode locationMode) { | ||
this.account = account; | ||
this.key = key; | ||
this.connectString = connectString; | ||
this.endpointSuffix = endpointSuffix; | ||
this.timeout = timeout; | ||
this.maxRetries = maxRetries; | ||
this.proxy = proxy; | ||
this.locationMode = locationMode; | ||
} | ||
|
||
AzureStorageSettings(String account, String key, String endpointSuffix, TimeValue timeout, int maxRetries, | ||
Proxy.Type proxyType, String proxyHost, Integer proxyPort) { | ||
private AzureStorageSettings(String account, String key, String sasToken, String endpointSuffix, TimeValue timeout, int maxRetries, | ||
Proxy.Type proxyType, String proxyHost, Integer proxyPort) { | ||
this.account = account; | ||
this.key = key; | ||
this.connectString = buildConnectString(account, key, sasToken, endpointSuffix); | ||
this.endpointSuffix = endpointSuffix; | ||
this.timeout = timeout; | ||
this.maxRetries = maxRetries; | ||
|
@@ -145,13 +150,26 @@ public Proxy getProxy() { | |
return proxy; | ||
} | ||
|
||
public String buildConnectionString() { | ||
public String getConnectString() { | ||
return connectString; | ||
} | ||
|
||
private static String buildConnectString(String account, @Nullable String key, @Nullable String sasToken, String endpointSuffix) { | ||
final boolean hasSasToken = Strings.hasText(sasToken); | ||
final boolean hasKey = Strings.hasText(key); | ||
if (hasSasToken == false && hasKey == false) { | ||
throw new SettingsException("Neither a secret key nor a shared access token was set."); | ||
} | ||
if (hasSasToken && hasKey) { | ||
throw new SettingsException("Both a secret as well as a shared access token were set."); | ||
} | ||
final StringBuilder connectionStringBuilder = new StringBuilder(); | ||
connectionStringBuilder.append("DefaultEndpointsProtocol=https") | ||
.append(";AccountName=") | ||
.append(account) | ||
.append(";AccountKey=") | ||
.append(key); | ||
connectionStringBuilder.append("DefaultEndpointsProtocol=https").append(";AccountName=").append(account); | ||
if (hasKey) { | ||
connectionStringBuilder.append(";AccountKey=").append(key); | ||
} else { | ||
connectionStringBuilder.append(";SharedAccessSignature=").append(sasToken); | ||
} | ||
if (Strings.hasText(endpointSuffix)) { | ||
connectionStringBuilder.append(";EndpointSuffix=").append(endpointSuffix); | ||
} | ||
|
@@ -166,7 +184,6 @@ public LocationMode getLocationMode() { | |
public String toString() { | ||
final StringBuilder sb = new StringBuilder("AzureStorageSettings{"); | ||
sb.append("account='").append(account).append('\''); | ||
sb.append(", key='").append(key).append('\''); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add connectString instead here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ywelsch this was actually an on purpose removal, do we really want to add secure credentials in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's better to remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, for that we'd have to keep some field that holds this information. Maybe not worth it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 |
||
sb.append(", timeout=").append(timeout); | ||
sb.append(", endpointSuffix='").append(endpointSuffix).append('\''); | ||
sb.append(", maxRetries=").append(maxRetries); | ||
|
@@ -201,8 +218,9 @@ public static Map<String, AzureStorageSettings> load(Settings settings) { | |
/** Parse settings for a single client. */ | ||
private static AzureStorageSettings getClientSettings(Settings settings, String clientName) { | ||
try (SecureString account = getConfigValue(settings, clientName, ACCOUNT_SETTING); | ||
SecureString key = getConfigValue(settings, clientName, KEY_SETTING)) { | ||
return new AzureStorageSettings(account.toString(), key.toString(), | ||
SecureString key = getConfigValue(settings, clientName, KEY_SETTING); | ||
SecureString sasToken = getConfigValue(settings, clientName, SAS_TOKEN_SETTING)) { | ||
return new AzureStorageSettings(account.toString(), key.toString(), sasToken.toString(), | ||
getValue(settings, clientName, ENDPOINT_SUFFIX_SETTING), | ||
getValue(settings, clientName, TIMEOUT_SETTING), | ||
getValue(settings, clientName, MAX_RETRIES_SETTING), | ||
|
@@ -228,10 +246,9 @@ static Map<String, AzureStorageSettings> overrideLocationMode(Map<String, AzureS | |
LocationMode locationMode) { | ||
final var map = new HashMap<String, AzureStorageSettings>(); | ||
for (final Map.Entry<String, AzureStorageSettings> entry : clientsSettings.entrySet()) { | ||
final AzureStorageSettings azureSettings = new AzureStorageSettings(entry.getValue().account, entry.getValue().key, | ||
entry.getValue().endpointSuffix, entry.getValue().timeout, entry.getValue().maxRetries, entry.getValue().proxy, | ||
locationMode); | ||
map.put(entry.getKey(), azureSettings); | ||
map.put(entry.getKey(), | ||
new AzureStorageSettings(entry.getValue().account, entry.getValue().connectString, entry.getValue().endpointSuffix, | ||
entry.getValue().timeout, entry.getValue().maxRetries, entry.getValue().proxy, locationMode)); | ||
} | ||
return Map.copyOf(map); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should say a word about the type of SAS token and the required permissions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the type doesn't matter just the permissions (service as well as accounts SAS should work)? I added a comment on the permissions now :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Talked to Armin using another channel. I'd expect to see some
create
orlist
permissions too hereThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, updated this with all the details now.
create
we don't need since it's implied bywrite
for the operations we execute https://docs.microsoft.com/en-us/rest/api/storageservices/Constructing-an-Account-SAS?redirectedfrom=MSDN#blob-service (as a matter of fact it is for all operations on the blob service). But we neededlist
.Also, I added some detail on the service and resource type the token has to apply to.