Skip to content

Commit a8e2d65

Browse files
committed
Add SAS Token support for authentification for azure repositories
This adds sas-token support for azure repositories based on elastic/elasticsearch#42982.
1 parent 815ba73 commit a8e2d65

File tree

9 files changed

+96
-53
lines changed

9 files changed

+96
-53
lines changed

docs/appendices/release-notes/5.9.0.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,5 +86,9 @@ None
8686
Administration and Operations
8787
-----------------------------
8888

89+
- Added support for :ref:`Shared Access Signatures (SAS) tokens <sql-create-repo-azure-sas-token>`
90+
as an alternative for authentication for :ref:`Azure repositories <sql-create-repo-azure>`.
91+
8992
- Added ``id`` column to the :ref:`sys.snapshots <sys-snapshots>` table which
9093
exposes snapshot UUID.
94+

docs/sql/statements/create-repository.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,25 @@ Parameters
434434
CrateDB masks this parameter. You cannot query the parameter value from
435435
the :ref:`sys.repositories <sys-repositories>` table.
436436

437+
.. _sql-create-repo-azure-sas-token:
438+
439+
**sas_token**
440+
| *Type:* ``text``
441+
442+
The Shared Access Signatures (SAS) token used for authentication for the Azure
443+
Storage account. This can be used as an alternative to the Azure Storage
444+
account secret key.
445+
446+
The SAS token must have read, write, list, and delete permissions for the
447+
repository base path and all its contents. These permissions need to be
448+
granted for the blob service and apply to resource types service, container,
449+
and object.
450+
451+
.. NOTE::
452+
453+
CrateDB masks this parameter. You cannot query the parameter value from
454+
the :ref:`sys.repositories <sys-repositories>` table.
455+
437456
.. _sql-create-repo-azure-endpoint:
438457

439458
**endpoint**

plugins/es-repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureBlobStore.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,8 @@ public AzureBlobStore(RepositoryMetadata metadata) {
5656
AzureBlobStore(RepositoryMetadata metadata, AzureStorageService service) {
5757
this.container = Repository.CONTAINER_SETTING.get(metadata.settings());
5858
this.locationMode = Repository.LOCATION_MODE_SETTING.get(metadata.settings());
59-
60-
AzureStorageSettings repositorySettings = AzureStorageSettings
61-
.getClientSettings(metadata.settings());
62-
service.refreshSettings(repositorySettings);
63-
6459
this.service = service;
60+
this.service.refreshSettings(service.storageSettings);
6561
}
6662

6763
@Override

plugins/es-repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureRepository.java

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ public class AzureRepository extends BlobStoreRepository {
6767
public static final String TYPE = "azure";
6868

6969
public static final class Repository {
70+
71+
static final Setting<SecureString> SAS_TOKEN_SETTING = Setting.maskedString("sas_token");
72+
7073
static final Setting<SecureString> ACCOUNT_SETTING = Setting.maskedString("account");
7174

7275
static final Setting<SecureString> KEY_SETTING = Setting.maskedString("key");
@@ -147,24 +150,26 @@ public static final class Repository {
147150

148151
public static List<Setting<?>> optionalSettings() {
149152
return List.of(Repository.CONTAINER_SETTING,
150-
Repository.BASE_PATH_SETTING,
151-
Repository.CHUNK_SIZE_SETTING,
152-
Repository.READONLY_SETTING,
153-
Repository.LOCATION_MODE_SETTING,
154-
COMPRESS_SETTING,
155-
// client specific repository settings
156-
Repository.MAX_RETRIES_SETTING,
157-
Repository.ENDPOINT_SETTING,
158-
Repository.SECONDARY_ENDPOINT_SETTING,
159-
Repository.ENDPOINT_SUFFIX_SETTING,
160-
Repository.TIMEOUT_SETTING,
161-
Repository.PROXY_TYPE_SETTING,
162-
Repository.PROXY_HOST_SETTING,
163-
Repository.PROXY_PORT_SETTING);
153+
Repository.BASE_PATH_SETTING,
154+
Repository.CHUNK_SIZE_SETTING,
155+
Repository.READONLY_SETTING,
156+
Repository.LOCATION_MODE_SETTING,
157+
COMPRESS_SETTING,
158+
// client specific repository settings
159+
Repository.MAX_RETRIES_SETTING,
160+
Repository.ENDPOINT_SETTING,
161+
Repository.SECONDARY_ENDPOINT_SETTING,
162+
Repository.ENDPOINT_SUFFIX_SETTING,
163+
Repository.TIMEOUT_SETTING,
164+
Repository.PROXY_TYPE_SETTING,
165+
Repository.PROXY_HOST_SETTING,
166+
Repository.PROXY_PORT_SETTING,
167+
Repository.KEY_SETTING,
168+
Repository.SAS_TOKEN_SETTING);
164169
}
165170

166171
public static List<Setting<?>> mandatorySettings() {
167-
return List.of(Repository.ACCOUNT_SETTING, Repository.KEY_SETTING);
172+
return List.of(Repository.ACCOUNT_SETTING);
168173
}
169174

170175
private final ByteSizeValue chunkSize;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,6 @@ public Repository create(RepositoryMetadata metadata) throws Exception {
7070

7171
@Override
7272
public List<Setting<?>> getSettings() {
73-
return List.of(AzureRepository.Repository.ACCOUNT_SETTING, AzureRepository.Repository.KEY_SETTING);
73+
return List.of(AzureRepository.Repository.ACCOUNT_SETTING);
7474
}
7575
}

plugins/es-repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureStorageService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ protected CloudBlobClient buildClient(AzureStorageSettings azureStorageSettings)
121121
}
122122

123123
protected CloudBlobClient createClient(AzureStorageSettings azureStorageSettings) throws InvalidKeyException, URISyntaxException {
124-
final String connectionString = azureStorageSettings.buildConnectionString();
124+
final String connectionString = azureStorageSettings.getConnectString();
125125
return CloudStorageAccount.parse(connectionString).createCloudBlobClient();
126126
}
127127

plugins/es-repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureStorageSettings.java

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,14 @@
3232

3333
import com.microsoft.azure.storage.LocationMode;
3434

35+
import org.jetbrains.annotations.Nullable;
3536
import org.jetbrains.annotations.VisibleForTesting;
3637
import io.crate.common.unit.TimeValue;
3738

3839
public final class AzureStorageSettings {
3940

4041
private final String account;
41-
private final String key;
42+
private final String connectString;
4243
private final String endpoint;
4344
private final String secondaryEndpoint;
4445
private final String endpointSuffix;
@@ -49,7 +50,7 @@ public final class AzureStorageSettings {
4950

5051
@VisibleForTesting
5152
AzureStorageSettings(String account,
52-
String key,
53+
String connectString,
5354
String endpoint,
5455
String secondaryEndpoint,
5556
String endpointSuffix,
@@ -58,7 +59,7 @@ public final class AzureStorageSettings {
5859
Proxy proxy,
5960
LocationMode locationMode) {
6061
this.account = account;
61-
this.key = key;
62+
this.connectString = connectString;
6263
this.endpoint = endpoint;
6364
this.secondaryEndpoint = secondaryEndpoint;
6465
this.endpointSuffix = endpointSuffix;
@@ -69,16 +70,17 @@ public final class AzureStorageSettings {
6970
}
7071

7172
private AzureStorageSettings(String account,
72-
String key,
73-
LocationMode locationMode,
74-
String endpoint,
75-
String secondaryEndpoint,
76-
String endpointSuffix,
77-
TimeValue timeout,
78-
int maxRetries,
79-
Proxy.Type proxyType,
80-
String proxyHost,
81-
Integer proxyPort) {
73+
String key,
74+
String sasToken,
75+
LocationMode locationMode,
76+
String endpoint,
77+
String secondaryEndpoint,
78+
String endpointSuffix,
79+
TimeValue timeout,
80+
int maxRetries,
81+
Proxy.Type proxyType,
82+
String proxyHost,
83+
Integer proxyPort) {
8284

8385
final boolean hasEndpointSuffix = Strings.hasText(endpointSuffix);
8486
final boolean hasEndpoint = Strings.hasText(endpoint);
@@ -92,7 +94,7 @@ private AzureStorageSettings(String account,
9294
}
9395

9496
this.account = account;
95-
this.key = key;
97+
this.connectString = buildConnectString(account, key, sasToken, endpoint, endpointSuffix, secondaryEndpoint);
9698
this.endpoint = endpoint;
9799
this.secondaryEndpoint = secondaryEndpoint;
98100
this.endpointSuffix = endpointSuffix;
@@ -131,18 +133,32 @@ public Proxy getProxy() {
131133
return proxy;
132134
}
133135

136+
public String getConnectString() {
137+
return connectString;
138+
}
139+
134140
public LocationMode getLocationMode() {
135141
return locationMode;
136142
}
137143

138-
public String buildConnectionString() {
144+
private String buildConnectString(String account, @Nullable String key, @Nullable String sasToken, String endpoint, String endpointSuffix, String secondaryEndpoint) {
145+
boolean hasSasToken = Strings.hasText(sasToken);
146+
boolean hasKey = Strings.hasText(key);
147+
if (hasSasToken == false && hasKey == false) {
148+
throw new SettingsException("Neither a secret key nor a shared access token was set.");
149+
}
150+
if (hasSasToken && hasKey) {
151+
throw new SettingsException("Both a secret as well as a shared access token were set.");
152+
}
139153
final StringBuilder connectionStringBuilder = new StringBuilder();
140154
connectionStringBuilder.append("DefaultEndpointsProtocol=https")
141-
.append(";AccountName=")
142-
.append(account)
143-
.append(";AccountKey=")
144-
.append(key);
145-
155+
.append(";AccountName=")
156+
.append(account);
157+
if (hasKey) {
158+
connectionStringBuilder.append(";AccountKey=").append(key);
159+
} else {
160+
connectionStringBuilder.append(";SharedAccessSignature=").append(sasToken);
161+
}
146162
if (Strings.hasText(endpointSuffix)) {
147163
connectionStringBuilder.append(";EndpointSuffix=").append(endpointSuffix);
148164
}
@@ -157,10 +173,12 @@ public String buildConnectionString() {
157173

158174
static AzureStorageSettings getClientSettings(Settings settings) {
159175
try (SecureString account = getConfigValue(settings, AzureRepository.Repository.ACCOUNT_SETTING);
160-
SecureString key = getConfigValue(settings, AzureRepository.Repository.KEY_SETTING)) {
176+
SecureString key = getConfigValue(settings,AzureRepository.Repository.KEY_SETTING);
177+
SecureString sasToken = getConfigValue(settings, AzureRepository.Repository.SAS_TOKEN_SETTING)) {
161178
return new AzureStorageSettings(
162179
account.toString(),
163180
key.toString(),
181+
sasToken.toString(),
164182
getConfigValue(settings, AzureRepository.Repository.LOCATION_MODE_SETTING),
165183
getConfigValue(settings, AzureRepository.Repository.ENDPOINT_SETTING),
166184
getConfigValue(settings, AzureRepository.Repository.SECONDARY_ENDPOINT_SETTING),
@@ -180,7 +198,7 @@ private static <T> T getConfigValue(Settings settings, Setting<T> clientSetting)
180198
static AzureStorageSettings copy(AzureStorageSettings settings) {
181199
return new AzureStorageSettings(
182200
settings.account,
183-
settings.key,
201+
settings.connectString,
184202
settings.endpoint,
185203
settings.secondaryEndpoint,
186204
settings.endpointSuffix,
@@ -193,7 +211,6 @@ static AzureStorageSettings copy(AzureStorageSettings settings) {
193211
@Override
194212
public String toString() {
195213
return "AzureStorageSettings{" + "account='" + account + '\'' +
196-
", key='" + key + '\'' +
197214
", timeout=" + timeout +
198215
", endpoint='" + endpoint + '\'' +
199216
", secondaryEndpoint='" + secondaryEndpoint + '\'' +

plugins/es-repository-azure/src/test/java/org/elasticsearch/repositories/azure/AzureRepositoryAnalyzerTest.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,6 @@ private <S> S analyze(SQLExecutor e, String stmt) {
7474
}
7575
}
7676

77-
@Test
78-
public void testCreateAzureRepoWithMissingMandatorySettings() {
79-
assertThatThrownBy(
80-
() -> analyze(e, "CREATE REPOSITORY foo TYPE azure WITH (account='test')"))
81-
.isExactlyInstanceOf(IllegalArgumentException.class)
82-
.hasMessage("The following required parameters are missing to create a repository of type \"azure\": [key]");
83-
}
84-
8577
@Test
8678
public void testCreateAzureRepositoryWithAllSettings() {
8779
PutRepositoryRequest request = analyze(

plugins/es-repository-azure/src/test/java/org/elasticsearch/repositories/azure/AzureSnapshotIntegrationTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,16 @@ public void create_azure_snapshot_and_restore_with_secondary_endpoint() {
150150
handler.blobs().keySet().forEach(x -> assertThat(x).doesNotEndWith("dat"));
151151
}
152152

153+
@Test
154+
public void testCreateAzureRepoWithMissingMandatorySettings() {
155+
Asserts.assertSQLError(() ->
156+
execute("CREATE REPOSITORY r1 TYPE AZURE WITH (account = 'devstoreaccount1')"))
157+
.hasPGError(INTERNAL_ERROR)
158+
.hasHTTPError(INTERNAL_SERVER_ERROR, 5000)
159+
.hasMessageContaining("[r1] Unable to verify the repository, [r1] is not accessible on master node: " +
160+
"SettingsException 'Neither a secret key nor a shared access token was set.'");
161+
}
162+
153163
@Test
154164
public void test_invalid_settings_to_create_azure_repository() {
155165
Asserts.assertSQLError(() -> execute(

0 commit comments

Comments
 (0)