Search in sources :

Example 1 with ControlledResourceIamRole

use of bio.terra.workspace.service.iam.model.ControlledResourceIamRole in project terra-workspace-manager by DataBiosphere.

the class ControlledResourceServiceTest method createAiNotebookInstanceNoWriterRoleThrowsBadRequest.

@Test
@DisabledIfEnvironmentVariable(named = "TEST_ENV", matches = BUFFER_SERVICE_DISABLED_ENVS_REG_EX)
void createAiNotebookInstanceNoWriterRoleThrowsBadRequest() throws Exception {
    String instanceId = "create-ai-notebook-instance-shared";
    ApiGcpAiNotebookInstanceCreationParameters creationParameters = ControlledResourceFixtures.defaultNotebookCreationParameters().instanceId(instanceId).location(DEFAULT_NOTEBOOK_LOCATION);
    ControlledAiNotebookInstanceResource resource = makeNotebookTestResource(workspace.getWorkspaceId(), instanceId, instanceId);
    // Shared notebooks not yet implemented.
    // Private IAM roles must include writer role.
    ControlledResourceIamRole notWriter = ControlledResourceIamRole.READER;
    BadRequestException noWriterException = assertThrows(BadRequestException.class, () -> controlledResourceService.createAiNotebookInstance(resource, creationParameters, notWriter, new ApiJobControl().id(UUID.randomUUID().toString()), "fakeResultPath", user.getAuthenticatedRequest()));
    assertEquals("A private, controlled AI Notebook instance must have the writer or editor role or else it is not useful.", noWriterException.getMessage());
}
Also used : ApiGcpAiNotebookInstanceCreationParameters(bio.terra.workspace.generated.model.ApiGcpAiNotebookInstanceCreationParameters) BadRequestException(bio.terra.common.exception.BadRequestException) ControlledResourceIamRole(bio.terra.workspace.service.iam.model.ControlledResourceIamRole) ControlledAiNotebookInstanceResource(bio.terra.workspace.service.resource.controlled.cloud.gcp.ainotebook.ControlledAiNotebookInstanceResource) ApiJobControl(bio.terra.workspace.generated.model.ApiJobControl) Test(org.junit.jupiter.api.Test) BaseConnectedTest(bio.terra.workspace.common.BaseConnectedTest) DisabledIfEnvironmentVariable(org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable)

Example 2 with ControlledResourceIamRole

use of bio.terra.workspace.service.iam.model.ControlledResourceIamRole in project terra-workspace-manager by DataBiosphere.

the class CopyGcsBucketDefinitionStep method doStep.

@Override
public StepResult doStep(FlightContext flightContext) throws InterruptedException, RetryException {
    final FlightMap inputParameters = flightContext.getInputParameters();
    final FlightMap workingMap = flightContext.getWorkingMap();
    final CloningInstructions cloningInstructions = Optional.ofNullable(inputParameters.get(ControlledResourceKeys.CLONING_INSTRUCTIONS, CloningInstructions.class)).orElse(sourceBucket.getCloningInstructions());
    // future steps need the resolved cloning instructions
    workingMap.put(ControlledResourceKeys.CLONING_INSTRUCTIONS, cloningInstructions);
    if (CloningInstructions.COPY_NOTHING.equals(cloningInstructions)) {
        final ApiClonedControlledGcpGcsBucket noOpResult = new ApiClonedControlledGcpGcsBucket().effectiveCloningInstructions(cloningInstructions.toApiModel()).bucket(null).sourceWorkspaceId(sourceBucket.getWorkspaceId()).sourceResourceId(sourceBucket.getResourceId());
        FlightUtils.setResponse(flightContext, noOpResult, HttpStatus.OK);
        return StepResult.getStepResultSuccess();
    }
    // todo: handle COPY_REFERENCE PF-811, PF-812
    final String resourceName = FlightUtils.getInputParameterOrWorkingValue(flightContext, ResourceKeys.RESOURCE_NAME, ResourceKeys.PREVIOUS_RESOURCE_NAME, String.class);
    final String description = FlightUtils.getInputParameterOrWorkingValue(flightContext, ResourceKeys.RESOURCE_DESCRIPTION, ResourceKeys.PREVIOUS_RESOURCE_DESCRIPTION, String.class);
    final String bucketName = Optional.ofNullable(inputParameters.get(ControlledResourceKeys.DESTINATION_BUCKET_NAME, String.class)).orElseGet(this::randomBucketName);
    final PrivateResourceState privateResourceState = sourceBucket.getAccessScope() == AccessScopeType.ACCESS_SCOPE_PRIVATE ? PrivateResourceState.INITIALIZING : PrivateResourceState.NOT_APPLICABLE;
    // Store effective bucket name for destination
    workingMap.put(ControlledResourceKeys.DESTINATION_BUCKET_NAME, bucketName);
    final UUID destinationWorkspaceId = inputParameters.get(ControlledResourceKeys.DESTINATION_WORKSPACE_ID, UUID.class);
    // bucket resource for create flight
    ControlledResourceFields commonFields = ControlledResourceFields.builder().workspaceId(destinationWorkspaceId).resourceId(// random ID for new resource
    UUID.randomUUID()).name(resourceName).description(description).cloningInstructions(sourceBucket.getCloningInstructions()).assignedUser(sourceBucket.getAssignedUser().orElse(null)).accessScope(sourceBucket.getAccessScope()).managedBy(sourceBucket.getManagedBy()).applicationId(sourceBucket.getApplicationId()).privateResourceState(privateResourceState).build();
    ControlledGcsBucketResource destinationBucketResource = ControlledGcsBucketResource.builder().bucketName(bucketName).common(commonFields).build();
    final ApiGcpGcsBucketCreationParameters destinationCreationParameters = getDestinationCreationParameters(inputParameters, workingMap);
    final ControlledResourceIamRole iamRole = IamRoleUtils.getIamRoleForAccessScope(sourceBucket.getAccessScope());
    // Launch a CreateControlledResourcesFlight to make the destination bucket
    final ControlledGcsBucketResource clonedBucket = controlledResourceService.createControlledResourceSync(destinationBucketResource, iamRole, userRequest, destinationCreationParameters).castByEnum(WsmResourceType.CONTROLLED_GCP_GCS_BUCKET);
    workingMap.put(ControlledResourceKeys.CLONED_RESOURCE_DEFINITION, clonedBucket);
    final ApiCreatedControlledGcpGcsBucket apiCreatedBucket = new ApiCreatedControlledGcpGcsBucket().gcpBucket(clonedBucket.toApiResource()).resourceId(destinationBucketResource.getResourceId());
    final ApiClonedControlledGcpGcsBucket apiBucketResult = new ApiClonedControlledGcpGcsBucket().effectiveCloningInstructions(cloningInstructions.toApiModel()).bucket(apiCreatedBucket).sourceWorkspaceId(sourceBucket.getWorkspaceId()).sourceResourceId(sourceBucket.getResourceId());
    workingMap.put(ControlledResourceKeys.CLONE_DEFINITION_RESULT, apiBucketResult);
    if (cloningInstructions.equals(CloningInstructions.COPY_DEFINITION)) {
        FlightUtils.setResponse(flightContext, apiBucketResult, HttpStatus.OK);
    }
    return StepResult.getStepResultSuccess();
}
Also used : CloningInstructions(bio.terra.workspace.service.resource.model.CloningInstructions) FlightMap(bio.terra.stairway.FlightMap) ApiClonedControlledGcpGcsBucket(bio.terra.workspace.generated.model.ApiClonedControlledGcpGcsBucket) ControlledResourceIamRole(bio.terra.workspace.service.iam.model.ControlledResourceIamRole) UUID(java.util.UUID) ControlledResourceFields(bio.terra.workspace.service.resource.controlled.model.ControlledResourceFields) ApiGcpGcsBucketCreationParameters(bio.terra.workspace.generated.model.ApiGcpGcsBucketCreationParameters) ControlledGcsBucketResource(bio.terra.workspace.service.resource.controlled.cloud.gcp.gcsbucket.ControlledGcsBucketResource) ApiCreatedControlledGcpGcsBucket(bio.terra.workspace.generated.model.ApiCreatedControlledGcpGcsBucket) PrivateResourceState(bio.terra.workspace.service.resource.controlled.model.PrivateResourceState)

Example 3 with ControlledResourceIamRole

use of bio.terra.workspace.service.iam.model.ControlledResourceIamRole in project terra-workspace-manager by DataBiosphere.

the class ClaimUserPrivateResourcesStep method doStep.

@Override
public StepResult doStep(FlightContext context) throws InterruptedException, RetryException {
    FlightMap workingMap = context.getWorkingMap();
    boolean userStillInWorkspace = workingMap.get(ControlledResourceKeys.REMOVED_USER_IS_WORKSPACE_MEMBER, Boolean.class);
    // to private resources.
    if (userStillInWorkspace) {
        return StepResult.getStepResultSuccess();
    }
    // Read the list of resources this user owns from WSM's DB which are not being cleaned up by
    // other flights and indicate this flight is cleaning them up.
    List<ControlledResource> userResources = resourceDao.claimCleanupForWorkspacePrivateResources(workspaceId, userEmail, context.getFlightId());
    // For each private resource, query Sam to find the roles the user has.
    List<ResourceRolePair> resourceRolesToRemove = new ArrayList<>();
    for (ControlledResource resource : userResources) {
        List<ControlledResourceIamRole> userRoles = samService.getUserRolesOnPrivateResource(resource, userEmail, userRequest);
        for (ControlledResourceIamRole role : userRoles) {
            resourceRolesToRemove.add(new ResourceRolePair(resource, role));
        }
    }
    workingMap.put(ControlledResourceKeys.RESOURCE_ROLES_TO_REMOVE, resourceRolesToRemove);
    return StepResult.getStepResultSuccess();
}
Also used : ControlledResource(bio.terra.workspace.service.resource.controlled.model.ControlledResource) ArrayList(java.util.ArrayList) FlightMap(bio.terra.stairway.FlightMap) ControlledResourceIamRole(bio.terra.workspace.service.iam.model.ControlledResourceIamRole)

Example 4 with ControlledResourceIamRole

use of bio.terra.workspace.service.iam.model.ControlledResourceIamRole in project terra-workspace-manager by DataBiosphere.

the class SamService method getUserRolesOnPrivateResource.

/**
 * Return the list of roles a user has directly on a private, user-managed controlled resource.
 * This will not return roles that a user holds via group membership.
 *
 * <p>This call to Sam is made as the WSM SA, as users do not have permission to directly modify
 * IAM on resources. This method still requires user credentials to validate as a safeguard, but
 * they are not used in the role removal call.
 *
 * @param resource The resource to fetch roles on
 * @param userEmail Email identifier of the user whose role is being removed.
 * @param userRequest User credentials. These are not used for the call to Sam, but must belong to
 *     a workspace owner to ensure the WSM SA is being used on a user's behalf correctly.
 */
public List<ControlledResourceIamRole> getUserRolesOnPrivateResource(ControlledResource resource, String userEmail, AuthenticatedUserRequest userRequest) throws InterruptedException {
    // Validate that the provided user credentials can modify the owners of the resource's
    // workspace.
    // Although the Sam call to revoke a resource role must use WSM SA credentials instead, this
    // is a safeguard against accidentally invoking these credentials for unauthorized users.
    checkAuthz(userRequest, SamConstants.SamResource.WORKSPACE, resource.getWorkspaceId().toString(), samActionToModifyRole(WsmIamRole.OWNER));
    try {
        ResourcesApi wsmSaResourceApi = samResourcesApi(getWsmServiceAccountToken());
        List<AccessPolicyResponseEntryV2> policyList = wsmSaResourceApi.listResourcePoliciesV2(resource.getCategory().getSamResourceName(), resource.getResourceId().toString());
        return policyList.stream().filter(policyEntry -> policyEntry.getPolicy().getMemberEmails().contains(userEmail)).map(AccessPolicyResponseEntryV2::getPolicyName).map(ControlledResourceIamRole::fromSamRole).collect(Collectors.toList());
    } catch (ApiException apiException) {
        throw SamExceptionFactory.create("Sam error removing resource role in Sam", apiException);
    }
}
Also used : RoleBinding(bio.terra.workspace.service.iam.model.RoleBinding) CreateResourceRequestV2(org.broadinstitute.dsde.workbench.client.sam.model.CreateResourceRequestV2) WsmIamRole(bio.terra.workspace.service.iam.model.WsmIamRole) ControlledResource(bio.terra.workspace.service.resource.controlled.model.ControlledResource) StatusApi(org.broadinstitute.dsde.workbench.client.sam.api.StatusApi) LoggerFactory(org.slf4j.LoggerFactory) Autowired(org.springframework.beans.factory.annotation.Autowired) InternalServerErrorException(bio.terra.common.exception.InternalServerErrorException) SamRetry(bio.terra.common.sam.SamRetry) Map(java.util.Map) GoogleApi(org.broadinstitute.dsde.workbench.client.sam.api.GoogleApi) AccessPolicyResponseEntryV2(org.broadinstitute.dsde.workbench.client.sam.model.AccessPolicyResponseEntryV2) ImmutableSet(com.google.common.collect.ImmutableSet) ServiceAccountName(bio.terra.cloudres.google.iam.ServiceAccountName) Set(java.util.Set) FullyQualifiedResourceId(org.broadinstitute.dsde.workbench.client.sam.model.FullyQualifiedResourceId) UUID(java.util.UUID) SamWorkspaceAction(bio.terra.workspace.service.iam.model.SamConstants.SamWorkspaceAction) Collectors(java.util.stream.Collectors) ControlledResourceCategory(bio.terra.workspace.service.resource.controlled.model.ControlledResourceCategory) SamExceptionFactory(bio.terra.common.sam.exception.SamExceptionFactory) List(java.util.List) ControlledResourceIamRole(bio.terra.workspace.service.iam.model.ControlledResourceIamRole) Optional(java.util.Optional) SystemStatus(org.broadinstitute.dsde.workbench.client.sam.model.SystemStatus) SamConfiguration(bio.terra.workspace.app.configuration.external.SamConfiguration) HashMap(java.util.HashMap) ApiException(org.broadinstitute.dsde.workbench.client.sam.ApiException) GcpUtils(bio.terra.workspace.common.utils.GcpUtils) ArrayList(java.util.ArrayList) SamConstants(bio.terra.workspace.service.iam.model.SamConstants) ImmutableList(com.google.common.collect.ImmutableList) AccessPolicyMembershipV2(org.broadinstitute.dsde.workbench.client.sam.model.AccessPolicyMembershipV2) InternalLogicException(bio.terra.workspace.common.exception.InternalLogicException) Traced(io.opencensus.contrib.spring.aop.Traced) ResourcesApi(org.broadinstitute.dsde.workbench.client.sam.api.ResourcesApi) Nullable(javax.annotation.Nullable) AccessPolicyResponseEntry(org.broadinstitute.dsde.workbench.client.sam.model.AccessPolicyResponseEntry) Logger(org.slf4j.Logger) GoogleCredentials(com.google.auth.oauth2.GoogleCredentials) ApiClient(org.broadinstitute.dsde.workbench.client.sam.ApiClient) IOException(java.io.IOException) ResourceAndAccessPolicy(org.broadinstitute.dsde.workbench.client.sam.model.ResourceAndAccessPolicy) ForbiddenException(bio.terra.common.exception.ForbiddenException) HttpStatus(org.springframework.http.HttpStatus) Component(org.springframework.stereotype.Component) OkHttpClient(okhttp3.OkHttpClient) UsersApi(org.broadinstitute.dsde.workbench.client.sam.api.UsersApi) VisibleForTesting(com.google.common.annotations.VisibleForTesting) StageService(bio.terra.workspace.service.stage.StageService) ResourcesApi(org.broadinstitute.dsde.workbench.client.sam.api.ResourcesApi) AccessPolicyResponseEntryV2(org.broadinstitute.dsde.workbench.client.sam.model.AccessPolicyResponseEntryV2) ApiException(org.broadinstitute.dsde.workbench.client.sam.ApiException)

Example 5 with ControlledResourceIamRole

use of bio.terra.workspace.service.iam.model.ControlledResourceIamRole in project terra-workspace-manager by DataBiosphere.

the class ControllerBase method computePrivateUserRole.

/**
 * Validate and provide defaulting for the private resource user. The property is never required.
 * The only time it is allowed is for application-private resources. If it is populated, we
 * validate the user email and the specified IAM roles.
 *
 * <p>user-private resources are always assigned to the caller. You can't create a user-private
 * resource and assign it to someone else. Because we can read the caller's email from the
 * AuthenticatedUserRequest, we don't need to supply assignedUser in the request body.
 *
 * <p>application-private resources can be assigned to users other than the caller. For example,
 * Leo could call WSM to create a VM (using the Leo SA's auth token) and request it be assigned to
 * user X, not to the Leo SA.
 *
 * @param commonFields common fields from a controlled resource create request
 * @param userRequest authenticate user
 * @return PrivateUserRole holding the user email and the role list
 */
public PrivateUserRole computePrivateUserRole(UUID workspaceId, ApiControlledResourceCommonFields commonFields, AuthenticatedUserRequest userRequest) {
    AccessScopeType accessScope = AccessScopeType.fromApi(commonFields.getAccessScope());
    ManagedByType managedBy = ManagedByType.fromApi(commonFields.getManagedBy());
    ApiPrivateResourceUser inputUser = commonFields.getPrivateResourceUser();
    // Shared access has no private user role
    if (accessScope == AccessScopeType.ACCESS_SCOPE_SHARED) {
        validateNoInputUser(inputUser);
        return new PrivateUserRole.Builder().present(false).build();
    }
    // Private access scope
    switch(managedBy) {
        case MANAGED_BY_APPLICATION:
            {
                // Supplying a user is optional for applications
                if (inputUser == null) {
                    return new PrivateUserRole.Builder().present(false).build();
                }
                // We have a private user, so make sure the email is present and valid
                String userEmail = commonFields.getPrivateResourceUser().getUserName();
                ControllerValidationUtils.validateEmail(userEmail);
                // Validate that the assigned user is a member of the workspace. It must have at least
                // READ action.
                SamRethrow.onInterrupted(() -> samService.userIsAuthorized(SamConstants.SamResource.WORKSPACE, workspaceId.toString(), SamConstants.SamWorkspaceAction.READ, userEmail, userRequest), "validate private user is workspace member");
                // Translate the incoming role list into our internal model form
                // This also validates that the incoming API model values are correct.
                List<ControlledResourceIamRole> roles = commonFields.getPrivateResourceUser().getPrivateResourceIamRoles().stream().map(ControlledResourceIamRole::fromApiModel).collect(Collectors.toList());
                if (roles.isEmpty()) {
                    throw new ValidationException("You must specify at least one role when you specify PrivateResourceIamRoles");
                }
                // The legal options for the assigned user of an application is READER
                // or WRITER. EDITOR is not allowed. We take the "max" of READER and WRITER.
                var maxRole = ControlledResourceIamRole.READER;
                for (ControlledResourceIamRole role : roles) {
                    if (role == ControlledResourceIamRole.WRITER) {
                        if (maxRole == ControlledResourceIamRole.READER) {
                            maxRole = role;
                        }
                    } else if (role != ControlledResourceIamRole.READER) {
                        throw new ValidationException("For application private controlled resources, only READER and WRITER roles are allowed. Found " + role.toApiModel());
                    }
                }
                return new PrivateUserRole.Builder().present(true).userEmail(userEmail).role(maxRole).build();
            }
        case MANAGED_BY_USER:
            {
                // TODO: PF-1218 The target state is that supplying a user is not allowed.
                // However, current CLI and maybe UI are supplying all or part of the structure,
                // so tolerate all states: no-input, only roles, roles and user
                /* Target state:
          // Supplying a user is not allowed. The creating user is always the assigned user.
          validateNoInputUser(inputUser);
          */
                // Fill in the user role for the creating user
                String userEmail = SamRethrow.onInterrupted(() -> samService.getUserEmailFromSam(userRequest), "getUserEmailFromSam");
                // matches the requesting name.
                if (inputUser != null && inputUser.getUserName() != null) {
                    if (!StringUtils.equalsIgnoreCase(userEmail, inputUser.getUserName())) {
                        throw new BadRequestException("User (" + userEmail + ") may only assign a private controlled resource to themselves");
                    }
                }
                // to different objects.
                return new PrivateUserRole.Builder().present(true).userEmail(userEmail).role(ControlledResourceIamRole.EDITOR).build();
            }
        default:
            throw new InternalLogicException("Unknown managedBy enum");
    }
}
Also used : ManagedByType(bio.terra.workspace.service.resource.controlled.model.ManagedByType) ValidationException(bio.terra.common.exception.ValidationException) InternalLogicException(bio.terra.workspace.common.exception.InternalLogicException) AccessScopeType(bio.terra.workspace.service.resource.controlled.model.AccessScopeType) ApiPrivateResourceUser(bio.terra.workspace.generated.model.ApiPrivateResourceUser) BadRequestException(bio.terra.common.exception.BadRequestException) List(java.util.List) ControlledResourceIamRole(bio.terra.workspace.service.iam.model.ControlledResourceIamRole) PrivateUserRole(bio.terra.workspace.service.resource.controlled.model.PrivateUserRole)

Aggregations

ControlledResourceIamRole (bio.terra.workspace.service.iam.model.ControlledResourceIamRole)7 FlightMap (bio.terra.stairway.FlightMap)3 InternalLogicException (bio.terra.workspace.common.exception.InternalLogicException)3 UUID (java.util.UUID)3 BadRequestException (bio.terra.common.exception.BadRequestException)2 ControlledResource (bio.terra.workspace.service.resource.controlled.model.ControlledResource)2 ControlledResourceFields (bio.terra.workspace.service.resource.controlled.model.ControlledResourceFields)2 CloningInstructions (bio.terra.workspace.service.resource.model.CloningInstructions)2 ArrayList (java.util.ArrayList)2 List (java.util.List)2 AccessPolicyMembershipV2 (org.broadinstitute.dsde.workbench.client.sam.model.AccessPolicyMembershipV2)2 ServiceAccountName (bio.terra.cloudres.google.iam.ServiceAccountName)1 ForbiddenException (bio.terra.common.exception.ForbiddenException)1 InternalServerErrorException (bio.terra.common.exception.InternalServerErrorException)1 ValidationException (bio.terra.common.exception.ValidationException)1 SamRetry (bio.terra.common.sam.SamRetry)1 SamExceptionFactory (bio.terra.common.sam.exception.SamExceptionFactory)1 SamConfiguration (bio.terra.workspace.app.configuration.external.SamConfiguration)1 BaseConnectedTest (bio.terra.workspace.common.BaseConnectedTest)1 GcpUtils (bio.terra.workspace.common.utils.GcpUtils)1