From 911f951e2afb29709c1eaeb2b6c83b2a5e465389 Mon Sep 17 00:00:00 2001 From: Daniel Augusto Veronezi Salvador <38945620+GutoVeronezi@users.noreply.github.com> Date: Fri, 20 Jan 2023 05:25:07 -0300 Subject: [PATCH] Handle console session in multiple management servers (#7094) --- .../consoleproxy/ConsoleAccessManager.java | 2 + .../java/com/cloud/vm/ConsoleSessionVO.java | 123 ++++++++++++++++++ .../com/cloud/vm/dao/ConsoleSessionDao.java | 31 +++++ .../cloud/vm/dao/ConsoleSessionDaoImpl.java | 37 ++++++ ...spring-engine-schema-core-daos-context.xml | 1 + .../META-INF/db/schema-41720to41800.sql | 18 +++ .../com/cloud/consoleproxy/AgentHookBase.java | 5 +- .../ConsoleAccessManagerImpl.java | 35 +++-- 8 files changed, 242 insertions(+), 10 deletions(-) create mode 100644 engine/schema/src/main/java/com/cloud/vm/ConsoleSessionVO.java create mode 100644 engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java create mode 100644 engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java diff --git a/api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManager.java b/api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManager.java index b1bd198309a..c763730a952 100644 --- a/api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManager.java +++ b/api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManager.java @@ -26,4 +26,6 @@ public interface ConsoleAccessManager extends Manager { boolean isSessionAllowed(String sessionUuid); void removeSessions(String[] sessionUuids); + + void removeSession(String sessionUuid); } diff --git a/engine/schema/src/main/java/com/cloud/vm/ConsoleSessionVO.java b/engine/schema/src/main/java/com/cloud/vm/ConsoleSessionVO.java new file mode 100644 index 00000000000..2373896b727 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/vm/ConsoleSessionVO.java @@ -0,0 +1,123 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.vm; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.Date; + +@Entity +@Table(name = "console_session") +public class ConsoleSessionVO { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "created") + private Date created; + + @Column(name = "account_id") + private long accountId; + + @Column(name = "user_id") + private long userId; + + @Column(name = "instance_id") + private long instanceId; + + @Column(name = "host_id") + private long hostId; + + @Column(name = "removed") + private Date removed; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public long getAccountId() { + return accountId; + } + + public void setAccountId(long accountId) { + this.accountId = accountId; + } + + public long getUserId() { + return userId; + } + + public void setUserId(long userId) { + this.userId = userId; + } + + public long getInstanceId() { + return instanceId; + } + + public void setInstanceId(long instanceId) { + this.instanceId = instanceId; + } + + public long getHostId() { + return hostId; + } + + public void setHostId(long hostId) { + this.hostId = hostId; + } + + public Date getRemoved() { + return removed; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } +} diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java new file mode 100644 index 00000000000..43f30970bef --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java @@ -0,0 +1,31 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.vm.dao; + +import com.cloud.vm.ConsoleSessionVO; +import com.cloud.utils.db.GenericDao; + +public interface ConsoleSessionDao extends GenericDao { + + void removeSession(String sessionUuid); + + boolean isSessionAllowed(String sessionUuid); + +} diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java new file mode 100644 index 00000000000..2ac41918c2c --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java @@ -0,0 +1,37 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.vm.dao; + +import com.cloud.vm.ConsoleSessionVO; +import com.cloud.utils.db.GenericDaoBase; + +public class ConsoleSessionDaoImpl extends GenericDaoBase implements ConsoleSessionDao { + + @Override + public void removeSession(String sessionUuid) { + ConsoleSessionVO session = findByUuid(sessionUuid); + remove(session.getId()); + } + + @Override + public boolean isSessionAllowed(String sessionUuid) { + return findByUuid(sessionUuid) != null; + } +} diff --git a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml index 511dccd0083..cec86d2d7be 100644 --- a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml +++ b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml @@ -45,6 +45,7 @@ + diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41720to41800.sql b/engine/schema/src/main/resources/META-INF/db/schema-41720to41800.sql index 89698ffede6..726d36caea8 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41720to41800.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41720to41800.sql @@ -1039,3 +1039,21 @@ WHERE role_id = (SELECT id FROM `cloud`.`roles` WHERE name = 'Read-Only User - INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`) SELECT UUID(), `roles`.`id`, 'isAccountAllowedToCreateOfferingsWithTags', 'ALLOW' FROM `cloud`.`roles` WHERE `role_type` = 'DomainAdmin'; + +--- Create table for handling console sessions #7094 + +CREATE TABLE IF NOT EXISTS `cloud`.`console_session` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY, + `uuid` varchar(40) NOT NULL COMMENT 'UUID generated for the session', + `created` datetime NOT NULL COMMENT 'When the session was created', + `account_id` bigint(20) unsigned NOT NULL COMMENT 'Account who generated the session', + `user_id` bigint(20) unsigned NOT NULL COMMENT 'User who generated the session', + `instance_id` bigint(20) unsigned NOT NULL COMMENT 'VM for which the session was generated', + `host_id` bigint(20) unsigned NOT NULL COMMENT 'Host where the VM was when the session was generated', + `removed` datetime COMMENT 'When the session was removed/used', + CONSTRAINT `fk_consolesession__account_id` FOREIGN KEY(`account_id`) REFERENCES `cloud`.`account` (`id`), + CONSTRAINT `fk_consolesession__user_id` FOREIGN KEY(`user_id`) REFERENCES `cloud`.`user`(`id`), + CONSTRAINT `fk_consolesession__instance_id` FOREIGN KEY(`instance_id`) REFERENCES `cloud`.`vm_instance`(`id`), + CONSTRAINT `fk_consolesession__host_id` FOREIGN KEY(`host_id`) REFERENCES `cloud`.`host`(`id`), + CONSTRAINT `uc_consolesession__uuid` UNIQUE (`uuid`) +); diff --git a/server/src/main/java/com/cloud/consoleproxy/AgentHookBase.java b/server/src/main/java/com/cloud/consoleproxy/AgentHookBase.java index 619825ecf43..0bac76923cf 100644 --- a/server/src/main/java/com/cloud/consoleproxy/AgentHookBase.java +++ b/server/src/main/java/com/cloud/consoleproxy/AgentHookBase.java @@ -106,10 +106,13 @@ public abstract class AgentHookBase implements AgentHook { } if (!consoleAccessManager.isSessionAllowed(sessionUuid)) { - s_logger.error("Invalid session, only one session allowed per token"); + s_logger.error(String.format("Session [%s] has been already used or does not exist.", sessionUuid)); return new ConsoleAccessAuthenticationAnswer(cmd, false); } + s_logger.debug(String.format("Removing session [%s] as it was just used.", sessionUuid)); + consoleAccessManager.removeSession(sessionUuid); + if (!ticket.equals(ticketInUrl)) { Date now = new Date(); // considering of minute round-up diff --git a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java index 559ceb43e05..0e3a0f822f6 100644 --- a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java @@ -38,10 +38,12 @@ import com.cloud.utils.Ternary; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.db.EntityManager; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.ConsoleSessionVO; import com.cloud.vm.UserVmDetailVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.VmDetailConstants; +import com.cloud.vm.dao.ConsoleSessionDao; import com.cloud.vm.dao.UserVmDetailsDao; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -49,6 +51,7 @@ import org.apache.cloudstack.api.command.user.consoleproxy.ConsoleEndpoint; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.security.keys.KeysManager; import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; @@ -60,10 +63,8 @@ import javax.naming.ConfigurationException; import java.util.Arrays; import java.util.Date; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.UUID; public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAccessManager { @@ -84,6 +85,8 @@ public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAcce private AgentManager agentManager; @Inject private ConsoleProxyManager consoleProxyManager; + @Inject + private ConsoleSessionDao consoleSessionDao; private static KeysManager secretKeysManager; private final Gson gson = new GsonBuilder().create(); @@ -94,12 +97,9 @@ public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAcce VirtualMachine.State.Stopped, VirtualMachine.State.Error, VirtualMachine.State.Destroyed ); - private static Set allowedSessions; - @Override public boolean configure(String name, Map params) throws ConfigurationException { ConsoleAccessManagerImpl.secretKeysManager = keysManager; - ConsoleAccessManagerImpl.allowedSessions = new HashSet<>(); return super.configure(name, params); } @@ -146,16 +146,23 @@ public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAcce @Override public boolean isSessionAllowed(String sessionUuid) { - return allowedSessions.contains(sessionUuid); + return consoleSessionDao.isSessionAllowed(sessionUuid); } @Override public void removeSessions(String[] sessionUuids) { - for (String r : sessionUuids) { - allowedSessions.remove(r); + if (ArrayUtils.isNotEmpty(sessionUuids)) { + for (String sessionUuid : sessionUuids) { + removeSession(sessionUuid); + } } } + @Override + public void removeSession(String sessionUuid) { + consoleSessionDao.removeSession(sessionUuid); + } + protected boolean checkSessionPermission(VirtualMachine vm, Account account) { if (accountManager.isRootAdmin(account.getId())) { return true; @@ -289,7 +296,7 @@ public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAcce String url = generateConsoleAccessUrl(rootUrl, param, token, vncPort, vm); s_logger.debug("Adding allowed session: " + sessionUuid); - allowedSessions.add(sessionUuid); + persistConsoleSession(sessionUuid, vm.getId(), hostVo.getId()); managementServer.setConsoleAccessForVm(vm.getId(), sessionUuid); ConsoleEndpoint consoleEndpoint = new ConsoleEndpoint(true, url); @@ -303,6 +310,16 @@ public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAcce return consoleEndpoint; } + protected void persistConsoleSession(String sessionUuid, long instanceId, long hostId) { + ConsoleSessionVO consoleSessionVo = new ConsoleSessionVO(); + consoleSessionVo.setUuid(sessionUuid); + consoleSessionVo.setAccountId(CallContext.current().getCallingAccountId()); + consoleSessionVo.setUserId(CallContext.current().getCallingUserId()); + consoleSessionVo.setInstanceId(instanceId); + consoleSessionVo.setHostId(hostId); + consoleSessionDao.persist(consoleSessionVo); + } + private String generateConsoleAccessUrl(String rootUrl, ConsoleProxyClientParam param, String token, int vncPort, VirtualMachine vm) { StringBuilder sb = new StringBuilder(rootUrl);