Skip to content

新加apisix_version和修复3.7.0以上版本出现的bug #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
3 changes: 1 addition & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

FROM neilpang/acme.sh:3.0.6
FROM neilpang/acme.sh:3.0.7

COPY ./ /app/

2 changes: 1 addition & 1 deletion build.sh
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ input=$1

case $input in
build)
docker build --no-cache --rm --tag ${image} .
docker build --platform linux/amd64 --no-cache --rm --tag ${image} .
;;
publish)
repository=tmaize/${image}
2 changes: 2 additions & 0 deletions config.example.yml
Original file line number Diff line number Diff line change
@@ -7,9 +7,11 @@ verify_token: custom_token
# 必填,apisix admin host
apisix_host: http://apisix:9180
apisix_token: '******'
apisix_version: ''

# 必填,将自身服务注册到apisix的地址
self_apisix_host: http://apisix-acme:80
apisix_api_prefix: apisix_acme

# 必填,证书申请使用,证书快过期时会发送邮件
acme_mail: 'mail@example.com'
857 changes: 857 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@
"main": "index.js",
"type": "module",
"scripts": {
"dev": "node src/index.js -c config.local.yml"
"dev": "node src/index.js -c ./config.local.yml"
},
"keywords": [],
"author": "",
47 changes: 42 additions & 5 deletions src/apisix.js
Original file line number Diff line number Diff line change
@@ -20,12 +20,28 @@ async function getVersion() {
})

const server = headers['server'] || ''
const version = server.replace('APISIX/', '') || '0.0.0'
const version = server.replace("APISIX", "").replace('/', '').trim() || config.apisix_version
return version
}

function setErrorLoggerInterceptor() {
axios.interceptors.response.use(function(response) {
return response
}, function (error) {
if (error.config) {
console.log("AdminApi Error", error.config.method, error.config.url, error.config.data, error.response.data)
}
return Promise.reject(error)
}
)
}

// 把自己注册到 apisix
async function addSelfRoute() {

// 不一定要放到这里
setErrorLoggerInterceptor()

async function add() {
const id = `apisix_acme`
await axios.request({
@@ -36,7 +52,7 @@ async function addSelfRoute() {
},
url: `${config.apisix_host}/apisix/admin/routes/${id}`,
data: {
uri: '/apisix_acme/*',
uri: '/'+config.apisix_api_prefix+'/*',
name: id,
methods: ['GET', 'POST', 'OPTIONS', 'HEAD'],
priority: config.route_priority,
@@ -171,6 +187,10 @@ async function listSSL(sni) {
return
}

if (!item.labels || !item.labels.acme) {
return
}

const isSingle = item.snis.length == 1

let isWildcard = false
@@ -189,7 +209,7 @@ async function listSSL(sni) {
id: item.id,
domain,
validity_start: item.validity_start,
validity_end: item.validity_end
validity_end: item.validity_end,
})
}
})
@@ -216,10 +236,27 @@ async function applySSL(domain, sslInfo) {

for (let i = 0; i < idList.length; i++) {
const id = idList[i]
const save = {
snis: sslInfo.snis,
cert: sslInfo.cert,
key: sslInfo.key,
validity_start: sslInfo.validity_start,
validity_end: sslInfo.validity_end,
labels: {
acme: "ok",
},
}
if (compareVersions(version, '3.0.0') >= 0) {
await v3.setupSsl(id, sslInfo)
// https://github.com/apache/apisix/pull/10323
// https://apisix.apache.org/zh/blog/2023/11/21/release-apache-apisix-3.7.0/
// 修复 invalid configuration: additional properties forbidden, found validity_end
if (compareVersions(version, '3.7.0') >= 0) {
delete save.validity_start
delete save.validity_end
}
await v3.setupSsl(id, save)
} else {
await v2.setupSsl(id, sslInfo)
await v2.setupSsl(id, save)
}
}
}
4 changes: 3 additions & 1 deletion src/apisix/v2.js
Original file line number Diff line number Diff line change
@@ -29,7 +29,9 @@ async function sslList() {
id: item.id,
snis: item.snis,
validity_start: item.validity_start,
validity_end: item.validity_end
validity_end: item.validity_end,
labels: item['labels'],
client: item['client'],
})
})

6 changes: 4 additions & 2 deletions src/apisix/v3.js
Original file line number Diff line number Diff line change
@@ -29,7 +29,9 @@ async function sslList() {
id: item.id,
snis: item.snis,
validity_start: item.validity_start,
validity_end: item.validity_end
validity_end: item.validity_end,
labels: item['labels'],
client: item['client'],
})
})

@@ -53,5 +55,5 @@ async function setupSsl(id, data) {

export default {
sslList,
setupSsl
setupSsl,
}
3 changes: 2 additions & 1 deletion src/common.js
Original file line number Diff line number Diff line change
@@ -205,5 +205,6 @@ export default {
sendMsg,
sleep,
setupConsole,
toFixed
toFixed,
parseCA,
}
6 changes: 5 additions & 1 deletion src/config.js
Original file line number Diff line number Diff line change
@@ -8,8 +8,10 @@ const config = {
verify_token: '',
apisix_host: '',
apisix_token: '',
apisix_version: '', // 防止 apisix enable_server_tokens = false 导致无法获取准确版本号而出错
self_apisix_host: '',
route_priority: 999,
apisix_api_prefix: "",
route_priority: 99,
acme_mail: '',
ding_ding_token: '',
renew_day: 0,
@@ -38,7 +40,9 @@ function init() {
config.verify_token = String(f.verify_token || '')
config.apisix_host = f.apisix_host || ''
config.apisix_token = f.apisix_token || ''
config.apisix_version = f.apisix_version || '0.0.0'
config.self_apisix_host = f.self_apisix_host || ''
config.apisix_api_prefix = f.apisix_api_prefix || 'apisix_acme'
config.acme_mail = f.acme_mail || ''
config.route_priority = f.route_priority === 0 ? 0 : Number(f.route_priority) || 999
config.ding_ding_token = f.ding_ding_token || ''
110 changes: 59 additions & 51 deletions src/router.js
Original file line number Diff line number Diff line change
@@ -5,67 +5,75 @@ import url from 'url'
import config from './config.js'
import task from './task.js'

const router = new KoaRouter()
export default function() {
const router = new KoaRouter()

const DIR_NAME = path.dirname(url.fileURLToPath(import.meta.url))
const DIR_NAME = path.dirname(url.fileURLToPath(import.meta.url))

// 查询任务
router.get('/apisix_acme/task_status', async (ctx, next) => {
if (ctx.state.verifyToken != config.verify_token) {
ctx.body = { code: 401, message: 'invalid VERIFY-TOKEN' }
return
}
// 查询任务
router.get('/'+config.apisix_api_prefix+'/task_status', async (ctx, next) => {
if (ctx.state.verifyToken != config.verify_token) {
ctx.body = { code: 401, message: 'invalid VERIFY-TOKEN' }
return
}

const domain = ctx.query.domain
if (!domain) {
ctx.body = { code: 400, message: 'domain is required' }
return
}
const status = await task.queryTask(domain)
ctx.body = { code: 200, data: status }
})
const domain = ctx.query.domain
if (!domain) {
ctx.body = { code: 400, message: 'domain is required' }
return
}
const status = await task.queryTask(domain)
ctx.body = { code: 200, data: status }
})

// 创建任务
router.post('/apisix_acme/task_create', async (ctx, next) => {
if (ctx.state.verifyToken != config.verify_token) {
ctx.body = { code: 401, message: 'invalid VERIFY-TOKEN' }
return
}
// 创建任务
router.post('/'+config.apisix_api_prefix+'/task_create', async (ctx, next) => {
if (ctx.state.verifyToken != config.verify_token) {
ctx.body = { code: 401, message: 'invalid VERIFY-TOKEN' }
return
}

const body = ctx.request.body || {}
const domain = body.domain
const serviceList = body.serviceList || []
const mail = body.mail || config.acme_mail
const force = body.force === true
const body = ctx.request.body || {}
const domain = body.domain
const serviceList = body.serviceList || []
const mail = body.mail || config.acme_mail
const force = body.force === true

if (!domain) {
ctx.body = { code: 400, message: 'domain is required' }
return
}
if (!domain) {
ctx.body = { code: 400, message: 'domain is required' }
return
}

const result = await task.createTask(domain, mail, serviceList, force)
ctx.body = result
})
const result = await task.createTask(domain, mail, serviceList, force)
ctx.body = result
})

// acme text verify
// 主要是处理 /.well-known/acme-challenge/random 这个请求
router.get('(.*)', (ctx, next) => {
let file = ctx.params[0]
// 工具页面
router.get('/'+config.apisix_api_prefix+'/tool.html', async (ctx, next) => {
const filePath = path.join(DIR_NAME, 'www', "tool.html")
ctx.body = fs.readFileSync(filePath, 'utf8')
})

const filePath = path.join(DIR_NAME, 'www', file)
// acme text verify
// 主要是处理 /.well-known/acme-challenge/random 这个请求
router.get('/.well-known/acme-challenge/(.*)', (ctx, next) => {
let file = ctx.params[0]

if (!fs.existsSync(filePath)) {
ctx.status = 404
return
}
const filePath = path.join(DIR_NAME, 'www', ".well-known", "acme-challenge", file)

const state = fs.statSync(filePath)
if (state.isDirectory()) {
ctx.status = 404
return
}
if (!fs.existsSync(filePath)) {
ctx.status = 404
return
}

ctx.body = fs.readFileSync(filePath, 'utf8')
})
const state = fs.statSync(filePath)
if (state.isDirectory()) {
ctx.status = 404
return
}

export default router
ctx.body = fs.readFileSync(filePath, 'utf8')
})

return router
}
61 changes: 31 additions & 30 deletions src/server.js
Original file line number Diff line number Diff line change
@@ -4,37 +4,38 @@ import common from './common.js'
import config from './config.js'
import router from './router.js'

const app = new Koa()

app.use(async (ctx, next) => {
const origin = ctx.header.origin || ctx.origin
ctx.set('Access-Control-Allow-Origin', origin)
ctx.set('Access-Control-Allow-Methods', '*')
ctx.set('Access-Control-Allow-Credentials', 'true')
ctx.set('Access-Control-Expose-Headers', 'Content-Disposition')
ctx.set('Access-Control-Allow-Headers', 'Content-Type,X-CSRF-Token,Authorization,Token,Check-Token')
ctx.set('Access-Control-Max-Age', '60')

if (ctx.method === 'OPTIONS') {
ctx.status = 204
return
}

ctx.state.verifyToken = ctx.header['verify-token'] || ctx.header.verify_token || ''

try {
await next()
} catch (error) {
const message = error.message || error
ctx.body = { code: 500, message }
common.sendMsg(`接口异常: ${message}\n\n` + '```\n' + error.stack + '\n```')
}
})

app.use(koaBody({}))
app.use(router.routes())

// 启动服务
async function start() {
const app = new Koa()

app.use(async (ctx, next) => {
const origin = ctx.header.origin || ctx.origin
ctx.set('Access-Control-Allow-Origin', origin)
ctx.set('Access-Control-Allow-Methods', '*')
ctx.set('Access-Control-Allow-Credentials', 'true')
ctx.set('Access-Control-Expose-Headers', 'Content-Disposition')
ctx.set('Access-Control-Allow-Headers', 'Content-Type,X-CSRF-Token,Authorization,Token,Check-Token')
ctx.set('Access-Control-Max-Age', '60')

if (ctx.method === 'OPTIONS') {
ctx.status = 204
return
}

ctx.state.verifyToken = ctx.header['verify-token'] || ctx.header.verify_token || ''

try {
await next()
} catch (error) {
const message = error.message || error
ctx.body = { code: 500, message }
common.sendMsg(`接口异常: ${message}\n\n` + '```\n' + error.stack + '\n```')
}
})

app.use(koaBody({}))
app.use(router().routes())

return new Promise(function (resolve, reject) {
const server = app.listen(config.port, '0.0.0.0')
server.on('error', reject)
18 changes: 18 additions & 0 deletions src/task.js
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ import schedule from 'node-schedule'
import apisix from './apisix.js'
import common from './common.js'
import config from './config.js'
import fs from 'fs'

// 运行中 {"status":"running", domain, mail, serviceList, error, force}
// 上次运行失败 {"status":"error", domain, mail, serviceList, error, force}
@@ -32,6 +33,23 @@ async function createTask(domain, mail, serviceList, force) {
console.log('跳过任务', '已在执行', domain)
return { code: 200, message: '证书申请中,等待片刻', data: { status: 'running', domain } }
case 'success':
// 等待新方案
// 修复3.7.0以上版本无法正常获取到validity_end
try {
if (!task.validity_end) {
const dc = common.getDomainConfig(domain)
if (dc && fs.existsSync(dc.cerPath)) {
const info = common.parseCA(dc.cerPath, dc.keyPath)
if (info) {
task.validity_start = info.validity_start
task.validity_end = info.validity_end
}
}
}
} catch(error) {
console.log("parseCA Error", error)
}

const left_seconds = task.validity_end - parseInt(Date.now() / 1000)
const end_date = moment(task.validity_end * 1000).format('YYYY-MM-DD HH:mm:ss')

21 changes: 12 additions & 9 deletions src/www/apisix_acme/tool.html → src/www/tool.html
Original file line number Diff line number Diff line change
@@ -36,23 +36,23 @@
<div id="app">
<form @submit.prevent="submit">
<label>
<span>URL</span>
<span>APIURL</span>
<input type="text" required v-model="state.url" />
</label>
<label>
<span>Verify Token</span>
<span>校验Token</span>
<input type="text" required v-model="state.verifyToken" />
</label>
<label>
<span>Domain</span>
<span>申请域名</span>
<input type="text" required v-model="state.domain" />
</label>
<label>
<span>ACME Mail</span>
<span>邮箱</span>
<input type="text" v-model="state.acmeMail" />
</label>
<label>
<span>Force</span>
<span>强制提交</span>
<input type="checkbox" v-model="state.force" />
</label>
<input type="submit" :value="state.loading?'请求中':'提交'" :disabled="state.loading" />
@@ -68,16 +68,19 @@
setup() {
const state = reactive({
loading: false,
url: location.origin + '/apisix_acme/task_create',
verifyToken: 'your token',
domain: location.hostname,
acmeMail: '',
url: location.origin + location.pathname.replace("/tool.html", "") + '/task_create',
verifyToken: localStorage.getItem('verifyToken'),
domain: localStorage.getItem('domain'),
acmeMail: localStorage.getItem('acmeMail'),
force: false,
result: ['等待提交']
})

const submit = async () => {
state.loading = true
localStorage.setItem("domain", state.domain)
localStorage.setItem("verifyToken", state.verifyToken)
localStorage.setItem("acmeMail", state.acmeMail)
await axios
.request({
method: 'POST',
1,013 changes: 509 additions & 504 deletions yarn.lock

Large diffs are not rendered by default.