Skip to content

Commit 4eee4fe

Browse files
authored
Simplify credentials format when bootstrapping (#806)
It turns out that only `root` credentials can be created during bootstrap, since the principal name is hard-coded to `PolarisEntityConstants.getRootPrincipalName()`: https://github.com/apache/polaris/blob/390f1fa57bb1af24a21aa95fdbff49a46e31add7/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisMetaStoreManagerImpl.java#L612-L615 Because of that, the second element in the quadruplet `realm,name,id,secret` is useless and can be removed.
1 parent 4cb75fb commit 4eee4fe

File tree

7 files changed

+74
-92
lines changed

7 files changed

+74
-92
lines changed

polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisCredentialsBootstrap.java

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
import java.util.HashMap;
2626
import java.util.List;
2727
import java.util.Map;
28-
import java.util.Map.Entry;
2928
import java.util.Optional;
29+
import org.apache.polaris.core.entity.PolarisEntityConstants;
3030
import org.apache.polaris.core.entity.PolarisPrincipalSecrets;
3131

3232
/**
@@ -54,7 +54,7 @@ public static PolarisCredentialsBootstrap fromEnvironment() {
5454
* Parse a string of credentials in the format:
5555
*
5656
* <pre>
57-
* realm1,user1a,client1a,secret1a;realm1,user1b,client1b,secret1b;realm2,user2a,client2a,secret2a;...
57+
* realm1,client1,secret1;realm2,client2,secret2;...
5858
* </pre>
5959
*/
6060
public static PolarisCredentialsBootstrap fromString(@Nullable String credentialsString) {
@@ -65,52 +65,51 @@ public static PolarisCredentialsBootstrap fromString(@Nullable String credential
6565

6666
/**
6767
* Parse a list of credentials; each element should be in the format: {@code
68-
* realm,principal,clientId,clientSecret}.
68+
* realm,clientId,clientSecret}.
6969
*/
7070
public static PolarisCredentialsBootstrap fromList(List<String> credentialsList) {
71-
Map<String, Map<String, Map.Entry<String, String>>> credentials = new HashMap<>();
72-
for (String quadruple : credentialsList) {
73-
if (!quadruple.isBlank()) {
74-
List<String> parts = Splitter.on(',').trimResults().splitToList(quadruple);
75-
if (parts.size() != 4) {
76-
throw new IllegalArgumentException("Invalid credentials format: " + quadruple);
71+
Map<String, Map.Entry<String, String>> credentials = new HashMap<>();
72+
for (String triplet : credentialsList) {
73+
if (!triplet.isBlank()) {
74+
List<String> parts = Splitter.on(',').trimResults().splitToList(triplet);
75+
if (parts.size() != 3) {
76+
throw new IllegalArgumentException("Invalid credentials format: " + triplet);
7777
}
7878
String realmName = parts.get(0);
79-
String principalName = parts.get(1);
80-
String clientId = parts.get(2);
81-
String clientSecret = parts.get(3);
82-
credentials
83-
.computeIfAbsent(realmName, k -> new HashMap<>())
84-
.merge(
85-
principalName,
86-
new SimpleEntry<>(clientId, clientSecret),
87-
(a, b) -> {
88-
throw new IllegalArgumentException("Duplicate principal: " + principalName);
89-
});
79+
String clientId = parts.get(1);
80+
String clientSecret = parts.get(2);
81+
82+
if (credentials.containsKey(realmName)) {
83+
throw new IllegalArgumentException("Duplicate realm: " + realmName);
84+
}
85+
credentials.put(realmName, new SimpleEntry<>(clientId, clientSecret));
9086
}
9187
}
9288
return credentials.isEmpty() ? EMPTY : new PolarisCredentialsBootstrap(credentials);
9389
}
9490

95-
@VisibleForTesting final Map<String, Map<String, Map.Entry<String, String>>> credentials;
91+
@VisibleForTesting final Map<String, Map.Entry<String, String>> credentials;
9692

97-
private PolarisCredentialsBootstrap(Map<String, Map<String, Entry<String, String>>> credentials) {
93+
private PolarisCredentialsBootstrap(Map<String, Map.Entry<String, String>> credentials) {
9894
this.credentials = credentials;
9995
}
10096

10197
/**
10298
* Get the secrets for the specified principal in the specified realm, if available among the
103-
* credentials that were supplied for bootstrap.
99+
* credentials that were supplied for bootstrap. Only credentials for the root principal are
100+
* supported.
104101
*/
105102
public Optional<PolarisPrincipalSecrets> getSecrets(
106103
String realmName, long principalId, String principalName) {
107-
return Optional.ofNullable(credentials.get(realmName))
108-
.flatMap(principals -> Optional.ofNullable(principals.get(principalName)))
109-
.map(
110-
credentials -> {
111-
String clientId = credentials.getKey();
112-
String secret = credentials.getValue();
113-
return new PolarisPrincipalSecrets(principalId, clientId, secret, secret);
114-
});
104+
if (principalName.equals(PolarisEntityConstants.getRootPrincipalName())) {
105+
return Optional.ofNullable(credentials.get(realmName))
106+
.map(
107+
credentials -> {
108+
String clientId = credentials.getKey();
109+
String secret = credentials.getValue();
110+
return new PolarisPrincipalSecrets(principalId, clientId, secret, secret);
111+
});
112+
}
113+
return Optional.empty();
115114
}
116115
}

polaris-core/src/test/java/org/apache/polaris/core/persistence/PolarisCredentialsBootstrapTest.java

Lines changed: 32 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,11 @@
2121
import static org.assertj.core.api.Assertions.assertThat;
2222
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2323

24-
import java.util.Comparator;
2524
import java.util.List;
26-
import org.apache.polaris.core.entity.PolarisPrincipalSecrets;
2725
import org.junit.jupiter.api.Test;
2826

2927
class PolarisCredentialsBootstrapTest {
3028

31-
private final Comparator<PolarisPrincipalSecrets> comparator =
32-
(a, b) ->
33-
a.getPrincipalId() == b.getPrincipalId()
34-
&& a.getPrincipalClientId().equals(b.getPrincipalClientId())
35-
&& a.getMainSecret().equals(b.getMainSecret())
36-
&& a.getSecondarySecret().equals(b.getSecondarySecret())
37-
? 0
38-
: 1;
39-
4029
@Test
4130
void nullString() {
4231
PolarisCredentialsBootstrap credentials = PolarisCredentialsBootstrap.fromString(null);
@@ -62,67 +51,63 @@ void invalidString() {
6251
}
6352

6453
@Test
65-
void duplicatePrincipal() {
54+
void duplicateRealm() {
6655
assertThatThrownBy(
6756
() ->
6857
PolarisCredentialsBootstrap.fromString(
69-
"realm1,user1a,client1a,secret1a;realm1,user1a,client1b,secret1b"))
70-
.hasMessage("Duplicate principal: user1a");
58+
"realm1,client1a,secret1a;realm1,client1b,secret1b"))
59+
.hasMessage("Duplicate realm: realm1");
7160
}
7261

7362
@Test
7463
void getSecretsValidString() {
7564
PolarisCredentialsBootstrap credentials =
7665
PolarisCredentialsBootstrap.fromString(
77-
" ; realm1 , user1a , client1a , secret1a ; realm1 , user1b , client1b , secret1b ; realm2 , user2a , client2a , secret2a ; ");
78-
assertThat(credentials.getSecrets("realm1", 123, "nonexistent")).isEmpty();
79-
assertThat(credentials.getSecrets("nonexistent", 123, "user1a")).isEmpty();
80-
assertThat(credentials.getSecrets("realm1", 123, "user1a"))
81-
.usingValueComparator(comparator)
82-
.contains(new PolarisPrincipalSecrets(123, "client1a", "secret1a", "secret1a"));
83-
assertThat(credentials.getSecrets("realm1", 123, "user1b"))
84-
.usingValueComparator(comparator)
85-
.contains(new PolarisPrincipalSecrets(123, "client1b", "secret1b", "secret1b"));
86-
assertThat(credentials.getSecrets("realm2", 123, "user2a"))
87-
.usingValueComparator(comparator)
88-
.contains(new PolarisPrincipalSecrets(123, "client2a", "secret2a", "secret2a"));
66+
" ; realm1 , client1 , secret1 ; realm2 , client2 , secret2 ; ");
67+
assertCredentials(credentials);
8968
}
9069

9170
@Test
9271
void getSecretsValidList() {
9372
PolarisCredentialsBootstrap credentials =
9473
PolarisCredentialsBootstrap.fromList(
95-
List.of(
96-
"realm1,user1a,client1a,secret1a",
97-
"realm1,user1b,client1b,secret1b",
98-
"realm2,user2a,client2a,secret2a"));
99-
assertThat(credentials.getSecrets("realm1", 123, "nonexistent")).isEmpty();
100-
assertThat(credentials.getSecrets("nonexistent", 123, "user1a")).isEmpty();
101-
assertThat(credentials.getSecrets("realm1", 123, "user1a"))
102-
.usingValueComparator(comparator)
103-
.contains(new PolarisPrincipalSecrets(123, "client1a", "secret1a", "secret1a"));
104-
assertThat(credentials.getSecrets("realm1", 123, "user1b"))
105-
.usingValueComparator(comparator)
106-
.contains(new PolarisPrincipalSecrets(123, "client1b", "secret1b", "secret1b"));
107-
assertThat(credentials.getSecrets("realm2", 123, "user2a"))
108-
.usingValueComparator(comparator)
109-
.contains(new PolarisPrincipalSecrets(123, "client2a", "secret2a", "secret2a"));
74+
List.of("realm1,client1,secret1", "realm2,client2,secret2"));
75+
assertCredentials(credentials);
11076
}
11177

11278
@Test
11379
void getSecretsValidSystemProperty() {
11480
PolarisCredentialsBootstrap credentials = PolarisCredentialsBootstrap.fromEnvironment();
11581
assertThat(credentials.credentials).isEmpty();
11682
try {
117-
System.setProperty("polaris.bootstrap.credentials", "realm1,user1a,client1a,secret1a");
83+
System.setProperty(
84+
"polaris.bootstrap.credentials", "realm1,client1,secret1;realm2,client2,secret2");
11885
credentials = PolarisCredentialsBootstrap.fromEnvironment();
119-
assertThat(credentials.getSecrets("realm1", 123, "nonexistent")).isEmpty();
120-
assertThat(credentials.getSecrets("nonexistent", 123, "user1a")).isEmpty();
121-
assertThat(credentials.getSecrets("realm1", 123, "user1a"))
122-
.usingValueComparator(comparator)
123-
.contains(new PolarisPrincipalSecrets(123, "client1a", "secret1a", "secret1a"));
86+
assertCredentials(credentials);
12487
} finally {
12588
System.clearProperty("polaris.bootstrap.credentials");
12689
}
12790
}
91+
92+
private void assertCredentials(PolarisCredentialsBootstrap credentials) {
93+
assertThat(credentials.getSecrets("realm3", 123, "root")).isEmpty();
94+
assertThat(credentials.getSecrets("nonexistent", 123, "root")).isEmpty();
95+
assertThat(credentials.getSecrets("realm1", 123, "non-root")).isEmpty();
96+
assertThat(credentials.getSecrets("realm1", 123, "root"))
97+
.hasValueSatisfying(
98+
secrets -> {
99+
assertThat(secrets.getPrincipalId()).isEqualTo(123);
100+
assertThat(secrets.getPrincipalClientId()).isEqualTo("client1");
101+
assertThat(secrets.getMainSecret()).isEqualTo("secret1");
102+
assertThat(secrets.getSecondarySecret()).isEqualTo("secret1");
103+
});
104+
assertThat(credentials.getSecrets("realm2", 123, "root"))
105+
.hasValueSatisfying(
106+
secrets -> {
107+
assertThat(secrets.getPrincipalId()).isEqualTo(123);
108+
assertThat(secrets.getPrincipalClientId()).isEqualTo("client2");
109+
assertThat(secrets.getMainSecret()).isEqualTo("secret2");
110+
assertThat(secrets.getSecondarySecret()).isEqualTo("secret2");
111+
});
112+
}
128113
}

polaris-core/src/test/java/org/apache/polaris/core/persistence/PrincipalSecretsGeneratorTest.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,8 @@ void testRandomSecrets() {
3939
@Test
4040
void testSecretOverride() {
4141
PrincipalSecretsGenerator gen =
42-
bootstrap(
43-
"test-Realm", PolarisCredentialsBootstrap.fromString("test-Realm,user1,client1,sec2"));
44-
PolarisPrincipalSecrets s = gen.produceSecrets("user1", 123);
42+
bootstrap("test-Realm", PolarisCredentialsBootstrap.fromString("test-Realm,client1,sec2"));
43+
PolarisPrincipalSecrets s = gen.produceSecrets("root", 123);
4544
assertThat(s).isNotNull();
4645
assertThat(s.getPrincipalId()).isEqualTo(123);
4746
assertThat(s.getPrincipalClientId()).isEqualTo("client1");

quarkus/admin/src/main/java/org/apache/polaris/admintool/BootstrapCommand.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public class BootstrapCommand extends BaseCommand {
3939
@CommandLine.Option(
4040
names = {"-c", "--credential"},
4141
description =
42-
"Principal credentials to bootstrap. Must be of the form 'realm,userName,clientId,clientSecret'.")
42+
"Root principal credentials to bootstrap. Must be of the form 'realm,clientId,clientSecret'.")
4343
List<String> credentials;
4444

4545
@Override

quarkus/admin/src/test/java/org/apache/polaris/admintool/BootstrapCommandTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ class BootstrapCommandTest {
3737
"-r",
3838
"realm2",
3939
"-c",
40-
"realm1,root,root,s3cr3t",
40+
"realm1,root,s3cr3t",
4141
"-c",
42-
"realm2,root,root,s3cr3t"
42+
"realm2,root,s3cr3t"
4343
})
4444
public void testBootstrap(LaunchResult result) {
4545
assertThat(result.getOutput()).contains("Bootstrap completed successfully.");

quarkus/service/build.gradle.kts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,9 @@ tasks.withType(Test::class.java).configureEach {
150150
if (System.getenv("AWS_REGION") == null) {
151151
environment("AWS_REGION", "us-west-2")
152152
}
153-
// Note: the test secrets are referenced in DropwizardServerManager
154-
environment("POLARIS_BOOTSTRAP_CREDENTIALS", "POLARIS,root,test-admin,test-secret")
153+
// Note: the test secrets are referenced in
154+
// org.apache.polaris.service.quarkus.it.QuarkusServerManager
155+
environment("POLARIS_BOOTSTRAP_CREDENTIALS", "POLARIS,test-admin,test-secret")
155156
jvmArgs("--add-exports", "java.base/sun.nio.ch=ALL-UNNAMED")
156157
// Need to allow a java security manager after Java 21, for Subject.getSubject to work
157158
// "getSubject is supported only if a security manager is allowed".

site/content/in-dev/unreleased/configuring-polaris-for-production.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,21 +75,19 @@ Before using Polaris when using a metastore manager other than `in-memory`, you
7575
By default, Polaris will create randomised `CLIENT_ID` and `CLIENT_SECRET` for the `root` principal and store their hashes in the metastore backend. In order to provide your own credentials for `root` principal (so you can request tokens via `api/catalog/v1/oauth/tokens`), set the `POLARIS_BOOTSTRAP_CREDENTIALS` environment variable as follows:
7676

7777
```
78-
export POLARIS_BOOTSTRAP_CREDENTIALS=my_realm,root,my-client-id,my-client-secret
78+
export POLARIS_BOOTSTRAP_CREDENTIALS=my_realm,my-client-id,my-client-secret
7979
```
8080

81-
The format of the environment variable is `realm,principal,client_id,client_secret`. You can provide multiple credentials separated by `;`. For example, to provide credentials for two realms `my_realm` and `my_realm2`:
81+
The format of the environment variable is `realm,client_id,client_secret`. You can provide multiple credentials separated by `;`. For example, to provide credentials for two realms `my_realm` and `my_realm2`:
8282

8383
```
84-
export POLARIS_BOOTSTRAP_CREDENTIALS=my_realm,root,my-client-id,my-client-secret;my_realm2,root,my-client-id2,my-client-secret2
84+
export POLARIS_BOOTSTRAP_CREDENTIALS=my_realm,my-client-id,my-client-secret;my_realm2,my-client-id2,my-client-secret2
8585
```
8686

87-
You can also provide credentials for other users too.
88-
8987
It is also possible to use system properties to provide the credentials:
9088

9189
```
92-
java -Dpolaris.bootstrap.credentials=my_realm,root,my-client-id,my-client-secret -jar /path/to/jar/polaris-service-all.jar bootstrap polaris-server.yml
90+
java -Dpolaris.bootstrap.credentials=my_realm,my-client-id,my-client-secret -jar /path/to/jar/polaris-service-all.jar bootstrap polaris-server.yml
9391
```
9492

9593
Now, to bootstrap Polaris, run:

0 commit comments

Comments
 (0)