Skip to content

Commit e9068d4

Browse files
authored
Merge branch 'main' into log-2-logger
2 parents f9cd4d7 + 64bb35c commit e9068d4

22 files changed

+647
-111
lines changed

.github/workflows/gradle.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ jobs:
4848
# See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md
4949
- name: Setup Gradle
5050
uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4
51+
with:
52+
# The setup-gradle action fails, if the wrapper is not using the right version or is not present.
53+
# Our `gradlew` validates the integrity of the `gradle-wrapper.jar`, so it's safe to disable this.
54+
validate-wrappers: false
5155

5256
- name: Check formatting
5357
run: ./gradlew check

build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ plugins {
2929
id "idea"
3030
id "eclipse"
3131
id "org.jetbrains.gradle.plugin.idea-ext" version "1.1.8"
32+
id "net.ltgt.errorprone" version "4.0.1"
3233
}
3334

3435
allprojects {
@@ -50,10 +51,14 @@ subprojects {
5051
apply plugin: "com.diffplug.spotless"
5152
apply plugin: "jacoco-report-aggregation"
5253
apply plugin: "groovy"
54+
apply plugin: "net.ltgt.errorprone"
5355

5456
tasks.withType(JavaCompile).configureEach {
5557
options.compilerArgs << "-Xlint:unchecked"
5658
options.compilerArgs << "-Xlint:deprecation"
59+
options.errorprone.disableAllWarnings = true
60+
options.errorprone.disableWarningsInGeneratedCode = true
61+
options.errorprone.error("StringCaseLocaleUsage")
5762

5863
// TODO Disabled until the code is only Java 11/17, see #76
5964
// options.release = 17
@@ -76,6 +81,7 @@ subprojects {
7681
testImplementation(libs.assertj.core)
7782
testImplementation(libs.mockito.core)
7883

84+
errorprone("com.google.errorprone:error_prone_core:${libs.errorprone.get().version}")
7985
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
8086
}
8187

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<!--
2+
Copyright (c) 2024 Snowflake Computing Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
-->
16+
17+
18+
# Configuring Polaris for Production
19+
20+
The default `polaris-server.yml` configuration is intended for develoment and testing. When deploying Polaris in production, there are several best practices to keep in mind.
21+
22+
## Security
23+
24+
### Configurations
25+
26+
Notable configuration used to secure a Polaris deployment are outlined below.
27+
28+
#### oauth2
29+
30+
> [!WARNING]
31+
> Ensure that the `tokenBroker` setting reflects the token broker specified in `authenticator` below.
32+
33+
* Configure [OAuth](https://oauth.net/2/) with this setting. Remove the `TestInlineBearerTokenPolarisAuthenticator` option and uncomment the `DefaultPolarisAuthenticator` authenticator option beneath it.
34+
* Then, configure the token broker. You can configure the token broker to use either [asymmetric](https://github.com/polaris-catalog/polaris/blob/b482617bf8cc508b37dbedf3ebc81a9408160a5e/polaris-service/src/main/java/io/polaris/service/auth/JWTRSAKeyPair.java#L24) or [symmetric](https://github.com/polaris-catalog/polaris/blob/b482617bf8cc508b37dbedf3ebc81a9408160a5e/polaris-service/src/main/java/io/polaris/service/auth/JWTSymmetricKeyBroker.java#L23) keys.
35+
36+
#### authenticator.tokenBroker
37+
38+
> [!WARNING]
39+
> Ensure that the `tokenBroker` setting reflects the token broker specified in `oauth2` above.
40+
41+
#### callContextResolver** & **realmContextResolver
42+
* Use these configurations to specify a service that can resolve a realm from bearer tokens.
43+
* The service(s) used here must implement the relevant interfaces (i.e. [CallContextResolver](https://github.com/polaris-catalog/polaris/blob/8290019c10290a600e40b35ddb1e2f54bf99e120/polaris-service/src/main/java/io/polaris/service/context/CallContextResolver.java#L27) and [RealmContextResolver](https://github.com/polaris-catalog/polaris/blob/7ce86f10a68a3b56aed766235c88d6027c0de038/polaris-service/src/main/java/io/polaris/service/context/RealmContextResolver.java)).
44+
45+
## Metastore Management
46+
47+
> [!IMPORTANT]
48+
> The default `in-memory` implementation for `metastoreManager` is meant for testing and not suitable for production usage. Instead, consider an implementation such as `eclipse-link` which allows you to store metadata in a remote database.
49+
50+
A Metastore Manger should be configured with an implementation that durably persists Polaris entities. Use the configuration `metaStoreManager` to configure a [MetastoreManager](https://github.com/polaris-catalog/polaris/blob/627dc602eb15a3258dcc32babf8def34cf6de0e9/polaris-core/src/main/java/io/polaris/core/persistence/PolarisMetaStoreManager.java#L47) implementation where Polaris entities will be persisted.
51+
52+
Be sure to secure your metastore backend since it will be storing credentials and catalog metadata.
53+
54+
### Configuring EclipseLink
55+
56+
To use [EclipseLink](https://eclipse.dev/eclipselink/) for metastore management, specify the configuration `metaStoreManager.conf-file` to point to an [EclipseLink `persistence.xml` file](https://eclipse.dev/eclipselink/documentation/2.5/solutions/testingjpa002.htm). This file, local to the Polaris service, will contain information on what database to use for metastore management and how to connect to it.
57+
58+
### Bootstrapping
59+
60+
Before using Polaris when using a metastore manager other than `in-memory`, you must **bootstrap** the metastore manager. This is a manual operation that must be performed **only once** in order to prepare the metastore manager to integrate with Polaris. When the metastore manager is bootstrapped, any existing Polaris entities in the metastore manager may be **purged**.
61+
62+
To bootstrap Polaris, run:
63+
64+
```bash
65+
java -jar /path/to/jar/polaris-service-1.0.0-all.jar bootstrap polaris-server.yml
66+
```
67+
68+
Afterwards, Polaris can be launched normally:
69+
70+
```bash
71+
java -jar /path/to/jar/polaris-service-1.0.0-all.jar server polaris-server.yml
72+
```
73+
74+
## Other Configurations
75+
76+
When deploying Polaris in production, consider adjusting the following configurations:
77+
78+
#### featureConfiguration.SUPPORTED_CATALOG_STORAGE_TYPES
79+
- By default Polaris catalogs are allowed to be located in local filesystem with the `FILE` storage type. This should be disabled for production systems.
80+
- Use this configuration to additionally disable any other storage types that will not be in use.
81+
82+

docs/index.html

Lines changed: 118 additions & 2 deletions
Large diffs are not rendered by default.

gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ commons-codec1 = { module = "commons-codec:commons-codec", version = "1.17.0" }
3333
commons-lang3 = { module = "org.apache.commons:commons-lang3", version = "3.14.0" }
3434
dropwizard-bom = { module = "io.dropwizard:dropwizard-bom", version = "4.0.7" }
3535
eclipselink = { module = "org.eclipse.persistence:eclipselink", version = "4.0.3" }
36+
errorprone = { module = "com.google.errorprone:error_prone_core", version = "2.29.2" }
3637
google-cloud-storage-bom = { module = "com.google.cloud:google-cloud-storage-bom", version = "2.40.1" }
3738
guava = { module = "com.google.guava:guava", version = "33.2.1-jre" }
3839
h2 = { module = "com.h2database:h2", version = "2.2.224" }

polaris-core/src/main/java/io/polaris/core/PolarisConfiguration.java

Lines changed: 126 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,136 @@
1515
*/
1616
package io.polaris.core;
1717

18-
public class PolarisConfiguration {
18+
import java.util.Optional;
1919

20-
public static final String ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING =
21-
"ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING";
22-
public static final String ALLOW_TABLE_LOCATION_OVERLAP = "ALLOW_TABLE_LOCATION_OVERLAP";
23-
public static final String ALLOW_NAMESPACE_LOCATION_OVERLAP = "ALLOW_NAMESPACE_LOCATION_OVERLAP";
24-
public static final String ALLOW_EXTERNAL_METADATA_FILE_LOCATION =
25-
"ALLOW_EXTERNAL_METADATA_FILE_LOCATION";
20+
public class PolarisConfiguration<T> {
2621

27-
public static final String ALLOW_OVERLAPPING_CATALOG_URLS = "ALLOW_OVERLAPPING_CATALOG_URLS";
22+
public final String key;
23+
public final String description;
24+
public final T defaultValue;
25+
private final Optional<String> catalogConfigImpl;
26+
private final Class<T> typ;
2827

29-
public static final String CATALOG_ALLOW_UNSTRUCTURED_TABLE_LOCATION =
30-
"allow.unstructured.table.location";
31-
public static final String CATALOG_ALLOW_EXTERNAL_TABLE_LOCATION =
32-
"allow.external.table.location";
28+
@SuppressWarnings("unchecked")
29+
public PolarisConfiguration(
30+
String key, String description, T defaultValue, Optional<String> catalogConfig) {
31+
this.key = key;
32+
this.description = description;
33+
this.defaultValue = defaultValue;
34+
this.catalogConfigImpl = catalogConfig;
35+
this.typ = (Class<T>) defaultValue.getClass();
36+
}
3337

34-
/*
35-
* Default values for the configuration properties
36-
*/
38+
public boolean hasCatalogConfig() {
39+
return catalogConfigImpl.isPresent();
40+
}
3741

38-
public static final boolean DEFAULT_ALLOW_OVERLAPPING_CATALOG_URLS = false;
39-
public static final boolean DEFAULT_ALLOW_TABLE_LOCATION_OVERLAP = false;
40-
public static final boolean DEFAULT_ALLOW_EXTERNAL_METADATA_FILE_LOCATION = false;
41-
public static final boolean DEFAULT_ALLOW_NAMESPACE_LOCATION_OVERLAP = false;
42+
public String catalogConfig() {
43+
return catalogConfigImpl.orElseThrow(
44+
() ->
45+
new IllegalStateException(
46+
"Attempted to read a catalog config key from a configuration that doesn't have one."));
47+
}
4248

43-
private PolarisConfiguration() {}
49+
T cast(Object value) {
50+
return this.typ.cast(value);
51+
}
52+
53+
public static class Builder<T> {
54+
private String key;
55+
private String description;
56+
private T defaultValue;
57+
private Optional<String> catalogConfig = Optional.empty();
58+
59+
public Builder<T> key(String key) {
60+
this.key = key;
61+
return this;
62+
}
63+
64+
public Builder<T> description(String description) {
65+
this.description = description;
66+
return this;
67+
}
68+
69+
public Builder<T> defaultValue(T defaultValue) {
70+
this.defaultValue = defaultValue;
71+
return this;
72+
}
73+
74+
public Builder<T> catalogConfig(String catalogConfig) {
75+
this.catalogConfig = Optional.of(catalogConfig);
76+
return this;
77+
}
78+
79+
public PolarisConfiguration<T> build() {
80+
if (key == null || description == null || defaultValue == null) {
81+
throw new IllegalArgumentException("key, description, and defaultValue are required");
82+
}
83+
return new PolarisConfiguration<>(key, description, defaultValue, catalogConfig);
84+
}
85+
}
86+
87+
public static <T> Builder<T> builder() {
88+
return new Builder<>();
89+
}
90+
91+
public static final PolarisConfiguration<Boolean>
92+
ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING =
93+
PolarisConfiguration.<Boolean>builder()
94+
.key("ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING")
95+
.description(
96+
"If set to true, require that principals must rotate their credentials before being used "
97+
+ "for anything else.")
98+
.defaultValue(false)
99+
.build();
100+
101+
public static final PolarisConfiguration<Boolean> ALLOW_TABLE_LOCATION_OVERLAP =
102+
PolarisConfiguration.<Boolean>builder()
103+
.key("ALLOW_TABLE_LOCATION_OVERLAP")
104+
.description(
105+
"If set to true, allow one table's location to reside within another table's location. "
106+
+ "This is only enforced within a given namespace.")
107+
.defaultValue(false)
108+
.build();
109+
110+
public static final PolarisConfiguration<Boolean> ALLOW_NAMESPACE_LOCATION_OVERLAP =
111+
PolarisConfiguration.<Boolean>builder()
112+
.key("ALLOW_NAMESPACE_LOCATION_OVERLAP")
113+
.description(
114+
"If set to true, allow one table's location to reside within another table's location. "
115+
+ "This is only enforced within a parent catalog or namespace.")
116+
.defaultValue(false)
117+
.build();
118+
119+
public static final PolarisConfiguration<Boolean> ALLOW_EXTERNAL_METADATA_FILE_LOCATION =
120+
PolarisConfiguration.<Boolean>builder()
121+
.key("ALLOW_EXTERNAL_METADATA_FILE_LOCATION")
122+
.description(
123+
"If set to true, allows metadata files to be located outside the default metadata directory.")
124+
.defaultValue(false)
125+
.build();
126+
127+
public static final PolarisConfiguration<Boolean> ALLOW_OVERLAPPING_CATALOG_URLS =
128+
PolarisConfiguration.<Boolean>builder()
129+
.key("ALLOW_OVERLAPPING_CATALOG_URLS")
130+
.description("If set to true, allows catalog URLs to overlap.")
131+
.defaultValue(false)
132+
.build();
133+
134+
public static final PolarisConfiguration<Boolean> ALLOW_UNSTRUCTURED_TABLE_LOCATION =
135+
PolarisConfiguration.<Boolean>builder()
136+
.key("ALLOW_UNSTRUCTURED_TABLE_LOCATION")
137+
.catalogConfig("allow.unstructured.table.location")
138+
.description("If set to true, allows unstructured table locations.")
139+
.defaultValue(false)
140+
.build();
141+
142+
public static final PolarisConfiguration<Boolean> ALLOW_EXTERNAL_TABLE_LOCATION =
143+
PolarisConfiguration.<Boolean>builder()
144+
.key("ALLOW_EXTERNAL_TABLE_LOCATION")
145+
.catalogConfig("allow.external.table.location")
146+
.description(
147+
"If set to true, allows tables to have external locations outside the default structure.")
148+
.defaultValue(false)
149+
.build();
44150
}

polaris-core/src/main/java/io/polaris/core/PolarisConfigurationStore.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,18 @@
1616
package io.polaris.core;
1717

1818
import com.google.common.base.Preconditions;
19+
import io.polaris.core.entity.CatalogEntity;
1920
import org.jetbrains.annotations.NotNull;
2021
import org.jetbrains.annotations.Nullable;
22+
import org.slf4j.Logger;
23+
import org.slf4j.LoggerFactory;
2124

2225
/**
2326
* Dynamic configuration store used to retrieve runtime parameters, which may vary by realm or by
2427
* request.
2528
*/
2629
public interface PolarisConfigurationStore {
30+
Logger LOGGER = LoggerFactory.getLogger(PolarisConfigurationStore.class);
2731

2832
/**
2933
* Retrieve the current value for a configuration key. May be null if not set.
@@ -53,4 +57,56 @@ public interface PolarisConfigurationStore {
5357
T configValue = getConfiguration(ctx, configName);
5458
return configValue != null ? configValue : defaultValue;
5559
}
60+
61+
/**
62+
* In some cases, we may extract a value that doesn't match the expected type for a config. This
63+
* method can be used to attempt to force-cast it using `String.valueOf`
64+
*/
65+
private <T> @NotNull T tryCast(PolarisConfiguration<T> config, Object value) {
66+
if (value == null) {
67+
return config.defaultValue;
68+
}
69+
70+
if (config.defaultValue instanceof Boolean) {
71+
return config.cast(Boolean.valueOf(String.valueOf(value)));
72+
} else {
73+
return config.cast(value);
74+
}
75+
}
76+
77+
/**
78+
* Retrieve the current value for a configuration.
79+
*
80+
* @param ctx the current call context
81+
* @param config the configuration to load
82+
* @return the current value set for the configuration key or null if not set
83+
* @param <T> the type of the configuration value
84+
*/
85+
default <T> @NotNull T getConfiguration(PolarisCallContext ctx, PolarisConfiguration<T> config) {
86+
T result = getConfiguration(ctx, config.key, config.defaultValue);
87+
return tryCast(config, result);
88+
}
89+
90+
/**
91+
* Retrieve the current value for a configuration, overriding with a catalog config if it is
92+
* present.
93+
*
94+
* @param ctx the current call context
95+
* @param catalogEntity the catalog to check for an override
96+
* @param config the configuration to load
97+
* @return the current value set for the configuration key or null if not set
98+
* @param <T> the type of the configuration value
99+
*/
100+
default <T> @NotNull T getConfiguration(
101+
PolarisCallContext ctx,
102+
@NotNull CatalogEntity catalogEntity,
103+
PolarisConfiguration<T> config) {
104+
if (config.hasCatalogConfig()
105+
&& catalogEntity.getPropertiesAsMap().containsKey(config.catalogConfig())) {
106+
LOGGER.debug("Loaded config from catalog: {}", config.catalogConfig());
107+
return tryCast(config, catalogEntity.getPropertiesAsMap().get(config.catalogConfig()));
108+
} else {
109+
return getConfiguration(ctx, config);
110+
}
111+
}
56112
}

polaris-core/src/main/java/io/polaris/core/auth/PolarisAuthorizer.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -500,8 +500,7 @@ public void authorizeOrThrow(
500500
boolean enforceCredentialRotationRequiredState =
501501
featureConfig.getConfiguration(
502502
CallContext.getCurrentContext().getPolarisCallContext(),
503-
PolarisConfiguration.ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING,
504-
false);
503+
PolarisConfiguration.ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING);
505504
if (enforceCredentialRotationRequiredState
506505
&& authenticatedPrincipal
507506
.getPrincipalEntity()

0 commit comments

Comments
 (0)