Skip to content

Commit 5d5ef8a

Browse files
committed
Fix plugin secrets in config
Signed-off-by: Ben Sherman <bentshermann@gmail.com>
1 parent 21a6470 commit 5d5ef8a

File tree

8 files changed

+123
-21
lines changed

8 files changed

+123
-21
lines changed

docs/secrets.md

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ This feature allows you to decouple the use of secrets in your pipelines from th
1313

1414
When a pipeline is launched, Nextflow injects the secrets into the run without leaking them into temporary execution files. Secrets are provided to tasks as environment variables.
1515

16+
Secrets can be used with the local executor and the grid executors (e.g., Slurm or Grid Engine). Secrets can be used with the AWS Batch executor when launched from [Seqera Platform](https://seqera.io/blog/pipeline-secrets-secure-handling-of-sensitive-information-in-tower/).
17+
1618
## Command line
1719

1820
The Nextflow {ref}`cli-secrets` sub-command can be used to manage secrets:
@@ -48,6 +50,21 @@ The above snippet accesses the secrets `MY_ACCESS_KEY` and `MY_SECRET_KEY` and a
4850
Secrets cannot be assigned to pipeline parameters.
4951
:::
5052

53+
:::{versionadded} 25.10.0
54+
:::
55+
56+
In order to enable the use of AWS secrets in configuration files, Nextflow loads the configuration once without secrets enabled, and reloads the configuration only if secrets were accessed. This is because the AWS secret integration is part of the `nf-amazon` plugin, and plugins are resolved after the configuration is loaded (since plugins can be specified in the configuration). As a result, config secrets must be used in a way that does not cause the config resolution to fail when secrets are not present.
57+
58+
For example:
59+
60+
```groovy
61+
includeConfig secrets.MY_SECRET
62+
? "https://example.com/extra.config?secret=${secrets.MY_SECRET}"
63+
: '/dev/null'
64+
```
65+
66+
The above snippet includes a secured config only if the secret is present. Otherwise, it includes `/dev/null`, which is equivalent to including an empty file. The initial check for `secrets.MY_SECRET` causes the config to be reloaded with secrets enabled, including secret integrations from plugins such as `nf-amazon`.
67+
5168
(secrets-pipeline-script)=
5269

5370
## Pipeline script
@@ -67,10 +84,6 @@ workflow {
6784
The above example is only meant to demonstrate how to access a secret, not how to use it. In practice, sensitive information should not be printed to the console or output files.
6885
:::
6986

70-
:::{note}
71-
Secrets can only be used with the local or grid executors (e.g., Slurm or Grid Engine). Secrets can be used with the AWS Batch executor when launched from [Seqera Platform](https://seqera.io/blog/pipeline-secrets-secure-handling-of-sensitive-information-in-tower/).
72-
:::
73-
7487
## Process directive
7588

7689
Secrets can be accesses by processes using the {ref}`process-secret` directive. For example:
@@ -92,7 +105,3 @@ In the above example, the secrets `MY_ACCESS_KEY` and `MY_SECRET_KEY` are inject
92105
:::{warning}
93106
Secrets are made available as environment variables in the process script. To prevent evaluation in the Nextflow script context, escape variable names with a backslash (e.g., `\$MY_ACCESS_KEY`) as shown above.
94107
:::
95-
96-
:::{note}
97-
Secrets can only be used with the local or grid executors (e.g., Slurm or Grid Engine). Secrets can be used with the AWS Batch executor when launched from [Seqera Platform](https://seqera.io/blog/pipeline-secrets-secure-handling-of-sensitive-information-in-tower/).
98-
:::

modules/nextflow/src/main/groovy/nextflow/cli/CmdRun.groovy

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ import nextflow.plugin.Plugins
4444
import nextflow.scm.AssetManager
4545
import nextflow.script.ScriptFile
4646
import nextflow.script.ScriptRunner
47+
import nextflow.secret.ConfigNullProvider
48+
import nextflow.secret.SecretsLoader
4749
import nextflow.util.CustomPoolFactory
4850
import nextflow.util.Duration
4951
import nextflow.util.HistoryFile
@@ -320,25 +322,39 @@ class CmdRun extends CmdBase implements HubOptions {
320322
checkRunName()
321323

322324
printBanner()
323-
Plugins.init()
324325

325-
// -- specify the arguments
326+
// -- resolve main script
326327
final scriptFile = getScriptFile(pipeline)
327328

328-
// create the config object
329-
final builder = new ConfigBuilder()
329+
// -- load config (without secrets)
330+
final secretsProvider = new ConfigNullProvider()
331+
ConfigBuilder builder = new ConfigBuilder()
332+
.setOptions(launcher.options)
333+
.setCmdRun(this)
334+
.setBaseDir(scriptFile.parent)
335+
.setSecretsProvider(secretsProvider)
336+
ConfigMap config = builder.build()
337+
338+
// -- load plugins
339+
Plugins.init()
340+
Plugins.load(config)
341+
342+
// -- load secrets provider
343+
SecretsLoader.getInstance().load()
344+
345+
// -- reload config if secrets were used
346+
if( secretsProvider.usedSecrets() ) {
347+
log.debug "Config file used secrets -- reloading config with secrets provider"
348+
builder = new ConfigBuilder()
330349
.setOptions(launcher.options)
331350
.setCmdRun(this)
332351
.setBaseDir(scriptFile.parent)
333-
final config = builder .build()
352+
config = builder.build()
353+
}
334354

335355
// check DSL syntax in the config
336356
launchInfo(config, scriptFile)
337357

338-
// -- load plugins
339-
final cfg = plugins ? [plugins: plugins.tokenize(',')] : config
340-
Plugins.load(cfg)
341-
342358
// -- validate config options
343359
if( NF.isSyntaxParserV2() )
344360
new ConfigValidator().validate(config)

modules/nextflow/src/main/groovy/nextflow/config/ConfigBuilder.groovy

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import nextflow.cli.CmdRun
3333
import nextflow.exception.AbortOperationException
3434
import nextflow.exception.ConfigParseException
3535
import nextflow.secret.SecretsLoader
36+
import nextflow.secret.SecretsProvider
3637
import nextflow.trace.GraphObserver
3738
import nextflow.trace.ReportObserver
3839
import nextflow.trace.TimelineObserver
@@ -77,6 +78,8 @@ class ConfigBuilder {
7778

7879
boolean showMissingVariables
7980

81+
SecretsProvider secretsProvider
82+
8083
Map<ConfigObject, String> emptyVariables = new LinkedHashMap<>(10)
8184

8285
Map<String,String> env = new HashMap<>(System.getenv())
@@ -103,6 +106,11 @@ class ConfigBuilder {
103106
return this
104107
}
105108

109+
ConfigBuilder setSecretsProvider(SecretsProvider value) {
110+
this.secretsProvider = value
111+
return this
112+
}
113+
106114
ConfigBuilder setOptions( CliOptions options ) {
107115
this.options = options
108116
return this
@@ -327,17 +335,20 @@ class ConfigBuilder {
327335
// this is needed to make sure to reuse the same
328336
// instance of the config vars across different instances of the ConfigBuilder
329337
// and prevent multiple parsing of the same params file (which can even be remote resource)
330-
return cacheableConfigVars(baseDir)
338+
final secretContext = secretsProvider
339+
? SecretsLoader.secretContext(secretsProvider)
340+
: SecretsLoader.secretContext()
341+
return cacheableConfigVars(baseDir, secretContext)
331342
}
332343

333344
@Memoized
334-
static private Map cacheableConfigVars(Path base) {
345+
static private Map cacheableConfigVars(Path base, Object secretContext) {
335346
final binding = new HashMap(10)
336347
binding.put('baseDir', base)
337348
binding.put('projectDir', base)
338349
binding.put('launchDir', Paths.get('.').toRealPath())
339350
binding.put('outputDir', Paths.get('results').complete())
340-
binding.put('secrets', SecretsLoader.secretContext())
351+
binding.put('secrets', secretContext)
341352
return binding
342353
}
343354

@@ -549,6 +560,9 @@ class ConfigBuilder {
549560
if( cmdRun.preview )
550561
config.preview = cmdRun.preview
551562

563+
if( cmdRun.plugins )
564+
config.plugins = cmdRun.plugins.tokenize(',')
565+
552566
// -- sets the working directory
553567
if( cmdRun.workDir )
554568
config.workDir = cmdRun.workDir
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2013-2024, Seqera Labs
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+
package nextflow.secret
19+
20+
import groovy.transform.CompileStatic
21+
22+
/**
23+
* Specialization of the null secrets provider that is used to
24+
* determine whether secrets are required in the config.
25+
*
26+
* @author Ben Sherman <bentshermann@gmail.com>
27+
*/
28+
@CompileStatic
29+
class ConfigNullProvider extends NullProvider {
30+
31+
private boolean accessed
32+
33+
@Override
34+
Secret getSecret(String name) {
35+
accessed = true
36+
return new SecretImpl(name, '')
37+
}
38+
39+
boolean usedSecrets() {
40+
return accessed
41+
}
42+
}

modules/nextflow/src/main/groovy/nextflow/secret/SecretsLoader.groovy

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,9 @@ class SecretsLoader {
7878
final provider = isEnabled() ? getInstance().load() : new NullProvider()
7979
return makeSecretsContext(provider)
8080
}
81-
81+
82+
static Object secretContext(SecretsProvider provider) {
83+
return makeSecretsContext(provider)
84+
}
85+
8286
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
set -e
3+
4+
$NXF_CMD secrets set MY_SECRET hello-world
5+
6+
$NXF_RUN -c ../../config-secrets.config | tee stdout
7+
8+
< .nextflow.log grep "Config file used secrets -- reloading config with secrets provider" || false
9+
< stdout grep "outputDir: results-hello-world" || false
10+
11+
$NXF_CMD secrets delete MY_SECRET

tests/config-secrets.config

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
outputDir = secrets.MY_SECRET ? "results-$secrets.MY_SECRET" : 'results'

tests/config-secrets.nf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
workflow {
3+
log.info "outputDir: ${workflow.outputDir.name}"
4+
}

0 commit comments

Comments
 (0)