From f38a8ecdc6164d081ef96f748ea37bd62b29b419 Mon Sep 17 00:00:00 2001 From: kolea2 <45548808+kolea2@users.noreply.github.com> Date: Wed, 21 Oct 2020 17:38:22 +0000 Subject: [PATCH] feat: backup level IAM (#450) * feat: backup level IAM * docs * extract out common logic * code feedback * lint --- .../admin/v2/BigtableTableAdminClient.java | 257 +++++++++++++++--- .../v2/BigtableTableAdminClientTest.java | 122 ++++++++- .../admin/v2/it/BigtableBackupIT.java | 29 ++ 3 files changed, 373 insertions(+), 35 deletions(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableTableAdminClient.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableTableAdminClient.java index 5d0350b9c..d4029e19b 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableTableAdminClient.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableTableAdminClient.java @@ -1319,21 +1319,8 @@ public Policy getIamPolicy(String tableId) { */ @SuppressWarnings("WeakerAccess") public ApiFuture getIamPolicyAsync(String tableId) { - String name = NameUtil.formatTableName(projectId, instanceId, tableId); - - GetIamPolicyRequest request = GetIamPolicyRequest.newBuilder().setResource(name).build(); - - final IamPolicyMarshaller marshaller = new IamPolicyMarshaller(); - - return ApiFutures.transform( - stub.getIamPolicyCallable().futureCall(request), - new ApiFunction() { - @Override - public Policy apply(com.google.iam.v1.Policy proto) { - return marshaller.fromPb(proto); - } - }, - MoreExecutors.directExecutor()); + String tableName = NameUtil.formatTableName(projectId, instanceId, tableId); + return getResourceIamPolicy(tableName); } /** @@ -1391,24 +1378,8 @@ public Policy setIamPolicy(String tableId, Policy policy) { */ @SuppressWarnings("WeakerAccess") public ApiFuture setIamPolicyAsync(String tableId, Policy policy) { - String name = NameUtil.formatTableName(projectId, instanceId, tableId); - final IamPolicyMarshaller marshaller = new IamPolicyMarshaller(); - - SetIamPolicyRequest request = - SetIamPolicyRequest.newBuilder() - .setResource(name) - .setPolicy(marshaller.toPb(policy)) - .build(); - - return ApiFutures.transform( - stub.setIamPolicyCallable().futureCall(request), - new ApiFunction() { - @Override - public Policy apply(com.google.iam.v1.Policy proto) { - return marshaller.fromPb(proto); - } - }, - MoreExecutors.directExecutor()); + String tableName = NameUtil.formatTableName(projectId, instanceId, tableId); + return setResourceIamPolicy(policy, tableName); } /** @@ -1463,9 +1434,227 @@ public List testIamPermission(String tableId, String... permissions) { */ @SuppressWarnings({"WeakerAccess"}) public ApiFuture> testIamPermissionAsync(String tableId, String... permissions) { + String tableName = NameUtil.formatTableName(projectId, instanceId, tableId); + return testResourceIamPermissions(tableName, permissions); + } + + /** + * Gets the IAM access control policy for the specified backup. + * + *

Sample code: + * + *

{@code
+   * Policy policy = client.getBackupIamPolicy("my-cluster-id", "my-backup-id");
+   * for(Map.Entry> entry : policy.getBindings().entrySet()) {
+   *   System.out.printf("Role: %s Identities: %s\n", entry.getKey(), entry.getValue());
+   * }
+   * }
+ * + * @see Table-level + * IAM management + */ + @SuppressWarnings("WeakerAccess") + public Policy getBackupIamPolicy(String clusterId, String backupId) { + return ApiExceptions.callAndTranslateApiException(getBackupIamPolicyAsync(clusterId, backupId)); + } + + /** + * Asynchronously gets the IAM access control policy for the specified backup. + * + *

Sample code: + * + *

{@code
+   * ApiFuture policyFuture = client.getBackupIamPolicyAsync("my-cluster-id", "my-backup-id");
+   *
+   * ApiFutures.addCallback(policyFuture,
+   *   new ApiFutureCallback() {
+   *     public void onSuccess(Policy policy) {
+   *       for (Entry> entry : policy.getBindings().entrySet()) {
+   *         System.out.printf("Role: %s Identities: %s\n", entry.getKey(), entry.getValue());
+   *       }
+   *     }
+   *
+   *     public void onFailure(Throwable t) {
+   *       t.printStackTrace();
+   *     }
+   *   },
+   *   MoreExecutors.directExecutor());
+   * }
+ * + * @see Table-level + * IAM management + */ + @SuppressWarnings("WeakerAccess") + public ApiFuture getBackupIamPolicyAsync(String clusterId, String backupId) { + String backupName = NameUtil.formatBackupName(projectId, instanceId, clusterId, backupId); + return getResourceIamPolicy(backupName); + } + + /** + * Replaces the IAM policy associated with the specified backup. + * + *

Sample code: + * + *

{@code
+   * Policy newPolicy = client.setBackupIamPolicy("my-cluster-id", "my-backup-id",
+   *   Policy.newBuilder()
+   *     .addIdentity(Role.of("bigtable.user"), Identity.user("someone@example.com"))
+   *     .addIdentity(Role.of("bigtable.admin"), Identity.group("admins@example.com"))
+   *     .build());
+   * }
+ * + * @see Table-level + * IAM management + */ + @SuppressWarnings("WeakerAccess") + public Policy setBackupIamPolicy(String clusterId, String backupId, Policy policy) { + return ApiExceptions.callAndTranslateApiException( + setBackupIamPolicyAsync(clusterId, backupId, policy)); + } + + /** + * Asynchronously replaces the IAM policy associated with the specified backup. + * + *

Sample code: + * + *

{@code
+   * ApiFuture newPolicyFuture = client.setBackupIamPolicyAsync("my-cluster-id", "my-backup-id",
+   *   Policy.newBuilder()
+   *     .addIdentity(Role.of("bigtable.user"), Identity.user("someone@example.com"))
+   *     .addIdentity(Role.of("bigtable.admin"), Identity.group("admins@example.com"))
+   *     .build());
+   *
+   * ApiFutures.addCallback(newPolicyFuture,
+   *   new ApiFutureCallback() {
+   *     public void onSuccess(Policy policy) {
+   *       for (Entry> entry : policy.getBindings().entrySet()) {
+   *         System.out.printf("Role: %s Identities: %s\n", entry.getKey(), entry.getValue());
+   *       }
+   *     }
+   *
+   *     public void onFailure(Throwable t) {
+   *       t.printStackTrace();
+   *     }
+   *   },
+   *   MoreExecutors.directExecutor());
+   * }
+ * + * @see Table-level + * IAM management + */ + @SuppressWarnings("WeakerAccess") + public ApiFuture setBackupIamPolicyAsync( + String clusterId, String backupId, Policy policy) { + String backupName = NameUtil.formatBackupName(projectId, instanceId, clusterId, backupId); + return setResourceIamPolicy(policy, backupName); + } + + /** + * Tests whether the caller has the given permissions for the specified backup. Returns a subset + * of the specified permissions that the caller has. + * + *

Sample code: + * + *

{@code
+   * List grantedPermissions = client.testBackupIamPermission("my-cluster-id", "my-backup-id",
+   *   "bigtable.backups.restore", "bigtable.backups.delete");
+   * }
+ * + * System.out.println("Has restore access: " + + * grantedPermissions.contains("bigtable.backups.restore")); + * + *

System.out.println("Has delete access: " + + * grantedPermissions.contains("bigtable.backups.delete")); + * + * @see Cloud Bigtable + * permissions + */ + @SuppressWarnings({"WeakerAccess"}) + public List testBackupIamPermission( + String clusterId, String backupId, String... permissions) { + return ApiExceptions.callAndTranslateApiException( + testBackupIamPermissionAsync(clusterId, backupId, permissions)); + } + + /** + * Asynchronously tests whether the caller has the given permissions for the specified backup. + * Returns a subset of the specified permissions that the caller has. + * + *

Sample code: + * + *

{@code
+   * ApiFuture> grantedPermissionsFuture = client.testBackupIamPermissionAsync("my-cluster-id", "my-backup-id",
+   *   "bigtable.backups.restore", "bigtable.backups.delete");
+   *
+   * ApiFutures.addCallback(grantedPermissionsFuture,
+   *   new ApiFutureCallback>() {
+   *     public void onSuccess(List grantedPermissions) {
+   *       System.out.println("Has restore access: " + grantedPermissions.contains("bigtable.backups.restore"));
+   *       System.out.println("Has delete access: " + grantedPermissions.contains("bigtable.backups.delete"));
+   *     }
+   *
+   *     public void onFailure(Throwable t) {
+   *       t.printStackTrace();
+   *     }
+   *   },
+   *   MoreExecutors.directExecutor());
+   * }
+ * + * @see Cloud Bigtable + * permissions + */ + @SuppressWarnings({"WeakerAccess"}) + public ApiFuture> testBackupIamPermissionAsync( + String clusterId, String backupId, String... permissions) { + String backupName = NameUtil.formatBackupName(projectId, instanceId, clusterId, backupId); + return testResourceIamPermissions(backupName, permissions); + } + + private ApiFuture getResourceIamPolicy(String name) { + GetIamPolicyRequest request = GetIamPolicyRequest.newBuilder().setResource(name).build(); + + final IamPolicyMarshaller marshaller = new IamPolicyMarshaller(); + + return ApiFutures.transform( + stub.getIamPolicyCallable().futureCall(request), + new ApiFunction() { + @Override + public Policy apply(com.google.iam.v1.Policy proto) { + return marshaller.fromPb(proto); + } + }, + MoreExecutors.directExecutor()); + } + + private ApiFuture setResourceIamPolicy(Policy policy, String name) { + final IamPolicyMarshaller marshaller = new IamPolicyMarshaller(); + + SetIamPolicyRequest request = + SetIamPolicyRequest.newBuilder() + .setResource(name) + .setPolicy(marshaller.toPb(policy)) + .build(); + + return ApiFutures.transform( + stub.setIamPolicyCallable().futureCall(request), + new ApiFunction() { + @Override + public Policy apply(com.google.iam.v1.Policy proto) { + return marshaller.fromPb(proto); + } + }, + MoreExecutors.directExecutor()); + } + + private ApiFuture> testResourceIamPermissions( + String resourceName, String[] permissions) { TestIamPermissionsRequest request = TestIamPermissionsRequest.newBuilder() - .setResource(NameUtil.formatTableName(projectId, instanceId, tableId)) + .setResource(resourceName) .addAllPermissions(Arrays.asList(permissions)) .build(); diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableTableAdminClientTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableTableAdminClientTest.java index c26551432..eeb87ada2 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableTableAdminClientTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableTableAdminClientTest.java @@ -44,6 +44,9 @@ import com.google.bigtable.admin.v2.RestoreTableMetadata; import com.google.bigtable.admin.v2.Table.View; import com.google.bigtable.admin.v2.TableName; +import com.google.cloud.Identity; +import com.google.cloud.Policy; +import com.google.cloud.Role; import com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient.ListBackupsPage; import com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient.ListBackupsPagedResponse; import com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient.ListTablesPage; @@ -59,6 +62,7 @@ import com.google.cloud.bigtable.admin.v2.models.UpdateBackupRequest; import com.google.cloud.bigtable.admin.v2.stub.EnhancedBigtableTableAdminStub; import com.google.common.collect.Lists; +import com.google.common.io.BaseEncoding; import com.google.longrunning.Operation; import com.google.protobuf.Any; import com.google.protobuf.ByteString; @@ -94,7 +98,6 @@ public class BigtableTableAdminClientTest { private static final String CLUSTER_ID = "my-cluster"; private static final String BACKUP_ID = "my-backup"; - private static final String PROJECT_NAME = NameUtil.formatProjectName(PROJECT_ID); private static final String INSTANCE_NAME = NameUtil.formatInstanceName(PROJECT_ID, INSTANCE_ID); private static final String TABLE_NAME = NameUtil.formatTableName(PROJECT_ID, INSTANCE_ID, TABLE_ID); @@ -156,6 +159,19 @@ public class BigtableTableAdminClientTest { RestoreTableMetadata> mockRestoreTableOperationCallable; + @Mock + private UnaryCallable + mockGetIamPolicyCallable; + + @Mock + private UnaryCallable + mockSetIamPolicyCallable; + + @Mock + private UnaryCallable< + com.google.iam.v1.TestIamPermissionsRequest, com.google.iam.v1.TestIamPermissionsResponse> + mockTestIamPermissionsCallable; + @Before public void setUp() { adminClient = BigtableTableAdminClient.create(PROJECT_ID, INSTANCE_ID, mockStub); @@ -177,6 +193,9 @@ public void setUp() { Mockito.when(mockStub.restoreTableCallable()).thenReturn(mockRestoreTableCallable); Mockito.when(mockStub.restoreTableOperationCallable()) .thenReturn(mockRestoreTableOperationCallable); + Mockito.when(mockStub.getIamPolicyCallable()).thenReturn(mockGetIamPolicyCallable); + Mockito.when(mockStub.setIamPolicyCallable()).thenReturn(mockSetIamPolicyCallable); + Mockito.when(mockStub.testIamPermissionsCallable()).thenReturn(mockTestIamPermissionsCallable); } @Test @@ -645,6 +664,107 @@ public void testListBackups() { assertThat(actualResults).containsExactlyElementsIn(expectedResults); } + @Test + public void testGetBackupIamPolicy() { + // Setup + com.google.iam.v1.GetIamPolicyRequest expectedRequest = + com.google.iam.v1.GetIamPolicyRequest.newBuilder() + .setResource(NameUtil.formatBackupName(PROJECT_ID, INSTANCE_ID, CLUSTER_ID, BACKUP_ID)) + .build(); + + com.google.iam.v1.Policy expectedResponse = + com.google.iam.v1.Policy.newBuilder() + .addBindings( + com.google.iam.v1.Binding.newBuilder() + .setRole("roles/bigtable.viewer") + .addMembers("user:someone@example.com")) + .setEtag(ByteString.copyFromUtf8("my-etag")) + .build(); + + Mockito.when(mockGetIamPolicyCallable.futureCall(expectedRequest)) + .thenReturn(ApiFutures.immediateFuture(expectedResponse)); + + // Execute + Policy actualResult = adminClient.getBackupIamPolicy(CLUSTER_ID, BACKUP_ID); + + // Verify + assertThat(actualResult) + .isEqualTo( + Policy.newBuilder() + .addIdentity(Role.of("bigtable.viewer"), Identity.user("someone@example.com")) + .setEtag(BaseEncoding.base64().encode("my-etag".getBytes())) + .build()); + } + + @Test + public void testSetIamPolicy() { + // Setup + com.google.iam.v1.SetIamPolicyRequest expectedRequest = + com.google.iam.v1.SetIamPolicyRequest.newBuilder() + .setResource(NameUtil.formatBackupName(PROJECT_ID, INSTANCE_ID, CLUSTER_ID, BACKUP_ID)) + .setPolicy( + com.google.iam.v1.Policy.newBuilder() + .addBindings( + com.google.iam.v1.Binding.newBuilder() + .setRole("roles/bigtable.viewer") + .addMembers("user:someone@example.com"))) + .build(); + + com.google.iam.v1.Policy expectedResponse = + com.google.iam.v1.Policy.newBuilder() + .addBindings( + com.google.iam.v1.Binding.newBuilder() + .setRole("roles/bigtable.viewer") + .addMembers("user:someone@example.com")) + .setEtag(ByteString.copyFromUtf8("my-etag")) + .build(); + + Mockito.when(mockSetIamPolicyCallable.futureCall(expectedRequest)) + .thenReturn(ApiFutures.immediateFuture(expectedResponse)); + + // Execute + Policy actualResult = + adminClient.setBackupIamPolicy( + CLUSTER_ID, + BACKUP_ID, + Policy.newBuilder() + .addIdentity(Role.of("bigtable.viewer"), Identity.user("someone@example.com")) + .build()); + + // Verify + assertThat(actualResult) + .isEqualTo( + Policy.newBuilder() + .addIdentity(Role.of("bigtable.viewer"), Identity.user("someone@example.com")) + .setEtag(BaseEncoding.base64().encode("my-etag".getBytes())) + .build()); + } + + @Test + public void testTestIamPermissions() { + // Setup + com.google.iam.v1.TestIamPermissionsRequest expectedRequest = + com.google.iam.v1.TestIamPermissionsRequest.newBuilder() + .setResource(NameUtil.formatBackupName(PROJECT_ID, INSTANCE_ID, CLUSTER_ID, BACKUP_ID)) + .addPermissions("bigtable.backups.get") + .build(); + + com.google.iam.v1.TestIamPermissionsResponse expectedResponse = + com.google.iam.v1.TestIamPermissionsResponse.newBuilder() + .addPermissions("bigtable.backups.get") + .build(); + + Mockito.when(mockTestIamPermissionsCallable.futureCall(expectedRequest)) + .thenReturn(ApiFutures.immediateFuture(expectedResponse)); + + // Execute + List actualResult = + adminClient.testBackupIamPermission(CLUSTER_ID, BACKUP_ID, "bigtable.backups.get"); + + // Verify + assertThat(actualResult).containsExactly("bigtable.backups.get"); + } + private void mockOperationResult( OperationCallable callable, ReqT request, diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/it/BigtableBackupIT.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/it/BigtableBackupIT.java index 4d17bbf0e..10bd7da14 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/it/BigtableBackupIT.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/it/BigtableBackupIT.java @@ -15,6 +15,7 @@ */ package com.google.cloud.bigtable.admin.v2.it; +import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static io.grpc.Status.Code.NOT_FOUND; import static org.junit.Assert.fail; @@ -22,6 +23,7 @@ import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutures; import com.google.api.gax.rpc.ApiException; +import com.google.cloud.Policy; import com.google.cloud.bigtable.admin.v2.BigtableTableAdminClient; import com.google.cloud.bigtable.admin.v2.BigtableTableAdminSettings; import com.google.cloud.bigtable.admin.v2.models.Backup; @@ -331,6 +333,33 @@ public void restoreTableTest() throws InterruptedException, ExecutionException { } } + @Test + public void backupIamTest() throws InterruptedException { + String backupId = generateId("iam-" + TEST_BACKUP_SUFFIX); + createBackupAndWait(backupId); + + Policy policy = tableAdmin.getBackupIamPolicy(targetCluster, backupId); + assertThat(policy).isNotNull(); + + Exception actualEx = null; + try { + assertThat(tableAdmin.setBackupIamPolicy(targetCluster, backupId, policy)).isNotNull(); + } catch (Exception iamException) { + actualEx = iamException; + } + assertThat(actualEx).isNull(); + + List permissions = + tableAdmin.testBackupIamPermission( + targetCluster, + backupId, + "bigtable.backups.get", + "bigtable.backups.delete", + "bigtable.backups.update", + "bigtable.backups.restore"); + assertThat(permissions).hasSize(4); + } + private CreateBackupRequest createBackupRequest(String backupName) { return CreateBackupRequest.of(targetCluster, backupName) .setSourceTableId(testTable.getId())