mirror of
https://github.com/certd/certd.git
synced 2026-04-03 14:10:54 +08:00
perf: dns-provider 支持bind9 ,support bind9
https://github.com/certd/certd/issues/683 https://github.com/certd/certd/discussions/668
This commit is contained in:
@@ -65,6 +65,20 @@ demoKeyId = '';
|
||||
encrypt: true, //该属性是否需要加密
|
||||
})
|
||||
demoKeySecret = '';
|
||||
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: '另外一个授权Id',//标题
|
||||
component: {
|
||||
name:"access-selector", //access选择组件
|
||||
vModel:"modelValue",
|
||||
type: "ssh", // access类型,让用户固定选择这种类型的access
|
||||
},
|
||||
required: true, //text组件可以省略
|
||||
})
|
||||
otherAccessId;
|
||||
|
||||
```
|
||||
|
||||
### 4. 实现测试方法
|
||||
@@ -93,7 +107,7 @@ async onTestRequest() {
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 获api接口示例 取域名列表,
|
||||
* api接口示例 获取域名列表,
|
||||
*/
|
||||
async GetDomainList(req: PageSearch): Promise<PageRes<DomainRecord>> {
|
||||
//输出日志必须使用ctx.logger
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 什么是插件转换工具
|
||||
|
||||
插件转换工具是一个用于将 Certd 插件转换为 YAML 配置文件的命令行工具。它可以分析单个插件文件,识别插件类型,并生成对应的 YAML 配置,方便插件的注册和管理。
|
||||
插件转换工具是一个用于将 Certd 插件转换为 YAML 配置文件的命令行工具。它可以分析单个插件文件,识别插件类型,并生成对应的 YAML 配置,可以让插件分发和在线注册。
|
||||
|
||||
## 工具位置
|
||||
|
||||
|
||||
@@ -80,7 +80,8 @@ certDomains!: string[];
|
||||
helper: 'demoAccess授权',
|
||||
component: {
|
||||
name: 'access-selector',
|
||||
type: 'demo', // 固定授权类型
|
||||
vModel:"modelValue",
|
||||
type: "demo", // access类型,让用户固定选择这种类型的access
|
||||
},
|
||||
// rules: [{ required: true, message: '此项必填' }],
|
||||
// required: true, // 必填
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { HttpClient, ILogger, utils } from "@certd/basic";
|
||||
import { IAccess, IServiceGetter, PageRes, PageSearch, Registrable } from "@certd/pipeline";
|
||||
import { IAccess, IAccessService, IServiceGetter, PageRes, PageSearch, Registrable } from "@certd/pipeline";
|
||||
|
||||
export type DnsProviderDefine = Registrable & {
|
||||
accessType: string;
|
||||
@@ -26,6 +26,7 @@ export type DnsProviderContext = {
|
||||
utils: typeof utils;
|
||||
domainParser: IDomainParser;
|
||||
serviceGetter: IServiceGetter;
|
||||
accessGetter?: IAccessService;
|
||||
};
|
||||
|
||||
export type DomainRecord = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { HttpClient, ILogger } from "@certd/basic";
|
||||
import { PageRes, PageSearch } from "@certd/pipeline";
|
||||
import { IAccessService, PageRes, PageSearch } from "@certd/pipeline";
|
||||
import punycode from "punycode.js";
|
||||
import { CreateRecordOptions, DnsProviderContext, DnsProviderDefine, DomainRecord, IDnsProvider, RemoveRecordOptions } from "./api.js";
|
||||
import { dnsProviderRegistry } from "./registry.js";
|
||||
@@ -59,6 +59,11 @@ export async function createDnsProvider(opts: { dnsProviderType: string; context
|
||||
if (dnsProviderDefine.deprecated) {
|
||||
context.logger.warn(dnsProviderDefine.deprecated);
|
||||
}
|
||||
|
||||
if (!context.accessGetter) {
|
||||
const accessGetter: IAccessService = await context.serviceGetter.get("accessService");
|
||||
context.accessGetter = accessGetter;
|
||||
}
|
||||
// @ts-ignore
|
||||
const dnsProvider: IDnsProvider = new DnsProviderClass();
|
||||
dnsProvider.setCtx(context);
|
||||
|
||||
@@ -72,14 +72,13 @@ import * as _ from "lodash-es";
|
||||
import { nanoid } from "nanoid";
|
||||
import PiStepForm from "../step-form/index.vue";
|
||||
import { Modal } from "ant-design-vue";
|
||||
import { CopyOutlined } from "@ant-design/icons-vue";
|
||||
import VDraggable from "vuedraggable";
|
||||
import { useUserStore } from "/@/store/user";
|
||||
import { useSettingStore } from "/@/store/settings";
|
||||
import { filter } from "lodash-es";
|
||||
export default {
|
||||
name: "PiTaskForm",
|
||||
components: { CopyOutlined, PiStepForm, VDraggable },
|
||||
components: { PiStepForm, VDraggable },
|
||||
props: {
|
||||
editMode: {
|
||||
type: Boolean,
|
||||
|
||||
@@ -805,9 +805,11 @@ export default defineComponent({
|
||||
let errorMessages: any = [];
|
||||
let errorIndex = 1;
|
||||
eachSteps(pp, (step: any, task: any, stage: any) => {
|
||||
if (step.disabled !== true) {
|
||||
stepIds.push(step.id);
|
||||
if (step.disabled === true) {
|
||||
return;
|
||||
}
|
||||
stepIds.push(step.id);
|
||||
|
||||
if (step.input) {
|
||||
for (const key in step.input) {
|
||||
const value = step.input[key];
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
SysSettingsService,
|
||||
ValidateException
|
||||
} from "@certd/lib-server";
|
||||
import { CnameProvider, CnameRecord } from "@certd/pipeline";
|
||||
import { CnameProvider, CnameRecord, IAccessService } from "@certd/pipeline";
|
||||
import { createDnsProvider, DomainParser, IDnsProvider } from "@certd/plugin-cert";
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
@@ -251,9 +251,9 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
return true;
|
||||
}
|
||||
|
||||
await this.getByDomain(bean.domain, bean.userId);
|
||||
await this.getByDomain(bean.domain, bean.userId,bean.projectId);
|
||||
|
||||
const taskService = this.taskServiceBuilder.create({ userId: bean.userId });
|
||||
const taskService = this.taskServiceBuilder.create({ userId: bean.userId, projectId: bean.projectId });
|
||||
const subDomainGetter = await taskService.getSubDomainsGetter();
|
||||
const domainParser = new DomainParser(subDomainGetter);
|
||||
|
||||
@@ -290,8 +290,9 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
});
|
||||
}
|
||||
|
||||
const serviceGetter = this.taskServiceBuilder.create({ userId: cnameProvider.userId });
|
||||
const access = await this.accessService.getById(cnameProvider.accessId, cnameProvider.userId);
|
||||
const serviceGetter = this.taskServiceBuilder.create({ userId: bean.userId, projectId: bean.projectId });
|
||||
const accessGetter:IAccessService = await serviceGetter.get("accessService");
|
||||
const access = await accessGetter.getById(cnameProvider.accessId);
|
||||
const context = { access, logger, http, utils, domainParser, serviceGetter };
|
||||
const dnsProvider: IDnsProvider = await createDnsProvider({
|
||||
dnsProviderType: cnameProvider.dnsProviderType,
|
||||
|
||||
83
packages/ui/certd-server/src/plugins/plugin-bind9/access.ts
Normal file
83
packages/ui/certd-server/src/plugins/plugin-bind9/access.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
|
||||
|
||||
@IsAccess({
|
||||
name: "bind9",
|
||||
title: "BIND9 DNS 授权",
|
||||
desc: "通过 SSH 连接到 BIND9 服务器,使用 nsupdate 命令管理 DNS 记录",
|
||||
icon: "clarity:host-line",
|
||||
})
|
||||
export class Bind9Access extends BaseAccess {
|
||||
@AccessInput({
|
||||
title: "SSH 授权",
|
||||
helper: "选择已配置的 SSH 授权",
|
||||
component: {
|
||||
name: "access-selector",
|
||||
type: "ssh",
|
||||
vModel:"modelValue"
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
sshAccessId!: string;
|
||||
|
||||
@AccessInput({
|
||||
title: "DNS 服务器地址",
|
||||
helper: "BIND9 DNS 服务器地址,用于 nsupdate 命令",
|
||||
component: {
|
||||
placeholder: "192.168.182.100",
|
||||
},
|
||||
})
|
||||
dnsServer: string;
|
||||
|
||||
@AccessInput({
|
||||
title: "DNS 服务器端口",
|
||||
helper: "BIND9 DNS 服务器端口,用于 nsupdate 命令,默认为 53",
|
||||
value: 53,
|
||||
component: {
|
||||
name: "a-input-number",
|
||||
placeholder: "53",
|
||||
},
|
||||
})
|
||||
dnsPort: number = 53;
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
type: "access",
|
||||
typeName: "bind9",
|
||||
action: "TestRequest",
|
||||
},
|
||||
mergeScript: `
|
||||
return {
|
||||
component:{
|
||||
form: ctx.compute(({form})=>{
|
||||
return form
|
||||
})
|
||||
},
|
||||
}
|
||||
`,
|
||||
helper: "点击测试 SSH 连接和 nsupdate 命令",
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
const { SshClient } = await import("../plugin-lib/ssh/ssh.js");
|
||||
const client = new SshClient(this.ctx.logger);
|
||||
|
||||
// 获取 SSH 授权配置
|
||||
const sshAccess = await this.ctx.accessService.getById(this.sshAccessId);
|
||||
if (!sshAccess) {
|
||||
throw new Error("SSH 授权不存在");
|
||||
}
|
||||
|
||||
// 测试 SSH 连接
|
||||
const script = ["echo 'SSH connection successful'", "which nsupdate", "exit"];
|
||||
await client.exec({
|
||||
connectConf: sshAccess,
|
||||
script: script,
|
||||
});
|
||||
return "SSH 连接成功,nsupdate 命令可用";
|
||||
}
|
||||
}
|
||||
|
||||
new Bind9Access();
|
||||
@@ -0,0 +1,127 @@
|
||||
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
|
||||
import { Bind9Access } from './access.js';
|
||||
import { SshClient } from '../plugin-lib/ssh/index.js';
|
||||
|
||||
export type Bind9Record = {
|
||||
fullRecord: string;
|
||||
value: string;
|
||||
type: string;
|
||||
domain: string;
|
||||
};
|
||||
|
||||
@IsDnsProvider({
|
||||
name: 'bind9',
|
||||
title: 'BIND9 DNS',
|
||||
desc: '通过 SSH 连接到 BIND9 服务器,使用 nsupdate 命令管理 DNS 记录',
|
||||
icon: 'clarity:host-line',
|
||||
accessType: 'bind9',
|
||||
})
|
||||
export class Bind9DnsProvider extends AbstractDnsProvider<Bind9Record> {
|
||||
access!: Bind9Access;
|
||||
sshClient!: SshClient;
|
||||
|
||||
async onInstance() {
|
||||
this.access = this.ctx.access as Bind9Access;
|
||||
this.sshClient = new SshClient(this.logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 SSH 连接配置
|
||||
*/
|
||||
private async getSshAccess() {
|
||||
// 从 accessService 获取 SSH 授权配置
|
||||
const sshAccess = await this.ctx.accessGetter.getById(this.access.sshAccessId);
|
||||
if (!sshAccess) {
|
||||
throw new Error('SSH 授权不存在');
|
||||
}
|
||||
return sshAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 nsupdate 命令
|
||||
*/
|
||||
private buildNsupdateCommand(commands: string[]): string {
|
||||
const { dnsServer, dnsPort } = this.access;
|
||||
const nsupdateScript = [
|
||||
`server ${dnsServer} ${dnsPort}`,
|
||||
...commands,
|
||||
"send",
|
||||
].join("\n");
|
||||
|
||||
// 使用 heredoc 方式执行 nsupdate
|
||||
return `nsupdate << 'EOF'
|
||||
${nsupdateScript}
|
||||
EOF`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 DNS 解析记录,用于验证域名所有权
|
||||
*/
|
||||
async createRecord(options: CreateRecordOptions): Promise<Bind9Record> {
|
||||
const { fullRecord, value, type, domain } = options;
|
||||
this.logger.info('添加域名解析:', fullRecord, value, type, domain);
|
||||
|
||||
// 构建 nsupdate 命令
|
||||
// 格式: update add <name> <ttl> <type> <value>
|
||||
const updateCommand = `update add ${fullRecord} 60 ${type} "${value}"`;
|
||||
const nsupdateCmd = this.buildNsupdateCommand([updateCommand]);
|
||||
|
||||
this.logger.info('执行 nsupdate 命令添加记录');
|
||||
|
||||
try {
|
||||
const sshAccess = await this.getSshAccess();
|
||||
await this.sshClient.exec({
|
||||
connectConf: sshAccess,
|
||||
script: nsupdateCmd,
|
||||
throwOnStdErr: true,
|
||||
});
|
||||
|
||||
this.logger.info(`添加域名解析成功: fullRecord=${fullRecord}, value=${value}`);
|
||||
} catch (error: any) {
|
||||
this.logger.error('添加域名解析失败:', error.message);
|
||||
throw new Error(`添加 DNS 记录失败: ${error.message}`);
|
||||
}
|
||||
|
||||
// 返回记录信息,用于后续删除
|
||||
const record: Bind9Record = {
|
||||
fullRecord,
|
||||
value,
|
||||
type,
|
||||
domain,
|
||||
};
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 DNS 解析记录,清理申请痕迹
|
||||
*/
|
||||
async removeRecord(options: RemoveRecordOptions<Bind9Record>): Promise<void> {
|
||||
const { fullRecord, value, type, domain } = options.recordRes;
|
||||
this.logger.info('删除域名解析:', fullRecord, value, type, domain);
|
||||
|
||||
// 构建 nsupdate 命令
|
||||
// 格式: update delete <name> <type> <value>
|
||||
const updateCommand = `update delete ${fullRecord} ${type} "${value}"`;
|
||||
const nsupdateCmd = this.buildNsupdateCommand([updateCommand]);
|
||||
|
||||
this.logger.info('执行 nsupdate 命令删除记录');
|
||||
|
||||
try {
|
||||
const sshAccess = await this.getSshAccess();
|
||||
await this.sshClient.exec({
|
||||
connectConf: sshAccess,
|
||||
script: nsupdateCmd,
|
||||
throwOnStdErr: false, // 删除时忽略错误(记录可能已不存在)
|
||||
});
|
||||
|
||||
this.logger.info(`删除域名解析成功: fullRecord=${fullRecord}, value=${value}`);
|
||||
} catch (error: any) {
|
||||
// 删除失败只记录警告,不抛出异常(清理操作不应影响主流程)
|
||||
this.logger.warn('删除域名解析时出现警告:', error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 实例化这个 provider,将其自动注册到系统中
|
||||
new Bind9DnsProvider();
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './dns-provider.js';
|
||||
export * from './access.js';
|
||||
Reference in New Issue
Block a user