Skip to content

Commit a34db4e

Browse files
authored
Support for accessing Azure repositories through a proxy (#23518)
You can define a proxy using the following settings: ```yml azure.client.default.proxy.host: proxy.host azure.client.default.proxy.port: 8888 azure.client.default.proxy.type: http ``` Supported values for `proxy.type` are `direct`, `http` or `socks`. Defaults to `direct` (no proxy). Closes #23506 BTW I changed a test `testGetSelectedClientBackoffPolicyNbRetries` as it was using an old setting name `cloud.azure.storage.azure.max_retries` instead of `azure.client.azure1.max_retries`.
1 parent 62a7205 commit a34db4e

File tree

8 files changed

+231
-33
lines changed

8 files changed

+231
-33
lines changed

docs/plugins/repository-azure.asciidoc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,19 @@ The Azure Repository plugin works with all Standard storage accounts
6767
https://azure.microsoft.com/en-gb/documentation/articles/storage-premium-storage[Premium Locally Redundant Storage] (`Premium_LRS`) is **not supported** as it is only usable as VM disk storage, not as general storage.
6868
===============================================
6969

70+
You can register a proxy per client using the following settings:
71+
72+
[source,yaml]
73+
----
74+
azure.client.default.proxy.host: proxy.host
75+
azure.client.default.proxy.port: 8888
76+
azure.client.default.proxy.type: http
77+
----
78+
79+
Supported values for `proxy.type` are `direct` (default), `http` or `socks`.
80+
When `proxy.type` is set to `http` or `socks`, `proxy.host` and `proxy.port` must be provided.
81+
82+
7083
[[repository-azure-repository-settings]]
7184
===== Repository settings
7285

plugins/repository-azure/src/main/java/org/elasticsearch/cloud/azure/storage/AzureStorageService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,16 @@
2222
import com.microsoft.azure.storage.LocationMode;
2323
import com.microsoft.azure.storage.StorageException;
2424
import org.elasticsearch.common.blobstore.BlobMetaData;
25+
import org.elasticsearch.common.settings.Setting;
2526
import org.elasticsearch.common.unit.ByteSizeUnit;
2627
import org.elasticsearch.common.unit.ByteSizeValue;
2728

2829
import java.io.IOException;
2930
import java.io.InputStream;
3031
import java.io.OutputStream;
32+
import java.net.Proxy;
3133
import java.net.URISyntaxException;
34+
import java.util.Locale;
3235
import java.util.Map;
3336

3437
/**

plugins/repository-azure/src/main/java/org/elasticsearch/cloud/azure/storage/AzureStorageServiceImpl.java

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import com.microsoft.azure.storage.CloudStorageAccount;
2323
import com.microsoft.azure.storage.LocationMode;
24+
import com.microsoft.azure.storage.OperationContext;
2425
import com.microsoft.azure.storage.RetryExponentialRetry;
2526
import com.microsoft.azure.storage.RetryPolicy;
2627
import com.microsoft.azure.storage.StorageException;
@@ -29,6 +30,7 @@
2930
import com.microsoft.azure.storage.blob.CloudBlobClient;
3031
import com.microsoft.azure.storage.blob.CloudBlobContainer;
3132
import com.microsoft.azure.storage.blob.CloudBlockBlob;
33+
import com.microsoft.azure.storage.blob.DeleteSnapshotsOption;
3234
import com.microsoft.azure.storage.blob.ListBlobItem;
3335
import org.apache.logging.log4j.message.ParameterizedMessage;
3436
import org.apache.logging.log4j.util.Supplier;
@@ -131,12 +133,23 @@ CloudBlobClient getSelectedClient(String clientName, LocationMode mode) {
131133
return client;
132134
}
133135

136+
private OperationContext generateOperationContext(String clientName) {
137+
OperationContext context = new OperationContext();
138+
AzureStorageSettings azureStorageSettings = this.storageSettings.get(clientName);
139+
140+
if (azureStorageSettings.getProxy() != null) {
141+
context.setProxy(azureStorageSettings.getProxy());
142+
}
143+
144+
return context;
145+
}
146+
134147
@Override
135148
public boolean doesContainerExist(String account, LocationMode mode, String container) {
136149
try {
137150
CloudBlobClient client = this.getSelectedClient(account, mode);
138151
CloudBlobContainer blobContainer = client.getContainerReference(container);
139-
return SocketAccess.doPrivilegedException(blobContainer::exists);
152+
return SocketAccess.doPrivilegedException(() -> blobContainer.exists(null, null, generateOperationContext(account)));
140153
} catch (Exception e) {
141154
logger.error("can not access container [{}]", container);
142155
}
@@ -148,7 +161,7 @@ public void removeContainer(String account, LocationMode mode, String container)
148161
CloudBlobClient client = this.getSelectedClient(account, mode);
149162
CloudBlobContainer blobContainer = client.getContainerReference(container);
150163
logger.trace("removing container [{}]", container);
151-
SocketAccess.doPrivilegedException(blobContainer::deleteIfExists);
164+
SocketAccess.doPrivilegedException(() -> blobContainer.deleteIfExists(null, null, generateOperationContext(account)));
152165
}
153166

154167
@Override
@@ -157,7 +170,7 @@ public void createContainer(String account, LocationMode mode, String container)
157170
CloudBlobClient client = this.getSelectedClient(account, mode);
158171
CloudBlobContainer blobContainer = client.getContainerReference(container);
159172
logger.trace("creating container [{}]", container);
160-
SocketAccess.doPrivilegedException(blobContainer::createIfNotExists);
173+
SocketAccess.doPrivilegedException(() -> blobContainer.createIfNotExists(null, null, generateOperationContext(account)));
161174
} catch (IllegalArgumentException e) {
162175
logger.trace((Supplier<?>) () -> new ParameterizedMessage("fails creating container [{}]", container), e);
163176
throw new RepositoryException(container, e.getMessage(), e);
@@ -174,7 +187,8 @@ public void deleteFiles(String account, LocationMode mode, String container, Str
174187
SocketAccess.doPrivilegedVoidException(() -> {
175188
if (blobContainer.exists()) {
176189
// We list the blobs using a flat blob listing mode
177-
for (ListBlobItem blobItem : blobContainer.listBlobs(path, true)) {
190+
for (ListBlobItem blobItem : blobContainer.listBlobs(path, true, EnumSet.noneOf(BlobListingDetails.class), null,
191+
generateOperationContext(account))) {
178192
String blobName = blobNameFromUri(blobItem.getUri());
179193
logger.trace("removing blob [{}] full URI was [{}]", blobName, blobItem.getUri());
180194
deleteBlob(account, mode, container, blobName);
@@ -208,9 +222,9 @@ public boolean blobExists(String account, LocationMode mode, String container, S
208222
// Container name must be lower case.
209223
CloudBlobClient client = this.getSelectedClient(account, mode);
210224
CloudBlobContainer blobContainer = client.getContainerReference(container);
211-
if (SocketAccess.doPrivilegedException(blobContainer::exists)) {
225+
if (SocketAccess.doPrivilegedException(() -> blobContainer.exists(null, null, generateOperationContext(account)))) {
212226
CloudBlockBlob azureBlob = blobContainer.getBlockBlobReference(blob);
213-
return SocketAccess.doPrivilegedException(azureBlob::exists);
227+
return SocketAccess.doPrivilegedException(() -> azureBlob.exists(null, null, generateOperationContext(account)));
214228
}
215229

216230
return false;
@@ -223,10 +237,11 @@ public void deleteBlob(String account, LocationMode mode, String container, Stri
223237
// Container name must be lower case.
224238
CloudBlobClient client = this.getSelectedClient(account, mode);
225239
CloudBlobContainer blobContainer = client.getContainerReference(container);
226-
if (SocketAccess.doPrivilegedException(blobContainer::exists)) {
240+
if (SocketAccess.doPrivilegedException(() -> blobContainer.exists(null, null, generateOperationContext(account)))) {
227241
logger.trace("container [{}]: blob [{}] found. removing.", container, blob);
228242
CloudBlockBlob azureBlob = blobContainer.getBlockBlobReference(blob);
229-
SocketAccess.doPrivilegedVoidException(azureBlob::delete);
243+
SocketAccess.doPrivilegedVoidException(() -> azureBlob.delete(DeleteSnapshotsOption.NONE, null, null,
244+
generateOperationContext(account)));
230245
}
231246
}
232247

@@ -235,15 +250,15 @@ public InputStream getInputStream(String account, LocationMode mode, String cont
235250
logger.trace("reading container [{}], blob [{}]", container, blob);
236251
CloudBlobClient client = this.getSelectedClient(account, mode);
237252
CloudBlockBlob blockBlobReference = client.getContainerReference(container).getBlockBlobReference(blob);
238-
return SocketAccess.doPrivilegedException(blockBlobReference::openInputStream);
253+
return SocketAccess.doPrivilegedException(() -> blockBlobReference.openInputStream(null, null, generateOperationContext(account)));
239254
}
240255

241256
@Override
242257
public OutputStream getOutputStream(String account, LocationMode mode, String container, String blob) throws URISyntaxException, StorageException {
243258
logger.trace("writing container [{}], blob [{}]", container, blob);
244259
CloudBlobClient client = this.getSelectedClient(account, mode);
245260
CloudBlockBlob blockBlobReference = client.getContainerReference(container).getBlockBlobReference(blob);
246-
return SocketAccess.doPrivilegedException(blockBlobReference::openOutputStream);
261+
return SocketAccess.doPrivilegedException(() -> blockBlobReference.openOutputStream(null, null, generateOperationContext(account)));
247262
}
248263

249264
@Override
@@ -260,7 +275,7 @@ public Map<String, BlobMetaData> listBlobsByPrefix(String account, LocationMode
260275
SocketAccess.doPrivilegedVoidException(() -> {
261276
if (blobContainer.exists()) {
262277
for (ListBlobItem blobItem : blobContainer.listBlobs(keyPath + (prefix == null ? "" : prefix), false,
263-
enumBlobListingDetails, null, null)) {
278+
enumBlobListingDetails, null, generateOperationContext(account))) {
264279
URI uri = blobItem.getUri();
265280
logger.trace("blob url [{}]", uri);
266281

@@ -284,11 +299,11 @@ public void moveBlob(String account, LocationMode mode, String container, String
284299
CloudBlobClient client = this.getSelectedClient(account, mode);
285300
CloudBlobContainer blobContainer = client.getContainerReference(container);
286301
CloudBlockBlob blobSource = blobContainer.getBlockBlobReference(sourceBlob);
287-
if (SocketAccess.doPrivilegedException(blobSource::exists)) {
302+
if (SocketAccess.doPrivilegedException(() -> blobSource.exists(null, null, generateOperationContext(account)))) {
288303
CloudBlockBlob blobTarget = blobContainer.getBlockBlobReference(targetBlob);
289304
SocketAccess.doPrivilegedVoidException(() -> {
290-
blobTarget.startCopy(blobSource);
291-
blobSource.delete();
305+
blobTarget.startCopy(blobSource, null, null, null, generateOperationContext(account));
306+
blobSource.delete(DeleteSnapshotsOption.NONE, null, null, generateOperationContext(account));
292307
});
293308
logger.debug("moveBlob container [{}], sourceBlob [{}], targetBlob [{}] -> done", container, sourceBlob, targetBlob);
294309
}

plugins/repository-azure/src/main/java/org/elasticsearch/cloud/azure/storage/AzureStorageSettings.java

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,55 +20,90 @@
2020
package org.elasticsearch.cloud.azure.storage;
2121

2222
import com.microsoft.azure.storage.RetryPolicy;
23+
import org.elasticsearch.common.Strings;
2324
import org.elasticsearch.common.settings.SecureSetting;
2425
import org.elasticsearch.common.settings.SecureString;
2526
import org.elasticsearch.common.settings.Setting;
2627
import org.elasticsearch.common.settings.Setting.AffixSetting;
2728
import org.elasticsearch.common.settings.Setting.Property;
2829
import org.elasticsearch.common.settings.Settings;
30+
import org.elasticsearch.common.settings.SettingsException;
2931
import org.elasticsearch.common.unit.TimeValue;
3032

33+
import java.net.InetAddress;
34+
import java.net.InetSocketAddress;
35+
import java.net.Proxy;
36+
import java.net.UnknownHostException;
3137
import java.util.Collections;
3238
import java.util.HashMap;
39+
import java.util.Locale;
3340
import java.util.Map;
3441
import java.util.Set;
3542

3643
public final class AzureStorageSettings {
3744
// prefix for azure client settings
3845
private static final String PREFIX = "azure.client.";
3946

40-
/**
41-
* Azure account name
42-
*/
43-
public static final AffixSetting<SecureString> ACCOUNT_SETTING = Setting.affixKeySetting(PREFIX, "account",
44-
key -> SecureSetting.secureString(key, null));
47+
/** Azure account name */
48+
public static final AffixSetting<SecureString> ACCOUNT_SETTING =
49+
Setting.affixKeySetting(PREFIX, "account", key -> SecureSetting.secureString(key, null));
4550

46-
/**
47-
* max_retries: Number of retries in case of Azure errors. Defaults to 3 (RetryPolicy.DEFAULT_CLIENT_RETRY_COUNT).
48-
*/
51+
/** max_retries: Number of retries in case of Azure errors. Defaults to 3 (RetryPolicy.DEFAULT_CLIENT_RETRY_COUNT). */
4952
private static final Setting<Integer> MAX_RETRIES_SETTING =
5053
Setting.affixKeySetting(PREFIX, "max_retries",
5154
(key) -> Setting.intSetting(key, RetryPolicy.DEFAULT_CLIENT_RETRY_COUNT, Setting.Property.NodeScope));
5255

53-
/**
54-
* Azure key
55-
*/
56+
/** Azure key */
5657
public static final AffixSetting<SecureString> KEY_SETTING = Setting.affixKeySetting(PREFIX, "key",
5758
key -> SecureSetting.secureString(key, null));
5859

5960
public static final AffixSetting<TimeValue> TIMEOUT_SETTING = Setting.affixKeySetting(PREFIX, "timeout",
6061
(key) -> Setting.timeSetting(key, TimeValue.timeValueMinutes(-1), Property.NodeScope));
6162

63+
/** The type of the proxy to connect to azure through. Can be direct (no proxy, default), http or socks */
64+
public static final AffixSetting<Proxy.Type> PROXY_TYPE_SETTING = Setting.affixKeySetting(PREFIX, "proxy.type",
65+
(key) -> new Setting<>(key, "direct", s -> Proxy.Type.valueOf(s.toUpperCase(Locale.ROOT)), Property.NodeScope));
66+
67+
/** The host name of a proxy to connect to azure through. */
68+
public static final Setting<String> PROXY_HOST_SETTING = Setting.affixKeySetting(PREFIX, "proxy.host",
69+
(key) -> Setting.simpleString(key, Property.NodeScope));
70+
71+
/** The port of a proxy to connect to azure through. */
72+
public static final Setting<Integer> PROXY_PORT_SETTING = Setting.affixKeySetting(PREFIX, "proxy.port",
73+
(key) -> Setting.intSetting(key, 0, 0, 65535, Setting.Property.NodeScope));
74+
6275
private final String account;
6376
private final String key;
6477
private final TimeValue timeout;
6578
private final int maxRetries;
79+
private final Proxy proxy;
80+
6681

67-
public AzureStorageSettings(String account, String key, TimeValue timeout, int maxRetries) {
82+
public AzureStorageSettings(String account, String key, TimeValue timeout, int maxRetries, Proxy.Type proxyType, String proxyHost,
83+
Integer proxyPort) {
6884
this.account = account;
6985
this.key = key;
7086
this.timeout = timeout;
7187
this.maxRetries = maxRetries;
88+
89+
// Register the proxy if we have any
90+
// Validate proxy settings
91+
if (proxyType.equals(Proxy.Type.DIRECT) && (proxyPort != 0 || Strings.hasText(proxyHost))) {
92+
throw new SettingsException("Azure Proxy port or host have been set but proxy type is not defined.");
93+
}
94+
if (proxyType.equals(Proxy.Type.DIRECT) == false && (proxyPort == 0 || Strings.isEmpty(proxyHost))) {
95+
throw new SettingsException("Azure Proxy type has been set but proxy host or port is not defined.");
96+
}
97+
98+
if (proxyType.equals(Proxy.Type.DIRECT)) {
99+
proxy = null;
100+
} else {
101+
try {
102+
proxy = new Proxy(proxyType, new InetSocketAddress(InetAddress.getByName(proxyHost), proxyPort));
103+
} catch (UnknownHostException e) {
104+
throw new SettingsException("Azure proxy host is unknown.", e);
105+
}
106+
}
72107
}
73108

74109
public String getKey() {
@@ -87,13 +122,18 @@ public int getMaxRetries() {
87122
return maxRetries;
88123
}
89124

125+
public Proxy getProxy() {
126+
return proxy;
127+
}
128+
90129
@Override
91130
public String toString() {
92131
final StringBuilder sb = new StringBuilder("AzureStorageSettings{");
93132
sb.append(", account='").append(account).append('\'');
94133
sb.append(", key='").append(key).append('\'');
95134
sb.append(", timeout=").append(timeout);
96135
sb.append(", maxRetries=").append(maxRetries);
136+
sb.append(", proxy=").append(proxy);
97137
sb.append('}');
98138
return sb.toString();
99139
}
@@ -127,7 +167,10 @@ static AzureStorageSettings getClientSettings(Settings settings, String clientNa
127167
SecureString key = getConfigValue(settings, clientName, KEY_SETTING)) {
128168
return new AzureStorageSettings(account.toString(), key.toString(),
129169
getValue(settings, clientName, TIMEOUT_SETTING),
130-
getValue(settings, clientName, MAX_RETRIES_SETTING));
170+
getValue(settings, clientName, MAX_RETRIES_SETTING),
171+
getValue(settings, clientName, PROXY_TYPE_SETTING),
172+
getValue(settings, clientName, PROXY_HOST_SETTING),
173+
getValue(settings, clientName, PROXY_PORT_SETTING));
131174
}
132175
}
133176

plugins/repository-azure/src/main/java/org/elasticsearch/plugin/repository/azure/AzureRepositoryPlugin.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ public List<Setting<?>> getSettings() {
6464
return Arrays.asList(
6565
AzureStorageSettings.ACCOUNT_SETTING,
6666
AzureStorageSettings.KEY_SETTING,
67-
AzureStorageSettings.TIMEOUT_SETTING
67+
AzureStorageSettings.TIMEOUT_SETTING,
68+
AzureStorageSettings.PROXY_TYPE_SETTING,
69+
AzureStorageSettings.PROXY_HOST_SETTING,
70+
AzureStorageSettings.PROXY_PORT_SETTING
6871
);
6972
}
7073
}

0 commit comments

Comments
 (0)