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

Runtime config application #1225

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Draft
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
5 changes: 3 additions & 2 deletions conf/default-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"enclave_platform": null,
"failure_shutdown_wait_hours": 120,
"sharing_token_expiry_seconds": 2592000,
"operator_type": "public"

"operator_type": "public",
"core_config_path": "/config",
"config_scan_period_ms": 300000
}
5 changes: 5 additions & 0 deletions src/main/java/com/uid2/operator/Const.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,10 @@ public class Config extends com.uid2.shared.Const.Config {
public static final String OptOutStatusMaxRequestSize = "optout_status_max_request_size";
public static final String MaxInvalidPaths = "logging_limit_max_invalid_paths_per_interval";
public static final String MaxVersionBucketsPerSite = "logging_limit_max_version_buckets_per_site";

public static final String CoreConfigPath = "core_config_path"; //TODO: update when endpoint name finalised
public static final String ConfigScanPeriodMs = "config_scan_period_ms";
public static final String Config = "config";
public static final String identityV3 = "identity_v3";
}
}
70 changes: 40 additions & 30 deletions src/main/java/com/uid2/operator/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
import com.uid2.operator.monitoring.IStatsCollectorQueue;
import com.uid2.operator.monitoring.OperatorMetrics;
import com.uid2.operator.monitoring.StatsCollectorVerticle;
import com.uid2.operator.service.SecureLinkValidatorService;
import com.uid2.operator.service.ShutdownService;
import com.uid2.operator.service.*;
import com.uid2.operator.vertx.Endpoints;
import com.uid2.operator.vertx.OperatorShutdownHandler;
import com.uid2.operator.store.CloudSyncOptOutStore;
Expand Down Expand Up @@ -37,6 +36,7 @@
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import io.micrometer.prometheus.PrometheusRenameFilter;
import io.vertx.config.ConfigRetriever;
import io.vertx.core.*;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.http.impl.HttpUtils;
Expand Down Expand Up @@ -265,39 +265,49 @@ private ICloudStorage wrapCloudStorageForOptOut(ICloudStorage cloudStorage) {
}

private void run() throws Exception {
Supplier<Verticle> operatorVerticleSupplier = () -> {
UIDOperatorVerticle verticle = new UIDOperatorVerticle(config, this.clientSideTokenGenerate, siteProvider, clientKeyProvider, clientSideKeypairProvider, getKeyManager(), saltProvider, optOutStore, Clock.systemUTC(), _statsCollectorQueue, new SecureLinkValidatorService(this.serviceLinkProvider, this.serviceProvider), this.shutdownHandler::handleSaltRetrievalResponse);
return verticle;
};
ConfigRetrieverFactory configRetrieverFactory = new ConfigRetrieverFactory();
ConfigRetriever configRetriever = configRetrieverFactory.create(vertx, config);

DeploymentOptions options = new DeploymentOptions();
int svcInstances = this.config.getInteger(Const.Config.ServiceInstancesProp);
options.setInstances(svcInstances);
ConfigService.create(configRetriever).compose(configService -> {

Promise<Void> compositePromise = Promise.promise();
List<Future> fs = new ArrayList<>();
fs.add(createAndDeployStatsCollector());
fs.add(createStoreVerticles());
Supplier<Verticle> operatorVerticleSupplier = () -> {
UIDOperatorVerticle verticle = new UIDOperatorVerticle(configService, this.clientSideTokenGenerate, siteProvider, clientKeyProvider, clientSideKeypairProvider, getKeyManager(), saltProvider, optOutStore, Clock.systemUTC(), _statsCollectorQueue, new SecureLinkValidatorService(this.serviceLinkProvider, this.serviceProvider), this.shutdownHandler::handleSaltRetrievalResponse);
return verticle;
};

CompositeFuture.all(fs).onComplete(ar -> {
if (ar.failed()) compositePromise.fail(new Exception(ar.cause()));
else compositePromise.complete();
});
DeploymentOptions options = new DeploymentOptions();
int svcInstances = this.config.getInteger(Const.Config.ServiceInstancesProp);
options.setInstances(svcInstances);

compositePromise.future()
.compose(v -> {
metrics.setup();
vertx.setPeriodic(60000, id -> metrics.update());

Promise<String> promise = Promise.promise();
vertx.deployVerticle(operatorVerticleSupplier, options, promise);
return promise.future();
})
.onFailure(t -> {
LOGGER.error("Failed to bootstrap operator: " + t.getMessage(), new Exception(t));
vertx.close();
System.exit(1);
Promise<Void> compositePromise = Promise.promise();
List<Future> fs = new ArrayList<>();
fs.add(createAndDeployStatsCollector());
try {
fs.add(createStoreVerticles());
} catch (Exception e) {
throw new RuntimeException(e);
}

CompositeFuture.all(fs).onComplete(ar -> {
if (ar.failed()) compositePromise.fail(new Exception(ar.cause()));
else compositePromise.complete();
});

return compositePromise.future()
.compose(v -> {
metrics.setup();
vertx.setPeriodic(60000, id -> metrics.update());

Promise<String> promise = Promise.promise();
vertx.deployVerticle(operatorVerticleSupplier, options, promise);
return promise.future();
})
.onFailure(t -> {
LOGGER.error("Failed to bootstrap operator: " + t.getMessage(), new Exception(t));
vertx.close();
System.exit(1);
});
});
}

private Future<Void> createStoreVerticles() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.uid2.operator.service;

import com.uid2.operator.Const;
import io.vertx.config.ConfigRetriever;
import io.vertx.config.ConfigRetrieverOptions;
import io.vertx.config.ConfigStoreOptions;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;

import static com.uid2.operator.Const.Config.ConfigScanPeriodMs;
import static com.uid2.operator.Const.Config.CoreConfigPath;

public class ConfigRetrieverFactory {
public ConfigRetriever create(Vertx vertx, JsonObject bootstrapConfig) {
String configPath = bootstrapConfig.getString(CoreConfigPath);


ConfigStoreOptions httpStore = new ConfigStoreOptions()
.setType("http")
.setConfig(new JsonObject()
.put("host", "127.0.0.1")
.put("port", Const.Port.ServicePortForCore)
.put("path", configPath));

ConfigStoreOptions bootstrapStore = new ConfigStoreOptions()
.setType("json")
.setConfig(bootstrapConfig);

ConfigRetrieverOptions retrieverOptions = new ConfigRetrieverOptions()
.setScanPeriod(bootstrapConfig.getLong(ConfigScanPeriodMs))
.addStore(bootstrapStore)
.addStore(httpStore);

return ConfigRetriever.create(vertx, retrieverOptions);
}
}
70 changes: 70 additions & 0 deletions src/main/java/com/uid2/operator/service/ConfigService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.uid2.operator.service;

import com.uid2.operator.Const;
import io.vertx.config.ConfigRetriever;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.uid2.operator.service.ConfigValidatorUtil.*;
import static com.uid2.operator.service.UIDOperatorService.*;

public class ConfigService implements IConfigService {

private ConfigRetriever configRetriever;
private static final Logger logger = LoggerFactory.getLogger(ConfigService.class);

private ConfigService(ConfigRetriever configRetriever) {
this.configRetriever = configRetriever;
this.configRetriever.setConfigurationProcessor(this::configValidationHandler);
}

public static Future<ConfigService> create(ConfigRetriever configRetriever) {
Promise<ConfigService> promise = Promise.promise();

ConfigService instance = new ConfigService(configRetriever);

configRetriever.getConfig(ar -> {
if (ar.succeeded()) {
System.out.println("Successfully loaded config");
promise.complete(instance);
} else {
System.err.println("Failed to load config: " + ar.cause().getMessage());
logger.error("Failed to load config{}", ar.cause().getMessage());
promise.fail(ar.cause());
}
});

return promise.future();
}

@Override
public JsonObject getConfig() {
return configRetriever.getCachedConfig();
}

private JsonObject configValidationHandler(JsonObject config) {
boolean isValid = true;
Integer identityExpiresAfter = config.getInteger(IDENTITY_TOKEN_EXPIRES_AFTER_SECONDS);
Integer refreshExpiresAfter = config.getInteger(REFRESH_TOKEN_EXPIRES_AFTER_SECONDS);
Integer refreshIdentityAfter = config.getInteger(REFRESH_IDENTITY_TOKEN_AFTER_SECONDS);
Integer maxBidstreamLifetimeSeconds = config.getInteger(Const.Config.MaxBidstreamLifetimeSecondsProp);

isValid &= validateIdentityRefreshTokens(identityExpiresAfter, refreshExpiresAfter, refreshIdentityAfter);

isValid &= validateBidstreamLifetime(maxBidstreamLifetimeSeconds, identityExpiresAfter);

if (!isValid) {
logger.error("Failed to update config");
JsonObject lastConfig = this.getConfig();
if (lastConfig == null || lastConfig.isEmpty()) {
throw new RuntimeException("Invalid config retrieved and no previous config to revert to");
}
return lastConfig;
}

return config;
}
}
47 changes: 47 additions & 0 deletions src/main/java/com/uid2/operator/service/ConfigValidatorUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.uid2.operator.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.uid2.operator.service.UIDOperatorService.*;

public class ConfigValidatorUtil {
private static final Logger logger = LoggerFactory.getLogger(ConfigValidatorUtil.class);
public static final String VALUES_ARE_NULL = "Required config values are null";

public static Boolean validateIdentityRefreshTokens(Integer identityExpiresAfter, Integer refreshExpiresAfter, Integer refreshIdentityAfter) {
boolean isValid = true;

if (identityExpiresAfter == null || refreshExpiresAfter == null || refreshIdentityAfter == null) {
logger.error(VALUES_ARE_NULL);
return false;
}


if (identityExpiresAfter > refreshExpiresAfter) {
logger.error(REFRESH_TOKEN_EXPIRES_AFTER_SECONDS + " must be >= " + IDENTITY_TOKEN_EXPIRES_AFTER_SECONDS);
isValid = false;
}
if (refreshIdentityAfter > identityExpiresAfter) {
logger.error(IDENTITY_TOKEN_EXPIRES_AFTER_SECONDS + " must be >= " + REFRESH_IDENTITY_TOKEN_AFTER_SECONDS);
isValid = false;
}
if (refreshIdentityAfter > refreshExpiresAfter) {
logger.error(REFRESH_TOKEN_EXPIRES_AFTER_SECONDS + " must be >= " + REFRESH_IDENTITY_TOKEN_AFTER_SECONDS);
}
return isValid;
}

public static Boolean validateBidstreamLifetime(Integer maxBidstreamLifetimeSeconds, Integer identityTokenExpiresAfterSeconds) {
if (maxBidstreamLifetimeSeconds == null || identityTokenExpiresAfterSeconds == null) {
logger.error(VALUES_ARE_NULL);
return false;
}

if (maxBidstreamLifetimeSeconds < identityTokenExpiresAfterSeconds) {
logger.error("Max bidstream lifetime seconds ({} seconds) is less than identity token lifetime ({} seconds)", maxBidstreamLifetimeSeconds, identityTokenExpiresAfterSeconds);
return false;
}
return true;
}
}
7 changes: 7 additions & 0 deletions src/main/java/com/uid2/operator/service/IConfigService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.uid2.operator.service;

import io.vertx.core.json.JsonObject;

public interface IConfigService {
JsonObject getConfig();
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@

public interface IUIDOperatorService {

IdentityTokens generateIdentity(IdentityRequest request);
IdentityTokens generateIdentity(IdentityRequest request, Duration refreshIdentityAfter, Duration refreshExpiresAfter, Duration identityExpiresAfter);

RefreshResponse refreshIdentity(RefreshToken refreshToken);
RefreshResponse refreshIdentity(RefreshToken token, Duration refreshIdentityAfter, Duration refreshExpiresAfter, Duration identityExpiresAfter);

MappedIdentity mapIdentity(MapRequest request);

Expand All @@ -27,6 +27,4 @@ public interface IUIDOperatorService {
boolean advertisingTokenMatches(String advertisingToken, UserIdentity userIdentity, Instant asOf);

Instant getLatestOptoutEntry(UserIdentity userIdentity, Instant asOf);

Duration getIdentityExpiryDuration();
}
Loading
Loading