diff --git a/src/main/java/net/geedge/confagent/ConfagentApplication.java b/src/main/java/net/geedge/confagent/ConfagentApplication.java index 18887c5..e1cc32a 100644 --- a/src/main/java/net/geedge/confagent/ConfagentApplication.java +++ b/src/main/java/net/geedge/confagent/ConfagentApplication.java @@ -2,8 +2,10 @@ package net.geedge.confagent; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.PropertySource; @SpringBootApplication +@PropertySource(value = { "classpath:version.properties" }, encoding = "utf-8", ignoreResourceNotFound = true) public class ConfagentApplication { public static void main(String[] args) { diff --git a/src/main/java/net/geedge/confagent/controller/BaseController.java b/src/main/java/net/geedge/confagent/controller/BaseController.java index 6d19f2f..7f61fe6 100644 --- a/src/main/java/net/geedge/confagent/controller/BaseController.java +++ b/src/main/java/net/geedge/confagent/controller/BaseController.java @@ -1,6 +1,7 @@ package net.geedge.confagent.controller; import cn.hutool.log.Log; +import net.geedge.confagent.util.Tool; import org.springframework.beans.factory.annotation.Value; public abstract class BaseController { @@ -22,6 +23,15 @@ public abstract class BaseController { @Value("${confagent.promtail.config}") protected String promtailConfPath; + + @Value("${version}") + protected String currentVersion; + + + /** + * aes 对称加密 密钥 + */ + public static final byte[] AES_SECRET_KEY = Tool.HexUtil.decodeHex("bde5430614b21baf1c53bd6f616d1a39"); /** * 调用 /-/reload 接口,热加载 server 配置文件, diff --git a/src/main/java/net/geedge/confagent/controller/OTAController.java b/src/main/java/net/geedge/confagent/controller/OTAController.java new file mode 100644 index 0000000..0eda385 --- /dev/null +++ b/src/main/java/net/geedge/confagent/controller/OTAController.java @@ -0,0 +1,83 @@ +package net.geedge.confagent.controller; + +import cn.hutool.log.Log; +import net.geedge.confagent.util.ConfagentUtil; +import net.geedge.confagent.util.R; +import net.geedge.confagent.util.RCode; +import net.geedge.confagent.util.Tool; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import java.io.*; +import java.util.UUID; + +@RestController +public class OTAController extends BaseController{ + + private static Log log = Log.get(); + + @Autowired + private ConfagentUtil confagentUtil; + + private static final String UPDATE_PACKAGE_PATH = "/tmp/nezha/nz-talon/%s/%s"; + + + @PostMapping("/ota") + public R ota(HttpServletRequest request, @RequestParam(name = "file", required = false) MultipartFile file, String md5 )throws IOException{ + + //检查token + String token = request.getHeader("Authorization"); + if (Tool.StrUtil.isBlank(token)) { + return R.error(RCode.AUTH_TOKEN_ISNULL); + }else { + if (confagentUtil.checkToken(token).getCode() != RCode.SUCCESS.getCode())return R.error(RCode.AUTH_TOKEN_INVALID); + } + + //将文件缓存到 /tmp 目录下 + String updateFileName = file.getOriginalFilename(); + File updatePackagePath = Tool.FileUtil.file(String.format(UPDATE_PACKAGE_PATH , UUID.randomUUID().toString().replaceAll("-", ""), updateFileName)); + updatePackagePath.mkdirs(); + try { + file.transferTo(updatePackagePath); + } catch (Exception e) { + return R.error(RCode.OTA_FILE_CACHE_ERROR); + } + + //校验MD5 + String updateFileMd5 = Tool.DigestUtil.md5Hex(new FileInputStream(updatePackagePath)); + log.info("updateFileMd5 : {}",updateFileMd5); + md5 = Tool.AesUtil.decrypt(md5 , AES_SECRET_KEY); + if(!updateFileMd5.equals(md5)){ + return R.error(RCode.OTA_MD5_ERROR); + } + + //校验版本号 + String fileName = file.getOriginalFilename(); + String updateVersion = fileName.split("-")[2]; + updateVersion = updateVersion.substring(0,updateVersion.lastIndexOf(".")); + log.info("current version : {} , update version : {}", currentVersion , updateVersion); + if( !(Tool.DateUtil.betweenDay(Tool.DateUtil.parseDate(updateVersion),Tool.DateUtil.parseDate(currentVersion), false) > 0)){ + return R.error(RCode.OTA_VERSION_ERROR); + } + + + //检查atd服务是否启动 + InputStream atdStatus = Tool.RuntimeUtil.exec("systemctl status atd").getInputStream(); + String atdStatusStr = Tool.IoUtil.read(atdStatus).toString(); + if(!atdStatusStr.contains("Active: active (running)")){ + return R.error(RCode.OTA_ATD_SERVICE_DOWN); + } + + //延迟一分钟更新 使用echo 将更新命令传入at + String atCommand ="echo \"rpm -Uvh %s\" | at now +1 minutes"; + atCommand = String.format(atCommand,updatePackagePath); + log.info("update nz-agent : {}", atCommand); + new ProcessBuilder("/bin/bash", "-c", atCommand).start(); + return R.ok(); + } + +} diff --git a/src/main/java/net/geedge/confagent/util/RCode.java b/src/main/java/net/geedge/confagent/util/RCode.java index 087cde2..2169886 100644 --- a/src/main/java/net/geedge/confagent/util/RCode.java +++ b/src/main/java/net/geedge/confagent/util/RCode.java @@ -18,6 +18,11 @@ public enum RCode { PROMTAIL_START_CMD_ERROR(10009,"The promtail start command is error"), PROMTAIL_STOP_CMD_ERROR(10010,"The promtail stop command is error"), + OTA_FILE_CACHE_ERROR(10011,"Cache rpm package failed"), + OTA_MD5_ERROR(10012,"MD5 is error"), + OTA_VERSION_ERROR(10013,"The updated version is lower than the current version"), + OTA_ATD_SERVICE_DOWN(10014,"The ATD service is down"), + ERROR(999, "error"); //通用错误/未知错误 private RCode(Integer code, String msg) { this.code = code; diff --git a/src/main/java/net/geedge/confagent/util/Tool.java b/src/main/java/net/geedge/confagent/util/Tool.java index 250b0c2..206d0de 100644 --- a/src/main/java/net/geedge/confagent/util/Tool.java +++ b/src/main/java/net/geedge/confagent/util/Tool.java @@ -16,6 +16,8 @@ import java.lang.reflect.Type; import java.math.BigDecimal; import java.net.URLDecoder; import java.nio.ByteBuffer; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.time.LocalDateTime; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; @@ -24,6 +26,10 @@ import java.util.Collection; import java.util.Iterator; import java.util.Spliterator; +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; @@ -1103,4 +1109,66 @@ public class Tool { } } + //aes 工具 + public static class AesUtil { + + private static final String KEY_ALGORITHM = "AES"; + private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";// 默认的加密算法 + + public static String encrypt(String content, byte[] key) { + try { + Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);// 创建密码器 + + byte[] byteContent = content.getBytes("utf-8"); + + cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key));// 初始化为加密模式的密码器 + + byte[] result = cipher.doFinal(byteContent);// 加密 + + return cn.hutool.core.util.HexUtil.encodeHexStr(result); + } catch (Exception ex) { + ex.printStackTrace(); + } + + return null; + } + + public static String decrypt(String content, byte[] key) { + + try { + // 实例化 + Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM); + + // 使用密钥初始化,设置为解密模式 + cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key)); + + // 执行操作 + byte[] result = cipher.doFinal(cn.hutool.core.util.HexUtil.decodeHex(content)); + String s = new String(result, "utf-8"); + return s; + } catch (Exception ex) { + ex.printStackTrace(); + } + return null; + } + + private static SecretKeySpec getSecretKey(byte[] key) { + // 返回生成指定算法密钥生成器的 KeyGenerator 对象 + KeyGenerator kg = null; + try { + kg = KeyGenerator.getInstance(KEY_ALGORITHM); + SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); + random.setSeed(key); + // AES 要求密钥长度为 128 + kg.init(128, random); + // 生成一个密钥 + SecretKey secretKey = kg.generateKey(); + return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);// 转换为AES专用密钥 + } catch (NoSuchAlgorithmException ex) { + ex.printStackTrace(); + } + return null; + } + + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 69016d5..8468a26 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -9,6 +9,11 @@ server: spring: profiles: active: prod + servlet: + multipart: + max-file-size: 100MB + max-request-size: 100MB + enabled: true confagent: tokenFile: ./config/token.auth diff --git a/tools/package.sh b/tools/package.sh index 677cc29..9d4ad5a 100644 --- a/tools/package.sh +++ b/tools/package.sh @@ -24,6 +24,12 @@ mkdir -p $RPM_TALON_PATH $RPM_PROMTAIL_PATH mkdir -p $RPM_TALON_PATH/config echo 'packaging nz-talon ...' +#添加版本信息 +cat >./src/main/resources/version.properties<