Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(SQLAdminFetcher): Support base AuthClient for auth and loginAuth #238

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
ruyadorno marked this conversation as resolved.
Show resolved Hide resolved
- **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 @@ -65,14 +65,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 @@ -102,7 +102,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 @@ -121,7 +121,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 @@ -147,21 +147,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 @@ -172,8 +180,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 @@ -205,7 +213,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 @@ -227,12 +235,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 @@ -260,13 +269,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 @@ -284,15 +293,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 @@ -305,24 +314,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 @@ -354,9 +381,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,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requires the following to be resolved in order to remove the casting:

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 => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests aren't really 'tested' as this suite may be refactored shortly; they only exist for coverage purposes.

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