diff --git a/API.md b/API.md index e25767c5c..dc977dd71 100644 --- a/API.md +++ b/API.md @@ -14,7 +14,7 @@ as well as in the [integration tests](https://github.com/Nike-Inc/cerberus-integ ### Authenticate with Cerberus as an App [POST] -This endpoint takes a [signed AWS STS get-caller-identity POST request](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html) and returns a payload containing an auth token needed to access Cerberus. Region is limited to "us-east-1" only and host is limited to "host:sts.amazonaws.com" only during the signing process. +This endpoint takes a [signed AWS STS get-caller-identity POST request](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html) and returns a payload containing an auth token needed to access Cerberus. Host is limited to regional endpoints ("host:sts.{region}.amazonaws.com") during the signing process. + Request (application/json) diff --git a/gradle.properties b/gradle.properties index c99c9dc5d..32f581546 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,6 +14,6 @@ # limitations under the License. # -version=3.25.1 +version=3.25.2 groupId=com.nike.cerberus artifactId=cms diff --git a/src/main/java/com/nike/cerberus/dao/PermissionsDao.java b/src/main/java/com/nike/cerberus/dao/PermissionsDao.java index 139d336e5..dc6a401ac 100644 --- a/src/main/java/com/nike/cerberus/dao/PermissionsDao.java +++ b/src/main/java/com/nike/cerberus/dao/PermissionsDao.java @@ -35,6 +35,10 @@ public Boolean doesIamPrincipalHaveRoleForSdb(String sdbId, String iamPrincipalA return permissionsMapper.doesIamPrincipalHaveGivenRoleForSdb(sdbId, iamPrincipalArn, iamRootArn, rolesThatAllowPermission); } + public Boolean doesAssumedRoleHaveRoleForSdb(String sdbId, String assumedRoleArn, String iamRoleArn, String iamRootArn, Set rolesThatAllowPermission) { + return permissionsMapper.doesAssumedRoleHaveGivenRoleForSdb(sdbId, assumedRoleArn, iamRoleArn, iamRootArn, rolesThatAllowPermission); + } + public Boolean doesUserPrincipalHaveRoleForSdb(String sdbId, Set rolesThatAllowPermission, Set userGroupsThatPrincipalBelongsTo) { return permissionsMapper.doesUserPrincipalHaveGivenRoleForSdb(sdbId, rolesThatAllowPermission, userGroupsThatPrincipalBelongsTo); } diff --git a/src/main/java/com/nike/cerberus/dao/SafeDepositBoxDao.java b/src/main/java/com/nike/cerberus/dao/SafeDepositBoxDao.java index ff4aa5f23..0854c2592 100644 --- a/src/main/java/com/nike/cerberus/dao/SafeDepositBoxDao.java +++ b/src/main/java/com/nike/cerberus/dao/SafeDepositBoxDao.java @@ -46,6 +46,12 @@ public List getIamRoleAssociatedSafeDepositBoxRoles(fi return safeDepositBoxMapper.getIamRoleAssociatedSafeDepositBoxRoles(awsIamRoleArn, iamRootArn); } + public List getIamAssumedRoleAssociatedSafeDepositBoxRoles(final String iamAssumedRoleArn, + final String awsIamRoleArn, + final String iamRootArn) { + return safeDepositBoxMapper.getIamAssumedRoleAssociatedSafeDepositBoxRoles(iamAssumedRoleArn, awsIamRoleArn, iamRootArn); + } + public List getUserAssociatedSafeDepositBoxes(final Set userGroups) { return safeDepositBoxMapper.getUserAssociatedSafeDepositBoxes(userGroups); } @@ -59,6 +65,12 @@ public List getIamPrincipalAssociatedSafeDepositBoxes(fina return safeDepositBoxMapper.getIamPrincipalAssociatedSafeDepositBoxes(iamPrincipalArn, iamRootArn); } + public List getAssumedRoleAssociatedSafeDepositBoxes(final String iamAssumedRoleArn, + final String iamRoleArn, + final String iamRootArn) { + return safeDepositBoxMapper.getIamAssumedRoleAssociatedSafeDepositBoxes(iamAssumedRoleArn, iamRoleArn, iamRootArn); + } + public List getSafeDepositBoxes(final int limit, final int offset) { return safeDepositBoxMapper.getSafeDepositBoxes(limit, offset); } diff --git a/src/main/java/com/nike/cerberus/error/DefaultApiError.java b/src/main/java/com/nike/cerberus/error/DefaultApiError.java index a39a1bdd2..d35ee931c 100644 --- a/src/main/java/com/nike/cerberus/error/DefaultApiError.java +++ b/src/main/java/com/nike/cerberus/error/DefaultApiError.java @@ -261,7 +261,7 @@ public enum DefaultApiError implements ApiError { /** * Signature does not match. Either the request is invalid or the request is signed with invalid region and/or wrong host. */ - SIGNATURE_DOES_NOT_MATCH(99237, "Signature does not match. Make sure the request is signed with us-east-1 as region and sts.amazonaws.com as host.", SC_BAD_REQUEST), + SIGNATURE_DOES_NOT_MATCH(99237, "Signature does not match. Make sure the request is signed with sts.{region}.amazonaws.com as host.", SC_BAD_REQUEST), /** * AWS token expired. diff --git a/src/main/java/com/nike/cerberus/mapper/PermissionsMapper.java b/src/main/java/com/nike/cerberus/mapper/PermissionsMapper.java index b1b72d13d..c5fecee9b 100644 --- a/src/main/java/com/nike/cerberus/mapper/PermissionsMapper.java +++ b/src/main/java/com/nike/cerberus/mapper/PermissionsMapper.java @@ -27,6 +27,12 @@ Boolean doesIamPrincipalHaveGivenRoleForSdb(@Param("sdbId") String sdbId, @Param("iamRootArn") String iamRootArn, @Param("rolesThatAllowPermission") Set rolesThatAllowPermission); + Boolean doesAssumedRoleHaveGivenRoleForSdb(@Param("sdbId") String sdbId, + @Param("assumedRoleArn") String assumedRoleArn, + @Param("iamRoleArn") String iamRoleArn, + @Param("iamRootArn") String iamRootArn, + @Param("rolesThatAllowPermission") Set rolesThatAllowPermission); + Boolean doesUserPrincipalHaveGivenRoleForSdb(@Param("sdbId") String sdbId, @Param("rolesThatAllowPermission") Set rolesThatAllowPermission, @Param("userGroupsThatPrincipalBelongsTo") Set userGroupsThatPrincipalBelongsTo); diff --git a/src/main/java/com/nike/cerberus/mapper/SafeDepositBoxMapper.java b/src/main/java/com/nike/cerberus/mapper/SafeDepositBoxMapper.java index 9da0914a6..c7e6e51aa 100644 --- a/src/main/java/com/nike/cerberus/mapper/SafeDepositBoxMapper.java +++ b/src/main/java/com/nike/cerberus/mapper/SafeDepositBoxMapper.java @@ -33,6 +33,10 @@ public interface SafeDepositBoxMapper { List getIamRoleAssociatedSafeDepositBoxRoles(@Param("awsIamRoleArn") final String awsIamRoleArn, @Param("iamRootArn") final String iamRootArn); + List getIamAssumedRoleAssociatedSafeDepositBoxRoles(@Param("iamAssumedRoleArn") final String iamAssumedRoleArn, + @Param("awsIamRoleArn") final String awsIamRoleArn, + @Param("iamRootArn") final String iamRootArn); + List getUserAssociatedSafeDepositBoxes(@Param("userGroups") Set userGroups); List getUserAssociatedSafeDepositBoxesIgnoreCase(@Param("userGroups") Set userGroups); @@ -40,6 +44,10 @@ List getIamRoleAssociatedSafeDepositBoxRoles(@Param("a List getIamPrincipalAssociatedSafeDepositBoxes(@Param("iamPrincipalArn") final String iamPrincipalArn, @Param("iamRootArn") final String iamRootArn); + List getIamAssumedRoleAssociatedSafeDepositBoxes(@Param("iamAssumedRoleArn") final String iamAssumedRoleArn, + @Param("iamRoleArn") final String iamRoleArn, + @Param("iamRootArn") final String iamRootArn); + SafeDepositBoxRecord getSafeDepositBox(@Param("id") String id); int countByPath(@Param("path") String path); diff --git a/src/main/java/com/nike/cerberus/service/AuthenticationService.java b/src/main/java/com/nike/cerberus/service/AuthenticationService.java index 33f30f770..1b1ec456d 100644 --- a/src/main/java/com/nike/cerberus/service/AuthenticationService.java +++ b/src/main/java/com/nike/cerberus/service/AuthenticationService.java @@ -484,14 +484,6 @@ protected Set buildCompleteSetOfPolicies(final String iamPrincipalArn) { final Set allPolicies = buildPolicySet(iamPrincipalArn); - if (! awsIamRoleArnParser.isRoleArn(iamPrincipalArn)) { - logger.debug("Detected non-role ARN, attempting to collect policies for the principal's base role..."); - final String iamPrincipalInRoleFormat = awsIamRoleArnParser.convertPrincipalArnToRoleArn(iamPrincipalArn); - - final Set additionalPolicies = buildPolicySet(iamPrincipalInRoleFormat); - allPolicies.addAll(additionalPolicies); - } - return allPolicies; } @@ -505,8 +497,18 @@ protected Set buildCompleteSetOfPolicies(final String iamPrincipalArn) { private Set buildPolicySet(final String iamRoleArn) { final String accountRootArn = awsIamRoleArnParser.convertPrincipalArnToRootArn(iamRoleArn); final Set policies = Sets.newHashSet(LOOKUP_SELF_POLICY); - final List sdbRolesForIamPrincipal = - safeDepositBoxDao.getIamRoleAssociatedSafeDepositBoxRoles(iamRoleArn, accountRootArn); + final List sdbRolesForIamPrincipal; + // This may cause issues for user/instance-profile if someone's relying on the old code that converts everything + // that's not a role ARN. + if (awsIamRoleArnParser.isAssumedRoleArn(iamRoleArn)) { + logger.debug("Detected assumed-role ARN, attempting to collect policies for the principal's base role..."); + String baseIamRoleArn = awsIamRoleArnParser.convertPrincipalArnToRoleArn(iamRoleArn); + sdbRolesForIamPrincipal = + safeDepositBoxDao.getIamAssumedRoleAssociatedSafeDepositBoxRoles(iamRoleArn, baseIamRoleArn, accountRootArn); + } else { + sdbRolesForIamPrincipal = + safeDepositBoxDao.getIamRoleAssociatedSafeDepositBoxRoles(iamRoleArn, accountRootArn); + } sdbRolesForIamPrincipal.forEach(i -> { policies.add(buildPolicyName(i.getSafeDepositBoxName(), i.getRoleName())); @@ -643,7 +645,11 @@ protected Map generateCommonIamPrincipalAuthMetadata(final Strin groups.add("registered-iam-principals"); // We will allow specific ARNs access to the user portions of the API - if (getAdminRoleArnSet().contains(iamPrincipalArn)) { + Set adminRoleArnSet = getAdminRoleArnSet(); + + if (adminRoleArnSet.contains(iamPrincipalArn) + || awsIamRoleArnParser.isAssumedRoleArn(iamPrincipalArn) + && adminRoleArnSet.contains(awsIamRoleArnParser.convertPrincipalArnToRoleArn(iamPrincipalArn))) { metadata.put(METADATA_KEY_IS_ADMIN, Boolean.toString(true)); groups.add("admin-iam-principals"); } else { diff --git a/src/main/java/com/nike/cerberus/service/PermissionsService.java b/src/main/java/com/nike/cerberus/service/PermissionsService.java index 2b7840f7a..cb9d847f4 100644 --- a/src/main/java/com/nike/cerberus/service/PermissionsService.java +++ b/src/main/java/com/nike/cerberus/service/PermissionsService.java @@ -80,9 +80,7 @@ public boolean doesPrincipalHaveOwnerPermissions(CerberusPrincipal principal, Sa boolean principalHasOwnerPermissions = false; switch (principal.getPrincipalType()) { case IAM: - String iamPrincipalArn = principal.getName(); - String iamRootArn = awsIamRoleArnParser.convertPrincipalArnToRootArn(iamPrincipalArn); - principalHasOwnerPermissions = permissionsDao.doesIamPrincipalHaveRoleForSdb(sdb.getId(), iamPrincipalArn, iamRootArn, Sets.newHashSet(ROLE_OWNER)); + principalHasOwnerPermissions = doesIamPrincipalHavePermission(principal, sdb.getId(), Sets.newHashSet(ROLE_OWNER)); break; case USER: principalHasOwnerPermissions = userGroupsCaseSensitive ? @@ -123,9 +121,7 @@ public boolean doesPrincipalHaveReadPermission(CerberusPrincipal principal, Stri switch (principal.getPrincipalType()) { case IAM: // if the authenticated principal is an IAM Principal check to see that the iam principal is associated with the requested sdb - String iamPrincipalArn = principal.getName(); - String iamRootArn = awsIamRoleArnParser.convertPrincipalArnToRootArn(iamPrincipalArn); - principalHasPermissionAssociationWithSdb = permissionsDao.doesIamPrincipalHaveRoleForSdb(sdbId, iamPrincipalArn, iamRootArn, Sets.newHashSet(ROLE_READ, ROLE_OWNER, ROLE_WRITE)); + principalHasPermissionAssociationWithSdb = doesIamPrincipalHavePermission(principal, sdbId, Sets.newHashSet(ROLE_READ, ROLE_OWNER, ROLE_WRITE)); break; case USER: // if the the principal is a user principal ensure that one of the users groups is associated with the sdb @@ -145,8 +141,7 @@ public boolean doesPrincipalHavePermission(CerberusPrincipal principal, String s boolean hasPermission = false; switch (principal.getPrincipalType()) { case IAM: - String iamRootArn = awsIamRoleArnParser.convertPrincipalArnToRootArn(principal.getName()); - hasPermission = permissionsDao.doesIamPrincipalHaveRoleForSdb(sdbId, principal.getName(), iamRootArn, action.getAllowedRoles()); + hasPermission = doesIamPrincipalHavePermission(principal, sdbId, action.getAllowedRoles()); break; case USER: hasPermission = userGroupsCaseSensitive ? @@ -168,6 +163,17 @@ public boolean doesPrincipalHavePermission(CerberusPrincipal principal, String s return hasPermission; } + protected boolean doesIamPrincipalHavePermission(CerberusPrincipal principal, String sdbId, Set roles) { + String iamPrincipalArn = principal.getName(); + String iamRootArn = awsIamRoleArnParser.convertPrincipalArnToRootArn(iamPrincipalArn); + if (awsIamRoleArnParser.isAssumedRoleArn(iamPrincipalArn)) { + String iamRoleArn = awsIamRoleArnParser.convertPrincipalArnToRoleArn(iamPrincipalArn); + return permissionsDao.doesAssumedRoleHaveRoleForSdb(sdbId, iamPrincipalArn, iamRoleArn, iamRootArn, roles); + } else { + return permissionsDao.doesIamPrincipalHaveRoleForSdb(sdbId, iamPrincipalArn, iamRootArn, roles); + } + } + /** * Does a case-insensitive check to see if the collection contains the given String * @param items List of strings from which to search diff --git a/src/main/java/com/nike/cerberus/service/SafeDepositBoxService.java b/src/main/java/com/nike/cerberus/service/SafeDepositBoxService.java index 834c11491..079a55b4c 100644 --- a/src/main/java/com/nike/cerberus/service/SafeDepositBoxService.java +++ b/src/main/java/com/nike/cerberus/service/SafeDepositBoxService.java @@ -136,8 +136,14 @@ public List getAssociatedSafeDepositBoxes(final CerberusP switch (principal.getPrincipalType()) { case IAM: - String rootArn = awsIamRoleArnParser.convertPrincipalArnToRootArn(principal.getName()); - sdbRecords = safeDepositBoxDao.getIamPrincipalAssociatedSafeDepositBoxes(principal.getName(), rootArn); + String principalName = principal.getName(); + String rootArn = awsIamRoleArnParser.convertPrincipalArnToRootArn(principalName); + if (awsIamRoleArnParser.isAssumedRoleArn(principalName)) { + String iamRoleArn = awsIamRoleArnParser.convertPrincipalArnToRoleArn(principalName); + sdbRecords = safeDepositBoxDao.getAssumedRoleAssociatedSafeDepositBoxes(principalName, iamRoleArn, rootArn); + } else { + sdbRecords = safeDepositBoxDao.getIamPrincipalAssociatedSafeDepositBoxes(principalName, rootArn); + } break; case USER: sdbRecords = userGroupsCaseSensitive ? diff --git a/src/main/java/com/nike/cerberus/util/AwsIamRoleArnParser.java b/src/main/java/com/nike/cerberus/util/AwsIamRoleArnParser.java index cd24d9706..afbf068e4 100644 --- a/src/main/java/com/nike/cerberus/util/AwsIamRoleArnParser.java +++ b/src/main/java/com/nike/cerberus/util/AwsIamRoleArnParser.java @@ -113,6 +113,18 @@ public boolean isRoleArn(final String arn) { return iamRoleArnMatcher.find(); } + /** + * Returns true if the ARN is in format 'arn:aws:sts::000000000:assumed-role/example/role-session' and false if not + * @param arn - ARN to test + * @return - True if is 'role' ARN, False if not + */ + public boolean isAssumedRoleArn(final String arn) { + + final Matcher iamAssumedRoleArnMatcher = IAM_ASSUMED_ROLE_ARN_PATTERN.matcher(arn); + + return iamAssumedRoleArnMatcher.find(); + } + /** * Returns true if the ARN is in format 'arn:aws:iam::000000000:root' and false if not * @param arn - ARN to test diff --git a/src/main/resources/com/nike/cerberus/mapper/PermissionsMapper.xml b/src/main/resources/com/nike/cerberus/mapper/PermissionsMapper.xml index aee674393..9338031eb 100644 --- a/src/main/resources/com/nike/cerberus/mapper/PermissionsMapper.xml +++ b/src/main/resources/com/nike/cerberus/mapper/PermissionsMapper.xml @@ -42,6 +42,30 @@ ) as HAS_PERM; + + + + + +