Search in sources :

Example 1 with ServiceAccountName

use of bio.terra.cloudres.google.iam.ServiceAccountName in project terra-workspace-manager by DataBiosphere.

the class PetSaService method enablePetServiceAccountImpersonationWithEtag.

/**
 * Grant a user's proxy group permission to impersonate their pet service account in a given
 * workspace. Unlike other operations, this does not run as a flight because it only requires one
 * write operation. This operation is idempotent.
 *
 * <p>The provided workspace must have a GCP context.
 *
 * <p>This method does not authenticate that the user should have access to impersonate their pet
 * SA, callers should validate this first.
 *
 * <p>userToEnableEmail is separate from token because of RevokePetUsagePermissionStep.undoStep().
 * If User A removes B from workspace, userToEnableEmail is B and token is from A's userRequest.
 *
 * @param workspaceId ID of the workspace to enable pet SA in
 * @param userToEnableEmail The user whose proxy group will be granted permission.
 * @param token Token for calling SAM.
 * @param eTag GCP eTag which must match the pet SA's current policy. If null, this is ignored.
 * @return The new IAM policy on the user's pet service account, or empty if the eTag value
 *     provided is non-null and does not match current IAM policy on the pet SA.
 */
public Optional<Policy> enablePetServiceAccountImpersonationWithEtag(UUID workspaceId, String userToEnableEmail, String token, @Nullable String eTag) {
    String petSaEmail = SamRethrow.onInterrupted(() -> samService.getOrCreatePetSaEmail(gcpCloudContextService.getRequiredGcpProject(workspaceId), token), "enablePet");
    String proxyGroupEmail = SamRethrow.onInterrupted(() -> samService.getProxyGroupEmail(userToEnableEmail, token), "enablePet");
    String projectId = gcpCloudContextService.getRequiredGcpProject(workspaceId);
    ServiceAccountName petSaName = ServiceAccountName.builder().email(petSaEmail).projectId(projectId).build();
    try {
        Policy saPolicy = crlService.getIamCow().projects().serviceAccounts().getIamPolicy(petSaName).execute();
        // clobbering other changes.
        if (eTag != null && !saPolicy.getEtag().equals(eTag)) {
            logger.warn("GCP IAM policy eTag did not match expected value when granting pet SA access for user {} in workspace {}. This is normal for Step retries.", userToEnableEmail, workspaceId);
            return Optional.empty();
        }
        Binding saUserBinding = new Binding().setRole(SERVICE_ACCOUNT_USER_ROLE).setMembers(ImmutableList.of("group:" + proxyGroupEmail));
        // If no bindings exist, getBindings() returns null instead of an empty list.
        List<Binding> bindingList = Optional.ofNullable(saPolicy.getBindings()).orElse(new ArrayList<>());
        // GCP automatically de-duplicates bindings, so this will have no effect if the user already
        // has permission to use their pet service account.
        bindingList.add(saUserBinding);
        saPolicy.setBindings(bindingList);
        SetIamPolicyRequest request = new SetIamPolicyRequest().setPolicy(saPolicy);
        return Optional.of(crlService.getIamCow().projects().serviceAccounts().setIamPolicy(petSaName, request).execute());
    } catch (IOException e) {
        throw new InternalServerErrorException("Error enabling user's proxy group to impersonate pet SA", e);
    }
}
Also used : Policy(com.google.api.services.iam.v1.model.Policy) Binding(com.google.api.services.iam.v1.model.Binding) SetIamPolicyRequest(com.google.api.services.iam.v1.model.SetIamPolicyRequest) InternalServerErrorException(bio.terra.common.exception.InternalServerErrorException) ServiceAccountName(bio.terra.cloudres.google.iam.ServiceAccountName) IOException(java.io.IOException)

Example 2 with ServiceAccountName

use of bio.terra.cloudres.google.iam.ServiceAccountName in project terra-workspace-manager by DataBiosphere.

the class PetSaService method disablePetServiceAccountImpersonationWithEtag.

/**
 * Revoke the permission to impersonate a pet service account granted by {@code
 * enablePetServiceAccountImpersonation}. Unlike other operations, this does not run in a flight
 * because it only requires one write operation. This operation is idempotent.
 *
 * <p>This method requires a user's pet service account email as input. As a transitive
 * dependency, this also means the provided workspace must have a GCP context.
 *
 * <p>This method does not authenticate that the user should have access to impersonate their pet
 * SA, callers should validate this first.
 *
 * @param workspaceId ID of the workspace to disable pet SA in
 * @param userToDisableEmail The user losing access to pet SA
 * @param userRequest This request's token will be used to authenticate SAM requests
 * @param eTag GCP eTag which must match the pet SA's current policy. If null, this is ignored.
 */
public Optional<Policy> disablePetServiceAccountImpersonationWithEtag(UUID workspaceId, String userToDisableEmail, AuthenticatedUserRequest userRequest, @Nullable String eTag) {
    String proxyGroupEmail = SamRethrow.onInterrupted(() -> samService.getProxyGroupEmail(userToDisableEmail, userRequest.getRequiredToken()), "disablePet");
    String projectId = gcpCloudContextService.getRequiredGcpProject(workspaceId);
    try {
        Optional<ServiceAccountName> userToDisablePetSA = getUserPetSa(projectId, userToDisableEmail, userRequest);
        if (userToDisablePetSA.isEmpty()) {
            return Optional.empty();
        }
        Policy saPolicy = crlService.getIamCow().projects().serviceAccounts().getIamPolicy(userToDisablePetSA.get()).execute();
        // clobbering other changes.
        if (eTag != null && !saPolicy.getEtag().equals(eTag)) {
            logger.warn("GCP IAM policy eTag did not match expected value when revoking pet SA access for user {} in workspace {}. This is normal for Step retries.", userToDisableEmail, workspaceId);
            return Optional.empty();
        }
        Binding bindingToRemove = new Binding().setRole(SERVICE_ACCOUNT_USER_ROLE).setMembers(ImmutableList.of("group:" + proxyGroupEmail));
        // If no bindings exist, getBindings() returns null instead of an empty list. If there are
        // no policies, there is nothing to revoke, so this method is finished.
        List<Binding> oldBindingList = saPolicy.getBindings();
        if (oldBindingList == null) {
            return Optional.empty();
        }
        List<Binding> newBindingsList = removeBinding(oldBindingList, bindingToRemove);
        saPolicy.setBindings(newBindingsList);
        SetIamPolicyRequest request = new SetIamPolicyRequest().setPolicy(saPolicy);
        return Optional.of(crlService.getIamCow().projects().serviceAccounts().setIamPolicy(userToDisablePetSA.get(), request).execute());
    } catch (IOException e) {
        throw new InternalServerErrorException("Error disabling user's proxy group to impersonate pet SA", e);
    }
}
Also used : Policy(com.google.api.services.iam.v1.model.Policy) Binding(com.google.api.services.iam.v1.model.Binding) SetIamPolicyRequest(com.google.api.services.iam.v1.model.SetIamPolicyRequest) InternalServerErrorException(bio.terra.common.exception.InternalServerErrorException) ServiceAccountName(bio.terra.cloudres.google.iam.ServiceAccountName) IOException(java.io.IOException)

Example 3 with ServiceAccountName

use of bio.terra.cloudres.google.iam.ServiceAccountName in project terra-workspace-manager by DataBiosphere.

the class ControlledResourceServiceTest method createAiNotebookInstanceDo.

@Test
@DisabledIfEnvironmentVariable(named = "TEST_ENV", matches = BUFFER_SERVICE_DISABLED_ENVS_REG_EX)
void createAiNotebookInstanceDo() throws Exception {
    UUID workspaceId = reusableWorkspace(user).getWorkspaceId();
    String instanceId = "create-ai-notebook-instance-do";
    ApiGcpAiNotebookInstanceCreationParameters creationParameters = ControlledResourceFixtures.defaultNotebookCreationParameters().instanceId(instanceId).location(DEFAULT_NOTEBOOK_LOCATION);
    ControlledAiNotebookInstanceResource resource = makeNotebookTestResource(workspaceId, "initial-notebook-name", instanceId);
    // Test idempotency of steps by retrying them once.
    Map<String, StepStatus> retrySteps = new HashMap<>();
    retrySteps.put(RetrieveNetworkNameStep.class.getName(), StepStatus.STEP_RESULT_FAILURE_RETRY);
    retrySteps.put(GrantPetUsagePermissionStep.class.getName(), StepStatus.STEP_RESULT_FAILURE_RETRY);
    retrySteps.put(CreateAiNotebookInstanceStep.class.getName(), StepStatus.STEP_RESULT_FAILURE_RETRY);
    retrySteps.put(NotebookCloudSyncStep.class.getName(), StepStatus.STEP_RESULT_FAILURE_RETRY);
    jobService.setFlightDebugInfoForTest(FlightDebugInfo.newBuilder().doStepFailures(retrySteps).build());
    String jobId = controlledResourceService.createAiNotebookInstance(resource, creationParameters, DEFAULT_ROLE, new ApiJobControl().id(UUID.randomUUID().toString()), "fakeResultPath", user.getAuthenticatedRequest());
    jobService.waitForJob(jobId);
    assertEquals(FlightStatus.SUCCESS, stairwayComponent.get().getFlightState(jobId).getFlightStatus());
    assertEquals(resource, controlledResourceService.getControlledResource(workspaceId, resource.getResourceId(), user.getAuthenticatedRequest()));
    InstanceName instanceName = resource.toInstanceName(workspaceService.getAuthorizedRequiredGcpProject(workspaceId, user.getAuthenticatedRequest()));
    Instance instance = crlService.getAIPlatformNotebooksCow().instances().get(instanceName).execute();
    // Test that the user has permissions from WRITER roles on the notebooks instance. Only notebook
    // instance level permissions can be checked on the notebook instance test IAM permissions
    // endpoint, so no "notebooks.instances.list" permission as that's project level.
    List<String> expectedWriterPermissions = ImmutableList.of("notebooks.instances.get", "notebooks.instances.reset", "notebooks.instances.setAccelerator", "notebooks.instances.setMachineType", "notebooks.instances.start", "notebooks.instances.stop", "notebooks.instances.use");
    assertThat(AIPlatformNotebooksCow.create(crlService.getClientConfig(), user.getGoogleCredentials()).instances().testIamPermissions(instanceName, new com.google.api.services.notebooks.v1.model.TestIamPermissionsRequest().setPermissions(expectedWriterPermissions)).execute().getPermissions(), Matchers.containsInAnyOrder(expectedWriterPermissions.toArray()));
    // Test that the user has access to the notebook with a service account through proxy mode.
    // git secrets gets a false positive if 'service_account' is double quoted.
    assertThat(instance.getMetadata(), Matchers.hasEntry("proxy-mode", "service_" + "account"));
    ServiceAccountName serviceAccountName = ServiceAccountName.builder().projectId(instanceName.projectId()).email(instance.getServiceAccount()).build();
    // The user needs to have the actAs permission on the service account.
    String actAsPermission = "iam.serviceAccounts.actAs";
    assertThat(IamCow.create(crlService.getClientConfig(), user.getGoogleCredentials()).projects().serviceAccounts().testIamPermissions(serviceAccountName, new TestIamPermissionsRequest().setPermissions(List.of(actAsPermission))).execute().getPermissions(), Matchers.contains(actAsPermission));
    // Creating a controlled resource with a duplicate underlying notebook instance is not allowed.
    ControlledAiNotebookInstanceResource duplicateResource = makeNotebookTestResource(workspaceId, "new-name-same-notebook-instance", instanceId);
    String duplicateResourceJobId = controlledResourceService.createAiNotebookInstance(duplicateResource, creationParameters, DEFAULT_ROLE, new ApiJobControl().id(UUID.randomUUID().toString()), "fakeResultPath", user.getAuthenticatedRequest());
    jobService.waitForJob(duplicateResourceJobId);
    JobService.JobResultOrException<ControlledAiNotebookInstanceResource> duplicateJobResult = jobService.retrieveJobResult(duplicateResourceJobId, ControlledAiNotebookInstanceResource.class, user.getAuthenticatedRequest());
    assertEquals(DuplicateResourceException.class, duplicateJobResult.getException().getClass());
}
Also used : HashMap(java.util.HashMap) Instance(com.google.api.services.notebooks.v1.model.Instance) TestInstance(org.junit.jupiter.api.TestInstance) TestIamPermissionsRequest(com.google.api.services.iam.v1.model.TestIamPermissionsRequest) ServiceAccountName(bio.terra.cloudres.google.iam.ServiceAccountName) StepStatus(bio.terra.stairway.StepStatus) RetrieveNetworkNameStep(bio.terra.workspace.service.resource.controlled.cloud.gcp.ainotebook.RetrieveNetworkNameStep) GrantPetUsagePermissionStep(bio.terra.workspace.service.resource.controlled.cloud.gcp.ainotebook.GrantPetUsagePermissionStep) ApiJobControl(bio.terra.workspace.generated.model.ApiJobControl) JobService(bio.terra.workspace.service.job.JobService) InstanceName(bio.terra.cloudres.google.notebooks.InstanceName) ApiGcpAiNotebookInstanceCreationParameters(bio.terra.workspace.generated.model.ApiGcpAiNotebookInstanceCreationParameters) CreateAiNotebookInstanceStep(bio.terra.workspace.service.resource.controlled.cloud.gcp.ainotebook.CreateAiNotebookInstanceStep) NotebookCloudSyncStep(bio.terra.workspace.service.resource.controlled.cloud.gcp.ainotebook.NotebookCloudSyncStep) UUID(java.util.UUID) ControlledAiNotebookInstanceResource(bio.terra.workspace.service.resource.controlled.cloud.gcp.ainotebook.ControlledAiNotebookInstanceResource) Test(org.junit.jupiter.api.Test) BaseConnectedTest(bio.terra.workspace.common.BaseConnectedTest) DisabledIfEnvironmentVariable(org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable)

Aggregations

ServiceAccountName (bio.terra.cloudres.google.iam.ServiceAccountName)3 InternalServerErrorException (bio.terra.common.exception.InternalServerErrorException)2 Binding (com.google.api.services.iam.v1.model.Binding)2 Policy (com.google.api.services.iam.v1.model.Policy)2 SetIamPolicyRequest (com.google.api.services.iam.v1.model.SetIamPolicyRequest)2 IOException (java.io.IOException)2 InstanceName (bio.terra.cloudres.google.notebooks.InstanceName)1 StepStatus (bio.terra.stairway.StepStatus)1 BaseConnectedTest (bio.terra.workspace.common.BaseConnectedTest)1 ApiGcpAiNotebookInstanceCreationParameters (bio.terra.workspace.generated.model.ApiGcpAiNotebookInstanceCreationParameters)1 ApiJobControl (bio.terra.workspace.generated.model.ApiJobControl)1 JobService (bio.terra.workspace.service.job.JobService)1 ControlledAiNotebookInstanceResource (bio.terra.workspace.service.resource.controlled.cloud.gcp.ainotebook.ControlledAiNotebookInstanceResource)1 CreateAiNotebookInstanceStep (bio.terra.workspace.service.resource.controlled.cloud.gcp.ainotebook.CreateAiNotebookInstanceStep)1 GrantPetUsagePermissionStep (bio.terra.workspace.service.resource.controlled.cloud.gcp.ainotebook.GrantPetUsagePermissionStep)1 NotebookCloudSyncStep (bio.terra.workspace.service.resource.controlled.cloud.gcp.ainotebook.NotebookCloudSyncStep)1 RetrieveNetworkNameStep (bio.terra.workspace.service.resource.controlled.cloud.gcp.ainotebook.RetrieveNetworkNameStep)1 TestIamPermissionsRequest (com.google.api.services.iam.v1.model.TestIamPermissionsRequest)1 Instance (com.google.api.services.notebooks.v1.model.Instance)1 HashMap (java.util.HashMap)1