Search in sources :

Example 6 with SimulatePrincipalPolicyResult

use of com.amazonaws.services.identitymanagement.model.SimulatePrincipalPolicyResult in project cloudbreak by hortonworks.

the class AwsIDBrokerAssumeRoleValidatorTest method checkCannotAssumeRoles.

@Test
public void checkCannotAssumeRoles() {
    Role instanceProfileRole = new Role();
    InstanceProfile instanceProfile = new InstanceProfile().withArn("instanceProfileArn").withRoles(instanceProfileRole);
    Role role = new Role().withArn("roleArn");
    Collection<Role> roles = Collections.singletonList(role);
    EvaluationResult evalResult = new EvaluationResult().withEvalDecision(PolicyEvaluationDecisionType.ImplicitDeny);
    when(iam.simulatePrincipalPolicy(any(SimulatePrincipalPolicyRequest.class))).thenReturn(new SimulatePrincipalPolicyResult().withEvaluationResults(evalResult));
    ValidationResultBuilder validationResultBuilder = new ValidationResultBuilder();
    assertThat(awsIDBrokerAssumeRoleValidator.canAssumeRoles(iam, instanceProfile, roles, validationResultBuilder)).isFalse();
    ValidationResult validationResult = validationResultBuilder.build();
    assertThat(validationResult.hasError()).isTrue();
    assertThat(validationResult.getErrors()).isEqualTo(Collections.singletonList(String.format("Data Access Instance profile (%s) doesn't have permissions to assume the role(s): %s. " + "Please check if you've used the correct Instance profile when setting up Data Access.", instanceProfile.getArn(), Collections.singletonList(role.getArn()))));
}
Also used : Role(com.amazonaws.services.identitymanagement.model.Role) InstanceProfile(com.amazonaws.services.identitymanagement.model.InstanceProfile) ValidationResultBuilder(com.sequenceiq.cloudbreak.validation.ValidationResult.ValidationResultBuilder) SimulatePrincipalPolicyRequest(com.amazonaws.services.identitymanagement.model.SimulatePrincipalPolicyRequest) ValidationResult(com.sequenceiq.cloudbreak.validation.ValidationResult) SimulatePrincipalPolicyResult(com.amazonaws.services.identitymanagement.model.SimulatePrincipalPolicyResult) EvaluationResult(com.amazonaws.services.identitymanagement.model.EvaluationResult) Test(org.junit.jupiter.api.Test)

Example 7 with SimulatePrincipalPolicyResult

use of com.amazonaws.services.identitymanagement.model.SimulatePrincipalPolicyResult in project cloudbreak by hortonworks.

the class AwsCredentialVerifier method validateAws.

@Cacheable(value = AwsCredentialCachingConfig.TEMPORARY_AWS_CREDENTIAL_VERIFIER_CACHE, unless = "#awsCredential == null")
public void validateAws(AwsCredentialView awsCredential, String policyJson) throws AwsPermissionMissingException {
    String policies = new String(Base64.getDecoder().decode(policyJson));
    try {
        List<RequiredAction> resourcesWithActions = getRequiredActions(policies);
        AmazonIdentityManagementClient amazonIdentityManagement = awsClient.createAmazonIdentityManagement(awsCredential);
        AmazonSecurityTokenServiceClient awsSecurityTokenService = awsClient.createSecurityTokenService(awsCredential);
        String arn;
        if (awsCredential.getRoleArn() != null) {
            arn = awsCredential.getRoleArn();
        } else {
            GetCallerIdentityResult callerIdentity = awsSecurityTokenService.getCallerIdentity(new GetCallerIdentityRequest());
            arn = callerIdentity.getArn();
        }
        List<String> failedActionList = new ArrayList<>();
        for (RequiredAction resourceAndAction : resourcesWithActions) {
            SimulatePrincipalPolicyRequest simulatePrincipalPolicyRequest = new SimulatePrincipalPolicyRequest();
            simulatePrincipalPolicyRequest.setMaxItems(MAX_ELEMENT_SIZE);
            simulatePrincipalPolicyRequest.setPolicySourceArn(arn);
            simulatePrincipalPolicyRequest.setActionNames(resourceAndAction.getActionNames());
            simulatePrincipalPolicyRequest.setResourceArns(Collections.singleton(resourceAndAction.getResourceArn()));
            simulatePrincipalPolicyRequest.setContextEntries(resourceAndAction.getConditions());
            LOGGER.debug("Simulate policy request: {}", simulatePrincipalPolicyRequest);
            SimulatePrincipalPolicyResult simulatePrincipalPolicyResult = amazonIdentityManagement.simulatePrincipalPolicy(simulatePrincipalPolicyRequest);
            LOGGER.debug("Simulate policy result: {}", simulatePrincipalPolicyResult);
            simulatePrincipalPolicyResult.getEvaluationResults().stream().filter(evaluationResult -> evaluationResult.getEvalDecision().toLowerCase().contains("deny")).map(evaluationResult -> {
                if (evaluationResult.getOrganizationsDecisionDetail() != null && !evaluationResult.getOrganizationsDecisionDetail().getAllowedByOrganizations()) {
                    return evaluationResult.getEvalActionName() + " : " + evaluationResult.getEvalResourceName() + " -> Denied by Organization Rule";
                } else {
                    return evaluationResult.getEvalActionName() + " : " + evaluationResult.getEvalResourceName();
                }
            }).forEach(failedActionList::add);
        }
        if (!failedActionList.isEmpty()) {
            throw new AwsPermissionMissingException(String.format("CDP Credential '%s' doesn't have permission for these actions which are required: %s", awsCredential.getName(), failedActionList.stream().collect(joining(", ", "[ ", " ]"))));
        }
    } catch (IOException e) {
        throw new IllegalStateException("Can not parse aws policy json", e);
    }
}
Also used : Policy(com.amazonaws.auth.policy.Policy) AwsCredentialCachingConfig(com.sequenceiq.cloudbreak.cloud.aws.common.cache.AwsCredentialCachingConfig) Action(com.amazonaws.auth.policy.Action) Cacheable(org.springframework.cache.annotation.Cacheable) LoggerFactory(org.slf4j.LoggerFactory) SimulatePrincipalPolicyRequest(com.amazonaws.services.identitymanagement.model.SimulatePrincipalPolicyRequest) ContextEntry(com.amazonaws.services.identitymanagement.model.ContextEntry) ArrayList(java.util.ArrayList) AwsPermissionMissingException(com.sequenceiq.cloudbreak.cloud.aws.common.exception.AwsPermissionMissingException) Inject(javax.inject.Inject) AwsCredentialView(com.sequenceiq.cloudbreak.cloud.aws.common.view.AwsCredentialView) Service(org.springframework.stereotype.Service) ContextKeyTypeEnum(com.amazonaws.services.identitymanagement.model.ContextKeyTypeEnum) AmazonIdentityManagementClient(com.sequenceiq.cloudbreak.cloud.aws.common.client.AmazonIdentityManagementClient) Statement(com.amazonaws.auth.policy.Statement) GetCallerIdentityResult(com.amazonaws.services.securitytoken.model.GetCallerIdentityResult) Logger(org.slf4j.Logger) JsonPolicyReader(com.amazonaws.auth.policy.internal.JsonPolicyReader) IOException(java.io.IOException) Collectors(java.util.stream.Collectors) Collectors.joining(java.util.stream.Collectors.joining) GetCallerIdentityRequest(com.amazonaws.services.securitytoken.model.GetCallerIdentityRequest) SimulatePrincipalPolicyResult(com.amazonaws.services.identitymanagement.model.SimulatePrincipalPolicyResult) Base64(java.util.Base64) List(java.util.List) AmazonSecurityTokenServiceClient(com.sequenceiq.cloudbreak.cloud.aws.common.client.AmazonSecurityTokenServiceClient) Optional(java.util.Optional) Collections(java.util.Collections) Condition(com.amazonaws.auth.policy.Condition) AwsPermissionMissingException(com.sequenceiq.cloudbreak.cloud.aws.common.exception.AwsPermissionMissingException) AmazonIdentityManagementClient(com.sequenceiq.cloudbreak.cloud.aws.common.client.AmazonIdentityManagementClient) ArrayList(java.util.ArrayList) IOException(java.io.IOException) SimulatePrincipalPolicyRequest(com.amazonaws.services.identitymanagement.model.SimulatePrincipalPolicyRequest) GetCallerIdentityRequest(com.amazonaws.services.securitytoken.model.GetCallerIdentityRequest) AmazonSecurityTokenServiceClient(com.sequenceiq.cloudbreak.cloud.aws.common.client.AmazonSecurityTokenServiceClient) SimulatePrincipalPolicyResult(com.amazonaws.services.identitymanagement.model.SimulatePrincipalPolicyResult) GetCallerIdentityResult(com.amazonaws.services.securitytoken.model.GetCallerIdentityResult) Cacheable(org.springframework.cache.annotation.Cacheable)

Example 8 with SimulatePrincipalPolicyResult

use of com.amazonaws.services.identitymanagement.model.SimulatePrincipalPolicyResult in project cloudbreak by hortonworks.

the class AwsCredentialVerifierTest method verifyCredentialAndThrowFailExceptionTest.

@Test
public void verifyCredentialAndThrowFailExceptionTest() throws IOException {
    URL url = Resources.getResource("definitions/aws-environment-minimal-policy.json");
    String awsEnvPolicy = Resources.toString(url, Charsets.UTF_8);
    String encodedAwsEnvPolicy = Base64.getEncoder().encodeToString(awsEnvPolicy.getBytes());
    Map<String, Object> awsParameters = new HashMap<>();
    awsParameters.put("accessKey", "a");
    awsParameters.put("secretKey", "b");
    CloudCredential cloudCredential = new CloudCredential("id", "name", awsParameters, "acc", false);
    AmazonIdentityManagementClient amazonIdentityManagement = mock(AmazonIdentityManagementClient.class);
    when(awsClient.createAmazonIdentityManagement(any(AwsCredentialView.class))).thenReturn(amazonIdentityManagement);
    AmazonSecurityTokenServiceClient awsSecurityTokenService = mock(AmazonSecurityTokenServiceClient.class);
    GetCallerIdentityResult getCallerIdentityResult = new GetCallerIdentityResult();
    getCallerIdentityResult.setArn("arn");
    when(awsSecurityTokenService.getCallerIdentity(any(GetCallerIdentityRequest.class))).thenReturn(getCallerIdentityResult);
    when(awsClient.createSecurityTokenService(any(AwsCredentialView.class))).thenReturn(awsSecurityTokenService);
    ArgumentCaptor<SimulatePrincipalPolicyRequest> requestArgumentCaptor = ArgumentCaptor.forClass(SimulatePrincipalPolicyRequest.class);
    AtomicInteger i = new AtomicInteger();
    when(amazonIdentityManagement.simulatePrincipalPolicy(requestArgumentCaptor.capture())).thenAnswer(invocation -> {
        SimulatePrincipalPolicyResult simulatePrincipalPolicyResult = new SimulatePrincipalPolicyResult();
        ArrayList<EvaluationResult> evaluationResults = new ArrayList<>();
        evaluationResults.add(new EvaluationResult().withEvalDecision("deny").withOrganizationsDecisionDetail(new OrganizationsDecisionDetail().withAllowedByOrganizations(true)).withEvalActionName("denied_action1_" + i).withEvalResourceName("aws:ec2"));
        evaluationResults.add(new EvaluationResult().withEvalDecision("deny").withOrganizationsDecisionDetail(new OrganizationsDecisionDetail().withAllowedByOrganizations(true)).withEvalActionName("denied_action2_" + i).withEvalResourceName("aws:ec2"));
        evaluationResults.add(new EvaluationResult().withEvalDecision("deny").withOrganizationsDecisionDetail(new OrganizationsDecisionDetail().withAllowedByOrganizations(true)).withEvalActionName("denied_action3_" + i).withEvalResourceName("aws:ec2"));
        evaluationResults.add(new EvaluationResult().withEvalDecision("accept").withOrganizationsDecisionDetail(new OrganizationsDecisionDetail().withAllowedByOrganizations(true)).withEvalActionName("accepted_action_" + i).withEvalResourceName("*"));
        simulatePrincipalPolicyResult.setEvaluationResults(evaluationResults);
        i.getAndIncrement();
        return simulatePrincipalPolicyResult;
    });
    try {
        awsCredentialVerifier.validateAws(new AwsCredentialView(cloudCredential), encodedAwsEnvPolicy);
        fail("It shoud throw verification exception");
    } catch (AwsPermissionMissingException e) {
        assertThat(e.getMessage(), CoreMatchers.containsString("denied_action1"));
        assertThat(e.getMessage(), CoreMatchers.containsString("denied_action2"));
        assertThat(e.getMessage(), CoreMatchers.containsString("denied_action3"));
        assertThat(e.getMessage(), not(CoreMatchers.containsString("accepted_action")));
    }
    List<SimulatePrincipalPolicyRequest> allSimulatePrincipalPolicyRequest = requestArgumentCaptor.getAllValues();
    int simulateRequestNumber = 5;
    assertEquals("expect if " + simulateRequestNumber + " simulate request has been sent", simulateRequestNumber, allSimulatePrincipalPolicyRequest.size());
    allSimulatePrincipalPolicyRequest.forEach(simulatePrincipalPolicyRequest -> assertEquals("arn", simulatePrincipalPolicyRequest.getPolicySourceArn()));
}
Also used : AwsPermissionMissingException(com.sequenceiq.cloudbreak.cloud.aws.common.exception.AwsPermissionMissingException) AmazonIdentityManagementClient(com.sequenceiq.cloudbreak.cloud.aws.common.client.AmazonIdentityManagementClient) HashMap(java.util.HashMap) CloudCredential(com.sequenceiq.cloudbreak.cloud.model.CloudCredential) OrganizationsDecisionDetail(com.amazonaws.services.identitymanagement.model.OrganizationsDecisionDetail) ArrayList(java.util.ArrayList) URL(java.net.URL) EvaluationResult(com.amazonaws.services.identitymanagement.model.EvaluationResult) AwsCredentialView(com.sequenceiq.cloudbreak.cloud.aws.common.view.AwsCredentialView) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) SimulatePrincipalPolicyRequest(com.amazonaws.services.identitymanagement.model.SimulatePrincipalPolicyRequest) GetCallerIdentityRequest(com.amazonaws.services.securitytoken.model.GetCallerIdentityRequest) AmazonSecurityTokenServiceClient(com.sequenceiq.cloudbreak.cloud.aws.common.client.AmazonSecurityTokenServiceClient) SimulatePrincipalPolicyResult(com.amazonaws.services.identitymanagement.model.SimulatePrincipalPolicyResult) GetCallerIdentityResult(com.amazonaws.services.securitytoken.model.GetCallerIdentityResult) Test(org.junit.Test)

Example 9 with SimulatePrincipalPolicyResult

use of com.amazonaws.services.identitymanagement.model.SimulatePrincipalPolicyResult in project cloudbreak by hortonworks.

the class AwsCredentialVerifierTest method verifyCredentialAndOrganizatioDecisionDetailIsNullTest.

@Test
public void verifyCredentialAndOrganizatioDecisionDetailIsNullTest() throws IOException {
    URL url = Resources.getResource("definitions/aws-environment-minimal-policy.json");
    String awsEnvPolicy = Resources.toString(url, Charsets.UTF_8);
    String encodedAwsEnvPolicy = Base64.getEncoder().encodeToString(awsEnvPolicy.getBytes());
    Map<String, Object> awsParameters = new HashMap<>();
    awsParameters.put("accessKey", "a");
    awsParameters.put("secretKey", "b");
    CloudCredential cloudCredential = new CloudCredential("id", "name", awsParameters, "acc", false);
    AmazonIdentityManagementClient amazonIdentityManagement = mock(AmazonIdentityManagementClient.class);
    when(awsClient.createAmazonIdentityManagement(any(AwsCredentialView.class))).thenReturn(amazonIdentityManagement);
    AmazonSecurityTokenServiceClient awsSecurityTokenService = mock(AmazonSecurityTokenServiceClient.class);
    GetCallerIdentityResult getCallerIdentityResult = new GetCallerIdentityResult();
    getCallerIdentityResult.setArn("arn");
    when(awsSecurityTokenService.getCallerIdentity(any(GetCallerIdentityRequest.class))).thenReturn(getCallerIdentityResult);
    when(awsClient.createSecurityTokenService(any(AwsCredentialView.class))).thenReturn(awsSecurityTokenService);
    ArgumentCaptor<SimulatePrincipalPolicyRequest> requestArgumentCaptor = ArgumentCaptor.forClass(SimulatePrincipalPolicyRequest.class);
    AtomicInteger i = new AtomicInteger();
    when(amazonIdentityManagement.simulatePrincipalPolicy(requestArgumentCaptor.capture())).thenAnswer(invocation -> {
        SimulatePrincipalPolicyResult simulatePrincipalPolicyResult = new SimulatePrincipalPolicyResult();
        ArrayList<EvaluationResult> evaluationResults = new ArrayList<>();
        evaluationResults.add(new EvaluationResult().withEvalDecision("deny").withOrganizationsDecisionDetail(null).withEvalActionName("denied_action1_" + i).withEvalResourceName("aws:ec2"));
        evaluationResults.add(new EvaluationResult().withEvalDecision("deny").withOrganizationsDecisionDetail(null).withEvalActionName("denied_action2_" + i).withEvalResourceName("aws:ec2"));
        evaluationResults.add(new EvaluationResult().withEvalDecision("deny").withOrganizationsDecisionDetail(null).withEvalActionName("denied_action3_" + i).withEvalResourceName("aws:ec2"));
        evaluationResults.add(new EvaluationResult().withEvalDecision("accept").withOrganizationsDecisionDetail(null).withEvalActionName("accepted_action_" + i).withEvalResourceName("*"));
        simulatePrincipalPolicyResult.setEvaluationResults(evaluationResults);
        i.getAndIncrement();
        return simulatePrincipalPolicyResult;
    });
    try {
        awsCredentialVerifier.validateAws(new AwsCredentialView(cloudCredential), encodedAwsEnvPolicy);
        fail("It shoud throw verification exception");
    } catch (AwsPermissionMissingException e) {
        assertThat(e.getMessage(), CoreMatchers.containsString("denied_action1_0 : aws:ec2,"));
        assertThat(e.getMessage(), CoreMatchers.containsString("denied_action2_0 : aws:ec2,"));
        assertThat(e.getMessage(), CoreMatchers.containsString("denied_action3_0 : aws:ec2,"));
        assertThat(e.getMessage(), not(CoreMatchers.containsString("accepted_action")));
    }
    List<SimulatePrincipalPolicyRequest> allSimulatePrincipalPolicyRequest = requestArgumentCaptor.getAllValues();
    int simulateRequestNumber = 5;
    assertEquals("expect if " + simulateRequestNumber + " simulate request has been sent", simulateRequestNumber, allSimulatePrincipalPolicyRequest.size());
    allSimulatePrincipalPolicyRequest.forEach(simulatePrincipalPolicyRequest -> assertEquals("arn", simulatePrincipalPolicyRequest.getPolicySourceArn()));
}
Also used : AwsPermissionMissingException(com.sequenceiq.cloudbreak.cloud.aws.common.exception.AwsPermissionMissingException) AmazonIdentityManagementClient(com.sequenceiq.cloudbreak.cloud.aws.common.client.AmazonIdentityManagementClient) HashMap(java.util.HashMap) CloudCredential(com.sequenceiq.cloudbreak.cloud.model.CloudCredential) ArrayList(java.util.ArrayList) URL(java.net.URL) EvaluationResult(com.amazonaws.services.identitymanagement.model.EvaluationResult) AwsCredentialView(com.sequenceiq.cloudbreak.cloud.aws.common.view.AwsCredentialView) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) SimulatePrincipalPolicyRequest(com.amazonaws.services.identitymanagement.model.SimulatePrincipalPolicyRequest) GetCallerIdentityRequest(com.amazonaws.services.securitytoken.model.GetCallerIdentityRequest) AmazonSecurityTokenServiceClient(com.sequenceiq.cloudbreak.cloud.aws.common.client.AmazonSecurityTokenServiceClient) SimulatePrincipalPolicyResult(com.amazonaws.services.identitymanagement.model.SimulatePrincipalPolicyResult) GetCallerIdentityResult(com.amazonaws.services.securitytoken.model.GetCallerIdentityResult) Test(org.junit.Test)

Example 10 with SimulatePrincipalPolicyResult

use of com.amazonaws.services.identitymanagement.model.SimulatePrincipalPolicyResult in project cloudbreak by hortonworks.

the class AwsIDBrokerAssumeRoleValidatorTest method checkCannotAssumeOneOfTheRoles.

@Test
public void checkCannotAssumeOneOfTheRoles() {
    Role instanceProfileRole = new Role();
    InstanceProfile instanceProfile = new InstanceProfile().withArn("instanceProfileArn").withRoles(instanceProfileRole);
    Role role1 = new Role().withArn("role1Arn");
    Role role2 = new Role().withArn("role2Arn");
    Collection<Role> roles = Arrays.asList(role1, role2);
    EvaluationResult evalResult1 = new EvaluationResult().withEvalDecision(PolicyEvaluationDecisionType.Allowed).withEvalResourceName(role1.getArn());
    EvaluationResult evalResult2 = new EvaluationResult().withEvalDecision(PolicyEvaluationDecisionType.ImplicitDeny).withEvalResourceName(role2.getArn());
    when(iam.simulatePrincipalPolicy(any(SimulatePrincipalPolicyRequest.class))).thenReturn(new SimulatePrincipalPolicyResult().withEvaluationResults(evalResult1)).thenReturn(new SimulatePrincipalPolicyResult().withEvaluationResults(evalResult2));
    ValidationResultBuilder validationResultBuilder = new ValidationResultBuilder();
    assertThat(awsIDBrokerAssumeRoleValidator.canAssumeRoles(iam, instanceProfile, roles, validationResultBuilder)).isFalse();
    ValidationResult validationResult = validationResultBuilder.build();
    assertThat(validationResult.hasError()).isTrue();
    assertThat(validationResult.getErrors()).isEqualTo(Collections.singletonList(String.format("Data Access Instance profile (%s) doesn't have permissions to assume the role(s): %s. " + "Please check if you've used the correct Instance profile when setting up Data Access.", instanceProfile.getArn(), Collections.singletonList(role2.getArn()))));
}
Also used : Role(com.amazonaws.services.identitymanagement.model.Role) InstanceProfile(com.amazonaws.services.identitymanagement.model.InstanceProfile) ValidationResultBuilder(com.sequenceiq.cloudbreak.validation.ValidationResult.ValidationResultBuilder) ValidationResult(com.sequenceiq.cloudbreak.validation.ValidationResult) SimulatePrincipalPolicyResult(com.amazonaws.services.identitymanagement.model.SimulatePrincipalPolicyResult) EvaluationResult(com.amazonaws.services.identitymanagement.model.EvaluationResult) Test(org.junit.jupiter.api.Test)

Aggregations

SimulatePrincipalPolicyResult (com.amazonaws.services.identitymanagement.model.SimulatePrincipalPolicyResult)11 EvaluationResult (com.amazonaws.services.identitymanagement.model.EvaluationResult)9 SimulatePrincipalPolicyRequest (com.amazonaws.services.identitymanagement.model.SimulatePrincipalPolicyRequest)9 GetCallerIdentityRequest (com.amazonaws.services.securitytoken.model.GetCallerIdentityRequest)5 GetCallerIdentityResult (com.amazonaws.services.securitytoken.model.GetCallerIdentityResult)5 AmazonIdentityManagementClient (com.sequenceiq.cloudbreak.cloud.aws.common.client.AmazonIdentityManagementClient)5 AmazonSecurityTokenServiceClient (com.sequenceiq.cloudbreak.cloud.aws.common.client.AmazonSecurityTokenServiceClient)5 AwsCredentialView (com.sequenceiq.cloudbreak.cloud.aws.common.view.AwsCredentialView)5 ArrayList (java.util.ArrayList)5 Test (org.junit.jupiter.api.Test)5 AwsPermissionMissingException (com.sequenceiq.cloudbreak.cloud.aws.common.exception.AwsPermissionMissingException)4 CloudCredential (com.sequenceiq.cloudbreak.cloud.model.CloudCredential)4 URL (java.net.URL)4 HashMap (java.util.HashMap)4 Test (org.junit.Test)4 Policy (com.amazonaws.auth.policy.Policy)3 InstanceProfile (com.amazonaws.services.identitymanagement.model.InstanceProfile)3 OrganizationsDecisionDetail (com.amazonaws.services.identitymanagement.model.OrganizationsDecisionDetail)3 Role (com.amazonaws.services.identitymanagement.model.Role)3 ValidationResultBuilder (com.sequenceiq.cloudbreak.validation.ValidationResult.ValidationResultBuilder)3