Skip to content

Commit daf4476

Browse files
Support IMPLICIT authentication type for federated catalogs (#1925)
Previously, the ConnectionConfigInfo required explicit AuthenticationParameters for every federated catalog. However, certain catalogs types that Polaris federates to (either now or in the future) allow `IMPLICIT` authentication, wherein the authentication parameters are picked from the environment or configuration files. This change enables federating to such catalogs without passing dummy secrets. The `IMPLICIT` option is guarded by the `SUPPORTED_EXTERNAL_CATALOG_AUTHENTICATION_TYPES`. Hence users may create federated catalogs with `IMPLICIT` authentication only when the administrator explicitly enables this feature.
1 parent d962c64 commit daf4476

File tree

7 files changed

+165
-28
lines changed

7 files changed

+165
-28
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ request adding CHANGELOG notes for breaking (!) changes and possibly other secti
3737

3838
- Added Catalog configuration for S3 and STS endpoints. This also allows using non-AWS S3 implementations.
3939

40+
- The `IMPLICIT` authentication type enables users to create federated catalogs without explicitly
41+
providing authentication parameters to Polaris. When the authentication type is set to `IMPLICIT`,
42+
the authentication parameters are picked from the environment or configuration files.
43+
4044
### Changes
4145

4246
### Deprecations

polaris-core/src/main/java/org/apache/polaris/core/connection/AuthenticationParametersDpo.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
@JsonSubTypes({
4040
@JsonSubTypes.Type(value = OAuthClientCredentialsParametersDpo.class, name = "1"),
4141
@JsonSubTypes.Type(value = BearerAuthenticationParametersDpo.class, name = "2"),
42+
@JsonSubTypes.Type(value = ImplicitAuthenticationParametersDpo.class, name = "3"),
4243
})
4344
public abstract class AuthenticationParametersDpo implements IcebergCatalogPropertiesProvider {
4445

@@ -81,6 +82,9 @@ public static AuthenticationParametersDpo fromAuthenticationParametersModelWithS
8182
new BearerAuthenticationParametersDpo(
8283
secretReferences.get(INLINE_BEARER_TOKEN_REFERENCE_KEY));
8384
break;
85+
case IMPLICIT:
86+
config = new ImplicitAuthenticationParametersDpo();
87+
break;
8488
default:
8589
throw new IllegalStateException(
8690
"Unsupported authentication type: " + authenticationParameters.getAuthenticationType());

polaris-core/src/main/java/org/apache/polaris/core/connection/AuthenticationType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public enum AuthenticationType {
3333
NULL_TYPE(0),
3434
OAUTH(1),
3535
BEARER(2),
36+
IMPLICIT(3),
3637
;
3738

3839
private static final AuthenticationType[] REVERSE_MAPPING_ARRAY;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.core.connection;
20+
21+
import com.google.common.base.MoreObjects;
22+
import java.util.Map;
23+
import org.apache.polaris.core.admin.model.AuthenticationParameters;
24+
import org.apache.polaris.core.admin.model.ImplicitAuthenticationParameters;
25+
import org.apache.polaris.core.secrets.UserSecretsManager;
26+
27+
/**
28+
* The internal persistence-object counterpart to ImplicitAuthenticationParameters defined in the
29+
* API model.
30+
*/
31+
public class ImplicitAuthenticationParametersDpo extends AuthenticationParametersDpo {
32+
33+
public ImplicitAuthenticationParametersDpo() {
34+
super(AuthenticationType.IMPLICIT.getCode());
35+
}
36+
37+
@Override
38+
public Map<String, String> asIcebergCatalogProperties(UserSecretsManager secretsManager) {
39+
return Map.of();
40+
}
41+
42+
@Override
43+
public AuthenticationParameters asAuthenticationParametersModel() {
44+
return ImplicitAuthenticationParameters.builder()
45+
.setAuthenticationType(AuthenticationParameters.AuthenticationTypeEnum.IMPLICIT)
46+
.build();
47+
}
48+
49+
@Override
50+
public String toString() {
51+
return MoreObjects.toStringHelper(this)
52+
.add("authenticationTypeCode", getAuthenticationTypeCode())
53+
.toString();
54+
}
55+
}

polaris-core/src/test/java/org/apache/polaris/core/connection/ConnectionConfigInfoDpoTest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,42 @@ void testBearerAuthenticationParameters() throws JsonProcessingException {
131131
objectMapper.readValue(expectedApiModelJson, ConnectionConfigInfo.class),
132132
connectionConfigInfoApiModel);
133133
}
134+
135+
@Test
136+
void testImplicitAuthenticationParameters() throws JsonProcessingException {
137+
// Test deserialization and reserialization of the persistence JSON.
138+
String json =
139+
""
140+
+ "{"
141+
+ " \"connectionTypeCode\": 2,"
142+
+ " \"uri\": \"file:///hadoop-catalog/warehouse\","
143+
+ " \"warehouse\": \"hadoop-catalog\","
144+
+ " \"authenticationParameters\": {"
145+
+ " \"authenticationTypeCode\": 3"
146+
+ " }"
147+
+ "}";
148+
ConnectionConfigInfoDpo connectionConfigInfoDpo =
149+
ConnectionConfigInfoDpo.deserialize(polarisDiagnostics, json);
150+
Assertions.assertNotNull(connectionConfigInfoDpo);
151+
JsonNode tree1 = objectMapper.readTree(json);
152+
JsonNode tree2 = objectMapper.readTree(connectionConfigInfoDpo.serialize());
153+
Assertions.assertEquals(tree1, tree2);
154+
155+
// Test conversion into API model JSON.
156+
ConnectionConfigInfo connectionConfigInfoApiModel =
157+
connectionConfigInfoDpo.asConnectionConfigInfoModel();
158+
String expectedApiModelJson =
159+
""
160+
+ "{"
161+
+ " \"connectionType\": \"HADOOP\","
162+
+ " \"uri\": \"file:///hadoop-catalog/warehouse\","
163+
+ " \"warehouse\": \"hadoop-catalog\","
164+
+ " \"authenticationParameters\": {"
165+
+ " \"authenticationType\": \"IMPLICIT\""
166+
+ " }"
167+
+ "}";
168+
Assertions.assertEquals(
169+
objectMapper.readValue(expectedApiModelJson, ConnectionConfigInfo.class),
170+
connectionConfigInfoApiModel);
171+
}
134172
}

service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package org.apache.polaris.service.admin;
2020

2121
import static com.google.common.base.Preconditions.checkArgument;
22+
import static com.google.common.base.Preconditions.checkState;
2223

2324
import jakarta.annotation.Nonnull;
2425
import jakarta.annotation.Nullable;
@@ -29,6 +30,7 @@
2930
import java.util.HashMap;
3031
import java.util.HashSet;
3132
import java.util.List;
33+
import java.util.Locale;
3234
import java.util.Map;
3335
import java.util.Objects;
3436
import java.util.Optional;
@@ -699,16 +701,10 @@ private Map<String, UserSecretReference> extractSecretReferences(
699701
/**
700702
* @see #extractSecretReferences
701703
*/
702-
private boolean requiresSecretReferenceExtraction(CreateCatalogRequest catalogRequest) {
703-
Catalog catalog = catalogRequest.getCatalog();
704-
if (catalog instanceof ExternalCatalog externalCatalog) {
705-
if (externalCatalog.getConnectionConfigInfo() != null) {
706-
// TODO: Make this more targeted once we have connection configs that don't involve
707-
// processing of inline secrets.
708-
return true;
709-
}
710-
}
711-
return false;
704+
private boolean requiresSecretReferenceExtraction(
705+
@NotNull ConnectionConfigInfo connectionConfigInfo) {
706+
return connectionConfigInfo.getAuthenticationParameters().getAuthenticationType()
707+
!= AuthenticationParameters.AuthenticationTypeEnum.IMPLICIT;
712708
}
713709

714710
public PolarisEntity createCatalog(CreateCatalogRequest catalogRequest) {
@@ -733,24 +729,54 @@ public PolarisEntity createCatalog(CreateCatalogRequest catalogRequest) {
733729
.setProperties(reservedProperties.removeReservedProperties(entity.getPropertiesAsMap()))
734730
.build();
735731

736-
if (requiresSecretReferenceExtraction(catalogRequest)) {
737-
LOGGER
738-
.atDebug()
739-
.addKeyValue("catalogName", entity.getName())
740-
.log("Extracting secret references to create federated catalog");
741-
FeatureConfiguration.enforceFeatureEnabledOrThrow(
742-
callContext, FeatureConfiguration.ENABLE_CATALOG_FEDERATION);
743-
// For fields that contain references to secrets, we'll separately process the secrets from
744-
// the original request first, and then populate those fields with the extracted secret
745-
// references as part of the construction of the internal persistence entity.
746-
Map<String, UserSecretReference> processedSecretReferences =
747-
extractSecretReferences(catalogRequest, entity);
748-
entity =
749-
new CatalogEntity.Builder(entity)
750-
.setConnectionConfigInfoDpoWithSecrets(
751-
((ExternalCatalog) catalogRequest.getCatalog()).getConnectionConfigInfo(),
752-
processedSecretReferences)
753-
.build();
732+
Catalog catalog = catalogRequest.getCatalog();
733+
if (catalog instanceof ExternalCatalog externalCatalog) {
734+
ConnectionConfigInfo connectionConfigInfo = externalCatalog.getConnectionConfigInfo();
735+
736+
if (connectionConfigInfo != null) {
737+
LOGGER
738+
.atDebug()
739+
.addKeyValue("catalogName", entity.getName())
740+
.log("Creating a federated catalog");
741+
FeatureConfiguration.enforceFeatureEnabledOrThrow(
742+
callContext, FeatureConfiguration.ENABLE_CATALOG_FEDERATION);
743+
Map<String, UserSecretReference> processedSecretReferences = Map.of();
744+
List<String> supportedAuthenticationTypes =
745+
callContext
746+
.getPolarisCallContext()
747+
.getConfigurationStore()
748+
.getConfiguration(
749+
callContext.getRealmContext(),
750+
FeatureConfiguration.SUPPORTED_EXTERNAL_CATALOG_AUTHENTICATION_TYPES)
751+
.stream()
752+
.map(s -> s.toUpperCase(Locale.ROOT))
753+
.toList();
754+
if (requiresSecretReferenceExtraction(connectionConfigInfo)) {
755+
// For fields that contain references to secrets, we'll separately process the secrets
756+
// from the original request first, and then populate those fields with the extracted
757+
// secret references as part of the construction of the internal persistence entity.
758+
checkState(
759+
supportedAuthenticationTypes.contains(
760+
connectionConfigInfo
761+
.getAuthenticationParameters()
762+
.getAuthenticationType()
763+
.name()),
764+
"Authentication type %s is not supported.",
765+
connectionConfigInfo.getAuthenticationParameters().getAuthenticationType());
766+
processedSecretReferences = extractSecretReferences(catalogRequest, entity);
767+
} else {
768+
// Support no-auth catalog federation only when the feature is enabled.
769+
checkState(
770+
supportedAuthenticationTypes.contains(
771+
AuthenticationParameters.AuthenticationTypeEnum.IMPLICIT.name()),
772+
"Implicit authentication based catalog federation is not supported.");
773+
}
774+
entity =
775+
new CatalogEntity.Builder(entity)
776+
.setConnectionConfigInfoDpoWithSecrets(
777+
connectionConfigInfo, processedSecretReferences)
778+
.build();
779+
}
754780
}
755781

756782
CreateCatalogResult catalogResult =

spec/polaris-management-service.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,7 @@ components:
917917
- OAUTH
918918
- BEARER
919919
- SIGV4
920+
- IMPLICIT
920921
description: The type of authentication to use when connecting to the remote rest service
921922
required:
922923
- authenticationType
@@ -926,6 +927,7 @@ components:
926927
OAUTH: "#/components/schemas/OAuthClientCredentialsParameters"
927928
BEARER: "#/components/schemas/BearerAuthenticationParameters"
928929
SIGV4: "#/components/schemas/SigV4AuthenticationParameters"
930+
IMPLICIT: "#/components/schemas/ImplicitAuthenticationParameters"
929931

930932
OAuthClientCredentialsParameters:
931933
type: object
@@ -990,6 +992,13 @@ components:
990992
- roleArn
991993
- signingRegion
992994

995+
ImplicitAuthenticationParameters:
996+
type: object
997+
description: Polaris does not explicity accept any authentication parameters for the connection. Authentication
998+
parameters found in the environment and/or configuration files will be used for this connection.
999+
allOf:
1000+
- $ref: '#/components/schemas/AuthenticationParameters'
1001+
9931002
StorageConfigInfo:
9941003
type: object
9951004
description: A storage configuration used by catalogs

0 commit comments

Comments
 (0)