make sadservers happy again
背景
从 阮一峰老师的博客 了解到 sadserver 这样一个可以提供 linux 服务器,并尝试解决系统和服务相关问题的在线测试平台。非常难得的是它可以直接提供一个公网的 linux 服务器(一般40-60分钟后会自动销毁),你可以在上面做任何探索。对于想要学习常用 linux 指令的同学,是一个非常不错的练手平台
sadserver 大部分题目都是免费的,46道题中11道需要会员,会员一个月5美元。可以在完成其他题目之后再小小氪💰一下挑战剩下的题目
本文整理了目前 sadserver 所有题目的分类和解题相关指令,带部分“剧透”,建议对题目本身有强烈兴趣的同学直接动手尝试
*推荐: 标注推荐的主要是和 linux 常用指令相关的题目,其他开源服务相关的题目可以选择性了解
*提醒: 部分题目需要 root 用户权限,以执行系统配置修改等相关指令,可通过sudo su
指令提权
题目分类
题目 | 题目/问题概述 | 相关指令 |
---|---|---|
"Saint John": what is writing to this log file? | 查找文件占用的进程 | lsof, find |
"Saskatoon": counting IPs. (推荐) | 字符串计数 | lsof, find |
"Santiago": Find the secret combination (推荐) | 文本内容过滤 | grep |
"Taipei": Come a-knocking (推荐) | knock 端口开放 | knocked |
"Lhasa": Easy Math (推荐) | 数值计算 | bc |
"Bucharest": Connecting to Postgres | pgsql配置修复 | |
"Bilbao": Basic Kubernetes Problems | 服务配置修复 | kubectl |
"Apia": Needle in a Haystack | 文本比较 | diff |
"Manhattan": can't write data into database. | pgsql 配置修复 | |
"Tokyo": can't serve web file (推荐) | httpd 配置修复 | iptables |
"Cape Town": Borked Nginx | nginx 配置修复 | systemctl |
"Salta": Docker container won't start | 容器内服务启动问题 | docker, netstat |
"Oaxaca": Close an Open File | 查找文件占用的进程 | lsof, exec |
"Melbourne": WSGI with Gunicorn | gunicorn 配置修复 | |
"Lisbon": etcd SSL cert troubles | etcd 访问不通 | etcd, iptables |
"Kihei": Surely Not Another Disk Space Scenario (推荐) | 挂盘 | pvcreate, vgcreate, lvcreate, mount |
"Unimak Island": Fun with Mr Jason | 文本解析 | jq |
"Ivujivik": Parlez-vous Français? | excel 解析 | python |
"Paris": Where is my webserver? | http 接口调用和参数 | curl |
"Buenos Aires": Kubernetes Pod Crashing | kubernetes 权限体系 | kubectl |
"Tarifa": Between Two Seas | nginx 配置修复 | docker |
"Marrakech": Word Histogram (推荐) | 文本解析 | sqlite |
"Rosario": Restore a MySQL database | mysql 故障恢复 | mysql |
"Abaokoro": Restore MySQL Databases Spooked by a Ghost | mysql 数据导入 | mysql |
"Poznań": Helm Chart Issue in Kubernetes | helm 基本使用 | helm |
"Manado": How much do you press? | 解压缩常用指令的使用 | zip, tar, xz |
"Warsaw": Prometheus can't scrape the webserver | go 服务问题修复 | docker |
"Moyogalpa": Security Snag. The Trials of Mary and John | 服务文件权限和防火墙设置 | apparmor, systemctl, update-ca-certificates |
"Helsingør": The first walls of postgres physical replication | postgresql 同步主库相关配置修复 | docker |
"Bekasi": Supervisor is still around | wsgi 启动配置修复 | curl, supervisorctl |
"Depok": Nginx with Brotli | nginx 插件安装 | cmake, make, systemctl |
"Tukaani": XZ LZMA Library Compromised | preload 库问题修复 | lsof |
"Jakarta": it's always DNS. | DNS 解析配置修复 | |
"Bern": Docker web container can't connect to db container. | 通过 docker 启动 wordpress | docker |
"Karakorum": WTFIT – What The Fun Is This? (推荐) | 定位系统和进程启动问题 | perl, strace, python |
"Singara": Docker and Kubernetes web app not working. | 镜像重建和提交 | docker, kubectl |
"Hong-Kong": can't write data into database. | postgresql 配置问题修复 | postgres, systemctl |
"Pokhara": SSH and other sshenanigans (推荐) | ssh 和 su 问题解决 | ssh, su, chage, ssh-keygen |
"Roseau": Hack a Web Server | john 暴力破解工具的使用 | john |
"Belo-Horizonte": A Java Enigma | java 程序执行问题解决 | java, javap, free, fallocate, mkswap, swapon |
"Chennai": Pull a Rabbit from a Hat | rabbitmq 测试生产消费 | docker, python |
"Monaco": Disappearing Trick | git和进程环境变量 | git |
"Florence": Database Migration Hell | js+web后台+pgsql问题解决 | docker, sed |
如何解题
Level: 简单
【linux】"Saint John": what is writing to this log file?
题意
服务器上有一个日志文件在被一个进程占用,需要找到这个进程并停止
相关指令
# 查看文件占用进程 id
lsof /var/log/bad.log
ls -l /proc/*/fd/* | grep bad.log
fuser /var/log/bad.log
# 确认文件是否最近未更新
find /var/log/bad.log -mmin -0.1
【推荐】【linux】"Saskatoon": counting IPs.
题意
/home/admin/access.log 是一个 web 服务的访问记录日志(如 nginx 的访问日志就在 access.log 中),从中统计出访问次数最多的源ip
相关指令
# 截取ip
cat /home/admin/access.log | awk '{print $1}' > /tmp/access_ips.log
# 排序
cat /tmp/access_ips.log | sort > /tmp/sort_ips.log
# 计数 & 再排序
## uniq: 统计出现次数
## sort: -n: 按数值排序而不是字符串序; -r: 逆序
cat /tmp/sort_ips.log | uniq -c | sort -nr > /tmp/count_ips.log
【推荐】【linux】"Santiago": Find the secret combination
题意
统计特定字符串在每个文件中的出现次数,并导出只出现一次的文件,出现位置的下一行内容
相关指令
# 查找文件内容 & 统计数量
## 注意: 题目要求是字符串出现次数,如果是统计词语可在 grep 之后加上 -w 参数
find /home/admin -name "*.txt" | xargs grep -c 'Alice'
# 输出之后一行(前一行是-B),且过滤出数字部分
grep "Alice" -A1 /home/admin/1342-0.txt | grep -oE "[0-9]+"
The Command Line Murders
本题更像是解密游戏,而不是解决服务器问题,因此笔者选择(偷懒)跳过了这题,喜欢读解密类小说的可以试一下
【推荐】【linux】"Taipei": Come a-knocking
题意
本机 80 端口启动了 http 服务,但端口被 knockd 进程限制访问,无法直接访问
需要通过 knock 对所有端口进行“开门”,使得 80 端口放开
相关指令
# 对 1000 到 65535 的所有端口发送 knock 请求( 发送 SYN )
for port in {1000..65535}
do
knock localhost ${port}
done
# 扫描本地常见服务端口( 发送 SYN )
nmap -p- localhost
# 查看 nmap 扫描的端口范围
nmap -v -oG - | grep "Ports scanned"
【推荐】【linux】"Lhasa": Easy Math
题意
将一个文本中的第二列数字求和以及求平均
linux 本身的运算符支持整数运算,因此节点还提供了 python3 和 bc
相关指令
# 获取第二列
sum_expr=`awk '{print $2}' /home/admin/scores.txt
# 将文本的每一行拼接到同一行,并用加号分隔
paste -sd+ test.txt
# 计算数学表达式
sum=`echo ${expr} | bc`
# 文本行数
count=`cat scores.txt | wc -l`
# 保留两位小数,计算两数相除
echo "scale=2; ${sum} / ${count}" | bc
# python3 test.py
sum = 0
num = 0
score_file = '/home/admin/scores.txt'
with open(score_file, "r") as scores_file:
lines = data = scores_file.read().splitlines()
for line in lines:
score = line.split(" ")[1]
sum += float(score)
num += 1
print("%.2f" % (sum / num))
// go run test.go
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
scoreFile, _ := os.Open("/root/scores.txt")
defer scoreFile.Close()
scanner := bufio.NewScanner(scoreFile)
scoreCount := 0
scoreSum := 0.0
for scanner.Scan() {
scoreStr := strings.Split(scanner.Text(), " ")[1]
scoreFloat, _ := strconv.ParseFloat(scoreStr, 32)
scoreSum += scoreFloat
scoreCount++
}
fmt.Printf("%.2f\n", float32(scoreSum)/float32(scoreCount))
}
【postgresql】"Bucharest": Connecting to Postgres
题意
本机启动了 postgresql 服务,但因为配置问题连接失败。需要修复 pgsql 配置并重启
相关指令
# 指定密码、用户和 db 连接 pgsql
PGPASSWORD=app1user psql -h 127.0.0.1 -d app1 -U app1user -c '\q'
# 查看 pgsql 进程和启动参数 (-e: 查看所有进程,-f: 列举所有进程信息)
ps -ef | grep pgsql
# 查看 pgsql 服务基础配置文件
vim /etc/postgresql/13/main/postgresql.conf
# 查看 pgsql 登录身份和允许访问源地址的配置文件
vim /etc/postgresql/13/main/pg_hba.conf
# 重启 pgsql
systemctl restart postgresql
【kubernetes】"Bilbao": Basic Kubernetes Problems
题意
manifest.yaml 中定义了 nginx service,但启动失败了,需要通过 pod 的启动信息去解决
相关指令
# 检查指令:
curl 10.43.216.196
# 查看 nginx pod 状态
kubectl get pods
kubectl describe pod nginx-deployment-67699598cc-zrj6f
# 解决 1 node(s) didn't match Pod's node affinity/selector ...
## node 添加 label
kubectl label nodes node1 disk=ssd
## 删除 pod (触发重建)
kubectl delete pod nginx-deployment-67699598cc-zrj6f
# 解决 1 Insufficient memory
## 减少 nginx 申请的内存资源
sed -i "s/memory:.*/memory: 500Mi/g" manifest.yml
## 重载 manifest.yml
kubectl apply -f manifest.yml
【linux】"Apia": Needle in a Haystack
题意
在 /home/admin/data 目录下有很多 txt 文件,其中一个和其他文件内容不一样,而且只是在一个段落中多了一个单词,找到这个单词
相关指令
# 输出文本的 md5 值
md5sum /home/admin/data/file1.txt
# 对比两个文本差异(段落级别)
diff /home/admin/data/file1.txt /home/admin/data/file2.txt
# 对比两个文本差异(单词级别)
wdiff /home/admin/data/file1.txt /home/admin/data/file2.txt
中等
【postgresql】"Manhattan": can't write data into database.
题意
本机启动了 postgresql 服务,但执行 insert 会失败,找到原因并解决
相关指令
# 查看 postgresql 日志
tail -f /var/log/postgresql/postgresql-14-main.log
# 重启 postgresql
systemctl restart postgresql
【推荐】【httpd、linux】"Tokyo": can't serve web file
题意
本机启动了 apache2(即 httpd) 服务,但执行 curl 127.0.0.1:80 没有任何返回,确认具体原因
相关指令
# 查看防火墙规则列表
iptables -L --line-numbers
# 删除指定规则
iptables -t filter --delete INPUT 1
# 清空所有防火墙规则
iptables -F
# 给文件添加所有用户可读权限
chmod +r /var/www/html/index.html
【nginx】"Cape Town": Borked Nginx
题意
本机安装了 nginx 但无法启动,检查启动失败原因,修复并启动
相关指令
# 重启 nginx 服务
systemctl restart nginx
# 查看 nginx 启动日志
journalctl -u nginx
# 查看 nginx 启动错误日志
tail -f /var/log/nginx/error.log
# 查看 nginx 配置
/etc/nginx/nginx.conf
# 查看 nginx systemd 服务管理配置
## 关键配置: LimitNOFILE
vim /etc/systemd/system/nginx.service
# 重新加载配置并重启 nginx
systemctl daemon-reload
systemctl restart nginx
【docker】"Salta": Docker container won't start.
题意
本机 /home/admin/app 目录下有一个支持容器化的 node 应用,并提供 Dockerfile 可以直接构建
服务主逻辑在 server.js 中
需要构建镜像后,启动该服务,并解决调用该服务接口遇到的问题
相关指令
# 构建镜像
docker build -t app .
# 启动
docker run -d --name app -p 8888:8888 app
# 检查端口占用
netstat -tunlp | grep 8888
# 查看容器日志
docker logs dd231758183e
【linux】"Oaxaca": Close an Open File
题意
/home/admin/somefile 正在被一个进程占用,找到占用原因,并使得下次登录终端后,该文件不会再被进程占用
相关知识点: exec 指令
相关指令
# 找到占用文件的进程 ,并直接 kill
lsof /home/admin/somefile | tail -2 | awk '{print $2}' | xargs kill -9
# 一个会导致 /tmp/test.txt 被bash进程占用的指令
exec 77> /tmp/test.txt
【nginx、gunicorn】"Melbourne": WSGI with Gunicorn
题意
本机运行了 nginx 和 gunicorn(实现了 WSGI 协议的 HTTP 服务器,master-worker 架构)
正常情况下,通过 curl 调用服务的调用链: curl -> nginx -> gunicorn -> wesi.py,其中 /home/admin/wesi.py 中定义了接口的响应方式为返回200状态码和字符串"Hello World"
但是通过 curl -i http://localhost 调用接口失败了,找到原因并通过修改配置解决
相关教程: Deploying Gunicorn
相关知识点: http header: content-length
相关指令
# 查看 nginx 错误日志
tail -f /var/log/nginx/error.log
# 查找 nginx 配置目录下带有指定关键字的配置文件
find /etc/nginx -name '*' -type f | xargs grep "gunicorn"
# 重启 nginx
systemctl restart nginx
# 查看 gunicorn 配置
vim /etc/systemd/system/gunicorn.service
# 查看 wsgi 服务配置
vim /home/admin/wsgi.py
# 重启 gunicorn
systemctl restart gunicorn
【etcd、linux】"Lisbon": etcd SSL cert troubles
题意
本机部署了 etcd 服务,正常情况下可通过 etcdctl get foo
或 curl -i https://localhost:2379/v2/keys/foo
获取配置,但目前访问失败,需要解决
相关指令
# 通过 etcdctl 获取配置
etcdctl get foo
# 设置系统时间
date -s '2023-01-29 00:00:00'
# 调用etcd接口
curl -ik https://localhost:2379/v2/keys/foo
# 查看防火墙NAT类型的配置
iptables -t nat -L
# 清空NAT类型防火墙规则
iptables -t nat -F
【推荐】【linux】"Kihei": Surely Not Another Disk Space Scenario
题意
当前路径的 kihei 程序会在 /home/admin/data 目录下创建一个 1.5G大小的文件,但因为磁盘空间不足,执行失败了,需要在不删除其他文件的情况下使得 kihei 能够正常执行
相关指令
本题是非常完整的磁盘挂载练手好题,可以结合挂盘相关的指令一起理解
参考: lvcreate 创建逻辑卷、vgcreate 创建卷组、pvcreate 创建物理卷、vgextend 扩容卷组、lv缩容
# 查看 kihei 进程的详细日志
## 注意: 这里的 kihei 可执行文件并不是来自开源项目...
./kihei -v
# 查看本地磁盘使用情况和剩余空间
df -h
# 查看所有磁盘
lsblk
# 创建物理卷
pvcreate /dev/nvme1n1
# 查看所有物理卷
pvs
# 创建卷组
vgcreate vg1 /dev/nvme1n1 /dev/nvme2n1
# 查看所有卷组
# 创建逻辑卷
lvcreate -n lv1 -l 100%FREE vg1
# 查看逻辑卷
lvs
# 格式化卷,初始化文件系统
mkfs -t ext4 /dev/vg1/lv1
# 卷挂载,并设置目录权限
mount /dev/vg1/lv1 /home/admin/data
chown -R admin:root /home/admin/data
【linux】"Unimak Island": Fun with Mr Jason
题意
station_information.json 中包含若干个单车租赁站的信息,筛选其中 没有自动服务机(has_kiosk=false)且剩余单车超过30辆(capacity>30)的租赁站,将它的id(station_id)写入答案文件中
相关指令
虽然题目提示本机有 jq 、 gron 和 jid 等工具,但后两者操作起来都不如同时包含解析和筛选器的 jq 方便,这里直接展示 jq 的几个基本指令,组合几个 filter ,就可以解答本题了
# 获取数组长度
echo "[1,2,3,4,5]" | jq length
# 过滤器
echo '{"dates":[{"day":"monday","emotion":"happy"},{"day":"tuesday","emotion":"crazy"}]}' | jq -r '.dates[] | select(.emotion=="happy")'
【python、go】"Ivujivik": Parlez-vous Français?
题意
table_tableau11.csv 文件中记录了总统选举中,各个选区的投票情况信息,需要按题目的筛选条件得到对应的选区名称
省份,选区名称,选区编号,人口,选民,投票站,有效选票,有效选票百分比,被拒绝的选票 被拒绝的选票,被拒绝的选票百分比,总投票数,选民投票率,当选候选人
相关指令
本题主要考察通过各个语言/工具解析 csv 的方法,这里展示 python 和 go 的解答方式
# pandas
import csv
max_reject = 0
answer = ""
with open('table_tableau11.csv') as f:
reader = csv.reader(f)
next(reader) # skip first line
for row in reader:
current_reject = int(float(row[8]))
current_population = int(row[3])
if current_reject > max_reject:
max_reject = current_reject
if current_population < 100000:
answer = row[1]
with open('mysolution', 'w') as f:
f.write(f"{answer}\n")
f.close()
package main
import (
"encoding/csv"
"os"
"io/ioutil"
"strconv"
)
func main() {
f, _ := os.Open("table_tableau11.csv")
csvReader := csv.NewReader(f)
csvReader.FieldsPerRecord = -1 // ignore fields per record
records, _ := csvReader.ReadAll()
answer := ""
maxReject := 0
for _, currentRecord := range records[1:] {
currentReject, _ := strconv.Atoi(currentRecord[8])
currentPopulation, _ := strconv.Atoi(currentRecord[3])
if currentReject > maxReject {
maxReject = currentReject
if currentPopulation < 100000 {
answer = currentRecord[1]
}
}
}
ioutil.WriteFile("mysolution", []byte(answer + "\n"), 0644)
}
【linux】"Paris": Where is my webserver?
题意
本题没有提示应该比较难做出来,因为问题相关的代码甚至都看不到,所以不是很推荐,主要考察对 user-agent header 的了解
user-agent: 表示客户端类型,默认为空,浏览器访问时将会自动填充
这里访问 5000 端口启动的服务端会对 user-agent 进行校验,如果为空将不会返回数据,需要指定任意的 user-agent 再调用 5000 端口,拿到答案需要的密码
相关指令
# 添加 user-agent 调用接口
curl --user-agent "hhh" http://localhost:5000
curl -H 'User-Agent: hhh' http://localhost:5000
curl -A 'hhh' http://localhost:5000
【kubernetes】"Buenos Aires": Kubernetes Pod Crashing
题意
本机部署了 k8s ,其中名为 logshipper 的 pod 启动失败了,需要确认失败原因
本题主要考察 k8s 的 clusterrole 对应的权限类型
相关指令
# 查看所有 pod 的 label
kubectl get pod --show-labels
# 查看指定 label 对应的 pod 信息和状态
kubectl describe pod -l app=logshipper
kubectl describe pod logshipper-597f84bf4f-6ssjq
kubectl describe deployment logshipper
# 查看 pod 日志(提示权限不足)
kubectl logs logshipper-597f84bf4f-6ssjq --follow
# 查看 logshipper pod 绑定的 serviceAccount 对应的 rolebinding 和 clusterrolebinding
kubectl get rolebinding,clusterrolebinding --all-namespaces -o jsonpath='{range .items[?(@.subjects[0].name=="logshipper-sa")]}[{.roleRef.kind},{.roleRef.name}]{end}'
# 查看指定 clusterrole
kubectl describe ClusterRole logshipper-cluster-role
# 编辑 clusterrole 权限
kubectl edit ClusterRole logshipper-cluster-role
# 删除(触发自动拉起新的pod)
kubectl delete pod logshipper-597f84bf4f-6ssjq
# 获取服务状态
kubectl get pods -l app=logshipper --no-headers -o json | jq -r '.items[] | "\(.status.containerStatuses[0].ready)"'
【haproxy、docker】"Tarifa": Between Two Seas
题意
本机通过 docker compose 启动了一个 haproxy 和两个 nginx。因为 haproxy 的配置(haproxy.cfg)策略是对两个 nginx 节点进行轮询(roundrobin)访问,因此正常情况下,调用 localhost:5000 会依次返回 "nginx_0" 和 "nginx_1"
但目前调用只返回了 "nginx_0",需要找到原因
相关指令
# 调用 haproxy
curl localhost:5000
# 查看 haproxy pod 日志
docker logs c79c9eb25143
# 查看 haproxy 配置
vim haproxy.cfg
# 查看 nginx 配置
vim custom-nginx_0.conf
vim custom-nginx_1.conf
# 查看 docker compose 服务定义
vim docker-compose.yml
# 重启 docker-compose 服务组
docker compose down
docker compose up -d
【推荐】【sqlite】"Marrakech": Word Histogram
题意
frankestein.txt 文本中有一段文章,找到其中出现次数排第二多的单词
常见的单词分隔符为: .,:; ,且单词需忽略大小写
相关指令
这里通过 sqlite3 来解决本题
# 所有单词分隔后,转成全大写并按换行符分隔写入新文件
cat frankestein.txt | tr '.' '\n' | tr ',' '\n' | tr ':' '\n' | tr ';' '\n' | tr ' ' '\n' | tr '"' '\n' | sed -r '/^\s*$/d' | tr 'a-z' 'A-Z' > frankestein_new.txt
# 写入文件头(作为 sqlite 加载的列名)
sed -i '1iword' frankestein_new.txt
# sqlite: 导入文本到words表
.import frankestein_new.txt words
# sql: 出现次数排第二的单词
SELECT a.word, a.count_word FROM (SELECT word, count(word) AS count_word FROM words GROUP BY word) AS a ORDER BY a.count_word DESC LIMIT 1 OFFSET 1;
【mysql】"Rosario": Restore a MySQL database
本机启动了 mariadb ,需要在 main 数据库中执行 backup.sql 中的语句,但题目没有提供数据库登录密码
本题主要考察 mysql/mariadb 如何通过免密模式启动,参考 B.3.3.2 How to Reset the Root Password
相关指令
# 查看/修改 mariadb 启动配置
vim /usr/lib/systemd/system/mariadb.service
# 添加systemctl启动服务的环境变量
[Service]
Environment="MYSQLD_OPTS='--skip-grant-tables'"
# 重启 mariadb
systemctl daemon-reload
service mariadb restart
# 免密登录
mysql
# 修正 backup.sql 语法问题
sed -i 's#?$#;#g' /home/admin/backup.sql
# 执行 backup.sql
use main;
source /home/admin/backup.sql;
mysql -Dmain < /home/admin/backup.sql
【mysql】"Abaokoro": Restore MySQL Databases Spooked by a Ghost
题意
本机启动了 mariadb ,需要将 dbs_to_restore.zip 中的三个 sql 文件分别导入 first 、 second 和 third 三个数据库中
相关指令
# 检查磁盘占用
du -h --max-depth=1 /var
# 解压
tar -xzvf dbs_to_restore.tar.gz
# 导入三个数据库(注:导入时间较长)
mysql -e "create database first"
mysql -Dfirst < first.sql
mysql -e "create database second"
mysql -Dsecond < second.sql
mysql -e "create database third"
mysql -Dthird < third.sql
【k8s、helm】"Poznań": Helm Chart Issue in Kubernetes
本机通过 web_chart 目录定义的 nginx chart 启动了一个 nginx pod,期望是调用任何一个 nginx 的 80 端口都能返回 configmap.yaml 中定义的 "Welcome SadServers" 页面
但现在调用返回的是 nginx 默认的页面,configmap 配的页面并没有生效。需要使之生效,还需要将 nginx 的副本数修改为 3
本题主要考察 helm 的基本使用,以及 k8s configmap 如何注入到 pod 中,参考:
How do I attach a configmap to a deployment in Kubernetes?
# 获取 nginx pod 对应的 ip 并调用 nginx 服务
pod_ip=`kubectl get pods -n default -o jsonpath='{.items[0].status.podIP}'`
curl ${pod_ip}
# 查看 deployment
kubectl describe deployment web-chart-nginx
# 修改 chart values(包含副本数配置)
helm get values web-chart --all
# 重装 web-chart
helm uninstall web-chart
helm install web-chart web_chart/
# 或: 更新 chart
helm upgrade web-chart ./web_chart --values ./web_chart/values.yaml
修改后的 deployment:
(省略)
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 80
protocol: TCP
volumeMounts:
- mountPath: /usr/share/nginx/html/
name: nginx-html
volumes:
- name: nginx-html
configMap:
name: {{ .Release.Name }}-cm-index-html
items:
- key: index.html
path: index.html
【linux】"Manado": How much do you press?
题意
通过本机提供的 gzip、xz、lzip、tar 等工具,将 35147 字节大小的 names 文件压缩成不到 9400 字节,并将压缩文件放到 solution 目录下
可以在不删除任何行的前提下对文件进行修改
相关指令
# gzip 压缩和解压
gzip -v9 names
gunzip names.gz
# xz
xz names
xz -d names.xz
# lzip
tar --use-compress-program=lbzip2 -cvf names.tar.bz2 names
tar --use-compress-program=lbzip2 -xvf names.tar.bz2
# lz4
lz4 names
lz4 -d names.lz4
# lzip
lzip --best names
lzip -d names.lz
# lzop
lzop -9 names
lzop -d names.lz.lzo
# zstd
zstd -19 names
zstd -d names.zst
# 文件内容排序
sort names > names_new
【docker、golang】"Warsaw": Prometheus can't scrape the webserver
题意
app 目录下是一个通过 gorilla/mux 框架搭建的 web 服务,并通过 promhttp 暴露 /metrics 接口
但是目前直接调用 http://localhost:9000/metrics 接口将失败,需要解决
相关指令
# 调用 metrics 接口
curl http://localhost:9000/metrics
# 重启 docker compose 服务
docker compose down --volumes
docker compose up -d
## 重新构建镜像再启动
docker compose up --build -d
# 查看 app 的启动逻辑
vim app/main.go
["Moyogalpa": Security Snag. The Trials of Mary and John
题意
本地有一个通过 systemctl 启动的 web 服务,虽然它可以运行但访问其接口会失败,需要修复证书和权限相关的问题,以及防火墙 apparmor 的配置问题
相关指令
# 查看服务定义
## 一个二进制文件,指定了静态文件路径为 /home/webapp/static-files, 证书文件为 /home/webapp/pki/server.crt
cat /etc/systemd/system/webapp.service
# 重启 webapp
systemctl restart webapp
# 解决 webapp 无法 su 的问题: 替换 webapp 的登录 shell
vim /etc/passwd
# 手动启动 webapp
export APP_STATIC_DIR=/home/webapp/static-files
export APP_CERT=/home/webapp/pki/server.crt
export APP_KEY=/home/webapp/pki/server.pem
su webapp -c "/usr/local/bin/webapp"
# 修复证书权限问题
chown -R webapp:webapp /home/webapp/
# 添加 hosts
echo "127.0.0.1 webapp" >> /etc/hosts
# 检查指令
curl https://webapp:7000/users.html
# 修复 SSL certificate problem: unable to get local issuer certificate 的问题
## 参考: https://stackoverflow.com/questions/24611640/curl-60-ssl-certificate-problem-unable-to-get-local-issuer-certificate
cp /home/webapp/pki/CA.crt /usr/local/share/ca-certificates
chmod 644 /usr/local/share/ca-certificates/CA.crt
update-ca-certificates
# 解决 open /home/webapp/static-files/users.html: permission denied 的问题
## 在防火墙配置中,添加 /home/webapp/static-files/ r 和 /home/webapp/static-files/* r
vim /etc/apparmor.d/usr.local.bin.webapp
## 更新配置到内核中
apparmor_parser -r /etc/apparmor.d/usr.local.bin.webapp
【postgresql】"Helsingør": The first walls of postgres physical replication
题意
本地通过 docker compose 启动了 postgresql,agent/check.sh 也就是检查答案的脚本中,将会先确认备库和主库的同步是否正常。但是目前备库启动失败了,需要找到原因并解决
扩展: yaml 中复用内容的写法(&)
相关指令
# 查看节点状态
docker ps -a
# 查看日志
docker logs 98e1b8d4a341
# 重启 compose
docker compose down --volumes
docker compose up -d
## 强制重建
docker compose up -d --force-recreate
# 修改配置
vim postgres/replica/postgres.conf
## 批量修复配置
sed -i "s/max_connections = 80/max_connections = 100/g" postgres/replica/postgres.conf
sed -i "s/max_worker_processes = 4/max_worker_processes = 8/g" postgres/replica/postgres.conf
sed -i "s/max_wal_senders = 5/max_wal_senders = 10/g" postgres/replica/postgres.conf
sed -i "s/max_locks_per_transaction = 32/max_locks_per_transaction = 64/g" postgres/replica/postgres.conf
【WSGI】"Bekasi": Supervisor is still around
题意
本地通过 uwsgi 启动了一个 https 服务,但通过 curl -k https://bekasi 调用失败
本题思路是先确认 uwsgi 的部署方式,再确认如何修改它的部署配置,最后重启
相关指令
# 调用服务
curl -k https://bekasi
# 查看 uwsgi 进程
ps -ef | grep uwsgi
# 查看后台逻辑
cat bekasi/bekasi.py
## 修改 supervisor 启动 uwsgi 配置
vim /etc/supervisor/conf.d/uwsgi.conf
# 重载配置并重启服务
sudo supervisorctl reload
【nginx】"Depok": Nginx with Brotli
题意
brotli 是 是 Google 在 2015 年 9 月推出的一种压缩算法,使用通用的 LZ77 无损压缩算法、Huffman 编码和二阶上下文建模(2nd order context modelling)的特定组合,旨在进一步提高压缩比
部署在本地的 nginx 服务目前还不支持 brotli 压缩算法,需要通过 brotli 源码安装插件
本题主要考察 nginx 插件的安装方法
参考: CentOS 8 安装并加载 Nginx 模组 ngx_brotli 然后启用 brotli 压缩
相关指令
# 调用 nginx 并指定编码为 br
curl -H "Accept-Encoding: br" -I http://localhost
# 编译 brotli 插件
## 步骤参考官方仓库 https://github.com/google/ngx_brotli
cd /home/admin/ngx_brotli/deps/brotli
mkdir out
cd out
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_C_FLAGS="-Ofast -m64 -march=native -mtune=native -flto -funroll-loops -ffunction-sections -fdata-sections -Wl,--gc-sections" -DCMAKE_CXX_FLAGS="-Ofast -m64 -march=native -mtune=native -flto -funroll-loops -ffunction-sections -fdata-sections -Wl,--gc-sections" -DCMAKE_INSTALL_PREFIX=./installed ..
cmake --build . --config Release --target brotlienc
# 重新编译 nginx 并链接 brotli
cd /home/admin/nginx
./configure --with-compat --add-dynamic-module=../ngx_brotli
make modules
# 拷贝编译后产生的链接文件
cp objs/ngx_http_brotli_filter_module.so /usr/lib/nginx/modules/
cp objs/ngx_http_brotli_static_module.so /usr/lib/nginx/modules/
# 添加
## vim /etc/nginx/modules-available/50-ngx-http-brotli.conf
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
# 开启 brotli 插件
## vim /etc/nginx/sites-enabled/default
server {
brotli on;
}
# 重载 nginx 配置
sudo systemctl reload nginx
# 重启 nginx
sudo systemctl restart nginx
【linux】"Tukaani": XZ LZMA Library Compromised
题意
liblzma 是一个用于数据压缩的库,提供对 LZMA(Lempel-Ziv-Markov chain algorithm)和 XZ 压缩格式的支持
正常情况下本地服务需要加载的是 /usr/lib/x86_64-linux-gnu/liblzma.so.5.2.5,但通过 sudo lsof | grep liblzma.so.5 可以看到很多进程都加载了 /opt/.trash/liblzma.so.5,需要解决这个问题
本题主要考察预加载库的设置(LD_PRELOAD)
相关指令
# 查看 liblzma 相关进程
lsof | grep liblzma.so.5
# 删除无效库
rm /opt/.trash/liblzma.so.5
# 查看所有动态链接库之前预加载的库配置
vim /etc/ld.so.preload
# 重启节点
reboot
困难
提醒: 困难的题目似乎登录节点后默认在根目录,需要记得切换到 /home/admin 才能看到题目提供的文件
【linux】"Jakarta": it's always DNS.
题意
本机无法 ping 通 google.com,直接 ping 返回的是 Name or service not known,
本题是以前 sadserver 的实验题,所以虽然放在困难部分,但只要修复 /etc/nsswitch.conf 配置即可解决
参考-Linux系统解析域名的先后顺序files(/etc/hosts)OR dns(/etc/resolv.conf)
相关指令
# 查看并修复域名解析优先级配置
vim /etc/nsswitch.conf
sed -i "s/hosts:.*/hosts: files dns/g" /etc/nsswitch.conf
# 检查指令
ping google.com
【wordpress、docker】"Bern": Docker web container can't connect to db container.
题意
本地通过 docker 启动了 wordpress 和 mariadb 两个容器,mariadb 数据库状态正常,但 wordpress 服务无法访问 mariadb,需要解决这个问题
相关指令
# 查看 wordpress 接口返回结果
curl -s localhost:80 |tail -4
# 检查 wordpress 容器访问域名 "mysql" 的结果
docker exec wordpress mysqladmin -h mysql -u root -ppassword --connect-timeout 2 ping
# 查看 wordpress 容器的详细信息( 主要看环境变量 Env )
docker inspect 6ffb084b515c
# 查看 wordpress 容器中设置的连接数据库相关的环境变量
docker exec wordpress env | grep WORDPRESS_DB_
# 查看 wordpress 配置文件中,和连接数据库相关的环境变量
grep WORDPRESS_DB_ /home/admin/html/wp-config.php
# 删除容器
docker rm 6ffb084b515c
# 启动新的 设置了域名映射的 wordpress 容器
docker run -it -p 80:80 -e WORDPRESS_DB_PASSWORD=password -e WORDPRESS_DB_USER=root --add-host mysql:172.17.0.1 --name wordpress -d wordpress:sad
【推荐】【linux】"Karakorum": WTFIT – What The Fun Is This?
题意
/home/admin/wtfit 文件是一个需要连接某个地址才能启动的服务。但首先它因为没有可执行权限而无法执行,其次它实际执行也有报错,需要给它添加可执行权限,并解决运行文件使得其能够正常启动
相关指令
# chmod 添加可执行权限
chmod +x /home/admin/wtfit
# 通过 perl 脚本调用 chmod
perl -e 'chmod 0755, "/usr/bin/chmod"'
# 通过 ld-linux (动态链接器) 调用 chmod
/lib64/ld-linux-x86-64.so.2 /usr/bin/chmod +x /usr/bin/chmod
# 执行二进制文件,并跟踪进程堆栈
strace /home/admin/wtfit
# 通过 python 快速启动一个监听端口 7777 http 服务端
python3 -m http.server 7777 &
【kubernetes】"Singara": Docker and Kubernetes web app not working.
题意
本地通过 k3s 启动了一个轻量级 kubernetes 集群,并在 /home/admin/deployment.yml 中定义了 webapp 这个 web 服务,但是容器启动有问题,通过 curl -i localhost:8888 无法调通,需要解决
相关指令
# 获取所有 pod
kubectl get pod -A
# 获取 pod 的描述信息
kubectl describe pod webapp-deployment-666b67994b-5sffz -n web
# 启动 register 镜像仓库
docker run -d -p 5000:5000 registry:2
# 提交镜像 webapp 到本地镜像仓库
docker tag webapp localhost:5000/webapp
docker push localhost:5000/webapp
# 修改 deployment 并重新启动
kubectl delete deployment webapp-deployment -n web
vim /home/admin/deployment.yml
kubectl apply -f /home/admin/deployment.yml
# 开放端口
kubectl port-forward deployments/webapp-deployment 8888 -n web &
【postgresql】"Hong-Kong": can't write data into database.
题意
本地启动了 postgresql 但无法正常连接和插入数据,需要从 pg 的存储目录配置去分析原因
相关指令
# 检查指令: 向 pg 中插入一条数据
sudo -u postgres psql -c "insert into persons(name) values ('jane smith');" -d dt
# 手动启动 pg server 并确认报错
pg_ctlcluster 14 main restart -m fast
# 查看配置中的存储目录
cat /etc/postgresql/14/main/postgresql.conf | grep data_directory
# 创建数据目录并初始化
mkdir -p /opt/pgdata
chown -R postgres:postgres /opt/pgdata
su postgres -c "/usr/lib/postgresql/14/bin/initdb -D /opt/pgdata/main"
# 重启 pgsql
systemctl restart postgresql
# 从日志确认启动启动是否正常
tail -f /var/log/postgresql/postgresql-14-main.log
# 创建库表
sudo -u postgres psql -c "create database dt"
sudo -u postgres psql -c "CREATE TABLE persons(name varchar(100))" -d dt
【推荐】【linux】"Pokhara": SSH and other sshenanigans
题意
本机启动了 sshd,且 client 用户下已经设置了 ssh key , 期望是可以通过 ssh client@localhost 免密访问本机,但是目前会失败,找到账号和 ssh 相关的问题并解决
注: /home/client/.ssh/authorized_keys 中已经添加了公钥
本题涉及面很广,包括 ssh 配置、linux 用户配置、用户可用系统资源配置等,但整体都是围绕 su client 和 ssh client@localhost 失败来解决的,建议实际解题时,通过这两个指令执行的报错一步步解决
相关指令
# 切换 client 用户
su client
# 解决 "Your account has expired; please contact your system administrator." 问题
## 查看 client 用户的过期时间
chage -l client
## 设置 client 用户过期时间
chage -E 2024-12-31 client
# 解决 "su: failed to execute /usr/sbin/nologin: Resource temporarily unavailable" 问题
## 查看用户登录信息 (登录脚本)
lslogins client
# 或: cat /etc/passwd | grep client
# 设置用户登录 shell 为 /bin/bash
usermod --shell /bin/bash client
# 或: sed -i "s#/home/client:/usr/sbin/nologin#/home/client:/bin/bash#g" /etc/passwd
# 检查指令: ssh 到 client 用户并执行 pwd
ssh -v client@localhost "pwd"
## 解决 "su: failed to execute /usr/sbin/nologin: Resource temporarily unavailable" 问题
## https://access.redhat.com/solutions/30316
## 查看文件描述符配置 (需要修改)
vim /etc/security/limits.conf
# 解决 ssh client 问题
## https://askubuntu.com/a/343217
## root 用户下生成ssh密钥对,并把公钥写入 client 的 authorized_keys 中
ssh-keygen
cat ~/.ssh/id_rsa.pub >> /home/client/.ssh/authorized_keys
## 清空 known_hosts
echo "" > /home/client/.ssh/known_hosts
# 修正私钥文件权限
chmod 600 /home/client/.ssh/*
# 删除 ssh 错误配置并重启 sshd
rm /etc/ssh/sshd_config.d/sad.conf
systemctl restart sshd
# 最后,切回 admin 用户并执行 ssh,使得 known hosts 被刷新
sudo -u client ssh client@localhost 'pwd'
【linux】"Roseau": Hack a Web Server
题意
在本机的 apache web 服务器上存有一个密码文件,通过 zip 方式加密压缩。这个压缩文件通过 admin 用户无法直接访问,需要调用 apache 接口拿到,然而 apache 接口也是需要密码(配置: AuthUserFile)。因此需要通过密码破解工具 john 分别破解 apache 的密码 以及 压缩文件
本题主要是介绍暴力破解工具 john 的使用。它的原理简单来说是通过 预先准备的词典、哈希字典,对特定加密方式进行特定的暴力破解
相关指令
# 查看 httpd 配置,包括根路径和认证方式
cat /etc/apache2/sites-enabled/000-default.conf
# 查看 apache 根路径下的压缩文件
ls -l /var/www/html
# 破解 httpd 密码
/home/admin/john/run/john /etc/apache2/.htpasswd
# 下载 webfile 文件
curl localhost/webfile -u "carlos:(这里替换成httpd密码)" --output secret
# 解密 secret (格式: zip) 密码
/home/admin/john/run/zip2john secret > zip.hash
/home/admin/john/run/john zip.hash
【java、linux】"Belo-Horizonte": A Java Enigma
题意
Sad.class 是一段 java 程序,成功执行后,会打印一段题目需要的密码,但它目前是无法执行的,需要通过反编译等方法解决
相关指令
# 运行 class
java Sad.class
# 查看 /usr/bin/java 指向的实际的文件
readlink -f /usr/bin/java
# 查看本机安装的其他版本 java
ls -l /usr/lib/jvm | grep java
# 将 /usr/bin/java 指向新的地址
sudo rm /usr/bin/java
sudo ln -s /usr/lib/jvm/java-1.17.0-openjdk-amd64/bin/java /usr/bin/java
# 反编译 Sad.class
javap -c -l Sad.class
# 重命名
mv Sad.class VerySad.class
# 查看本地内存(总剩余内存只有 200m 左右,不足以运行进程)
free -m
# 查看本地磁盘(可以看到至少有4G空间)
df -h
# 以本地磁盘申请交换空间
## 申请1G空间
sudo fallocate -l 1G /swapfile
## 设置文件权限为600
sudo chmod 600 /swapfile
## 将文件设置为交换空间
sudo mkswap /swapfile
## 启用交换文件
sudo swapon /swapfile
## 查看交换空间
sudo swapon --show
## 再次查看内存
free -m
【rabbitmq、docker】"Chennai": Pull a Rabbit from a Hat
题意
在本地的 rabbitmq-cluster-docker-master 目录下,提供了启动 rabbitmq 三节点集群的 compose 仓库(参考这个 serkodev/rabbitmq-cluster-docker ),需要正常启动集群,并先后运行 python3 ~/producer.py hello-lwc
和 python3 ~/consumer.py
,完成一个完整的生产和消费过程
注意: 虽然 consumer.py 和 producer.py 中确实有一些连接参数不太一致,但题目要求不能修改这两个代码文件,否则校验答案会失败,因此只能通过设置环境变量的方式来解决
相关指令
# 重启 rabbitmq 集群
docker compose down --volumes
docker compose up -d
# 修改启动脚本最后一段,tail 之前创建一个空日志文件,解决 tail 执行失败导致卡住的问题
touch /var/log/rabbitmq/empty.log
vim cluster-entrypoint.sh
# 查看 .env 文件( compose 默认使用的环境变量 ),并将其修改为 consumer.py 中写死的用户名和密码
cat ./.env
export RABBITMQ_DEFAULT_USER=username
export RABBITMQ_DEFAULT_PASS=password
# 设置环境变量并触发生产者
export RMQ_QUEUE=hello
export RMQ_USER=username
export RMQ_PASSWORD=password
python3 ~/producer.py hello-lwc
# 触发消费者
python3 ~/consumer.py
【git、linux】"Monaco": Disappearing Trick
题意
本地 5000 端口上启动了一个 web 服务,通过 post 方法指定密码调用它的接口后,会返回一个密钥,需要把这个密钥写入 mysolution 文件中
根据提示,当前路径是一个代码工作路径,“那么”(虽然感觉这个推理有点牵强)可以推测是一个 git 路径,但是本地并没有代码,可以通过 git 指令恢复 webserver 的代码,确认其需要的密码是在哪个环境变量中定义的,并在已启动的 webserver 进程中,找到对应的环境变量
相关指令
# 恢复 git 项目(即:重新拉取代码)
## 显示目前文件差异
git status
## 恢复指定文件
git restore webserver_v1.py
## 或: 恢复当前路径下的所有文件
git restore .
# 查看 webserver 逻辑
cat webserver_v1.py
# 查找 webserver_v1.py 配置的密码相关的环境变量,在已启动的 webserver 服务中定义的值
ps -ef | grep webserver.py | grep -v grep | awk '{print $2}' | xargs -I {} bash -c "cat /proc/{}/environ" | tr '\000' '\n' | grep "(环境变量名)"
# 访问 webserver 并指定密码,并将得到的 secret 写入 mysolution 文件中
secret=`curl -X POST localhost:5000 --data-raw "password=(上一步得到的密码)" | sed 's/.* //g'`
echo "$secret" > ~/mysolution
【docker】"Florence": Database Migration Hell
题意
/home/admin/app/docker-compose.yml 中定义了一个 nginx + nodejs + postgresql 的后台服务,其中 pgsql 作为数据库;api 服务进行数据库访问用户的初始化,以及提供接口;api_aggregator 对 pgsql 进行访问权限验证;nginx 服务提供代理和 https 接口。正常的启动和初始化顺序为: pgsql -> d
现在这些服务的启动和访问有问题,需要解决
相关指令
# 查看 compose 定义
cat ~/app/docker-compose.yml
# 查看并修改 postgresql Dockerfile,以解决 postgresql 容器日志中的 "initdb: cannot be run as root" 问题
vim ~/app/postgresql/Dockerfile
# 重启容器并重新构建镜像
docker compose down --volumes
docker compose up --build -d
# 添加 解决 api_aggregator 连接 pg 失败的问题
## 第14行添加内容
sed -i "14iecho \"host all api_aggregator 0.0.0.0/0 md5\" >> /var/lib/postgresql/data/pg_hba.conf" ~/app/postgresql/entrypoint.sh
# 解决 api_aggregator 容器日志提示连接 pgsql 失败问题
## 查看 app 服务启动时对 pgsql 的 migrations 逻辑(go-pg/migrations.NewCollection.DiscoverSQLMigrations
cat ~/app/api/pkg/db/db.go
## 查看 api_aggregator 服务所用的账号和密码
cat ~/app/docker-compose.yml | grep DATABASE_URL
## 计算密码的 md5 值
## 注意计算方式: 密码和用户名一起计算,参考源码 https://github.com/postgres/postgres/blob/REL_17_STABLE/src/interfaces/libpq/fe-auth.c#L732
echo -n (上一步得到的密码)(上一步得到的用户名) | md5sum | awk '{print $1}'
## 创建 api_aggregator 访问需要的用户
sed -i "s/CREATE ROLE api_aggregator .*/CREATE ROLE api_aggregator LOGIN PASSWORD '(上一步计算得到的md5值)';/g" ~/app/api/migrations/1_users.up.sql
## api 服务中添加适当 sleep ,防止执行 migration 时 pgsql 还未启动导致报错
sed -i "10i\"time\"" ~/app/api/server.go
sed -i "14itime.Sleep(10 * time.Second)" ~/app/api/server.go
## api_aggregator 服务中添加适当 sleep,原因同上
sed -i "s#CMD node.*#CMD sleep 10; node /usr/src/app/index.js#g" ~/app/api_aggregator/Dockerfile
## 注: 截止到这一步,重启容器后4个容器应该状态都是正常的了
# 将 sadserver.local 域名映射到本地(for nginx)
echo "127.0.0.1 sadserver.local" >> /etc/hosts
# 重新生成证书,解决证书过期问题: curl: (60) SSL certificate problem: certificate has expired
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/certs/sadserver.key -out /etc/nginx/certs/sadserver.crt -subj "/C=CN/ST=SZ/L=NS/O=Internet/OU=Company/CN=sadserver.local"
# 重启 nginx
systemctl restart nginx
# 验证接口访问
curl --cacert /etc/nginx/certs/sadserver.crt https://sadserver.local
总结
不是所有 linux 指令都能经常用到,但对一个专业的开(yun)发(wei)来说,总会有用到它们的时候