Skip to content

Commit 43a8699

Browse files
Fix / Handling of unmodified secrets in config updates (#551)
* Fix handling of unmodified secrets in config updates * Explicit error handling for inconsistent secret alias in the orchestrator * Add Python license aliases for compliance * In orchestrator, filter repositories for a job to just what the job requires * Handle non-standard license alias for greenlet * Restrict versions due to compliance issues around support of PEP 639 * Bring in repo resources used for import model jobs * Update Python requirements for compliance tools
1 parent 52cf5a4 commit 43a8699

File tree

5 files changed

+90
-18
lines changed

5 files changed

+90
-18
lines changed

dev/compliance/license-config-python.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ ALLOWED_LICENSES="${ALLOWED_LICENSES};Python Software Foundation License"
2424
ALLOWED_LICENSES="${ALLOWED_LICENSES};ISC License (ISCL)"
2525
ALLOWED_LICENSES="${ALLOWED_LICENSES};The Unlicense (Unlicense)"
2626

27+
# License for greenlet is specified this way
28+
ALLOWED_LICENSES="${ALLOWED_LICENSES};MIT AND Python-2.0"
29+
2730
# The "certifi" package is a dependency of Python Safety, licensed under MPL 2.0
2831
# It is OK to use since the compliance tools are not distributed
2932
# So, we can exclude the package from our license report

tracdap-runtime/python/requirements.txt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ polars >= 1.0.0
5858

5959
idna >= 3.7
6060
typing_extensions < 4.13
61+
urllib3 < 2.4.0
6162

6263

6364
# ----------------------------------------------------------------------------------------------------------------------
@@ -81,8 +82,10 @@ packaging >= 23.0
8182

8283
# Compliance dependencies
8384

84-
# Safety 3.3 introduces a GPL dependency, excluding for now
85-
safety >= 3.2, < 3.3
85+
# Safety 3.3.0 introduces a GPL dependency, fixed in 3.3.1
86+
# Safety uses marhmallow, which has breaking API changes in 4.0
87+
safety != 3.3.0
88+
marshmallow < 4.0
8689

8790
# Series 3.x does not work with Python 3.11
8891
# Series 4.x does not work with Python 3.7

tracdap-runtime/python/setup.cfg

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@ install_requires =
7272
pyyaml == 6.0.2
7373
dulwich == 0.22.7
7474
requests == 2.32.3
75-
# Bad release, excluded until fixed
75+
# Not usable yet due to compliance issues around PEP 639
7676
typing_extensions < 4.13
77+
urllib3 < 2.4.0
7778

7879

7980
[options.extras_require]

tracdap-services/tracdap-svc-admin/src/main/java/org/finos/tracdap/svc/admin/services/ConfigService.java

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.finos.tracdap.api.internal.ConfigUpdateType;
2323
import org.finos.tracdap.api.internal.InternalMetadataApiGrpc;
2424
import org.finos.tracdap.common.config.ConfigKeys;
25+
import org.finos.tracdap.common.exception.EConfigParse;
2526
import org.finos.tracdap.common.metadata.MetadataUtil;
2627
import org.finos.tracdap.common.middleware.GrpcConcern;
2728

@@ -236,7 +237,7 @@ private ConfigWriteRequest processSecrets(
236237

237238
if (objectType == ObjectType.RESOURCE) {
238239

239-
var secureResource = processSecrets(
240+
var secureResource = processResourceSecrets(
240241
request.getDefinition().getResource(),
241242
prior.getDefinition().getResource(),
242243
secrets, secretsUpdated);
@@ -245,14 +246,16 @@ private ConfigWriteRequest processSecrets(
245246

246247
return request.toBuilder().setDefinition(secureObject).build();
247248
}
249+
else {
248250

249-
// Other object types are not processed for secrets
250-
secretsUpdated.setResult(false);
251+
// Other object types are not processed for secrets
252+
secretsUpdated.setResult(false);
251253

252-
return request;
254+
return request;
255+
}
253256
}
254257

255-
private ResourceDefinition processSecrets(
258+
private ResourceDefinition processResourceSecrets(
256259
ResourceDefinition newResource, ResourceDefinition oldResource,
257260
ISecretService secrets, SimpleResult<Boolean> secretsUpdated) {
258261

@@ -265,18 +268,34 @@ private ResourceDefinition processSecrets(
265268

266269
if (secretValue != null && !secretValue.isEmpty()) {
267270

271+
// Secret value is supplied - update the secret store
268272
var secretAlias = secrets.storePassword(secretKey, secretValue);
269273
secureResource.putSecrets(secretKey, secretAlias);
270274

271275
if (!secretsUpdated.isDone())
272276
secretsUpdated.setResult(true);
273277
}
278+
else if (oldResource.containsSecrets(secretKey)) {
279+
280+
// If secret value is blank and a previous version exists, carry the secret over
281+
var secretAlias = oldResource.getSecretsOrThrow(secretKey);
282+
secureResource.putSecrets(secretKey, secretAlias);
283+
}
284+
else {
285+
286+
// Do not allow setting blank secrets
287+
var message = String.format("No value supplied for config secret [%s]", secretKey);
288+
log.error(message);
289+
throw new EConfigParse(message);
290+
}
274291
}
275292

276293
for (var secretKey : oldResource.getSecretsMap().keySet()) {
277294
if (!newResource.containsSecrets(secretKey)) {
278295

279-
secrets.deleteSecret(secretKey);
296+
// Delete any secrets that have been removed since the prior version
297+
var secretAlias = oldResource.getSecretsOrThrow(secretKey);
298+
secrets.deleteSecret(secretAlias);
280299

281300
if (!secretsUpdated.isDone())
282301
secretsUpdated.setResult(true);

tracdap-services/tracdap-svc-orch/src/main/java/org/finos/tracdap/svc/orch/service/JobProcessorHelpers.java

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
import org.finos.tracdap.api.*;
2121
import org.finos.tracdap.api.internal.InternalMetadataApiGrpc;
2222
import org.finos.tracdap.common.config.ConfigHelpers;
23+
import org.finos.tracdap.common.config.ConfigKeys;
2324
import org.finos.tracdap.common.config.ConfigManager;
2425
import org.finos.tracdap.common.config.IDynamicResources;
26+
import org.finos.tracdap.common.exception.EConsistencyValidation;
2527
import org.finos.tracdap.common.exception.EUnexpected;
2628
import org.finos.tracdap.common.metadata.MetadataBundle;
2729
import org.finos.tracdap.common.metadata.MetadataCodec;
@@ -306,7 +308,7 @@ JobState buildJobConfig(JobState jobState) {
306308

307309
for (var storageEntry : internalStorage.entrySet()) {
308310
var storageKey = storageEntry.getKey();
309-
var storage = translateResourceConfig(storageEntry.getValue());
311+
var storage = translateResourceConfig(storageKey, storageEntry.getValue(), jobState);
310312
storageConfig.putBuckets(storageKey, storage);
311313
}
312314

@@ -315,7 +317,7 @@ JobState buildJobConfig(JobState jobState) {
315317

316318
for (var storageEntry : externalStorage.entrySet()) {
317319
var storageKey = storageEntry.getKey();
318-
var storage = translateResourceConfig(storageEntry.getValue());
320+
var storage = translateResourceConfig(storageKey, storageEntry.getValue(), jobState);
319321
storageConfig.putExternal(storageKey, storage);
320322
}
321323

@@ -336,27 +338,71 @@ JobState buildJobConfig(JobState jobState) {
336338
resource -> resource.getResourceType() == ResourceType.MODEL_REPOSITORY);
337339

338340
for (var repoEntry : repositories.entrySet()) {
341+
339342
var repoKey = repoEntry.getKey();
340-
var repoConfig = translateResourceConfig(repoEntry.getValue());
341-
sysConfig.putRepositories(repoKey, repoConfig);
343+
344+
// Only translate repositories required for the job
345+
if (jobUsesRepository(repoKey, jobState)) {
346+
var repoConfig = translateResourceConfig(repoKey, repoEntry.getValue(), jobState);
347+
sysConfig.putRepositories(repoKey, repoConfig);
348+
}
342349
}
343350

344351
jobState.sysConfig = sysConfig.build();
345352

346353
return jobState;
347354
}
348355

349-
private PluginConfig translateResourceConfig(ResourceDefinition resource) {
356+
private boolean jobUsesRepository(String repoKey, JobState jobState) {
357+
358+
// This method filters repo resources for the currently known job types
359+
// TODO: Generic resource filtering in the IJobLogic interface
360+
361+
// Import model jobs can refer to repositories directly
362+
if (jobState.jobType == JobType.IMPORT_MODEL) {
363+
var importModelJob = jobState.definition.getImportModel();
364+
if (importModelJob.getRepository().equals(repoKey)) {
365+
return true;
366+
}
367+
}
368+
369+
// Check if any resources refer to the repo key
370+
for (var object : jobState.resources.values()) {
371+
if (object.getObjectType() == ObjectType.MODEL) {
372+
if (object.getModel().getRepository().equals(repoKey)) {
373+
return true;
374+
}
375+
}
376+
}
377+
378+
return false;
379+
}
380+
381+
private PluginConfig translateResourceConfig(String resourceKey, ResourceDefinition resource, JobState jobState) {
350382

351383
var pluginConfig = ConfigHelpers.resourceToPluginConfig(resource).toBuilder();
352384

385+
var tenantScope = String.format("/%s/%s/", ConfigKeys.TENANT_SCOPE, jobState.tenant);
386+
353387
for (var secretEntry : pluginConfig.getSecretsMap().entrySet()) {
354388

355-
var propKey = secretEntry.getKey();
356-
var secretKey = secretEntry.getValue();
357-
var secret = configManager.loadPassword(secretKey);
389+
var propertyName = secretEntry.getKey();
390+
var secretAlias = secretEntry.getValue();
391+
392+
// Secret loader will not load the secret if the alias is not valid
393+
// This handling provides more meaningful errors
394+
if (secretAlias.isBlank() || !secretAlias.startsWith(tenantScope)) {
395+
396+
var message = String.format("Resource configuration for [%s] is not valid", resourceKey);
397+
var detail = String.format("Inconsistent secret alias for [%s]", propertyName);
398+
399+
log.error("{}: {}", message, detail);
400+
throw new EConsistencyValidation(message);
401+
}
402+
403+
var secret = configManager.loadPassword(secretAlias);
358404

359-
pluginConfig.putProperties(propKey, secret);
405+
pluginConfig.putProperties(propertyName, secret);
360406
}
361407

362408
pluginConfig.clearSecrets();

0 commit comments

Comments
 (0)