乐于分享
好东西不私藏

OpenClaw 30 天系列 - Day 23:安全加固:权限管理、敏感信息保护、审计日志

OpenClaw 30 天系列 - Day 23:安全加固:权限管理、敏感信息保护、审计日志

🎯 学习目标:学会安全加固:权限管理、敏感信息保护、审计日志

⏱️ 阅读时间:约 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,timestampnewDate()      });thrownewError(`权限不足:需要 ${requiredPermission}`);    }  }}

技能权限控制

const skillPermissions = {// 高风险技能'exec': {requiredPermission'execute:shell',riskLevel'high',requireConfirmationtrue  },'file-write': {requiredPermission'write:files',riskLevel'medium',requireConfirmationfalse  },'file-delete': {requiredPermission'write:files',riskLevel'high',requireConfirmationtrue  },// 中等风险技能'browser': {requiredPermission'execute:browser',riskLevel'medium',requireConfirmationfalse  },'feishu-message': {requiredPermission'write:messages',riskLevel'medium',requireConfirmationfalse  },// 低风险技能'web-search': {requiredPermission'read:web',riskLevel'low',requireConfirmationfalse  },'read-file': {requiredPermission'read:files',riskLevel'low',requireConfirmationfalse  }};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,timestampnewDate()        });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 点taskasync () => {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(02) + '***' + 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(08) + '...' + key.slice(-4);  }// 自动检测并脱敏maskSensitiveData(text) {// 手机号    text = text.replace(/1[3-9]\d{9}/gm =>this.maskPhone(m));// 邮箱    text = text.replace(/[\w.-]+@[\w.-]+\.\w+/gm =>this.maskEmail(m));// 身份证    text = text.replace(/\d{17}[\dXx]/gm =>this.maskIdCard(m));// API Key(简单模式)    text = text.replace(/[a-zA-Z0-9]{32,}/gm =>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.algorithmthis.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 = {timestampnewDate().toISOString(),eventIdgenerateId(),      ...event    };// 写入日志文件awaitthis.writeToFile(logEntry);// 发送到日志服务awaitthis.sendToLogService(logEntry);// 检查是否需要告警awaitthis.checkAlert(logEntry);  }asyncwriteToFile(logEntry) {const logFile = `/var/log/openclaw/audit-${newDate().toISOString().slice(010)}.log`;awaitappendFile(logFile, JSON.stringify(logEntry) + '\n');  }asyncsendToLogService(logEntry) {// 发送到 ELK、Splunk 等日志服务awaitfetch(process.env.LOG_SERVICE_URL, {method'POST',headers: { 'Content-Type''application/json' },bodyJSON.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: { success0failure0 }    };    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,byTypegroupByType(securityLogs),criticalEvents: securityLogs.filter(l => l.severity === 'critical')    };  }}

🛡️ 安全最佳实践

1. 最小权限原则

// ✅ 好的做法:只授予必要权限awaitgrantPermissions(userId, ['read:documents']);// ❌ 坏的做法:授予过多权限awaitgrantPermissions(userId, ['*']);

2. 定期审计

// 每周审计权限awaitcreateCronJob({cron'0 9 * * 1',taskasync () => {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 * *',taskasync () => {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 研习社