詳細なアクセス制御を構成する

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

このページでは、Cloud Spanner データベースの詳細なアクセス制御を構成する方法について説明します。

詳細なアクセス制御については、詳細なアクセス制御についてをご覧ください。

詳細なアクセス制御の設定手順は次のとおりです。

  1. データベース ロールを作成し、権限を付与します

  2. 省略可: 継承されたロールの階層を作成します

  3. Identity and Access Management(IAM)プリンシパルにデータベース ロールへのアクセス権を付与する

きめ細かいアクセス制御ユーザーは、データベースに対してクエリ、DML、または行オペレーションを実行するために、データベースの役割を指定する必要があります。

データベース ロールを作成し権限を付与する

データベース ロールは、詳細なアクセス権限のコレクションです。データベースごとに最大 100 個のデータベース ロールを作成できます。

データベース内の役割と階層を決定し、DDL でエンコードします。Spanner の他のスキーマの変更と同様に、スキーマの変更は個別にではなくバッチで発行することを強くおすすめします。詳細については、スキーマの更新頻度を制限するをご覧ください。

コンソール

データベース ロールを作成し、詳細なアクセス権限を付与するには、次の手順に沿って操作します。

  1. Google Cloud コンソールの [インスタンス] ページに移動します。

    Instances

  2. ロールを追加するデータベースを含むインスタンスを選択します。

  3. データベースを選択します。

  4. [概要] ページで、[DDL の書き込み] をクリックします。

  5. [DDL ステートメントの書き込み] ページで、権限を作成して権限を付与するデータベース ロールごとに、次の操作を行います。

    1. ロールを作成するには、次のステートメントを入力します。

      CREATE ROLE ROLE_NAME;
      

      まだ [Submit] はクリックしないでください。

    2. ロールを付与するには、CREATE ROLE ステートメントの次の行に GRANT ステートメントを入力します。

      GRANT ステートメントの構文の詳細については、GoogleSQL データ定義言語をご覧ください。権限の詳細については、きめ細かいアクセス制御権限をご覧ください。

      たとえば、テーブル employeescontractors に対する SELECTINSERTUPDATE をデータベース ロール hr_manager に付与するには、次のステートメントを入力します。

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

      DDL テンプレートは GRANT ステートメントに使用できます。[DDL テンプレート] プルダウン メニューで、[データベースのロール] を選択し、[ロールの権限を付与] でテンプレートを選択します。

  6. [送信] をクリックします。

    DDL にエラーがある場合は、Google Cloud コンソールからエラーが返されます。

gcloud

データベース ロールを作成してきめ細かなアクセス権を付与するには、CREATE ROLE ステートメントと GRANT ステートメントを指定して、gcloud spanner databases ddl update コマンドを使用します。

CREATE ROLE ステートメントと GRANT ステートメントの構文の詳細については、GoogleSQL データ定義言語をご覧ください。

たとえば、次のコマンドを使用してデータベース ロールを作成し、1 つ以上のテーブルに対する権限を付与することができます。

gcloud spanner databases ddl update DATABASE_NAME --instance=INSTANCE_NAME \
--ddl='CREATE ROLE ROLE_NAME; GRANT PRIVILEGES ON TABLE TABLES TO ROLE ROLE_NAME;'
  • PRIVILEGES は、詳細なアクセス制御の権限のカンマ区切りリストです。権限の詳細については、きめ細かいアクセス制御権限をご覧ください。

  • TABLES は、テーブルのカンマ区切りのリストです。

たとえば、SELECTINSERTおよびUPDATEテーブルemployeescontractorsデータベース ロールhr_analystデータベースhrdb1インスタンス内hr次のステートメントを入力します。

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;'

クライアント ライブラリ

これらのコードサンプルは、データベース ロールの作成と削除の両方を行います。

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.api.gax.longrunning.OperationFuture;
import com.google.cloud.spanner.DatabaseAdminClient;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import com.google.common.collect.ImmutableList;
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
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 = "my-new-parent-role";
    String childRole = "my-new-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())  {
      final DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient();
      OperationFuture<Void, UpdateDatabaseDdlMetadata> operation =
          adminClient.updateDatabaseDdl(
              instanceId,
              databaseId,
              ImmutableList.of(
                  "CREATE ROLE " + parentRole,
                  "GRANT SELECT ON TABLE Albums TO ROLE " + parentRole,
                  "CREATE ROLE " + childRole,
                  "GRANT ROLE " + parentRole + " TO ROLE " + childRole),
              null);
      try {
        System.out.println("Waiting for role create operation to complete...");
        operation.get(5, TimeUnit.MINUTES);
        System.out.printf(
            "Created roles %s and %s and granted privileges%n", parentRole, childRole);
        // Delete role and membership.
        operation =
            adminClient.updateDatabaseDdl(
                instanceId,
                databaseId,
                ImmutableList.of(
                    "REVOKE ROLE " + parentRole + " FROM ROLE " + childRole,
                    "DROP ROLE " + childRole),
                null);
        System.out.println("Waiting for role revoke & drop operation to complete...");
        operation.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 Spanner client library
const {Spanner} = require('@google-cloud/spanner');

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

async function addAndDropNewDatabaseRole() {
  // Gets a reference to a Cloud Spanner instance and database.
  const instance = spanner.instance(instanceId);
  const database = instance.database(databaseId);

  // 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 database.updateSchema(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 database.updateSchema(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 database when finished.
    await database.close();
  }
}
addAndDropNewDatabaseRole();

PHP

use Google\Cloud\Spanner\SpannerClient;

/**
 * Adds and drops roles to the Singers table in the example database.
 * Example:
 * ```
 * add_drop_database_role($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function add_drop_database_role(string $instanceId, string $databaseId): void
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $roleParent = 'new_parent';
    $roleChild = 'new_child';

    $operation = $database->updateDdlBatch([
        sprintf('CREATE ROLE %s', $roleParent),
        sprintf('GRANT SELECT ON TABLE Singers TO ROLE %s', $roleParent),
        sprintf('CREATE ROLE %s', $roleChild),
        sprintf('GRANT ROLE %s TO ROLE %s', $roleParent, $roleChild)
    ]);

    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', $roleParent, $roleChild, PHP_EOL);

    $operation = $database->updateDdlBatch([
        sprintf('REVOKE ROLE %s FROM ROLE %s', $roleParent, $roleChild),
        sprintf('DROP ROLE %s', $roleChild)
    ]);

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

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

Python

# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"
spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)
role_parent = "new_parent"
role_child = "new_child"

operation = database.update_ddl(
    [
        "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.result(OPERATION_TIMEOUT_SECONDS)
print(
    "Created roles {} and {} and granted privileges".format(role_parent, role_child)
)

operation = database.update_ddl(
    [
        "REVOKE ROLE {} FROM ROLE {}".format(role_parent, role_child),
        "DROP ROLE {}".format(role_child),
    ]
)
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

継承を使用してロールの階層を作成する

あるデータベース ロールを別のデータベース ロールに付与することで、データベース ロールの階層を作成できます。子のロール(メンバーのロール)は親から権限を継承します。

データベース ロールを別のデータベース ロールを付与するには、次のステートメントを使用します。

GRANT ROLE role1 TO ROLE role2;

詳細については、データベース ロールの階層と継承をご覧ください。

データベース ロールに対するアクセス権を IAM プリンシパルに付与する

IAM を使用して、データベースのロールへのアクセス権を付与します。

コンソール

データベース ロールに IAM プリンシパルへのアクセス権を付与する手順は次のとおりです。

  1. データベースの [概要] ページで、[情報パネル] がまだ開いていない場合は [情報パネルを表示] をクリックします。

  2. [プリンシパルを追加] をクリックします。

  3. プリンシパルを追加するの下のdatabase_nameへのアクセスを許可パネルで、1 つ以上の IAM プリンシパルを指定します。

  4. ロールを選択メニューのロールを割り当てるで、Cloud Spanner > Cloud Spanner のきめ細かいアクセス制御ユーザーを選択します。

    このロールは、プリンシパルごとに 1 回だけ付与する必要があります。プリンシパルにきめ細かいアクセス制御ユーザーを設定します。

  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"))
      

      このコードでは、次の 2 つのロールが付与されます。ROLE1ROLE2 はロール名に置き換えます。3 つ以上のロールを付与するには、or 条件を追加します。

      IAM でサポートされている任意の条件式を使用できます。詳細については、IAM 条件の概要をご覧ください。

    5. [保存] をクリックします。

    6. 前のパネルに戻り、[Role] フィールドの横にある [IAM Condition] 列に条件が表示されていることを確認します。

    7. [保存] をクリックします。

      情報パネルの [ロール / プリンシパル] に戻り、定義された各条件の [Cloud Spanner データベース ロールユーザー] が表示されます。

      条件の横にあるかっこ内の数字は、その条件によってデータベース ロールが付与されたプリンシパルの数を示します。展開矢印をクリックすると、プリンシパルのリストが表示されます。

    8. データベース ロールの名前や条件を修正する場合や、プリンシパルにデータベース ロールを追加する場合は、次の手順に沿って操作します。

      1. 必要な条件を一覧表示する [Cloud Spanner Database Role User] エントリを展開します。

      2. プリンシパルの横にある [編集](鉛筆)アイコンをクリックします。

      3. [database_name の編集アクセス権] パネルで、次のいずれかを行います。

        • [別のロールを追加] をクリックします。

        • 条件を編集するには、条件名の横にある [編集](鉛筆)アイコンをクリックします。次に、[条件の編集] ページで、[条件エディタ] をクリックして修正を行い、[保存] を 2 回クリックします。

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 にする必要があります。

    • このコマンドにより、プリンシパルはきめ細かいアクセス制御ユーザーになります。このコマンドは、プリンシパルごとに 1 回だけ送信します。

    • 成功した場合、コマンドはデータベースのポリシー全体を出力します。

  2. 次のように gcloud spanner databases add-iam-policy-binding コマンドを使用して、1 つ以上のデータベース ロールを使用する権限を付与します。

    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'
      

      このコードでは、次の 2 つのロールが付与されます。ROLE1ROLE2 はロール名に置き換えます。3 つ以上のロールを付与するには、|| 演算子を使用して、または条件を追加します。

      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.Binding;
import com.google.cloud.Condition;
import com.google.cloud.Policy;
import com.google.cloud.spanner.DatabaseAdminClient;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import com.google.common.collect.ImmutableList;

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()) {
      final DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient();
      Policy policy = adminClient.getDatabaseIAMPolicy(instanceId, databaseId, 3);
      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")
              .setMembers(ImmutableList.of(iamMember))
              .build();

      Binding binding2 =
          Binding.newBuilder()
              .setRole("roles/spanner.databaseRoleUser")
              .setCondition(
                  Condition.newBuilder()
                      .setDescription(title)
                      .setExpression(
                          String.format("resource.name.endsWith(\"/databaseRoles/%s\")", role))
                      .setTitle(title)
                      .build())
              .setMembers(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())
              .setBindings(bindings)
              .build();
      Policy response =
          adminClient.setDatabaseIAMPolicy(instanceId, databaseId, policyWithConditions);
      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} = require('@google-cloud/spanner');

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

async function enableFineGrainedAccess() {
  // Gets a reference to a Cloud Spanner instance and database.
  const instance = spanner.instance(instanceId);
  const database = instance.database(databaseId);

  const [policy] = await database.getIamPolicy({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 database.setIamPolicy({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 database.getIamPolicy({requestedPolicyVersion: 3});
  console.log(newPolicy);
}
enableFineGrainedAccess();

PHP

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

/**
 * 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 = sprintf('projects/%s/instances/%s/databases/%s', $projectId, $instanceId, $databaseId);
    $policy = $adminClient->getIamPolicy($resource);

    // 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]);
    $adminClient->setIamPolicy($resource, $policy);

    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"
spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)

# 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.
policy = database.get_iam_policy(3)
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)
database.set_iam_policy(policy)

new_policy = database.get_iam_policy(3)
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

プリンシパルを詳細なアクセス制御に移行する

IAM プリンシパルをデータベース レベルのアクセス制御からきめ細かなアクセス制御に移行するには、次の手順に沿って操作します。

  1. プリンシパルに対する詳細なアクセス制御を有効にし、必要なすべてのデータベース ロールへのアクセス権を付与します(IAM プリンシパルにデータベース ロールへのアクセス権を付与するをご覧ください)。

  2. このプリンシパルとして実行されるすべてのアプリケーションを更新します。クライアント ライブラリのメソッドの呼び出しで、適切なデータベース ロールを指定します。

  3. プリンシパルからすべての IAM データベース レベルのロールを取り消します。これにより、プリンシパルのアクセス権が 1 つの方法でのみ管理されます。

    IAM データベース レベルのロールを取り消すには、データベース レベルの権限の削除の手順に沿って操作してください。

データベース ロールを一覧表示する

データベースに関連付けられているデータベース ロールを一覧表示できます。

コンソール

データベース ロールを一覧表示するには、データベースの [クエリ] ページで次のクエリを入力します。

SELECT * FROM INFORMATION_SCHEMA.ROLES;

レスポンスには、現在のロールと、現在のロールが継承によって使用できる権限が含まれています。すべてのロールを取得するには、gcloud コマンドを使用します。

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.DatabaseAdminClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.DatabaseRole;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import java.util.concurrent.ExecutionException;

public class ListDatabaseRoles {

  static void listDatabaseRoles() throws InterruptedException, ExecutionException {
    // 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()) {
      final DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient();
      String databasePath = DatabaseId.of(projectId, instanceId, databaseId).getName();
      System.out.println("List of Database roles");
      for (DatabaseRole role : adminClient.listDatabaseRoles(instanceId, databaseId).iterateAll()) {
        System.out.printf("%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 Spanner client library
const {Spanner} = require('@google-cloud/spanner');

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

async function getDatabaseRoles() {
  // Gets a reference to a Cloud Spanner instance and database.
  const instance = spanner.instance(instanceId);
  const database = instance.database(databaseId);

  // Fetching database roles
  const [databaseRoles] = await database.getDatabaseRoles();
  console.log(`Roles for Database: ${database.formattedName_}`);
  databaseRoles.forEach(role => {
    console.log(`Role: ${role.name}`);
  });
}
getDatabaseRoles();

PHP

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

/**
 * 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 = sprintf('projects/%s/instances/%s/databases/%s', $projectId, $instanceId, $databaseId);

    $roles = $adminClient->listDatabaseRoles($resource);
    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"
spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)

# List database roles.
print("Database Roles are:")
for role in database.list_database_roles():
    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

データベース ロールに付与されている権限を表示する

ロールに付与された権限を表示するには、次のクエリを実行します。

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_PRIVILEGESSELECTINSERTUPDATE の権限も 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 ポリシー バインディングをすべて削除します。これにより、同じ名前で作成されたデータベース ロールがこれらのバインディングを継承しません。

コンソール

データベースのロールを削除するには、次の手順に沿って操作します。

  1. データベースの [概要] ページで、[DDL を記述] をクリックします。

  2. ロールから権限を取り消すには、REVOKE ステートメントを入力します。

    REVOKE ステートメントの構文の詳細については、GoogleSQL データ定義言語をご覧ください。権限の詳細については、きめ細かいアクセス制御権限をご覧ください。

    たとえば、データベース ロール hr_manager からテーブル employeescontractorsSELECTINSERTUPDATE を取り消すには、次のステートメントを入力します。 :

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

    DDL テンプレートは REVOKE ステートメントに使用できます。[DDL テンプレート] プルダウン メニューで、[データベースのロール] を選択し、[ロールの権限を取り消す] でテンプレートを選択します。

  3. ロールに関連付けられた IAM 条件を削除します。

    1. 情報パネルのロールのリストで、条件のタイトルに隣接する Cloud Spanner データベース ロールユーザーのロールを見つけます。次に、ロールを展開して、役割にアクセスできるプリンシパルを表示します。

    2. プリンシパルの 1 つについて、[プリンシパルを編集](鉛筆)アイコンをクリックします。

    3. [アクセス権の編集] ページで、Cloud Spanner データベース ロールのユーザーロールの横にある [ロールを削除](ゴミ箱)アイコンをクリックします。

    4. [保存] をクリックします。

    5. 条件に含まれる他のプリンシパルに対して、前の 3 つの手順を繰り返します。

  4. ロールを削除するには、[DDL の書き込み] ページに移動し、次のステートメントを入力します。

    DROP ROLE ROLE_NAME;
    

  5. [送信] をクリックします。

gcloud

  1. ロールのすべての権限を取り消してからロールを取り消すには、次のように gcloud spanner databases ddl update コマンドを使用します。

    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;'
    

    PERMISSIONS の有効な値は、SELECTINSERTUPDATEDELETE です。

  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.api.gax.longrunning.OperationFuture;
import com.google.cloud.spanner.DatabaseAdminClient;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import com.google.common.collect.ImmutableList;
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
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 = "my-new-parent-role";
    String childRole = "my-new-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())  {
      final DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient();
      OperationFuture<Void, UpdateDatabaseDdlMetadata> operation =
          adminClient.updateDatabaseDdl(
              instanceId,
              databaseId,
              ImmutableList.of(
                  "CREATE ROLE " + parentRole,
                  "GRANT SELECT ON TABLE Albums TO ROLE " + parentRole,
                  "CREATE ROLE " + childRole,
                  "GRANT ROLE " + parentRole + " TO ROLE " + childRole),
              null);
      try {
        System.out.println("Waiting for role create operation to complete...");
        operation.get(5, TimeUnit.MINUTES);
        System.out.printf(
            "Created roles %s and %s and granted privileges%n", parentRole, childRole);
        // Delete role and membership.
        operation =
            adminClient.updateDatabaseDdl(
                instanceId,
                databaseId,
                ImmutableList.of(
                    "REVOKE ROLE " + parentRole + " FROM ROLE " + childRole,
                    "DROP ROLE " + childRole),
                null);
        System.out.println("Waiting for role revoke & drop operation to complete...");
        operation.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 Spanner client library
const {Spanner} = require('@google-cloud/spanner');

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

async function addAndDropNewDatabaseRole() {
  // Gets a reference to a Cloud Spanner instance and database.
  const instance = spanner.instance(instanceId);
  const database = instance.database(databaseId);

  // 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 database.updateSchema(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 database.updateSchema(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 database when finished.
    await database.close();
  }
}
addAndDropNewDatabaseRole();

PHP

use Google\Cloud\Spanner\SpannerClient;

/**
 * Adds and drops roles to the Singers table in the example database.
 * Example:
 * ```
 * add_drop_database_role($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function add_drop_database_role(string $instanceId, string $databaseId): void
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $roleParent = 'new_parent';
    $roleChild = 'new_child';

    $operation = $database->updateDdlBatch([
        sprintf('CREATE ROLE %s', $roleParent),
        sprintf('GRANT SELECT ON TABLE Singers TO ROLE %s', $roleParent),
        sprintf('CREATE ROLE %s', $roleChild),
        sprintf('GRANT ROLE %s TO ROLE %s', $roleParent, $roleChild)
    ]);

    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', $roleParent, $roleChild, PHP_EOL);

    $operation = $database->updateDdlBatch([
        sprintf('REVOKE ROLE %s FROM ROLE %s', $roleParent, $roleChild),
        sprintf('DROP ROLE %s', $roleChild)
    ]);

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

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

Python

# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"
spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)
role_parent = "new_parent"
role_child = "new_child"

operation = database.update_ddl(
    [
        "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.result(OPERATION_TIMEOUT_SECONDS)
print(
    "Created roles {} and {} and granted privileges".format(role_parent, role_child)
)

operation = database.update_ddl(
    [
        "REVOKE ROLE {} FROM ROLE {}".format(role_parent, role_child),
        "DROP ROLE {}".format(role_child),
    ]
)
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

詳細情報