Only grant IAP access to the console for new users (#3167)

Currently we grant users IAP_SECURED_WEB_APP_USER_ROLE access to the
entire project upon creation if there is no preexisting group set up for
them to use. These users only need to be able to access the console, so
we should restrict it to the console.

If there is a group (which we do have in our prod + sandbox
configuration) then we just add them to that group instead and that
group controls all the privileges. So this is a no-op for our setup.

We prioritize the group -- if both the group and the IAP service ID are
specified, we'll still just add them to the group

b/534931170
This commit is contained in:
gbrodman
2026-07-22 16:53:41 +00:00
committed by GitHub
parent f0fae9898b
commit d3a1d0709e
17 changed files with 244 additions and 41 deletions
@@ -165,6 +165,12 @@ public final class RegistryConfig {
return config.misc.isEmailSendingEnabled; return config.misc.isEmailSendingEnabled;
} }
@Provides
@Config("consoleIapServiceId")
public static Optional<String> provideConsoleIapServiceId(RegistryConfigSettings config) {
return Optional.ofNullable(config.registrarConsole.consoleIapServiceId);
}
/** /**
* The e-mail address for general support. Used in the "contact-us" section of the registrar * The e-mail address for general support. Used in the "contact-us" section of the registrar
* console. * console.
@@ -187,6 +187,7 @@ public class RegistryConfigSettings {
/** Configuration for the web-based registrar console. */ /** Configuration for the web-based registrar console. */
public static class RegistrarConsole { public static class RegistrarConsole {
public String consoleIapServiceId;
public String dumFileName; public String dumFileName;
public String supportPhoneNumber; public String supportPhoneNumber;
public String supportEmailAddress; public String supportEmailAddress;
@@ -390,6 +390,11 @@ rde:
sshIdentityEmailAddress: rde@example.com sshIdentityEmailAddress: rde@example.com
registrarConsole: registrarConsole:
# IAP service ID of the HTTP(s) load balancer that serves the console-api backend.
# Users created in the console will be bound only to this service to limit access.
# This is only applicable to non-group-based auth (if there is no consoleUserGroupEmailAddress)
consoleIapServiceId: null
# DUM download file name, excluding the extension # DUM download file name, excluding the extension
dumFileName: dum_file_name dumFileName: dum_file_name
@@ -197,8 +197,7 @@ public final class OteAccountBuilder {
registrars.stream() registrars.stream()
.collect( .collect(
toImmutableMap( toImmutableMap(
Registrar::getRegistrarId, Registrar::getRegistrarId, _ -> RegistrarRole.ACCOUNT_MANAGER)))
registrar -> RegistrarRole.ACCOUNT_MANAGER)))
.build()) .build())
.build()); .build());
return this; return this;
@@ -265,10 +264,18 @@ public final class OteAccountBuilder {
/** Grants the users permission to pass IAP. */ /** Grants the users permission to pass IAP. */
public void grantIapPermission( public void grantIapPermission(
Optional<String> groupEmailAddress, CloudTasksUtils cloudTasksUtils, IamClient iamClient) { Optional<String> groupEmailAddress,
Optional<String> consoleIapServiceId,
CloudTasksUtils cloudTasksUtils,
IamClient iamClient) {
for (User user : users) { for (User user : users) {
User.grantIapPermission( User.grantIapPermission(
user.getEmailAddress(), groupEmailAddress, cloudTasksUtils, null, iamClient); user.getEmailAddress(),
groupEmailAddress,
consoleIapServiceId,
cloudTasksUtils,
null,
iamClient);
} }
} }
@@ -86,6 +86,7 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
public static void grantIapPermission( public static void grantIapPermission(
String emailAddress, String emailAddress,
Optional<String> groupEmailAddress, Optional<String> groupEmailAddress,
Optional<String> consoleIapServiceId,
@Nullable CloudTasksUtils cloudTasksUtils, @Nullable CloudTasksUtils cloudTasksUtils,
@Nullable ServiceConnection connection, @Nullable ServiceConnection connection,
IamClient iamClient) { IamClient iamClient) {
@@ -93,14 +94,14 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
return; return;
} }
checkArgument( checkArgument(
cloudTasksUtils != null || connection != null, cloudTasksUtils == null ^ connection == null,
"At least one of cloudTasksUtils or connection must be set"); "Precisely one of cloudTasksUtils or connection can be set");
checkArgument( checkArgument(
cloudTasksUtils == null || connection == null, groupEmailAddress.isPresent() || consoleIapServiceId.isPresent(),
"Only one of cloudTasksUtils or connection can be set"); "At least one of groupEmailAddress or consoleIapServiceId must be present");
if (groupEmailAddress.isEmpty()) { if (groupEmailAddress.isEmpty()) {
logger.atInfo().log("Granting IAP role to user %s", emailAddress); logger.atInfo().log("Granting IAP role to user %s", emailAddress);
iamClient.addBinding(emailAddress, IAP_SECURED_WEB_APP_USER_ROLE); iamClient.addBinding(emailAddress, IAP_SECURED_WEB_APP_USER_ROLE, consoleIapServiceId.get());
} else { } else {
logger.atInfo().log("Adding %s to group %s", emailAddress, groupEmailAddress.get()); logger.atInfo().log("Adding %s to group %s", emailAddress, groupEmailAddress.get());
if (cloudTasksUtils != null) { if (cloudTasksUtils != null) {
@@ -129,11 +130,8 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
return; return;
} }
checkArgument( checkArgument(
cloudTasksUtils != null || connection != null, cloudTasksUtils == null ^ connection == null,
"At least one of cloudTasksUtils or connection must be set"); "Precisely one of cloudTasksUtils or connection can be set");
checkArgument(
cloudTasksUtils == null || connection == null,
"Only one of cloudTasksUtils or connection can be set");
if (groupEmailAddress.isEmpty()) { if (groupEmailAddress.isEmpty()) {
logger.atInfo().log("Removing IAP role from user %s", emailAddress); logger.atInfo().log("Removing IAP role from user %s", emailAddress);
iamClient.removeBinding(emailAddress, IAP_SECURED_WEB_APP_USER_ROLE); iamClient.removeBinding(emailAddress, IAP_SECURED_WEB_APP_USER_ROLE);
@@ -38,6 +38,10 @@ public class CreateUserCommand extends CreateOrUpdateUserCommand implements Comm
@Config("gSuiteConsoleUserGroupEmailAddress") @Config("gSuiteConsoleUserGroupEmailAddress")
Optional<String> maybeGroupEmailAddress; Optional<String> maybeGroupEmailAddress;
@Inject
@Config("consoleIapServiceId")
Optional<String> consoleIapServiceId;
@Nullable @Nullable
@Override @Override
User getExistingUser(String email) { User getExistingUser(String email) {
@@ -49,7 +53,8 @@ public class CreateUserCommand extends CreateOrUpdateUserCommand implements Comm
@Override @Override
protected String execute() throws Exception { protected String execute() throws Exception {
String ret = super.execute(); String ret = super.execute();
grantIapPermission(email, maybeGroupEmailAddress, null, connection, iamClient); grantIapPermission(
email, maybeGroupEmailAddress, consoleIapServiceId, null, connection, iamClient);
return ret; return ret;
} }
@@ -16,6 +16,7 @@ package google.registry.tools;
import com.google.api.services.cloudresourcemanager.CloudResourceManager; import com.google.api.services.cloudresourcemanager.CloudResourceManager;
import com.google.api.services.cloudresourcemanager.model.Binding; import com.google.api.services.cloudresourcemanager.model.Binding;
import com.google.api.services.cloudresourcemanager.model.Expr;
import com.google.api.services.cloudresourcemanager.model.GetIamPolicyRequest; import com.google.api.services.cloudresourcemanager.model.GetIamPolicyRequest;
import com.google.api.services.cloudresourcemanager.model.Policy; import com.google.api.services.cloudresourcemanager.model.Policy;
import com.google.api.services.cloudresourcemanager.model.SetIamPolicyRequest; import com.google.api.services.cloudresourcemanager.model.SetIamPolicyRequest;
@@ -31,7 +32,10 @@ import java.util.Optional;
@Singleton @Singleton
public class IamClient { public class IamClient {
private static final String MEMBER_FORMAT = "user:%s"; private static final String MEMBER_FORMAT = "user:%s";
private static final String IAP_ACCESS_EXPRESSION_FORMAT =
"resource.name == 'projects/%s/iap_web/compute/services/%s'";
private final CloudResourceManager resourceManager; private final CloudResourceManager resourceManager;
private final String projectId; private final String projectId;
@@ -60,7 +64,7 @@ public class IamClient {
* *
* <p>No-op if the role is already bound to the account. * <p>No-op if the role is already bound to the account.
*/ */
public void addBinding(String account, String role) { public void addBinding(String account, String role, String consoleIapServiceId) {
String member = String.format(MEMBER_FORMAT, account); String member = String.format(MEMBER_FORMAT, account);
Policy policy = getPolicy(); Policy policy = getPolicy();
Binding binding = Binding binding =
@@ -69,7 +73,21 @@ public class IamClient {
.findFirst() .findFirst()
.orElseGet( .orElseGet(
() -> { () -> {
Binding newBinding = new Binding().setRole(role).setMembers(new ArrayList<>()); Binding newBinding =
new Binding()
.setRole(role)
.setMembers(new ArrayList<>())
.setCondition(
new Expr()
.setTitle("Registrar Console IAP access")
.setDescription(
"Restrict IAP access only to the Registrar Console HTTP(s)"
+ " load balancer")
.setExpression(
String.format(
IAP_ACCESS_EXPRESSION_FORMAT,
projectId,
consoleIapServiceId)));
policy.getBindings().add(newBinding); policy.getBindings().add(newBinding);
return newBinding; return newBinding;
}); });
@@ -86,6 +86,10 @@ final class SetupOteCommand extends ConfirmingCommand {
@Config("gSuiteConsoleUserGroupEmailAddress") @Config("gSuiteConsoleUserGroupEmailAddress")
Optional<String> maybeGroupEmailAddress; Optional<String> maybeGroupEmailAddress;
@Inject
@Config("consoleIapServiceId")
Optional<String> consoleIapServiceId;
OteAccountBuilder oteAccountBuilder; OteAccountBuilder oteAccountBuilder;
String password; String password;
@@ -138,7 +142,8 @@ you sure you didn't mean to run this against sandbox (e.g. "-e SANDBOX")?\
@Override @Override
public String execute() { public String execute() {
ImmutableMap<String, String> clientIdToTld = oteAccountBuilder.buildAndPersist(); ImmutableMap<String, String> clientIdToTld = oteAccountBuilder.buildAndPersist();
oteAccountBuilder.grantIapPermission(maybeGroupEmailAddress, cloudTasksUtils, iamClient); oteAccountBuilder.grantIapPermission(
maybeGroupEmailAddress, consoleIapServiceId, cloudTasksUtils, iamClient);
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
@@ -62,6 +62,7 @@ public class ConsoleOteAction extends ConsoleApiAction {
private final StringGenerator passwordGenerator; private final StringGenerator passwordGenerator;
private final Optional<OteCreateData> oteCreateData; private final Optional<OteCreateData> oteCreateData;
private final Optional<String> maybeGroupEmailAddress; private final Optional<String> maybeGroupEmailAddress;
private final Optional<String> consoleIapServiceId;
private final IamClient iamClient; private final IamClient iamClient;
private final String registrarId; private final String registrarId;
@@ -71,12 +72,14 @@ public class ConsoleOteAction extends ConsoleApiAction {
IamClient iamClient, IamClient iamClient,
@Parameter("registrarId") String registrarId, // Get request param @Parameter("registrarId") String registrarId, // Get request param
@Config("gSuiteConsoleUserGroupEmailAddress") Optional<String> maybeGroupEmailAddress, @Config("gSuiteConsoleUserGroupEmailAddress") Optional<String> maybeGroupEmailAddress,
@Config("consoleIapServiceId") Optional<String> consoleIapServiceId,
@Named("base58StringGenerator") StringGenerator passwordGenerator, @Named("base58StringGenerator") StringGenerator passwordGenerator,
@Parameter("oteCreateData") Optional<OteCreateData> oteCreateData) { @Parameter("oteCreateData") Optional<OteCreateData> oteCreateData) {
super(consoleApiParams); super(consoleApiParams);
this.passwordGenerator = passwordGenerator; this.passwordGenerator = passwordGenerator;
this.oteCreateData = oteCreateData; this.oteCreateData = oteCreateData;
this.maybeGroupEmailAddress = maybeGroupEmailAddress; this.maybeGroupEmailAddress = maybeGroupEmailAddress;
this.consoleIapServiceId = consoleIapServiceId;
this.iamClient = iamClient; this.iamClient = iamClient;
this.registrarId = registrarId; this.registrarId = registrarId;
} }
@@ -105,7 +108,8 @@ public class ConsoleOteAction extends ConsoleApiAction {
.setPassword(password); .setPassword(password);
ImmutableMap<String, String> registrarIdToTld = oteAccountBuilder.buildAndPersist(); ImmutableMap<String, String> registrarIdToTld = oteAccountBuilder.buildAndPersist();
oteAccountBuilder.grantIapPermission(maybeGroupEmailAddress, cloudTasksUtils, iamClient); oteAccountBuilder.grantIapPermission(
maybeGroupEmailAddress, consoleIapServiceId, cloudTasksUtils, iamClient);
consoleApiParams.response().setStatus(SC_OK); consoleApiParams.response().setStatus(SC_OK);
consoleApiParams consoleApiParams
.response() .response()
@@ -79,6 +79,7 @@ public class ConsoleUsersAction extends ConsoleApiAction {
private final StringGenerator passwordGenerator; private final StringGenerator passwordGenerator;
private final Optional<UserData> userData; private final Optional<UserData> userData;
private final Optional<String> maybeGroupEmailAddress; private final Optional<String> maybeGroupEmailAddress;
private final Optional<String> consoleIapServiceId;
private final IamClient iamClient; private final IamClient iamClient;
private final String gSuiteDomainName; private final String gSuiteDomainName;
@@ -89,6 +90,7 @@ public class ConsoleUsersAction extends ConsoleApiAction {
IamClient iamClient, IamClient iamClient,
@Config("gSuiteDomainName") String gSuiteDomainName, @Config("gSuiteDomainName") String gSuiteDomainName,
@Config("gSuiteConsoleUserGroupEmailAddress") Optional<String> maybeGroupEmailAddress, @Config("gSuiteConsoleUserGroupEmailAddress") Optional<String> maybeGroupEmailAddress,
@Config("consoleIapServiceId") Optional<String> consoleIapServiceId,
@Named("base58StringGenerator") StringGenerator passwordGenerator, @Named("base58StringGenerator") StringGenerator passwordGenerator,
@Parameter("userData") Optional<UserData> userData, @Parameter("userData") Optional<UserData> userData,
@Parameter("registrarId") String registrarId) { @Parameter("registrarId") String registrarId) {
@@ -98,6 +100,7 @@ public class ConsoleUsersAction extends ConsoleApiAction {
this.passwordGenerator = passwordGenerator; this.passwordGenerator = passwordGenerator;
this.userData = userData; this.userData = userData;
this.maybeGroupEmailAddress = maybeGroupEmailAddress; this.maybeGroupEmailAddress = maybeGroupEmailAddress;
this.consoleIapServiceId = consoleIapServiceId;
this.iamClient = iamClient; this.iamClient = iamClient;
this.gSuiteDomainName = gSuiteDomainName; this.gSuiteDomainName = gSuiteDomainName;
} }
@@ -256,7 +259,8 @@ public class ConsoleUsersAction extends ConsoleApiAction {
User.Builder builder = new User.Builder().setUserRoles(userRoles).setEmailAddress(newEmail); User.Builder builder = new User.Builder().setUserRoles(userRoles).setEmailAddress(newEmail);
tm().put(builder.build()); tm().put(builder.build());
User.grantIapPermission(newEmail, maybeGroupEmailAddress, cloudTasksUtils, null, iamClient); User.grantIapPermission(
newEmail, maybeGroupEmailAddress, consoleIapServiceId, cloudTasksUtils, null, iamClient);
sendConfirmationEmail(registrarId, newEmail, "Created user"); sendConfirmationEmail(registrarId, newEmail, "Created user");
consoleApiParams.response().setStatus(SC_CREATED); consoleApiParams.response().setStatus(SC_CREATED);
consoleApiParams consoleApiParams
@@ -116,6 +116,7 @@ public final class OteAccountBuilderTest {
public static void verifyIapPermission( public static void verifyIapPermission(
@Nullable String emailAddress, @Nullable String emailAddress,
Optional<String> maybeGroupEmailAddress, Optional<String> maybeGroupEmailAddress,
Optional<String> consoleIapServiceId,
CloudTasksHelper cloudTasksHelper, CloudTasksHelper cloudTasksHelper,
IamClient iamClient) { IamClient iamClient) {
if (emailAddress == null) { if (emailAddress == null) {
@@ -125,7 +126,8 @@ public final class OteAccountBuilderTest {
String groupEmailAddress = maybeGroupEmailAddress.orElse(null); String groupEmailAddress = maybeGroupEmailAddress.orElse(null);
if (groupEmailAddress == null) { if (groupEmailAddress == null) {
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update"); cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
verify(iamClient).addBinding(emailAddress, IAP_SECURED_WEB_APP_USER_ROLE); verify(iamClient)
.addBinding(emailAddress, IAP_SECURED_WEB_APP_USER_ROLE, consoleIapServiceId.get());
} else { } else {
cloudTasksHelper.assertTasksEnqueued( cloudTasksHelper.assertTasksEnqueued(
"console-user-group-update", "console-user-group-update",
@@ -146,9 +148,32 @@ public final class OteAccountBuilderTest {
CloudTasksUtils cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils(); CloudTasksUtils cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
OteAccountBuilder.forRegistrarId("myclientid") OteAccountBuilder.forRegistrarId("myclientid")
.addUser("email@example.com") .addUser("email@example.com")
.grantIapPermission(Optional.of("console@example.com"), cloudTasksUtils, iamClient); .grantIapPermission(
Optional.of("console@example.com"),
Optional.of("consoleIapServiceId"),
cloudTasksUtils,
iamClient);
verifyIapPermission( verifyIapPermission(
"email@example.com", Optional.of("console@example.com"), cloudTasksHelper, iamClient); "email@example.com",
Optional.of("console@example.com"),
Optional.of("consoleIapServiceId"),
cloudTasksHelper,
iamClient);
}
@Test
void testUpdateUserGroup_worksWithoutIapServiceId() {
CloudTasksUtils cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
OteAccountBuilder.forRegistrarId("myclientid")
.addUser("email@example.com")
.grantIapPermission(
Optional.of("console@example.com"), Optional.empty(), cloudTasksUtils, iamClient);
verifyIapPermission(
"email@example.com",
Optional.of("console@example.com"),
Optional.empty(),
cloudTasksHelper,
iamClient);
} }
@Test @Test
@@ -156,8 +181,14 @@ public final class OteAccountBuilderTest {
CloudTasksUtils cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils(); CloudTasksUtils cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
OteAccountBuilder.forRegistrarId("myclientid") OteAccountBuilder.forRegistrarId("myclientid")
.addUser("email@example.com") .addUser("email@example.com")
.grantIapPermission(Optional.empty(), cloudTasksUtils, iamClient); .grantIapPermission(
verifyIapPermission("email@example.com", Optional.empty(), cloudTasksHelper, iamClient); Optional.empty(), Optional.of("consoleIapServiceId"), cloudTasksUtils, iamClient);
verifyIapPermission(
"email@example.com",
Optional.empty(),
Optional.of("consoleIapServiceId"),
cloudTasksHelper,
iamClient);
} }
@Test @Test
@@ -66,13 +66,21 @@ public class UserTest extends EntityTestCase {
void testFailure_asyncAndSyncModeConflict() { void testFailure_asyncAndSyncModeConflict() {
assertThrows( assertThrows(
IllegalArgumentException.class, IllegalArgumentException.class,
() -> User.grantIapPermission("email@example.com", Optional.empty(), null, null, null)); () ->
User.grantIapPermission(
"email@example.com",
Optional.empty(),
Optional.of("consoleIapServiceId"),
null,
null,
null));
assertThrows( assertThrows(
IllegalArgumentException.class, IllegalArgumentException.class,
() -> () ->
User.grantIapPermission( User.grantIapPermission(
"email@example.com", "email@example.com",
Optional.empty(), Optional.empty(),
Optional.of("consoleIapServiceId"),
mock(CloudTasksUtils.class), mock(CloudTasksUtils.class),
mock(ServiceConnection.class), mock(ServiceConnection.class),
null)); null));
@@ -90,6 +98,24 @@ public class UserTest extends EntityTestCase {
null)); null));
} }
@Test
void testGrantIapPermission_neitherGroupNorIapServiceId_fails() {
IllegalArgumentException e =
assertThrows(
IllegalArgumentException.class,
() ->
User.grantIapPermission(
"email@example.com",
Optional.empty(),
Optional.empty(),
mock(CloudTasksUtils.class),
null,
mock(IamClient.class)));
assertThat(e)
.hasMessageThat()
.isEqualTo("At least one of groupEmailAddress or consoleIapServiceId must be present");
}
@Test @Test
void testFailure_badInputs() { void testFailure_badInputs() {
User.Builder builder = new User.Builder(); User.Builder builder = new User.Builder();
@@ -156,13 +182,33 @@ public class UserTest extends EntityTestCase {
// Individual permission. // Individual permission.
User.grantIapPermission( User.grantIapPermission(
"email@example.com", Optional.empty(), cloudTasksUtils, null, iamClient); "email@example.com",
Optional.empty(),
Optional.of("consoleIapServiceId"),
cloudTasksUtils,
null,
iamClient);
cloudTasksHelper.assertNoTasksEnqueued(); cloudTasksHelper.assertNoTasksEnqueued();
verify(iamClient).addBinding("email@example.com", IAP_SECURED_WEB_APP_USER_ROLE); verify(iamClient)
.addBinding("email@example.com", IAP_SECURED_WEB_APP_USER_ROLE, "consoleIapServiceId");
// Group membership. // Group membership.
User.grantIapPermission( User.grantIapPermission(
"email@example.com", Optional.of("console@example.com"), cloudTasksUtils, null, iamClient); "email@example.com",
Optional.of("console@example.com"),
Optional.empty(),
cloudTasksUtils,
null,
iamClient);
// Group membership takes precedence over individual membership
User.grantIapPermission(
"groupemail@example.com",
Optional.of("console@example.com"),
Optional.of("consoleIapServiceId"),
cloudTasksUtils,
null,
iamClient);
cloudTasksHelper.assertTasksEnqueued( cloudTasksHelper.assertTasksEnqueued(
"console-user-group-update", "console-user-group-update",
new TaskMatcher() new TaskMatcher()
@@ -171,6 +217,13 @@ public class UserTest extends EntityTestCase {
.path("/_dr/admin/updateUserGroup") .path("/_dr/admin/updateUserGroup")
.param("userEmailAddress", "email@example.com") .param("userEmailAddress", "email@example.com")
.param("groupEmailAddress", "console@example.com") .param("groupEmailAddress", "console@example.com")
.param("groupUpdateMode", "ADD"),
new TaskMatcher()
.service("BACKEND")
.method(HttpMethod.POST)
.path("/_dr/admin/updateUserGroup")
.param("userEmailAddress", "groupemail@example.com")
.param("groupEmailAddress", "console@example.com")
.param("groupUpdateMode", "ADD")); .param("groupUpdateMode", "ADD"));
verifyNoMoreInteractions(iamClient); verifyNoMoreInteractions(iamClient);
} }
@@ -181,13 +234,25 @@ public class UserTest extends EntityTestCase {
IamClient iamClient = mock(IamClient.class); IamClient iamClient = mock(IamClient.class);
// Individual permission. // Individual permission.
User.grantIapPermission("email@example.com", Optional.empty(), null, connection, iamClient); User.grantIapPermission(
"email@example.com",
Optional.empty(),
Optional.of("consoleIapServiceId"),
null,
connection,
iamClient);
verifyNoInteractions(connection); verifyNoInteractions(connection);
verify(iamClient).addBinding("email@example.com", IAP_SECURED_WEB_APP_USER_ROLE); verify(iamClient)
.addBinding("email@example.com", IAP_SECURED_WEB_APP_USER_ROLE, "consoleIapServiceId");
// Group membership. // Group membership.
User.grantIapPermission( User.grantIapPermission(
"email@example.com", Optional.of("console@example.com"), null, connection, iamClient); "email@example.com",
Optional.of("console@example.com"),
Optional.empty(),
null,
connection,
iamClient);
verify(connection) verify(connection)
.sendPostRequest( .sendPostRequest(
UpdateUserGroupAction.PATH, UpdateUserGroupAction.PATH,
@@ -45,6 +45,7 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
void beforeEach() { void beforeEach() {
command.iamClient = iamClient; command.iamClient = iamClient;
command.maybeGroupEmailAddress = Optional.empty(); command.maybeGroupEmailAddress = Optional.empty();
command.consoleIapServiceId = Optional.of("consoleIapServiceId");
command.setConnection(connection); command.setConnection(connection);
} }
@@ -56,7 +57,8 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
assertThat(onlyUser.getUserRoles().isAdmin()).isFalse(); assertThat(onlyUser.getUserRoles().isAdmin()).isFalse();
assertThat(onlyUser.getUserRoles().getGlobalRole()).isEqualTo(GlobalRole.NONE); assertThat(onlyUser.getUserRoles().getGlobalRole()).isEqualTo(GlobalRole.NONE);
assertThat(onlyUser.getUserRoles().getRegistrarRoles()).isEmpty(); assertThat(onlyUser.getUserRoles().getRegistrarRoles()).isEmpty();
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE); verify(iamClient)
.addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE, "consoleIapServiceId");
verifyNoMoreInteractions(iamClient); verifyNoMoreInteractions(iamClient);
verifyNoInteractions(connection); verifyNoInteractions(connection);
} }
@@ -86,6 +88,41 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
verifyNoMoreInteractions(connection); verifyNoMoreInteractions(connection);
} }
@Test
void testSuccess_addToGroup_noIapServiceId() throws Exception {
command.maybeGroupEmailAddress = Optional.of("group@example.test");
command.consoleIapServiceId = Optional.empty();
runCommandForced("--email", "user@example.test");
User onlyUser = Iterables.getOnlyElement(DatabaseHelper.loadAllOf(User.class));
assertThat(onlyUser.getEmailAddress()).isEqualTo("user@example.test");
verify(connection)
.sendPostRequest(
UpdateUserGroupAction.PATH,
ImmutableMap.of(
"userEmailAddress",
"user@example.test",
"groupEmailAddress",
"group@example.test",
"groupUpdateMode",
"ADD"),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
verifyNoInteractions(iamClient);
verifyNoMoreInteractions(connection);
}
@Test
void testFailure_neitherGroupNorIapServiceId() {
command.maybeGroupEmailAddress = Optional.empty();
command.consoleIapServiceId = Optional.empty();
IllegalArgumentException e =
assertThrows(
IllegalArgumentException.class, () -> runCommandForced("--email", "user@example.test"));
assertThat(e)
.hasMessageThat()
.isEqualTo("At least one of groupEmailAddress or consoleIapServiceId must be present");
}
@Test @Test
void testSuccess_registryLock() throws Exception { void testSuccess_registryLock() throws Exception {
runCommandForced( runCommandForced(
@@ -107,7 +144,8 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
void testSuccess_admin() throws Exception { void testSuccess_admin() throws Exception {
runCommandForced("--email", "user@example.test", "--admin", "true"); runCommandForced("--email", "user@example.test", "--admin", "true");
assertThat(loadExistingUser("user@example.test").getUserRoles().isAdmin()).isTrue(); assertThat(loadExistingUser("user@example.test").getUserRoles().isAdmin()).isTrue();
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE); verify(iamClient)
.addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE, "consoleIapServiceId");
verifyNoMoreInteractions(iamClient); verifyNoMoreInteractions(iamClient);
verifyNoInteractions(connection); verifyNoInteractions(connection);
} }
@@ -117,7 +155,8 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
runCommandForced("--email", "user@example.test", "--global_role", "FTE"); runCommandForced("--email", "user@example.test", "--global_role", "FTE");
assertThat(loadExistingUser("user@example.test").getUserRoles().getGlobalRole()) assertThat(loadExistingUser("user@example.test").getUserRoles().getGlobalRole())
.isEqualTo(GlobalRole.FTE); .isEqualTo(GlobalRole.FTE);
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE); verify(iamClient)
.addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE, "consoleIapServiceId");
verifyNoMoreInteractions(iamClient); verifyNoMoreInteractions(iamClient);
verifyNoInteractions(connection); verifyNoInteractions(connection);
} }
@@ -136,7 +175,8 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
RegistrarRole.ACCOUNT_MANAGER, RegistrarRole.ACCOUNT_MANAGER,
"NewRegistrar", "NewRegistrar",
RegistrarRole.PRIMARY_CONTACT)); RegistrarRole.PRIMARY_CONTACT));
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE); verify(iamClient)
.addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE, "consoleIapServiceId");
verifyNoMoreInteractions(iamClient); verifyNoMoreInteractions(iamClient);
verifyNoInteractions(connection); verifyNoInteractions(connection);
} }
@@ -144,7 +184,8 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
@Test @Test
void testFailure_alreadyExists() throws Exception { void testFailure_alreadyExists() throws Exception {
runCommandForced("--email", "user@example.test"); runCommandForced("--email", "user@example.test");
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE); verify(iamClient)
.addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE, "consoleIapServiceId");
verifyNoMoreInteractions(iamClient); verifyNoMoreInteractions(iamClient);
assertThat( assertThat(
assertThrows( assertThrows(
@@ -38,6 +38,7 @@ import org.mockito.ArgumentCaptor;
/** Unit tests for {@link IamClient}. */ /** Unit tests for {@link IamClient}. */
public class IamClientTest { public class IamClientTest {
private final CloudResourceManager resourceManager = mock(CloudResourceManager.class); private final CloudResourceManager resourceManager = mock(CloudResourceManager.class);
private final String consoleIapServiceId = "123456789";
private final String projectId = "my-project"; private final String projectId = "my-project";
private final String account = "test@example.test"; private final String account = "test@example.test";
private final String role = "roles/fakeRole"; private final String role = "roles/fakeRole";
@@ -89,11 +90,17 @@ public class IamClientTest {
void testSuccess_addBinding_noMatchedBindingExists() throws Exception { void testSuccess_addBinding_noMatchedBindingExists() throws Exception {
setupRequests(); setupRequests();
assertThat(bindings.size()).isEqualTo(1); assertThat(bindings.size()).isEqualTo(1);
client.addBinding(account, role); client.addBinding(account, role, consoleIapServiceId);
assertThat(bindings.size()).isEqualTo(2); assertThat(bindings.size()).isEqualTo(2);
Binding binding = bindings.get(1); Binding binding = bindings.get(1);
assertThat(binding.getRole()).isEqualTo(role); assertThat(binding.getRole()).isEqualTo(role);
assertThat(binding.getMembers()).containsExactly("user:" + account); assertThat(binding.getMembers()).containsExactly("user:" + account);
assertThat(binding.getCondition()).isNotNull();
assertThat(binding.getCondition().getTitle()).isEqualTo("Registrar Console IAP access");
assertThat(binding.getCondition().getDescription())
.isEqualTo("Restrict IAP access only to the Registrar Console HTTP(s) load balancer");
assertThat(binding.getCondition().getExpression())
.isEqualTo("resource.name == 'projects/my-project/iap_web/compute/services/123456789'");
verifySetPolicyRequest(); verifySetPolicyRequest();
} }
@@ -107,7 +114,7 @@ public class IamClientTest {
when(matchedBinding.getMembers()).thenReturn(existingMembers); when(matchedBinding.getMembers()).thenReturn(existingMembers);
bindings.add(matchedBinding); bindings.add(matchedBinding);
assertThat(bindings.size()).isEqualTo(2); assertThat(bindings.size()).isEqualTo(2);
client.addBinding(account, role); client.addBinding(account, role, consoleIapServiceId);
assertThat(bindings.size()).isEqualTo(2); assertThat(bindings.size()).isEqualTo(2);
assertThat(existingMembers) assertThat(existingMembers)
.containsExactly("serviceAccount:service@example.test", "user:" + account); .containsExactly("serviceAccount:service@example.test", "user:" + account);
@@ -125,7 +132,7 @@ public class IamClientTest {
when(matchedBinding.getMembers()).thenReturn(existingMembers); when(matchedBinding.getMembers()).thenReturn(existingMembers);
bindings.add(matchedBinding); bindings.add(matchedBinding);
assertThat(bindings.size()).isEqualTo(2); assertThat(bindings.size()).isEqualTo(2);
client.addBinding(account, role); client.addBinding(account, role, consoleIapServiceId);
assertThat(bindings.size()).isEqualTo(2); assertThat(bindings.size()).isEqualTo(2);
assertThat(existingMembers) assertThat(existingMembers)
.containsExactly("serviceAccount:service@example.test", "user:" + account); .containsExactly("serviceAccount:service@example.test", "user:" + account);
@@ -73,6 +73,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
command.clock = fakeClock; command.clock = fakeClock;
command.passwordGenerator = passwordGenerator; command.passwordGenerator = passwordGenerator;
command.maybeGroupEmailAddress = Optional.of("group@example.com"); command.maybeGroupEmailAddress = Optional.of("group@example.com");
command.consoleIapServiceId = Optional.of("consoleIapServiceId");
command.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils(); command.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
command.iamClient = iamClient; command.iamClient = iamClient;
persistPremiumList("default_sandbox_list", USD, "sandbox,USD 1000"); persistPremiumList("default_sandbox_list", USD, "sandbox,USD 1000");
@@ -129,7 +130,9 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
String groupEmailAddress = command.maybeGroupEmailAddress.orElse(null); String groupEmailAddress = command.maybeGroupEmailAddress.orElse(null);
if (groupEmailAddress == null) { if (groupEmailAddress == null) {
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update"); cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
verify(iamClient).addBinding(emailAddress, IAP_SECURED_WEB_APP_USER_ROLE); verify(iamClient)
.addBinding(
emailAddress, IAP_SECURED_WEB_APP_USER_ROLE, command.consoleIapServiceId.get());
} else { } else {
cloudTasksHelper.assertTasksEnqueued( cloudTasksHelper.assertTasksEnqueued(
"console-user-group-update", "console-user-group-update",
@@ -144,6 +144,7 @@ class ConsoleOteActionTest extends ConsoleActionBaseTestCase {
verifyIapPermission( verifyIapPermission(
"contact@registry.example", "contact@registry.example",
Optional.of("someRandomString@email.test"), Optional.of("someRandomString@email.test"),
Optional.of("consoleIapServiceId"),
cloudTasksHelper, cloudTasksHelper,
iamClient); iamClient);
} }
@@ -216,6 +217,7 @@ class ConsoleOteActionTest extends ConsoleActionBaseTestCase {
iamClient, iamClient,
registrarId, registrarId,
maybeGroupEmailAddress, maybeGroupEmailAddress,
Optional.of("consoleIapServiceId"),
passwordGenerator, passwordGenerator,
oteCreateData); oteCreateData);
} }
@@ -663,6 +663,7 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
iamClient, iamClient,
"email.com", "email.com",
Optional.of("someRandomString"), Optional.of("someRandomString"),
Optional.of("consoleIapServiceId"),
passwordGenerator, passwordGenerator,
userData, userData,
"TheRegistrar"); "TheRegistrar");