Search code examples
node.jspostgresqlgoogle-app-enginegoogle-cloud-sqlgoogle-secret-manager

How to connect google app engine with secret manager to Postgres?


I'm trying to connect from a nodejs/typescript codebase running in GAE to a GCP managed Postgres db via secret manager.

I'm getting:

 Error: 7 PERMISSION_DENIED: Permission denied on resource project DATABASE_USER.   

when I run it in GAE.


Solution

  • First, make sure you've granted Secrets Access to the GAE service account in IAM.

    Then use the following code example to get your ENV vars from secret manager.

    import * as path from 'path';
    import {SecretManagerServiceClient} from '@google-cloud/secret-manager';
    import deasync from 'deasync';
    
    
    require('dotenv').config();
    
    const SnakeNamingStrategy =
      require('typeorm-naming-strategies').SnakeNamingStrategy;
    
    const googleProjectId = process.env.GOOGLE_CLOUD_PROJECT;
    const isInGAE = googleProjectId !== undefined;
    const isLocalUsingCloudProxy = process.env.USE_CLOUD_SQL_AUTH_PROXY !== undefined;
    
    
    const getSecretSync = deasync((name: string, cb:any) => {
      const c = new SecretManagerServiceClient();
      
      c.accessSecretVersion({name: c.secretVersionPath(googleProjectId, name, "latest")}).then(([secret]) => {
        cb(null, secret.payload.data.toString());
      }).catch((err) => {
        cb(err);
      });
    });
    
      
    
    let config = {
      type: 'postgres',
      host: process.env.DATABASE_HOST || 'localhost',
      port: parseInt(process.env.DATABASE_PORT, 10) || 5432,
      username: process.env.DATABASE_USER,
      password: process.env.DATABASE_PASSWORD,
      database: process.env.DATABASE_NAME,
      synchronize: false,
      logging: false,
      subscribers: [path.join(__dirname, '..', 'subscribers', '*.{ts,js}')],
      entities: [path.join(__dirname, '..', 'models', '*.{ts,js}')],
      migrations: [path.join(__dirname, '..', 'migrations', '*.{ts,js}')],
      cli: {
        entitiesDir: [path.join(__dirname, '..', 'models', '*.{ts,js}')],
        migrationsDir: [path.join(__dirname, '..', 'migrations', '*.{ts,js}')],
      },
      namingStrategy: new SnakeNamingStrategy(),
    };
    
    
    if (isInGAE || isLocalUsingCloudProxy) {
      config.username = getSecretSync("DATABASE_USER");
      config.password = getSecretSync("DATABASE_PASSWORD");
      config.database = getSecretSync("DATABASE_NAME");
      config.host = isInGAE ? "/cloudsql/" + getSecretSync("DATABASE_HOST") : 'localhost';
      config.port = isInGAE ? parseInt(getSecretSync("DATABASE_PORT"), 10) : 5432;
      console.log("dbuser", config.username);
    
    }
    

    It's important to note that the DATABASE_HOST should be in the form of the "Connection Name" on the SQL tab, like project-id:us-central1:db-name