【机密计算-大厂有话说】AMD

基于 VirTEE/SEV 的 SEV-SNP 平台证明

刊号 58217,版本 v1.2,发布于 2023.7

1. 介绍

        VirTEE/sev 工具箱提供了一套基于 rust 语言的简单易用的 API 来访问 AMD EPYC 处理器内的安全处理器,这个库已经早已经支持传统的 SEV 固件,最近在 VirTEE 社区中又增加了新的 SEV-SNP 固件的支持,主要是第三代和新上市的 AMD EPYC 处理器。用户手册中包含了使用 AMD SEV-SNP 技术对可信执行环境 TEE 进行平台证明的一些潜在的解决方案,主要针对以下用户:

  • 平台所有者:管理用来部署虚拟机、容器系统软件,比如主机或者云服务商。这些系软件主要指的是运行机密虚拟机、机器容器的虚拟机监视器 hypervisor。
  • 来宾所有者:指的是部署工作负载者

        这篇用户指南探讨了 VirTEE/sev 工具箱中 SEV-SNP 技术更新带给平台所有者和来宾所有者带来的一些新的能力,同时提供了一些用例以及解释了一些用户关心的配置选项。

2. 平台所有者

        平台所有者是包括 hypervisor 在内的系统软件,用来部署机密虚拟机或者容器。

2.1 系统要求

  • 硬件:系统必须是第三代或者更新的 AMD EPYC 处理器
  • 固件:必须使用支持最新 SEV 固件的 BIOS 版本,保证具有对应的安全保护功能。VirTEE/sev SEV-SNP 特性在版本1.54.01 中兼容
  • 内核:SEV-SNP 正处在开发中,ADM 建议使用上游最新的内核补丁
  • 软件:通常来讲 AMD 建议使用上游的 OVMF/EDK II 和 QEMU 7.1,不过对于平台所有者或者来宾所有者有以下需求的,建议使用 OVMF/EDK II 最新和 QEMU 最新
    •  证明报告中需要包含验证来宾身份        
    • OVMF/EDK II 需要验证内核、initrd、cmdline 等参数

2.2 API 能力

        平台所有者应该:

  • 请求 AMD 安全处理器状态
  • 加载新扩展配置
  • 请求既存的扩展配置

2.2.1 请求 AMD 安全处理器状态

        在配置主机平台来缓存证书前,AMD 建议检查 AMD 安全处理器的状态,包括以下步骤:

1.包含 VirTEE/sev 到用户 rust 工程里

// Import library
use sev::firmware::host::*;

2.连接到固件并请求 AMD 安全处理 snp 的状态

// Open a connection to the firmware.
let mut firmware: Firmware = Firmware::open().unwrap();
// Request the current snp status of the AMD Secure Processor.
let snp_status: SnpPlatformStatus = firmware.snp_platform_status().unwrap();

2.2.2 加载新的扩展配置

        hypervisor 内存里的存储策略和证书链使得平台所有者提升了来宾所有者的使用体验,避免了向 AMD 密钥发布服务器 KDS 请求证书的麻烦,同时也减少了 CSP 对于 AMD KDS 的依赖,更重要的,这避免了在大规模部署虚拟机/容器时手动请求证书链的麻烦。

 1.包含 VirTEE/sev 到 Rust 工程中

// Import library
use sev::firmware::host::*;

2.读取证书

// Read certificate bytes.
pub const ARK: &[u8] = include_bytes!("ark.pem");
pub const ASK: &[u8] = include_bytes!("ask.pem");
pub const VCEK: &[u8] = include_bytes!("vcek.pem");

3.创建来扩展报告的配置

  • 仅证书:允许 hypervisor 在不修改报告的 TCB 的前提下存储证书链
// Generate a vector of certificates to store in hypervisor memory.
let certificates: Vec<CertTableEntry> = vec![CertTableEntry::new(CertType::ARK, ARK.to_vec()),CertTableEntry::new(CertType::ASK, ASK.to_vec()),CertTableEntry::new(CertType::VCEK, VCEK.to_vec()),
];
// Call the `new_certs_only` constructor to generate the extended configuration.
let ext_config: ExtConfig = ExtConfig::new_certs_only(certificates
);
  • 仅配置:不存储证书链的前提下配置和更新报告的 TCB 内容
// Specify the desired configuration
let configuration: Config = Config::new(TcbVersion::new(3, 0, 10, 169),0,
);
// Call the `new_config_only` constructor to generate the extended configuration.
let ext_config: ExtConfig = ExtConfig::new_config_only(configuration
);
  • 配置和证书:更改 TCB 报告并存储证书链
// Specify the desired configuration
let configuration: Config = Config::new(TcbVersion::new(3, 0, 10, 169),0,
);
// Generate a vector of certificates to store in hypervisor memory.
let certificates: Vec<CertTableEntry> = vec![CertTableEntry::new(CertType::ARK, ARK.to_vec()),CertTableEntry::new(CertType::ASK, ASK.to_vec()),CertTableEntry::new(CertType::VCEK, VCEK.to_vec()),
];
// Call the `new` constructor to generate the extended configuration.
let ext_config: ExtConfig = ExtConfig::new(configuration,certificates
);

4.连接固件并将扩展请求转发给 AMD 安全处理器

// Open a connection to the firmware.
let mut fw: Firmware = Firmware::open().unwrap();
// Forward the certificates to the AMD Secure Processor to be loaded.
if let Err(error) = fw.snp_reset_config(&ext_config) {// Handle an error if one is encountered....
}

2.2.3 请求既存扩展配置

请求既存扩展配置包含以下步骤:

  1.包含 VirTEE/sev 到 Rust 工程中

// Import library
use sev::firmware::host::*;

2.连接到固件并请求当前配置

// Open a connection to the firmware.
let mut fw: Firmware = Firmware::open().unwrap();
// Request the current configuration.
let current_configuration: ExtConfig = fw.snp_get_ext_config().unwrap();

3.来宾所有者

        来宾所有者是虚拟化提供商的租户,它可能拥有一个或者多个机密虚拟机或者容器不属于平台所有者的环境中。

3.1 系统要求

来宾级的支持已经全部完成,使用 Linux Kernel 5.19 或者更新的内核来访问来宾驱动。

3.2 API 能力

来宾所有者可以:

  • 请求标准的证明报告
  • 请求扩展证明报告
  • 请求通过硬件密钥派生的唯一密码学密钥

3.2.1 标准证明保证请求和证明

1.从工具箱导入需要的代码模块

// Import the modules
use sev::firmware::guest::* ;

2.创建用于证明报告的 64 字节唯一数据

// This could be a unique message, a public key, etc.
let unique_data: [u8; 64] = [65, 77, 68, 32, 105, 115, 32, 101, 120, 116, 114, 101, 109, 101, 108, 121, 32, 97, 
119,101, 115, 111, 109, 101, 33, 32, 87, 101, 32, 109, 97, 107, 101, 32, 116, 104, 101, 
32,98, 101, 115, 116, 32, 67, 80, 85, 115, 33, 32, 65, 77, 68, 32, 82, 111, 99, 107, 
115,33, 33, 33, 33, 33, 33,
];

3.连接到固件并请求 SEV-SNP 证明报告

// Open a connection to the firmware.
let mut fw: Firmware = Firmware::open()?;
// Request a standard attestation report.
let attestation_report: AttestationReport = fw.get_report(None, Some(unique_data), 
None);

4.验证信任根。验证信任根是整个证明过程中最重要的一步,opehttps://crates.io/crates/opensslnssl 工具箱提供了所有用来验证证书链签名的工具,VirTEE/sev 库也包含了一些简化证书处理的结构和函数。当前 AMD 信任根是:

  • AMD 根密钥 ARK 是自签名的
  • ARK 给 ADM 签名密钥 ASK 签名
  • ASK 给芯片版本背书密钥 VCEK 签名

比如:

  • 导入必要的证书处理的逻辑
use sev::{certs::snp::{ca, Certificate, Chain},firmware::host::CertType,
};
  • 导入相关 openssl 库
 ecdsa::EcdsaSig,pkey::{PKey, Public},sha::Sha384,x509::X509,
  • 从 AMD 密钥分发系统 AKS 拉取证书链,参考 VCEK 规范。所有域都至少是 2 字节长度,并且用 0 补全。证明报告中 hwid 和 chip_id 是匹配的
const KDS_CERT_SITE: &str = "https://kdsintf.amd.com";
const KDS_VCEK: &str = "/vcek/v1";
const KDS_CERT_CHAIN: &str = "cert_chain";
/// Requests the certificate-chain (AMD ASK + AMD ARK)
/// These may be used to verify the downloaded VCEK is authentic.
pub fn request_cert_chain (sev_prod_name: &str) -> (ask, ark) {// Should make -> https://kdsintf.amd.com/vcek/v1/{SEV_PROD_NAME}/cert_chainlet url: String = format!("{KDS_CERT_SITE}{KDS_VCEK}/{sev_prod_name}/
{KDS_CERT_CHAIN}");println!("Requesting AMD certificate-chain from: {url}");let rsp: Response = get(&url).unwrap();
let body: Vec<u8> = rsp.bytes().unwrap().to_vec(); 
let chain: Vec<x509> = X509::stack_from_pem(&body).unwrap();
// Create a ca chain with ark and ask
let ca_chain: ca::Chain = ca::Chain::from_pem(&chain[1].to_pem, &chain[0].to_pem);
ca_chain
};
/// Requests the VCEK for the specified chip and TCP
pub fn request_vcek(chip_id: [u8; 64], reported_tcb: TcbVersion) -> X509 {let hw_id: String = hexify(&chip_id);let url: String = format!("{KDS_CERT_SITE}{KDS_VCEK}/{SEV_PROD_NAME}/\{hw_id}?blSPL={:02}&teeSPL={:02}&snpSPL={:02}&ucodeSPL={:02}",reported_tcb.boot_loader,reported_tcb.microcode); println!("Requesting VCEK from: {url}\n");let rsp_bytes = get(&url).unwrap().bytes().unwrap().to_vec();Certificate::from_der(&rsp_bytes)
};
  • 验证信任根
let ca_chain: ca::Chain = request_cert_chain("milan");
// chip_id and reported_tcb should be pulled from the host machine,
// or an attestation report. let vcek: Certificate = request_vcek(
chip_id,
reported_tcb
);
// Create a full-chain with the certificates:
Let cert_chain = Chain{ca: ca_chain, vcek: vcek};
//Now you can simply verify the whole chain in one command.
cert_chain.verify().unwrap();
//Or you can verify each certificate individually
let ark = cert_chain.ca.ark;
let ask = cert_chain.ca.ask;
if (&ark,&ark).verify().unwrap() {println!("The AMD ARK was self-signed...");if (&ark,&ask).verify().unwrap() {iprintln!("The AMD ASK was signed by the AMD ARK...");f (&ask,&vcek).verify().unwrap() {println!("The VCEK was signed by the AMD ASK...");
} else {eprintln!("The VCEK was not signed by the AMD ASK!");}
} else {eprintln!("The AMD ASK was not signed by the AMD ARK!");}
} else {eprintln!("The AMD ARK is not self-signed!");
}

5.通过验证证明报告中以下域和 VCEK 来验证来宾可信计算机 TCB

  • Bootloader
  • TEE
  • SNP
  • Microcode
  • Chip ID

openssl 和 VirTEE/sev 工具箱都不支持 x509v3 扩展验证,一个可信的解决方案是使用 x509解析器工具箱的 asn1 rs。下面示例就是基于这些定义构建起来的:

/
******************************************************************************************* RELEVANT X509v3 EXTENSION OIDS
******************************************************************************************
/
use asn1_rs::{oid, Oid};
use x509_parser::{self,certificate::X509Certificate,pem::{parse_x509_pem, Pem},prelude::X509Extension,
};
enum SnpOid {BootLoader,Tee,Snp,Ucode,HwId,
}
impl SnpOid {fn oid(&self) -> Oid {match self {SnpOid::BootLoader => oid!(1.3.6.1.4.1.3704.1.3.1),SnpOid::Tee => oid!(1.3.6.1.4.1.3704.1.3.2),SnpOid::Snp => oid!(1.3.6.1.4.1.3704.1.3.3),SnpOid::Ucode => oid!(1.3.6.1.4.1.3704.1.3.8),SnpOid::HwId => oid!(1.3.6.1.4.1.3704.1.4),}}
}
impl std::fmt::Display for SnpOid {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {write!(f, "{}", self.oid().to_id_string())}
}/
******************************************************************************************* HELPER FUNCTIONS
******************************************************************************************
/
fn check_cert_ext_byte(ext: &X509Extension, val: u8) -> bool {if ext.value[0] != 0x2 {panic!("Invalid type encountered!");}if ext.value[1] != 0x1 && ext.value[1] != 0x2 {panic!("Invalid octet length encountered");}if let Some(byte_value) = ext.value.last() {*byte_value == val} else {false}
}
fn check_cert_ext_bytes(ext: &X509Extension, val: &[u8]) -> bool {ext.value == val
}
/
******************************************************************************************* EXAMPLE ATTESTATION FUNCTION:
******************************************************************************************
/
fn validate_cert_metadata(cert: &X509Certificate,report: &AttestationReport,
) -> bool {let extensions: HashMap<Oid, &X509Extension> = cert.extensions_map().unwrap();if let Some(cert_bl) = extensions.get(&SnpOid::BootLoader.oid()) {if !check_cert_ext_byte(cert_bl, report.reported_tcb.boot_loader) {eprintln!("Report TCB Boot Loader and Certificate Boot Loader mismatch 
encountered.");return false;}println!("Reported TCB Boot Loader from certificate matches the attestation 
report.");}if let Some(cert_tee) = extensions.get(&SnpOid::Tee.oid()) {if !check_cert_ext_byte(cert_tee, report.reported_tcb.tee) {eprintln!("Report TCB TEE and Certificate TEE mismatch encountered.");return false;}println!("Reported TCB TEE from certificate matches the attestation report.");}if let Some(cert_snp) = extensions.get(&SnpOid::Snp.oid()) {if !check_cert_ext_byte(cert_snp, report.reported_tcb.snp) {eprintln!("Report TCB SNP and Certificate SNP mismatch encountered.");return false;}println!("Reported TCB SNP from certificate matches the attestation report.");}if let Some(cert_ucode) = extensions.get(&SnpOid::Ucode.oid()) {if !check_cert_ext_byte(cert_ucode, report.reported_tcb.microcode) {eprintln!("Report TCB Microcode and Certificate Microcode mismatch 
encountered.");return false;}println!("Reported TCB Microcode from certificate matches the attestation 
report.");}if let Some(cert_hwid) = extensions.get(&SnpOid::HwId.oid()) {if !check_cert_ext_bytes(cert_hwid, &report.chip_id) {eprintln!("Report TCB Microcode and Certificate Microcode mismatch 
encountered.");return false;}println!("Chip ID from certificate matches the attestation report.");}true
}

6.验证报告中的签名确实是由 VCEK 签发的

let ar_signature: EcdsaSig = EcdsaSig::try_from(&report.signature).unwrap();
let signed_bytes: &[u8] = &bincode::serialize(&report).unwrap()[0x0..0x2A0];
let amd_vcek_pubkey: EcKey<Public> = vcek.public_key().unwrap().ec_key().unwrap();
let mut hasher: Sha384 = Sha384::new()
hasher.update(signed_bytes);
let base_message_digest: [u8; 48] = hasher.finish();
if ar_signature.verify(base_message_digest.as_ref(), vcek_pubkey.as_ref()).unwrap() {println!("VCEK signed the Attestation Report!");
} else {eprintln!("VCEK did NOT sign the Attestation Report!");
}
// Or you can use a complete certificate chain to verify the attestation report
(&report, &certificate_chain).verify().unwrap()

3.2.2 扩展证明报告的请求和证明

1.创建 64 字节唯一数据用来证明报告中

// This could be a unique message, a public key, etc.
let unique_data: [u8; 64] = [65, 77, 68, 32, 105, 115, 32, 101, 120, 116, 114, 101, 109, 101, 108, 121, 32, 97, 
119,101, 115, 111, 109, 101, 33, 32, 87, 101, 32, 109, 97, 107, 101, 32, 116, 104, 101, 
32,98, 101, 115, 116, 32, 67, 80, 85, 115, 33, 32, 65, 77, 68, 32, 82, 111, 99, 107, 
115,33, 33, 33, 33, 33, 33,
];

2. 连接到固件并请求扩展报告

let mut fw: Firmware = Firmware::open().unwrap();
let (extended_report, certificates): (AttestationReport, Vec<CertTableEntry>) = 
fw.get_ext_report(None, Some(unique_data), 0)

3.使用 VirTEE/SEV 库从 AMD 安全处理器中解析 ARK、ASK、VCEK

// Assumes all certificates are in PEM format (for simplicity).
let certs: Chain = Chain::from_cert_table_pem(certificates).unwrap();

4.使用标准证明报告信任根进行验证,跳过连接到 AMD 密钥发布服务器。

3.2.3 请求一把派生密钥

        来宾所有者根据硬件信任根衍生唯一的加密密钥有很多使用场景。这个派生的密钥可能依赖多个 TCB 部件参数,只有相同的参数才能派生出相同的密钥。

1.根据规范勾走一个 DerivedKey

let request: DerivedKey = DerivedKey::new(false, GuestFieldSelect(1), 0, 0, 0);

2.连接到固件并请求一把派生密钥

let mut fw: Firmware = Firmware::open().unwrap();
let derived_key: [u8; 32]= fw.get_derived_key(None, request).unwrap();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/26491.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

荐读 | 《揭秘云计算与大数据》

当我们回顾过去几十年的科技进步时&#xff0c;云计算和大数据在现代科技发展史上无疑具有里程碑式的意义&#xff0c;它们不仅改变了我们的生活方式&#xff0c;而且对各行各业产生了深远的影响。 在这个数字化时代&#xff0c;云计算和大数据技术已经成为推动全球发展的关键…

线程池优雅关闭

背景 线程池是日常我们写代码时经常打交道的知识点了&#xff0c;围绕线程池除了core核心线程数和最大max线程数的知识点外&#xff0c;我们一般会忽略然而却绕不开的问题时如何关闭线程池 如何关闭线程池 首先从优雅关闭线程池代码说起&#xff1a; public boolean graful…

当编程遇上AI,纵享丝滑

目录 前言 一、提出需求 二、检查代码 三、进一步提出需求 总结 前言 自从CHATGPT火了以后&#xff0c;我发现我身边的人再也不怕写报告了&#xff0c;什么个人总结&#xff0c;汇报材料&#xff0c;年度总结&#xff0c;伸手就来&#xff08;反正哪些报告也没人看&#x…

等保基本要求

技术要求&#xff1a; 1、安全物理环境&#xff1a;&#xff08;物理位置选择、物理访问控制、防盗窃和放破坏、防雷击、防火、防水和防潮、防静电、温湿度控制、电力供应、电磁防护&#xff09; 2、安全通信网络&#xff1a;&#xff08;网络架构、通信传输、可信验证&#…

flink如何监听kafka主题配置变更

背景&#xff1a; 从前一篇文章我们知道flink消费kafka主题时是采用的手动assign指定分区的方式&#xff0c;这种消费方式是不处理主题的rebalance操作的&#xff0c;也就是消费者组中即使有消费者退出或者进入也是不会触发消费者所消费的分区的&#xff0c;那么疑问就来了&am…

ctfshow web93-98

web93 打开环境是一个代码审计题目 简单分析就是输入一个变量num&#xff0c;其值不能等于4476与包含字母&#xff0c;但是他的值需要为4476 函数intval作用为获取变量的整数值&#xff0c;第二个参数的意思是进制&#xff0c;默认为10进制。题目参数为0&#xff0c;就根据变…

EC200 CAT1 拨号PPP

**硬件支持型号 点击 查看 硬件支持 详情** DTU701 产品详情 DTU702 产品详情 DTU801 产品详情 DTU802 产品详情 DTU902 产品详情 G5501 产品详情 目前 DTU系列 产品&#xff0c;WIFI4G拨号 &#xff0c;默认开机自启动拨号。 WIFI 只需要 根据现场 修改SSID热点和密码…

MyBatis-Plus是什么以及特性[MyBatis-Plus系列] - 第481篇

​ 悟纤&#xff1a;师傅&#xff0c;宝宝不开心呢。 师傅&#xff1a;怎么不开心&#xff1f; 悟纤&#xff1a;感觉好多重复的代码来着。 师傅&#xff1a;是哪个部分重复的代码来着&#xff1f; 悟纤&#xff1a;就是对于一个model的增删改查部分。 师傅&#xff1a;那这…

1 swagger简单案例

1.1 加入依赖 <!--swagger图形化接口--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version> </dependency><dependency><groupId>io.spri…

dotConnect for DB2 Crack

dotConnect for DB2 Crack dotConnect for DB2是一个增强的DB2 ORM数据提供程序&#xff0c;它构建在ADO.NET技术和IBM DB2.NET数据提供程序之上&#xff0c;为开发基于DB2的数据库应用程序提供了一个完整的解决方案。它允许您轻松地将DB2数据与广泛使用的面向数据的技术集成&a…

SpringBoot统一功能处理

我们要实现以下3个目标&#xff1a; 统一用户登录权限统一数据格式返回统一异常处理 1.用户的登录权限校验 1.1Spring AOP用户统一登录验证问题 Aspect Component public class UserAspect {// 定义切点controller包下、子孙包下所有类的所有方法Pointcut("execution(…

机器学习中的工作流机制

机器学习中的工作流机制 在项目开发的时候&#xff0c;经常需要我们选择使用哪一种模型。同样的数据&#xff0c;可能决策树效果不错&#xff0c;朴素贝叶斯也不错&#xff0c;SVM也挺好。有没有一种方法能够让我们用一份数据&#xff0c;同时训练多个模型&#xff0c;并用某种…

【笔记】移动光猫改桥接

1. 登录后台 移动光猫的超管和密码&#xff08;百度的&#xff09; 账号&#xff1a;CMCCAdmin 密码&#xff1a;aDm8H%MdA 浏览器访问 192.168.1.1 并登录 2. 选择连接 点击“网络”&#xff0c;在“连接名称”下拉框选择 INTENET_R_VID 字样的连接&#xff0c;并截图备…

【GPT-3 】创建能写博客的AI工具

一、说明 如何使用OpenAI API&#xff0c;GPT-3和Python创建AI博客写作工具。 在本教程中&#xff0c;我们将从 OpenAI API 中断的地方继续&#xff0c;并创建我们自己的 AI 版权工具&#xff0c;我们可以使用它使用 GPT-3 人工智能 &#xff08;AI&#xff09; API 创建独特的…

js修改img的src属性显示变换图片到前端页面,img的src属性显示java后台读取返回的本地图片

文章目录 前言一、HTML 图像- 图像标签&#xff08; <img>&#xff09;1.1图像标签的源属性&#xff08;Src&#xff09;1.2图像标签源属性&#xff08;Src&#xff09;显示项目中图片1.3图像标签源属性&#xff08;Src&#xff09;显示网络图片 二、图像标签&#xff08…

了解Swarm 集群管理

Swarm 集群管理 简介 Docker Swarm 是 Docker 的集群管理工具。它将 Docker 主机池转变为单个虚拟 Docker 主机。 Docker Swarm 提供了标准的 Docker API&#xff0c;所有任何已经与 Docker 守护程序通信的工具都可以使用 Swarm 轻松地扩展到多个主机。 支持的工具包括但不限…

前端安全XSS和CSRF讲解

文章目录 XSSXSS攻击原理常见的攻击方式预防措施 CSRFCSRF攻击原理常见攻击情景预防措施&#xff1a; CSRF和XSS的区别 XSS 全称Cross Site Scripting&#xff0c;名为跨站脚本攻击。为啥不是单词第一个字母组合CSS&#xff0c;大概率与样式名称css进行区分。 XSS攻击原理 不…

框框大学之——教育技术学

清一色劝退的教育技术学。。。。。。 https://www.kkdaxue.com/?current1&major%E6%95%99%E8%82%B2%E6%8A%80%E6%9C%AF%E5%AD%A6&pageSize10&sortFieldcreateTime&sortOrderdescend 总结&#xff1a; 1 杂而不经 2 摆烂劝退居多 3 适合躺平 4 考公不行 5 要多…

python 将excel 多行进行分组合并

def exc():"""# 需要用到分组的概念:将角色和业务单据的进行分组,结果合并为一行"""df pd.read_excel(test33.xlsx)# 设置需要分组的字段cols [姓名, 科目]#agg() 其中的参数字段为之后输出的表格中的列字段df df.groupby(cols).agg({姓名: f…

Wordpress升级版本后插件和主题常见出错及处理方法整理【持续更新】

Wordpress报错怎么解决&#xff1f; 一般常用的排查方法&#xff1a; 暂时禁用所有插件&#xff1b;将主题更改为默认主题&#xff1b; 修改wp-config.php文件&#xff1b;更新固定链接设置&#xff0c;确保设置正确&#xff1b;检查.htaccess文件是否存在且是否可写&#xf…