Skip to content

Commit 3d66366

Browse files
authored
Merge pull request #31 from nextflow-io/secrets
Fix default credentials being used if secrets aren't resolved
2 parents fbf0e6f + 68adb18 commit 3d66366

File tree

8 files changed

+163
-9
lines changed

8 files changed

+163
-9
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ The following options are available:
5757
`sql.db.'<DB-NAME>'.password`
5858
: The database connection password.
5959

60+
For information on using secrets with database credentials, see [docs/secrets.md](docs/secrets.md).
61+
6062
## Dataflow Operators
6163

6264
This plugin provides the following dataflow operators for querying from and inserting into database tables.

changelog.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
NF-SQLDB CHANGE-LOG
22
===================
3+
0.7.1 - 21 Aug 2025
4+
- Fix unresolved secrets detection to prevent silent fallback to default credentials
5+
- Add comprehensive secrets documentation and troubleshooting guide
6+
- Improve error messages for unknown database configurations
7+
38
0.7.0 - 28 May 2025
49
- Add sqlExecutor and other minor improvements [072ae039]
510

docs/secrets.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Using Secrets for Database Credentials
2+
3+
For production deployments, it's recommended to use Nextflow secrets instead of hardcoding credentials in configuration files. This is especially important when working with cloud databases like AWS Athena.
4+
5+
## Configuration with Secrets
6+
7+
When using [Nextflow secrets](https://www.nextflow.io/docs/latest/secrets.html) (available in Nextflow 25.04.0+), you can reference workspace or user-level secrets in your database configuration:
8+
9+
```groovy
10+
sql {
11+
db {
12+
athena {
13+
url = 'jdbc:awsathena://AwsRegion=us-east-1;S3OutputLocation=s3://bucket;Workgroup=workgroup'
14+
user = secrets.ATHENA_USER
15+
password = secrets.ATHENA_PASSWORD
16+
driver = 'com.simba.athena.jdbc.Driver'
17+
}
18+
}
19+
}
20+
```
21+
22+
## Setting Up Secrets in Seqera Platform
23+
24+
1. **Workspace Secrets**: Navigate to your workspace → Secrets → Add secret
25+
2. **User Secrets**: Navigate to Your profile → Secrets → Add secret
26+
3. Create secrets with names matching your configuration (e.g., `ATHENA_USER`, `ATHENA_PASSWORD`)
27+
28+
## Troubleshooting Secrets Issues
29+
30+
If you encounter authentication errors like "Missing credentials error", verify:
31+
32+
- **Secret Names**: Ensure secret names in your configuration match exactly (case-sensitive)
33+
- **Permissions**: Verify you have access to workspace secrets or have defined user-level secrets
34+
- **Nextflow Version**: Secrets require Nextflow >=25.04.0
35+
- **Secret Values**: Ensure secrets contain valid credentials (no empty values)
36+
37+
Common error patterns:
38+
- `user=sa; password=null` indicates secrets were not resolved
39+
- `Unresolved secret detected` means secret names don't match or aren't accessible
40+
41+
## Local Development
42+
43+
For local testing, you can use the Nextflow secrets command:
44+
45+
```bash
46+
# Set secrets locally
47+
nextflow secrets set ATHENA_USER "your-username"
48+
nextflow secrets set ATHENA_PASSWORD "your-password"
49+
50+
# List secrets
51+
nextflow secrets list
52+
53+
# Run pipeline with secrets
54+
nextflow run your-pipeline.nf
55+
```

plugins/nf-sqldb/src/main/nextflow/sql/ChannelSqlExtension.groovy

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,9 @@ class ChannelSqlExtension extends PluginExtensionPoint {
106106
final dataSource = config.getDataSource(dsName)
107107
if( dataSource==null ) {
108108
def msg = "Unknown db name: $dsName"
109-
def choices = config.getDataSourceNames().closest(dsName) ?: config.getDataSourceNames()
110-
if( choices?.size() == 1 )
111-
msg += " - Did you mean: ${choices.get(0)}?"
112-
else if( choices )
113-
msg += " - Did you mean any of these?\n" + choices.collect { " $it"}.join('\n') + '\n'
109+
def choices = config.getDataSourceNames()
110+
if( choices )
111+
msg += " - Available databases: " + choices.join(', ')
114112
throw new IllegalArgumentException(msg)
115113
}
116114
return dataSource

plugins/nf-sqldb/src/main/nextflow/sql/config/SqlDataSource.groovy

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,21 +43,53 @@ class SqlDataSource {
4343
SqlDataSource(Map opts) {
4444
this.url = opts.url ?: DEFAULT_URL
4545
this.driver = opts.driver ?: urlToDriver(url) ?: DEFAULT_DRIVER
46-
this.user = opts.user ?: DEFAULT_USER
47-
this.password = opts.password
46+
this.user = resolveCredential(opts.user, 'user') ?: DEFAULT_USER
47+
this.password = resolveCredential(opts.password, 'password')
4848
}
4949

5050
SqlDataSource(Map opts, SqlDataSource fallback) {
5151
this.url = opts.url ?: fallback.url ?: DEFAULT_URL
5252
this.driver = opts.driver ?: urlToDriver(url) ?: fallback.driver ?: DEFAULT_DRIVER
53-
this.user = opts.user ?: fallback.user ?: DEFAULT_USER
54-
this.password = opts.password ?: fallback.password
53+
this.user = resolveCredential(opts.user, 'user') ?: fallback.user ?: DEFAULT_USER
54+
this.password = resolveCredential(opts.password, 'password') ?: fallback.password
5555
}
5656

5757
protected String urlToDriver(String url) {
5858
DriverRegistry.DEFAULT.urlToDriver(url)
5959
}
6060

61+
/**
62+
* Resolves a credential value, checking for unresolved secrets and providing appropriate error handling
63+
*
64+
* @param value The credential value from configuration
65+
* @param credType The type of credential ('user' or 'password') for error messages
66+
* @return The resolved credential value, or null if not provided
67+
* @throws IllegalArgumentException if an unresolved secret is detected
68+
*/
69+
protected String resolveCredential(Object value, String credType) {
70+
if (value == null) {
71+
return null
72+
}
73+
74+
String stringValue = value.toString()
75+
76+
// Check for unresolved secrets (patterns like 'secrets.ATHENA_USER' or similar)
77+
if (stringValue.startsWith('secrets.') || stringValue.contains('secret') && stringValue.contains('[') && stringValue.contains(']')) {
78+
throw new IllegalArgumentException(
79+
"Unresolved secret detected for $credType: '$stringValue'. " +
80+
"This typically indicates that workspace secrets are not properly configured or accessible. " +
81+
"Please verify that:\n" +
82+
"1. The secret is defined in your workspace/user secrets\n" +
83+
"2. The secret name matches exactly (case-sensitive)\n" +
84+
"3. You have proper permissions to access the secret\n" +
85+
"4. The Nextflow version supports secrets integration (>=25.04.0)\n" +
86+
"See: https://www.nextflow.io/docs/latest/secrets.html"
87+
)
88+
}
89+
90+
return stringValue.isEmpty() ? null : stringValue
91+
}
92+
6193
Map toMap() {
6294
final result = new HashMap(10)
6395
if( url )

plugins/nf-sqldb/src/test/nextflow/sql/ChannelSqlExtensionTest.groovy

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,34 @@ class ChannelSqlExtensionTest extends Specification {
9595
rows.alpha == ['x1','y2','z3']
9696
}
9797

98+
def 'should error on unresolved secrets' () {
99+
given:
100+
def session = Mock(Session) {
101+
getConfig() >> [sql: [db: [test: [user: 'secrets.ATHENA_USER']]]]
102+
}
103+
104+
when:
105+
new ChannelSqlExtension().init(session)
106+
107+
then:
108+
thrown(IllegalArgumentException)
109+
}
110+
111+
def 'should handle unknown database' () {
112+
given:
113+
def session = Mock(Session) {
114+
getConfig() >> [sql: [db: [default: [url: 'jdbc:h2:mem:'], postgres: [url: 'jdbc:postgresql:']]]]
115+
}
116+
def sqlExtension = new ChannelSqlExtension()
117+
sqlExtension.init(session)
118+
119+
when:
120+
sqlExtension.fromQuery([db: 'invalid'], 'select * from table')
121+
122+
then:
123+
def e = thrown(IllegalArgumentException)
124+
e.message.contains("Unknown db name: invalid")
125+
e.message.contains("Available databases:")
126+
}
127+
98128
}

plugins/nf-sqldb/src/test/nextflow/sql/SqlDslTest.groovy

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,12 @@ class SqlDslTest extends Dsl2Spec {
209209
@Requires({System.getenv('NF_SQLDB_TEST_ATHENA_REGION')})
210210
@Requires({System.getenv('NF_SQLDB_ATHENA_TEST_S3_BUCKET')})
211211
@IgnoreIf({ System.getenv('NXF_SMOKE') })
212+
@Requires({
213+
System.getenv('NF_SQLDB_TEST_ATHENA_USERNAME') &&
214+
System.getenv('NF_SQLDB_TEST_ATHENA_PASSWORD') &&
215+
System.getenv('NF_SQLDB_TEST_ATHENA_REGION') &&
216+
System.getenv('NF_SQLDB_ATHENA_TEST_S3_BUCKET')
217+
})
212218
@Timeout(60)
213219
def 'should perform a query for AWS Athena and create a channel'() {
214220
given:

plugins/nf-sqldb/src/test/nextflow/sql/config/SqlDataSourceTest.groovy

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,30 @@ class SqlDataSourceTest extends Specification {
132132
ds1.hashCode() == ds2.hashCode()
133133
ds1.hashCode() != ds3.hashCode()
134134
}
135+
136+
def 'should detect unresolved secrets' () {
137+
when:
138+
new SqlDataSource([user: pattern])
139+
then:
140+
def e = thrown(IllegalArgumentException)
141+
e.message.contains("Unresolved secret detected")
142+
e.message.contains("workspace secrets are not properly configured")
143+
144+
where:
145+
pattern << ['secrets.ATHENA_USER', '[secret]']
146+
}
147+
148+
def 'should handle various credential inputs' () {
149+
when:
150+
def ds = new SqlDataSource([user: userInput, password: passInput])
151+
then:
152+
ds.user == expectedUser
153+
ds.password == expectedPass
154+
155+
where:
156+
userInput | passInput | expectedUser | expectedPass
157+
'validuser' | 'validpass' | 'validuser' | 'validpass'
158+
null | null | SqlDataSource.DEFAULT_USER| null
159+
'' | '' | SqlDataSource.DEFAULT_USER| null
160+
}
135161
}

0 commit comments

Comments
 (0)