Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support providing root client ID via env. variables when bootstrapping #422

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import org.apache.polaris.core.exceptions.AlreadyExistsException;
import org.apache.polaris.core.persistence.PolarisMetaStoreManagerImpl;
import org.apache.polaris.core.persistence.PolarisMetaStoreSession;
import org.apache.polaris.core.persistence.PrincipalSecretsGenerator;
import org.apache.polaris.core.persistence.RetryOnConcurrencyException;
import org.apache.polaris.core.persistence.models.ModelEntity;
import org.apache.polaris.core.persistence.models.ModelEntityActive;
Expand Down Expand Up @@ -645,13 +646,16 @@ public int lookupEntityGrantRecordsVersion(
/** {@inheritDoc} */
@Override
public @NotNull PolarisPrincipalSecrets generateNewPrincipalSecrets(
@NotNull PolarisCallContext callCtx, @NotNull String principalName, long principalId) {
@NotNull PolarisCallContext callCtx,
@NotNull String principalName,
long principalId,
PrincipalSecretsGenerator generator) {
// ensure principal client id is unique
PolarisPrincipalSecrets principalSecrets;
ModelPrincipalSecrets lookupPrincipalSecrets;
do {
// generate new random client id and secrets
principalSecrets = new PolarisPrincipalSecrets(principalId);
principalSecrets = generator.produceSecrets(principalName, principalId);

// load the existing secrets
lookupPrincipalSecrets =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import org.apache.polaris.core.PolarisCallContext;
import org.apache.polaris.core.PolarisDefaultDiagServiceImpl;
Expand Down Expand Up @@ -75,17 +76,20 @@ private void initializeForRealm(RealmContext realmContext) {
}

@Override
public synchronized Map<String, PolarisMetaStoreManager.PrincipalSecretsResult> bootstrapRealms(
List<String> realms) {
public Map<String, PolarisMetaStoreManager.PrincipalSecretsResult> bootstrapRealms(
List<String> realms, Function<String, PrincipalSecretsGenerator> rootSecretsPerRealm) {
Map<String, PolarisMetaStoreManager.PrincipalSecretsResult> results = new HashMap<>();

for (String realm : realms) {
RealmContext realmContext = () -> realm;
PrincipalSecretsGenerator rootSecrets = rootSecretsPerRealm.apply(realm);
if (!metaStoreManagerMap.containsKey(realmContext.getRealmIdentifier())) {
initializeForRealm(realmContext);
PolarisMetaStoreManager.PrincipalSecretsResult secretsResult =
bootstrapServiceAndCreatePolarisPrincipalForRealm(
realmContext, metaStoreManagerMap.get(realmContext.getRealmIdentifier()));
realmContext,
metaStoreManagerMap.get(realmContext.getRealmIdentifier()),
rootSecrets);
results.put(realmContext.getRealmIdentifier(), secretsResult);
}
}
Expand Down Expand Up @@ -159,7 +163,9 @@ public void setStorageIntegrationProvider(PolarisStorageIntegrationProvider stor
*/
private PolarisMetaStoreManager.PrincipalSecretsResult
bootstrapServiceAndCreatePolarisPrincipalForRealm(
RealmContext realmContext, PolarisMetaStoreManager metaStoreManager) {
RealmContext realmContext,
PolarisMetaStoreManager metaStoreManager,
PrincipalSecretsGenerator rootSecretsGenerator) {
// While bootstrapping we need to act as a fake privileged context since the real
// CallContext hasn't even been resolved yet.
PolarisCallContext polarisContext =
Expand All @@ -182,7 +188,7 @@ public void setStorageIntegrationProvider(PolarisStorageIntegrationProvider stor
throw new IllegalArgumentException(overrideMessage);
}

metaStoreManager.bootstrapPolarisService(polarisContext);
metaStoreManager.bootstrapPolarisService(polarisContext, rootSecretsGenerator);

PolarisMetaStoreManager.EntityResult rootPrincipalLookup =
metaStoreManager.readEntityByName(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.dropwizard.jackson.Discoverable;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import org.apache.polaris.core.context.RealmContext;
import org.apache.polaris.core.monitor.PolarisMetricRegistry;
Expand All @@ -45,7 +46,8 @@ public interface MetaStoreManagerFactory extends Discoverable {

void setMetricRegistry(PolarisMetricRegistry metricRegistry);

Map<String, PolarisMetaStoreManager.PrincipalSecretsResult> bootstrapRealms(List<String> realms);
Map<String, PolarisMetaStoreManager.PrincipalSecretsResult> bootstrapRealms(
List<String> realms, Function<String, PrincipalSecretsGenerator> rootSecretsPerRealm);

/** Purge all metadata for the realms provided */
void purgeRealms(List<String> realms);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,8 @@ public boolean alreadyExists() {
* @return the result of the bootstrap attempt
*/
@NotNull
BaseResult bootstrapPolarisService(@NotNull PolarisCallContext callCtx);
BaseResult bootstrapPolarisService(
@NotNull PolarisCallContext callCtx, PrincipalSecretsGenerator rootSecretsGenerator);

/**
* Purge all metadata associated with the Polaris service, resetting the metastore to the state it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
package org.apache.polaris.core.persistence;

import static org.apache.polaris.core.persistence.PrincipalSecretsGenerator.RANDOM_SECRETS;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
Expand Down Expand Up @@ -636,9 +638,12 @@ private void revokeGrantRecord(
*
* @param callCtx call context
* @param ms meta store in read/write mode
* @param rootSecretsGenerator
*/
private void bootstrapPolarisService(
@NotNull PolarisCallContext callCtx, @NotNull PolarisMetaStoreSession ms) {
@NotNull PolarisCallContext callCtx,
@NotNull PolarisMetaStoreSession ms,
PrincipalSecretsGenerator rootSecretsGenerator) {

// Create a root container entity that can represent the securable for any top-level grants.
PolarisBaseEntity rootContainer =
Expand All @@ -665,7 +670,7 @@ private void bootstrapPolarisService(
PolarisEntityConstants.getRootPrincipalName());

// create this principal
this.createPrincipal(callCtx, ms, rootPrincipal);
this.createPrincipal(callCtx, ms, rootPrincipal, rootSecretsGenerator);

// now create the account admin principal role
long serviceAdminPrincipalRoleId = ms.generateNewId(callCtx);
Expand Down Expand Up @@ -698,12 +703,14 @@ private void bootstrapPolarisService(

/** {@inheritDoc} */
@Override
public @NotNull BaseResult bootstrapPolarisService(@NotNull PolarisCallContext callCtx) {
public @NotNull BaseResult bootstrapPolarisService(
@NotNull PolarisCallContext callCtx, PrincipalSecretsGenerator rootSecretsGenerator) {
// get meta store we should be using
PolarisMetaStoreSession ms = callCtx.getMetaStore();

// run operation in a read/write transaction
ms.runActionInTransaction(callCtx, () -> this.bootstrapPolarisService(callCtx, ms));
ms.runActionInTransaction(
callCtx, () -> this.bootstrapPolarisService(callCtx, ms, rootSecretsGenerator));

// all good
return new BaseResult(ReturnStatus.SUCCESS);
Expand Down Expand Up @@ -879,7 +886,8 @@ public Map<String, String> deserializeProperties(PolarisCallContext callCtx, Str
private @NotNull CreatePrincipalResult createPrincipal(
@NotNull PolarisCallContext callCtx,
@NotNull PolarisMetaStoreSession ms,
@NotNull PolarisBaseEntity principal) {
@NotNull PolarisBaseEntity principal,
PrincipalSecretsGenerator generator) {
// validate input
callCtx.getDiagServices().checkNotNull(principal, "unexpected_null_principal");

Expand Down Expand Up @@ -956,7 +964,7 @@ public Map<String, String> deserializeProperties(PolarisCallContext callCtx, Str

// generate new secretes for this principal
PolarisPrincipalSecrets principalSecrets =
ms.generateNewPrincipalSecrets(callCtx, principal.getName(), principal.getId());
ms.generateNewPrincipalSecrets(callCtx, principal.getName(), principal.getId(), generator);

// generate properties
Map<String, String> internalProperties = getInternalPropertyMap(callCtx, principal);
Expand All @@ -981,7 +989,8 @@ public Map<String, String> deserializeProperties(PolarisCallContext callCtx, Str
PolarisMetaStoreSession ms = callCtx.getMetaStore();

// need to run inside a read/write transaction
return ms.runInTransaction(callCtx, () -> this.createPrincipal(callCtx, ms, principal));
return ms.runInTransaction(
callCtx, () -> this.createPrincipal(callCtx, ms, principal, RANDOM_SECRETS));
}

/** See {@link #loadPrincipalSecrets(PolarisCallContext, String)} */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,10 @@ PolarisPrincipalSecrets loadPrincipalSecrets(
*/
@NotNull
PolarisPrincipalSecrets generateNewPrincipalSecrets(
@NotNull PolarisCallContext callCtx, @NotNull String principalName, long principalId);
@NotNull PolarisCallContext callCtx,
@NotNull String principalName,
long principalId,
PrincipalSecretsGenerator generator);

/**
* Rotate the secrets of a principal entity, i.e. make the specified main secrets the secondary
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,13 +448,16 @@ public int lookupEntityGrantRecordsVersion(
/** {@inheritDoc} */
@Override
public @NotNull PolarisPrincipalSecrets generateNewPrincipalSecrets(
@NotNull PolarisCallContext callCtx, @NotNull String principalName, long principalId) {
@NotNull PolarisCallContext callCtx,
@NotNull String principalName,
long principalId,
PrincipalSecretsGenerator generator) {
// ensure principal client id is unique
PolarisPrincipalSecrets principalSecrets;
PolarisPrincipalSecrets lookupPrincipalSecrets;
do {
// generate new random client id and secrets
principalSecrets = new PolarisPrincipalSecrets(principalId);
principalSecrets = generator.produceSecrets(principalName, principalId);

// load the existing secrets
lookupPrincipalSecrets =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.polaris.core.persistence;

import java.util.Locale;
import java.util.function.Function;
import org.apache.polaris.core.entity.PolarisPrincipalSecrets;
import org.jetbrains.annotations.NotNull;

@FunctionalInterface
public interface PrincipalSecretsGenerator {

PrincipalSecretsGenerator RANDOM_SECRETS = (name, id) -> new PolarisPrincipalSecrets(id);

PolarisPrincipalSecrets produceSecrets(@NotNull String principalName, long principalId);

static Function<String, PrincipalSecretsGenerator> bootstrap(Function<String, String> config) {
return (realmName) ->
(principalName, principalId) -> {
String propId =
String.format("POLARIS_BOOTSTRAP_%s_%s_CLIENT_ID", realmName, principalName);
String propSecret =
String.format("POLARIS_BOOTSTRAP_%s_%s_CLIENT_SECRET", realmName, principalName);
String clientId = config.apply(propId.toUpperCase(Locale.ROOT));
String secret = config.apply(propSecret.toUpperCase(Locale.ROOT));
if (clientId == null || secret == null) {
return new PolarisPrincipalSecrets(principalId); // random secrets
} else {
// Use the same secondary secret for bootstrapping. It can be rotated later, if required
return new PolarisPrincipalSecrets(principalId, clientId, secret, secret);
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ public List<EntityWithPath> getPendingUpdates() {
}

@Override
public BaseResult bootstrapPolarisService(@NotNull PolarisCallContext callCtx) {
public @NotNull BaseResult bootstrapPolarisService(
@NotNull PolarisCallContext callCtx, PrincipalSecretsGenerator rootSecretsGenerator) {
callCtx
.getDiagServices()
.fail("illegal_method_in_transaction_workspace", "bootstrapPolarisService");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.polaris.core.persistence;

import static org.apache.polaris.core.persistence.PrincipalSecretsGenerator.bootstrap;
import static org.assertj.core.api.Assertions.assertThat;

import java.util.Map;
import org.apache.polaris.core.entity.PolarisPrincipalSecrets;
import org.junit.jupiter.api.Test;

class PrincipalSecretsGeneratorTest {

@Test
void testRandomSecrets() {
PolarisPrincipalSecrets s =
bootstrap((name) -> null).apply("test").produceSecrets("name1", 123);
assertThat(s).isNotNull();
assertThat(s.getPrincipalId()).isEqualTo(123);
assertThat(s.getPrincipalClientId()).isNotNull();
assertThat(s.getMainSecret()).isNotNull();
assertThat(s.getSecondarySecret()).isNotNull();
}

@Test
void testSecretOverride() {
PolarisPrincipalSecrets s =
bootstrap(
Map.of(
"POLARIS_BOOTSTRAP_TEST-REALM_USER1_CLIENT_ID",
"client1",
"POLARIS_BOOTSTRAP_TEST-REALM_USER1_CLIENT_SECRET",
"sec2")
::get)
.apply("test-Realm")
.produceSecrets("user1", 123);
assertThat(s).isNotNull();
assertThat(s.getPrincipalId()).isEqualTo(123);
assertThat(s.getPrincipalClientId()).isEqualTo("client1");
assertThat(s.getMainSecret()).isEqualTo("sec2");
assertThat(s.getSecondarySecret()).isEqualTo("sec2");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
package org.apache.polaris.core.persistence;

import static org.apache.polaris.core.persistence.PrincipalSecretsGenerator.RANDOM_SECRETS;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand Down Expand Up @@ -69,7 +71,7 @@ public PolarisTestMetaStoreManager(

// bootstrap the Polaris service
polarisMetaStoreManager.purge(polarisCallContext);
polarisMetaStoreManager.bootstrapPolarisService(polarisCallContext);
polarisMetaStoreManager.bootstrapPolarisService(polarisCallContext, RANDOM_SECRETS);
}

public void forceRetry() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.apache.polaris.core.PolarisConfigurationStore;
import org.apache.polaris.core.persistence.MetaStoreManagerFactory;
import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
import org.apache.polaris.core.persistence.PrincipalSecretsGenerator;
import org.apache.polaris.service.config.ConfigurationStoreAware;
import org.apache.polaris.service.config.PolarisApplicationConfig;
import org.apache.polaris.service.context.CallContextResolver;
Expand Down Expand Up @@ -62,7 +63,9 @@ protected void run(

// Execute the bootstrap
Map<String, PolarisMetaStoreManager.PrincipalSecretsResult> results =
metaStoreManagerFactory.bootstrapRealms(configuration.getDefaultRealms());
metaStoreManagerFactory.bootstrapRealms(
configuration.getDefaultRealms(),
PrincipalSecretsGenerator.bootstrap(System.getenv()::get));

// Log any errors:
boolean success = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.apache.polaris.core.persistence.PolarisMetaStoreSession;
import org.apache.polaris.core.persistence.PolarisTreeMapMetaStoreSessionImpl;
import org.apache.polaris.core.persistence.PolarisTreeMapStore;
import org.apache.polaris.core.persistence.PrincipalSecretsGenerator;
import org.jetbrains.annotations.NotNull;

@JsonTypeName("in-memory")
Expand Down Expand Up @@ -71,7 +72,9 @@ public synchronized Supplier<PolarisMetaStoreSession> getOrCreateSessionSupplier

private void bootstrapRealmAndPrintCredentials(String realmId) {
Map<String, PolarisMetaStoreManager.PrincipalSecretsResult> results =
this.bootstrapRealms(Collections.singletonList(realmId));
this.bootstrapRealms(
Collections.singletonList(realmId),
PrincipalSecretsGenerator.bootstrap(System.getenv()::get));
bootstrappedRealms.add(realmId);

PolarisMetaStoreManager.PrincipalSecretsResult principalSecrets = results.get(realmId);
Expand Down
Loading