本文 cnblog 博客地址

背景

阮一峰老师的博客 了解到 sadserver 这样一个可以提供 linux 服务器,并尝试解决系统和服务相关问题的在线测试平台。非常难得的是它可以直接提供一个公网的linux服务器(一般40-60分钟后会自动销毁),你可以在上面做任何探索。对于想要学习常用 linux 指令的同学,是一个非常不错的练手平台

sadserver 大部分题目都是免费的,46道题中11道需要会员,会员一个月5美元。可以在完成其他题目之后再小小氪💰一下挑战剩下的题目

本文整理了目前sadserver所有题目的分类和解题相关指令,带部分“剧透”,建议对题目本身有强烈兴趣的同学直接动手尝试

*推荐: 标注推荐的主要是和 linux 常用指令相关的题目,其他开源服务相关的题目可以选择性了解

*提醒: 部分题目需要root用户权限,以执行系统配置修改等相关指令,可通过sudo su指令提权

题目分类

题目 类型/相关服务 题目/问题概述 相关指令
“Saint John”: what is writing to this log file? linux 查找文件占用的进程 lsof, find
“Saskatoon”: counting IPs. (推荐) linux 字符串计数 awk, uniq, sort
“Santiago”: Find the secret combination (推荐) linux 文本内容过滤 grep
“Taipei”: Come a-knocking (推荐) linux knock 端口开放 knocked
“Lhasa”: Easy Math (推荐) linux 数值计算 bc
“Bucharest”: Connecting to Postgres postgresql pgsql配置修复
“Bilbao”: Basic Kubernetes Problems kubernetes 服务配置修复 kubectl
“Apia”: Needle in a Haystack linux 文本比较 diff
“Manhattan”: can’t write data into database. postgresql pgsql 配置修复
“Tokyo”: can’t serve web file (推荐) httpd, linux httpd 配置修复 iptables
“Cape Town”: Borked Nginx nginx nginx 配置修复 systemctl
“Salta”: Docker container won’t start Docker 容器内服务启动问题 docker, netstat
“Oaxaca”: Close an Open File linux 查找文件占用的进程 lsof, exec
“Melbourne”: WSGI with Gunicorn gunicorn gunicorn 配置修复
“Lisbon”: etcd SSL cert troubles etcd 访问不通 etcd, iptables
“Kihei”: Surely Not Another Disk Space Scenario (推荐) linux 挂盘 pvcreate, vgcreate, lvcreate, mount
“Unimak Island”: Fun with Mr Jason linux 文本解析 jq
“Ivujivik”: Parlez-vous Français? python excel 解析 python
“Paris”: Where is my webserver? linux http 接口调用和参数 curl
“Buenos Aires”: Kubernetes Pod Crashing kubernetes kubernetes 权限体系 kubectl
“Tarifa”: Between Two Seas nginx nginx 配置修复 docker
“Marrakech”: Word Histogram (推荐) sqlite 文本解析 sqlite
“Rosario”: Restore a MySQL database mysql mysql 故障恢复 mysql
“Abaokoro”: Restore MySQL Databases Spooked by a Ghost mysql mysql 数据导入 mysql
“Poznań”: Helm Chart Issue in Kubernetes kubernetes helm 基本使用 helm
“Manado”: How much do you press? linux 解压缩常用指令的使用 zip, tar, xz
“Warsaw”: Prometheus can’t scrape the webserver linux go 服务问题修复 docker
“Moyogalpa”: Security Snag. The Trials of Mary and John linux 服务文件权限和防火墙设置 apparmor, systemctl, update-ca-certificates
“Helsingør”: The first walls of postgres physical replication postgresql postgresql 同步主库相关配置修复 docker
“Bekasi”: Supervisor is still around wsgi wsgi 启动配置修复 curl, supervisorctl
“Depok”: Nginx with Brotli nginx nginx 插件安装 cmake, make, systemctl
“Tukaani”: XZ LZMA Library Compromised linux preload 库问题修复 lsof
“Jakarta”: it’s always DNS. linux DNS 解析配置修复
“Bern”: Docker web container can’t connect to db container. wordpress 通过 docker 启动 wordpress docker
“Karakorum”: WTFIT – What The Fun Is This? (推荐) linux 定位系统和进程启动问题 perl, strace, python
“Singara”: Docker and Kubernetes web app not working. kubernetes 镜像重建和提交 docker, kubectl
“Hong-Kong”: can’t write data into database. postgresql postgresql 配置问题修复 postgres, systemctl
“Pokhara”: SSH and other sshenanigans (推荐) linux ssh 和 su 问题解决 ssh, su, chage, ssh-keygen
“Roseau”: Hack a Web Server linux john 暴力破解工具的使用 john
“Belo-Horizonte”: A Java Enigma linux java 程序执行问题解决 java, javap, free, fallocate, mkswap, swapon
“Chennai”: Pull a Rabbit from a Hat rabbitmq rabbitmq 测试生产消费 docker, python
“Monaco”: Disappearing Trick linux git和进程环境变量 git
“Florence”: Database Migration Hell 微服务 js+web后台+pgsql问题解决 docker, sed

如何解题

打开题目

题目示例

检查成功结果

Level: 简单

【linux】“Saint John”: what is writing to this log file?

题目

题意

服务器上有一个日志文件在被一个进程占用,需要找到这个进程并停止

相关指令

1
2
3
4
5
6
7
# 查看文件占用进程 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

相关指令

1
2
3
4
5
6
7
8
9
10
# 截取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

题目

题意

统计特定字符串在每个文件中的出现次数,并导出只出现一次的文件,出现位置的下一行内容

相关指令

1
2
3
4
5
6
# 查找文件内容 & 统计数量
## 注意: 题目要求是字符串出现次数,如果是统计词语可在 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 端口放开

参考-knock:端口敲门服务

相关指令

1
2
3
4
5
6
7
8
9
10
11
# 对 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

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 获取第二列
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
1
2
3
4
5
6
7
8
9
10
11
12
13
# 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))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 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 配置并重启

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 指定密码、用户和 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 的启动信息去解决

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 检查指令: 
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 文件,其中一个和其他文件内容不一样,而且只是在一个段落中多了一个单词,找到这个单词

相关指令

1
2
3
4
5
6
7
8
# 输出文本的 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 会失败,找到原因并解决

相关指令

1
2
3
4
5
# 查看 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 没有任何返回,确认具体原因

相关指令

1
2
3
4
5
6
7
8
9
10
11
# 查看防火墙规则列表
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 但无法启动,检查启动失败原因,修复并启动

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 重启 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 中
需要构建镜像后,启动该服务,并解决调用该服务接口遇到的问题

相关指令

1
2
3
4
5
6
7
8
9
10
11
# 构建镜像
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 指令

相关指令

1
2
3
4
5
# 找到占用文件的进程 ,并直接 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

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查看 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 foocurl -i https://localhost:2379/v2/keys/foo 获取配置,但目前访问失败,需要解决

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 通过 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缩容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 查看 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)写入答案文件中

相关指令

虽然题目提示本机有 jqgronjid 等工具,但后两者操作起来都不如同时包含解析和筛选器的 jq 方便,这里直接展示 jq 的几个基本指令,组合几个 filter ,就可以解答本题了

1
2
3
4
5
# 获取数组长度
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 的解答方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 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()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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 端口,拿到答案需要的密码

相关指令

1
2
3
4
# 添加 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 对应的权限类型

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 查看所有 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”,需要找到原因

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 调用 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 来解决本题

1
2
3
4
5
6
7
8
9
10
11
# 所有单词分隔后,转成全大写并按换行符分隔写入新文件
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

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 查看/修改 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 三个数据库中

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 检查磁盘占用
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?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 获取 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(省略)
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 目录下

可以在不删除任何行的前提下对文件进行修改

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 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 接口将失败,需要解决

相关指令

1
2
3
4
5
6
7
8
9
10
11
# 调用 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 的配置问题

apparmor 的使用方式参考

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 查看服务定义
## 一个二进制文件,指定了静态文件路径为 /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 中复用内容的写法(&)

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 查看节点状态
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 的部署方式,再确认如何修改它的部署配置,最后重启

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 调用服务
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 压缩

How to use nginx modules

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 调用 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)

相关指令

1
2
3
4
5
6
7
8
9
10
11
# 查看 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)

相关指令

1
2
3
4
5
6
# 查看并修复域名解析优先级配置
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,需要解决这个问题

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 查看 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 文件是一个需要连接某个地址才能启动的服务。但首先它因为没有可执行权限而无法执行,其次它实际执行也有报错,需要给它添加可执行权限,并解决运行文件使得其能够正常启动

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 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 无法调通,需要解决

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 获取所有 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 的存储目录配置去分析原因

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 检查指令: 向 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 失败来解决的,建议实际解题时,通过这两个指令执行的报错一步步解决

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 切换 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 的使用。它的原理简单来说是通过 预先准备的词典、哈希字典,对特定加密方式进行特定的暴力破解

参考-使用John the ripper破解密码

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看 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 程序,成功执行后,会打印一段题目需要的密码,但它目前是无法执行的,需要通过反编译等方法解决

参考: 使用fallocate命令创建swap分区

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 运行 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-lwcpython3 ~/consumer.py,完成一个完整的生产和消费过程

注意: 虽然 consumer.py 和 producer.py 中确实有一些连接参数不太一致,但题目要求不能修改这两个代码文件,否则校验答案会失败,因此只能通过设置环境变量的方式来解决

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 重启 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 进程中,找到对应的环境变量

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 恢复 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

现在这些服务的启动和访问有问题,需要解决

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 查看 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

总结

最近看了IT狂人这部剧,刚好想到,IT人平时的工作可能很杂,这里搞一点开发,那里做一点运维,甚至还没有程序那样有逻辑,但从外面来看,IT人确实是在做很多事情的,只是他们好像在一个锁上的礼物盒那样,不打开就不知道里面有什么

这部剧,和 sadserver 这个解题平台,都像是打开这个盒子的钥匙