Ali-DDNS
借助阿里云 openapi 实现的 DDNS 服务
简介
借助阿里云 openapi 实现的 DDNS 服务
细节
# Architecture
- Server: Obtain domain records from Ali and receive domain name change requests from clients
- Client: Gets the domain record from the server and requests to change the domain record
+---+ Get Domain Record +---+
| +------------------------>| |
+------------+ | G |<------------------------+ G | +------------+ +---------+
| Client API | | R | Return Domain Record | R | | Server API +--->| Ali API |<------+
+------------+ | P | | P | +------+-----+ +---------+ |
| C +------------------------>| C | | +-------+ +----+----+
| | Update Domain Record | | +--------->| Redis +--->| CronJob |
+---+ +---+ +-------+ +---------+
^
+---+ +---+ +---+ |
+------------+ | H | register/login/logout | H | grpc-gateway | G | +--+---------+
| Web | | T |<----------------------->| T |<-------------->| R | | Interface |
+------------+ | T | add/del Domain Name | T | | P | | API |
| P | | P | | C | +------------+
+---+ +---+ +---+
如何使用
在客户端和服务端克隆项目:
git clone --depth=1 https://github.com/hominsu/Ali-DDNS.git
客户端
和服务端
之间的连接是通过tls
进行身份验证的,因此必须首先创建相关的证书,这里使用 openssl 生成san
证书创建 CA 证书
❯ openssl genrsa -out ca.key 4096 Generating RSA private key, 4096 bit long modulus (2 primes) ..................................................................++++ .........................++++ e is 65537 (0x010001) ❯ openssl req -new -x509 -days 3650 -key ca.key -out ca.crt You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:CN State or Province Name (full name) [Some-State]:Guangdong Locality Name (eg, city) []:Foshan Organization Name (eg, company) [Internet Widgits Pty Ltd]:hominsu Organizational Unit Name (eg, section) []:hominsu Common Name (e.g. server FQDN or YOUR name) []:localhost Email Address []:[email protected]
准备
openssl
配置文件拷贝
openssl
的默认配置文件到当前目录
linux
:cp /etc/pki/tls/openssl.cnf .
macos
:cp /System/Library/OpenSSL/openssl.cnf .
修改
openssl.cnf
中的以下选项找到
[CA_default]
并取消copy_extensions = copy
注释:[ CA_default ] # Extension copying option: use with caution. copy_extensions = copy
找到
[ req ]
并取消req_extensions = v3_req
的注释:[ req ] req_extensions = v3_req # The extensions to add to a certificate request
找到
[ v3_req ]
然后添加subjectAltName = @alt_names
, 然后添加标签[ alt_names ]
和对应的字段:[ v3_req ] # Extensions to add to a certificate request basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment subjectAltName = @alt_names [ alt_names ] DNS.1 = localhost DNS.2 = *.example.com
生成服务端证书
❯ openssl genpkey -algorithm RSA -out server.key .....................................................................................................+++++ ..........+++++ ❯ openssl req -new -nodes -key server.key -out server.csr -days 3650 -config ./openssl.cnf -extensions v3_req Ignoring -days; not generating a certificate You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:CN State or Province Name (full name) [Some-State]:Guangdong Locality Name (eg, city) []:Foshan Organization Name (eg, company) [Internet Widgits Pty Ltd]:hominsu Organizational Unit Name (eg, section) []:hominsu Common Name (e.g. server FQDN or YOUR name) []:localhost Email Address []:[email protected] Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []:your_password An optional company name []:hominsu ❯ openssl x509 -req -days 3650 -in server.csr -out server.pem -CA ca.crt -CAkey ca.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req Signature ok subject=C = CN, ST = Guangdong, L = Foshan, O = hominsu, OU = hominsu, CN = localhost, emailAddress = [email protected] Getting CA Private Key
生成客户端证书
❯ openssl genpkey -algorithm RSA -out client.key ..................................................................................+++++ ..............................................+++++ ❯ openssl req -new -nodes -key client.key -out client.csr -days 3650 -config ./openssl.cnf -extensions v3_req Ignoring -days; not generating a certificate You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:CN State or Province Name (full name) [Some-State]:Guangdong Locality Name (eg, city) []:Foshan Organization Name (eg, company) [Internet Widgits Pty Ltd]:hominsu Organizational Unit Name (eg, section) []:hominsu Common Name (e.g. server FQDN or YOUR name) []:localhost Email Address []:[email protected] Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []:your_password An optional company name []:hominsu ❯ openssl x509 -req -days 3650 -in client.csr -out client.pem -CA ca.crt -CAkey ca.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req Signature ok subject=C = CN, ST = Guangdong, L = Foshan, O = hominsu, OU = hominsu, CN = localhost, emailAddress = [email protected] Getting CA Private Key
全部文件如下所示:
❯ tree . ├── ca.crt ├── ca.key ├── ca.srl ├── client.csr ├── client.key ├── client.pem ├── openssl.cnf ├── server.csr ├── server.key └── server.pem 0 directories, 10 files
在服务端
ca.crt
、server.pem
、server.key
会被用作服务端连接的鉴权,grpc-gateway
会使用ca.crt
、client.pem
、client.key
去设置grpc-gateway
和grpc server
之间的连接的认证在客户端,如果你要将服务端提供给别人使员工,为了安全考虑, 你应该为每个客户端使用根证书(
ca.crt
andca.key
)生成单独的证书,或者你可以直接使用和gateway
相同的证书(不推荐)然后创建一个cert目录,并将证书复制到其中
在服务端,文件结构如下所示:
❯ tree cert cert ├── ca.crt ├── client.key ├── client.pem ├── server.key └── server.pem 0 directories, 5 files
在客户端,文件结构如下所示:
❯ tree cert cert ├── ca.crt ├── client.key └── client.pem 0 directories, 3 files
在服务端,你需要在
docker-compose.yml
中填写你的 Ali Access-Key,设置你的 redis 密码。接口使用 jwt 进行身份验证,你要确保设置了 jwt 令牌(例如 "www.hauhau.cn")。运营商通常在凌晨更新公网 IP,所以你可以使用Cron表达式(例如:CRON_TZ=Asia/Shanghai 1/10 2-4 * * *
)来指定它在清晨以更高的频率更新,而在其他时间以较慢的速度更新(比如每小时更新一次)。你当然可以定义自己的时间。redis-ddns: image: redis:alpine container_name: ali-ddns-redis-ddns # 设置 Redis 的密码,下面记得主服务中填写对应的密码 command: redis-server --port 6380 --requirepass redis-ddns-password ali-ddns-server-service: # 阿里云仓库: registry.cn-shenzhen.aliyuncs.com/hominsu/ali-ddns-server-service:latest # GHCR: ghcr.io/hominsu/ali-ddns-server-service:latest # DockerHub: hominsu/ali-ddns-server-service:latest image: hominsu/ali-ddns-server-service:latest container_name: ali-ddns-server-service # build: # context: . # dockerfile: ./Dockerfile depends_on: - redis-ddns restart: always environment: # 设置时区,不然 logs 的时间不对 TZ: "Asia/Shanghai" # 时区 # 设置阿里云的 AK,建议使用 RAM 用户,只分配 AliyunDNSFullAccess 权限 ALIDDNSSERVER_ACCESSKEY_ID: "*" # 阿里云 AK ID ALIDDNSSERVER_ACCESSKEY_SECRET: "*" # 阿里云 AK SECRET ALIDDNSSERVER_BASIC_ENDPOINT: "alidns.cn-shenzhen.aliyuncs.com" # 阿里云服务地址 ALIDDNSSERVER_BASIC_INTERFACE_PORT: "50001" # WEB 服务监听端口 ALIDDNSSERVER_BASIC_DOMAIN_GRPC_NETWORK: "tcp" # RPC 协议 ALIDDNSSERVER_BASIC_DOMAIN_GRPC_PORT: "50002" # RPC 服务端口 ALIDDNSSERVER_BASIC_INTERFACE_GRPC_NETWORK: "tcp" # RPC 协议 ALIDDNSSERVER_BASIC_INTERFACE_GRPC_PORT: "50003" # RPC 服务端口 # 保存域名相关信息的 Redis,要改的只有密码,和上面设置的密码相同 ALIDDNSSERVER_DOMAIN_RECORD_REDIS_ADDR: "redis-ddns" # 保存域名信息的 Redis 地址 ALIDDNSSERVER_DOMAIN_RECORD_REDIS_PORT: "6380" # 保存域名信息的 Redis 端口 ALIDDNSSERVER_DOMAIN_RECORD_REDIS_PASSWORD: "redis-ddns-password" # 保存域名信息的 Redis 密码 ALIDDNSSERVER_DOMAIN_RECORD_REDIS_DB: "1" # 保存域名信息的 Redis 数据库 ALIDDNSCLIENT_OPTION_JWT_TOKEN: "jwt_token" # jwt token ALIDDNSCLIENT_OPTION_TTL: "3600" # 每隔多少秒向服务端获取更新信息 ALIDDNSCLIENT_OPTION_DELAY_CHECK_CRON: "CRON_TZ=Asia/Shanghai 1/10 2-4 * * *" # 每天的 2-4 点的 1m 开始,每 10m 执行一次
启动服务端
cd Ali-DDNS/deploy/docker-compose/ddns-server docker-compose up -d
启动客户端
cd Ali-DDNS/deploy/docker-compose/ddns-client docker-compose up -d
设置服务端
注册用户
通过
cURL
发送请求:curl --location --request POST 'http://127.0.0.1:50001/v1/register' \ --header 'Content-Type: application/json' \ --data-raw '{ "username": "admin", "password": "passwd" }'
或者使用
wget
:wget --no-check-certificate --quiet \ --method POST \ --timeout=0 \ --header 'Content-Type: application/json' \ --body-data '{ "username": "admin", "password": "passwd" }' \ 'http://127.0.0.1:50001/v1/register'
如果请求成功,你会得到以下输出:
{"status":true}
登陆并获取 token
cURL
:curl --location --request POST 'http://127.0.0.1:50001/v1/login' \ --header 'Content-Type: application/json' \ --data-raw '{ "username": "admin", "password": "passwd" }'
wget
:wget --no-check-certificate --quiet \ --method POST \ --timeout=0 \ --header 'Content-Type: application/json' \ --body-data '{ "username": "admin", "password": "passwd" }' \ 'http://127.0.0.1:50001/v1/login'
如果请求成功,您将获得以下带有
token
和username
的输出{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VybmFtZSI6ImFkbWluIiwiaXNzIjoiMTI3LjAuMC4xIiwic3ViIjoidXNlciB0b2tlbiIsImV4cCI6MTY0MzEwNTY3MSwiaWF0IjoxNjQzMTAyMDcxfQ.EmYB_PApYocKSbdyT0ykUMPMJErMykv3AASBcYngJTQ", "username":"admin"}
添加你需要监控的域名, 注意在 url (
/v1/{username}/domain_name
) 中的{username}
需要改成你自己的用户名 (例如admin
),token
在上一步中获得cURL
:curl --location --request POST 'http://127.0.0.1:50001/v1/admin/domain_name' \ --header 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VybmFtZSI6ImFkbWluIiwiaXNzIjoiMTI3LjAuMC4xIiwic3ViIjoidXNlciB0b2tlbiIsImV4cCI6MTY0MzEwNTY3MSwiaWF0IjoxNjQzMTAyMDcxfQ.EmYB_PApYocKSbdyT0ykUMPMJErMykv3AASBcYngJTQ' \ --header 'Content-Type: application/json' \ --data-raw '{ "domain_name": "haomingsu.cn" }'
wget
:wget --no-check-certificate --quiet \ --method POST \ --timeout=0 \ --header 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VybmFtZSI6ImFkbWluIiwiaXNzIjoiMTI3LjAuMC4xIiwic3ViIjoidXNlciB0b2tlbiIsImV4cCI6MTY0MzEwNTY3MSwiaWF0IjoxNjQzMTAyMDcxfQ.EmYB_PApYocKSbdyT0ykUMPMJErMykv3AASBcYngJTQ' \ --header 'Content-Type: application/json' \ --body-data '{ "domain_name": "haomingsu.cn" }' \ 'http://127.0.0.1:50001/v1/admin/domain_name'
如果请求成功,你会得到以下输出:
{"status":true, "domainName":"haomingsu.cn"}
检查域名是否添加成功, 注意在 url (
/v1/{username}/domain_name
) 中的{username}
需要改成你自己的用户名 (例如admin
),token
在前两步中获得cURL
:curl --location --request GET 'http://127.0.0.1:50001/v1/admin/domain_name' \ --header 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VybmFtZSI6ImFkbWluIiwiaXNzIjoiMTI3LjAuMC4xIiwic3ViIjoidXNlciB0b2tlbiIsImV4cCI6MTY0MzEwNTY3MSwiaWF0IjoxNjQzMTAyMDcxfQ.EmYB_PApYocKSbdyT0ykUMPMJErMykv3AASBcYngJTQ'
wget
:wget --no-check-certificate --quiet \ --method GET \ --timeout=0 \ --header 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VybmFtZSI6ImFkbWluIiwiaXNzIjoiMTI3LjAuMC4xIiwic3ViIjoidXNlciB0b2tlbiIsImV4cCI6MTY0MzEwNTY3MSwiaWF0IjoxNjQzMTAyMDcxfQ.EmYB_PApYocKSbdyT0ykUMPMJErMykv3AASBcYngJTQ' \ 'http://127.0.0.1:50001/v1/admin/domain_name'
如果请求成功,你会得到以下输出:
{"domainNames":["haomingsu.cn"]}
容器镜像仓库
Docker Hub:
docker pull hominsu/ali-ddns-client-service:latest docker pull hominsu/ali-ddns-server-service:latest
GitHub Container Repository:
docker pull ghcr.io/hominsu/ali-ddns-client-service:latest docker pull ghcr.io/hominsu/ali-ddns-server-service:latest
Ali Container Repository:
registry.cn-shenzhen.aliyuncs.com/hominsu/ali-ddns-client:latest
docker pull registry.cn-shenzhen.aliyuncs.com/hominsu/ali-ddns-client-service:latest docker pull registry.cn-shenzhen.aliyuncs.com/hominsu/ali-ddns-server-service:latest
其他
在服务端,日志会保存在 ads/logs/ads.log
中
[root@iZwz9diii276grug5qq3byZ ddns-server-service]# cat ads/logs/ads.log
2022-01-26T13:59:54.423+0800 info runtime/proc.go:255 service.id: e4fd4c9652af, service.name: ali-ddns-server-service, service.version: 1.2.5, git sha1: a3936d5d8b6044bbbed686d6b2222c2c5813fa39, build stamp: 1643173896
2022-01-26T13:59:54.428+0800 info grpclog/grpclog.go:37 [core]original dial target is: "localhost:50003" {"system": "grpc", "grpc_log": true}
2022-01-26T13:59:54.429+0800 info grpclog/grpclog.go:37 [core]parsed dial target is: {Scheme:localhost Authority: Endpoint:50003 URL:{Scheme:localhost Opaque:50003 User: Host: Path: RawPath: ForceQuery:false RawQuery: Fragment: RawFragment:}} {"system": "grpc", "grpc_log": true}
2022-01-26T13:59:54.429+0800 info grpclog/grpclog.go:37 [core]fallback to scheme "passthrough" {"system": "grpc", "grpc_log": true}
2022-01-26T13:59:54.429+0800 info grpclog/grpclog.go:37 [core]parsed dial target is: {Scheme:passthrough Authority: Endpoint:localhost:50003 URL:{Scheme:passthrough Opaque: User: Host: Path:/localhost:50003 RawPath: ForceQuery:false RawQuery: Fragment: RawFragment:}} {"system": "grpc", "grpc_log": true}
2022-01-26T13:59:54.429+0800 info grpclog/grpclog.go:37 [core]Channel authority set to "localhost" {"system": "grpc", "grpc_log": true}
2022-01-26T13:59:54.429+0800 info grpclog/grpclog.go:37 [core]ccResolverWrapper: sending update to cc: {[{localhost:50003 <nil> <nil> 0 <nil>}] <nil> <nil>} {"system": "grpc", "grpc_log": true}
2022-01-26T13:59:54.429+0800 info grpclog/grpclog.go:37 [core]ClientConn switching balancer to "pick_first" {"system": "grpc", "grpc_log": true}
2022-01-26T13:59:54.429+0800 info grpclog/grpclog.go:37 [core]Channel switches to new LB policy "pick_first" {"system": "grpc", "grpc_log": true}
2022-01-26T13:59:54.429+0800 info grpclog/grpclog.go:37 [core]Subchannel Connectivity change to CONNECTING {"system": "grpc", "grpc_log": true}
2022-01-26T13:59:54.429+0800 info grpclog/grpclog.go:37 [core]Subchannel picks a new address "localhost:50003" to connect {"system": "grpc", "grpc_log": true}
2022-01-26T13:59:54.429+0800 info grpclog/grpclog.go:37 [core]pickfirstBalancer: UpdateSubConnState: 0xc000171e40, {CONNECTING <nil>} {"system": "grpc", "grpc_log": true}
2022-01-26T13:59:54.429+0800 info grpclog/grpclog.go:37 [core]Channel Connectivity change to CONNECTING {"system": "grpc", "grpc_log": true}
2022-01-26T13:59:54.444+0800 info grpclog/grpclog.go:37 [core]Subchannel Connectivity change to READY {"system": "grpc", "grpc_log": true}
2022-01-26T13:59:54.444+0800 info grpclog/grpclog.go:37 [core]pickfirstBalancer: UpdateSubConnState: 0xc000171e40, {READY <nil>} {"system": "grpc", "grpc_log": true}
2022-01-26T13:59:54.444+0800 info grpclog/grpclog.go:37 [core]Channel Connectivity change to READY {"system": "grpc", "grpc_log": true}
2022-01-26T14:13:57.084+0800 info zap/server_interceptors.go:39 finished unary call with code OK {"grpc.start_time": "2022-01-26T14:13:57+08:00", "system": "grpc", "span.kind": "server", "grpc.service": "server.service.v1.DomainService", "grpc.method": "GetDomainRecord", "grpc.code": "OK", "grpc.time_ms": 1.3580000400543213}
2022-01-26T14:23:57.094+0800 info zap/server_interceptors.go:39 finished unary call with code OK {"grpc.start_time": "2022-01-26T14:23:57+08:00", "system": "grpc", "span.kind": "server", "grpc.service": "server.service.v1.DomainService", "grpc.method": "GetDomainRecord", "grpc.code": "OK", "grpc.time_ms": 1.2719999551773071}
2022-01-26T14:33:44.731+0800 warn grpclog/grpclog.go:46 [core]grpc: Server.Serve failed to create ServerTransport: connection error: desc = "ServerHandshake(\"47.103.37.203:36666\") failed: tls: client didn't provide a certificate" {"system": "grpc", "grpc_log": true}
2022-01-26T14:33:44.797+0800 warn grpclog/grpclog.go:46 [core]grpc: Server.Serve failed to create ServerTransport: connection error: desc = "ServerHandshake(\"47.103.37.203:36682\") failed: tls: first record does not look like a TLS handshake" {"system": "grpc", "grpc_log": true}
2022-01-26T14:33:44.897+0800 warn grpclog/grpclog.go:46 [core]grpc: Server.Serve failed to create ServerTransport: connection error: desc = "ServerHandshake(\"47.103.37.203:36696\") failed: tls: client didn't provide a certificate" {"system": "grpc", "grpc_log": true}
2022-01-26T14:33:44.954+0800 warn grpclog/grpclog.go:46 [core]grpc: Server.Serve failed to create ServerTransport: connection error: desc = "ServerHandshake(\"47.103.37.203:36710\") failed: tls: first record does not look like a TLS handshake" {"system": "grpc", "grpc_log": true}
2022-01-26T14:33:57.008+0800 info zap/server_interceptors.go:39 finished unary call with code OK {"grpc.start_time": "2022-01-26T14:33:57+08:00", "system": "grpc", "span.kind": "server", "grpc.service": "server.service.v1.DomainService", "grpc.method": "GetDomainRecord", "grpc.code": "OK", "grpc.time_ms": 1.125}