Skip to content

Commit a91fd2a

Browse files
emar-karcrwilcox
authored andcommitted
feat(bigtable): add table level IAM policy controls (#9877)
* feat(bigtable): add table level IAM policy controls * remove extra lines * system test * black * fix system test * update system-tests * chg comment lines * comment correction
1 parent 5588a03 commit a91fd2a

File tree

4 files changed

+261
-0
lines changed

4 files changed

+261
-0
lines changed

packages/google-cloud-bigtable/docs/snippets_table.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,55 @@ def test_bigtable_get_cluster_states():
356356
assert CLUSTER_ID in get_cluster_states
357357

358358

359+
def test_bigtable_table_test_iam_permissions():
360+
table_policy = Config.INSTANCE.table("table_id_iam_policy")
361+
table_policy.create()
362+
assert table_policy.exists
363+
364+
# [START bigtable_table_test_iam_permissions]
365+
from google.cloud.bigtable import Client
366+
367+
client = Client(admin=True)
368+
instance = client.instance(INSTANCE_ID)
369+
table = instance.table("table_id_iam_policy")
370+
371+
permissions = ["bigtable.tables.mutateRows", "bigtable.tables.readRows"]
372+
permissions_allowed = table.test_iam_permissions(permissions)
373+
# [END bigtable_table_test_iam_permissions]
374+
assert permissions_allowed == permissions
375+
376+
377+
def test_bigtable_table_set_iam_policy_then_get_iam_policy():
378+
table_policy = Config.INSTANCE.table("table_id_iam_policy")
379+
assert table_policy.exists
380+
service_account_email = Config.CLIENT._credentials.service_account_email
381+
382+
# [START bigtable_table_set_iam_policy]
383+
from google.cloud.bigtable import Client
384+
from google.cloud.bigtable.policy import Policy
385+
from google.cloud.bigtable.policy import BIGTABLE_ADMIN_ROLE
386+
387+
client = Client(admin=True)
388+
instance = client.instance(INSTANCE_ID)
389+
table = instance.table("table_id_iam_policy")
390+
new_policy = Policy()
391+
new_policy[BIGTABLE_ADMIN_ROLE] = [Policy.service_account(service_account_email)]
392+
393+
policy_latest = table.set_iam_policy(new_policy)
394+
# [END bigtable_table_set_iam_policy]
395+
assert len(policy_latest.bigtable_admins) > 0
396+
397+
# [START bigtable_table_get_iam_policy]
398+
from google.cloud.bigtable import Client
399+
400+
client = Client(admin=True)
401+
instance = client.instance(INSTANCE_ID)
402+
table = instance.table("table_id_iam_policy")
403+
policy = table.get_iam_policy()
404+
# [END bigtable_table_get_iam_policy]
405+
assert len(policy.bigtable_admins) > 0
406+
407+
359408
def test_bigtable_table_exists():
360409
# [START bigtable_check_table_exists]
361410
from google.cloud.bigtable import Client

packages/google-cloud-bigtable/google/cloud/bigtable/table.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from google.cloud.bigtable.column_family import ColumnFamily
2929
from google.cloud.bigtable.batcher import MutationsBatcher
3030
from google.cloud.bigtable.batcher import FLUSH_COUNT, MAX_ROW_BYTES
31+
from google.cloud.bigtable.policy import Policy
3132
from google.cloud.bigtable.row import AppendRow
3233
from google.cloud.bigtable.row import ConditionalRow
3334
from google.cloud.bigtable.row import DirectRow
@@ -138,6 +139,74 @@ def name(self):
138139
project=project, instance=instance_id, table=self.table_id
139140
)
140141

142+
def get_iam_policy(self):
143+
"""Gets the IAM access control policy for this table.
144+
145+
For example:
146+
147+
.. literalinclude:: snippets_table.py
148+
:start-after: [START bigtable_table_get_iam_policy]
149+
:end-before: [END bigtable_table_get_iam_policy]
150+
151+
:rtype: :class:`google.cloud.bigtable.policy.Policy`
152+
:returns: The current IAM policy of this table.
153+
"""
154+
table_client = self._instance._client.table_admin_client
155+
resp = table_client.get_iam_policy(resource=self.name)
156+
return Policy.from_pb(resp)
157+
158+
def set_iam_policy(self, policy):
159+
"""Sets the IAM access control policy for this table. Replaces any
160+
existing policy.
161+
162+
For more information about policy, please see documentation of
163+
class `google.cloud.bigtable.policy.Policy`
164+
165+
For example:
166+
167+
.. literalinclude:: snippets_table.py
168+
:start-after: [START bigtable_table_set_iam_policy]
169+
:end-before: [END bigtable_table_set_iam_policy]
170+
171+
:type policy: :class:`google.cloud.bigtable.policy.Policy`
172+
:param policy: A new IAM policy to replace the current IAM policy
173+
of this table.
174+
175+
:rtype: :class:`google.cloud.bigtable.policy.Policy`
176+
:returns: The current IAM policy of this table.
177+
"""
178+
table_client = self._instance._client.table_admin_client
179+
resp = table_client.set_iam_policy(resource=self.name, policy=policy.to_pb())
180+
return Policy.from_pb(resp)
181+
182+
def test_iam_permissions(self, permissions):
183+
"""Tests whether the caller has the given permissions for this table.
184+
Returns the permissions that the caller has.
185+
186+
For example:
187+
188+
.. literalinclude:: snippets_table.py
189+
:start-after: [START bigtable_table_test_iam_permissions]
190+
:end-before: [END bigtable_table_test_iam_permissions]
191+
192+
:type permissions: list
193+
:param permissions: The set of permissions to check for
194+
the ``resource``. Permissions with wildcards (such as '*'
195+
or 'storage.*') are not allowed. For more information see
196+
`IAM Overview
197+
<https://cloud.google.com/iam/docs/overview#permissions>`_.
198+
`Bigtable Permissions
199+
<https://cloud.google.com/bigtable/docs/access-control>`_.
200+
201+
:rtype: list
202+
:returns: A List(string) of permissions allowed on the table.
203+
"""
204+
table_client = self._instance._client.table_admin_client
205+
resp = table_client.test_iam_permissions(
206+
resource=self.name, permissions=permissions
207+
)
208+
return list(resp.permissions)
209+
141210
def column_family(self, column_family_id, gc_rule=None):
142211
"""Factory to create a column family associated with this table.
143212

packages/google-cloud-bigtable/tests/system.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
from google.cloud._helpers import UTC
3030
from google.cloud.bigtable.client import Client
3131
from google.cloud.bigtable.column_family import MaxVersionsGCRule
32+
from google.cloud.bigtable.policy import Policy
33+
from google.cloud.bigtable.policy import BIGTABLE_ADMIN_ROLE
3234
from google.cloud.bigtable.row_filters import ApplyLabelFilter
3335
from google.cloud.bigtable.row_filters import ColumnQualifierRegexFilter
3436
from google.cloud.bigtable.row_filters import RowFilterChain
@@ -688,6 +690,42 @@ def test_create_table(self):
688690
sorted_tables = sorted(tables, key=name_attr)
689691
self.assertEqual(sorted_tables, expected_tables)
690692

693+
def test_test_iam_permissions(self):
694+
temp_table_id = "test-test-iam-policy-table"
695+
temp_table = Config.INSTANCE_DATA.table(temp_table_id)
696+
temp_table.create()
697+
self.tables_to_delete.append(temp_table)
698+
699+
permissions = ["bigtable.tables.mutateRows", "bigtable.tables.readRows"]
700+
permissions_allowed = temp_table.test_iam_permissions(permissions)
701+
self.assertEqual(permissions, permissions_allowed)
702+
703+
def test_get_iam_policy(self):
704+
temp_table_id = "test-get-iam-policy-table"
705+
temp_table = Config.INSTANCE_DATA.table(temp_table_id)
706+
temp_table.create()
707+
self.tables_to_delete.append(temp_table)
708+
709+
policy = temp_table.get_iam_policy().to_api_repr()
710+
self.assertEqual(policy["etag"], "ACAB")
711+
self.assertEqual(policy["version"], 0)
712+
713+
def test_set_iam_policy(self):
714+
temp_table_id = "test-set-iam-policy-table"
715+
temp_table = Config.INSTANCE_DATA.table(temp_table_id)
716+
temp_table.create()
717+
self.tables_to_delete.append(temp_table)
718+
719+
new_policy = Policy()
720+
service_account_email = Config.CLIENT._credentials.service_account_email
721+
new_policy[BIGTABLE_ADMIN_ROLE] = [
722+
Policy.service_account(service_account_email)
723+
]
724+
policy_latest = temp_table.set_iam_policy(new_policy).to_api_repr()
725+
726+
self.assertEqual(policy_latest["bindings"][0]["role"], "roles/bigtable.admin")
727+
self.assertIn(service_account_email, policy_latest["bindings"][0]["members"][0])
728+
691729
def test_create_table_with_families(self):
692730
temp_table_id = "test-create-table-with-failies"
693731
temp_table = Config.INSTANCE_DATA.table(temp_table_id)

packages/google-cloud-bigtable/tests/unit/test_table.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,111 @@ def test_mutations_batcher_factory(self):
10481048
self.assertEqual(mutation_batcher.flush_count, flush_count)
10491049
self.assertEqual(mutation_batcher.max_row_bytes, max_row_bytes)
10501050

1051+
def test_get_iam_policy(self):
1052+
from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client
1053+
from google.iam.v1 import policy_pb2
1054+
from google.cloud.bigtable.policy import BIGTABLE_ADMIN_ROLE
1055+
1056+
credentials = _make_credentials()
1057+
client = self._make_client(
1058+
project="project-id", credentials=credentials, admin=True
1059+
)
1060+
instance = client.instance(instance_id=self.INSTANCE_ID)
1061+
table = self._make_one(self.TABLE_ID, instance)
1062+
1063+
version = 1
1064+
etag = b"etag_v1"
1065+
members = ["serviceAccount:service_acc1@test.com", "user:user1@test.com"]
1066+
bindings = [{"role": BIGTABLE_ADMIN_ROLE, "members": members}]
1067+
iam_policy = policy_pb2.Policy(version=version, etag=etag, bindings=bindings)
1068+
1069+
table_api = mock.create_autospec(
1070+
bigtable_table_admin_client.BigtableTableAdminClient
1071+
)
1072+
client._table_admin_client = table_api
1073+
table_api.get_iam_policy.return_value = iam_policy
1074+
1075+
result = table.get_iam_policy()
1076+
1077+
table_api.get_iam_policy.assert_called_once_with(resource=table.name)
1078+
self.assertEqual(result.version, version)
1079+
self.assertEqual(result.etag, etag)
1080+
admins = result.bigtable_admins
1081+
self.assertEqual(len(admins), len(members))
1082+
for found, expected in zip(sorted(admins), sorted(members)):
1083+
self.assertEqual(found, expected)
1084+
1085+
def test_set_iam_policy(self):
1086+
from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client
1087+
from google.iam.v1 import policy_pb2
1088+
from google.cloud.bigtable.policy import Policy
1089+
from google.cloud.bigtable.policy import BIGTABLE_ADMIN_ROLE
1090+
1091+
credentials = _make_credentials()
1092+
client = self._make_client(
1093+
project="project-id", credentials=credentials, admin=True
1094+
)
1095+
instance = client.instance(instance_id=self.INSTANCE_ID)
1096+
table = self._make_one(self.TABLE_ID, instance)
1097+
1098+
version = 1
1099+
etag = b"etag_v1"
1100+
members = ["serviceAccount:service_acc1@test.com", "user:user1@test.com"]
1101+
bindings = [{"role": BIGTABLE_ADMIN_ROLE, "members": sorted(members)}]
1102+
iam_policy_pb = policy_pb2.Policy(version=version, etag=etag, bindings=bindings)
1103+
1104+
table_api = mock.create_autospec(
1105+
bigtable_table_admin_client.BigtableTableAdminClient
1106+
)
1107+
client._table_admin_client = table_api
1108+
table_api.set_iam_policy.return_value = iam_policy_pb
1109+
1110+
iam_policy = Policy(etag=etag, version=version)
1111+
iam_policy[BIGTABLE_ADMIN_ROLE] = [
1112+
Policy.user("user1@test.com"),
1113+
Policy.service_account("service_acc1@test.com"),
1114+
]
1115+
1116+
result = table.set_iam_policy(iam_policy)
1117+
1118+
table_api.set_iam_policy.assert_called_once_with(
1119+
resource=table.name, policy=iam_policy_pb
1120+
)
1121+
self.assertEqual(result.version, version)
1122+
self.assertEqual(result.etag, etag)
1123+
admins = result.bigtable_admins
1124+
self.assertEqual(len(admins), len(members))
1125+
for found, expected in zip(sorted(admins), sorted(members)):
1126+
self.assertEqual(found, expected)
1127+
1128+
def test_test_iam_permissions(self):
1129+
from google.cloud.bigtable_admin_v2.gapic import bigtable_table_admin_client
1130+
from google.iam.v1 import iam_policy_pb2
1131+
1132+
credentials = _make_credentials()
1133+
client = self._make_client(
1134+
project="project-id", credentials=credentials, admin=True
1135+
)
1136+
instance = client.instance(instance_id=self.INSTANCE_ID)
1137+
table = self._make_one(self.TABLE_ID, instance)
1138+
1139+
permissions = ["bigtable.tables.mutateRows", "bigtable.tables.readRows"]
1140+
1141+
response = iam_policy_pb2.TestIamPermissionsResponse(permissions=permissions)
1142+
1143+
table_api = mock.create_autospec(
1144+
bigtable_table_admin_client.BigtableTableAdminClient
1145+
)
1146+
table_api.test_iam_permissions.return_value = response
1147+
client._table_admin_client = table_api
1148+
1149+
result = table.test_iam_permissions(permissions)
1150+
1151+
self.assertEqual(result, permissions)
1152+
table_api.test_iam_permissions.assert_called_once_with(
1153+
resource=table.name, permissions=permissions
1154+
)
1155+
10511156

10521157
class Test__RetryableMutateRowsWorker(unittest.TestCase):
10531158
from grpc import StatusCode

0 commit comments

Comments
 (0)