Skip to content

Commit cbfbe8a

Browse files
author
Alex Mononen
committed
add compatibility with aws iam auth custom jdbc driver
1 parent 8537fce commit cbfbe8a

14 files changed

Lines changed: 1541 additions & 176 deletions

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Added
11+
- **AWS RDS IAM authentication**: Connections that authenticate with an IAM token instead of a stored password now work natively, for both PostgreSQL and MySQL. Tokens are minted with `aws rds generate-db-auth-token` (so SSO and role-chained profiles work as configured), cached for 13 minutes under their 15-minute lifetime, and re-minted per physical connection so long-lived pools keep working. TLS is forced for these connections, as RDS requires. Recognised from AWS Advanced JDBC Wrapper properties (`wrapperPlugins: "iam"`) or an `iam` auth model.
12+
- **Database username derivation**: When an IAM connection records no username, it is derived from the caller's AWS identity — either the per-developer role name (`<profile>-<user>`) or the assumed SSO session name.
13+
- **Custom driver support**: Drivers with an opaque id (a UUID, for instance) now route to the right native driver by falling back to the connection's `provider` and then the JDBC URL sub-protocol, including wrapped protocols such as `jdbc:aws-wrapper:postgresql://`. Previously any such driver fell through to the CLI fallback and failed.
14+
- New `OMNISQL_AWS_CLI_PATH` and `OMNISQL_IAM_TOKEN_TIMEOUT` environment variables.
15+
16+
### Fixed
17+
- **MySQL TLS options were read from the wrong place**: only top-level connection properties were checked, so the nested `properties` block written by the JSON workspace format was ignored. PostgreSQL already handled both.
18+
- **MySQL `ssl-mode` semantics**: `REQUIRED` now encrypts without validating the certificate chain, per MySQL's documented behaviour, and only the `VERIFY_CA`/`VERIFY_IDENTITY` modes verify it. Previously `REQUIRED` implied full verification, which fails against managed engines whose CA is not in the system trust store. `REQUIRED`/`DISABLED` spellings are also recognised now.
19+
- **Host, port and database are backfilled from the JDBC URL** when a connection config omits them.
20+
- Unsupported-driver errors now name the raw driver id and provider alongside the resolved driver, instead of only the resolved one.
21+
22+
### Internal
23+
- TLS resolution is now shared between the direct-query and pooled connection paths, which previously read different property locations and disagreed about what `require` meant.
24+
825
## [2.0.1] - 2026-04-20
926

1027
### Changed

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ Universal database MCP server — give AI assistants read/write access to your d
1919

2020
**Other databases**: Fall back to an external CLI configured via `OMNISQL_CLI_PATH`. Results vary by CLI.
2121

22+
**Custom drivers** wrapping any of the above are detected automatically — see [Custom and IAM-Authenticated Drivers](#custom-and-iam-authenticated-drivers).
23+
2224
## Features
2325

2426
- Reuses connections already configured in your local DB client workspace — no duplicate setup
2527
- Native query execution for PostgreSQL, MySQL/MariaDB, SQLite, SQL Server
28+
- AWS RDS IAM authentication, including custom drivers built on the AWS Advanced JDBC Wrapper
2629
- Connection pooling with configurable pool size and timeouts
2730
- Transaction support (BEGIN/COMMIT/ROLLBACK)
2831
- Query execution plan analysis (EXPLAIN)
@@ -104,6 +107,8 @@ Add to Cursor Settings > MCP Servers:
104107
| `OMNISQL_POOL_MAX` | Maximum connections per pool | `10` |
105108
| `OMNISQL_POOL_IDLE_TIMEOUT` | Idle connection timeout (ms) | `30000` |
106109
| `OMNISQL_POOL_ACQUIRE_TIMEOUT` | Connection acquire timeout (ms) | `10000` |
110+
| `OMNISQL_AWS_CLI_PATH` | Path to the AWS CLI (used for RDS IAM authentication) | `aws` |
111+
| `OMNISQL_IAM_TOKEN_TIMEOUT` | Timeout for minting an RDS IAM auth token (ms) | `20000` |
107112

108113
### Read-Only Mode
109114

@@ -206,6 +211,43 @@ Supports both configuration formats written by DBeaver-compatible DB clients:
206211

207212
Credentials are automatically decrypted from the workspace `credentials-config.json`.
208213

214+
## Custom and IAM-Authenticated Drivers
215+
216+
### Custom drivers
217+
218+
Native routing normally keys off the driver id (`postgres-jdbc`, `mysql8`). Custom drivers often use an
219+
opaque id instead — a UUID, say — which names no engine. Those connections are resolved by falling back
220+
to the connection's `provider` (`postgresql`, `mysql`, …) and then to the JDBC URL's sub-protocol,
221+
including wrapped ones such as `jdbc:aws-wrapper:postgresql://…`. A custom driver wrapping a supported
222+
engine therefore works with no extra configuration.
223+
224+
If an engine still cannot be identified, the resulting error names both the driver id and the provider
225+
so you can see what was missing.
226+
227+
### AWS RDS IAM authentication
228+
229+
Connections that authenticate with an RDS IAM token instead of a stored password are detected and
230+
handled automatically. Both shapes are recognised:
231+
232+
- **AWS Advanced JDBC Wrapper** drivers, which record `wrapperPlugins: "iam"` alongside `awsProfile`
233+
and `iamRegion`.
234+
- The DB client's own **AWS IAM auth models**.
235+
236+
For these connections OmniSQL:
237+
238+
1. Mints a token with `aws rds generate-db-auth-token` (via the AWS CLI, so SSO and role-chained
239+
profiles work as configured) and uses it as the password.
240+
2. Caches each token for 13 minutes, under its 15-minute lifetime, and re-mints per physical
241+
connection so long-lived pools keep working.
242+
3. Forces TLS, which RDS requires for IAM tokens.
243+
4. Resolves the database username from the connection when present. Where it is absent, the username is
244+
derived from your AWS identity: either the per-developer role name (`<profile>-<user>`) or the
245+
assumed SSO session name.
246+
247+
**Requirements**: the AWS CLI on `PATH` (or `OMNISQL_AWS_CLI_PATH`), a valid session for the
248+
connection's profile (`aws sso login --profile <profile>`), and network reachability to the endpoint.
249+
An expired SSO session produces an error naming the profile to re-authenticate.
250+
209251
## Development
210252

211253
```bash

src/auth/connection-props.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { DatabaseConnection } from '../types.js';
2+
3+
/**
4+
* Read a connection property, looking through both levels the workspace uses.
5+
*
6+
* The JSON workspace format keeps engine/driver properties nested under a
7+
* `properties` key inside the connection configuration, while the legacy XML
8+
* format keeps everything flat. Nested values win, since that is where
9+
* driver-specific configuration lives.
10+
*
11+
* Names are matched case-insensitively because casing varies by driver
12+
* (Postgres uses `sslmode`, MySQL uses `sslMode`). Nested objects are skipped
13+
* so a container never masks a scalar of the same name.
14+
*/
15+
export function readConnectionProp(
16+
connection: DatabaseConnection,
17+
...names: string[]
18+
): string | undefined {
19+
const props = (connection.properties ?? {}) as Record<string, unknown>;
20+
const nested = (props['properties'] as Record<string, unknown> | undefined) ?? {};
21+
22+
for (const name of names) {
23+
const wanted = name.toLowerCase();
24+
for (const source of [nested, props]) {
25+
const key = Object.keys(source).find((k) => k.toLowerCase() === wanted);
26+
if (key === undefined) {
27+
continue;
28+
}
29+
const value = source[key];
30+
if (value === undefined || value === null || typeof value === 'object') {
31+
continue;
32+
}
33+
const str = String(value);
34+
if (str.length > 0) {
35+
return str;
36+
}
37+
}
38+
}
39+
40+
return undefined;
41+
}

0 commit comments

Comments
 (0)