feat: 连接主机.

This commit is contained in:
lijiahang
2023-12-26 18:29:42 +08:00
parent ad914eb7bb
commit c0982bfc2c
16 changed files with 423 additions and 32 deletions

View File

@@ -0,0 +1,52 @@
package com.orion.ops.module.asset.entity.dto;
import com.orion.ops.module.asset.entity.domain.HostKeyDO;
import com.orion.ops.module.asset.enums.HostSshAuthTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 主机连接参数
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/12/26 15:47
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "HostSshConnectDTO", description = "主机连接参数")
public class HostSshConnectDTO {
@Schema(description = "hostId")
private Long hostId;
@Schema(description = "主机地址")
private String address;
@Schema(description = "端口")
private Integer port;
@Schema(description = "超时时间")
private Integer timeout;
@Schema(description = "认证方式")
private HostSshAuthTypeEnum authType;
@Schema(description = "用户名")
private String username;
@Schema(description = "密码")
private String password;
@Schema(description = "主机秘钥")
private HostKeyDO key;
// @Schema(description = "")
// private ;
}

View File

@@ -28,14 +28,14 @@ public enum HostExtraSshAuthTypeEnum {
public static HostExtraSshAuthTypeEnum of(String type) {
if (type == null) {
return DEFAULT;
return null;
}
for (HostExtraSshAuthTypeEnum value : values()) {
if (value.name().equals(type)) {
return value;
}
}
return DEFAULT;
return null;
}
}

View File

@@ -7,7 +7,7 @@ package com.orion.ops.module.asset.enums;
* @version 1.0.0
* @since 2023/9/21 19:01
*/
public enum HostConfigSshAuthTypeEnum {
public enum HostSshAuthTypeEnum {
/**
* 密码验证
@@ -26,11 +26,11 @@ public enum HostConfigSshAuthTypeEnum {
;
public static HostConfigSshAuthTypeEnum of(String type) {
public static HostSshAuthTypeEnum of(String type) {
if (type == null) {
return null;
}
for (HostConfigSshAuthTypeEnum value : values()) {
for (HostSshAuthTypeEnum value : values()) {
if (value.name().equals(type)) {
return value;
}

View File

@@ -12,7 +12,7 @@ import com.orion.ops.framework.common.security.PasswordModifier;
import com.orion.ops.framework.common.utils.Valid;
import com.orion.ops.module.asset.dao.HostIdentityDAO;
import com.orion.ops.module.asset.dao.HostKeyDAO;
import com.orion.ops.module.asset.enums.HostConfigSshAuthTypeEnum;
import com.orion.ops.module.asset.enums.HostSshAuthTypeEnum;
import com.orion.ops.module.asset.handler.host.config.model.HostSshConfigModel;
import org.springframework.stereotype.Component;
@@ -44,7 +44,7 @@ public class HostSshConfigStrategy implements MapDataStrategy<HostSshConfigModel
return HostSshConfigModel.builder()
.port(SSH_PORT)
.username(USERNAME)
.authType(HostConfigSshAuthTypeEnum.PASSWORD.name())
.authType(HostSshAuthTypeEnum.PASSWORD.name())
.charset(Const.UTF_8)
.connectTimeout(Const.MS_S_10)
.fileNameCharset(Const.UTF_8)
@@ -55,7 +55,7 @@ public class HostSshConfigStrategy implements MapDataStrategy<HostSshConfigModel
@Override
public void preValid(HostSshConfigModel model) {
// 验证认证类型
Valid.valid(HostConfigSshAuthTypeEnum::of, model.getAuthType());
Valid.valid(HostSshAuthTypeEnum::of, model.getAuthType());
// 验证编码格式
this.validCharset(model.getCharset());
this.validCharset(model.getFileNameCharset());
@@ -105,7 +105,7 @@ public class HostSshConfigStrategy implements MapDataStrategy<HostSshConfigModel
*/
private void checkEncryptPassword(HostSshConfigModel before, HostSshConfigModel after) {
// 非密码认证则直接赋值
if (!HostConfigSshAuthTypeEnum.PASSWORD.name().equals(after.getAuthType())) {
if (!HostSshAuthTypeEnum.PASSWORD.name().equals(after.getAuthType())) {
after.setPassword(before.getPassword());
return;
}

View File

@@ -74,12 +74,14 @@ public class HostSshExtraStrategy implements MapDataStrategy<HostSshExtraModel>
// 验证主机秘钥是否有权限
if (keyId != null) {
Valid.isTrue(dataPermissionApi.hasPermission(DataPermissionTypeEnum.HOST_KEY, userId, keyId),
ErrorMessage.DATA_NO_PERMISSION);
ErrorMessage.ANY_NO_PERMISSION,
DataPermissionTypeEnum.HOST_KEY.getPermissionName());
}
// 验证主机身份是否有权限
if (identityId != null) {
Valid.isTrue(dataPermissionApi.hasPermission(DataPermissionTypeEnum.HOST_IDENTITY, userId, identityId),
ErrorMessage.DATA_NO_PERMISSION);
ErrorMessage.ANY_NO_PERMISSION,
DataPermissionTypeEnum.HOST_IDENTITY.getPermissionName());
}
}
}

View File

@@ -34,6 +34,14 @@ public interface AssetAuthorizedDataService {
*/
AuthorizedHostWrapperVO getUserAuthorizedHostGroup(Long userId);
/**
* 获取用户已授权的主机id 不查询角色
*
* @param userId userId
* @return hostId
*/
List<Long> getUserAuthorizedHostId(Long userId);
/**
* 查询用户已授权的主机秘钥
*

View File

@@ -0,0 +1,33 @@
package com.orion.ops.module.asset.service;
import com.orion.net.host.SessionStore;
/**
* 主机连接服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/12/26 14:22
*/
public interface HostConnectService {
/**
* 打开主机会话
* 鉴权并且读取用户配置
*
* @param hostId hostId
* @param userId userId
* @return session
*/
SessionStore openSessionStore(Long hostId, Long userId);
/**
* 打开主机会话
* 使用默认配置 不鉴权
*
* @param hostId hostId
* @return session
*/
SessionStore openSessionStore(Long hostId);
}

View File

@@ -1,8 +1,10 @@
package com.orion.ops.module.asset.service;
import com.orion.ops.framework.common.handler.data.model.GenericsDataModel;
import com.orion.ops.module.asset.entity.request.host.HostAliasUpdateRequest;
import com.orion.ops.module.asset.entity.request.host.HostExtraQueryRequest;
import com.orion.ops.module.asset.entity.request.host.HostExtraUpdateRequest;
import com.orion.ops.module.asset.enums.HostExtraItemEnum;
import java.util.Map;
@@ -32,6 +34,17 @@ public interface HostExtraService {
*/
Map<String, Object> getHostExtra(Long hostId, String item);
/**
* 获取主机额外配置
*
* @param userId userId
* @param hostId hostId
* @param item item
* @param <T> T
* @return extra
*/
<T extends GenericsDataModel> T getHostExtra(Long userId, Long hostId, HostExtraItemEnum item);
/**
* 获取多个主机拓展信息
*

View File

@@ -101,6 +101,24 @@ public class AssetAuthorizedDataServiceImpl implements AssetAuthorizedDataServic
}
}
@Override
public List<Long> getUserAuthorizedHostId(Long userId) {
// 查询授权的分组
List<Long> authorizedIdList = dataPermissionApi.getUserAuthorizedRelIdList(DataPermissionTypeEnum.HOST_GROUP, userId);
if (authorizedIdList.isEmpty()) {
return Lists.empty();
}
// 查询分组主机映射
Map<Long, Set<Long>> dataGroupRel = dataGroupRelApi.getGroupRelList(DataGroupTypeEnum.HOST);
// 返回
return authorizedIdList.stream()
.map(dataGroupRel::get)
.filter(Lists::isNotEmpty)
.flatMap(Collection::stream)
.distinct()
.collect(Collectors.toList());
}
@Override
public List<HostKeyVO> getUserAuthorizedHostKey(Long userId) {
if (systemUserApi.isAdminUser(userId)) {

View File

@@ -1,6 +1,5 @@
package com.orion.ops.module.asset.service.impl;
import com.alibaba.fastjson.JSON;
import com.orion.ops.framework.biz.operator.log.core.uitls.OperatorLogs;
import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.framework.common.constant.ErrorMessage;
@@ -67,7 +66,7 @@ public class HostConfigServiceImpl implements HostConfigService {
if (config == null) {
return null;
}
return (T) JSON.parseObject(config.getConfig(), type.getModel());
return type.parse(config.getConfig());
}
@Override

View File

@@ -0,0 +1,243 @@
package com.orion.ops.module.asset.service.impl;
import com.orion.lang.exception.AuthenticationException;
import com.orion.lang.utils.Exceptions;
import com.orion.lang.utils.Strings;
import com.orion.net.host.SessionHolder;
import com.orion.net.host.SessionStore;
import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.framework.common.constant.ErrorMessage;
import com.orion.ops.framework.common.utils.CryptoUtils;
import com.orion.ops.framework.common.utils.Valid;
import com.orion.ops.module.asset.dao.HostDAO;
import com.orion.ops.module.asset.dao.HostIdentityDAO;
import com.orion.ops.module.asset.dao.HostKeyDAO;
import com.orion.ops.module.asset.entity.domain.HostDO;
import com.orion.ops.module.asset.entity.domain.HostIdentityDO;
import com.orion.ops.module.asset.entity.domain.HostKeyDO;
import com.orion.ops.module.asset.entity.dto.HostSshConnectDTO;
import com.orion.ops.module.asset.enums.HostConfigTypeEnum;
import com.orion.ops.module.asset.enums.HostExtraItemEnum;
import com.orion.ops.module.asset.enums.HostExtraSshAuthTypeEnum;
import com.orion.ops.module.asset.enums.HostSshAuthTypeEnum;
import com.orion.ops.module.asset.handler.host.config.model.HostSshConfigModel;
import com.orion.ops.module.asset.handler.host.extra.model.HostSshExtraModel;
import com.orion.ops.module.asset.service.HostConfigService;
import com.orion.ops.module.asset.service.HostConnectService;
import com.orion.ops.module.asset.service.HostExtraService;
import com.orion.ops.module.infra.api.DataPermissionApi;
import com.orion.ops.module.infra.api.SystemUserApi;
import com.orion.ops.module.infra.enums.DataPermissionTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.Optional;
/**
* 主机连接服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/12/26 14:27
*/
@Slf4j
@Service
public class HostConnectServiceImpl implements HostConnectService {
@Resource
private HostConfigService hostConfigService;
@Resource
private HostExtraService hostExtraService;
@Resource
private AssetAuthorizedDataServiceImpl assetAuthorizedDataService;
@Resource
private HostDAO hostDAO;
@Resource
private HostIdentityDAO hostIdentityDAO;
@Resource
private HostKeyDAO hostKeyDAO;
@Resource
private DataPermissionApi dataPermissionApi;
@Resource
private SystemUserApi systemUserApi;
@Override
public SessionStore openSessionStore(Long hostId, Long userId) {
log.info("HostConnectService.openSessionStore-withUser hostId: {}, userId: {}", hostId, userId);
// 查询主机
HostDO host = hostDAO.selectById(hostId);
Valid.notNull(host, ErrorMessage.HOST_ABSENT);
// 查询主机配置
HostSshConfigModel config = hostConfigService.getHostConfig(hostId, HostConfigTypeEnum.SSH);
Valid.notNull(config, ErrorMessage.CONFIG_ABSENT);
// 查询主机额外配置
HostSshExtraModel extra = hostExtraService.getHostExtra(userId, hostId, HostExtraItemEnum.SSH);
// 非管理员检查权限
if (!systemUserApi.isAdminUser(userId)) {
// 验证主机是否有权限
List<Long> hostIdList = assetAuthorizedDataService.getUserAuthorizedHostId(userId);
Valid.isTrue(hostIdList.contains(hostId),
ErrorMessage.ANY_NO_PERMISSION,
DataPermissionTypeEnum.HOST_GROUP.getPermissionName());
// 检查额外配置权限
if (extra != null) {
HostExtraSshAuthTypeEnum extraAuthType = HostExtraSshAuthTypeEnum.of(extra.getAuthType());
if (HostExtraSshAuthTypeEnum.CUSTOM_KEY.equals(extraAuthType)) {
// 验证主机秘钥是否有权限
Valid.isTrue(dataPermissionApi.hasPermission(DataPermissionTypeEnum.HOST_KEY, userId, extra.getKeyId()),
ErrorMessage.ANY_NO_PERMISSION,
DataPermissionTypeEnum.HOST_KEY.getPermissionName());
} else if (HostExtraSshAuthTypeEnum.CUSTOM_IDENTITY.equals(extraAuthType)) {
// 验证主机身份是否有权限
Valid.isTrue(dataPermissionApi.hasPermission(DataPermissionTypeEnum.HOST_IDENTITY, userId, extra.getIdentityId()),
ErrorMessage.ANY_NO_PERMISSION,
DataPermissionTypeEnum.HOST_IDENTITY.getPermissionName());
}
}
}
// 连接
return this.openSessionStoreWithHost(host, config, extra);
}
@Override
public SessionStore openSessionStore(Long hostId) {
log.info("HostConnectService.openSessionStore-withHost hostId: {}", hostId);
// 查询主机
HostDO host = hostDAO.selectById(hostId);
Valid.notNull(host, ErrorMessage.HOST_ABSENT);
// 查询主机配置
HostSshConfigModel config = hostConfigService.getHostConfig(hostId, HostConfigTypeEnum.SSH);
Valid.notNull(config, ErrorMessage.CONFIG_ABSENT);
// 连接
return this.openSessionStoreWithHost(host, config, null);
}
/**
* 打开主机会话
*
* @param host host
* @param config config
* @param extra extra
* @return session
*/
private SessionStore openSessionStoreWithHost(HostDO host,
HostSshConfigModel config,
HostSshExtraModel extra) {
// 获取认证方式
HostSshAuthTypeEnum authType = HostSshAuthTypeEnum.of(config.getAuthType());
HostExtraSshAuthTypeEnum extraAuthType = Optional.ofNullable(extra)
.map(HostSshExtraModel::getAuthType)
.map(HostExtraSshAuthTypeEnum::of)
.orElse(HostExtraSshAuthTypeEnum.DEFAULT);
if (HostExtraSshAuthTypeEnum.CUSTOM_KEY.equals(extraAuthType)) {
// 自定义秘钥
authType = HostSshAuthTypeEnum.KEY;
config.setKeyId(extra.getKeyId());
if (extra.getUsername() != null) {
config.setUsername(extra.getUsername());
}
} else if (HostExtraSshAuthTypeEnum.CUSTOM_IDENTITY.equals(extraAuthType)) {
// 自定义身份
authType = HostSshAuthTypeEnum.IDENTITY;
config.setIdentityId(extra.getIdentityId());
}
// 填充认证信息
HostSshConnectDTO conn = new HostSshConnectDTO();
conn.setHostId(host.getId());
conn.setAddress(host.getAddress());
conn.setPort(config.getPort());
conn.setTimeout(config.getConnectTimeout());
conn.setAuthType(authType);
conn.setUsername(config.getUsername());
// 填充身份信息
if (HostSshAuthTypeEnum.PASSWORD.equals(authType)) {
conn.setPassword(config.getPassword());
} else if (HostSshAuthTypeEnum.KEY.equals(authType)) {
// 秘钥认证
HostKeyDO key = hostKeyDAO.selectById(config.getKeyId());
Valid.notNull(key, ErrorMessage.KEY_ABSENT);
conn.setKey(key);
} else if (HostSshAuthTypeEnum.IDENTITY.equals(authType)) {
// 身份认证
HostIdentityDO identity = hostIdentityDAO.selectById(config.getIdentityId());
Valid.notNull(identity, ErrorMessage.IDENTITY_ABSENT);
if (identity.getKeyId() != null) {
// 秘钥认证
HostKeyDO key = hostKeyDAO.selectById(config.getKeyId());
Valid.notNull(key, ErrorMessage.KEY_ABSENT);
conn.setKey(key);
}
conn.setUsername(identity.getUsername());
conn.setPassword(identity.getPassword());
}
// 连接
return this.openSessionStoreWithConfig(conn);
}
/**
* 打开主机会话
*
* @param conn conn
* @return session
*/
private SessionStore openSessionStoreWithConfig(HostSshConnectDTO conn) {
Long hostId = conn.getHostId();
String address = conn.getAddress();
String username = conn.getUsername();
log.info("HostConnectService-openSessionStore-start hostId: {}, address: {}, username: {}", hostId, address, username);
try {
SessionHolder sessionHolder = new SessionHolder();
HostKeyDO key = conn.getKey();
final boolean useKey = key != null;
// 使用秘钥认证
if (useKey) {
// 加载秘钥
String publicKey = Optional.ofNullable(key.getPublicKey())
.map(CryptoUtils::decryptAsString)
.orElse(null);
String privateKey = Optional.ofNullable(key.getPrivateKey())
.map(CryptoUtils::decryptAsString)
.orElse(null);
String password = Optional.ofNullable(key.getPassword())
.map(CryptoUtils::decryptAsString)
.orElse(null);
sessionHolder.addIdentityValue(String.valueOf(key.getId()),
privateKey,
publicKey,
password);
}
// 获取会话
SessionStore session = sessionHolder.getSession(address, conn.getPort(), username);
// 使用密码认证
if (!useKey) {
session.password(CryptoUtils.decryptAsString(conn.getPassword()));
}
// 连接
session.connect(conn.getTimeout());
log.info("HostConnectService-openSessionStore-success hostId: {}, address: {}, username: {}", hostId, address, username);
return session;
} catch (Exception e) {
String message = e.getMessage();
log.error("HostConnectService-openSessionStore-error hostId: {}, address: {}, username: {}, message: {}", hostId, address, username, message, e);
if (Strings.contains(message, Const.TIMEOUT)) {
// 连接超时
throw Exceptions.timeout(message, e);
} else if (e instanceof AuthenticationException) {
// 认证失败
throw Exceptions.authentication(message, e);
} else {
throw e;
}
}
}
}

View File

@@ -67,6 +67,17 @@ public class HostExtraServiceImpl implements HostExtraService {
return this.checkItemAndToView(extraItem, extraValue, userId, hostId);
}
@Override
public <T extends GenericsDataModel> T getHostExtra(Long userId, Long hostId, HostExtraItemEnum item) {
DataExtraQueryDTO query = DataExtraQueryDTO.builder()
.userId(userId)
.relId(hostId)
.item(item.getItem())
.build();
String extraValue = dataExtraApi.getExtraValue(query, DataExtraTypeEnum.HOST);
return item.parse(extraValue);
}
@Override
public Map<String, Map<String, Object>> getHostExtraList(HostExtraQueryRequest request) {
Long hostId = request.getHostId();