세분화된 액세스 제어 구성

이 페이지에서는 Spanner 데이터베이스의 세분화된 액세스 제어를 구성하는 방법을 설명합니다.

세분화된 액세스 제어에 대한 자세한 내용은 세분화된 액세스 제어 정보를 참조하세요.

세분화된 액세스 제어 구성에는 다음 단계가 포함됩니다.

  1. 데이터베이스 역할을 만들고 권한 부여

  2. 선택사항: 상속을 사용하여 역할 계층 구조 만들기

  3. Identity and Access Management(IAM) 주 구성원에게 데이터베이스 역할에 대한 액세스 권한 부여

  4. 사용자와 개발자에게 데이터베이스 역할 사용 시작 안내

세분화된 액세스 제어 사용자가 데이터베이스에 대한 쿼리, DML 또는 행 작업을 수행할 데이터베이스 역할을 지정해야 합니다.

시작하기 전에

세분화된 액세스 제어 사용자가 될 각 주 구성원에게 Cloud Spanner Viewer IAM 역할(roles/spanner.viewer)이 부여되었는지 확인합니다. 이 역할은 Google Cloud 콘솔에서 Spanner 리소스와 상호작용해야 하는 사용자를 위해 프로젝트 수준에서 권장됩니다.

자세한 내용은 주 구성원에게 권한 부여를 참조하세요.

데이터베이스 역할 만들기 및 권한 부여

데이터베이스 역할은 세분화된 액세스 권한 모음입니다. 데이터베이스마다 최대 100개의 데이터베이스 역할을 만들 수 있습니다.

데이터베이스의 역할 및 역할 계층 구조를 결정하고 DDL로 인코딩합니다. Spanner에서 다른 스키마 변경과 마찬가지로, 스키마 변경을 개별적으로 실행하는 대신 일괄적으로 실행하는 것이 좋습니다. 자세한 내용은 스키마 업데이트의 실행 빈도 제한을 참조하세요.

콘솔

데이터베이스 역할을 만들고 여기에 세분화된 액세스 권한을 부여하려면 다음 단계를 따르세요.

  1. Google Cloud console에서 인스턴스 페이지로 이동합니다.

    인스턴스

  2. 역할을 추가할 데이터베이스가 포함된 인스턴스를 선택합니다.

  3. 데이터베이스를 선택합니다.

  4. 개요 페이지에서 Spanner 스튜디오를 클릭합니다.

  5. Spanner 스튜디오 페이지에서 권한을 만들고 부여할 각 데이터베이스 역할에 다음 단계를 수행합니다.

    1. 역할을 만들려면 다음 문을 입력합니다.

      CREATE ROLE ROLE_NAME;
      

      아직은 제출을 클릭하지 마세요.

    2. 역할에 권한을 부여하려면 CREATE ROLE 문 다음 줄에 GRANT 문을 입력합니다.

      GRANT 문의 구문 세부정보는 GoogleSQL 데이터 정의 언어를 참조하세요. 권한에 대한 자세한 내용은 세분화된 액세스 제어 권한을 참조하세요.

      예를 들어 데이터베이스 역할 hr_manager에 테이블 employeescontractors에 대한 SELECT, INSERT, UPDATE 권한을 부여하려면 다음 문을 입력합니다.

      GoogleSQL

      GRANT SELECT, INSERT, UPDATE ON TABLE employees, contractors TO ROLE hr_manager;
      

      PostgreSQL

      GRANT SELECT, INSERT, UPDATE ON TABLE employees, contractors TO hr_manager;
      

      GRANT 문에 DDL 템플릿을 사용할 수 있습니다. 탐색기 창에서 권한을 부여할 역할로 이동합니다. 작업 보기를 클릭하고 이 역할에 대한 액세스 권한을 부여하려는 권한 유형을 선택합니다. 새 편집기 탭에 GRANT 템플릿 문이 채워집니다.

  6. 제출을 클릭합니다.

    DDL에 오류가 있으면 Google Cloud 콘솔이 오류를 반환합니다.

gcloud

데이터베이스 역할을 만들고 여기에 세분화된 액세스 권한을 부여하려면 CREATE ROLEGRANT 문과 함께 gcloud spanner databases ddl update 명령어를 사용합니다.

CREATE ROLEGRANT 문의 구문 세부정보는 GoogleSQL 데이터 정의 언어를 참조하세요.

예를 들어 다음 명령어를 사용하여 데이터베이스 역할을 만들고 하나 이상의 테이블에 대한 권한을 부여합니다.

GoogleSQL

gcloud spanner databases ddl update DATABASE_NAME --instance=INSTANCE_NAME \
--ddl='CREATE ROLE ROLE_NAME; GRANT PRIVILEGES ON TABLE TABLES TO ROLE ROLE_NAME;'

PostgreSQL

gcloud spanner databases ddl update DATABASE_NAME --instance=INSTANCE_NAME \
--ddl='CREATE ROLE ROLE_NAME; GRANT PRIVILEGES ON TABLE TABLES TO ROLE_NAME;'

다음을 바꿉니다.

  • PRIVILEGES는 세분화된 액세스 제어 권한의 쉼표로 구분된 목록입니다. 권한에 대한 자세한 내용은 세분화된 액세스 제어 권한을 참조하세요.

  • TABLES는 쉼표로 구분된 테이블 목록입니다.

employeescontractors 테이블에 대한 SELECT, INSERT, UPDATEhr 인스턴스의 hrdb1 데이터베이스에 있는 데이터베이스 역할 hr_analyst에 부여하려면 다음 문을 입력합니다.

GoogleSQL

gcloud spanner databases ddl update hrdb1 --instance=hr \
--ddl='CREATE ROLE hr_analyst; GRANT SELECT, INSERT, UPDATE ON TABLE employees, contractors TO ROLE hr_analyst;'

PostgreSQL

gcloud spanner databases ddl update hrdb1 --instance=hr \
--ddl='CREATE ROLE hr_analyst; GRANT SELECT, INSERT, UPDATE ON TABLE employees, contractors TO hr_analyst;'

클라이언트 라이브러리

모두 데이터베이스 역할을 만들고 삭제하는 코드 샘플입니다.

C++

void AddAndDropDatabaseRole(
    google::cloud::spanner_admin::DatabaseAdminClient client,
    std::string const& project_id, std::string const& instance_id,
    std::string const& database_id, std::string const& role_parent,
    std::string const& role_child) {
  google::cloud::spanner::Database database(project_id, instance_id,
                                            database_id);
  std::vector<std::string> grant_statements = {
      "CREATE ROLE " + role_parent,
      "GRANT SELECT ON TABLE Singers TO ROLE " + role_parent,
      "CREATE ROLE " + role_child,
      "GRANT ROLE " + role_parent + " TO ROLE " + role_child,
  };
  auto metadata =
      client.UpdateDatabaseDdl(database.FullName(), grant_statements).get();
  google::cloud::spanner_testing::LogUpdateDatabaseDdl(  //! TODO(#4758)
      client, database, metadata.status());              //! TODO(#4758)
  if (!metadata) throw std::move(metadata).status();
  std::cout << "Created roles " << role_parent << " and " << role_child
            << " and granted privileges\n";

  std::vector<std::string> revoke_statements = {
      "REVOKE ROLE " + role_parent + " FROM ROLE " + role_child,
      "DROP ROLE " + role_child,
  };
  metadata =
      client.UpdateDatabaseDdl(database.FullName(), revoke_statements).get();
  google::cloud::spanner_testing::LogUpdateDatabaseDdl(  //! TODO(#4758)
      client, database, metadata.status());              //! TODO(#4758)
  if (!metadata) throw std::move(metadata).status();
  std::cout << "Revoked privileges and dropped role " << role_child << "\n";
}

C#


using Google.Cloud.Spanner.Data;
using System.Threading.Tasks;

public class AddAndDropDatabaseRoleAsyncSample
{
    public async Task AddDatabaseRoleAsync(string projectId, string instanceId, string databaseId, string databaseRole)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
        string createRoleStatement = $"CREATE ROLE {databaseRole}";

        // Creates the given database role.
        using var connection = new SpannerConnection(connectionString);
        using var updateCmd = connection.CreateDdlCommand(createRoleStatement);
        await updateCmd.ExecuteNonQueryAsync();
    }

    public async Task DropDatabaseRoleAsync(string projectId, string instanceId, string databaseId, string databaseRole)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
        string deleteRoleStatement = $"DROP ROLE {databaseRole}";

        // Drops the given database role.
        using var connection = new SpannerConnection(connectionString);
        using var updateCmd = connection.CreateDdlCommand(deleteRoleStatement);
        await updateCmd.ExecuteNonQueryAsync();
    }
}

Go


import (
	"context"
	"io"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

func addAndDropDatabaseRole(w io.Writer, db string) error {
	ctx := context.Background()
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}
	defer adminClient.Close()

	// Set up database roles and membership. After database roles are created,
	// users can be granted roles by setting IAM policies.
	op, err := adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: db,
		Statements: []string{
			"CREATE ROLE parent",
			"GRANT SELECT ON TABLE Albums TO ROLE parent",
			"CREATE ROLE child",
			"GRANT ROLE parent TO ROLE child",
		},
	})
	if err != nil {
		return err
	}
	if err := op.Wait(ctx); err != nil {
		return err
	}

	// Delete role and membership.
	op, err = adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: db,
		Statements: []string{
			"REVOKE ROLE parent FROM ROLE child",
			"DROP ROLE child",
		},
	})
	if err != nil {
		return err
	}
	if err := op.Wait(ctx); err != nil {
		return err
	}
	return nil
}

Java


import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient;
import com.google.common.collect.ImmutableList;
import com.google.spanner.admin.database.v1.DatabaseName;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class AddAndDropDatabaseRole {

  static void addAndDropDatabaseRole() {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "my-project";
    String instanceId = "my-instance";
    String databaseId = "my-database";
    String parentRole = "parent_role";
    String childRole = "child_role";
    addAndDropDatabaseRole(projectId, instanceId, databaseId, parentRole, childRole);
  }

  static void addAndDropDatabaseRole(
      String projectId, String instanceId, String databaseId,
      String parentRole, String childRole) {
    try (Spanner spanner =
        SpannerOptions.newBuilder()
            .setProjectId(projectId)
            .build()
            .getService();
        DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) {
      System.out.println("Waiting for role create operation to complete...");
      databaseAdminClient.updateDatabaseDdlAsync(
              DatabaseName.of(projectId, instanceId, databaseId),
              ImmutableList.of(
                  String.format("CREATE ROLE %s", parentRole),
                  String.format("GRANT SELECT ON TABLE Albums TO ROLE %s", parentRole),
                  String.format("CREATE ROLE %s", childRole),
                  String.format("GRANT ROLE %s TO ROLE %s", parentRole, childRole)))
          .get(5, TimeUnit.MINUTES);
      System.out.printf(
          "Created roles %s and %s and granted privileges%n", parentRole, childRole);
      // Delete role and membership.
      System.out.println("Waiting for role revoke & drop operation to complete...");
      databaseAdminClient.updateDatabaseDdlAsync(
          DatabaseName.of(projectId, instanceId, databaseId),
          ImmutableList.of(
              String.format("REVOKE ROLE %s FROM ROLE %s", parentRole, childRole),
              String.format("DROP ROLE %s", childRole))).get(5, TimeUnit.MINUTES);
      System.out.printf("Revoked privileges and dropped role %s%n", childRole);
    } catch (ExecutionException | TimeoutException e) {
      System.out.printf(
          "Error: AddAndDropDatabaseRole failed with error message %s\n", e.getMessage());
      e.printStackTrace();
    } catch (InterruptedException e) {
      System.out.println(
          "Error: Waiting for AddAndDropDatabaseRole operation to finish was interrupted");
    }
  }
}

Node.js

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// const instanceId = 'my-instance';
// const databaseId = 'my-database';
// const projectId = 'my-project-id';

// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');

// creates a client
const spanner = new Spanner({
  projectId: projectId,
});

const databaseAdminClient = spanner.getDatabaseAdminClient();

async function addAndDropNewDatabaseRole() {
  // Creates a new user defined role and grant permissions
  try {
    const request = [
      'CREATE ROLE parent',
      'GRANT SELECT ON TABLE Singers TO ROLE parent',
      'CREATE ROLE child',
      'GRANT ROLE parent TO ROLE child',
    ];
    const [operation] = await databaseAdminClient.updateDatabaseDdl({
      database: databaseAdminClient.databasePath(
        projectId,
        instanceId,
        databaseId
      ),
      statements: request,
    });

    console.log('Waiting for operation to complete...');
    await operation.promise();

    console.log('Created roles child and parent and granted privileges');
  } catch (err) {
    console.error('ERROR:', err);
  }

  // Revoke permissions and drop child role.
  // A role can't be dropped until all its permissions are revoked.
  try {
    const request = ['REVOKE ROLE parent FROM ROLE child', 'DROP ROLE child'];
    const [operation] = await databaseAdminClient.updateDatabaseDdl({
      database: databaseAdminClient.databasePath(
        projectId,
        instanceId,
        databaseId
      ),
      statements: request,
    });

    console.log('Waiting for operation to complete...');
    await operation.promise();

    console.log('Revoked privileges and dropped role child');
  } catch (err) {
    console.error('ERROR:', err);
  } finally {
    // Close the spanner client when finished.
    // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient.
    spanner.close();
  }
}
addAndDropNewDatabaseRole();

PHP

use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient;
use Google\Cloud\Spanner\Admin\Database\V1\UpdateDatabaseDdlRequest;

/**
 * Adds and drops roles to the Singers table in the example database.
 * Example:
 * ```
 * add_drop_database_role($projectId, $instanceId, $databaseId);
 * ```
 *
 * @param string $projectId The Google Cloud project ID.
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function add_drop_database_role(string $projectId, string $instanceId, string $databaseId): void
{
    $databaseAdminClient = new DatabaseAdminClient();
    $databaseName = DatabaseAdminClient::databaseName($projectId, $instanceId, $databaseId);

    $request = new UpdateDatabaseDdlRequest([
        'database' => $databaseName,
        'statements' => [
            'CREATE ROLE new_parent',
            'GRANT SELECT ON TABLE Singers TO ROLE new_parent',
            'CREATE ROLE new_child',
            'GRANT ROLE new_parent TO ROLE new_child'
        ]
    ]);

    $operation = $databaseAdminClient->updateDatabaseDdl($request);

    printf('Waiting for create role and grant operation to complete...%s', PHP_EOL);
    $operation->pollUntilComplete();

    printf('Created roles %s and %s and granted privileges%s', 'new_parent', 'new_child', PHP_EOL);

    $request = new UpdateDatabaseDdlRequest([
        'database' => $databaseName,
        'statements' => [
            'REVOKE ROLE new_parent FROM ROLE new_child',
            'DROP ROLE new_child'
        ]
    ]);

    $operation = $databaseAdminClient->updateDatabaseDdl($request);

    printf('Waiting for revoke role and drop role operation to complete...%s', PHP_EOL);
    $operation->pollUntilComplete();

    printf('Revoked privileges and dropped role %s%s', 'new_child', PHP_EOL);
}

Python

# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"

from google.cloud.spanner_admin_database_v1.types import \
    spanner_database_admin

spanner_client = spanner.Client()
database_admin_api = spanner_client.database_admin_api

role_parent = "new_parent"
role_child = "new_child"

request = spanner_database_admin.UpdateDatabaseDdlRequest(
    database=database_admin_api.database_path(
        spanner_client.project, instance_id, database_id
    ),
    statements=[
        "CREATE ROLE {}".format(role_parent),
        "GRANT SELECT ON TABLE Singers TO ROLE {}".format(role_parent),
        "CREATE ROLE {}".format(role_child),
        "GRANT ROLE {} TO ROLE {}".format(role_parent, role_child),
    ],
)
operation = database_admin_api.update_database_ddl(request)

operation.result(OPERATION_TIMEOUT_SECONDS)
print(
    "Created roles {} and {} and granted privileges".format(role_parent, role_child)
)

request = spanner_database_admin.UpdateDatabaseDdlRequest(
    database=database_admin_api.database_path(
        spanner_client.project, instance_id, database_id
    ),
    statements=[
        "REVOKE ROLE {} FROM ROLE {}".format(role_parent, role_child),
        "DROP ROLE {}".format(role_child),
    ],
)
operation = database_admin_api.update_database_ddl(request)

operation.result(OPERATION_TIMEOUT_SECONDS)
print("Revoked privileges and dropped role {}".format(role_child))

Ruby

require "google/cloud/spanner"

def spanner_add_and_drop_database_role project_id:, instance_id:, database_id:
  # project_id  = "Your Google Cloud project ID"
  # instance_id = "Your Spanner instance ID"
  # database_id = "Your Spanner database ID"

  admin_client = Google::Cloud::Spanner::Admin::Database::V1::DatabaseAdmin::Client.new
  role_parent = "new_parent"
  role_child = "new_child"

  db_path = admin_client.database_path project: project_id, instance: instance_id, database: database_id

  job = admin_client.update_database_ddl database: db_path, statements: [
    "CREATE ROLE #{role_parent}",
    "GRANT SELECT ON TABLE Singers TO ROLE #{role_parent}",
    "CREATE ROLE #{role_child}",
    "GRANT ROLE #{role_parent} TO ROLE #{role_child}"
  ]

  job.wait_until_done!
  puts "Created roles #{role_parent} and #{role_child} and granted privileges"

  job = admin_client.update_database_ddl database: db_path, statements: [
    "REVOKE ROLE #{role_parent} FROM ROLE #{role_child}",
    "DROP ROLE #{role_child}"
  ]

  job.wait_until_done!
  puts "Revoked privileges and dropped role #{role_child}"
end

상속을 사용하여 역할 계층 구조 만들기

한 데이터베이스 역할을 다른 데이터베이스 역할에 부여하여 데이터베이스 역할의 계층 구조를 만들 수 있습니다. 하위 역할(구성원 역할)은 상위 역할의 권한을 상속합니다.

다른 데이터베이스 역할에 데이터베이스 역할을 부여하려면 다음 문을 사용합니다.

GoogleSQL

GRANT ROLE role1 TO ROLE role2;

PostgreSQL

GRANT role1 TO role2;

자세한 내용은 데이터베이스 역할 계층 및 상속을 참조하세요.

IAM 주 구성원에게 데이터베이스 역할에 대한 액세스 권한 부여

주 구성원이 데이터베이스 역할을 사용하여 Spanner 리소스에 액세스하려면 먼저 데이터베이스 역할에 대한 액세스 권한을 부여해야 합니다.

콘솔

IAM 주 구성원에게 데이터베이스 역할에 대한 액세스 권한을 부여하려면 다음 단계를 수행합니다.

  1. 데이터베이스 개요 페이지에서 정보 패널이 아직 열려 있지 않으면 정보 패널 표시를 클릭합니다.

  2. 주 구성원 추가를 클릭합니다.

  3. database_name에 액세스 권한 부여 패널의 주 구성원 추가에서 IAM 주 구성원을 하나 이상 지정합니다.

  4. 역할 할당역할 선택 메뉴에서 Cloud Spanner > Cloud Spanner의 세분화된 액세스 권한 사용자를 선택합니다.

    이 역할은 각각의 주 구성원에 대해 한 번만 부여해야 합니다. 주 구성원을 세분화된 액세스 제어 사용자로 만듭니다.

  5. 다른 역할 추가를 클릭합니다.

  6. 역할 선택 메뉴에서 Cloud Spanner > Cloud Spanner 데이터베이스 역할 사용자를 선택합니다.

  7. 다음 단계를 수행하여 부여할 역할을 지정하는 IAM 조건을 만듭니다.

    1. Cloud Spanner 데이터베이스 역할 사용자 역할 옆에 있는 IAM 조건 추가를 클릭합니다.

    2. 조건 추가 패널에서 조건의 제목 및 설명(선택사항)을 입력합니다.

      단일 데이터베이스 역할을 부여하는 경우 일반적으로 조건 제목에 역할 이름을 포함합니다. 여러 역할을 부여하는 경우 역할 집합에 대해 표시할 수 있습니다.

    3. 조건 편집기를 클릭합니다.

    4. 표현식 필드에 다음 코드를 입력합니다.

      resource.type == "spanner.googleapis.com/DatabaseRole" &&
      resource.name.endsWith("/ROLE")
      

      ROLE을 역할 이름으로 바꿉니다.

      또는 둘 이상의 역할에 대한 액세스 권한을 주 구성원에 부여하려면 다음 예시와 같이 or(||) 연산자를 사용하여 조건을 추가합니다.

      resource.type == "spanner.googleapis.com/DatabaseRole" &&
      (resource.name.endsWith("/ROLE1") || resource.name.endsWith("/ROLE2"))
      

      이 코드는 두 가지 역할을 부여합니다. ROLE1ROLE2를 역할 이름으로 바꿉니다. 셋 이상의 역할을 부여하려면 or 조건을 더 추가합니다.

      IAM에서 지원되는 모든 조건 표현식을 사용할 수 있습니다. 자세한 내용은 IAM 조건 개요를 참조하세요.

    5. 저장을 클릭합니다.

    6. 이전 패널로 돌아가서 역할 필드 옆에 있는 IAM 조건 열 아래에 조건이 표시되는지 확인합니다.

    7. 저장을 클릭합니다.

      정보 패널로 돌아가서 역할/주 구성원 아래에 정의된 각 조건에 대해 Cloud Spanner 데이터베이스 역할 사용자가 표시됩니다.

      조건 옆에 있는 괄호 안의 숫자는 해당 조건에 따라 데이터베이스 역할이 부여된 주 구성원 수를 나타냅니다. 펼치기 화살표를 클릭하여 주 구성원 목록을 볼 수 있습니다.

    8. 데이터베이스 역할 이름 또는 조건의 오류를 수정하거나 주 구성원에 추가로 데이터베이스 역할을 추가하려면 다음 단계를 따르세요.

      1. 원하는 조건을 나열하는 Cloud Spanner 데이터베이스 역할 사용자 항목을 펼칩니다.

      2. 주 구성원 옆에 있는 수정(연필) 아이콘을 클릭합니다.

      3. database_name에 대한 액세스 수정 패널에서 다음 중 하나를 수행합니다.

        • 다른 역할 추가를 클릭합니다.

        • 조건을 수정하려면 조건 이름 옆에 있는 수정(연필) 아이콘을 클릭합니다. 그런 다음 조건 수정 페이지에서 조건 편집기를 클릭하고 수정한 후 저장을 두 번 클릭합니다.

gcloud

IAM 주 구성원에게 데이터베이스 역할에 대한 액세스 권한을 부여하려면 다음 단계를 따르세요.

  1. 다음과 같이 gcloud spanner databases add-iam-policy-binding 명령어를 사용하여 주 구성원에게 세분화된 액세스 제어를 사용 설정합니다.

    gcloud spanner databases add-iam-policy-binding DATABASE_NAME \
    --instance=INSTANCE_NAME \
    --role=roles/spanner.fineGrainedAccessUser \
    --member=MEMBER_NAME \
    --condition=None
    
    • MEMBER_NAME은 주 구성원의 식별자입니다. user|group|serviceAccount:email 또는 domain:domain 형식이어야 합니다.

    • 이 명령어는 주 구성원을 세분화된 액세스 제어 사용자로 만듭니다. 이 명령어는 각각의 주 구성원에 대해 한 번만 제출하세요.

    • 성공하면 명령어에서 데이터베이스의 전체 정책을 출력합니다.

  2. 다음과 같이 gcloud spanner databases add-iam-policy-binding 명령어를 사용하여 데이터베이스 역할을 하나 이상 사용할 수 있는 권한을 부여합니다.

    gcloud spanner databases add-iam-policy-binding DATABASE_NAME \
    --instance=INSTANCE_NAME \
    --role=roles/spanner.databaseRoleUser \
    --member=MEMBER_NAME \
    --condition=CONDITION
    
    • MEMBER_NAME은 주 구성원의 식별자입니다. user|group|serviceAccount:email 또는 domain:domain 형식이어야 합니다.

    • CONDITION은 주 구성원에게 부여할 역할을 지정하는 IAM 조건 표현식입니다.

      CONDITION의 형식은 다음과 같습니다.

      --condition='expression=(resource.type == "spanner.googleapis.com/DatabaseRole" && resource.name.endsWith("/ROLE1")),title=TITLE,description=DESCRIPTION'
      

      또는 둘 이상의 역할에 대한 액세스 권한을 주 구성원에 부여하려면 다음 예시와 같이 or(||) 연산자를 사용하여 조건을 추가합니다.

      --condition='expression=(resource.type == "spanner.googleapis.com/DatabaseRole" && (resource.name.endsWith("/ROLE1") || resource.name.endsWith("/ROLE2"))),title=TITLE,description=DESCRIPTION'
      

      이 코드는 두 가지 역할을 부여합니다. ROLE1ROLE2를 역할 이름으로 바꿉니다. 셋 이상의 역할을 부여하려면 || 연산자를 사용하여 or 조건을 더 추가합니다.

      IAM에서 지원되는 모든 조건 표현식을 사용할 수 있습니다. 자세한 내용은 IAM 조건 개요를 참조하세요.

    성공하면 명령어에서 데이터베이스의 전체 정책을 출력합니다.

    다음 예시에서는 데이터베이스 역할 hr_rephr_manager을 주 구성원 jsmith@example.com에 부여합니다.

    gcloud spanner databases add-iam-policy-binding myDatabase \
      --instance=myInstance \
      --role=roles/spanner.databaseRoleUser \
      --member=user:jsmith@example.com \
      --condition='expression=(resource.type == "spanner.googleapis.com/DatabaseRole" && (resource.name.endsWith("/hr_rep") || resource.name.endsWith("/hr_manager"))),title=HR roles,description=Grant permissions on HR roles'
    

클라이언트 라이브러리

C++

void EnableFineGrainedAccess(
    google::cloud::spanner_admin::DatabaseAdminClient client,
    std::string const& project_id, std::string const& instance_id,
    std::string const& database_id, std::string const& iam_member,
    std::string const& database_role, std::string const& title) {
  google::cloud::spanner::Database database(project_id, instance_id,
                                            database_id);

  google::iam::v1::GetIamPolicyRequest request;
  request.set_resource(database.FullName());
  request.mutable_options()->set_requested_policy_version(3);
  auto policy = client.GetIamPolicy(request);
  if (!policy) throw std::move(policy).status();
  if (policy->version() < 3) policy->set_version(3);

  auto& binding = *policy->add_bindings();
  binding.set_role("roles/spanner.fineGrainedAccessUser");
  binding.add_members(iam_member);
  auto& condition = *binding.mutable_condition();
  condition.set_expression("resource.name.endsWith(\"/databaseRoles/" +
                           database_role + "\")");
  condition.set_title(title);

  auto new_policy =
      client.SetIamPolicy(database.FullName(), *std::move(policy));
  if (!new_policy) throw std::move(new_policy).status();
  std::cout << "Enabled fine-grained access in IAM. New policy has version "
            << new_policy->version() << "\n";
}

C#


using Google.Api.Gax;
using Google.Cloud.Iam.V1;
using Google.Cloud.Spanner.Admin.Database.V1;

public class EnableFineGrainedAccessSample
{
    public Policy EnableFineGrainedAccess(
        string projectId, string instanceId, string databaseId,
        string databaseRole, string iamMember)
    {
        var resourceName = new UnparsedResourceName($"projects/{projectId}/instances/{instanceId}/databases/{databaseId}");

        var client = new DatabaseAdminClientBuilder().Build();

        // Request policy version 3 as earlier versions do not support condition field in role binding.
        // For more information see https://cloud.google.com/iam/docs/policies#versions.

        GetIamPolicyRequest getIamPolicyRequest = new GetIamPolicyRequest
        {
            ResourceAsResourceName = resourceName,
            Options = new GetPolicyOptions
            {
                RequestedPolicyVersion = 3
            }
        };

        var policy = client.GetIamPolicy(getIamPolicyRequest);

        // Gives the given IAM member access to the all the database roles
        // with resource name ending in ../databaseRoles/{databaseRole}.
        // For more information see https://cloud.google.com/iam/docs/conditions-overview.
        Binding newBinding = new Binding
        {
            Role = "roles/spanner.fineGrainedAccessUser",
            Members = { iamMember },
            Condition = new Google.Type.Expr
            {
                Title = "DatabaseRoleBindingTitle",
                Expression = $"resource.name.endsWith('/databaseRoles/{databaseRole}')"
            }
        };

        policy.Bindings.Add(newBinding);
        if (policy.Version < 3)
        {
            policy.Version = 3;
        }
        SetIamPolicyRequest setIamPolicyRequest = new SetIamPolicyRequest
        {
            Policy = policy,
            ResourceAsResourceName = resourceName,
        };
        var updatedPolicy = client.SetIamPolicy(setIamPolicyRequest);
        return updatedPolicy;
    }
}

Go


import (
	"context"
	"fmt"
	"io"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	iampb "google.golang.org/genproto/googleapis/iam/v1"
	expr "google.golang.org/genproto/googleapis/type/expr"
)

func enableFineGrainedAccess(w io.Writer, db string, iamMember string, databaseRole string, title string) error {
	// iamMember = "user:alice@example.com"
	// databaseRole = "parent"
	// title = "condition title"
	ctx := context.Background()
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}
	defer adminClient.Close()

	policy, err := adminClient.GetIamPolicy(ctx, &iampb.GetIamPolicyRequest{
		Resource: db,
		Options: &iampb.GetPolicyOptions{
			// IAM conditions need at least version 3
			RequestedPolicyVersion: 3,
		},
	})
	if err != nil {
		return err
	}

	// IAM conditions need at least version 3
	if policy.Version < 3 {
		policy.Version = 3
	}
	policy.Bindings = append(policy.Bindings, []*iampb.Binding{
		{
			Role:    "roles/spanner.fineGrainedAccessUser",
			Members: []string{iamMember},
		},
		{
			Role:    "roles/spanner.databaseRoleUser",
			Members: []string{iamMember},
			Condition: &expr.Expr{
				Expression: fmt.Sprintf(`resource.name.endsWith("/databaseRoles/%s")`, databaseRole),
				Title:      title,
			},
		},
	}...)
	_, err = adminClient.SetIamPolicy(ctx, &iampb.SetIamPolicyRequest{
		Resource: db,
		Policy:   policy,
	})
	if err != nil {
		return err
	}

	fmt.Fprintf(w, "Enabled fine-grained access in IAM.\n")
	return nil
}

Java


import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient;
import com.google.common.collect.ImmutableList;
import com.google.iam.v1.Binding;
import com.google.iam.v1.GetIamPolicyRequest;
import com.google.iam.v1.GetPolicyOptions;
import com.google.iam.v1.Policy;
import com.google.iam.v1.SetIamPolicyRequest;
import com.google.spanner.admin.database.v1.DatabaseName;
import com.google.type.Expr;

public class EnableFineGrainedAccess {

  static void enableFineGrainedAccess() {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "my-project";
    String instanceId = "my-instance";
    String databaseId = "my-database";
    String iamMember = "user:alice@example.com";
    String role = "my-role";
    String title = "my-condition-title";
    enableFineGrainedAccess(projectId, instanceId, databaseId, iamMember, title, role);
  }

  static void enableFineGrainedAccess(
      String projectId,
      String instanceId,
      String databaseId,
      String iamMember,
      String title,
      String role) {
    try (Spanner spanner =
        SpannerOptions.newBuilder().setProjectId(projectId).build().getService();
        DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) {
      final GetPolicyOptions options =
          GetPolicyOptions.newBuilder().setRequestedPolicyVersion(3).build();
      final GetIamPolicyRequest getRequest =
          GetIamPolicyRequest.newBuilder()
              .setResource(DatabaseName.of(projectId, instanceId, databaseId).toString())
              .setOptions(options).build();
      final Policy policy = databaseAdminClient.getIamPolicy(getRequest);
      int policyVersion = policy.getVersion();
      // The policy in the response from getDatabaseIAMPolicy might use the policy version
      // that you specified, or it might use a lower policy version. For example, if you
      // specify version 3, but the policy has no conditional role bindings, the response
      // uses version 1. Valid values are 0, 1, and 3.
      if (policy.getVersion() < 3) {
        // conditional role bindings work with policy version 3
        policyVersion = 3;
      }

      Binding binding1 =
          Binding.newBuilder()
              .setRole("roles/spanner.fineGrainedAccessUser")
              .addAllMembers(ImmutableList.of(iamMember))
              .build();

      Binding binding2 =
          Binding.newBuilder()
              .setRole("roles/spanner.databaseRoleUser")
              .setCondition(
                  Expr.newBuilder().setDescription(title).setExpression(
                      String.format("resource.name.endsWith(\"/databaseRoles/%s\")", role)
                  ).setTitle(title).build())
              .addAllMembers(ImmutableList.of(iamMember))
              .build();
      ImmutableList<Binding> bindings =
          ImmutableList.<Binding>builder()
              .addAll(policy.getBindingsList())
              .add(binding1)
              .add(binding2)
              .build();
      Policy policyWithConditions =
          Policy.newBuilder()
              .setVersion(policyVersion)
              .setEtag(policy.getEtag())
              .addAllBindings(bindings)
              .build();
      final SetIamPolicyRequest setRequest =
          SetIamPolicyRequest.newBuilder()
              .setResource(DatabaseName.of(projectId, instanceId, databaseId).toString())
              .setPolicy(policyWithConditions).build();
      final Policy response = databaseAdminClient.setIamPolicy(setRequest);
      System.out.printf(
          "Enabled fine-grained access in IAM with version %d%n", response.getVersion());
    }
  }
}

Node.js

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// const instanceId = 'my-instance';
// const databaseId = 'my-database';
// const projectId = 'my-project-id';
// iamMember = 'user:alice@example.com';
// databaseRole = 'parent';
// title = 'condition title';
// Imports the Google Cloud Spanner client library
const {Spanner, protos} = require('@google-cloud/spanner');

// Instantiates a client
const spanner = new Spanner({
  projectId: projectId,
});

async function enableFineGrainedAccess() {
  // Gets a reference to a Cloud Spanner Database Admin Client object
  const databaseAdminClient = spanner.getDatabaseAdminClient();

  const [policy] = await databaseAdminClient.getIamPolicy({
    resource: databaseAdminClient.databasePath(
      projectId,
      instanceId,
      databaseId
    ),
    options: (protos.google.iam.v1.GetPolicyOptions = {
      requestedPolicyVersion: 3,
    }),
  });
  if (policy.version < 3) {
    policy.version = 3;
  }

  const newBinding = {
    role: 'roles/spanner.fineGrainedAccessUser',
    members: [`user:${iamMember}`],
    condition: {
      title: title,
      expression: `resource.name.endsWith("/databaseRoles/${databaseRole}")`,
    },
  };
  policy.bindings.push(newBinding);
  await databaseAdminClient.setIamPolicy({
    resource: databaseAdminClient.databasePath(
      projectId,
      instanceId,
      databaseId
    ),
    policy: policy,
  });
  // Requested Policy Version is Optional. The maximum policy version that will be used to format the policy.
  // Valid values are 0, 1, and 3. Requests specifying an invalid value will be rejected.
  const newPolicy = await databaseAdminClient.getIamPolicy({
    resource: databaseAdminClient.databasePath(
      projectId,
      instanceId,
      databaseId
    ),
    options: (protos.google.iam.v1.GetPolicyOptions = {
      requestedPolicyVersion: 3,
    }),
  });
  console.log(newPolicy);
}
enableFineGrainedAccess();

PHP

use \Google\Cloud\Iam\V1\Binding;
use \Google\Type\Expr;
use Google\Cloud\Iam\V1\GetIamPolicyRequest;
use Google\Cloud\Iam\V1\SetIamPolicyRequest;
use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient;

/**
 * Enable Fine Grained Access.
 * Example:
 * ```
 * enable_fine_grained_access($projectId, $instanceId, $databaseId, $iamMember, $databaseRole, $title);
 * ```
 *
 * @param string $projectId The Google cloud project ID
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 * @param string $iamMember The IAM member. Eg: `user:{emailid}`,
 *        `serviceAccount:{emailid}`, `group:{emailid}`, `domain:{domain}`
 * @param string $databaseRole The database role bound to
 *        the IAM member.
 * @param string $title Condition title.
 */
function enable_fine_grained_access(
    string $projectId,
    string $instanceId,
    string $databaseId,
    string $iamMember,
    string $databaseRole,
    string $title
): void {
    $adminClient = new DatabaseAdminClient();
    $resource = $adminClient->databaseName($projectId, $instanceId, $databaseId);
    $getIamPolicyRequest = (new GetIamPolicyRequest())
        ->setResource($resource);
    $policy = $adminClient->getIamPolicy($getIamPolicyRequest);

    // IAM conditions need at least version 3
    if ($policy->getVersion() != 3) {
        $policy->setVersion(3);
    }

    $binding = new Binding([
        'role' => 'roles/spanner.fineGrainedAccessUser',
        'members' => [$iamMember],
        'condition' => new Expr([
            'title' => $title,
            'expression' => sprintf("resource.name.endsWith('/databaseRoles/%s')", $databaseRole)
        ])
    ]);
    $policy->setBindings([$binding]);
    $setIamPolicyRequest = (new SetIamPolicyRequest())
        ->setResource($resource)
        ->setPolicy($policy);
    $adminClient->setIamPolicy($setIamPolicyRequest);

    printf('Enabled fine-grained access in IAM' . PHP_EOL);
}

Python

# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"
# iam_member = "user:alice@example.com"
# database_role = "new_parent"
# title = "condition title"

from google.iam.v1 import iam_policy_pb2, options_pb2, policy_pb2
from google.type import expr_pb2

spanner_client = spanner.Client()
database_admin_api = spanner_client.database_admin_api

# The policy in the response from getDatabaseIAMPolicy might use the policy version
# that you specified, or it might use a lower policy version. For example, if you
# specify version 3, but the policy has no conditional role bindings, the response
# uses version 1. Valid values are 0, 1, and 3.
request = iam_policy_pb2.GetIamPolicyRequest(
    resource=database_admin_api.database_path(
        spanner_client.project, instance_id, database_id
    ),
    options=options_pb2.GetPolicyOptions(requested_policy_version=3),
)
policy = database_admin_api.get_iam_policy(request=request)
if policy.version < 3:
    policy.version = 3

new_binding = policy_pb2.Binding(
    role="roles/spanner.fineGrainedAccessUser",
    members=[iam_member],
    condition=expr_pb2.Expr(
        title=title,
        expression=f'resource.name.endsWith("/databaseRoles/{database_role}")',
    ),
)

policy.version = 3
policy.bindings.append(new_binding)
set_request = iam_policy_pb2.SetIamPolicyRequest(
    resource=database_admin_api.database_path(
        spanner_client.project, instance_id, database_id
    ),
    policy=policy,
)
database_admin_api.set_iam_policy(set_request)

new_policy = database_admin_api.get_iam_policy(request=request)
print(
    f"Enabled fine-grained access in IAM. New policy has version {new_policy.version}"
)

Ruby

require "google/cloud/spanner"

def spanner_enable_fine_grained_access project_id:, instance_id:, database_id:, iam_member:, database_role:, title:
  # project_id  = "Your Google Cloud project ID"
  # instance_id = "Your Spanner instance ID"
  # database_id = "Your Spanner database ID"
  # iam_member = "user:alice@example.com"
  # database_role = "new_parent"
  # title = "condition title"

  admin_client = Google::Cloud::Spanner::Admin::Database::V1::DatabaseAdmin::Client.new
  db_path = admin_client.database_path project: project_id, instance: instance_id, database: database_id

  policy = admin_client.get_iam_policy resource: db_path, options: { requested_policy_version: 3 }

  policy.version = 3 if policy.version < 3

  binding = Google::Iam::V1::Binding.new(
    role: "roles/spanner.fineGrainedAccessUser",
    members: [iam_member],
    condition: Google::Type::Expr.new(
      title: title,
      expression: "resource.name.endsWith('/databaseRoles/#{database_role}')"
    )
  )

  policy.bindings << binding
  result = admin_client.set_iam_policy resource: db_path, policy: policy

  puts "Enabled fine-grained access in IAM."
end

사용자와 개발자에게 데이터베이스 역할 사용 시작 안내

초기 세분화된 액세스 제어 구성이 완료되면 사용자와 애플리케이션 개발자에게 데이터베이스 역할을 사용해야 한다고 알립니다.

  • 세분화된 액세스 제어 사용자는 Google Cloud 콘솔 또는 Google Cloud CLI를 통해 Spanner 데이터베이스에 액세스할 때 데이터베이스 역할을 지정해야 합니다.

  • 세분화된 액세스 제어를 사용하는 애플리케이션은 데이터베이스에 액세스할 때 데이터베이스 역할을 지정해야 합니다.

자세한 내용은 세분화된 액세스 제어로 데이터베이스 액세스를 참조하세요.

세분화된 액세스 제어로 주 구성원 전환

IAM 주 구성원을 데이터베이스 수준 액세스 제어에서 세분화된 액세스 제어로 전환하려면 다음 단계를 수행합니다.

  1. IAM 주 구성원에게 데이터베이스 역할에 대한 액세스 권한 부여에 설명된 대로 주 구성원에게 세분화된 액세스 제어를 사용 설정하고 모든 필수 데이터베이스 역할에 대한 액세스 권한을 부여합니다.

  2. 이 주 구성원으로 실행되는 모든 애플리케이션을 업데이트합니다. 클라이언트 라이브러리 메서드에 대한 호출에서 적절한 데이터베이스 역할을 지정합니다.

  3. 주 구성원의 모든 IAM 데이터베이스 수준 역할을 취소합니다. 이렇게 하면 주 구성원에 대한 액세스는 하나의 메서드로만 관리됩니다.

    예외: Google Cloud 콘솔에서 Spanner 리소스와 상호작용하려면 모든 사용자에게 roles/spanner.viewer IAM 역할이 있어야 합니다.

    IAM 데이터베이스 수준 역할을 취소하려면 데이터베이스 수준 권한 삭제의 안내를 따릅니다.

데이터베이스 역할 나열

데이터베이스와 연결된 데이터베이스 역할을 나열할 수 있습니다.

콘솔

데이터베이스 역할을 나열하려면 다음 쿼리를 데이터베이스의 Spanner 스튜디오 페이지에 입력합니다.

GoogleSQL

SELECT * FROM INFORMATION_SCHEMA.ROLES;

PostgreSQL

SELECT * FROM information_schema.enabled_roles;

응답에는 현재 역할과 현재 역할이 상속을 통해 사용할 수 있는 권한을 가진 역할이 포함됩니다. 모든 역할을 가져오려면 Google Cloud CLI 명령어를 사용합니다.

gcloud

필터링되지 않은 데이터베이스 역할 목록을 가져오려면 다음 명령어를 입력합니다. spanner.databaseRoles.list 권한이 필요합니다.

gcloud spanner databases roles list --database=DATABASE_NAME --instance=INSTANCE_NAME

클라이언트 라이브러리

C++

void ListDatabaseRoles(google::cloud::spanner_admin::DatabaseAdminClient client,
                       std::string const& project_id,
                       std::string const& instance_id,
                       std::string const& database_id) {
  google::cloud::spanner::Database database(project_id, instance_id,
                                            database_id);
  std::cout << "Database Roles are:\n";
  for (auto& role : client.ListDatabaseRoles(database.FullName())) {
    if (!role) throw std::move(role).status();
    std::cout << role->name() << "\n";
  }
}

C#


using Google.Api.Gax;
using Google.Cloud.Spanner.Admin.Database.V1;
using System;

public class ListDatabaseRolesSample
{
    public PagedEnumerable<ListDatabaseRolesResponse, DatabaseRole> ListDatabaseRoles(string projectId, string instanceId, string databaseId)
    {
        string parent = $"projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
        var client = DatabaseAdminClient.Create();
        PagedEnumerable<ListDatabaseRolesResponse, DatabaseRole> databaseRoles = client.ListDatabaseRoles(parent);
        foreach (var dbRole in databaseRoles)
        {
            Console.WriteLine($"Database Role: {dbRole.DatabaseRoleName}");
        }
        return databaseRoles;
    }
}

Go


import (
	"context"
	"fmt"
	"io"
	"strings"

	"google.golang.org/api/iterator"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

func listDatabaseRoles(w io.Writer, db string) error {
	ctx := context.Background()
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}
	defer adminClient.Close()

	iter := adminClient.ListDatabaseRoles(ctx, &adminpb.ListDatabaseRolesRequest{
		Parent: db,
	})
	rolePrefix := db + "/databaseRoles/"
	for {
		role, err := iter.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return err
		}
		if !strings.HasPrefix(role.Name, rolePrefix) {
			return fmt.Errorf("Role %v does not have prefix %v", role.Name, rolePrefix)
		}
		fmt.Fprintf(w, "%s\n", strings.TrimPrefix(role.Name, rolePrefix))
	}
	return nil
}

Java


import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient;
import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient.ListDatabaseRolesPage;
import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient.ListDatabaseRolesPagedResponse;
import com.google.spanner.admin.database.v1.DatabaseName;
import com.google.spanner.admin.database.v1.DatabaseRole;

public class ListDatabaseRoles {

  static void listDatabaseRoles() {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "my-project";
    String instanceId = "my-instance";
    String databaseId = "my-database";
    listDatabaseRoles(projectId, instanceId, databaseId);
  }

  static void listDatabaseRoles(String projectId, String instanceId, String databaseId) {
    try (Spanner spanner =
        SpannerOptions.newBuilder().setProjectId(projectId).build().getService();
        DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) {
      DatabaseName databaseName = DatabaseName.of(projectId, instanceId, databaseId);
      ListDatabaseRolesPagedResponse response
          = databaseAdminClient.listDatabaseRoles(databaseName);
      System.out.println("List of Database roles");
      for (ListDatabaseRolesPage page : response.iteratePages()) {
        for (DatabaseRole role : page.iterateAll()) {
          System.out.printf("Obtained role %s%n", role.getName());
        }
      }
    }
  }
}

Node.js

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// const instanceId = 'my-instance';
// const databaseId = 'my-database';
// const projectId = 'my-project-id';

// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');

// creates a client
const spanner = new Spanner({
  projectId: projectId,
});

const databaseAdminClient = spanner.getDatabaseAdminClient();

async function getDatabaseRoles() {
  // Fetching database roles
  const [databaseRoles] = await databaseAdminClient.listDatabaseRoles({
    parent: databaseAdminClient.databasePath(
      projectId,
      instanceId,
      databaseId
    ),
  });
  console.log(
    `Roles for Database: ${databaseAdminClient.databasePath(
      projectId,
      instanceId,
      databaseId
    )}`
  );
  databaseRoles.forEach(role => {
    console.log(`Role: ${role.name}`);
  });
}
getDatabaseRoles();

PHP

use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient;
use Google\Cloud\Spanner\Admin\Database\V1\ListDatabaseRolesRequest;

/**
 * List Database roles in the given database.
 * Example:
 * ```
 * list_database_roles($projectId, $instanceId, $databaseId);
 * ```
 *
 * @param string $projectId The Google cloud project ID
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function list_database_roles(
    string $projectId,
    string $instanceId,
    string $databaseId
): void {
    $adminClient = new DatabaseAdminClient();
    $resource = $adminClient->databaseName($projectId, $instanceId, $databaseId);
    $listDatabaseRolesRequest = (new ListDatabaseRolesRequest())
        ->setParent($resource);

    $roles = $adminClient->listDatabaseRoles($listDatabaseRolesRequest);
    printf('List of Database roles:' . PHP_EOL);
    foreach ($roles as $role) {
        printf($role->getName() . PHP_EOL);
    }
}

Python

# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"
from google.cloud.spanner_admin_database_v1.types import \
    spanner_database_admin

spanner_client = spanner.Client()
database_admin_api = spanner_client.database_admin_api

request = spanner_database_admin.ListDatabaseRolesRequest(
    parent=database_admin_api.database_path(
        spanner_client.project, instance_id, database_id
    )
)
# List database roles.
print("Database Roles are:")
for role in database_admin_api.list_database_roles(request):
    print(role.name.split("/")[-1])

Ruby

require "google/cloud/spanner"

def spanner_list_database_roles project_id:, instance_id:, database_id:
  # project_id  = "Your Google Cloud project ID"
  # instance_id = "Your Spanner instance ID"
  # database_id = "Your Spanner database ID"

  admin_client = Google::Cloud::Spanner::Admin::Database::V1::DatabaseAdmin::Client.new

  db_path = admin_client.database_path project: project_id, instance: instance_id, database: database_id

  result = admin_client.list_database_roles parent: db_path

  puts "List of Database roles:"
  result.each do |role|
    puts role.name
  end
end

데이터베이스 역할에 부여된 권한 보기

역할에 부여된 권한을 보려면 다음 쿼리를 실행합니다.

GoogleSQL

SELECT * FROM INFORMATION_SCHEMA.TABLE_PRIVILEGES WHERE grantee = 'ROLE_NAME';
SELECT * FROM INFORMATION_SCHEMA.COLUMN_PRIVILEGES WHERE grantee = 'ROLE_NAME';
SELECT * FROM INFORMATION_SCHEMA.CHANGE_STREAM_PRIVILEGES WHERE grantee = 'ROLE_NAME';

INFORMATION_SCHEMA.TABLE_PRIVILEGES는 테이블과 뷰 모두에 대한 권한을 반환합니다. TABLE_PRIVILEGESSELECT, INSERT, UPDATE 권한은 COLUMN_PRIVILEGES에도 표시됩니다.

PostgreSQL

SELECT * FROM information_schema.table_privileges WHERE grantee = 'ROLE_NAME';
SELECT * FROM information_schema.column_privileges WHERE grantee = 'ROLE_NAME';
SELECT * FROM information_schema.change_stream_privileges WHERE grantee = 'ROLE_NAME';

information_schema.table_privileges는 테이블과 뷰 모두에 대한 권한을 반환합니다. table_privilegesSELECT, INSERT, UPDATE 권한은 column_privileges에도 표시됩니다.

세분화된 액세스 제어 사용자 보기

세분화된 액세스 제어 사용자에 해당하는 주 구성원 목록을 보려면 다음 명령어를 실행합니다. 명령어를 실행하려면 프로젝트에 Cloud Asset API가 사용 설정되어 있고 cloudasset.assets.searchAllIamPolicies IAM 권한이 있어야 합니다.

gcloud asset search-all-iam-policies \
--scope=projects/PROJECT_NAME \
--query='roles=roles/spanner.fineGrainedAccessUser AND resource=//spanner.googleapis.com/projects/PROJECT_NAME/instances/INSTANCE_NAME/databases/DATABASE_NAME' \
--flatten=policy.bindings[].members[] \
--format='table(policy.bindings.members)'

출력은 다음과 비슷합니다.

MEMBERS
user:222larabrown@gmail.com
user:baklavainthebalkans@gmail.com
serviceAccount:cs-fgac-sa-1@cloud-spanner-demo.google.com.iam.gserviceaccount.com
serviceAccount:cs-fgac-sa-2@cloud-spanner-demo.google.com.iam.gserviceaccount.com

자세한 내용은 Google Cloud 프로젝트에서 API 사용 설정을 참조하세요.

데이터베이스 역할에 대한 액세스 권한이 있는 IAM 주 구성원 보기

특정 데이터베이스 역할에 대한 액세스 권한이 부여된 주 구성원 목록을 보려면 다음 명령어를 실행합니다. 명령어를 실행하려면 프로젝트에 Cloud Asset API가 사용 설정되어 있고 cloudasset.assets.searchAllIamPolicies IAM 권한이 있어야 합니다.

gcloud asset search-all-iam-policies \
--scope=projects/PROJECT_NAME \
--query='roles=roles/spanner.databaseRoleUser AND policy:"resource.name" AND policy:/ROLE_NAME AND resource=//spanner.googleapis.com/projects/PROJECT_NAME/instances/INSTANCE_NAME/databases/DATABASE_NAME' \
--flatten=policy.bindings[].members[] \
--format='table(policy.bindings.members)'

출력은 다음과 비슷합니다.

MEMBERS
222larabrown@gmail.com

주 구성원의 IAM 조건 보기

Cloud Spanner 데이터베이스 역할 사용자 역할을 주 구성원에게 부여할 때 지정된 IAM 조건 목록을 보려면 다음 명령어를 실행합니다.

gcloud asset search-all-iam-policies \
--scope=projects/PROJECT_NAME \
--query='roles=roles/spanner.databaseRoleUser AND policy:resource.name AND policy:"PRINCIPAL_IDENTIFIER" AND resource=//spanner.googleapis.com/projects/PROJECT_NAME/instances/INSTANCE_NAME/databases/DATABASE_NAME' \
--flatten=policy.bindings[] \
--format='table(policy.bindings.condition.expression)'

여기서 PRINCIPAL_IDENTIFIER은 다음과 같습니다.

  { user:user-account-name | serviceAccount:service-account-name }

예시 PRINCIPAL_IDENTIFIER:

user:222larabrown@gmail.com
serviceAccount:cs-fgac-sa-1@cloud-spanner-demo.google.com.iam.gserviceaccount.com

다음 샘플 출력에서는 조건 표현식 2개를 보여줍니다.

EXPRESSION
resource.type == "spanner.googleapis.com/DatabaseRole" &&
resource.name.endsWith("/hr_analyst")
resource.type == "spanner.googleapis.com/DatabaseRole" &&
resource.name.endsWith("/hr_manager")

IAM 정책에서 누락된 데이터베이스 역할 조건 확인

주 구성원에 데이터베이스 역할에 대한 액세스 권한을 부여한 후 각 IAM 바인딩에 지정된 조건이 있는지 확인하는 것이 좋습니다.

이 검사를 수행하려면 다음 명령어를 실행합니다.

gcloud asset search-all-iam-policies \
--scope=projects/PROJECT_NAME \
--query='roles:roles/spanner.databaseRoleUser AND resource=//spanner.googleapis.com/projects/PROJECT_NAME/instances/INSTANCE_NAME/databases/DATABASE_NAME'
--flatten=policy.bindings[].members[]

출력은 다음과 비슷합니다.

ROLE                              MEMBERS                         EXPRESSION
roles/spanner.databaseRoleUser    serviceAccount:cs-fgac-sa-1@...
roles/spanner.databaseRoleUser    serviceAccount:cs-fgac-sa-2@... resource.type == "spanner…"

첫 번째 결과에는 조건이 없으므로 이 바인딩의 주 구성원이 모든 데이터베이스 역할에 액세스할 수 있습니다.

데이터베이스 역할 삭제

데이터베이스 역할을 삭제하면 해당 역할에서 다른 역할의 멤버십이 자동으로 취소되고 다른 역할에서 이 역할의 멤버십도 취소됩니다.

데이터베이스 역할을 삭제하려면 먼저 다음을 수행해야 합니다.

  1. 역할에서 세분화된 모든 액세스 제어 권한을 취소합니다.
  2. 나중에 같은 이름으로 생성된 데이터베이스 역할에서 IAM 정책 바인딩을 상속하지 않도록 해당 역할을 참조하는 모든 IAM 정책 바인딩을 삭제합니다.

콘솔

데이터베이스 역할을 삭제하려면 다음 단계를 따르세요.

  1. 데이터베이스 개요 페이지에서 Spanner 스튜디오를 클릭합니다.

  2. 역할에서 권한을 취소하려면 REVOKE 문을 입력합니다.

    GoogleSQL

    REVOKE 문의 구문 세부정보는 GoogleSQL 데이터 정의 언어를 참조하세요. 권한에 대한 자세한 내용은 세분화된 액세스 제어 권한을 참조하세요.

    예를 들어 데이터베이스 역할 hr_manager에서 테이블 employeescontractors에 대한 SELECT, INSERT, UPDATE 권한을 취소하려면 다음 문을 입력합니다.

    REVOKE SELECT, INSERT, UPDATE ON TABLE employees, contractors FROM ROLE hr_manager;
    

    PostgreSQL

    REVOKE 문의 구문 세부정보는 PostgreSQL 데이터 정의 언어를 참조하세요. 권한에 대한 자세한 내용은 세분화된 액세스 제어 권한을 참조하세요.

    예를 들어 데이터베이스 역할 hr_manager에서 employeescontractors 테이블에 대한 SELECT, INSERT, UPDATE 권한을 취소하려면 다음 문을 입력합니다.

    REVOKE SELECT, INSERT, UPDATE ON TABLE employees, contractors FROM hr_manager;
    

    REVOKE 문에 DDL 템플릿을 사용할 수 있습니다. 탐색기 창에서 권한을 취소하려는 역할로 이동합니다. 작업 보기를 클릭하고 이 역할에 대한 액세스 권한을 취소하려는 권한 유형을 선택합니다. 새 편집기 탭에 REVOKE 템플릿 문이 채워집니다.

  3. 역할과 연결된 모든 IAM 조건을 삭제합니다.

    1. 정보 패널의 역할 목록에서 인접한 조건 제목이 있는 Cloud Spanner 데이터베이스 역할 사용자 역할을 찾은 다음 역할을 확장하여 역할에 대한 액세스 권한이 있는 주 구성원을 확인합니다.

    2. 주 구성원 중 하나에 대해 주 구성원 수정(연필) 아이콘을 클릭합니다.

    3. 액세스 수정 페이지에서 Cloud Spanner 데이터베이스 역할 사용자 역할 옆에 있는 역할 삭제(휴지통) 아이콘을 클릭합니다.

    4. 저장을 클릭합니다.

    5. 조건에 나열된 다른 주 구성원에 이전 세 단계를 반복합니다.

  4. 역할을 삭제하려면 Spanner 스튜디오 페이지로 이동하여 다음 문을 입력합니다.

    DROP ROLE ROLE_NAME;
    

  5. 제출을 클릭합니다.

gcloud

  1. 역할의 모든 권한을 취소한 다음 역할을 삭제하려면 다음과 같이 gcloud spanner databases ddl update 명령어를 사용합니다.

    GoogleSQL

    gcloud spanner databases ddl update DATABASE_NAME \
    --instance=INSTANCE_NAME \
    --ddl='REVOKE PERMISSIONS ON TABLE TABLE_NAME FROM ROLE ROLE_NAME; DROP ROLE ROLE_NAME;'
    

    PostgreSQL

    gcloud spanner databases ddl update DATABASE_NAME \
    --instance=INSTANCE_NAME \
    --ddl='REVOKE PERMISSIONS ON TABLE TABLE_NAME FROM ROLE_NAME; DROP ROLE ROLE_NAME;'
    

    유효한 PERMISSIONS 값은 SELECT, INSERT, UPDATE, DELETE입니다.

  2. 관련 IAM 조건을 삭제하려면 다음과 같이 gcloud spanner databases remove-iam-policy-binding 명령어를 사용합니다.

    gcloud spanner databases remove-iam-policy-binding DATABASE_NAME \
    --instance=INSTANCE_NAME \
    --role=ROLE_NAME \
    --member=MEMBER_NAME \
    --condition=CONDITION
    
    • MEMBER_NAME은 주 구성원의 식별자입니다. user|group|serviceAccount:email 또는 domain:domain 형식이어야 합니다.

    • CONDITION은 주 구성원에게 부여할 역할을 지정하는 IAM 조건 표현식입니다.

      CONDITION의 형식은 다음과 같습니다.

      --condition='expression=(resource.type == "spanner.googleapis.com/DatabaseRole" && (resource.name.endsWith("/ROLE1") || resource.name.endsWith("/ROLE2"))),title=TITLE,description=DESCRIPTION'
      

      전체 조건 사양은 권한을 부여한 명령어에 사용된 조건 사양과 제목 및 설명 등이 정확히 일치해야 합니다.

클라이언트 라이브러리

모두 데이터베이스 역할을 만들고 삭제하는 코드 샘플입니다.

C++

void AddAndDropDatabaseRole(
    google::cloud::spanner_admin::DatabaseAdminClient client,
    std::string const& project_id, std::string const& instance_id,
    std::string const& database_id, std::string const& role_parent,
    std::string const& role_child) {
  google::cloud::spanner::Database database(project_id, instance_id,
                                            database_id);
  std::vector<std::string> grant_statements = {
      "CREATE ROLE " + role_parent,
      "GRANT SELECT ON TABLE Singers TO ROLE " + role_parent,
      "CREATE ROLE " + role_child,
      "GRANT ROLE " + role_parent + " TO ROLE " + role_child,
  };
  auto metadata =
      client.UpdateDatabaseDdl(database.FullName(), grant_statements).get();
  google::cloud::spanner_testing::LogUpdateDatabaseDdl(  //! TODO(#4758)
      client, database, metadata.status());              //! TODO(#4758)
  if (!metadata) throw std::move(metadata).status();
  std::cout << "Created roles " << role_parent << " and " << role_child
            << " and granted privileges\n";

  std::vector<std::string> revoke_statements = {
      "REVOKE ROLE " + role_parent + " FROM ROLE " + role_child,
      "DROP ROLE " + role_child,
  };
  metadata =
      client.UpdateDatabaseDdl(database.FullName(), revoke_statements).get();
  google::cloud::spanner_testing::LogUpdateDatabaseDdl(  //! TODO(#4758)
      client, database, metadata.status());              //! TODO(#4758)
  if (!metadata) throw std::move(metadata).status();
  std::cout << "Revoked privileges and dropped role " << role_child << "\n";
}

C#


using Google.Cloud.Spanner.Data;
using System.Threading.Tasks;

public class AddAndDropDatabaseRoleAsyncSample
{
    public async Task AddDatabaseRoleAsync(string projectId, string instanceId, string databaseId, string databaseRole)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
        string createRoleStatement = $"CREATE ROLE {databaseRole}";

        // Creates the given database role.
        using var connection = new SpannerConnection(connectionString);
        using var updateCmd = connection.CreateDdlCommand(createRoleStatement);
        await updateCmd.ExecuteNonQueryAsync();
    }

    public async Task DropDatabaseRoleAsync(string projectId, string instanceId, string databaseId, string databaseRole)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
        string deleteRoleStatement = $"DROP ROLE {databaseRole}";

        // Drops the given database role.
        using var connection = new SpannerConnection(connectionString);
        using var updateCmd = connection.CreateDdlCommand(deleteRoleStatement);
        await updateCmd.ExecuteNonQueryAsync();
    }
}

Go


import (
	"context"
	"io"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

func addAndDropDatabaseRole(w io.Writer, db string) error {
	ctx := context.Background()
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}
	defer adminClient.Close()

	// Set up database roles and membership. After database roles are created,
	// users can be granted roles by setting IAM policies.
	op, err := adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: db,
		Statements: []string{
			"CREATE ROLE parent",
			"GRANT SELECT ON TABLE Albums TO ROLE parent",
			"CREATE ROLE child",
			"GRANT ROLE parent TO ROLE child",
		},
	})
	if err != nil {
		return err
	}
	if err := op.Wait(ctx); err != nil {
		return err
	}

	// Delete role and membership.
	op, err = adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: db,
		Statements: []string{
			"REVOKE ROLE parent FROM ROLE child",
			"DROP ROLE child",
		},
	})
	if err != nil {
		return err
	}
	if err := op.Wait(ctx); err != nil {
		return err
	}
	return nil
}

Java


import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient;
import com.google.common.collect.ImmutableList;
import com.google.spanner.admin.database.v1.DatabaseName;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class AddAndDropDatabaseRole {

  static void addAndDropDatabaseRole() {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "my-project";
    String instanceId = "my-instance";
    String databaseId = "my-database";
    String parentRole = "parent_role";
    String childRole = "child_role";
    addAndDropDatabaseRole(projectId, instanceId, databaseId, parentRole, childRole);
  }

  static void addAndDropDatabaseRole(
      String projectId, String instanceId, String databaseId,
      String parentRole, String childRole) {
    try (Spanner spanner =
        SpannerOptions.newBuilder()
            .setProjectId(projectId)
            .build()
            .getService();
        DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) {
      System.out.println("Waiting for role create operation to complete...");
      databaseAdminClient.updateDatabaseDdlAsync(
              DatabaseName.of(projectId, instanceId, databaseId),
              ImmutableList.of(
                  String.format("CREATE ROLE %s", parentRole),
                  String.format("GRANT SELECT ON TABLE Albums TO ROLE %s", parentRole),
                  String.format("CREATE ROLE %s", childRole),
                  String.format("GRANT ROLE %s TO ROLE %s", parentRole, childRole)))
          .get(5, TimeUnit.MINUTES);
      System.out.printf(
          "Created roles %s and %s and granted privileges%n", parentRole, childRole);
      // Delete role and membership.
      System.out.println("Waiting for role revoke & drop operation to complete...");
      databaseAdminClient.updateDatabaseDdlAsync(
          DatabaseName.of(projectId, instanceId, databaseId),
          ImmutableList.of(
              String.format("REVOKE ROLE %s FROM ROLE %s", parentRole, childRole),
              String.format("DROP ROLE %s", childRole))).get(5, TimeUnit.MINUTES);
      System.out.printf("Revoked privileges and dropped role %s%n", childRole);
    } catch (ExecutionException | TimeoutException e) {
      System.out.printf(
          "Error: AddAndDropDatabaseRole failed with error message %s\n", e.getMessage());
      e.printStackTrace();
    } catch (InterruptedException e) {
      System.out.println(
          "Error: Waiting for AddAndDropDatabaseRole operation to finish was interrupted");
    }
  }
}

Node.js

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// const instanceId = 'my-instance';
// const databaseId = 'my-database';
// const projectId = 'my-project-id';

// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');

// creates a client
const spanner = new Spanner({
  projectId: projectId,
});

const databaseAdminClient = spanner.getDatabaseAdminClient();

async function addAndDropNewDatabaseRole() {
  // Creates a new user defined role and grant permissions
  try {
    const request = [
      'CREATE ROLE parent',
      'GRANT SELECT ON TABLE Singers TO ROLE parent',
      'CREATE ROLE child',
      'GRANT ROLE parent TO ROLE child',
    ];
    const [operation] = await databaseAdminClient.updateDatabaseDdl({
      database: databaseAdminClient.databasePath(
        projectId,
        instanceId,
        databaseId
      ),
      statements: request,
    });

    console.log('Waiting for operation to complete...');
    await operation.promise();

    console.log('Created roles child and parent and granted privileges');
  } catch (err) {
    console.error('ERROR:', err);
  }

  // Revoke permissions and drop child role.
  // A role can't be dropped until all its permissions are revoked.
  try {
    const request = ['REVOKE ROLE parent FROM ROLE child', 'DROP ROLE child'];
    const [operation] = await databaseAdminClient.updateDatabaseDdl({
      database: databaseAdminClient.databasePath(
        projectId,
        instanceId,
        databaseId
      ),
      statements: request,
    });

    console.log('Waiting for operation to complete...');
    await operation.promise();

    console.log('Revoked privileges and dropped role child');
  } catch (err) {
    console.error('ERROR:', err);
  } finally {
    // Close the spanner client when finished.
    // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient.
    spanner.close();
  }
}
addAndDropNewDatabaseRole();

PHP

use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient;
use Google\Cloud\Spanner\Admin\Database\V1\UpdateDatabaseDdlRequest;

/**
 * Adds and drops roles to the Singers table in the example database.
 * Example:
 * ```
 * add_drop_database_role($projectId, $instanceId, $databaseId);
 * ```
 *
 * @param string $projectId The Google Cloud project ID.
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function add_drop_database_role(string $projectId, string $instanceId, string $databaseId): void
{
    $databaseAdminClient = new DatabaseAdminClient();
    $databaseName = DatabaseAdminClient::databaseName($projectId, $instanceId, $databaseId);

    $request = new UpdateDatabaseDdlRequest([
        'database' => $databaseName,
        'statements' => [
            'CREATE ROLE new_parent',
            'GRANT SELECT ON TABLE Singers TO ROLE new_parent',
            'CREATE ROLE new_child',
            'GRANT ROLE new_parent TO ROLE new_child'
        ]
    ]);

    $operation = $databaseAdminClient->updateDatabaseDdl($request);

    printf('Waiting for create role and grant operation to complete...%s', PHP_EOL);
    $operation->pollUntilComplete();

    printf('Created roles %s and %s and granted privileges%s', 'new_parent', 'new_child', PHP_EOL);

    $request = new UpdateDatabaseDdlRequest([
        'database' => $databaseName,
        'statements' => [
            'REVOKE ROLE new_parent FROM ROLE new_child',
            'DROP ROLE new_child'
        ]
    ]);

    $operation = $databaseAdminClient->updateDatabaseDdl($request);

    printf('Waiting for revoke role and drop role operation to complete...%s', PHP_EOL);
    $operation->pollUntilComplete();

    printf('Revoked privileges and dropped role %s%s', 'new_child', PHP_EOL);
}

Python

# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"

from google.cloud.spanner_admin_database_v1.types import \
    spanner_database_admin

spanner_client = spanner.Client()
database_admin_api = spanner_client.database_admin_api

role_parent = "new_parent"
role_child = "new_child"

request = spanner_database_admin.UpdateDatabaseDdlRequest(
    database=database_admin_api.database_path(
        spanner_client.project, instance_id, database_id
    ),
    statements=[
        "CREATE ROLE {}".format(role_parent),
        "GRANT SELECT ON TABLE Singers TO ROLE {}".format(role_parent),
        "CREATE ROLE {}".format(role_child),
        "GRANT ROLE {} TO ROLE {}".format(role_parent, role_child),
    ],
)
operation = database_admin_api.update_database_ddl(request)

operation.result(OPERATION_TIMEOUT_SECONDS)
print(
    "Created roles {} and {} and granted privileges".format(role_parent, role_child)
)

request = spanner_database_admin.UpdateDatabaseDdlRequest(
    database=database_admin_api.database_path(
        spanner_client.project, instance_id, database_id
    ),
    statements=[
        "REVOKE ROLE {} FROM ROLE {}".format(role_parent, role_child),
        "DROP ROLE {}".format(role_child),
    ],
)
operation = database_admin_api.update_database_ddl(request)

operation.result(OPERATION_TIMEOUT_SECONDS)
print("Revoked privileges and dropped role {}".format(role_child))

Ruby

require "google/cloud/spanner"

def spanner_add_and_drop_database_role project_id:, instance_id:, database_id:
  # project_id  = "Your Google Cloud project ID"
  # instance_id = "Your Spanner instance ID"
  # database_id = "Your Spanner database ID"

  admin_client = Google::Cloud::Spanner::Admin::Database::V1::DatabaseAdmin::Client.new
  role_parent = "new_parent"
  role_child = "new_child"

  db_path = admin_client.database_path project: project_id, instance: instance_id, database: database_id

  job = admin_client.update_database_ddl database: db_path, statements: [
    "CREATE ROLE #{role_parent}",
    "GRANT SELECT ON TABLE Singers TO ROLE #{role_parent}",
    "CREATE ROLE #{role_child}",
    "GRANT ROLE #{role_parent} TO ROLE #{role_child}"
  ]

  job.wait_until_done!
  puts "Created roles #{role_parent} and #{role_child} and granted privileges"

  job = admin_client.update_database_ddl database: db_path, statements: [
    "REVOKE ROLE #{role_parent} FROM ROLE #{role_child}",
    "DROP ROLE #{role_child}"
  ]

  job.wait_until_done!
  puts "Revoked privileges and dropped role #{role_child}"
end

추가 정보