1919package org .apache .polaris .service .it ;
2020
2121import static java .nio .charset .StandardCharsets .UTF_8 ;
22+ import static org .apache .iceberg .CatalogProperties .TABLE_DEFAULT_PREFIX ;
2223import static org .apache .iceberg .aws .AwsClientProperties .REFRESH_CREDENTIALS_ENDPOINT ;
2324import static org .apache .iceberg .aws .s3 .S3FileIOProperties .ACCESS_KEY_ID ;
2425import static org .apache .iceberg .aws .s3 .S3FileIOProperties .ENDPOINT ;
2526import static org .apache .iceberg .aws .s3 .S3FileIOProperties .SECRET_ACCESS_KEY ;
2627import static org .apache .iceberg .types .Types .NestedField .optional ;
2728import static org .apache .iceberg .types .Types .NestedField .required ;
29+ import static org .apache .polaris .core .storage .StorageAccessProperty .AWS_KEY_ID ;
30+ import static org .apache .polaris .core .storage .StorageAccessProperty .AWS_SECRET_KEY ;
2831import static org .apache .polaris .service .catalog .AccessDelegationMode .VENDED_CREDENTIALS ;
2932import static org .apache .polaris .service .it .env .PolarisClient .polarisClient ;
3033import static org .assertj .core .api .Assertions .assertThat ;
34+ import static org .assertj .core .api .Assertions .assertThatThrownBy ;
3135
3236import com .google .common .collect .ImmutableMap ;
3337import io .quarkus .test .junit .QuarkusIntegrationTest ;
@@ -165,20 +169,24 @@ private RESTCatalog createCatalog(
165169 Optional <String > endpoint ,
166170 Optional <String > stsEndpoint ,
167171 boolean pathStyleAccess ,
168- Optional <AccessDelegationMode > delegationMode ) {
169- return createCatalog (endpoint , stsEndpoint , pathStyleAccess , Optional .empty (), delegationMode );
172+ Optional <AccessDelegationMode > delegationMode ,
173+ boolean stsEnabled ) {
174+ return createCatalog (
175+ endpoint , stsEndpoint , pathStyleAccess , Optional .empty (), delegationMode , stsEnabled );
170176 }
171177
172178 private RESTCatalog createCatalog (
173179 Optional <String > endpoint ,
174180 Optional <String > stsEndpoint ,
175181 boolean pathStyleAccess ,
176182 Optional <String > endpointInternal ,
177- Optional <AccessDelegationMode > delegationMode ) {
183+ Optional <AccessDelegationMode > delegationMode ,
184+ boolean stsEnabled ) {
178185 AwsStorageConfigInfo .Builder storageConfig =
179186 AwsStorageConfigInfo .builder ()
180187 .setStorageType (StorageConfigInfo .StorageTypeEnum .S3 )
181188 .setPathStyleAccess (pathStyleAccess )
189+ .setStsUnavailable (!stsEnabled )
182190 .setAllowedLocations (List .of (storageBase .toString ()));
183191
184192 endpoint .ifPresent (storageConfig ::setEndpoint );
@@ -187,6 +195,12 @@ private RESTCatalog createCatalog(
187195
188196 CatalogProperties .Builder catalogProps =
189197 CatalogProperties .builder (storageBase .toASCIIString () + "/" + catalogName );
198+ if (!stsEnabled ) {
199+ catalogProps .addProperty (
200+ TABLE_DEFAULT_PREFIX + AWS_KEY_ID .getPropertyName (), MINIO_ACCESS_KEY );
201+ catalogProps .addProperty (
202+ TABLE_DEFAULT_PREFIX + AWS_SECRET_KEY .getPropertyName (), MINIO_SECRET_KEY );
203+ }
190204 Catalog catalog =
191205 PolarisCatalog .builder ()
192206 .setType (Catalog .TypeEnum .INTERNAL )
@@ -227,9 +241,12 @@ public void cleanUp() {
227241 }
228242
229243 @ ParameterizedTest
230- @ ValueSource (booleans = {true , false })
231- public void testCreateTable (boolean pathStyle ) throws IOException {
232- LoadTableResponse response = doTestCreateTable (pathStyle , Optional .empty ());
244+ @ CsvSource ("true, true," )
245+ @ CsvSource ("false, true," )
246+ @ CsvSource ("true, false," )
247+ @ CsvSource ("false, false," )
248+ public void testCreateTable (boolean pathStyle , boolean stsEnabled ) throws IOException {
249+ LoadTableResponse response = doTestCreateTable (pathStyle , Optional .empty (), stsEnabled );
233250 assertThat (response .config ()).doesNotContainKey (SECRET_ACCESS_KEY );
234251 assertThat (response .config ()).doesNotContainKey (ACCESS_KEY_ID );
235252 assertThat (response .config ()).doesNotContainKey (REFRESH_CREDENTIALS_ENDPOINT );
@@ -239,18 +256,19 @@ public void testCreateTable(boolean pathStyle) throws IOException {
239256 @ ParameterizedTest
240257 @ ValueSource (booleans = {true , false })
241258 public void testCreateTableVendedCredentials (boolean pathStyle ) throws IOException {
242- LoadTableResponse response = doTestCreateTable (pathStyle , Optional .of (VENDED_CREDENTIALS ));
259+ LoadTableResponse response =
260+ doTestCreateTable (pathStyle , Optional .of (VENDED_CREDENTIALS ), true );
243261 assertThat (response .config ())
244262 .containsEntry (
245263 REFRESH_CREDENTIALS_ENDPOINT ,
246264 "v1/" + catalogName + "/namespaces/test-ns/tables/t1/credentials" );
247265 assertThat (response .credentials ()).hasSize (1 );
248266 }
249267
250- private LoadTableResponse doTestCreateTable (boolean pathStyle , Optional < AccessDelegationMode > dm )
251- throws IOException {
268+ private LoadTableResponse doTestCreateTable (
269+ boolean pathStyle , Optional < AccessDelegationMode > dm , boolean stsEnabled ) throws IOException {
252270 try (RESTCatalog restCatalog =
253- createCatalog (Optional .of (endpoint ), Optional .empty (), pathStyle , dm )) {
271+ createCatalog (Optional .of (endpoint ), Optional .empty (), pathStyle , dm , stsEnabled )) {
254272 LoadTableResponse loadTableResponse = doTestCreateTable (restCatalog , dm );
255273 if (pathStyle ) {
256274 assertThat (loadTableResponse .config ())
@@ -268,7 +286,8 @@ public void testInternalEndpoints() throws IOException {
268286 Optional .of (endpoint ),
269287 false ,
270288 Optional .of (endpoint ),
271- Optional .empty ())) {
289+ Optional .empty (),
290+ true )) {
272291 StorageConfigInfo storageConfig =
273292 managementApi .getCatalog (catalogName ).getStorageConfigInfo ();
274293 assertThat ((AwsStorageConfigInfo ) storageConfig )
@@ -283,6 +302,62 @@ public void testInternalEndpoints() throws IOException {
283302 }
284303 }
285304
305+ @ Test
306+ public void testCreateTableFailureWithCredentialVendingWithoutSts () throws IOException {
307+ try (RESTCatalog restCatalog =
308+ createCatalog (
309+ Optional .of (endpoint ),
310+ Optional .of ("http://sts.example.com" ), // not called
311+ false ,
312+ Optional .of (VENDED_CREDENTIALS ),
313+ false )) {
314+ StorageConfigInfo storageConfig =
315+ managementApi .getCatalog (catalogName ).getStorageConfigInfo ();
316+ assertThat ((AwsStorageConfigInfo ) storageConfig )
317+ .extracting (
318+ AwsStorageConfigInfo ::getEndpoint ,
319+ AwsStorageConfigInfo ::getStsEndpoint ,
320+ AwsStorageConfigInfo ::getEndpointInternal ,
321+ AwsStorageConfigInfo ::getPathStyleAccess ,
322+ AwsStorageConfigInfo ::getStsUnavailable )
323+ .containsExactly (endpoint , "http://sts.example.com" , null , false , true );
324+
325+ catalogApi .createNamespace (catalogName , "test-ns" );
326+ TableIdentifier id = TableIdentifier .of ("test-ns" , "t2" );
327+ // Credential vending is not supported without STS
328+ assertThatThrownBy (() -> restCatalog .createTable (id , SCHEMA ))
329+ .hasMessageContaining ("but no credentials are available" )
330+ .hasMessageContaining (id .toString ());
331+ }
332+ }
333+
334+ @ Test
335+ public void testLoadTableFailureWithCredentialVendingWithoutSts () throws IOException {
336+ try (RESTCatalog restCatalog =
337+ createCatalog (
338+ Optional .of (endpoint ),
339+ Optional .of ("http://sts.example.com" ), // not called
340+ false ,
341+ Optional .empty (),
342+ false )) {
343+
344+ catalogApi .createNamespace (catalogName , "test-ns" );
345+ TableIdentifier id = TableIdentifier .of ("test-ns" , "t3" );
346+ restCatalog .createTable (id , SCHEMA );
347+
348+ // Credential vending is not supported without STS
349+ assertThatThrownBy (
350+ () ->
351+ catalogApi .loadTable (
352+ catalogName ,
353+ id ,
354+ "ALL" ,
355+ Map .of ("X-Iceberg-Access-Delegation" , VENDED_CREDENTIALS .protocolValue ())))
356+ .hasMessageContaining ("but no credentials are available" )
357+ .hasMessageContaining (id .toString ());
358+ }
359+ }
360+
286361 public LoadTableResponse doTestCreateTable (
287362 RESTCatalog restCatalog , Optional <AccessDelegationMode > dm ) {
288363 catalogApi .createNamespace (catalogName , "test-ns" );
@@ -319,18 +394,22 @@ public LoadTableResponse doTestCreateTable(
319394 }
320395
321396 @ ParameterizedTest
322- @ CsvSource ("true," )
323- @ CsvSource ("false," )
324- @ CsvSource ("true,VENDED_CREDENTIALS" )
325- @ CsvSource ("false,VENDED_CREDENTIALS" )
326- public void testAppendFiles (boolean pathStyle , AccessDelegationMode delegationMode )
397+ @ CsvSource ("true, true," )
398+ @ CsvSource ("false, true," )
399+ @ CsvSource ("true, false," )
400+ @ CsvSource ("false, false," )
401+ @ CsvSource ("true, true, VENDED_CREDENTIALS" )
402+ @ CsvSource ("false, true, VENDED_CREDENTIALS" )
403+ public void testAppendFiles (
404+ boolean pathStyle , boolean stsEnabled , AccessDelegationMode delegationMode )
327405 throws IOException {
328406 try (RESTCatalog restCatalog =
329407 createCatalog (
330408 Optional .of (endpoint ),
331409 Optional .of (endpoint ),
332410 pathStyle ,
333- Optional .ofNullable (delegationMode ))) {
411+ Optional .ofNullable (delegationMode ),
412+ stsEnabled )) {
334413 catalogApi .createNamespace (catalogName , "test-ns" );
335414 TableIdentifier id = TableIdentifier .of ("test-ns" , "t1" );
336415 Table table = restCatalog .createTable (id , SCHEMA );
@@ -344,7 +423,8 @@ public void testAppendFiles(boolean pathStyle, AccessDelegationMode delegationMo
344423 table
345424 .locationProvider ()
346425 .newDataLocation (
347- String .format ("test-file-%s-%s.txt" , pathStyle , delegationMode )));
426+ String .format (
427+ "test-file-%s-%s-%s.txt" , pathStyle , delegationMode , stsEnabled )));
348428 OutputFile f1 = io .newOutputFile (loc .toString ());
349429 try (PositionOutputStream os = f1 .create ()) {
350430 os .write ("Hello World" .getBytes (UTF_8 ));
0 commit comments