in sources/src/main/java/com/google/solutions/jitaccess/apis/clients/AbstractIamClient.java [85:193]
public void modifyIamPolicy(
@NotNull String fullResourcePath,
@NotNull Consumer<Policy> modify,
@NotNull String requestReason
) throws AccessException, IOException {
Preconditions.checkNotNull(fullResourcePath, "fullResourcePath");
Preconditions.checkNotNull(modify, "modify");
final var optionsV3 = new GetPolicyOptions().setRequestedPolicyVersion(3);
try {
var client = createClient();
//
// IAM policies use optimistic concurrency control, so we might need to perform
// multiple attempts to update the policy.
//
for (int attempt = 0; attempt < MAX_SET_IAM_POLICY_ATTEMPTS; attempt++) {
//
// Read current version of policy.
//
// NB. The API might return a v1 policy even if we
// request a v3 policy.
//
var getRequest = new GetIamPolicy(
client,
fullResourcePath,
new GetIamPolicyRequest()
.setOptions(optionsV3));
var policy = getRequest.execute();
//
// Make sure we're using v3; older versions don't support conditions.
//
policy.setVersion(3);
//
// Apply changes.
//
modify.accept(policy);
try {
var request = new SetIamPolicy(
client,
fullResourcePath,
new SetIamPolicyRequest().setPolicy(policy));
request.getRequestHeaders().set("x-goog-request-reason", requestReason);
request.execute();
//
// Successful update -> quit loop.
//
return;
}
catch (GoogleJsonResponseException e) {
if (e.getStatusCode() == 412) {
//
// Concurrent modification - back off and retry.
//
try {
Thread.sleep(200);
}
catch (InterruptedException ignored) {
}
}
else {
throw (GoogleJsonResponseException) e.fillInStackTrace();
}
}
}
throw new AlreadyExistsException(
"Failed to update IAM bindings due to concurrent modifications");
}
catch (GoogleJsonResponseException e) {
switch (e.getStatusCode()) {
case 400:
//
// One possible reason for an INVALID_ARGUMENT error is that we've tried
// to grant a role on a resource that cannot be granted on this type of resource.
// If that's the case, provide a more descriptive error message.
//
if (e.getDetails() != null &&
e.getDetails().getErrors() != null &&
!e.getDetails().getErrors().isEmpty() &&
isRoleNotGrantableErrorMessage(e.getDetails().getErrors().get(0).getMessage())) {
throw new AccessDeniedException(
String.format("Modifying IAM policy of '%s' failed because one of the " +
"roles isn't compatible with this resource",
fullResourcePath));
}
else {
// Fallthrough.
}
case 401:
throw new NotAuthenticatedException("Not authenticated", e);
case 403:
throw new AccessDeniedException(String.format(
"Access to '%s' is denied", fullResourcePath),
e);
default:
throw (GoogleJsonResponseException) e.fillInStackTrace();
}
}
}