Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/src/main/sphinx/object-storage/metastores.md
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,9 @@ following properties:
- The credential to exchange for a token in the OAuth2 client credentials flow
with the server. A `token` or `credential` is required for `OAUTH2`
security. Example: `AbCdEf123456`
* - `iceberg.rest-catalog.oauth2.scope`
- Scope to be used when communicating with the REST Catalog. Applicable only
when using `credential`.
:::

The following example shows a minimal catalog configuration using an Iceberg
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
public class OAuth2SecurityConfig
{
private String credential;
private String scope;
private String token;

public Optional<String> getCredential()
Expand All @@ -39,6 +40,19 @@ public OAuth2SecurityConfig setCredential(String credential)
return this;
}

public Optional<String> getScope()
{
return Optional.ofNullable(scope);
}

@Config("iceberg.rest-catalog.oauth2.scope")
@ConfigDescription("The scope which will be used for interactions with the server")
public OAuth2SecurityConfig setScope(String scope)
{
this.scope = scope;
return this;
}

public Optional<String> getToken()
{
return Optional.ofNullable(token);
Expand All @@ -58,4 +72,10 @@ public boolean credentialOrTokenPresent()
{
return credential != null || token != null;
}

@AssertTrue(message = "Scope is applicable only when using credential")
public boolean scopePresentOnlyWithCredential()
{
return !(token != null && scope != null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ public OAuth2SecurityProperties(OAuth2SecurityConfig securityConfig)

ImmutableMap.Builder<String, String> propertiesBuilder = ImmutableMap.builder();
securityConfig.getCredential().ifPresent(
value -> propertiesBuilder.put(OAuth2Properties.CREDENTIAL, value));
credential -> {
propertiesBuilder.put(OAuth2Properties.CREDENTIAL, credential);
securityConfig.getScope()
.ifPresent(scope -> propertiesBuilder.put(OAuth2Properties.SCOPE, scope));
});
securityConfig.getToken().ifPresent(
value -> propertiesBuilder.put(OAuth2Properties.TOKEN, value));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,8 @@ public static void main(String[] args)
.addIcebergProperty("iceberg.rest-catalog.uri", polarisCatalog.restUri() + "/api/catalog")
.addIcebergProperty("iceberg.rest-catalog.warehouse", TestingPolarisCatalog.WAREHOUSE)
.addIcebergProperty("iceberg.rest-catalog.security", "OAUTH2")
.addIcebergProperty("iceberg.rest-catalog.oauth2.token", polarisCatalog.oauth2Token())
.addIcebergProperty("iceberg.rest-catalog.oauth2.credential", polarisCatalog.oauth2Credentials())
.addIcebergProperty("iceberg.rest-catalog.oauth2.scope", "PRINCIPAL_ROLE:ALL")
.setInitialTables(TpchTable.getTables())
.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ protected QueryRunner createQueryRunner()
.addIcebergProperty("iceberg.rest-catalog.uri", polarisCatalog.restUri() + "/api/catalog")
.addIcebergProperty("iceberg.rest-catalog.warehouse", TestingPolarisCatalog.WAREHOUSE)
.addIcebergProperty("iceberg.rest-catalog.security", "OAUTH2")
.addIcebergProperty("iceberg.rest-catalog.oauth2.token", polarisCatalog.oauth2Token())
.addIcebergProperty("iceberg.rest-catalog.oauth2.credential", polarisCatalog.oauth2Credentials())
.addIcebergProperty("iceberg.rest-catalog.oauth2.scope", "PRINCIPAL_ROLE:ALL")
.setInitialTables(REQUIRED_TPCH_TABLES)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ public void testDefaults()
{
assertRecordedDefaults(recordDefaults(OAuth2SecurityConfig.class)
.setCredential(null)
.setToken(null));
.setToken(null)
.setScope(null));
}

@Test
Expand All @@ -39,12 +40,15 @@ public void testExplicitPropertyMappings()
Map<String, String> properties = ImmutableMap.<String, String>builder()
.put("iceberg.rest-catalog.oauth2.token", "token")
.put("iceberg.rest-catalog.oauth2.credential", "credential")
.put("iceberg.rest-catalog.oauth2.scope", "scope")
.buildOrThrow();

OAuth2SecurityConfig expected = new OAuth2SecurityConfig()
.setCredential("credential")
.setToken("token");
.setToken("token")
.setScope("scope");
assertThat(expected.credentialOrTokenPresent()).isTrue();
assertThat(expected.scopePresentOnlyWithCredential()).isFalse();
assertFullMapping(properties, expected);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,12 @@ public final class TestingPolarisCatalog
{
public static final String WAREHOUSE = "polaris";
private static final int POLARIS_PORT = 8181;
private static final Pattern PATTERN = Pattern.compile("realm: default-realm root principal credentials: [a-f0-9]+:(?<secret>[a-f0-9]+)");
private static final Pattern PATTERN = Pattern.compile("realm: default-realm root principal credentials: (?<id>[a-f0-9]+):(?<secret>[a-f0-9]+)");

private static final HttpClient HTTP_CLIENT = new JettyHttpClient();

private final GenericContainer<?> polarisCatalog;
private final String clientId;
private final String clientSecret;
private final String warehouseLocation;

Expand All @@ -58,11 +59,21 @@ public TestingPolarisCatalog(String warehouseLocation)
polarisCatalog.waitingFor(new LogMessageWaitStrategy().withRegEx(".*o.eclipse.jetty.server.Server: Started.*"));
polarisCatalog.start();

clientId = findClientId();
clientSecret = findClientSecret();
createCatalog();
grantPrivilege();
}

private String findClientId()
{
return Stream.of(polarisCatalog.getLogs().split("\n"))
.map(PATTERN::matcher)
.filter(Matcher::find)
.map(matcher -> matcher.group("id"))
.collect(onlyElement());
}

private String findClientSecret()
{
return Stream.of(polarisCatalog.getLogs().split("\n"))
Expand Down Expand Up @@ -124,6 +135,11 @@ public String oauth2Token()
return "principal:root;password:%s;realm:default-realm;role:ALL".formatted(clientSecret);
}

public String oauth2Credentials()
{
return "%s:%s".formatted(clientId, clientSecret);
}

@Override
public void close()
{
Expand Down