Skip to content

Commit

Permalink
feat: Add support to an auth config (#238)
Browse files Browse the repository at this point in the history
Adds a new `auth` property to the connector constructor that
can be used by `SQLAdminFetcher` to extend from or provide
support to a custom auth object defined by the user.

Fixes: #200
  • Loading branch information
danielbankhead committed Nov 13, 2023
1 parent 5d81f75 commit e1a50a5
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 45 deletions.
91 changes: 59 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ alternative to the [Cloud SQL Auth Proxy](https://cloud.google.com/sql/docs/mysq
while providing the following benefits:

- **IAM Authorization:** uses IAM permissions to control who/what can connect to
your Cloud SQL instances
your Cloud SQL instances
- **Improved Security:** uses robust, updated TLS 1.3 encryption and identity
verification between the client connector and the server-side proxy,
independent of the database protocol.
verification between the client connector and the server-side proxy,
independent of the database protocol.
- **Convenience:** removes the requirement to use and distribute SSL
certificates, as well as manage firewalls or source/destination IP addresses.
certificates, as well as manage firewalls or source/destination IP addresses.
- (optionally) **IAM DB Authentication:** provides support for
[Cloud SQL’s automatic IAM DB AuthN][iam-db-authn] feature.

Expand Down Expand Up @@ -90,14 +90,14 @@ const {Pool} = pg;
const connector = new Connector();
const clientOpts = await connector.getOptions({
instanceConnectionName: 'my-project:region:my-instance',
ipType: 'PUBLIC',
ipType: 'PUBLIC',
});
const pool = new Pool({
...clientOpts,
user: 'my-user',
password: 'my-password',
database: 'db-name',
max: 5
max: 5,
});
const {rows} = await pool.query('SELECT NOW()');
console.table(rows); // prints returned time value from server
Expand Down Expand Up @@ -127,7 +127,7 @@ const pool = await mysql.createPool({
database: 'db-name',
});
const conn = await pool.getConnection();
const [result] = await conn.query( `SELECT NOW();`);
const [result] = await conn.query(`SELECT NOW();`);
console.table(result); // prints returned time value from server

await pool.end();
Expand All @@ -146,7 +146,7 @@ const {Connector} = require('@google-cloud/cloud-sql-connector');
const connector = new Connector();
const clientOpts = await connector.getTediousOptions({
instanceConnectionName: process.env.SQLSERVER_CONNECTION_NAME,
ipType: 'PUBLIC'
ipType: 'PUBLIC',
});
const connection = new Connection({
// Please note that the `server` property here is not used and is only defined
Expand All @@ -172,21 +172,29 @@ const connection = new Connection({
port: 9999,
database: 'my-database',
},
})
});

connection.connect(err => {
if (err) { throw err; }
if (err) {
throw err;
}
let result;
const req = new Request('SELECT GETUTCDATE()', (err) => {
if (err) { throw err; }
})
req.on('error', (err) => { throw err; });
req.on('row', (columns) => { result = columns; });
const req = new Request('SELECT GETUTCDATE()', err => {
if (err) {
throw err;
}
});
req.on('error', err => {
throw err;
});
req.on('row', columns => {
result = columns;
});
req.on('requestCompleted', () => {
console.table(result);
});
connection.execSql(req);
})
});

connection.close();
connector.close();
Expand All @@ -197,8 +205,8 @@ connector.close();
The Cloud SQL Connector for Node.js can be used to connect to Cloud SQL
instances using both public and private IP addresses, as well as
[Private Service Connect](https://cloud.google.com/vpc/docs/private-service-connect)
(PSC). Specifying which IP address type to connect to can be configured within
`getOptions` through the `ipType` argument.
(PSC). Specifying which IP address type to connect to can be configured within
`getOptions` through the `ipType` argument.

By default, connections will be configured to `'PUBLIC'` and connect over
public IP, to configure connections to use an instance's private IP,
Expand Down Expand Up @@ -230,7 +238,7 @@ const clientOpts = await connector.getOptions({
#### Example on how to use `IpAddressTypes` in TypeScript

```js
import { Connector, IpAddressTypes } from '@google-cloud/cloud-sql-connector';
import {Connector, IpAddressTypes} from '@google-cloud/cloud-sql-connector';
const clientOpts = await connector.getOptions({
instanceConnectionName: 'my-project:region:my-instance',
ipType: IpAddressTypes.PSC,
Expand All @@ -252,12 +260,13 @@ automatic IAM database authentication with `getOptions` through the
```js
const clientOpts = await connector.getOptions({
instanceConnectionName: 'my-project:region:my-instance',
authType: 'IAM'
authType: 'IAM',
});
```

When configuring a connection for IAM authentication, the `password` argument
can be omitted and the `user` argument should be formatted as follows:

> Postgres: For an IAM user account, this is the user's email address.
> For a service account, it is the service account's email without the
> `.gserviceaccount.com` domain suffix.
Expand Down Expand Up @@ -285,13 +294,13 @@ const {Pool} = pg;
const connector = new Connector();
const clientOpts = await connector.getOptions({
instanceConnectionName: 'my-project:region:my-instance',
authType: 'IAM'
authType: 'IAM',
});
const pool = new Pool({
...clientOpts,
user: 'test-sa@test-project.iam',
database: 'db-name',
max: 5
max: 5,
});
const {rows} = await pool.query('SELECT NOW()');
console.table(rows); // prints returned time value from server
Expand All @@ -309,15 +318,15 @@ import {Connector} from '@google-cloud/cloud-sql-connector';
const connector = new Connector();
const clientOpts = await connector.getOptions({
instanceConnectionName: 'my-project:region:my-instance',
authType: 'IAM'
authType: 'IAM',
});
const pool = await mysql.createPool({
...clientOpts,
user: 'test-sa',
database: 'db-name',
});
const conn = await pool.getConnection();
const [result] = await conn.query( `SELECT NOW();`);
const [result] = await conn.query(`SELECT NOW();`);
console.table(result); // prints returned time value from server

await pool.end();
Expand All @@ -330,24 +339,42 @@ For TypeScript users, the `AuthTypes` type can be imported and used directly
for automatic IAM database authentication.

```js
import { AuthTypes, Connector } from '@google-cloud/cloud-sql-connector';
import {AuthTypes, Connector} from '@google-cloud/cloud-sql-connector';
const clientOpts = await connector.getOptions({
instanceConnectionName: 'my-project:region:my-instance',
authType: AuthTypes.IAM,
});
```

## Using With `Google Auth Library: Node.js Client` Credentials

One can use [`google-auth-library`](https://github.com/googleapis/google-auth-library-nodejs/) credentials
with this library by providing an `AuthClient` or `GoogleAuth` instance to the `Connector`.

```sh
npm install google-auth-library
```

```js
import {GoogleAuth} from 'google-auth-library';
import {Connector} from '@google-cloud/cloud-sql-connector';

const connector = new Connector({
auth: new GoogleAuth(),
});
```

## Additional customization via Environment Variables

It is possible to change some of the library default behavior via environment
variables. Here is a quick reference to supported values and their effect:

- `GOOGLE_APPLICATION_CREDENTIALS`: If defined the connector will use this
file as a custom credential files to authenticate to Cloud SQL APIs. Should be
a path to a JSON file. You can
[find more on how to get a valid credentials file here][credentials-json-file].
file as a custom credential files to authenticate to Cloud SQL APIs. Should be
a path to a JSON file. You can
[find more on how to get a valid credentials file here][credentials-json-file].
- `GOOGLE_CLOUD_QUOTA_PROJECT`: Used to set a custom quota project to Cloud SQL
APIs when defined.
APIs when defined.

## Support policy

Expand Down Expand Up @@ -379,9 +406,9 @@ update as soon as possible to an actively supported LTS version.
Google's client libraries support legacy versions of Node.js runtimes on a
best-efforts basis with the following warnings:

* Legacy versions are not tested in continuous integration.
* Some security patches and features cannot be backported.
* Dependencies cannot be kept up-to-date.
- Legacy versions are not tested in continuous integration.
- Some security patches and features cannot be backported.
- Dependencies cannot be kept up-to-date.

### Release cadence

Expand Down
9 changes: 7 additions & 2 deletions src/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

import tls from 'node:tls';
import {AuthClient, GoogleAuth} from 'google-auth-library';
import {CloudSQLInstance} from './cloud-sql-instance';
import {getSocket} from './socket';
import {IpAddressTypes} from './ip-addresses';
Expand Down Expand Up @@ -147,6 +148,7 @@ class CloudSQLInstanceMap extends Map {
}

interface ConnectorOptions {
auth?: GoogleAuth<AuthClient> | AuthClient;
sqlAdminAPIEndpoint?: string;
}

Expand All @@ -156,9 +158,12 @@ export class Connector {
private readonly instances: CloudSQLInstanceMap;
private readonly sqlAdminFetcher: SQLAdminFetcher;

constructor({sqlAdminAPIEndpoint}: ConnectorOptions = {}) {
constructor(opts: ConnectorOptions = {}) {
this.instances = new CloudSQLInstanceMap();
this.sqlAdminFetcher = new SQLAdminFetcher({sqlAdminAPIEndpoint});
this.sqlAdminFetcher = new SQLAdminFetcher({
loginAuth: opts.auth,
sqlAdminAPIEndpoint: opts.sqlAdminAPIEndpoint,
});
}

// Connector.getOptions is a method that accepts a Cloud SQL instance
Expand Down
33 changes: 23 additions & 10 deletions src/sqladmin-fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import {GoogleAuth} from 'google-auth-library';
import {AuthClient, GoogleAuth} from 'google-auth-library';
import {sqladmin_v1beta4} from '@googleapis/sqladmin';
import {instance as gaxios} from 'gaxios';
const {Sqladmin} = sqladmin_v1beta4;
Expand Down Expand Up @@ -69,21 +69,31 @@ function cleanGaxiosConfig() {
}

export interface SQLAdminFetcherOptions {
loginAuth?: GoogleAuth;
loginAuth?: GoogleAuth<AuthClient> | AuthClient;
sqlAdminAPIEndpoint?: string;
}

export class SQLAdminFetcher {
private readonly client: sqladmin_v1beta4.Sqladmin;
private readonly auth: GoogleAuth;
private readonly auth: GoogleAuth<AuthClient>;

constructor({loginAuth, sqlAdminAPIEndpoint}: SQLAdminFetcherOptions = {}) {
const auth = new GoogleAuth({
scopes: ['https://www.googleapis.com/auth/sqlservice.admin'],
});
let auth: GoogleAuth<AuthClient>;

if (loginAuth instanceof GoogleAuth) {
auth = new GoogleAuth({
scopes: ['https://www.googleapis.com/auth/sqlservice.admin'],
});
} else {
auth = new GoogleAuth({
authClient: loginAuth, // either an `AuthClient` or undefined
scopes: ['https://www.googleapis.com/auth/sqlservice.admin'],
});
}

this.client = new Sqladmin({
rootUrl: sqlAdminAPIEndpoint,
auth,
auth: auth as GoogleAuth,
userAgentDirectives: [
{
product: 'cloud-sql-nodejs-connector',
Expand All @@ -92,11 +102,14 @@ export class SQLAdminFetcher {
],
});

this.auth =
loginAuth ||
new GoogleAuth({
if (loginAuth instanceof GoogleAuth) {
this.auth = loginAuth;
} else {
this.auth = new GoogleAuth({
authClient: loginAuth, // either an `AuthClient` or undefined
scopes: ['https://www.googleapis.com/auth/sqlservice.login'],
});
}
}

async getInstanceMetadata({
Expand Down
14 changes: 13 additions & 1 deletion test/sqladmin-fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import {resolve} from 'node:path';
import t from 'tap';
import nock from 'nock';
import {GoogleAuth} from 'google-auth-library';
import {GoogleAuth, OAuth2Client} from 'google-auth-library';
import {sqladmin_v1beta4} from '@googleapis/sqladmin';
import {SQLAdminFetcher} from '../src/sqladmin-fetcher';
import {InstanceConnectionInfo} from '../src/instance-connection-info';
Expand Down Expand Up @@ -71,6 +71,18 @@ const mockRequest = (
});
};

t.test('constructor', async t => {
await t.test('should support GoogleAuth for `loginAuth`', async t => {
const auth = new GoogleAuth();
t.ok(new SQLAdminFetcher({loginAuth: auth}));
});

await t.test('should support `AuthClient` for `loginAuth`', async t => {
const authClient = new OAuth2Client();
t.ok(new SQLAdminFetcher({loginAuth: authClient}));
});
});

t.test('getInstanceMetadata', async t => {
setupCredentials(t);
const instanceConnectionInfo: InstanceConnectionInfo = {
Expand Down

0 comments on commit e1a50a5

Please sign in to comment.