Nb
Study
.com
🔍 请输入搜索关键字
最新小记

centos 7.8 手动编译安装和升级openssl 和 OpenSSH

因等保安全需要,需要在centos 7.8系统上, 手动编译安装和升级openssl 和 OpenSSH,记录一下备用。

需要将OpenSSH升级到9.9p2,但是当前 openssl的版本为1.0,而OpenSSH_9.9p2需要openssl1.1或openssl3.0, 因此需要先升级openssl。

由于openssl11的官方源及国内镜像经常失效或下载失败,这里才有源码编译安装。

源码安装 openssl

1.下载源码(1.1.1k 版本)

bash 复制代码
wget https://www.openssl.org/source/old/1.1.1/openssl-1.1.1k.tar.gz
tar -zxf openssl-1.1.1k.tar.gz
cd openssl-1.1.1k

2.编译安装(指定路径,避免覆盖系统默认库)

bash 复制代码
./config --prefix=/usr/local/openssl11 --openssldir=/usr/local/openssl11 shared zlib
make -j 4 && make install

确保已经安装gcc等基础工具和依赖
// 安装编译工具和依赖
sudo yum install -y gcc make zlib-devel openssl-devel

3.确认头文件和库文件路径

  • 头文件路径:/usr/local/openssl11/include/openssl/
  • 库文件路径:/usr/local/openssl11/lib/

验证安装

无论哪种方案,安装/编译完成后,确认头文件存在:

bash 复制代码
# 通过源码编译:
ls /usr/local/openssl11/include/openssl/ssl.h

若输出文件路径,则说明头文件存在,可继续编译 OpenSSH。

注意,通过源码编译安装的 OpenSSL 1.1.1k,默认不会生成 openssl11 这个命令。
可通过绝对路径直接调用源码编译的 OpenSSL,或配置环境变量后直接使用 openssl 命令。

bash 复制代码
# 使用绝对路径
/usr/local/openssl11/bin/openssl version
# 正常输出应显示 OpenSSL 1.1.1k FIPS 25 Mar 2021,说明安装的新版本可用。

# 配置环境变量(永久生效,推荐)
vim /etc/profile
# 在文件末尾添加以下内容,指定OpenSSL的bin目录
export PATH=/usr/local/openssl11/bin:$PATH
# 保存退出后,让配置立即生效
source /etc/profile
# 验证
openssl version
# 输出示例:OpenSSL 1.1.1k  FIPS 25 Mar 2021

注意,若安装后,输出版本时,提示不找到 OpenSSL 1.1.1 的动态共享库 libssl.so.1.1 和 libcrypto.so.1.1。是因为系统默认的动态链接库搜索路径里没有包含该目录下的 lib 文件夹,导致执行 openssl 时加载库失败。

可以将 /usr/local/openssl11/lib 路径添加到系统动态链接库的搜索列表中,让系统能识别到编译生成的共享库。

bash 复制代码
# 创建配置文件,存放 OpenSSL 库路径
echo "/usr/local/openssl11/lib" > /etc/ld.so.conf.d/openssl11.conf
# 更新动态链接库缓存
ldconfig -v

源码安装OpenSSH

假设已经升级openssl 到1.1

升级 OpenSSH 到 9.9p2 需通过源码编译,关键是确保编译时正确关联已安装的 OpenSSL 1.1.1k(路径为 /usr/local/openssl11)。

前置准备

1.备份现有配置和二进制文件(防止升级失败无法回滚):

bash 复制代码
# 备份 ssh 配置文件
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
# 备份现有 ssh 二进制文件(如 ssh、sshd 等)
cp /usr/bin/ssh /usr/bin/ssh.old
cp /usr/sbin/sshd /usr/sbin/sshd.old

2.安装编译依赖(OpenSSH 编译需要的系统库):

bash 复制代码
yum install -y gcc make zlib-devel pam-devel libselinux-devel openssh-clients

如果报错提示当前系统的 IUS 仓库配置失效(镜像地址 404),可以禁用。

bash 复制代码
yum install -y gcc make zlib-devel pam-devel libselinux-devel openssh-clients --disablerepo=ius

该命令会忽略 IUS 仓库,优先使用 CentOS 7 官方的 base、updates 等基础仓库,这些仓库的依赖包稳定且可正常访问。

步骤:编译安装 OpenSSH 9.9p2

  1. 解压源码包并进入目录
bash 复制代码
tar -zxf openssh-9.9p2.tar.gz
cd openssh-9.9p2
  1. 配置编译参数(核心:指定 OpenSSL 1.1.1k 的路径):
bash 复制代码
./configure \
  --prefix=/usr \                  # 安装到系统默认路径(覆盖旧版本)
  --sysconfdir=/etc/ssh \          # 配置文件存放路径
  --with-ssl-dir=/usr/local/openssl11 \  # 关联已安装的 OpenSSL 1.1.1k 库目录
  --with-includes=/usr/local/openssl11/include \  # 关联 OpenSSL 头文件目录
  --with-pam \                     # 启用 PAM 认证(用于系统用户登录)
  --with-zlib \                    # 启用 zlib 压缩
  --with-selinux \                 # 支持 SELinux(CentOS 系统建议开启)
  --without-hardening              # 避免编译时因系统环境导致的报错


# 无注释可执行版本
./configure \
 --prefix=/usr \
 --sysconfdir=/etc/ssh \
 --with-ssl-dir=/usr/local/openssl11 \
 --with-includes=/usr/local/openssl11/include \
 --with-pam \
 --with-zlib \
 --with-selinux \
 --without-hardening
  • 配置过程中若提示“configure: error: ...”,通常是依赖缺失,根据提示安装对应包(如 libselinux-devel 等)。
  1. 编译并安装
bash 复制代码
# 编译(-j 4 表示用 4 核加速,根据 CPU 核心数调整)
make -j 4

# 安装(会覆盖系统默认的 ssh、sshd 等文件)
make install

后续配置(确保服务正常启动)

  1. 恢复/调整配置文件
  • 若之前备份了 sshd_config,可对比新生成的 /etc/ssh/sshd_config,将自定义配置(如端口、登录权限等)迁移过去:
bash 复制代码
# 查看新配置文件,保留默认安全设置,添加旧配置中的自定义项
vim /etc/ssh/sshd_config
  • 关键配置建议(按需添加):
bash 复制代码
PermitRootLogin yes  # 允许 root 登录(根据安全需求调整)
PasswordAuthentication yes  # 允许密码登录(默认可能禁用)

配置完成,重启服务即可

bash 复制代码
sudo systemctl restart sshd
  1. 配置 systemd 服务(确保可通过 systemctl 管理)
    OpenSSH 源码编译默认不生成 systemd 服务文件,需手动复制:
bash 复制代码
# 复制服务文件到 systemd 目录
cp contrib/redhat/sshd.init /etc/init.d/sshd
cp contrib/redhat/sshd.service /usr/lib/systemd/system/

# 重新加载 systemd 配置
systemctl daemon-reload

验证升级结果

检查 OpenSSH 版本及关联的 OpenSSL 版本

bash 复制代码
ssh -V
# 预期输出:OpenSSH_9.9p2, OpenSSL 1.1.1k  FIPS 25 Mar 2021

阿里云 Alibaba Cloud Linux 3.2104 LTS 64位 升级 OpenSSH_9.9p2

因等保需要,监测到CVE-2023-38408、CVE-2020-15778、CVE-2023-51767等漏洞,因阿里云官方源最高只要OpenSSH_8.3。

需要手动编译升级 OpenSSH_9.9p2,记录一下

🔍 步骤 1:确认当前 OpenSSH 版本

bash 复制代码
ssh -V
# 示例输出:OpenSSH_9.3p2, OpenSSL 1.1.1f 31 Mar 2020

如果版本 ≤ 9.9p1,您需要升级。

🛠️ 步骤 2:安装依赖

bash 复制代码
# 安装编译工具和依赖
sudo yum install -y gcc make zlib-devel openssl-devel

🔗 步骤 3:下载并编译 OpenSSH 9.9p2

bash 复制代码
# 创建临时目录并下载源码
cd /usr/local/src
wget https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-9.9p2.tar.gz
tar -xzf openssh-9.9p2.tar.gz
cd openssh-9.9p2

# 配置编译选项
./configure --prefix=/usr --sysconfdir=/etc/ssh --with-ssl-dir=/usr/include/openssl

# 编译
make

# 备份当前 SSH 配置和二进制文件(重要!)
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
sudo cp /usr/sbin/sshd /usr/sbin/sshd.bak
sudo cp /usr/bin/ssh /usr/bin/ssh.bak

# 安装新版本
sudo make install

📌 步骤 4:配置和启动新版本

bash 复制代码
# 检查安装是否成功
ssh -V
# 应显示:OpenSSH_9.9p2

# 重启 SSH 服务
sudo systemctl restart sshd

✅ 步骤 5:验证升级

bash 复制代码
# 验证 OpenSSH 版本
ssh -V

# 验证 CVE-2023-51767 修复
ssh -V 2>&1 | grep -i "CVE-2023-51767"
# 如果显示修复信息,表示已修复

🛡️ 步骤 6:安全加固(可选但推荐)

bash 复制代码
# 限制 SSH 访问(仅允许特定 IP)
sudo sed -i 's/^#PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sudo sed -i 's/^#PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo sed -i 's/^#AllowUsers.*/AllowUsers your_username/' /etc/ssh/sshd_config

# 重启 SSH 服务
sudo systemctl restart sshd

⚠️ 重要注意事项

  1. 不要关闭当前 SSH 会话:在执行升级步骤时,保持至少一个 SSH 会话连接,以防升级失败导致无法连接。

  2. 备份至关重要:在升级前备份了当前配置和二进制文件,如果升级失败可以快速恢复。

  3. 阿里云特定注意事项

    • 阿里云 Alibaba Cloud Linux 3.2104 LTS 通常使用 Systemd 作为初始化系统
    • 确保使用 systemctl 管理 SSH 服务
  4. 如果升级失败:可以使用备份恢复:

    bash 复制代码
    sudo cp /etc/ssh/sshd_config.bak /etc/ssh/sshd_config
    sudo cp /usr/sbin/sshd.bak /usr/sbin/sshd
    sudo cp /usr/bin/ssh.bak /usr/bin/ssh
    sudo systemctl restart sshd

rust项目中的lib.rs 和 mod.rs 有什么作用和区别?

在 Rust 的 Cargo 项目中,lib.rsmod.rs(或 mod/ 目录结构)用于组织模块系统。

它们的作用和区别如下:

1. lib.rs 的作用

lib.rs 是 Rust 库项目的入口文件,用于定义库的公共 API。当你执行 cargo new --lib mylibrary 时,Cargo 会自动生成这个文件。

关键特点

  • 库的根模块:所有子模块都相对于 lib.rs 进行组织。
  • 公共 API 的定义:通过 pub 关键字暴露模块、函数或类型。
  • 无需显式声明:Cargo 会自动将 src/lib.rs 作为库的入口。

示例

假设项目结构如下:

复制代码
mylibrary/
├── Cargo.toml
└── src/
    └── lib.rs

lib.rs 内容:

rust 复制代码
// 定义公共模块
pub mod utils;  // 对应 src/utils.rs 或 src/utils/mod.rs

// 定义公共函数
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

// 私有模块(外部不可见)
mod internal {
    pub fn secret_function() -> &'static str {
        "This is internal!"
    }
}

2. mod.rs 的作用

mod.rs 用于定义子模块的内容。它可以是一个单独的文件(如 src/module_name/mod.rs),也可以简化为 src/module_name.rs

关键特点

  • 子模块的入口:当模块包含多个文件时,使用 mod.rs 组织它们。
  • 模块嵌套:可以通过 mod.rs 创建多级模块结构。
  • 现代替代方案:Rust 2018 之后,推荐使用 module_name.rs 替代 module_name/mod.rs(见下文示例)。

示例 1:传统 mod.rs 结构

复制代码
mylibrary/
├── Cargo.toml
└── src/
    ├── lib.rs
    └── utils/
        ├── mod.rs       # 子模块入口
        └── math.rs      # 子模块中的文件

src/utils/mod.rs 内容:

rust 复制代码
// 引入同级文件作为子模块
pub mod math;  // 对应 src/utils/math.rs

// 定义模块内的公共函数
pub fn say_hello() {
    println!("Hello from utils!");
}

src/utils/math.rs 内容:

rust 复制代码
// 子模块中的公共函数
pub fn multiply(a: i32, b: i32) -> i32 {
    a * b
}

示例 2:简化的模块结构(Rust 2018+)

Rust 2018 之后,可以直接用 module_name.rs 替代 module_name/mod.rs,减少嵌套层级。

复制代码
mylibrary/
├── Cargo.toml
└── src/
    ├── lib.rs
    ├── utils.rs       # 替代 utils/mod.rs
    └── utils/
        └── math.rs    # 子模块文件

src/utils.rs 内容:

rust 复制代码
// 引入子目录中的文件
pub mod math;  // 对应 src/utils/math.rs

pub fn say_hello() {
    println!("Hello from utils!");
}

3. 主要区别

对比项 lib.rs mod.rs / module.rs
角色 库的根入口 子模块的入口
必需性 库项目必须存在 非必需,按需创建
文件名 固定为 lib.rs 可以是 mod.rsmodule_name.rs
路径映射 对应 crate 根(如 mylibrary:: 对应模块路径(如 mylibrary::utils
使用场景 定义公共 API、引入子模块 组织子模块的内部结构

4. 最佳实践

  1. 小型模块:直接使用 module_name.rs(如 src/utils.rs)。
  2. 大型模块:使用 module_name/mod.rs + 子文件(如 src/utils/mod.rs)。
  3. 公共 API:在 lib.rs 中通过 pub use 重导出模块内容,简化外部调用。
rust 复制代码
// lib.rs 中重导出
pub use utils::math::multiply;  // 使 mylibrary::multiply 可用

总结

  • lib.rs:是库项目的核心入口,负责定义公共 API 和组织顶级模块。
  • mod.rs / module_name.rs:用于组织子模块的内部结构,避免单个文件过大。

合理使用这两个文件,可以让 Rust 项目的模块结构清晰且易于维护。

Rust 中的 #[inline] 属性是什么意思?

在 Rust 中,内联(Inline) 是一种编译器优化技术,核心思想是将函数或方法的代码直接嵌入到调用它的位置,从而避免传统函数调用的开销(如压栈、跳转、返回等)。下面通过具体例子和对比,帮助你直观理解这一概念:


1. 什么是“代码嵌入调用位置”?

假设我们有一个简单函数 add,用于计算两个数的和:

rust 复制代码
fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn main() {
    let result = add(1, 2);
}

未内联时的执行过程

  1. 调用 add(1, 2) 时,程序会跳转到 add 函数的地址。
  2. 将参数 12 压入栈中。
  3. 执行 a + b,得到结果 3
  4. 返回到 main 函数,将结果存入 result

内联后的执行过程(添加 #[inline] 属性):

rust 复制代码
#[inline]
fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn main() {
    // 内联后等效于直接写入 a + b
    let result = 1 + 2;  // 编译器将函数体直接替换到此处
}

此时,add 函数的代码 1 + 2 被直接嵌入到 main 函数的调用位置,省去了跳转和参数传递的步骤。


2. 为什么需要内联?
优化场景

  • 高频调用的小函数(如数学运算、Getter/Setter)。
  • 性能关键路径(如循环内的计算)。

示例对比

假设有一个循环调用 square 函数计算平方:

rust 复制代码
fn square(x: i32) -> i32 {
    x * x
}

fn main() {
    let mut sum = 0;
    for i in 1..=1000 {
        sum += square(i);  // 每次循环都需执行函数调用
    }
}

未内联时:循环中每次调用 square(i) 都会产生函数调用开销。
内联优化后(添加 #[inline]):

rust 复制代码
#[inline]
fn square(x: i32) -> i32 {
    x * x
}

fn main() {
    let mut sum = 0;
    for i in 1..=1000 {
        sum += i * i;  // 直接替换为乘法操作
    }
}

内联后,循环内直接执行 i * i,避免了 1000 次函数调用开销,性能显著提升。


3. 内联的代价与注意事项

优点

  • 减少函数调用开销,提升执行速度。
  • 可能触发更多编译器优化(如常量传播、死代码消除)。

缺点

  • 代码膨胀:多次内联会导致编译后的二进制文件变大(如大函数被内联多次)。
  • 调试困难:内联后的代码堆栈信息可能不直观。

适用场景建议

  • 小函数(如 addgetter)适合内联。
  • 大函数或低频调用函数(如复杂算法)应避免内联。

4. 内联属性的分类

Rust 提供不同内联属性,控制优化力度:

  1. #[inline]:建议编译器内联,但最终由编译器决定。

    rust 复制代码
    #[inline]
    fn calculate(a: i32) -> i32 { a * 2 }
  2. #[inline(always)]:强制内联(除非不可行)。

    rust 复制代码
    #[inline(always)]
    fn critical_code(x: f64) -> f64 { x.sqrt() }
  3. #[inline(never)]:禁止内联。

    rust 复制代码
    #[inline(never)]
    fn large_function(data: &[u8]) { /* 复杂处理 */ }

Vue3 中KeepAlive在嵌套路由中的使用及注意事项

<KeepAlive> 是一个内置组件,它的功能是在多个组件间动态切换时缓存被移除的组件实例。通过缓存不活动的组件实例,避免重复渲染。通常在动态组件或者路由组件中使用,可以保留组件状态或者提高性能。

假设router/index.js 如下:

javascript 复制代码
const routes = [
  {
    path: '/',
    component: () => import('../layouts/MainLayout.vue'),
    children: [
      {
        path: '/parent',
        component: () => import('../views/ParentView.vue'), // 父组件
        children: [
          {
            path: 'child1',
            component: () => import('../views/Child1.vue'),
            meta: { keepAlive: true } // 启用缓存
          },
          {
            path: 'child2',
            component: () => import('../views/Child2.vue')
          }
        ]
      }
    ]
  }
]

其中 App.vueMainLayout 中都有router-view 组件。该如何配置 keepalive呢?

代码如下:

App.vue 配置

html 复制代码
<!-- App.vue -->
<template>
  <!-- 注意在嵌套路由中使用 keep-alive -->
  <router-view  v-slot="{ Component }">
    <keep-alive :include="['MainLayout']">
      <component :is="Component" />
    </keep-alive>
  </router-view >
</template>

MainLayout.vue 配置

javascript 复制代码
<template>
<el-scrollbar class="flex-1 layout-main-content">
  <div class="flex-1 shadow bg-white p-3">
    <router-view v-slot="{ Component }" >
        <keep-alive :include="cachedRoutes">
          <component :is="Component" />
        </keep-alive>
      </router-view>
  </div>
</el-scrollbar>
</template>
<script lang="ts" setup>
const router = useRouter();
// 动态计算需要缓存的路由名称
const cachedRoutes = computed(() => {
  let arr =  router.getRoutes().filter((r) => r.meta?.keepAlive).map(r =>r.name);  
  // 过滤掉 undefined 值
  const validRoutes = arr.filter((name): name is string => name !== undefined);
  return validRoutes;
});
</script>

注意事项

component中了key的使用

如果component 中使用了key,可能会造成重复渲染,axios 多次请求。

javascript 复制代码
<component :is="Component" :key="$route.fullPath"/>

可以修改key的绑定或删除key,以生成稳定 Key。

javascript 复制代码
<!-- 修改后 -->
<component :is="Component" 
  :key="`${$route.name}-${JSON.stringify($route.params)}`"/>

生命周期

keepAlive组件特有的生命周期,可用于调试或进行智能缓存策略控制。

javascript 复制代码
onActivated(() => console.log('Activated'))
onDeactivated(() => console.log('Deactivated'))

如何灵活控制请求执行条件?

javascript 复制代码
<script setup>
import { ref, watch, onActivated } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()
const data = ref(null)
const hasLoaded = ref(false) // 请求状态标记

// 统一请求方法
const fetchData = async () => {
  if (hasLoaded.value) return
  try {
    const res = await axios.get(`/api/data/${route.params.id}`)
    data.value = res.data
    hasLoaded.value = true
  } catch (err) {
    console.error(err)
  }
}

// 常规加载
if (!hasLoaded.value) {
  fetchData()
}

// 监听路由参数变化
watch(() => route.params.id, (newVal, oldVal) => {
  if (newVal !== oldVal) {
    hasLoaded.value = false
    fetchData()
  }
})

// 缓存激活时刷新数据
onActivated(() => {
  if (Date.now() - lastLoadTime > 5 * 60 * 1000) { // 5分钟缓存
    hasLoaded.value = false
    fetchData()
  }
})
</script>