IAM 데이터베이스 인증을 사용하여 데이터베이스에 로그인

이 페이지에서는 사용자 및 서비스 계정이 Cloud SQL IAM 데이터베이스 인증을 사용하여 Cloud SQL 데이터베이스에 로그인하는 방법을 설명합니다. 자세한 내용은 Cloud SQL IAM 데이터베이스 인증 개요를 참조하세요.

시작하기 전에

  • IAM 데이터베이스 인증을 사용하도록 인스턴스를 구성합니다. 자세한 내용은 IAM 데이터베이스 인증을 위한 인스턴스 구성을 참조하세요.
  • 데이터베이스에 IAM 사용자 또는 서비스 계정을 추가합니다. 자세한 내용은 데이터베이스에 IAM 사용자 또는 서비스 계정 추가를 참조하세요.
  • 이 태스크를 수행하려면 사용자 계정에 roles/cloudsql.instanceUser IAM 역할을 추가합니다. 이 역할은 필요한 Cloud SQL IAM 권한 cloudsql.instances.login이 포함된 사전 정의된 역할입니다. 데이터베이스 인스턴스에 로그인하려면 이 권한이 필요합니다.

IAM 데이터베이스 인증을 사용하여 로그인

사용자 또는 애플리케이션을 대신해서 Cloud SQL 인스턴스 인증을 자동으로 처리하도록 Cloud SQL 커넥터를 구성할 수 있습니다. 커넥터에는 Cloud SQL 인증 프록시, 자바 커넥터, Python 커넥터가 포함됩니다. 이러한 커넥터는 모두 자동 IAM 데이터베이스 인증을 지원합니다. 자동 IAM 데이터베이스 인증에 Cloud SQL 커넥터를 사용할 때 커넥터 시작을 위해 사용되는 IAM 계정은 데이터베이스에 인증을 수행하는 것과 동일한 계정이어야 합니다. 자세한 내용은 Cloud SQL 인증 프록시 인증 옵션을 참조하세요.

자동 IAM 데이터베이스 인증을 사용하여 로그인하려면 다음 안내를 따르세요.

Cloud SQL 인증 프록시

  1. -enable_iam_login 플래그로 Cloud SQL 인증 프록시를 시작합니다.

    다음을 바꿉니다.

    • INSTANCE_CONNECTION_NAME: Cloud SQL 인스턴스를 식별하기 위한 연결 문자열입니다. 이 문자열을 찾는 방법에 대한 자세한 내용은 Cloud SQL 인증 프록시 인증 옵션을 참조하세요.
    ./cloud_sql_proxy -enable_iam_login -instances=INSTANCE_CONNECTION_NAME=tcp:5432
    

    프록시를 시작하는 방법에 대한 자세한 내용은 Cloud SQL 인증 프록시 시작을 참조하세요.

  2. 클라이언트의 Cloud SQL 인증 프록시 연결이 준비되면 IAM 사용자 또는 서비스 계정의 이메일 주소를 데이터베이스 사용자 이름으로 사용합니다.

    서비스 계정의 경우 .gserviceaccount.com 도메인 서픽스가 없는 서비스 계정의 이메일입니다.

    Cloud SQL 인증 프록시에 연결하는 방법에 대한 상세 설명은 Cloud SQL 인증 프록시를 사용하여 연결을 참조하세요.

자바 JDBC

private static final String CONNECTION_NAME = System.getenv("POSTGRES_IAM_CONNECTION_NAME");
private static final String DB_NAME = System.getenv("POSTGRES_DB");
private static final String DB_USER = System.getenv("POSTGRES_IAM_USER");
  // Set up URL parameters
  String jdbcURL = String.format("jdbc:postgresql:///%s", DB_NAME);
  Properties connProps = new Properties();
  connProps.setProperty("user", DB_USER);
  // Password must be set to a nonempty value to bypass driver validation errors
  connProps.setProperty("password", "password");
  connProps.setProperty("sslmode", "disable");
  connProps.setProperty("socketFactory", "com.google.cloud.sql.postgres.SocketFactory");
  connProps.setProperty("cloudSqlInstance", CONNECTION_NAME);
  connProps.setProperty("enableIamAuth", "true");

  // Initialize connection pool
  HikariConfig config = new HikariConfig();
  config.setJdbcUrl(jdbcURL);
  config.setDataSourceProperties(connProps);
  config.setConnectionTimeout(10000); // 10s

  this.connectionPool = new HikariDataSource(config);

자바 R2DBC

private static final String CONNECTION_NAME = System.getenv("POSTGRES_IAM_CONNECTION_NAME");
private static final String DB_NAME = System.getenv("POSTGRES_DB");
private static final String DB_USER = System.getenv("POSTGRES_IAM_USER");
  // Set up ConnectionFactoryOptions
  ConnectionFactoryOptions options = ConnectionFactoryOptions.builder()
      .option(DRIVER, "gcp")
      .option(PROTOCOL, "postgresql")
      // Password must be set to a nonempty value to bypass driver validation errors
      .option(PASSWORD, "password")
      .option(USER, DB_USER)
      .option(DATABASE, DB_NAME)
      .option(HOST, CONNECTION_NAME)
      .option(ENABLE_IAM_AUTH, true)
      .build();

  // Initialize connection pool
  ConnectionFactory connectionFactory = ConnectionFactories.get(options);
  ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration
      .builder(connectionFactory)
      .build();

  this.connectionPool = new ConnectionPool(configuration);

Python

# The Cloud SQL Python Connector can be used along with SQLAlchemy using the
# 'creator' argument to 'create_engine'
def init_connection_engine() -> sqlalchemy.engine.Engine:
    def getconn() -> pg8000.dbapi.Connection:
        conn: pg8000.dbapi.Connection = connector.connect(
            os.environ["POSTGRES_IAM_CONNECTION_NAME"],
            "pg8000",
            user=os.environ["POSTGRES_IAM_USER"],
            db=os.environ["POSTGRES_DB"],
            enable_iam_auth=True,
        )
        return conn

    engine = sqlalchemy.create_engine(
        "postgresql+pg8000://",
        creator=getconn,
    )
    engine.dialect.description_encoding = None
    return engine

수동 IAM 데이터베이스 인증을 사용하여 로그인

참고: Cloud SQL 인증 프록시, 자바 커넥터, Python 커넥터를 사용하려면 자동 로그인을 참조하세요.

사용자 또는 애플리케이션은 Google Cloud에서 액세스 토큰을 수동으로 요청하고 데이터베이스에 제공해 IAM을 사용하여 데이터베이스에 인증할 수 있습니다. Cloud SDK를 사용하면 데이터베이스에 로그인하는 데 사용되는 Cloud SQL API 범위와 함께 OAuth 2.0 토큰을 명시적으로 요청할 수 있습니다. 수동 IAM 데이터베이스 인증을 사용하여 데이터베이스 사용자로 로그인할 때는 이메일 주소를 사용자 이름으로, 액세스 토큰을 비밀번호로 사용합니다. 이 메서드를 데이터베이스에 직접 연결하거나 Cloud SQL 커넥터와 함께 사용할 수 있습니다.

이 단계에서는 Google Cloud에 인증하고 액세스 토큰을 요청한 후 토큰을 IAM 데이터베이스 사용자의 비밀번호로 전달하여 데이터베이스에 연결합니다.

Cloud SDK를 사용하여 이 토큰을 생성하고 로그인하려면 다음 안내를 따르세요.

gcloud

  1. Google Cloud에 인증합니다.

    User

    gcloud auth login을 사용하여 IAM에 인증합니다. 자세한 내용은 사용자 계정으로 승인을 참조하세요.

    서비스 계정

    gcloud auth activate-service-account를 사용하여 IAM에 인증합니다. 자세한 내용은 서비스 계정을 사용하여 승인을 참조하세요.

  2. 액세스 토큰을 요청하고 클라이언트로 로그인합니다.

    다음을 바꿉니다.

    • HOSTNAME: 인스턴스의 IP 주소입니다.
    • USERNAME: 사용자 이름은 호스트 머신에 연결하는 데 사용할 이메일 주소입니다. 서비스 계정의 경우 .gserviceaccount.com 도메인 서픽스가 없는 서비스 계정의 이메일입니다.
    • DATABASE_NAME: 연결할 데이터베이스의 이름입니다.

    PGPASSWORD=$(gcloud auth print-access-token) psql --host=HOSTNAME 
    --username=USERNAME
    --dbname=DATABASE_NAME

자바

서비스 계정

  1. 서비스 계정 키 만들기의 단계에 따라 사용자 인증 정보를 만듭니다.
  2. 다음 코드를 사용하여 액세스 토큰 문자열을 생성한 후 이 토큰을 비밀번호로 사용하여 데이터베이스에 인증합니다.

    import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
    import com.google.api.services.sqladmin.SQLAdminScopes;
    // ...
    GoogleCredential credentials = GoogleCredential.fromStream(new FileInputStream("PATH_TO_SERVICE_ACCT_KEY")).createScoped(Collections.singleton(SQLAdminScopes.SQLSERVICE_ADMIN));
    String accessToken = credentials.getAccessToken();
    
  3. 연결 풀을 초기화합니다. 이전 단계의 액세스 토큰을 비밀번호로 사용하고 이메일을 사용자 이름으로 사용합니다.

    웹 애플리케이션의 컨텍스트에서 이 스니펫을 보려면 GitHub의 README를 참조하세요.

    // Note: For Java users, the Cloud SQL JDBC Socket Factory can provide authenticated connections
    // which is preferred to using the Cloud SQL Proxy with Unix sockets.
    // See https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory for details.
    
    // The configuration object specifies behaviors for the connection pool.
    HikariConfig config = new HikariConfig();
    
    // The following URL is equivalent to setting the config options below:
    // jdbc:mysql:///<DB_NAME>?cloudSqlInstance=<CLOUD_SQL_CONNECTION_NAME>&
    // socketFactory=com.google.cloud.sql.mysql.SocketFactory&user=<DB_USER>&password=<DB_PASS>
    // See the link below for more info on building a JDBC URL for the Cloud SQL JDBC Socket Factory
    // https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory#creating-the-jdbc-url
    
    // Configure which instance and what database user to connect with.
    config.setJdbcUrl(String.format("jdbc:mysql:///%s", DB_NAME));
    config.setUsername(DB_USER); // e.g. "root", "mysql"
    config.setPassword(DB_PASS); // e.g. "my-password"
    
    config.addDataSourceProperty("socketFactory", "com.google.cloud.sql.mysql.SocketFactory");
    config.addDataSourceProperty("cloudSqlInstance", CLOUD_SQL_CONNECTION_NAME);
    
    // The ipTypes argument can be used to specify a comma delimited list of preferred IP types
    // for connecting to a Cloud SQL instance. The argument ipTypes=PRIVATE will force the
    // SocketFactory to connect with an instance's associated private IP.
    config.addDataSourceProperty("ipTypes", "PUBLIC,PRIVATE");
    
    // ... Specify additional connection properties here.
    // ...
    
    // Initialize the connection pool using the configuration object.
    DataSource pool = new HikariDataSource(config);
  4. 내 인스턴스에 연결

    // Using a try-with-resources statement ensures that the connection is always released back
    // into the pool at the end of the statement (even if an error occurs)
    try (Connection conn = pool.getConnection()) {
    
      // PreparedStatements can be more efficient and project against injections.
      String stmt = "INSERT INTO votes (time_cast, candidate) VALUES (?, ?);";
      try (PreparedStatement voteStmt = conn.prepareStatement(stmt);) {
        voteStmt.setTimestamp(1, now);
        voteStmt.setString(2, team);
    
        // Finally, execute the statement. If it fails, an error will be thrown.
        voteStmt.execute();
      }
    } catch (SQLException ex) {
      // If something goes wrong, handle the error in this section. This might involve retrying or
      // adjusting parameters depending on the situation.
      // ...
    }

Python

서비스 계정

  1. 서비스 계정 키 만들기의 단계에 따라 사용자 인증 정보를 만듭니다.
  2. 다음 코드를 사용하여 액세스 토큰 문자열을 생성합니다. 그런 후 이 토큰을 비밀번호로 사용하여 데이터베이스에 인증합니다.

    from google.oauth2 import service_account
    SCOPES = ['https://www.googleapis.com/auth/sqlservice.admin']
    SERVICE_ACCOUNT_FILE = '/PATH_TO_SERVICE_ACCT_KEY'
    credentials = service_account.Credentials.from_service_account_file( SERVICE_ACCOUNT_FILE, scopes=SCOPES)
    access_token = credentials.token
    
  3. 연결 풀을 초기화합니다. 이전 단계의 액세스 토큰을 비밀번호로 사용하고 이메일을 사용자 이름으로 사용합니다.

    웹 애플리케이션의 컨텍스트에서 이 스니펫을 보려면 GitHub의 README를 참조하세요.

    # Remember - storing secrets in plaintext is potentially unsafe. Consider using
    # something like https://cloud.google.com/secret-manager/docs/overview to help keep
    # secrets secret.
    db_user = os.environ["DB_USER"]
    db_pass = os.environ["DB_PASS"]
    db_name = os.environ["DB_NAME"]
    db_host = os.environ["DB_HOST"]
    
    # Extract host and port from db_host
    host_args = db_host.split(":")
    db_hostname, db_port = host_args[0], int(host_args[1])
    
    pool = sqlalchemy.create_engine(
        # Equivalent URL:
        # mysql+pymysql://<db_user>:<db_pass>@<db_host>:<db_port>/<db_name>
        sqlalchemy.engine.url.URL.create(
            drivername="mysql+pymysql",
            username=db_user,  # e.g. "my-database-user"
            password=db_pass,  # e.g. "my-database-password"
            host=db_hostname,  # e.g. "127.0.0.1"
            port=db_port,  # e.g. 3306
            database=db_name,  # e.g. "my-database-name"
        ),
        **db_config
    )
  4. 내 인스턴스에 연결

    # Preparing a statement before hand can help protect against injections.
    stmt = sqlalchemy.text(
        "INSERT INTO votes (time_cast, candidate)" " VALUES (:time_cast, :candidate)"
    )
    try:
        # Using a with statement ensures that the connection is always released
        # back into the pool at the end of statement (even if an error occurs)
        with db.connect() as conn:
            conn.execute(stmt, time_cast=time_cast, candidate=team)
    except Exception as e:
        # If something goes wrong, handle the error in this section. This might
        # involve retrying or adjusting parameters depending on the situation.
        # ...

다음 단계