🎯 学习目标:学会安全加固:权限管理、敏感信息保护、审计日志
⏱️ 阅读时间:约 12 分钟
💡 前置要求:无
🔐 安全三要素
核心方向总览
| 权限管理 | ||
| 敏感信息保护 | ||
| 审计日志 |
👥 权限管理
基于角色的访问控制(RBAC)
const roles = {admin: {name: '管理员',permissions: ['*'], // 所有权限description: '系统管理员,拥有全部权限' },manager: {name: '经理',permissions: ['read:*','write:documents','write:tasks','execute:skills','manage:team' ],description: '团队经理,可以管理团队和查看文档' },developer: {name: '开发者',permissions: ['read:documents','write:code','execute:dev-skills','read:logs' ],description: '开发者,可以编写代码和执行开发相关技能' },viewer: {name: '观察者',permissions: ['read:public-documents' ],description: '只读用户,只能查看公开文档' }};classPermissionManager {asynccheckPermission(userId, requiredPermission) {const user = awaitgetUser(userId);const role = roles[user.role];// 管理员拥有所有权限if (role.permissions.includes('*')) {returntrue; }// 检查具体权限return role.permissions.some(permission => {// 通配符匹配if (permission.endsWith(':*')) {const prefix = permission.slice(0, -1);return requiredPermission.startsWith(prefix); }// 精确匹配return permission === requiredPermission; }); }asyncrequirePermission(userId, requiredPermission, action) {const hasPermission = awaitthis.checkPermission(userId, requiredPermission);if (!hasPermission) {// 记录未授权访问尝试awaitlogSecurityEvent({type: 'unauthorized_access', userId, requiredPermission, action,timestamp: newDate() });thrownewError(`权限不足:需要 ${requiredPermission}`); } }}技能权限控制
const skillPermissions = {// 高风险技能'exec': {requiredPermission: 'execute:shell',riskLevel: 'high',requireConfirmation: true },'file-write': {requiredPermission: 'write:files',riskLevel: 'medium',requireConfirmation: false },'file-delete': {requiredPermission: 'write:files',riskLevel: 'high',requireConfirmation: true },// 中等风险技能'browser': {requiredPermission: 'execute:browser',riskLevel: 'medium',requireConfirmation: false },'feishu-message': {requiredPermission: 'write:messages',riskLevel: 'medium',requireConfirmation: false },// 低风险技能'web-search': {requiredPermission: 'read:web',riskLevel: 'low',requireConfirmation: false },'read-file': {requiredPermission: 'read:files',riskLevel: 'low',requireConfirmation: false }};asyncfunctionexecuteSkill(userId, skillName, params) {const skillConfig = skillPermissions[skillName];if (!skillConfig) {thrownewError(`未知技能:${skillName}`); }// 检查权限await permissionManager.requirePermission( userId, skillConfig.requiredPermission,`execute_skill:${skillName}` );// 高风险操作需要确认if (skillConfig.requireConfirmation) {const confirmed = awaitconfirmAction(userId, {skill: skillName, params,riskLevel: skillConfig.riskLevel });if (!confirmed) {return'操作已取消'; } }// 执行技能returnawaitexecute(skillName, params);}文件访问控制
classFileAccessControl {constructor() {// 允许访问的目录this.allowedDirs = ['~/projects','~/documents','~/workspace' ];// 禁止访问的目录this.forbiddenDirs = ['~/.ssh','~/.aws','~/.config','/etc','/system','/private' ]; }asynccheckAccess(filePath, operation) {const resolvedPath = awaitresolvePath(filePath);// 检查是否在禁止目录for (const forbidden ofthis.forbiddenDirs) {if (resolvedPath.startsWith(awaitresolvePath(forbidden))) {awaitlogSecurityEvent({type: 'forbidden_access',filePath: resolvedPath, operation,timestamp: newDate() });thrownewError(`禁止访问该目录:${filePath}`); } }// 检查是否在允许目录let isAllowed = false;for (const allowed ofthis.allowedDirs) {if (resolvedPath.startsWith(awaitresolvePath(allowed))) { isAllowed = true;break; } }if (!isAllowed) {thrownewError(`只能访问指定目录:${this.allowedDirs.join(', ')}`); }returntrue; }}🔒 敏感信息保护
API Key 管理
classSecretManager {constructor() {// 使用环境变量或密钥管理服务this.storage = process.env.SECRET_STORAGE || 'env'; }asyncget(secretName) {if (this.storage === 'env') {return process.env[secretName]; }if (this.storage === 'aws-secrets') {returnawaitthis.getFromAWSSecrets(secretName); }if (this.storage === 'vault') {returnawaitthis.getFromVault(secretName); }thrownewError(`未知的密钥存储方式:${this.storage}`); }asyncgetFromAWSSecrets(secretName) {const client = newSecretsManagerClient();const response = await client.send(newGetSecretValueCommand({SecretId: secretName }));return response.SecretString; }asyncgetFromVault(secretName) {const client = newVaultClient();const secret = await client.read(`secret/${secretName}`);return secret.data.value; }}// 使用示例const secrets = newSecretManager();const apiKey = await secrets.get('OPENAI_API_KEY');飞书权限最佳实践 ⭐
## 飞书应用权限配置最佳实践### 1. 最小权限原则只申请必要的权限:**必需权限**:- 消息:发送和接收消息(im:message)- 通讯录:读取用户信息(contact:users)**可选权限**(按需申请):- 日历:读取和创建日程(calendar:events)- 任务:读取和创建任务(task:tasks)- 云文档:读写文档(drive:docs)### 2. 权限有效期- 临时权限:设置过期时间- 定期审查:每季度审查一次权限- 及时回收:员工离职时立即回收### 3. 权限隔离- 开发环境:使用测试权限- 生产环境:使用正式权限- 权限分离:不同服务使用不同权限密钥轮换 ⭐
classKeyRotator {constructor() {this.rotationInterval = 90 * 24 * 60 * 60 * 1000; // 90 天 }asyncrotateKey(service) {try {console.log(`开始轮换 ${service} 密钥...`);// 1. 生成新密钥const newKey = awaitthis.generateKey(service);// 2. 更新配置awaitthis.updateConfig(service, newKey);// 3. 验证新密钥const valid = awaitthis.validateKey(service, newKey);if (!valid) {thrownewError('新密钥验证失败'); }// 4. 记录轮换日志awaitthis.logRotation(service);console.log(`${service} 密钥轮换完成`);returntrue; } catch (error) {console.error(`${service} 密钥轮换失败:`, error);awaitnotifyAdmin(`${service} 密钥轮换失败`, error);returnfalse; } }asyncgenerateKey(service) {// 根据不同服务生成密钥switch (service) {case'feishu':returnawaitthis.rotateFeishuAppSecret();case'openai':returnawaitthis.rotateOpenAIKey();default:thrownewError(`不支持的服务:${service}`); } }}// 定期轮换任务asyncfunctionscheduleKeyRotation() {const rotator = newKeyRotator();// 每天检查是否需要轮换awaitcreateCronJob({cron: '0 2 * * *', // 每天凌晨 2 点task: async () => {const keys = awaitlistApiKeys();for (const key of keys) {if (awaitisExpiringSoon(key, 7)) { // 7 天内过期await rotator.rotateKey(key.service); } } } });}数据脱敏
classDataMasker {// 手机号脱敏maskPhone(phone) {return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'); }// 邮箱脱敏maskEmail(email) {const [username, domain] = email.split('@');const maskedUsername = username.slice(0, 2) + '***' + username.slice(-1);return`${maskedUsername}@${domain}`; }// 身份证脱敏maskIdCard(idCard) {return idCard.replace(/(\d{6})\d{8}(\d{4})/, '$1********$2'); }// API Key 脱敏maskApiKey(key) {return key.slice(0, 8) + '...' + key.slice(-4); }// 自动检测并脱敏maskSensitiveData(text) {// 手机号 text = text.replace(/1[3-9]\d{9}/g, m =>this.maskPhone(m));// 邮箱 text = text.replace(/[\w.-]+@[\w.-]+\.\w+/g, m =>this.maskEmail(m));// 身份证 text = text.replace(/\d{17}[\dXx]/g, m =>this.maskIdCard(m));// API Key(简单模式) text = text.replace(/[a-zA-Z0-9]{32,}/g, m =>this.maskApiKey(m));return text; }}// 使用示例const masker = newDataMasker();const safeLog = masker.maskSensitiveData(sensitiveLog);加密存储
const crypto = require('crypto');classEncryptor {constructor(key) {this.algorithm = 'aes-256-gcm';this.key = crypto.createHash('sha256').update(key).digest(); }encrypt(text) {const iv = crypto.randomBytes(16);const cipher = crypto.createCipheriv(this.algorithm, this.key, iv);let encrypted = cipher.update(text, 'utf8', 'hex'); encrypted += cipher.final('hex');const authTag = cipher.getAuthTag().toString('hex');return {iv: iv.toString('hex'),encryptedData: encrypted, authTag }; }decrypt(encrypted) {const decipher = crypto.createDecipheriv(this.algorithm,this.key,Buffer.from(encrypted.iv, 'hex') ); decipher.setAuthTag(Buffer.from(encrypted.authTag, 'hex'));let decrypted = decipher.update(encrypted.encryptedData, 'hex', 'utf8'); decrypted += decipher.final('utf8');return decrypted; }}// 使用示例const encryptor = newEncryptor(process.env.ENCRYPTION_KEY);const encrypted = encryptor.encrypt(sensitiveData);const decrypted = encryptor.decrypt(encrypted);📋 审计日志
日志记录
classAuditLogger {asynclog(event) {const logEntry = {timestamp: newDate().toISOString(),eventId: generateId(), ...event };// 写入日志文件awaitthis.writeToFile(logEntry);// 发送到日志服务awaitthis.sendToLogService(logEntry);// 检查是否需要告警awaitthis.checkAlert(logEntry); }asyncwriteToFile(logEntry) {const logFile = `/var/log/openclaw/audit-${newDate().toISOString().slice(0, 10)}.log`;awaitappendFile(logFile, JSON.stringify(logEntry) + '\n'); }asyncsendToLogService(logEntry) {// 发送到 ELK、Splunk 等日志服务awaitfetch(process.env.LOG_SERVICE_URL, {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify(logEntry) }); }asynccheckAlert(logEntry) {// 安全事件告警if (logEntry.type.startsWith('security.')) {awaitnotifySecurityTeam(logEntry); }// 高频操作告警if (awaitisHighFrequency(logEntry.userId, logEntry.type)) {awaitnotifyAdmin(`高频操作告警:${logEntry.userId}`); } }}// 使用示例const auditLogger = newAuditLogger();await auditLogger.log({type: 'file.read',userId: 'user123',filePath: '/projects/app/src/main.js',result: 'success'});await auditLogger.log({type: 'skill.execute',userId: 'user456',skillName: 'exec',params: { command: 'git status' },result: 'success'});await auditLogger.log({type: 'security.unauthorized_access',userId: 'user789',requiredPermission: 'write:files',action: 'delete_file',result: 'blocked'});日志查询
classAuditQuery {asyncsearch(filters) {const logs = awaitloadLogs(filters.dateRange);return logs.filter(log => {if (filters.userId && log.userId !== filters.userId) {returnfalse; }if (filters.type && !log.type.startsWith(filters.type)) {returnfalse; }if (filters.result && log.result !== filters.result) {returnfalse; }returntrue; }); }asyncgetUserActivity(userId, dateRange) {const logs = awaitthis.search({ userId, dateRange });// 统计const stats = {totalActions: logs.length,byType: {},byResult: { success: 0, failure: 0 } }; logs.forEach(log => { stats.byType[log.type] = (stats.byType[log.type] || 0) + 1; stats.byResult[log.result]++; });return { stats, logs }; }asyncgetSecurityReport(dateRange) {const securityLogs = awaitthis.search({type: 'security', dateRange });return {totalEvents: securityLogs.length,byType: groupByType(securityLogs),criticalEvents: securityLogs.filter(l => l.severity === 'critical') }; }}🛡️ 安全最佳实践
1. 最小权限原则
// ✅ 好的做法:只授予必要权限awaitgrantPermissions(userId, ['read:documents']);// ❌ 坏的做法:授予过多权限awaitgrantPermissions(userId, ['*']);2. 定期审计
// 每周审计权限awaitcreateCronJob({cron: '0 9 * * 1',task: async () => {const users = awaitlistAllUsers();for (const user of users) {const report = awaitgeneratePermissionReport(user.id);// 检查是否有过多权限if (report.hasExcessivePermissions) {awaitnotifyAdmin(`用户 ${user.name} 权限过多,请审查`); } } }});3. 密钥轮换
// 每 90 天轮换一次 API KeyawaitcreateCronJob({cron: '0 0 1 * *',task: async () => {const keys = awaitlistApiKeys();for (const key of keys) {if (awaitisExpiringSoon(key)) {const newKey = awaitrotateKey(key.service);awaitupdateKey(key.service, newKey);console.log(`已轮换 ${key.service} 密钥`); } } }});4. 异常检测
asyncfunctiondetectAnomalies() {const logs = awaitgetRecentLogs();// 检测异常登录const loginsByIp = groupBy(logs, l => l.ip);for (const [ip, loginLogs] ofObject.entries(loginsByIp)) {if (loginLogs.length > 10) {awaitalert(`异常登录:${ip} 在 1 小时内登录 ${loginLogs.length} 次`); } }// 检测异常文件访问const fileAccess = logs.filter(l => l.type.startsWith('file.'));const sensitiveAccess = fileAccess.filter(l => l.filePath.includes('sensitive') );if (sensitiveAccess.length > 5) {awaitalert(`异常文件访问:1 小时内访问敏感文件 ${sensitiveAccess.length} 次`); }}✅ 学完这篇你能做什么
学完 Day 23,你将能够:
✅ 配置基于角色的权限 ✅ 保护敏感信息 ✅ 配置审计日志 ✅ 实施密钥轮换 ✅ 检测异常行为
🔜 下篇预告
Day 24:性能调优:并发处理、缓存策略、资源优化
⚡ 并发处理 💾 缓存策略 📊 资源优化
💬 互动环节
你在安全方面遇到过什么问题?留言分享!
公众号:OpenClaw 研习社
系列:OpenClaw 30 天入门到精通
作者:OpenClaw 研习社
夜雨聆风