阅心笔记
type: channel
link: https://opswill.com/
description: 积一时之跬步，臻千里之遥程
items: 用 powershell 实现 DDNS 功能
items: 我的 Typecho 插件 CommentToTelegram
items: 微型家用网络折腾记
items: 探索 Harbor API 的一些玩法
items: Amazon EKS集群升级流程
items: 一个简单的文件服务器
items: MongoDB分片集群配置的流水账
items: 通过Boto3 API从WAF中获取攻击IP
items: 借助 CloudWatch 和 WAF 缓解 DDOS 攻击
items: 借助 Cloudflare Workers 实现查询 ip 信息的API

用 powershell 实现 DDNS 功能
type: item
date: 2024-12-13T06:42:00+00:00
description: 最近想实现在外面访问家里的迷你电脑，因为电脑的 IPv6 地址是通过 SLACC 分配的，不能在路由器上获得迷你电脑的 IPv6 地址，所以需要在电脑上实现 DDNS 功能。就写了一个脚本，实现了已下功能：从本地网卡获取 IPv6 的第一个首选地址，通过 cloudfalre api 获取 dns 记录的 ipv6 地址对比获取的 IPv6 地址与 cloudflare 中的 dns 记录是否一致如果比对失败则通过 cloudflare api 更新 dns 解析发生错误则发送 telegram 通知下面是脚本内容：## ------------------------------------------------------------------------------ # 脚本名称: UpdateDNS.ps1 # 描述: 遍历所有网卡（排除WiFi），获取本机 IPv6 公网地址，通过 Cloudflare API 更新 DNS 记录，并发送 Telegram 通知 # 作者: [will https://opswill.com] # 日期: [2024/12/31] # 版本: 1.3 # ------------------------------------------------------------------------------ # 注意: 请确保已正确配置 API Token 和其他变量 # ------------------------------------------------------------------------------ # 设置 PowerShell 执行策略（如果需要） # Set-ExecutionPolicy Bypass -Scope Process -Force # 建议仅在测试时使用，生产环境应避免使用 Bypass # 配置 Cloudflare API 和 Telegram Bot 信息 $apiToken = "" $zoneId = "" $recordId = "" $domain = "" $botToken = "" $chatId = "" # 日志文件路径 $logFilePath = "C:\data\DDNS.log" # 排除的网卡接口名称（正则表达式） $excludedInterfaces = "Wi-Fi|Wireless|WLAN|Loopback|Bluetooth" # 函数：记录日志 function Log-Message { param( [string]$Message ) $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $logEntry = "$timestamp - $Message" Add-Content -Path $logFilePath -Value $logEntry } # 函数：发送 Telegram 消息 function Send-TelegramMessage { param( [string]$Message ) $telegramApiUrl = "https://api.telegram.org/bot$botToken/sendMessage" $params = @{ chat_id = $chatId text = $Message } try { Invoke-RestMethod -Uri $telegramApiUrl -Method Post -Body $params } catch { Log-Message "发送 Telegram 通知失败: $_" } } # 函数：获取公网 IPv6 地址 function Get-PublicIPv6Address { $ipv6AddressesWithInterface = Get-NetIPAddress -AddressFamily IPv6 | Where-Object { $_.InterfaceAlias -notmatch $excludedInterfaces -and $_.PrefixOrigin -in "RouterAdvertisement", "DHCPv6" -and $_.SuffixOrigin -ne "Random" -and $_.AddressState -eq "Preferred" # 确保地址是首选状态 } if (-not $ipv6AddressesWithInterface) { throw "未找到有效的公网 IPv6 地址。" } foreach ($ipv6AddressInfo in $ipv6Addr…

我的 Typecho 插件 CommentToTelegram
type: item
date: 2024-04-29T08:56:00+00:00
description: 不是程序员，作为运维平时写的比较多的是 Bash 和 Python 脚本。工作上运维过挺多的 PHP 站点，被动看过一些 PHP 代码，但从来没写过。最近有点时间，参考了 joyqi 的 Sitemap 插件，试着写了这个 CommentToTelegram 插件，竟然没有太大的阻碍。遇到最大的困难是对 Typecho 接口的理解。 我的需求就是通过 telegram 管理评论，现有的 tyepcho 插件推送到 telegram 都是通过第三方的服务，只能收到通知。唯一一个可以管理的插件已经不能用了，所以自己调用 telegram bot api 写了一个。下面是插件的介绍：介绍CommentToTelegram 是一个全新的 typecho 插件，使用 Telegram API 将评论推送到 Telegram Bot 通知并管理 github 地址 ： CommentToTelegram功能及特性支持 Telegram Inline Button 管理评论状态，可以通过 Telegram 将评论: 批准/删除/标记垃圾支持在Telegram通知上回复评论，在评论通知上点击回复，回复的内容会同步到博客，无需登录博客支持配置代理，支持使用 socks5、https、http 代理访问 Telegram支持异步回调，不会阻塞评论插件版本要求建议 Typecho 版本大于 1.2.1，其他版本未进行测试php: >=8.1.0, 本插件依赖 php-curl 和 php-json如果使用代理功能，建议使用新版本的 php-curl 扩展安装教程下载后将压缩包解压到 /usr/plugins 目录文件夹名改为 CommentToTelegram登录管理后台，激活插件配置插件 填写 Telegram Bot Token 及 Telegram Chat ID，并设置其他参数保存配置，并注意是否有错误提示信息插件升级禁用旧版本插件删除旧版本的文件，并上传然后传新版本解压，文件夹名改为 CommentToTelegram激活插件并设置参数使用教程一、 使用前提：申请 Telegram bot Token： 官方教程通过 Telegam bot api 获取 chat_id: getUpdates API如果看不懂，请自行百度谷歌相关教程二、可配置选项如果插件配置有错误，会在点击 保存设置 后提示错误信息。如网络错误、代理信息错误、用户uid错误等等三、通知详情及评论管理按钮3.1 评论通知每条评论只能管理一次，在点击管理按钮后，通知下方的管理按钮会消失并提示评论处理结果：3.2 评论回复启用 Telegram Inline Button管理评论后，即可以使用回复功能。需要先设置 Typecho 真实的用户 uid，查看 uid：在 Telegam 的评论通知上进行评论回复：回复成功会有提示成功：Typecho 博客上的评论状态：3.3 使用建议建议将所有评论设置为先需要审核（后台->设置->评论->评论提交->所有评论必须经过审核），然后通过本插件进行管理。建议使用typecho前台通过主题的评论框进行回复，本插件回复虽然使用 Typecho 内置接口实现，但被回复的访客无法收到评论提醒（如邮件提醒），查看 Tyepcho 代码怀疑是 bug，已经提了一个 issue 询问，目前暂时无法解决。更多问题可以通过 github issue 页面提交，或者通过 博客、邮件向我反馈感谢https://github.com/joyqi/typecho-plugin-sitemaphttps://github.com/Adoream/typecho-plugin-comment2telegram

微型家用网络折腾记
type: item
date: 2024-03-19T08:03:00+00:00
description: 家里网络环境比较简单，网络相关的硬件也比较少，所以也没啥能折腾的，只有以下几个设备：联通光猫型号是中兴的 F657GV9-C，在没有联通没有下发动态密码时改了桥接。本来还想破解一下开telnet，后来想想把桥接的配置备份出来就行了，没啥特别的需求就不折腾了。软路由彻视的N4100，4核4线程，基础频率1.1g，最高频率2.4g，性能比网上这几年流行的J4125还要差，但比arm架构的强多了。主机有4个 Intel I226 的 2.5G 网口，自己买了8G内存和128G的msata，又加了一个内置风扇，本身发热就不大，和光猫一起丢弱电箱夏天也不用担心散热。不打算折腾虚拟机，直接装一个路由系统足够带千兆的带宽了，重点是一套下来也才四百块。AP，是TP-Link XAP5400GC，单 AP 放在房子中间基本就完全覆盖了物理连接： 光猫（改桥接） --> N4100 --> TPlink AP XAP5400GC。由于想找个和 linux 贴近的开源路由系统，最好是和云也能沾点边，所有选择了 vyos，经过一段时间的使用，实现了以下功能：WAN/LAN 分别使用两个网口，网口绑定，实现盲插PPPoE 拨号 及 DHCP servercake qosZone Based firewall科学相关（略）一些远程访问相关的容器一、物理网卡配置这个小主机有四个 i226 的 2.5g 网口，网口分配 eth0/eth1 WAN 口，eth2/eth3 LAN 口，这个小机器的网卡顺序在系统中默认不是0/1/2/3，因此要绑定网卡名字和 mac 地址，防止重启后名字变化。1.1 绑定网卡名字及 mac 地址set interfaces ethernet eth0 hw-id '60:be:xx:xx:xx:xx' set interfaces ethernet eth1 hw-id '60:be:xx:xx:xx:yy'1.2 开启硬件 offloadoffload 简单理解就是网卡硬件中已经实现了一些数据包的处理，开启后不在使用 cpu 处理，能够提升性能。以 eth0 为例子，其他的网卡也需要设置：set interfaces ethernet eth0 offload gro set interfaces ethernet eth0 offload gso set interfaces ethernet eth0 offload lro set interfaces ethernet eth0 offload rfs set interfaces ethernet eth0 offload rps set interfaces ethernet eth0 offload sg set interfaces ethernet eth0 offload tso set interfaces ethernet eth0 ring-buffer rx '4096' set interfaces ethernet eth0 ring-buffer tx '4096'1.3 WAN/LAN 接口配置我要实现的是 eth0/eth1 绑定成一个 bond0 ，在 bond0 上在启用一个桥接的 br0 作为 wan 口；同理，eth2/eth3 绑定成一个 bond1，在 bond1 上在启用一个桥接的 br1 作为 Lan 口。这样做的好处是 eth0/eth1 和 eth2/eth3 可以随便插，有冗余的效果，稍加配置也可以实现两个端口的链路聚合，实现带宽叠加。配置 wan 和 Lan 口物理网卡绑定：set interfaces bonding bond0 description 'Bond Interface WAN' set interfaces bonding bond0 member interface 'eth0' set interfaces bonding bond0 member interface 'eth1' set interfaces bonding bond1 description 'Bond Interface LAN' set interfaces bonding bond1 member interface 'eth2' set interfaces bonding bond1 member interface 'eth3'设置WAN的桥接 br0 和 LAN 的桥接 br1，这里的 stp 其实开不开都用处不大。另外我给 Lan 口配置了一个10.0.0.254的ip，后续的容器都绑定到这个 ip 上。set interfaces bridge br0 description 'WAN bridge' se…

探索 Harbor API 的一些玩法
type: item
date: 2024-03-06T03:39:00+00:00
description: 最近有个需求是通过 Harbor API 获取 image 的最新 tag，来发送提醒进行更新，于是简单研究了一下 Harbor 的API 用法。Harbor 是一个功能丰富、易用且安全的开源 Docker Registry 项目，专为企业级容器镜像管理而设计。主要特性包括：安全与合规性：Harbor 提供安全扫描和漏洞管理功能，自动扫描镜像中的漏洞并生成详细报告。多租户支持：为每个项目提供独立的命名空间，支持自定义角色，实现多租户镜像管理。多镜像源：支持镜像在多个 Registry 之间的复制、备份和迁移。直观界面：提供易用的图形界面，支持镜像的推送、拉取、删除、日志查询和配置管理。垃圾回收：定期清理不再使用的镜像，释放存储空间。可扩展性：架构设计支持功能扩展和插件添加。Harbor REST-API 的认证和用法Harbor的 REST-API 提供了灵活的接口，下面是API认证及简单用法的介绍：API认证Harbor API 采用基于 HTTP 的认证，可使用 curl 命令直接提供用户名和密码进行请求，示例：curl -X GET -u 'username:password' "https://{HarborURL.OPSWILL.COM}:{PORT}/api/v2.0/projects"可以通过 base64 算法加密用户名和密码，然后添加 Authorization 头进行认证：curl -H "Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=" "https://{HarborURL.OPSWILL.COM}:{PORT}/api/v2.0/projects"API 的使用方法查看 Harbor 状态可以通过/health API查看 Harbor 组件状态，示例：curl -H 'Accept: application/json' "https://{HarborURL.OPSWILL.COM}:{PORT}/api/v2.0/health"列出全部 projectscurl -H "Accept: application/json" -H "Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=" "https://{HarborURL.OPSWILL.COM}:{PORT}/api/v2.0/projects"列出某个 project 下的全部 repositoriescurl -H "Accept: application/json" -H "Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=" "https://{HarborURL.OPSWILL.COM}:{PORT}/api/v2.0/projects/{PROJECT_NAME}/repositories"列出某个 repo 下的 artifactscurl -H "Accept: application/json" -H "Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=" "https://{HarborURL.OPSWILL.COM}:{PORT}/api/v2.0/projects/{PROJECT_NAME}/repositories/{REPO_NAME}/artifacts"通过上述命令可获取镜像的详细信息，包括标签、漏洞情况、命令、环境变量、暴露端口等。全部 Harbor REST-API可通过 Swagger 查看和使用所有可用 API ，直接访问链接：Harbor REST-API Swagger总结Harbor API 使用起来还是不难的，配合脚本使用很方便。闲着没事又水了一篇文章 :) 。

Amazon EKS集群升级流程
type: item
date: 2024-02-28T04:39:00+00:00
description: AWS 会对每个 EKS 版本提供14个月支持，同时 AWS 会强制更新太旧的 EKS 集群，因此可能导致生产服务中断。要升级 EKS 集群，首先要注意以下事项：验证与应用程序关联的容器映像版本是否可用，主要是排除应用异常导致的干扰。检查集群所在子网有足够的可用 IP，可用IP不足也可能会导致 EKS 升级失败。检查 node group 配置中的最大不可用性节点数量，这个数量表示每次更新期间节点组中可以替换的节点数。在大规模集群中，设置为百分比更有利于提高更新的速度。一、升级EKS的检查工作EKS 不允许跨版本升级，在升级 EKS 集群之前需要做兼容性的检查：1.1 与 Kubernetes 相关的变更每一个版本的 EKS 都有一些 API 已被弃用。 因此需要检查特定版本更改的详细信息，需要参考 Kubernetes 的变更日志和 EKS 版本更新文档。https://github.com/kubernetes/kubernetes/tree/master/CHANGELOG 需要重点关注 Urgent Upgrade Notes 部分https://docs.amazonaws.cn/eks/latest/userguide/kubernetes-versions.html1.2 使用 kubent 检查 API 变更国际版的 EKS web console 中提供了 Upgrade insights 工具，可以检查 API 变动。但是中国区没有这个工具，可以使用第三方工具 kube-no-trouble 进行检查。下载 https://github.com/doitintl/kube-no-trouble/releases 并然后解压。确保 kubectl 当前的 context 是要升级的 EKS 集群，使用下面的cmd来检查：kubectl config current-context执行./kubent开始检查。 确保 >>> Deprecated APIs removed in 1.XX <<< 中没有任何内容。注意：要保证 aws cli 的版本应大于2.0，尽量使用新版本的 aws cli 。如果API版本发生变化，需要参考 https://docs.amazonaws.cn/en_us/eks/latest/userguide/update-cluster.html 进行更新。1.3 检查 addons 版本是否需要更新所有的 addon 都在 kube-system namesapce中，首先需要确定所有 addon 需要升级的目标版本号，供后续更新使用。1.3.1 检查兼容的版本使用以下命令可以列出与 EKS 1.22 版本兼容的所有插件版本：查看 kube-proxy 全部兼容的版本aws eks describe-addon-versions --kubernetes-version 1.22 --addon-name kube-proxy | grep addonVersion查看 coredns 兼容的版本aws eks describe-addon-versions --kubernetes-version 1.22 --addon-name coredns | grep addonVersion查看 vpc-cni 兼容的版本aws eks describe-addon-versions --kubernetes-version 1.22 --addon-name vpc-cni | grep addonVersion另外，建议结合 AWS 的文档确定最终要的 addon 版本：https://docs.aws.amazon.com/eks/latest/userguide/managing-kube-proxy.htmlhttps://docs.aws.amazon.com/eks/latest/userguide/managing-coredns.htmlhttps://docs.aws.amazon.com/eks/latest/userguide/managing-vpc-cni.html1.3.2 检查当前 addons 版本您可以使用下面的 AWS CLI 命令来查询 addon的 image 版本：#kube-proxy kubectl get daemonset kube-proxy -n kube-system -o yaml | grep image # coredns kubectl get deployment coredns -n kube-system -o yaml | grep image # vpc-cni kubectl get daemonset aw…

一个简单的文件服务器
type: item
date: 2024-02-05T10:52:37+00:00
description: 家里的路由器基本上折腾差不多了，用的是小厂家的x86工控机，硬盘也是捡的垃圾，想到自己时不时的升级/重启/异常断电，还是备份一下的配置文件比较稳妥。我用的路由系统的基于debian的VYOS rolling版本，折腾了半个月已经非常熟悉了，用起来比较爽也很稳定，基本满足自己的各种需求。系统自带了同步配置的功能,每次commit前会先同步配置，支持以下几种方式： http://<user>:<passwd>@<host>/<path> https://<user>:<passwd>@<host>/<path> ftp://<user>:<passwd>@<host>/<path> sftp://<user>:<passwd>@<host>/<path> scp://<user>:<passwd>@<host>/<path> tftp://<host>/<path> git+https://<user>:<passwd>@<host>/<path>以上方式中，最简单的就是http方式了，经过抓取日志，vyos 会使用 "POST /config.boot-vyos.20240205_171540" 方式上传配置，其中uri是配置文件名，包体是明文的配置内容，正好我的服务器使用的是openresty，就用lua写了一个简单的文件服务器，接收vyos POST过来的配置。lua脚本实现了以下功能：接收POST包体并保存到文件，文件名为POST请求的uri使用http basic authentication的用户名密码认证接收POST请求,直接接收POST包体然后使用io.open写入文件： local file local upload_path = '/var/www/html/vyos/' ngx.req.read_body() local file_name = ngx.var.uri local data = ngx.req.get_body_data() if file_name then file = io.open(upload_path .. file_name, 'a+') if not file then ngx.log(ngx.ERR, "failed to open file ", file_name) return end end if data then file:write(data) file:close() else ngx.log(ngx.ERR, "data is empty") return end用户名密码认证，使用nginx也能实现，但是认证文件要单独写进文件里。用lua就一个配置文件就能可以了。http basic auth主要是是解析http头Authorization，并使用base64解密用户名密码： local function split(str, delimiter) local result = {} if not str or not delimiter then return result end for match in (str..delimiter):gmatch("(.-)"..delimiter) do if match then table.insert(result, match) end end return result end local function parseHeader(authorization) local base64 = string.sub(authorization, 7) local data = split(ngx.decode_base64(base64), ":") return data[1], data[2] end local header = ngx.req.get_headers()["Authorization"] if header then local username, password = parseHeader(header) if username ~= "vyos.user" or password ~= "vyos.pass" then ngx.header["WWW-Authenticate"] = Basic realm="restricted" ngx.exit(401) end end以上就是核心逻辑，经过测试ngx.req.get_body_data() 读请求包体，会偶尔出现读取不到直接返回 nil 的情况。还需要额外设置client_max_body_size 和client_body_buffer_size, 强制在内存中保存请求体。完整的nginx配置文件如下：ser…

MongoDB分片集群配置的流水账
type: item
date: 2024-01-15T08:20:00+00:00
description: MongoDB的分布式架构是通过使用Sharding（分片）来实现的，使用MongoDB的sharding可以带来以下好处：横向扩展：通过添加更多的shard，MongoDB能够处理更大的数据集和更高的并发访问，提供更好的性能和可扩展性。高可用性：通过将数据复制到多个shard上，并使用复制集提供数据冗余和自动故障转移，MongoDB能够提供高可用性。负载均衡：Mongos路由请求到不同的shard上，从而实现负载均衡，避免单个shard成为瓶颈。一、分片集群组件在一个分片集群中，包含以下组件：分片(shards)，实际存储数据的节点。集群配置集(config)，存储整个分片集群的metadata信息。路由(mongos)，作为应用接入路由节点，是mongodb数据的入口。1.1 Shards每个shard是一个独立的MongoDB实例，负责存储一部分数据，多个独立的shard分片组成整个shards分片集群。每个shard都可以是一个独立的物理服务器或一个复制（Replica Set）。Shard可以动态地添加或删除，以适应数据量的变化。Shard Key用来将数据分发到不同shard。MongoDB根据Shard Key决定将数据存储在哪个shard上。1.2 Config servers（metadata）Config Server是存储集群的元数据（metadata）的特殊MongoDB实例。元数据包括分片键（shard key）的范围和每个分片负责的数据范围。Config Server通常以复制集的形式部署，官方推荐至少3个节点，以提供高可用性和数据冗余。作为config server的复制集必须采用 WiredTiger storage engine。副本节点中没有arbiter。副本节点中没有delayed members。副本节点中必须build indexes。1.3 Router（mongos）mongos是应用程序和分片集群之间的接口，所有对集群的操作及数据读写都连接到mongos进行。应用程序通过Mongos与集群交互，Mongos负责将请求路由到正确的shard上。Mongos还负责跟踪分片集群的状态和管理元数据。二、配置高可用mongodb分片集群MongoDB sharding 高可用集群配置要点：配置mongodb分片集群时，推荐对每个mongod组件使用用域名或hosts绑定。shard集群至少配置2个分片，每个分片都为复制集(1 Primary + 1 Replica + 1 Arbiter)，实现数据冗余。至少3个config server，实现集群高可用。启用多个mongos实例，通过在应用上配置，实现负载均衡。2.1 集群配置说明通常按以下顺序进行集群配置：分别创建shards、config、mongos配置文件确认mongodb各目录是否存在及权限正确按顺序配置并启动分片集群各个组件实例(依次启动config->shards->mongos)初始化复制集分片初始化config server复制集配置集群分片，对DB或Collection开启分片功能使用数据库都连接到mongos节点进行操作2.2 各组件的配置文件2.2.1 shard 配置文件 shard.conf多个shard节点配置相同，需注意dbPath是否存在且权限正确。processManagement: fork: true net: port: 27018 bindIp: 0.0.0.0 systemLog: destination: file path: /opt/mongodb.log logAppend: true logRotate: rename storage: journal: enabled: true dbPath: /opt/data/mongodb/ directoryPerDB: true engine: wiredTiger wiredTiger: engineConfig: cacheSizeGB: 10 directoryForIndexes: true collectionConfig: blockCompressor: zlib indexConfig: prefixCompression: true replication: oplogSizeMB: 20480 replSetName: shardN sharding: clusterRole: shardsvr 2.2.2 config server 配置文件 config.conf多个config节点配置相同，需注意dbPath是否存在且权限正确。processManagement: fork: true net: port: …

通过Boto3 API从WAF中获取攻击IP
type: item
date: 2023-08-04T08:10:00+00:00
description: 之前写了一篇 借助 CloudWatch 和 WAF 缓解 DDOS 攻击,经过实践发现了一些问题：在受到攻击时 Cloudwatch 产生存储和查询的费用都高获取详细攻击信息使用了较多的 log 查询，查询次数多、时间长通过 Cloudwatch 中日志统计攻击 IP ，获取 IP 比较慢，且不够准确发生攻击时产生的请求已经非常多了，将请求转换成日志在分析，不是经济和有效率的做法。查了一下 boto3 的文档，发现 Waf 已经提供了一个获取被频率限制 ip 的 api : get_rate_based_statement_managed_keys而且通过分析 sampled request : get_sampled_requests 可以获取攻击的域名。既然可以直接获取攻击 IP 和域名，Cloudwatch 的日志存储和分析的费用就可以省下了。更新了一下 Lambda 代码，如下：import boto3 from collections import Counter from datetime import datetime, timedelta def lambda_handler(event, context): acl_name = 'webapi' acl_id = 'ada04a19-0fc9-5abd-c497-6646b2369d8e' arn = 'arn:aws:wafv2:us-east-1:849873481391:global/webacl/webapi/eda04a18-9fc8-4abc-b497-5646b2369d8g' rule_names = ['WAF-RateLimit-URI','WAF-RateLimit-All'] ip_set_name = 'Global_blacklist-auto' # 检查 CloudWatch Alarm 状态 print(f"Getting CloudWatch Alarm state...") cloudwatch_client = boto3.client('cloudwatch') alarm_name = 'WAF-ALL-BLOCKED-Req-5mins' response = cloudwatch_client.describe_alarms( AlarmNames=[alarm_name] ) alarm_state = response['MetricAlarms'][0]['StateValue'] print(f"CloudWatch Alarm state: {alarm_state}") if alarm_state == 'OK': print("CloudWatch Alarm is in OK state. Exiting Lambda function.") return merged_ip_addresses = [] wafv2_client = boto3.client('wafv2') # 从sampled requests获取被攻击域名，并告警 host_counter = Counter() print("Step 1: Getting all rate limited rule logs from WAF...") for rule in rule_names: response = wafv2_client.get_sampled_requests( WebAclArn=arn, RuleMetricName=rule, Scope='CLOUDFRONT', TimeWindow={ 'StartTime': (datetime.utcnow() - timedelta(hours=3)).strftime("%Y-%m-%dT%H:%MZ") , 'EndTime': datetime.utcnow().strftime("%Y-%m-%dT%H:%MZ") }, MaxItems=500 ) for request in response['SampledRequests']: headers = {header['Name']: header['Value'] for header in request['Request']['Headers']} if 'Host' in headers: host_counter[headers['Host']] += 1 most_common_host = host_counter.most_common(1) print(f'Most common host: {most_common_host[0][0]} with {most_commo…

借助 CloudWatch 和 WAF 缓解 DDOS 攻击
type: item
date: 2023-07-31T10:56:00+00:00
description: 先来几张图说明一下做这个的原因：一、前提条件需要使用以下几个服务：AWS WAF 启用全局频率限制、全局黑名单、启用 logging 输出日志AWS CloudWatch 存储日志，攻击检测，发送告警到SNSAWS SNS 触发Lambda相关功能AWS Lambda 发送通知，更新 AWS WAF 全局黑名单Amazon EventBridge 定期执行 Lambda函数，更新 WAF 黑名单1.1 AWS WAF 配置需要在 WAF ACL 中设置以下几项：创建一个 IP Sets 作为全局黑名单创建一个全局黑名单规则，如果 IP 在全局黑名单中则 block，并设置优先级最高根据业务需求创建一个全局频率限制规则，优先级仅次于黑名单规则启用 WAF 的 logging，设置 Logging destination 为 CloudWatch，设置一个 filter ： rule action on request 是 block 就 keep in logs二、攻击检测及告警配置2.1 Cloudwatch 配置Cloudwatch 的告警基于 WAF ACL 中频率限制的规则。在 Cloudwatch 中设置一个告警，告警 Metric 基于 AWS/WAFV2 中 五分钟内的 BlockedRequests 总数，如果5分钟内被限制的请求大于 3000 则是 In alarm 状态，则触发 SNS Alert 告警通知；否则是 OK 状态，触发 SNS OK 通知 。2.2 Amazon SNS 配置SNS 需要配置两个 Standard 的 topic，一个是WAF-Ddos-Alert，一个是WAF-Ddos-Ok，用于触发 Lambda 发送攻击告警和攻击结束通知。2.3 编写 Lambda 实现攻击检测和告警新建一个 Lambda python function，代码放在下面，设置以下参数：trigger 设置为 SNS topic ： WAF-Ddos-AlertLambda 的 Execution role 需添加权限 ： CloudWatchReadOnlyAccess CloudWatchLogsReadOnlyAccess设置 telegram 相关的环境变量 ： BOT_TOKEN 和 USER_WILL设置超时时间为5分钟代码如下：import os import json import urllib3 import boto3 from datetime import datetime, timedelta import time def lambda_handler(event, context): # 从环境变量中获取 Telegram Bot Token 和聊天 ID bot_token = os.environ['BOT_TOKEN'] user_will_chat_id = os.environ['USER_WILL'] send_telegram_message(bot_token,user_will_chat_id, "请注意有DDos攻击！！！") #按实际情况修改 log_group_name = 'aws-waf-logs-All-Blocked-Logs' # 获取当前时间和五分钟前的时间 current_time = datetime.now() period = current_time - timedelta(minutes=15) # 创建 CloudWatch Logs Insights 客户端 logs_client = boto3.client('logs') queries = [ { 'name': 'Top5_Host', 'query': "fields @timestamp, @message | parse @message '{\"name\":\"Host\",\"value\":\"*\"}' as host | stats count(*) as requestCount by host | sort requestCount desc | limit 5" }, { 'name': 'Top5_IP', 'query': "fields httpRequest.clientIp | stats count(*) as requestCount by httpRequest.clientIp | sort requestCount desc | limit 5" }, { 'name': 'Top5_Country', 'query': "fields httpRequest.country | stats count(*) as reque…

借助 Cloudflare Workers 实现查询 ip 信息的API
type: item
date: 2023-07-25T03:31:35+00:00
description: 在使用Cloudflare CDN时，CDN传给后端服务器中只传递了有限的 http request hearder。其中只包含了非常简陋的 ip 信息，例如只有 CF-IPCountry ，不足以实现一个查询ip信息的api，要想实现查询ip详细信息需要借助 Cloudflare Workers 来实现。Cloudflare Workers 传入的 HTTP 请求都被称为 fetch 事件，fetch 事件中都包含一个 Request 接口实例,在这个 Request 实例中就包括了访问 ip 的详细信息通过访问 Request.cf 可以获得 Cloudflare 提供的请求信息，包含了 ip 的详细信息；访问 Request.headers 则可以获得访客的 HTTP headers，写一个简单的 Workers 处理一下 Request.cf 、Request.headers 中的信息，并返回 Json 格式即可。访问该 Cloudflare Workers 后返回包含访客 IP、Continent、Country、Region、ASN 等 ip 信息及请求的Header信息，同时也会把 Cloudflare 的 https 连接信息及 bot 识别的相关信息一起返回。代码如下：export default { async fetch(request) { const data = { Method: request.method, Url: request.url, IP: { IP: request.headers.get('CF-Connecting-IP'), Continent: request.cf.continent, Country: request.cf.country, IsEU: request.cf.isEUCountry, Region: request.cf.region, RegionCode: request.cf.regionCode, City: request.cf.city, Latitude: request.cf.latitude, Longitude: request.cf.longitude, PostalCode: request.cf.postalCode, MetroCode: request.cf.metroCode, Colo: request.cf.colo, ASN: request.cf.asn, ASOrganization: request.cf.asOrganization, Timezone: request.cf.timezone }, Headers: {}, Security: {} }; // 遍历并存储每个 HTTP 头，排除以 cf- 开头的 HTTP 头 request.headers.forEach((value, name) => { if (!name.toLowerCase().startsWith('cf-')) { data.Headers[name] = value; } }); // 遍历 request.cf 并存储所需对象的属性到 Security 中 for (const key in request.cf) { if ( key == 'clientTcpRtt' || key == 'tlsCipher' || key == 'tlsVersion' || key == 'httpProtocol' || key == 'clientHandshake' || key == 'clientFinished' || key == 'serverHandshake' || key == 'serverFinished' || key == 'corporateProxy' || key == 'verifiedBot' || key == 'score' ) { if (typeof request.cf[key] === 'object') { for (const innerKey in request.cf[key]) { data.Security[innerKey] = request.cf[key][innerKey]; } } else { data.Security[key] = request.cf[key]; } } } var dataJson = JSON.stringify(data, null, 4); console.log(dataJson); return new Response(dataJson, { headers: { "Content-Type": "applicati…